update
This commit is contained in:
parent
4186cd0caf
commit
df4c3dd46f
51
go.mod
51
go.mod
@ -3,35 +3,36 @@ module management
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.3.898
|
||||
github.com/alexedwards/scs/v2 v2.8.0
|
||||
github.com/a-h/templ v0.3.960
|
||||
github.com/alexedwards/scs/v2 v2.9.0
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/drhin/logger v0.0.0-20250417021954-aa33afe047bc
|
||||
github.com/evanw/esbuild v0.25.5
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/hibiken/asynq v0.25.1
|
||||
github.com/jackc/pgx/v5 v5.7.5
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/justinas/nosurf v1.2.0
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||
github.com/mojocn/base64Captcha v1.3.8
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/redis/go-redis/v9 v9.10.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/redis/go-redis/v9 v9.16.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/zhang2092/browser v0.0.2
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/sync v0.15.0
|
||||
gorm.io/datatypes v1.2.5
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/sync v0.17.0
|
||||
gorm.io/datatypes v1.2.7
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
gorm.io/gorm v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -39,9 +40,9 @@ require (
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
@ -53,20 +54,18 @@ require (
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/image v0.28.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/image v0.32.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
)
|
||||
|
||||
124
go.sum
124
go.sum
@ -2,16 +2,18 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/a-h/templ v0.3.898 h1:g9oxL/dmM6tvwRe2egJS8hBDQTncokbMoOFk1oJMX7s=
|
||||
github.com/a-h/templ v0.3.898/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ=
|
||||
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||
github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM=
|
||||
github.com/a-h/templ v0.3.960/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
|
||||
github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90=
|
||||
github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
@ -30,35 +32,32 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
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.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
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/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
@ -73,8 +72,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@ -111,36 +110,35 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
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/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
||||
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
|
||||
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@ -152,16 +150,18 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||
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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@ -175,8 +175,6 @@ 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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=
|
||||
@ -184,8 +182,8 @@ 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/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -197,8 +195,8 @@ 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.17.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
@ -216,12 +214,10 @@ 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/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@ -229,10 +225,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
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=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@ -243,15 +237,15 @@ 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=
|
||||
gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I=
|
||||
gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=
|
||||
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
|
||||
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc=
|
||||
gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw=
|
||||
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
||||
@ -82,12 +82,11 @@ func (a *app) login(w http.ResponseWriter, r *http.Request) {
|
||||
//err := a.userService.Login(ctx, &req)
|
||||
risk, err := a.authService.Authenticate(ctx, req)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
a.render.JSONErr(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Println(risk)
|
||||
log.Println("risk:", risk)
|
||||
|
||||
a.render.JSONOk(w, "login successfully")
|
||||
default:
|
||||
|
||||
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -11,8 +12,8 @@ import (
|
||||
systemService "management/internal/erpserver/service/v1"
|
||||
"management/internal/erpserver/templ/system/config"
|
||||
"management/internal/pkg/convertor"
|
||||
"management/internal/pkg/database"
|
||||
"management/internal/pkg/render"
|
||||
"management/internal/pkg/sqldb"
|
||||
)
|
||||
|
||||
type app struct {
|
||||
@ -91,7 +92,7 @@ func (a *app) save(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
err := a.configService.Create(ctx, arg)
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err) {
|
||||
if errors.Is(err, sqldb.ErrDBDuplicatedEntry) {
|
||||
a.render.JSONErr(w, "数据已存在")
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -10,9 +11,9 @@ import (
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/erpserver/templ/system/menu"
|
||||
"management/internal/pkg/convertor"
|
||||
"management/internal/pkg/database"
|
||||
"management/internal/pkg/mid"
|
||||
"management/internal/pkg/render"
|
||||
"management/internal/pkg/sqldb"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@ -144,7 +145,7 @@ func (a *app) save(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
err := a.menuService.Create(ctx, arg)
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err) {
|
||||
if errors.Is(err, sqldb.ErrDBDuplicatedEntry) {
|
||||
a.render.JSONErr(w, "菜单已存在")
|
||||
return
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type DepartmentRepository interface {
|
||||
Initialize(ctx context.Context) error
|
||||
Create(ctx context.Context, obj *Department) error
|
||||
Initialize(ctx context.Context) (*Department, error)
|
||||
Create(ctx context.Context, obj *Department) (*Department, error)
|
||||
Update(ctx context.Context, obj *Department) error
|
||||
Get(ctx context.Context, id int32) (*Department, error)
|
||||
All(ctx context.Context) ([]*Department, error)
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
|
||||
type RoleRepository interface {
|
||||
Initialize(ctx context.Context) (*Role, error)
|
||||
Create(ctx context.Context, obj *Role) error
|
||||
Create(ctx context.Context, obj *Role) (*Role, error)
|
||||
Update(ctx context.Context, obj *Role) error
|
||||
Get(ctx context.Context, id int32) (*Role, error)
|
||||
GetByVip(ctx context.Context, vip bool) (*Role, error)
|
||||
|
||||
@ -11,8 +11,8 @@ import (
|
||||
|
||||
type UserRepository interface {
|
||||
Initialize(ctx context.Context, departId, roleId int32) error
|
||||
Create(ctx context.Context, obj *User) error
|
||||
Update(ctx context.Context, obj *User) error
|
||||
Create(ctx context.Context, obj *User) (*User, error)
|
||||
Update(ctx context.Context, obj *User) (*User, error)
|
||||
Get(ctx context.Context, id int32) (*User, error)
|
||||
GetByEmail(ctx context.Context, email string) (*User, error)
|
||||
All(ctx context.Context) ([]*User, error)
|
||||
|
||||
@ -41,7 +41,7 @@ func (s *Seed) Run() error {
|
||||
}
|
||||
|
||||
// 部门
|
||||
err := s.departmentRepository.Initialize(ctx)
|
||||
depart, err := s.departmentRepository.Initialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -53,7 +53,7 @@ func (s *Seed) Run() error {
|
||||
}
|
||||
|
||||
// 用户
|
||||
if err := s.userRepository.Initialize(ctx, 0, role.ID); err != nil {
|
||||
if err := s.userRepository.Initialize(ctx, depart.ID, role.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@ -3,13 +3,14 @@ package config
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"management/internal/erpserver/model/dto"
|
||||
"management/internal/erpserver/model/system"
|
||||
"management/internal/erpserver/repository"
|
||||
"management/internal/pkg/database"
|
||||
"management/internal/pkg/know/pearadmin"
|
||||
"management/internal/pkg/sqldb"
|
||||
|
||||
@ -32,7 +33,7 @@ func NewStore(db *repository.Store, log *logger.Logger) system.ConfigRepository
|
||||
func (s *store) Initialize(ctx context.Context) error {
|
||||
_, err := s.GetByKey(ctx, pearadmin.PearKey)
|
||||
if err != nil {
|
||||
if database.IsNoRows(err) {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
b, e := json.Marshal(pearadmin.PearJson)
|
||||
if e != nil {
|
||||
return e
|
||||
|
||||
@ -26,14 +26,14 @@ func NewStore(db *repository.Store, log *logger.Logger) system.DepartmentReposit
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) Initialize(ctx context.Context) error {
|
||||
func (s *store) Initialize(ctx context.Context) (*system.Department, error) {
|
||||
count, err := s.Count(ctx, dto.SearchDto{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
obj := system.Department{
|
||||
obj := &system.Department{
|
||||
Name: "公司",
|
||||
ParentID: 0,
|
||||
ParentPath: ",0,",
|
||||
@ -42,21 +42,33 @@ func (s *store) Initialize(ctx context.Context) error {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return s.Create(ctx, &obj)
|
||||
return s.Create(ctx, obj)
|
||||
}
|
||||
return nil
|
||||
return s.Get(ctx, 1)
|
||||
}
|
||||
|
||||
func (s *store) Create(ctx context.Context, obj *system.Department) error {
|
||||
func (s *store) Create(ctx context.Context, obj *system.Department) (*system.Department, error) {
|
||||
//goland:noinspection ALL
|
||||
const q = `
|
||||
INSERT INTO sys_department (
|
||||
name, parent_id, parent_path, status, sort
|
||||
) VALUES (
|
||||
:name, :parent_id, :parent_path, :status, :sort
|
||||
);`
|
||||
) RETURNING *;`
|
||||
|
||||
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
|
||||
data := map[string]any{
|
||||
"name": obj.Name,
|
||||
"parent_id": obj.ParentID,
|
||||
"parent_path": obj.ParentPath,
|
||||
"status": obj.Status,
|
||||
"sort": obj.Sort,
|
||||
}
|
||||
|
||||
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func (s *store) Update(ctx context.Context, obj *system.Department) error {
|
||||
|
||||
@ -30,9 +30,24 @@ func (s *store) Create(ctx context.Context, obj *system.Menu) (*system.Menu, err
|
||||
name, display_name, url, type, parent_id, parent_path, avatar, style, visible, is_list, status, sort
|
||||
) VALUES (
|
||||
:name, :display_name, :url, :type, :parent_id, :parent_path, :avatar, :style, :visible, :is_list, :status, :sort
|
||||
);`
|
||||
) RETURNING *;`
|
||||
|
||||
err := sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
|
||||
data := map[string]any{
|
||||
"name": obj.Name,
|
||||
"display_name": obj.DisplayName,
|
||||
"url": obj.Url,
|
||||
"type": obj.Type,
|
||||
"parent_id": obj.ParentID,
|
||||
"parent_path": obj.ParentPath,
|
||||
"avatar": obj.Avatar,
|
||||
"style": obj.Style,
|
||||
"visible": obj.Visible,
|
||||
"is_list": obj.IsList,
|
||||
"status": obj.Status,
|
||||
"sort": obj.Sort,
|
||||
}
|
||||
|
||||
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -32,7 +32,8 @@ func (s *store) Initialize(ctx context.Context) (*system.Role, error) {
|
||||
return nil, err
|
||||
}
|
||||
if count == 0 {
|
||||
obj := system.Role{
|
||||
var err error
|
||||
obj := &system.Role{
|
||||
Name: "Company",
|
||||
DisplayName: "公司",
|
||||
Vip: false,
|
||||
@ -42,11 +43,12 @@ func (s *store) Initialize(ctx context.Context) (*system.Role, error) {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if err := s.Create(ctx, &obj); err != nil {
|
||||
obj, err = s.Create(ctx, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj1 := system.Role{
|
||||
obj1 := &system.Role{
|
||||
Name: "SuperAdmin",
|
||||
DisplayName: "超级管理员",
|
||||
Vip: true,
|
||||
@ -56,26 +58,42 @@ func (s *store) Initialize(ctx context.Context) (*system.Role, error) {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if err := s.Create(ctx, &obj1); err != nil {
|
||||
obj1, err = s.Create(ctx, obj1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &obj1, nil
|
||||
return obj1, nil
|
||||
}
|
||||
|
||||
return s.GetByVip(ctx, true)
|
||||
|
||||
}
|
||||
|
||||
func (s *store) Create(ctx context.Context, obj *system.Role) error {
|
||||
func (s *store) Create(ctx context.Context, obj *system.Role) (*system.Role, error) {
|
||||
//goland:noinspection ALL
|
||||
const q = `
|
||||
INSERT INTO sys_role (
|
||||
name, display_name, parent_id, parent_path, vip, status, sort
|
||||
) VALUES (
|
||||
:name, :display_name, :parent_id, :parent_path, :vip, :status, :sort
|
||||
)`
|
||||
) RETURNING *`
|
||||
|
||||
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
|
||||
data := map[string]any{
|
||||
"name": obj.Name,
|
||||
"display_name": obj.DisplayName,
|
||||
"parent_id": obj.ParentID,
|
||||
"parent_path": obj.ParentPath,
|
||||
"vip": obj.Vip,
|
||||
"status": obj.Status,
|
||||
"sort": obj.Sort,
|
||||
}
|
||||
|
||||
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (s *store) Update(ctx context.Context, obj *system.Role) error {
|
||||
|
||||
@ -67,25 +67,44 @@ func (s *store) Initialize(ctx context.Context, departId, roleId int32) error {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return s.Create(ctx, &user)
|
||||
_, err = s.Create(ctx, &user)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) Create(ctx context.Context, obj *system.User) error {
|
||||
func (s *store) Create(ctx context.Context, obj *system.User) (*system.User, error) {
|
||||
//goland:noinspection ALL
|
||||
const q = `
|
||||
INSERT INTO sys_user (
|
||||
uuid, email, username, hashed_password, salt, avatar, gender, department_id, role_id, status
|
||||
) VALUES (
|
||||
:uuid, :email, :username, :hashed_password, :salt, :avatar, :gender, :department_id, :role_id, :status
|
||||
);`
|
||||
) RETURNING *;`
|
||||
|
||||
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
|
||||
data := map[string]any{
|
||||
"uuid": obj.Uuid.String(),
|
||||
"email": obj.Email,
|
||||
"username": obj.Username,
|
||||
"hashed_password": obj.HashedPassword,
|
||||
"salt": obj.Salt,
|
||||
"avatar": obj.Avatar,
|
||||
"gender": obj.Gender,
|
||||
"department_id": obj.DepartmentID,
|
||||
"role_id": obj.RoleID,
|
||||
"status": obj.Status,
|
||||
}
|
||||
|
||||
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (s *store) Update(ctx context.Context, obj *system.User) error {
|
||||
func (s *store) Update(ctx context.Context, obj *system.User) (*system.User, error) {
|
||||
//goland:noinspection ALL
|
||||
const q = `
|
||||
UPDATE sys_user
|
||||
@ -99,9 +118,28 @@ func (s *store) Update(ctx context.Context, obj *system.User) error {
|
||||
status = :status,
|
||||
change_password_at = :change_password_at,
|
||||
updated_at = :updated_at
|
||||
WHERE id = :id;`
|
||||
WHERE id = :id RETURNING *;`
|
||||
|
||||
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
|
||||
data := map[string]any{
|
||||
"email": obj.Email,
|
||||
"username": obj.Username,
|
||||
"hashed_password": obj.HashedPassword,
|
||||
"avatar": obj.Avatar,
|
||||
"gender": obj.Gender,
|
||||
"department_id": obj.DepartmentID,
|
||||
"role_id": obj.RoleID,
|
||||
"status": obj.Status,
|
||||
"change_password_at": obj.ChangePasswordAt,
|
||||
"updated_at": obj.UpdatedAt,
|
||||
"id": obj.ID,
|
||||
}
|
||||
|
||||
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (s *store) Get(ctx context.Context, id int32) (*system.User, error) {
|
||||
|
||||
@ -167,7 +167,7 @@ func (a *Auth) validateUser(ctx context.Context, email, password string) (*syste
|
||||
|
||||
user.Role, err = a.roleService.Get(ctx, user.RoleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.New("账号没有配置角色, 请联系管理员")
|
||||
}
|
||||
|
||||
if user.Role == nil || user.Role.ID == 0 {
|
||||
|
||||
@ -12,10 +12,10 @@ import (
|
||||
"management/internal/erpserver/model/system"
|
||||
"management/internal/erpserver/model/view"
|
||||
"management/internal/erpserver/service/util"
|
||||
"management/internal/erpserver/service/v1"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/convertor"
|
||||
"management/internal/pkg/database"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/sqldb"
|
||||
)
|
||||
|
||||
type departmentService struct {
|
||||
@ -58,9 +58,9 @@ func (s *departmentService) Create(ctx context.Context, req *form.Department) er
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := s.repo.Create(ctx, arg)
|
||||
_, err := s.repo.Create(ctx, arg)
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err) {
|
||||
if errors.Is(err, sqldb.ErrDBDuplicatedEntry) {
|
||||
return errors.New("部门已存在")
|
||||
}
|
||||
return err
|
||||
|
||||
@ -12,10 +12,10 @@ import (
|
||||
"management/internal/erpserver/model/system"
|
||||
"management/internal/erpserver/model/view"
|
||||
"management/internal/erpserver/service/util"
|
||||
"management/internal/erpserver/service/v1"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/convertor"
|
||||
"management/internal/pkg/database"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/sqldb"
|
||||
)
|
||||
|
||||
type roleService struct {
|
||||
@ -60,9 +60,9 @@ func (s *roleService) Create(ctx context.Context, req *form.Role) error {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := s.repo.Create(ctx, arg)
|
||||
_, err := s.repo.Create(ctx, arg)
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err) {
|
||||
if errors.Is(err, sqldb.ErrDBDuplicatedEntry) {
|
||||
return errors.New("角色名称已存在")
|
||||
}
|
||||
return err
|
||||
|
||||
@ -10,11 +10,11 @@ import (
|
||||
"management/internal/erpserver/model/form"
|
||||
"management/internal/erpserver/model/system"
|
||||
"management/internal/erpserver/model/view"
|
||||
"management/internal/erpserver/service/v1"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/crypto"
|
||||
"management/internal/pkg/database"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/rand"
|
||||
"management/internal/pkg/sqldb"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
@ -75,9 +75,9 @@ func (s *userService) Create(ctx context.Context, req *form.User) error {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = s.repo.Create(ctx, user)
|
||||
_, err = s.repo.Create(ctx, user)
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err) {
|
||||
if errors.Is(err, sqldb.ErrDBDuplicatedEntry) {
|
||||
return errors.New("用户已经存在")
|
||||
}
|
||||
return err
|
||||
@ -106,7 +106,9 @@ func (s *userService) Update(ctx context.Context, req *form.User) error {
|
||||
user.HashedPassword = hashedPassword
|
||||
user.ChangePasswordAt = time.Now()
|
||||
}
|
||||
return s.repo.Update(ctx, user)
|
||||
|
||||
_, err = s.repo.Update(ctx, user)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *userService) All(ctx context.Context) ([]*system.User, error) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package auth
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package base
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package component
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package component
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package home
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package home
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package auditlog
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package category
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package config
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package config
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package department
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
@ -61,7 +61,7 @@ func Edit(ctx context.Context, item *system.Department) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<input type=\"hidden\" id=\"id\" name=\"id\" value=\"\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<input type=\"hidden\" id=\"id\" name=\"id\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -70,11 +70,11 @@ func Edit(ctx context.Context, item *system.Department) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/department/edit.templ`, Line: 21, Col: 75}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(` ` + templ_7745c5c3_Var3))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "><div class=\"layui-tab layui-tab-card\"><ul class=\"layui-tab-title\"><li class=\"layui-this\">基础信息</li><li>其它</li></ul><div class=\"layui-tab-content\"><!-- 基础信息 --><div class=\"layui-tab-item layui-show\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><div class=\"layui-tab layui-tab-card\"><ul class=\"layui-tab-title\"><li class=\"layui-this\">基础信息</li><li>其它</li></ul><div class=\"layui-tab-content\"><!-- 基础信息 --><div class=\"layui-tab-item layui-show\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package department
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package loginlog
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package menu
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package menu
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package role
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package role
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package role
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package user
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package user
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.960
|
||||
package user
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
@ -1,15 +1,48 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
// import (
|
||||
// "database/sql"
|
||||
// "errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
// "github.com/jackc/pgx/v5/pgconn"
|
||||
// "gorm.io/gorm"
|
||||
// )
|
||||
|
||||
func IsUniqueViolation(err error) bool {
|
||||
return errors.Is(err, gorm.ErrDuplicatedKey)
|
||||
}
|
||||
// func IsGORMUniqueViolation(err error) bool {
|
||||
// return errors.Is(err, gorm.ErrDuplicatedKey)
|
||||
// }
|
||||
|
||||
func IsNoRows(err error) bool {
|
||||
return errors.Is(err, gorm.ErrRecordNotFound)
|
||||
}
|
||||
// func IsGORMNoRows(err error) bool {
|
||||
// return errors.Is(err, gorm.ErrRecordNotFound)
|
||||
// }
|
||||
|
||||
// // ****************** errors ******************
|
||||
|
||||
// const (
|
||||
// foreignKeyViolation = "23503"
|
||||
// uniqueViolation = "23505"
|
||||
// )
|
||||
|
||||
// // var ErrUniqueViolation = &pgconn.PgError{
|
||||
// // Code: UniqueViolation,
|
||||
// // }
|
||||
|
||||
// func ErrorCode(err error) string {
|
||||
// var pgErr *pgconn.PgError
|
||||
// if errors.As(err, &pgErr) {
|
||||
// return pgErr.Code
|
||||
// }
|
||||
// return ""
|
||||
// }
|
||||
|
||||
// func IsUniqueViolation(err error) bool {
|
||||
// var pgErr *pgconn.PgError
|
||||
// if errors.As(err, &pgErr) {
|
||||
// return pgErr.Code == uniqueViolation
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
// func IsNoRows(err error) bool {
|
||||
// return errors.Is(err, sql.ErrNoRows)
|
||||
// }
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/h2non/filetype"
|
||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||
)
|
||||
@ -20,82 +21,94 @@ const (
|
||||
|
||||
var ErrUnsupported = errors.New("文件格式不支持")
|
||||
|
||||
type FileType int
|
||||
type Type int
|
||||
|
||||
const (
|
||||
ALL FileType = 0
|
||||
IMG FileType = 1
|
||||
ALL Type = 0
|
||||
IMG Type = 1
|
||||
)
|
||||
|
||||
func UploadFilename(filepath string, t FileType) (string, error) {
|
||||
fileOpen, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer fileOpen.Close()
|
||||
|
||||
fileBytes, err := io.ReadAll(fileOpen)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to read file")
|
||||
}
|
||||
//func UploadFilename(filepath string, t Type) (string, error) {
|
||||
// fileOpen, err := os.Open(filepath)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// defer func(fileOpen *os.File) {
|
||||
// _ = fileOpen.Close()
|
||||
// }(fileOpen)
|
||||
//
|
||||
// fileBytes, err := io.ReadAll(fileOpen)
|
||||
// if err != nil {
|
||||
// return "", errors.New("failed to read file")
|
||||
// }
|
||||
//
|
||||
// if t == IMG {
|
||||
// // 判断是不是图片
|
||||
// if !filetype.IsImage(fileBytes) {
|
||||
// return "", ErrUnsupported
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// kind, err := filetype.Match(fileBytes)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
//
|
||||
// if kind == filetype.Unknown {
|
||||
// return "", ErrUnsupported
|
||||
// }
|
||||
//
|
||||
// // 使用 filetype 判断类型后已经去读了一些bytes了
|
||||
// // 要恢复文件读取位置
|
||||
// _, err = fileOpen.Seek(0, io.SeekStart)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
//
|
||||
// dir := GetPath()
|
||||
// exist, _ := Exists(dir)
|
||||
// if !exist {
|
||||
// if err := Mkdir(dir); err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// filename := GenFilename(kind.Extension)
|
||||
// fullPath := path.Join(dir, filename)
|
||||
// f, err := os.Create(fullPath)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// defer func(f *os.File) {
|
||||
// _ = f.Close()
|
||||
// }(f)
|
||||
//
|
||||
// _, err = io.Copy(f, fileOpen)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
//
|
||||
// return "/" + fullPath, nil
|
||||
//}
|
||||
|
||||
func UploadFile(file *multipart.FileHeader, t Type) (string, error) {
|
||||
if t == IMG {
|
||||
// 判断是不是图片
|
||||
if !filetype.IsImage(fileBytes) {
|
||||
return "", ErrUnsupported
|
||||
if file.Size > MaxImageSize {
|
||||
return "", errors.New("failed to receive images too large")
|
||||
}
|
||||
}
|
||||
|
||||
kind, err := filetype.Match(fileBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if kind == filetype.Unknown {
|
||||
return "", ErrUnsupported
|
||||
}
|
||||
|
||||
// 使用 filetype 判断类型后已经去读了一些bytes了
|
||||
// 要恢复文件读取位置
|
||||
_, err = fileOpen.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir := GetPath()
|
||||
exist, _ := Exists(dir)
|
||||
if !exist {
|
||||
if err := Mkdir(dir); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
if file.Size > MaxFileSize {
|
||||
return "", errors.New("failed to receive file too large")
|
||||
}
|
||||
}
|
||||
|
||||
filename := GenFilename(kind.Extension)
|
||||
path := path.Join(dir, filename)
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, fileOpen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "/" + path, nil
|
||||
}
|
||||
|
||||
func UploadFile(file *multipart.FileHeader, t FileType) (string, error) {
|
||||
if file.Size > MaxFileSize {
|
||||
return "", errors.New("failed to receive file too large")
|
||||
}
|
||||
|
||||
fileOpen, err := file.Open()
|
||||
if err != nil {
|
||||
return "", errors.New("fialed to open file")
|
||||
return "", errors.New("failed to open file")
|
||||
}
|
||||
defer fileOpen.Close()
|
||||
defer func(fileOpen multipart.File) {
|
||||
_ = fileOpen.Close()
|
||||
}(fileOpen)
|
||||
|
||||
fileBytes, err := io.ReadAll(fileOpen)
|
||||
if err != nil {
|
||||
@ -134,19 +147,21 @@ func UploadFile(file *multipart.FileHeader, t FileType) (string, error) {
|
||||
}
|
||||
|
||||
filename := GenFilename(kind.Extension)
|
||||
path := path.Join(dir, filename)
|
||||
f, err := os.Create(path)
|
||||
fullPath := path.Join(dir, filename)
|
||||
f, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func(f *os.File) {
|
||||
_ = f.Close()
|
||||
}(f)
|
||||
|
||||
_, err = io.Copy(f, fileOpen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "/" + path, nil
|
||||
return "/" + fullPath, nil
|
||||
}
|
||||
|
||||
func GetPath() string {
|
||||
@ -154,6 +169,9 @@ func GetPath() string {
|
||||
}
|
||||
|
||||
func GenFilename(ext string) string {
|
||||
id, _ := gonanoid.New()
|
||||
id, err := gonanoid.New()
|
||||
if err != nil {
|
||||
return uuid.New().String()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", id, ext)
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ func Audit(sess session.Manager, log *logger.Logger, task tasks.TaskDistributor)
|
||||
opts := []asynq.Option{
|
||||
asynq.MaxRetry(10),
|
||||
asynq.ProcessIn(1 * time.Second),
|
||||
asynq.Queue(tasks.QueueCritical),
|
||||
asynq.Queue(tasks.QueueDefault),
|
||||
}
|
||||
|
||||
c, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
|
||||
920
internal/pkg/sqldb/dbarray/dbarray.go
Normal file
920
internal/pkg/sqldb/dbarray/dbarray.go
Normal file
@ -0,0 +1,920 @@
|
||||
/*
|
||||
Code taken from https://github.com/lib/pq
|
||||
|
||||
Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so, subject
|
||||
to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package dbarray provides support for database array types.
|
||||
package dbarray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var typeByteSlice = reflect.TypeOf([]byte{})
|
||||
var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||
var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||
|
||||
// Array returns the optimal driver.Valuer and sql.Scanner for an array or
|
||||
// slice of any dimension.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401}))
|
||||
//
|
||||
// var x []sql.NullInt64
|
||||
// db.QueryRow(`SELECT ARRAY[235, 401]`).Scan(pq.Array(&x))
|
||||
//
|
||||
// Scanning multi-dimensional arrays is not supported. Arrays where the lower
|
||||
// bound is not one (such as `[0:0]={1}') are not supported.
|
||||
func Array(a any) interface {
|
||||
driver.Valuer
|
||||
sql.Scanner
|
||||
} {
|
||||
switch a := a.(type) {
|
||||
case []bool:
|
||||
return (*Bool)(&a)
|
||||
case []float64:
|
||||
return (*Float64)(&a)
|
||||
case []float32:
|
||||
return (*Float32)(&a)
|
||||
case []int64:
|
||||
return (*Int64)(&a)
|
||||
case []int32:
|
||||
return (*Int32)(&a)
|
||||
case []string:
|
||||
return (*String)(&a)
|
||||
case [][]byte:
|
||||
return (*Bytea)(&a)
|
||||
|
||||
case *[]bool:
|
||||
return (*Bool)(a)
|
||||
case *[]float64:
|
||||
return (*Float64)(a)
|
||||
case *[]float32:
|
||||
return (*Float32)(a)
|
||||
case *[]int64:
|
||||
return (*Int64)(a)
|
||||
case *[]int32:
|
||||
return (*Int32)(a)
|
||||
case *[]string:
|
||||
return (*String)(a)
|
||||
case *[][]byte:
|
||||
return (*Bytea)(a)
|
||||
}
|
||||
|
||||
return Generic{a}
|
||||
}
|
||||
|
||||
// Delimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||
// to override the array delimiter used by Generic.
|
||||
type Delimiter interface {
|
||||
// Delimiter returns the delimiter character(s) for this element's type.
|
||||
Delimiter() string
|
||||
}
|
||||
|
||||
// Bool represents a one-dimensional array of the PostgreSQL boolean type.
|
||||
type Bool []bool
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Bool) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to Bool", src)
|
||||
}
|
||||
|
||||
func (a *Bool) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Bool")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Bool, len(elems))
|
||||
for i, v := range elems {
|
||||
if len(v) != 1 {
|
||||
return fmt.Errorf("database: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
}
|
||||
switch v[0] {
|
||||
case 't':
|
||||
b[i] = true
|
||||
case 'f':
|
||||
b[i] = false
|
||||
default:
|
||||
return fmt.Errorf("database: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Bool) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be exactly two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1+2*n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
b[2*i] = ','
|
||||
if a[i] {
|
||||
b[1+2*i] = 't'
|
||||
} else {
|
||||
b[1+2*i] = 'f'
|
||||
}
|
||||
}
|
||||
|
||||
b[0] = '{'
|
||||
b[2*n] = '}'
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Bytea represents a one-dimensional array of the PostgreSQL bytea type.
|
||||
type Bytea [][]byte
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Bytea) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to Bytea", src)
|
||||
}
|
||||
|
||||
func (a *Bytea) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Bytea")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Bytea, len(elems))
|
||||
for i, v := range elems {
|
||||
b[i], err = parseBytea(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse bytea array index %d: %s", i, err.Error())
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface. It uses the "hex" format which
|
||||
// is only supported on PostgreSQL 9.0 or newer.
|
||||
func (a Bytea) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||
// 3*N bytes of hex formatting, and N-1 bytes of delimiters.
|
||||
size := 1 + 6*n
|
||||
for _, x := range a {
|
||||
size += hex.EncodedLen(len(x))
|
||||
}
|
||||
|
||||
b := make([]byte, size)
|
||||
|
||||
for i, s := 0, b; i < n; i++ {
|
||||
o := copy(s, `,"\\x`)
|
||||
o += hex.Encode(s[o:], a[i])
|
||||
s[o] = '"'
|
||||
s = s[o+1:]
|
||||
}
|
||||
|
||||
b[0] = '{'
|
||||
b[size-1] = '}'
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Float64 represents a one-dimensional array of the PostgreSQL double
|
||||
// precision type.
|
||||
type Float64 []float64
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Float64) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to Float64", src)
|
||||
}
|
||||
|
||||
func (a *Float64) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Float64")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Float64, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i], err = strconv.ParseFloat(string(v), 64); err != nil {
|
||||
return fmt.Errorf("database: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Float64) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+2*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = strconv.AppendFloat(b, a[0], 'f', -1, 64)
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = strconv.AppendFloat(b, a[i], 'f', -1, 64)
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Float32 represents a one-dimensional array of the PostgreSQL double
|
||||
// precision type.
|
||||
type Float32 []float32
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Float32) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to Float32", src)
|
||||
}
|
||||
|
||||
func (a *Float32) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Float32")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Float32, len(elems))
|
||||
for i, v := range elems {
|
||||
var x float64
|
||||
if x, err = strconv.ParseFloat(string(v), 32); err != nil {
|
||||
return fmt.Errorf("database: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
b[i] = float32(x)
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Float32) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+2*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 32)
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 32)
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Generic implements the driver.Valuer and sql.Scanner interfaces for
|
||||
// an array or slice of any dimension.
|
||||
type Generic struct{ A any }
|
||||
|
||||
func (Generic) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||
var assign func([]byte, reflect.Value) error
|
||||
var del = ","
|
||||
|
||||
// TODO calculate the assign function for other types
|
||||
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||
{
|
||||
if reflect.PointerTo(rt).Implements(typeSQLScanner) {
|
||||
// dest is always addressable because it is an element of a slice.
|
||||
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||
ss := dest.Addr().Interface().(sql.Scanner)
|
||||
if src == nil {
|
||||
err = ss.Scan(nil)
|
||||
} else {
|
||||
err = ss.Scan(src)
|
||||
}
|
||||
return
|
||||
}
|
||||
goto FoundType
|
||||
}
|
||||
|
||||
assign = func([]byte, reflect.Value) error {
|
||||
return fmt.Errorf("database: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||
}
|
||||
}
|
||||
|
||||
FoundType:
|
||||
|
||||
if ad, ok := reflect.Zero(rt).Interface().(Delimiter); ok {
|
||||
del = ad.Delimiter()
|
||||
}
|
||||
|
||||
return rt, assign, del
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a Generic) Scan(src any) error {
|
||||
dpv := reflect.ValueOf(a.A)
|
||||
switch {
|
||||
case dpv.Kind() != reflect.Ptr:
|
||||
return fmt.Errorf("database: destination %T is not a pointer to array or slice", a.A)
|
||||
case dpv.IsNil():
|
||||
return fmt.Errorf("database: destination %T is nil", a.A)
|
||||
}
|
||||
|
||||
dv := dpv.Elem()
|
||||
switch dv.Kind() {
|
||||
case reflect.Slice:
|
||||
case reflect.Array:
|
||||
default:
|
||||
return fmt.Errorf("database: destination %T is not a pointer to array or slice", a.A)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src, dv)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src), dv)
|
||||
case nil:
|
||||
if dv.Kind() == reflect.Slice {
|
||||
dv.Set(reflect.Zero(dv.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to %s", src, dv.Type())
|
||||
}
|
||||
|
||||
func (a Generic) scanBytes(src []byte, dv reflect.Value) error {
|
||||
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||
dims, elems, err := parseArray(src, []byte(del))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO allow multidimensional
|
||||
|
||||
if len(dims) > 1 {
|
||||
return fmt.Errorf("database: scanning from multidimensional ARRAY%s is not implemented",
|
||||
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||
}
|
||||
|
||||
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||
if len(dims) == 0 {
|
||||
dims = append(dims, 0)
|
||||
}
|
||||
|
||||
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
case reflect.Array:
|
||||
if rt.Len() != dims[i] {
|
||||
return fmt.Errorf("database: cannot convert ARRAY%s to %s",
|
||||
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||
}
|
||||
default:
|
||||
// TODO handle multidimensional
|
||||
}
|
||||
}
|
||||
|
||||
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||
for i, e := range elems {
|
||||
if err := assign(e, values.Index(i)); err != nil {
|
||||
return fmt.Errorf("database: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle multidimensional
|
||||
|
||||
switch dv.Kind() {
|
||||
case reflect.Slice:
|
||||
dv.Set(values.Slice(0, dims[0]))
|
||||
case reflect.Array:
|
||||
for i := 0; i < dims[0]; i++ {
|
||||
dv.Index(i).Set(values.Index(i))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Generic) Value() (driver.Value, error) {
|
||||
if a.A == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(a.A)
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice:
|
||||
if rv.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
case reflect.Array:
|
||||
default:
|
||||
return nil, fmt.Errorf("database: Unable to convert %T to array", a.A)
|
||||
}
|
||||
|
||||
if n := rv.Len(); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 0, 1+2*n)
|
||||
|
||||
b, _, err := appendArray(b, rv, n)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Int64 represents a one-dimensional array of the PostgreSQL integer types.
|
||||
type Int64 []int64
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Int64) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to Int64", src)
|
||||
}
|
||||
|
||||
func (a *Int64) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Int64")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Int64, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil {
|
||||
return fmt.Errorf("database: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Int64) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+2*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = strconv.AppendInt(b, a[0], 10)
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = strconv.AppendInt(b, a[i], 10)
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// Int32 represents a one-dimensional array of the PostgreSQL integer types.
|
||||
type Int32 []int32
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *Int32) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to Int32", src)
|
||||
}
|
||||
|
||||
func (a *Int32) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "Int32")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(Int32, len(elems))
|
||||
for i, v := range elems {
|
||||
x, err := strconv.ParseInt(string(v), 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("database: parsing array element index %d: %v", i, err)
|
||||
}
|
||||
b[i] = int32(x)
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a Int32) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, N bytes of values,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+2*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = strconv.AppendInt(b, int64(a[0]), 10)
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = strconv.AppendInt(b, int64(a[i]), 10)
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// String represents a one-dimensional array of the PostgreSQL character types.
|
||||
type String []string
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (a *String) Scan(src any) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
return a.scanBytes(src)
|
||||
case string:
|
||||
return a.scanBytes([]byte(src))
|
||||
case nil:
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("database: cannot convert %T to String", src)
|
||||
}
|
||||
|
||||
func (a *String) scanBytes(src []byte) error {
|
||||
elems, err := scanLinearArray(src, []byte{','}, "String")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *a != nil && len(elems) == 0 {
|
||||
*a = (*a)[:0]
|
||||
} else {
|
||||
b := make(String, len(elems))
|
||||
for i, v := range elems {
|
||||
if b[i] = string(v); v == nil {
|
||||
return fmt.Errorf("database: parsing array element index %d: cannot convert nil to string", i)
|
||||
}
|
||||
}
|
||||
*a = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (a String) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if n := len(a); n > 0 {
|
||||
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||
// and N-1 bytes of delimiters.
|
||||
b := make([]byte, 1, 1+3*n)
|
||||
b[0] = '{'
|
||||
|
||||
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, ',')
|
||||
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
||||
}
|
||||
|
||||
return string(append(b, '}')), nil
|
||||
}
|
||||
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
// appendArray appends rv to the buffer, returning the extended buffer and
|
||||
// the delimiter used between elements.
|
||||
//
|
||||
// It panics when n <= 0 or rv's Kind is not reflect.Array nor reflect.Slice.
|
||||
func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) {
|
||||
var del string
|
||||
var err error
|
||||
|
||||
b = append(b, '{')
|
||||
|
||||
if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil {
|
||||
return b, del, err
|
||||
}
|
||||
|
||||
for i := 1; i < n; i++ {
|
||||
b = append(b, del...)
|
||||
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
|
||||
return b, del, err
|
||||
}
|
||||
}
|
||||
|
||||
return append(b, '}'), del, nil
|
||||
}
|
||||
|
||||
// appendArrayElement appends rv to the buffer, returning the extended buffer
|
||||
// and the delimiter to use before the next element.
|
||||
//
|
||||
// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted
|
||||
// using driver.DefaultParameterConverter and the resulting []byte or string
|
||||
// is double-quoted.
|
||||
//
|
||||
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||
func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) {
|
||||
if k := rv.Kind(); k == reflect.Array || k == reflect.Slice {
|
||||
if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) {
|
||||
if n := rv.Len(); n > 0 {
|
||||
return appendArray(b, rv, n)
|
||||
}
|
||||
|
||||
return b, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
var del = ","
|
||||
var err error
|
||||
var iv = rv.Interface()
|
||||
|
||||
if ad, ok := iv.(Delimiter); ok {
|
||||
del = ad.Delimiter()
|
||||
}
|
||||
|
||||
if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil {
|
||||
return b, del, err
|
||||
}
|
||||
|
||||
switch v := iv.(type) {
|
||||
case nil:
|
||||
return append(b, "NULL"...), del, nil
|
||||
case []byte:
|
||||
return appendArrayQuotedBytes(b, v), del, nil
|
||||
case string:
|
||||
return appendArrayQuotedBytes(b, []byte(v)), del, nil
|
||||
}
|
||||
|
||||
b, err = appendValue(b, iv)
|
||||
return b, del, err
|
||||
}
|
||||
|
||||
func appendArrayQuotedBytes(b, v []byte) []byte {
|
||||
b = append(b, '"')
|
||||
for {
|
||||
i := bytes.IndexAny(v, `"\`)
|
||||
if i < 0 {
|
||||
b = append(b, v...)
|
||||
break
|
||||
}
|
||||
if i > 0 {
|
||||
b = append(b, v[:i]...)
|
||||
}
|
||||
b = append(b, '\\', v[i])
|
||||
v = v[i+1:]
|
||||
}
|
||||
return append(b, '"')
|
||||
}
|
||||
|
||||
func appendValue(b []byte, v driver.Value) ([]byte, error) {
|
||||
return append(b, encode(nil, v, 0)...), nil
|
||||
}
|
||||
|
||||
// parseArray extracts the dimensions and elements of an array represented in
|
||||
// text format. Only representations emitted by the backend are supported.
|
||||
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
||||
// is case-sensitive.
|
||||
//
|
||||
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
||||
var depth, i int
|
||||
|
||||
if len(src) < 1 || src[0] != '{' {
|
||||
return nil, nil, fmt.Errorf("database: unable to parse array; expected %q at offset %d", '{', 0)
|
||||
}
|
||||
|
||||
Open:
|
||||
for i < len(src) {
|
||||
switch src[i] {
|
||||
case '{':
|
||||
depth++
|
||||
i++
|
||||
case '}':
|
||||
elems = make([][]byte, 0)
|
||||
goto Close
|
||||
default:
|
||||
break Open
|
||||
}
|
||||
}
|
||||
dims = make([]int, i)
|
||||
|
||||
Element:
|
||||
for i < len(src) {
|
||||
switch src[i] {
|
||||
case '{':
|
||||
if depth == len(dims) {
|
||||
break Element
|
||||
}
|
||||
depth++
|
||||
dims[depth-1] = 0
|
||||
i++
|
||||
case '"':
|
||||
var elem = []byte{}
|
||||
var escape bool
|
||||
for i++; i < len(src); i++ {
|
||||
if escape {
|
||||
elem = append(elem, src[i])
|
||||
escape = false
|
||||
} else {
|
||||
switch src[i] {
|
||||
default:
|
||||
elem = append(elem, src[i])
|
||||
case '\\':
|
||||
escape = true
|
||||
case '"':
|
||||
elems = append(elems, elem)
|
||||
i++
|
||||
break Element
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
for start := i; i < len(src); i++ {
|
||||
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||
elem := src[start:i]
|
||||
if len(elem) == 0 {
|
||||
return nil, nil, fmt.Errorf("database: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
if bytes.Equal(elem, []byte("NULL")) {
|
||||
elem = nil
|
||||
}
|
||||
elems = append(elems, elem)
|
||||
break Element
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i < len(src) {
|
||||
if bytes.HasPrefix(src[i:], del) && depth > 0 {
|
||||
dims[depth-1]++
|
||||
i += len(del)
|
||||
goto Element
|
||||
} else if src[i] == '}' && depth > 0 {
|
||||
dims[depth-1]++
|
||||
depth--
|
||||
i++
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("database: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
}
|
||||
|
||||
Close:
|
||||
for i < len(src) {
|
||||
if src[i] == '}' && depth > 0 {
|
||||
depth--
|
||||
i++
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("database: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||
}
|
||||
}
|
||||
if depth > 0 {
|
||||
err = fmt.Errorf("database: unable to parse array; expected %q at offset %d", '}', i)
|
||||
}
|
||||
if err == nil {
|
||||
for _, d := range dims {
|
||||
if (len(elems) % d) != 0 {
|
||||
err = fmt.Errorf("database: multidimensional arrays must have elements with matching dimensions")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
||||
dims, elems, err := parseArray(src, del)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dims) > 1 {
|
||||
return nil, fmt.Errorf("database: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||
}
|
||||
return elems, err
|
||||
}
|
||||
235
internal/pkg/sqldb/dbarray/encode.go
Normal file
235
internal/pkg/sqldb/dbarray/encode.go
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
Code taken from https://github.com/lib/pq
|
||||
|
||||
Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so, subject
|
||||
to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package dbarray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
infinityTSEnabledAlready = "database: infinity timestamp enabled already"
|
||||
infinityTSNegativeMustBeSmaller = "database: infinity timestamp: negative value must be smaller (before) than positive"
|
||||
)
|
||||
|
||||
var infinityTSEnabled = false
|
||||
var infinityTSNegative time.Time
|
||||
var infinityTSPositive time.Time
|
||||
|
||||
type parameterStatus struct {
|
||||
// server version in the same format as server_version_num, or 0 if unavailable.
|
||||
serverVersion int
|
||||
}
|
||||
|
||||
// EnableInfinityTS controls the handling of Postgres' "-infinity" and
|
||||
// "infinity" "timestamp"s.
|
||||
//
|
||||
// If EnableInfinityTS is not called, "-infinity" and "infinity" will return
|
||||
// []byte("-infinity") and []byte("infinity") respectively, and potentially
|
||||
// cause error "sql: Scan error on column index 0: unsupported driver -> Scan
|
||||
// pair: []uint8 -> *time.Time", when scanning into a time.Time value.
|
||||
//
|
||||
// Once EnableInfinityTS has been called, all connections created using this
|
||||
// driver will decode Postgres' "-infinity" and "infinity" for "timestamp",
|
||||
// "timestamp with time zone" and "date" types to the predefined minimum and
|
||||
// maximum times, respectively. When encoding time.Time values, any time which
|
||||
// equals or precedes the predefined minimum time will be encoded to
|
||||
// "-infinity". Any values at or past the maximum time will similarly be
|
||||
// encoded to "infinity".
|
||||
//
|
||||
// If EnableInfinityTS is called with negative >= positive, it will panic.
|
||||
// Calling EnableInfinityTS after a connection has been established results in
|
||||
// undefined behavior. If EnableInfinityTS is called more than once, it will
|
||||
// panic.
|
||||
func EnableInfinityTS(negative time.Time, positive time.Time) {
|
||||
if infinityTSEnabled {
|
||||
panic(infinityTSEnabledAlready)
|
||||
}
|
||||
if !negative.Before(positive) {
|
||||
panic(infinityTSNegativeMustBeSmaller)
|
||||
}
|
||||
infinityTSEnabled = true
|
||||
infinityTSNegative = negative
|
||||
infinityTSPositive = positive
|
||||
}
|
||||
|
||||
func encode(parameterStatus *parameterStatus, x any, oid int) []byte {
|
||||
const oidBytea = 17
|
||||
|
||||
switch v := x.(type) {
|
||||
case int64:
|
||||
return strconv.AppendInt(nil, v, 10)
|
||||
case float64:
|
||||
return strconv.AppendFloat(nil, v, 'f', -1, 64)
|
||||
case []byte:
|
||||
if oid == oidBytea {
|
||||
return encodeBytea(parameterStatus.serverVersion, v)
|
||||
}
|
||||
|
||||
return v
|
||||
case string:
|
||||
if oid == oidBytea {
|
||||
return encodeBytea(parameterStatus.serverVersion, []byte(v))
|
||||
}
|
||||
|
||||
return []byte(v)
|
||||
case bool:
|
||||
return strconv.AppendBool(nil, v)
|
||||
case time.Time:
|
||||
return formatTS(v)
|
||||
|
||||
default:
|
||||
errorf("encode: unknown type for %T", v)
|
||||
}
|
||||
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// formatTS formats t into a format postgres understands.
|
||||
func formatTS(t time.Time) []byte {
|
||||
if infinityTSEnabled {
|
||||
// t <= -infinity : ! (t > -infinity)
|
||||
if !t.After(infinityTSNegative) {
|
||||
return []byte("-infinity")
|
||||
}
|
||||
// t >= infinity : ! (!t < infinity)
|
||||
if !t.Before(infinityTSPositive) {
|
||||
return []byte("infinity")
|
||||
}
|
||||
}
|
||||
return formatTimestamp(t)
|
||||
}
|
||||
|
||||
// formatTimestamp formats t into Postgres' text format for timestamps.
|
||||
func formatTimestamp(t time.Time) []byte {
|
||||
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
|
||||
// minus sign preferred by Go.
|
||||
// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
|
||||
bc := false
|
||||
if t.Year() <= 0 {
|
||||
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
|
||||
t = t.AddDate((-t.Year())*2+1, 0, 0)
|
||||
bc = true
|
||||
}
|
||||
b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00"))
|
||||
|
||||
_, offset := t.Zone()
|
||||
offset %= 60
|
||||
if offset != 0 {
|
||||
// RFC3339Nano already printed the minus sign
|
||||
if offset < 0 {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
b = append(b, ':')
|
||||
if offset < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
b = strconv.AppendInt(b, int64(offset), 10)
|
||||
}
|
||||
|
||||
if bc {
|
||||
b = append(b, " BC"...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func errorf(s string, args ...any) {
|
||||
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||
}
|
||||
|
||||
// Parse a bytea value received from the server. Both "hex" and the legacy
|
||||
// "escape" format are supported.
|
||||
func parseBytea(s []byte) (result []byte, err error) {
|
||||
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
|
||||
// bytea_output = hex
|
||||
s = s[2:] // trim off leading "\\x"
|
||||
result = make([]byte, hex.DecodedLen(len(s)))
|
||||
_, err := hex.Decode(result, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// bytea_output = escape
|
||||
for len(s) > 0 {
|
||||
if s[0] == '\\' {
|
||||
// escaped '\\'
|
||||
if len(s) >= 2 && s[1] == '\\' {
|
||||
result = append(result, '\\')
|
||||
s = s[2:]
|
||||
continue
|
||||
}
|
||||
|
||||
// '\\' followed by an octal number
|
||||
if len(s) < 4 {
|
||||
return nil, fmt.Errorf("invalid bytea sequence %v", s)
|
||||
}
|
||||
r, err := strconv.ParseUint(string(s[1:4]), 8, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
|
||||
}
|
||||
result = append(result, byte(r))
|
||||
s = s[4:]
|
||||
} else {
|
||||
// We hit an unescaped, raw byte. Try to read in as many as
|
||||
// possible in one go.
|
||||
i := bytes.IndexByte(s, '\\')
|
||||
if i == -1 {
|
||||
result = append(result, s...)
|
||||
break
|
||||
}
|
||||
result = append(result, s[:i]...)
|
||||
s = s[i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func encodeBytea(serverVersion int, v []byte) (result []byte) {
|
||||
if serverVersion >= 90000 {
|
||||
// Use the hex format if we know that the server supports it
|
||||
result = make([]byte, 2+hex.EncodedLen(len(v)))
|
||||
result[0] = '\\'
|
||||
result[1] = 'x'
|
||||
hex.Encode(result[2:], v)
|
||||
} else {
|
||||
// .. or resort to "escape"
|
||||
for _, b := range v {
|
||||
if b == '\\' {
|
||||
result = append(result, '\\', '\\')
|
||||
} else if b < 0x20 || b > 0x7e {
|
||||
result = append(result, []byte(fmt.Sprintf("\\%03o", b))...)
|
||||
} else {
|
||||
result = append(result, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
// Package sqldb provides support for access the database.
|
||||
package sqldb
|
||||
|
||||
import (
|
||||
@ -17,17 +18,64 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// lib/pq errorCodeNames
|
||||
// https://github.com/lib/pq/blob/master/error.go#L178
|
||||
const (
|
||||
uniqueViolation = "23505"
|
||||
undefinedTable = "42P01"
|
||||
)
|
||||
|
||||
// Set of error variables for CRUD operations.
|
||||
var (
|
||||
ErrDBNotFound = sql.ErrNoRows
|
||||
ErrDBDuplicatedEntry = errors.New("duplicated entry")
|
||||
ErrUndefinedTable = errors.New("undefined table")
|
||||
)
|
||||
|
||||
// Config is the required properties to use the database.
|
||||
//type Config struct {
|
||||
// User string
|
||||
// Password string
|
||||
// Host string
|
||||
// Name string
|
||||
// Schema string
|
||||
// MaxIdleConns int
|
||||
// MaxOpenConns int
|
||||
// DisableTLS bool
|
||||
//}
|
||||
|
||||
// Open knows how to open a database connection based on the configuration.
|
||||
//func Open(cfg Config) (*sqlx.DB, error) {
|
||||
// sslMode := "require"
|
||||
// if cfg.DisableTLS {
|
||||
// sslMode = "disable"
|
||||
// }
|
||||
//
|
||||
// q := make(url.Values)
|
||||
// q.Set("sslmode", sslMode)
|
||||
// q.Set("timezone", "utc")
|
||||
// if cfg.Schema != "" {
|
||||
// q.Set("search_path", cfg.Schema)
|
||||
// }
|
||||
//
|
||||
// u := url.URL{
|
||||
// Scheme: "postgres",
|
||||
// User: url.UserPassword(cfg.User, cfg.Password),
|
||||
// Host: cfg.Host,
|
||||
// Path: cfg.Name,
|
||||
// RawQuery: q.Encode(),
|
||||
// }
|
||||
//
|
||||
// db, err := sqlx.Open("pgx", u.String())
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// db.SetMaxIdleConns(cfg.MaxIdleConns)
|
||||
// db.SetMaxOpenConns(cfg.MaxOpenConns)
|
||||
//
|
||||
// return db, nil
|
||||
//}
|
||||
|
||||
type Config struct {
|
||||
User string
|
||||
Password string
|
||||
@ -82,26 +130,66 @@ func NewDB(config *config.Config, log *logger.Logger) (*sqlx.DB, func(), error)
|
||||
return db, cleanup, nil
|
||||
}
|
||||
|
||||
// StatusCheck returns nil if it can successfully talk to the database. It
|
||||
// returns a non-nil error otherwise.
|
||||
func StatusCheck(ctx context.Context, db *sqlx.DB) error {
|
||||
|
||||
// If the user doesn't give us a deadline set 1 second.
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
for attempts := 1; ; attempts++ {
|
||||
if err := db.Ping(); err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(attempts) * 100 * time.Millisecond)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// Run a simple query to determine connectivity.
|
||||
// Running this query forces a round trip through the database.
|
||||
const q = `SELECT TRUE`
|
||||
var tmp bool
|
||||
return db.QueryRowContext(ctx, q).Scan(&tmp)
|
||||
}
|
||||
|
||||
// ExecContext is a helper function to execute a CUD operation with
|
||||
// logging and tracing.
|
||||
func ExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string) error {
|
||||
return NamedExecContext(ctx, log, db, query, struct{}{})
|
||||
}
|
||||
|
||||
// NamedExecContext is a helper function to execute a CUD operation with
|
||||
// logging and tracing where field replacement is necessary.
|
||||
func NamedExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any) (err error) {
|
||||
q := queryString(query, data)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
switch data.(type) {
|
||||
case struct{}:
|
||||
log.Error("database.NamedExecContext (data is struct)", err,
|
||||
zap.String("query", query),
|
||||
zap.Any("ERROR", err))
|
||||
log.Info("database.NamedExecContext", zap.String("query", q), zap.Int("type", 6), zap.Error(err))
|
||||
default:
|
||||
log.Error("database.NamedExecContext", err,
|
||||
zap.String("query", query),
|
||||
zap.Any("ERROR", err))
|
||||
log.Info("database.NamedExecContext", zap.String("query", q), zap.Int("type", 5), zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := sqlx.NamedExecContext(ctx, db, query, data); err != nil {
|
||||
var pgError *pgconn.PgError
|
||||
if errors.As(err, &pgError) {
|
||||
switch pgError.Code {
|
||||
var pqerr *pgconn.PgError
|
||||
if errors.As(err, &pqerr) {
|
||||
switch pqerr.Code {
|
||||
case undefinedTable:
|
||||
return ErrUndefinedTable
|
||||
case uniqueViolation:
|
||||
@ -114,71 +202,73 @@ func NamedExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContex
|
||||
return nil
|
||||
}
|
||||
|
||||
func NamedQueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest any) (err error) {
|
||||
q := queryString(query, data)
|
||||
rows, err := sqlx.NamedQueryContext(ctx, db, q, data)
|
||||
if err != nil {
|
||||
var pqErr *pgconn.PgError
|
||||
if errors.As(err, &pqErr) && pqErr.Code == undefinedTable {
|
||||
return ErrUndefinedTable
|
||||
}
|
||||
log.Error("NamedQueryStruct NamedQueryContext error", err,
|
||||
zap.String("query", q),
|
||||
zap.Any("data", data),
|
||||
)
|
||||
return err
|
||||
}
|
||||
defer func(rows *sqlx.Rows) {
|
||||
err := rows.Close()
|
||||
if err != nil {
|
||||
log.Error("rows close error", err)
|
||||
}
|
||||
}(rows)
|
||||
|
||||
if !rows.Next() {
|
||||
return ErrDBNotFound
|
||||
}
|
||||
|
||||
if err := rows.StructScan(dest); err != nil {
|
||||
log.Error("NamedQueryStruct StructScan error", err,
|
||||
zap.String("query", q),
|
||||
zap.Any("data", data),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
// QuerySlice is a helper function for executing queries that return a
|
||||
// collection of data to be unmarshalled into a slice.
|
||||
func QuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, dest *[]T) error {
|
||||
return namedQuerySlice(ctx, log, db, query, struct{}{}, dest, false)
|
||||
}
|
||||
|
||||
func NamedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T) (err error) {
|
||||
// NamedQuerySlice is a helper function for executing queries that return a
|
||||
// collection of data to be unmarshalled into a slice where field replacement is
|
||||
// necessary.
|
||||
func NamedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T) error {
|
||||
return namedQuerySlice(ctx, log, db, query, data, dest, false)
|
||||
}
|
||||
|
||||
// NamedQuerySliceUsingIn is a helper function for executing queries that return
|
||||
// a collection of data to be unmarshalled into a slice where field replacement
|
||||
// is necessary. Use this if the query has an IN clause.
|
||||
func NamedQuerySliceUsingIn[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T) error {
|
||||
return namedQuerySlice(ctx, log, db, query, data, dest, true)
|
||||
}
|
||||
|
||||
func namedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T, withIn bool) (err error) {
|
||||
q := queryString(query, data)
|
||||
rows, err := sqlx.NamedQueryContext(ctx, db, q, data)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Info("database.NamedQuerySlice", zap.String("query", q), zap.Int("type", 6), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var rows *sqlx.Rows
|
||||
|
||||
switch withIn {
|
||||
case true:
|
||||
rows, err = func() (*sqlx.Rows, error) {
|
||||
named, args, err := sqlx.Named(query, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query, args, err := sqlx.In(named, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = db.Rebind(query)
|
||||
return db.QueryxContext(ctx, query, args...)
|
||||
}()
|
||||
|
||||
default:
|
||||
rows, err = sqlx.NamedQueryContext(ctx, db, query, data)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
var pqErr *pgconn.PgError
|
||||
if errors.As(err, &pqErr) && pqErr.Code == undefinedTable {
|
||||
return ErrUndefinedTable
|
||||
}
|
||||
log.Error("NamedQueryStruct NamedQueryContext error", err,
|
||||
zap.String("query", q),
|
||||
zap.Any("data", data),
|
||||
)
|
||||
return err
|
||||
}
|
||||
defer func(rows *sqlx.Rows) {
|
||||
err := rows.Close()
|
||||
if err != nil {
|
||||
log.Error("rows close error", err)
|
||||
}
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
|
||||
var slice []T
|
||||
for rows.Next() {
|
||||
v := new(T)
|
||||
if err := rows.StructScan(v); err != nil {
|
||||
log.Error("NamedQuerySlice StructScan error", err,
|
||||
zap.String("query", q),
|
||||
zap.Any("data", data),
|
||||
)
|
||||
return err
|
||||
}
|
||||
slice = append(slice, *v)
|
||||
@ -188,6 +278,80 @@ func NamedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.Ext
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryStruct is a helper function for executing queries that return a
|
||||
// single value to be unmarshalled into a struct type where field replacement is necessary.
|
||||
func QueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, dest any) error {
|
||||
return namedQueryStruct(ctx, log, db, query, struct{}{}, dest, false)
|
||||
}
|
||||
|
||||
// NamedQueryStruct is a helper function for executing queries that return a
|
||||
// single value to be unmarshalled into a struct type where field replacement is necessary.
|
||||
func NamedQueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest any) error {
|
||||
return namedQueryStruct(ctx, log, db, query, data, dest, false)
|
||||
}
|
||||
|
||||
// NamedQueryStructUsingIn is a helper function for executing queries that return
|
||||
// a single value to be unmarshalled into a struct type where field replacement
|
||||
// is necessary. Use this if the query has an IN clause.
|
||||
func NamedQueryStructUsingIn(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest any) error {
|
||||
return namedQueryStruct(ctx, log, db, query, data, dest, true)
|
||||
}
|
||||
|
||||
func namedQueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest any, withIn bool) (err error) {
|
||||
q := queryString(query, data)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Info("database.NamedQuerySlice", zap.String("query", q), zap.Int("type", 6), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var rows *sqlx.Rows
|
||||
|
||||
switch withIn {
|
||||
case true:
|
||||
rows, err = func() (*sqlx.Rows, error) {
|
||||
named, args, err := sqlx.Named(query, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query, args, err := sqlx.In(named, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query = db.Rebind(query)
|
||||
return db.QueryxContext(ctx, query, args...)
|
||||
}()
|
||||
|
||||
default:
|
||||
rows, err = sqlx.NamedQueryContext(ctx, db, query, data)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
var pqErr *pgconn.PgError
|
||||
if errors.As(err, &pqErr) && pqErr.Code == undefinedTable {
|
||||
return ErrUndefinedTable
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func(rows *sqlx.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
|
||||
if !rows.Next() {
|
||||
return ErrDBNotFound
|
||||
}
|
||||
|
||||
if err := rows.StructScan(dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryString provides a pretty print version of the query and parameters.
|
||||
func queryString(query string, args any) string {
|
||||
query, params, err := sqlx.Named(query, args)
|
||||
if err != nil {
|
||||
|
||||
@ -44,7 +44,7 @@ func (d *RedisTaskDistributor) DistributeTaskConsumeAuditLog(
|
||||
func (p *RedisTaskProcessor) ProcessTaskConsumeAuditLog(ctx context.Context, task *asynq.Task) error {
|
||||
var payload PayloadConsumeAuditLog
|
||||
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
|
||||
return fmt.Errorf("failed to unmarshal payload: %w", err)
|
||||
}
|
||||
|
||||
if err := p.auditService.Create(ctx, payload.AuditLog); err != nil {
|
||||
|
||||
@ -36,6 +36,7 @@ func NewRedisTaskProcessor(log *logger.Logger, opt *RedisClientConnector, auditS
|
||||
QueueCritical: 10,
|
||||
QueueDefault: 5,
|
||||
},
|
||||
|
||||
ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
|
||||
log.Error("process task failed", err,
|
||||
zap.String("type", task.Type()),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user