diff --git a/app/app.go b/app/app.go index c84a71c..e13f8bc 100644 --- a/app/app.go +++ b/app/app.go @@ -7,7 +7,7 @@ import ( "github.com/go-redis/redis/v8" "heruvym/dal/minio" "heruvym/dal/mongo" - "heruvym/middleware" + "heruvym/internal/utils/middleware" "heruvym/router" "heruvym/service" "heruvym/tools" @@ -47,7 +47,7 @@ type Options struct { RedisHost string `env:"REDIS_HOST" default:"localhost:6379"` RedisPassword string `env:"REDIS_PASSWORD" default:""` RedisDB uint64 `env:"REDIS_DB" default:"0"` - TgChatID uint64 `env:"TELEGRAM_CHAT_ID" default:"1001344671794"` + TgChatID uint64 `env:"TELEGRAM_CHAT_ID" default:"1001344671794"` } var ( diff --git a/app/app_account.go b/app/app_account.go index 6a015db..4860f85 100644 --- a/app/app_account.go +++ b/app/app_account.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2/middleware/recover" "go.uber.org/zap" "heruvym/dal/mongo" - "heruvym/middleware" + "heruvym/internal/utils/middleware" "heruvym/service" ) diff --git a/go.mod b/go.mod index 26d43cb..b8672d2 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,117 @@ module heruvym -go 1.16 +go 1.21 + +toolchain go1.23.1 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-playground/validator/v10 v10.13.0 - github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/gofiber/contrib/fiberzap v1.0.2 - github.com/gofiber/fiber/v2 v2.45.0 - github.com/golang/snappy v0.0.4 // indirect + github.com/gofiber/fiber/v2 v2.51.0 github.com/gorilla/mux v1.8.0 - github.com/klauspost/cpuid/v2 v2.1.2 // indirect github.com/minio/minio-go/v7 v7.0.43 - github.com/montanaflynn/stats v0.6.6 // indirect github.com/pkg/errors v0.9.1 github.com/rs/xid v1.4.0 github.com/skeris/appInit v1.0.2 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.4 github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - go.mongodb.org/mongo-driver v1.10.3 - go.uber.org/zap v1.24.0 - gopkg.in/ini.v1 v1.67.0 // indirect + go.mongodb.org/mongo-driver v1.13.1 + go.uber.org/zap v1.27.0 gopkg.in/tucnak/telebot.v2 v2.5.0 ) + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect + github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + github.com/creack/pty v1.1.9 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fatih/color v1.10.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/go-playground/assert/v2 v2.2.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.5 // indirect + github.com/google/gofuzz v1.0.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/google/renameio v0.1.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/hpcloud/tail v1.0.0 // indirect + github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kisielk/gotool v1.0.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.1.2 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/kr/pty v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.2.3 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.6.6 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/ginkgo/v2 v2.0.0 // indirect + github.com/onsi/gomega v1.18.1 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.3 // indirect + github.com/rogpeppe/go-internal v1.3.0 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/tidwall/pretty v1.0.0 // indirect + github.com/tinylib/msgp v1.1.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/yuin/goldmark v1.4.13 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/goleak v1.3.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/tools v0.6.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/errgo.v2 v2.1.0 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/tools v0.0.1-2019.2.3 // indirect + penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240804072029-436a9f848461 // indirect +) diff --git a/go.sum b/go.sum index 70c620e..713d9c1 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,14 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -39,6 +42,7 @@ github.com/gofiber/contrib/fiberzap v1.0.2/go.mod h1:jGO8BHU4gRI9U0JtM6zj2CIhYfg github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc= github.com/gofiber/fiber/v2 v2.45.0 h1:p4RpkJT9GAW6parBSbcNFH2ApnAuW3OzaQzbOCoDu+s= github.com/gofiber/fiber/v2 v2.45.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= +github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -62,10 +66,13 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -74,6 +81,7 @@ github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -84,6 +92,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -94,8 +103,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.43 h1:14Q4lwblqTdlAmba05oq5xL0VBLHi06zS4yLnIkz6hI= @@ -156,6 +167,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 h1:N9f/Q+2Ssa+yDcbfaoLTYvXmdeyUUxsJKdPUVsjSmiA= github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33/go.mod h1:rpcH99JknBh8seZmlOlUg51gasZH6QH34oXNsIwYT6E= github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo= @@ -170,14 +182,17 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= @@ -186,6 +201,7 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.10.3 h1:XDQEvmh6z1EUsXuIkXE9TaVeqHw6SwS1uf93jFs0HBA= go.mongodb.org/mongo-driver v1.10.3/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= @@ -193,14 +209,17 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -272,6 +291,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= @@ -331,3 +352,5 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240804072029-436a9f848461 h1:Qfgv2eitRRtbM4/THFi1ZopdkurTItYpE/j/IEL1/ws= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240804072029-436a9f848461/go.mod h1:+bPxq2wfW5S1gd+83vZYmHm33AE7nEBfznWS8AM1TKE= diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..04d0acf --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,109 @@ +package app + +import ( + "context" + "errors" + "github.com/themakers/hlog" + "go.uber.org/zap" + "heruvym/app" + "heruvym/internal/initialize" + "heruvym/pkg/closer" + "strconv" + "time" +) + +var zapOptions = []zap.Option{ + zap.AddCaller(), + zap.AddCallerSkip(2), + zap.AddStacktrace(zap.ErrorLevel), +} + +type Build struct { + Commit string + Version string + BuildTime int64 +} + +func Run(ctx context.Context, cfg initialize.Config, build Build) error { + var logger *zap.Logger + var err error + + if cfg.LoggerDevMode { + logger, err = zap.NewProduction(zapOptions...) + if err != nil { + return err + } + } else { + logger, err = zap.NewDevelopment(zapOptions...) + if err != nil { + return err + } + } + + logger = logger.With( + zap.String("SvcCommit", build.Commit), + zap.String("SvcVersion", build.Version), + zap.String("SvcBuildTime", strconv.FormatInt(build.BuildTime, 10)), + ) + + hlogger := hlog.New(logger) + hlogger.Emit(app.InfoSvcStarted{}) + + defer func() { + if r := recover(); r != nil { + logger.Error("Recovered from a panic", zap.Any("error", r)) + } + }() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + shutdownGroup := closer.NewCloserGroup() + + mdb, err := initialize.MongoDB(ctx, cfg) + if err != nil { + logger.Error("Error initializing MongoDB", zap.Error(err)) + return err + } + + minioClient, err := initialize.Minio(ctx, cfg) + if err != nil { + logger.Error("Error initializing Minio", zap.Error(err)) + return err + } + + redisClient, err := initialize.Redis(ctx, cfg) + if err != nil { + logger.Error("Error initializing Redis", zap.Error(err)) + return err + } + + tgBot, err := initialize.NewTgBot(cfg) + if err != nil { + logger.Error("Error initializing Telegram", zap.Error(err)) + return err + } + + //shutdownGroup.Add(closer.CloserFunc(clientServer.Shutdown)) + //shutdownGroup.Add(closer.CloserFunc(adminServer.Shutdown)) + //shutdownGroup.Add(closer.CloserFunc(grpcServer.Stop)) + //shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect)) + //shutdownGroup.Add(closer.CloserFunc(recoveryWC.Stop)) + //shutdownGroup.Add(closer.CloserFunc(purgeWC.Stop)) + + <-ctx.Done() + + timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer timeoutCancel() + if err := shutdownGroup.Call(timeoutCtx); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + logger.Error("Shutdown timed out", zap.Error(err)) + } else { + logger.Error("Failed to shutdown services gracefully", zap.Error(err)) + } + return err + } + + logger.Info("Application has stopped") + return nil +} diff --git a/internal/initialize/config.go b/internal/initialize/config.go new file mode 100644 index 0000000..a7df0bf --- /dev/null +++ b/internal/initialize/config.go @@ -0,0 +1,43 @@ +package initialize + +import ( + "github.com/caarlos0/env/v8" + "github.com/joho/godotenv" + "log" +) + +type Config struct { + //MongoURI string `env:"BB_MONGO_URI" envDefault:"mongodb://localhost:27017"` + MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"` + MongoPort string `env:"MONGO_PORT" envDefault:"27020"` + MongoUser string `env:"MONGO_USER" envDefault:"test"` + MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` + MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` + MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` + NumberPortLocal string `env:"BB_PORT" envDefault:"1488"` + AccountAddress string `env:"BB_AccountAddress" envDefault:":8931"` + LoggerDevMode bool `env:"BB_IS_PROD" envDefault:"false"` + MinioEndpoint string `env:"BB_MINIO_EP" envDefault:"minio:9001"` + MinioAccessKey string `env:"BB_MINIO_AK" envDefault:"minio"` + MinioSecretKey string `env:"BB_MINIO_SK" envDefault:"miniostorage"` + MinioRegion string `env:"S3_REGION" envDefault:""` + MinioToken string `env:"BB_MINIO_TOKEN" envDefault:""` + MongoDbTable string `env:"DATABASE_TABLE" envDefault:"profile"` + MongoCollections string `env:"COLLECTION_NAME" envDefault:"profile,role"` + TgToken string `env:"TELEGRAM_TOKEN" envDefault:"5851043588:AAGXhigZAaNV1--n-jfS8eBgM7iZ2IDm668"` + RedisHost string `env:"REDIS_HOST" envDefault:"localhost:6379"` + RedisPassword string `env:"REDIS_PASSWORD" envDefault:""` + RedisDB int `env:"REDIS_DB" envDefault:"0"` + TgChatID uint64 `env:"TELEGRAM_CHAT_ID" envDefault:"1001344671794"` +} + +func LoadConfig() (*Config, error) { + if err := godotenv.Load(); err != nil { + log.Print("No .env file found") + } + var config Config + if err := env.Parse(&config); err != nil { + return nil, err + } + return &config, nil +} diff --git a/internal/initialize/minio.go b/internal/initialize/minio.go new file mode 100644 index 0000000..8481294 --- /dev/null +++ b/internal/initialize/minio.go @@ -0,0 +1,21 @@ +package initialize + +import ( + "context" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +func Minio(ctx context.Context, cfg Config) (*minio.Client, error) { + conn, err := minio.New(cfg.MinioEndpoint, + &minio.Options{ + Creds: credentials.NewStaticV4(cfg.MinioAccessKey, cfg.MinioSecretKey, cfg.MinioToken), + Secure: false, + }, + ) + if err != nil { + return nil, err + } + + return conn, nil +} diff --git a/internal/initialize/mongo.go b/internal/initialize/mongo.go new file mode 100644 index 0000000..76df7b6 --- /dev/null +++ b/internal/initialize/mongo.go @@ -0,0 +1,39 @@ +package initialize + +import ( + "context" + "go.mongodb.org/mongo-driver/mongo" + mdb "penahub.gitlab.yandexcloud.net/backend/penahub_common/mongo" + "time" +) + +func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { + dbConfig := &mdb.Configuration{ + Host: cfg.MongoHost, + Port: cfg.MongoPort, + User: cfg.MongoUser, + Password: cfg.MongoPassword, + DatabaseName: cfg.MongoDatabase, + Auth: cfg.MongoAuth, + } + + newCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + mongoDeps := &mdb.ConnectDeps{ + Configuration: dbConfig, + Timeout: 10 * time.Second, + } + + db, err := mdb.Connect(newCtx, mongoDeps) + if err != nil { + return nil, err + } + + err = db.Client().Ping(newCtx, nil) + if err != nil { + return nil, err + } + + return db, nil +} diff --git a/internal/initialize/redis.go b/internal/initialize/redis.go new file mode 100644 index 0000000..6b9cd53 --- /dev/null +++ b/internal/initialize/redis.go @@ -0,0 +1,21 @@ +package initialize + +import ( + "context" + "github.com/go-redis/redis/v8" +) + +func Redis(ctx context.Context, cfg Config) (*redis.Client, error) { + rdb := redis.NewClient(&redis.Options{ + Addr: cfg.RedisHost, + Password: cfg.RedisPassword, + DB: cfg.RedisDB, + }) + + status := rdb.Ping(ctx) + if err := status.Err(); err != nil { + return nil, err + } + + return rdb, nil +} diff --git a/internal/initialize/repository.go b/internal/initialize/repository.go new file mode 100644 index 0000000..4f4c516 --- /dev/null +++ b/internal/initialize/repository.go @@ -0,0 +1,45 @@ +package initialize + +import ( + "context" + minioClient "github.com/minio/minio-go/v7" + "github.com/themakers/hlog" + mongoDatabase "go.mongodb.org/mongo-driver/mongo" + "heruvym/internal/repository/minio" + "heruvym/internal/repository/mongo" +) + +type DepsRepositories struct { + MinioClient *minioClient.Client + MongoDatabase *mongoDatabase.Database + Cfg Config + HLogger hlog.Logger +} + +type Repositories struct { + Minio *minio.BlobStore + Mongo *mongo.DAL +} + +func NewRepositories(ctx context.Context, deps DepsRepositories) (*Repositories, error) { + minioRepo, err := minio.New(ctx, minio.Deps{ + HLogger: deps.HLogger, + Store: deps.MinioClient, + }) + if err != nil { + return nil, err + } + + mongoRepo, err := mongo.New(ctx, mongo.DepsDAL{ + MongoDatabase: deps.MongoDatabase, + HLogger: deps.HLogger, + }) + if err != nil { + return nil, err + } + + return &Repositories{ + Minio: minioRepo, + Mongo: mongoRepo, + }, nil +} diff --git a/internal/initialize/tg.go b/internal/initialize/tg.go new file mode 100644 index 0000000..21b4cde --- /dev/null +++ b/internal/initialize/tg.go @@ -0,0 +1,21 @@ +package initialize + +import ( + tb "gopkg.in/tucnak/telebot.v2" + "time" +) + +func NewTgBot(cfg Config) (*tb.Bot, error) { + newBot, err := tb.NewBot(tb.Settings{ + Token: cfg.TgToken, + Verbose: false, + ParseMode: tb.ModeHTML, + Poller: &tb.LongPoller{ + Timeout: time.Second, + }, + }) + if err != nil { + return nil, err + } + return newBot, nil +} diff --git a/internal/repository/minio/minio.go b/internal/repository/minio/minio.go new file mode 100644 index 0000000..34553f1 --- /dev/null +++ b/internal/repository/minio/minio.go @@ -0,0 +1,101 @@ +package minio + +import ( + "context" + "fmt" + "io" + "net/http" + + "github.com/minio/minio-go/v7" + "github.com/themakers/hlog" +) + +type Deps struct { + HLogger hlog.Logger + Store *minio.Client +} + +type BlobStore struct { + log hlog.Logger + store *minio.Client +} + +const bucket = "pair" + +func New(ctx context.Context, deps Deps) (*BlobStore, error) { + bStore := &BlobStore{ + log: deps.HLogger.Module("minio"), + store: deps.Store, + } + + bucketExists, err := bStore.store.BucketExists(ctx, bucket) + if err != nil { + return nil, err + } + + if !bucketExists { + if err := bStore.store.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}); err != nil { + return nil, err + } + } + + return bStore, nil +} + +func (bs *BlobStore) PutFile( + ctx context.Context, + filename string, + reader io.Reader, + size int64) error { + info, err := bs.store.PutObject(ctx, bucket, filename, reader, size, minio.PutObjectOptions{ + //UserMetadata: nil, + //UserTags: nil, + //Progress: nil, + //ContentType: "", + //ContentEncoding: "", + //ContentDisposition: "", + //ContentLanguage: "", + //CacheControl: "", + //Mode: "", + //RetainUntilDate: time.Time{}, + //ServerSideEncryption: nil, + //NumThreads: 0, + //StorageClass: "", + //WebsiteRedirectLocation: "", + //PartSize: 0, + //LegalHold: "", + //SendContentMd5: false, + //DisableMultipart: false, + //Internal: minio.AdvancedPutOptions{}, + }) + if err != nil { + fmt.Println(err) + return err + } + fmt.Println("info", info) + + return nil +} + +func (bs *BlobStore) DeleteFile(ctx context.Context, id string) error { + if err := bs.store.RemoveObject(ctx, bucket, id, minio.RemoveObjectOptions{ + //GovernanceBypass: false, + //VersionID: "", + //Internal: AdvancedRemoveOptions + }); err != nil { + return err + } + + return nil +} + +func (bs *BlobStore) FileExists(ctx context.Context, filename string) (bool, error) { + _, err := bs.store.StatObject(ctx, bucket, filename, minio.StatObjectOptions{}) + if err != nil { + if minio.ToErrorResponse(err).StatusCode == http.StatusNotFound { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/internal/repository/mongo/dal.go b/internal/repository/mongo/dal.go new file mode 100644 index 0000000..8af796a --- /dev/null +++ b/internal/repository/mongo/dal.go @@ -0,0 +1,1037 @@ +package mongo + +import ( + "context" + "errors" + "fmt" + "heruvym/model" + "time" + + "github.com/rs/xid" + "github.com/themakers/hlog" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +const ( + collMessages = "messages" + collTickets = "tickets" + collAccount = "account" +) + +type DepsDAL struct { + MongoDatabase *mongo.Database + HLogger hlog.Logger +} + +type DAL struct { + logger hlog.Logger + colMsg, colTck, colAcc *mongo.Collection + client *mongo.Client +} + +type ErrorConnectToDB struct { + Err error + MongoURI string +} + +type ErrorPingDB struct { + Err error + MongoURI string +} + +type InfoPing struct { + MongoURI string + Nanoseconds int64 +} + +func New(ctx context.Context, deps DepsDAL) (*DAL, error) { + dal := &DAL{ + client: deps.MongoDatabase.Client(), + colMsg: deps.MongoDatabase.Collection(collMessages), + colTck: deps.MongoDatabase.Collection(collTickets), + colAcc: deps.MongoDatabase.Collection(collAccount), + logger: deps.HLogger.Module("DAL"), + } + + // Будет ошибка создания индексов только если эти индексы изменить и предварительно не удалить старые + err := dal.CreateTicketIndex(ctx) + if err != nil { + return nil, err + } + + err = dal.CreateMessageIndex(ctx) + if err != nil { + return nil, err + } + + err = dal.CreateAccountIndex(ctx) + if err != nil { + return nil, err + } + return dal, nil +} + +type ErrorInsert struct { + Err error + UserID, SessionID string +} + +type ErrorCreateIndex struct { + Err error +} + +func (d *DAL) CreateMessageIndex(ctx context.Context) error { + keys := bson.D{{"$**", "text"}} + + opts := options.Index().SetDefaultLanguage("russian") + + _, err := d.colMsg.Indexes().CreateOne(ctx, mongo.IndexModel{Keys: keys, Options: opts}) + + if err != nil { + d.logger.Emit(ErrorCreateIndex{err}) + } + + return err +} + +func (d *DAL) CreateTicketIndex(ctx context.Context) error { + keys := bson.D{{"$**", "text"}} + + opts := options.Index().SetDefaultLanguage("russian") + + _, err := d.colTck.Indexes().CreateOne(ctx, mongo.IndexModel{Keys: keys, Options: opts}) + + if err != nil { + d.logger.Emit(ErrorCreateIndex{err}) + } + + return err +} + +func (d *DAL) CreateAccountIndex(ctx context.Context) error { + keys := bson.D{{"$**", "text"}} + + opts := options.Index().SetDefaultLanguage("russian") + + _, err := d.colAcc.Indexes().CreateOne(ctx, mongo.IndexModel{Keys: keys, Options: opts}) + + if err != nil { + d.logger.Emit(ErrorCreateIndex{err}) + } + + return err +} + +func (d *DAL) PutMessage( + ctx context.Context, + message, userID, sessionID, ticketID string, + files []string, +) (*model.Message, error) { + insertable := model.Message{ + ID: xid.New().String(), + UserID: userID, + SessionID: sessionID, + TicketID: ticketID, + Message: message, + Files: files, + Shown: map[string]int{}, + CreatedAt: time.Now(), + } + + if _, err := d.colMsg.InsertOne(ctx, &insertable); err != nil { + d.logger.Emit(ErrorInsert{ + Err: err, + UserID: userID, + SessionID: sessionID, + }) + + return nil, err + } + + return &insertable, nil +} + +func (d *DAL) PutSCResponse( + ctx context.Context, + message, userID, sessionID, ticketID string, + files []string, +) (*model.Message, error) { + insertable := model.Message{ + ID: xid.New().String(), + UserID: userID, + SessionID: sessionID, + TicketID: ticketID, + Message: message, + Files: files, + RequestScreenshot: "response", + Shown: map[string]int{}, + CreatedAt: time.Now(), + } + + if _, err := d.colMsg.InsertOne(ctx, &insertable); err != nil { + d.logger.Emit(ErrorInsert{ + Err: err, + UserID: userID, + SessionID: sessionID, + }) + + return nil, err + } + + return &insertable, nil +} + +func (d *DAL) PutSCRequest( + ctx context.Context, + userID, sessionID, ticketID string, +) (*model.Message, error) { + insertable := model.Message{ + ID: xid.New().String(), + UserID: userID, + SessionID: sessionID, + TicketID: ticketID, + Message: "", + Files: []string{}, + Shown: map[string]int{}, + RequestScreenshot: "acquisition", + CreatedAt: time.Now(), + } + + if _, err := d.colMsg.InsertOne(ctx, &insertable); err != nil { + d.logger.Emit(ErrorInsert{ + Err: err, + UserID: userID, + SessionID: sessionID, + }) + + return nil, err + } + + return &insertable, nil +} + +func (d *DAL) CreateTicket( + ctx context.Context, + userID, + sessionID, + origin, + title, message string, + files []string, +) (string, error) { + + ticketID := xid.New().String() + + if _, err := d.colTck.InsertOne(ctx, &model.Ticket{ + ID: ticketID, + UserID: userID, + SessionID: sessionID, + Title: title, + State: model.StateOpen, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Rate: -1, + Origin: origin, + TopMessage: model.Message{ + ID: xid.New().String(), + UserID: userID, + SessionID: sessionID, + TicketID: ticketID, + Message: message, + Files: []string{}, + Shown: map[string]int{}, + CreatedAt: time.Now(), + }, + }); err != nil { + d.logger.Emit(ErrorInsert{ + Err: err, + UserID: userID, + SessionID: sessionID, + }) + + return "", err + } + + return ticketID, nil +} + +func (d *DAL) GetTickets4Sess(ctx context.Context, sessID string) ([]model.Ticket, int64, error) { + sort := bson.M{"State": -1, "UpdatedAt": 1} + opts := options.Find().SetSort(sort) + + query := bson.M{ + "SessionID": sessID, + } + + cur, err := d.colTck.Find(ctx, query, opts) + if err != nil { + return nil, 0, err + } + + var result []model.Ticket + if err := cur.All(ctx, &result); err != nil { + return nil, 0, err + } + + col, err := d.colTck.CountDocuments(ctx, query) + if err != nil { + return nil, 0, err + } + + return result, col, nil +} + +func (d *DAL) GetTicket4Sess( + ctx context.Context, + ticketID, + sessID string) (*model.Ticket, error) { + + var result model.Ticket + if err := d.colTck.FindOne(ctx, bson.M{ + "_id": ticketID, + "SessionID": sessID, + }).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} + +func (d *DAL) GetTicket4User( + ctx context.Context, + ticketID, + userID string) (*model.Ticket, error) { + + var result model.Ticket + if err := d.colTck.FindOne(ctx, bson.M{ + "_id": ticketID, + "UserID": userID, + }).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} + +func (d *DAL) WatchTickets( + ctx context.Context, + userID string, + yield func(ticket model.Ticket) error) error { + operationTypes := []bson.D{{{"operationType", "insert"}}, + {{"operationType", "update"}}} + + matchStage := bson.M{ + "$and": bson.A{ + bson.M{"$or": operationTypes}, + bson.M{"fullDocument.UserID": userID}, + }, + } + + matchPipeline := mongo.Pipeline{ + bson.D{{"$match", matchStage}}, + } + + cs, err := d.colTck.Watch(ctx, matchPipeline, + options.ChangeStream().SetFullDocument(options.UpdateLookup)) + if err != nil { + fmt.Println("111", err, matchPipeline) + return err + } + + for cs.Next(ctx) { + var ( + piece model.Ticket + change Change + ) + if err := cs.Decode(&change); err != nil { + return err + } + + if err := bson.Unmarshal(change.FullDocument, &piece); err != nil { + return err + } + + if err := yield(piece); err != nil { + return err + } + } + + return nil +} + +func (d *DAL) YieldActiveTickets( + ctx context.Context, + yield func(ticket model.Ticket) error) error { + cursor, err := d.colTck.Find(ctx, bson.M{ + "State": model.StateOpen, + }, options.Find().SetLimit(20)) + if err != nil { + return err + } + + var piece model.Ticket + for cursor.Next(ctx) { + if err := cursor.Decode(&piece); err != nil { + return err + } + + if err := yield(piece); err != nil { + return err + } + } + + return nil +} + +func (d *DAL) YieldTickets(ctx context.Context, limit int64) ([]model.Ticket, int64, error) { + sort := bson.D{{"State", -1}, {"UpdatedAt", 1}} + + cursor, err := d.colTck.Find(ctx, bson.M{}, options.Find().SetLimit(limit).SetSort(sort)) + if err != nil { + return nil, 0, err + } + + var result []model.Ticket + + err = cursor.All(ctx, &result) + + if err != nil { + return nil, 0, err + } + + col, err := d.colTck.CountDocuments(ctx, bson.M{}) + if err != nil { + return nil, 0, err + } + + return result, col, nil +} + +func (d *DAL) YieldUserTickets(ctx context.Context, userID string, limit, offset int64) ([]model.Ticket, int64, error) { + query := bson.M{ + "UserID": userID, + } + fmt.Println("UserID", userID) + + sort := bson.D{{"State", -1}, {"UpdatedAt", -1}} + + cursor, err := d.colTck.Find(ctx, query, options.Find().SetSort(sort).SetLimit(limit).SetSkip(offset)) + if err != nil { + return nil, 0, err + } + + var result []model.Ticket + + err = cursor.All(ctx, &result) + + if err != nil { + return nil, 0, err + } + + col, err := d.colTck.CountDocuments(ctx, query) + if err != nil { + return nil, 0, err + } + + return result, col, nil +} + +func (d *DAL) WatchAllTickets(ctx context.Context, yield func(ticket model.Ticket) error) error { + operationTypes := []bson.D{ + {{"operationType", "insert"}}, + {{"operationType", "update"}}, + } + + matchStage := bson.D{{"$or", operationTypes}} + + matchPipeline := mongo.Pipeline{ + bson.D{ + {"$match", matchStage}, + }, + } + + cs, err := d.colTck.Watch(ctx, matchPipeline, + options.ChangeStream().SetFullDocument(options.UpdateLookup)) + if err != nil { + return err + } + + for cs.Next(ctx) { + var ( + piece model.Ticket + change Change + ) + + if err := cs.Decode(&change); err != nil { + return err + } + + if err := bson.Unmarshal(change.FullDocument, &piece); err != nil { + return err + } + + if err := yield(piece); err != nil { + return err + } + } + + return nil +} + +func (d *DAL) UpdateTopMessage(ctx context.Context, ticketID string, msg *model.Message) error { + if err := d.colTck.FindOneAndUpdate(ctx, bson.M{ + "_id": ticketID, + }, bson.M{ + "$set": bson.M{ + "UpdatedAt": time.Now(), + "TopMessage": msg, + }, + }).Decode(&model.Ticket{}); err != nil { + return err + } + + return nil +} + +func (d *DAL) YieldMessages( + ctx context.Context, + ticketID string, + yield func(ticket model.Message) error) error { + cursor, err := d.colMsg.Find(ctx, bson.M{ + "TicketID": ticketID, + }, options.Find().SetLimit(20).SetSort(bson.D{{"CreatedAt", -1}})) + if err != nil { + return err + } + + var piece model.Message + for cursor.Next(ctx) { + if err := cursor.Decode(&piece); err != nil { + return err + } + + if err := yield(piece); err != nil { + return err + } + } + + return nil +} + +type OpType string + +const ( + OpInsert OpType = "insert" + OpDelete OpType = "delete" + OpReplace OpType = "replace" + OpUpdate OpType = "update" +) + +type Change struct { + ID struct { + Data string `bson:"_data"` + } `bson:"_id"` + + OperationType OpType `bson:"operationType"` + + FullDocument bson.Raw `bson:"fullDocument"` + + NS struct { + DB string `bson:"db"` + Coll string `bson:"coll"` + } `bson:"ns"` + + To *struct { + DB string `bson:"db"` + Coll string `bson:"coll"` + } `bson:"to"` + + DocumentKey bson.Raw `bson:"documentKey"` + + UpdateDescription *struct { + UpdatedFields bson.Raw `bson:"updatedFields"` + RemovedFields []string `bson:"removedFields"` + } `bson:"updateDescription"` + + ClusterTime primitive.Timestamp `bson:"clusterTime"` + + TxnNumber int64 `bson:"txnNumber"` + + LSID bson.Raw `bson:"lsid"` + + //LSID *struct { + // ID primitive.ObjectID `bson:"id"` + // UID primitive.Binary `bson:"uid"` + //} `bson:"lsid"` +} + +func (d *DAL) WatchMessages( + ctx context.Context, ticketID string, yield func(ticket model.Message) error) error { + operationTypes := []bson.D{{{"operationType", "insert"}}, + {{"operationType", "update"}}} + + matchStage := bson.D{{"$and", []bson.D{ + { + {"$or", operationTypes}, + }, + { + { + "fullDocument.TicketID", ticketID, + }, + }, + }}} + + matchPipeline := mongo.Pipeline{ + bson.D{{"$match", matchStage}}, + } + + cs, err := d.colMsg.Watch(ctx, matchPipeline, + options.ChangeStream().SetFullDocument(options.UpdateLookup)) + if err != nil { + return err + } + + for cs.Next(ctx) { + var ( + piece model.Message + change Change + ) + + if err := cs.Decode(&change); err != nil { + return err + } + + bson.Unmarshal(change.FullDocument, &piece) + if err := yield(piece); err != nil { + return err + } + } + + return nil +} + +func (d *DAL) SetShown(ctx context.Context, messageID, userID string) error { + if err := d.colMsg.FindOneAndUpdate(ctx, bson.M{ + "_id": messageID, + }, bson.M{ + "$set": bson.M{ + fmt.Sprintf("Shown.%s", userID): 1, + }, + }).Decode(&model.Message{}); err != nil { + return err + } + + if err := d.colTck.FindOneAndUpdate(ctx, bson.M{ + "TopMessage._id": messageID, + }, bson.M{ + "$set": bson.M{ + fmt.Sprintf("TopMessage.Shown.%s", userID): 1, + }, + }).Decode(&model.Message{}); err != nil { + return err + } + + return nil +} + +func (d *DAL) GetTicketPage( + ctx context.Context, + state, srch string, + limit, skip int64, +) (*[]model.Ticket, int64, error) { + query := bson.M{} + + if state != "" { + query["State"] = state + } + + if srch != "" { + query["$text"] = bson.M{ + "$search": srch, + } + } + + sort := bson.D{{"State", -1}, {"UpdatedAt", -1}} + + cur, err := d.colTck.Find(ctx, query, options.Find().SetSort(sort).SetLimit(limit).SetSkip(skip*limit)) + if err != nil { + return nil, 0, err + } + + var result []model.Ticket + + if err := cur.All(ctx, &result); err != nil { + return nil, 0, err + } + + col, err := d.colTck.CountDocuments(ctx, query) + if err != nil { + return nil, 0, err + } + + return &result, col, nil +} + +func (d *DAL) GetMessagesPage(ctx context.Context, + search, ticketID string, + limit, offset int64) ([]model.Message, error) { + + var ( + query bson.M + result []model.Message + ) + + if ticketID != "" { + query = bson.M{ + "TicketID": ticketID, + } + } + + if search != "" { + query = bson.M{ + "TicketID": ticketID, + "$text": bson.M{ + "$search": search, + }, + } + } + + sort := bson.D{{"CreatedAt", -1}} + + cur, err := d.colMsg.Find(ctx, query, options.Find().SetLimit(limit).SetSkip(limit*offset).SetSort(sort)) + if err != nil { + return nil, err + } + + if err := cur.All(ctx, &result); err != nil { + return nil, err + } + + return result, nil +} + +func (d *DAL) SetTicketStatus(ctx context.Context, + ticket, status string) error { + if _, err := d.colTck.UpdateByID(ctx, ticket, bson.M{ + "$set": bson.M{ + "State": status, + "UpdatedAt": time.Now(), + }, + }); err != nil { + return err + } + + return nil +} + +func (d *DAL) SetRate(ctx context.Context, ticket string, rate int) error { + if _, err := d.colTck.UpdateByID(ctx, ticket, bson.M{"$set": bson.M{ + "Rate": rate, + "UpdatedAt": time.Now(), + }}); err != nil { + return err + } + + return nil +} + +func (d *DAL) SetAnswerer(ctx context.Context, ticket, answerer string) error { + if _, err := d.colTck.UpdateByID(ctx, ticket, bson.M{"$set": bson.M{ + "AnswererID": answerer, + "UpdatedAt": time.Now(), + }}); err != nil { + return err + } + + return nil +} + +type Additional struct { + Email string + Uid int64 +} +type Identites struct { + ID string `bson:"_id"` + Identites []Identity `bson:"Identities"` +} +type Identity struct { + Name string `bson:"Name"` + Identity string `bson:"Identity"` +} +type UidTechs struct { + DisplayID int64 `bson:"display_id"` + ID string `bson:"_id"` +} + +func (d *DAL) GetAdditionalData(ctx context.Context, id string) (*Additional, error) { + result := Additional{} + idens := Identites{} + if err := d.client.Database("bb-identity").Collection("tp-users").FindOne(ctx, bson.M{ + "_id": id, + }, options.FindOne().SetProjection(bson.D{{ + "Identities", 1, + }})).Decode(&idens); err != nil { + return nil, err + } + + for _, iden := range idens.Identites { + fmt.Println(iden.Name, iden) + if iden.Name == "email" { + result.Email = iden.Identity + } + } + + u := UidTechs{} + if err := d.client.Database("profile").Collection("profile").FindOne(ctx, bson.M{ + "_id": id, + }, options.FindOne().SetProjection(bson.D{{ + "display_id", 1, + }})).Decode(&u); err != nil { + return nil, err + } + result.Uid = u.DisplayID + return &result, nil +} + +func (d *DAL) InsertAccount(ctx context.Context, record *model.Account) (*model.Account, error) { + now := time.Now() + record.CreatedAt, record.UpdatedAt = now, now + record.ID = xid.New().String() + + if record.Avatar == "" { + record.Avatar = "/media/avatar/default-avatar.jpg" + } + + if record.Nickname == "" { + record.Nickname = "Unknown" + } + + if record.Role == "" { + record.Role = "user" + } + + _, err := d.colAcc.InsertOne(ctx, record) + + if err != nil { + d.logger.Emit(ErrorInsertAccount{err, record}) + return nil, err + } + + return record, nil +} + +type ErrorInsertAccount struct { + Err error + Account *model.Account +} + +func (d *DAL) GetAccount(ctx context.Context, id string) (*model.Account, error) { + if id == "" { + err := errors.New("id cannot be empty") + d.logger.Emit(ErrorGetAccount{err, id}) + return nil, err + } + + var result model.Account + + err := d.colAcc.FindOne(ctx, bson.M{"_id": id}).Decode(&result) + if err != nil { + d.logger.Emit(ErrorGetAccount{err, id}) + return nil, err + } + + return &result, nil +} + +type ErrorGetAccount struct { + Err error + ID string +} + +func (d *DAL) GetAccountByUserID(ctx context.Context, userId string) (*model.Account, error) { + if userId == "" { + err := errors.New("userId cannot be empty") + d.logger.Emit(ErrorGetAccount{err, userId}) + return nil, err + } + + var result model.Account + + err := d.colAcc.FindOne(ctx, bson.M{"userId": userId}).Decode(&result) + if err != nil { + d.logger.Emit(ErrorGetAccount{err, userId}) + return nil, err + } + + return &result, nil +} + +func (d *DAL) GetAccountPage(ctx context.Context, search string, offset, limit int64) (*model.AccountPage, error) { + var query bson.M + if search != "" { + query = bson.M{ + "$text": bson.M{ + "$search": search, + }, + } + } + + count, err := d.colAcc.CountDocuments(ctx, query) + + sort := bson.D{{"CreatedAt", -1}} + + cur, err := d.colAcc.Find(ctx, query, options.Find().SetLimit(limit).SetSkip(limit*offset).SetSort(sort)) + if err != nil { + return nil, err + } + + var items []model.Account + if err := cur.All(ctx, &items); err != nil { + return nil, err + } + + return &model.AccountPage{Count: count, Items: items}, nil +} + +func (d *DAL) SetAccountRole(ctx context.Context, userId, role string) (*model.Account, error) { + if userId == "" { + err := errors.New("userId cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + if role == "" { + err := errors.New("role cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + filter := bson.M{"userId": userId} + update := bson.M{"role": role, "updatedAt": time.Now()} + + var result model.Account + + opts := options.FindOneAndUpdate().SetReturnDocument(options.After) + err := d.colAcc.FindOneAndUpdate(ctx, filter, bson.M{"$set": update}, opts).Decode(&result) + if err != nil { + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + return &result, nil +} + +func (d *DAL) SetAccountNickname(ctx context.Context, userId, nickname string) (*model.Account, error) { + if userId == "" { + err := errors.New("userId cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + if nickname == "" { + err := errors.New("nickname cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + filter := bson.M{"userId": userId} + update := bson.M{"nickname": nickname, "updatedAt": time.Now()} + + var result model.Account + + opts := options.FindOneAndUpdate().SetReturnDocument(options.After) + err := d.colAcc.FindOneAndUpdate(ctx, filter, bson.M{"$set": update}, opts).Decode(&result) + if err != nil { + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + return &result, nil +} + +func (d *DAL) SetAccountAvatar(ctx context.Context, userId, avatar string) (*model.Account, error) { + if userId == "" { + err := errors.New("userId cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + if avatar == "" { + err := errors.New("avatar cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + filter := bson.M{"userId": userId} + update := bson.M{"avatar": avatar, "updatedAt": time.Now()} + + var result model.Account + + opts := options.FindOneAndUpdate().SetReturnDocument(options.After) + err := d.colAcc.FindOneAndUpdate(ctx, filter, bson.M{"$set": update}, opts).Decode(&result) + if err != nil { + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + return &result, nil +} + +func (d *DAL) SetAccountDelete(ctx context.Context, userId string, isDeleted bool) (*model.Account, error) { + if userId == "" { + err := errors.New("userId cannot be empty") + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + filter := bson.M{"userId": userId} + update := bson.M{"isDeleted": isDeleted, "updatedAt": time.Now()} + + var result model.Account + + opts := options.FindOneAndUpdate().SetReturnDocument(options.After) + err := d.colAcc.FindOneAndUpdate(ctx, filter, bson.M{"$set": update}, opts).Decode(&result) + if err != nil { + d.logger.Emit(ErrorSetAccount{err, userId}) + return nil, err + } + + return &result, nil +} + +type ErrorSetAccount struct { + Err error + UserID string +} + +func (d *DAL) DeleteAccount(ctx context.Context, userId string) (*model.Account, error) { + if userId == "" { + err := errors.New("userId cannot be empty") + d.logger.Emit(ErrorDeleteAccount{err, userId}) + return nil, err + } + + filter := bson.M{"userId": userId} + var acc model.Account + + err := d.colAcc.FindOne(ctx, filter).Decode(&acc) + if err != nil { + d.logger.Emit(ErrorDeleteAccount{err, userId}) + return nil, err + } + + _, err = d.colAcc.DeleteOne(ctx, filter) + if err != nil { + d.logger.Emit(ErrorDeleteAccount{err, userId}) + return nil, err + } + + return &acc, err +} + +type ErrorDeleteAccount struct { + Err error + UserID string +} diff --git a/internal/repository/mongo/dal_test.go b/internal/repository/mongo/dal_test.go new file mode 100644 index 0000000..ab58def --- /dev/null +++ b/internal/repository/mongo/dal_test.go @@ -0,0 +1,102 @@ +package mongo + +import ( + "context" + "github.com/stretchr/testify/suite" + "github.com/themakers/hlog" + "go.uber.org/zap" + "heruvym/model" + "testing" +) + +var mongoUri = "mongodb://localmongo1:30001,localmongo2:30002,localmongo3:30003/?replicaSet=my-rs" + +type AccountTestSuite struct { + args *model.Account + dal *DAL + suite.Suite +} + +func TestAccountTestSuite(t *testing.T) { + suite.Run(t, new(AccountTestSuite)) +} + +func (s *AccountTestSuite) SetupSuite() { + s.args = &model.Account{ + UserID: "fhka41hakfh41z", + Nickname: "SomeNick", + Role: "manager", + } + + logger := hlog.New(zap.NewNop()) + dal, err := New(context.Background(), mongoUri, "support", logger) + + if s.NoError(err) { + s.dal = dal + result, err := s.dal.InsertAccount(context.Background(), s.args) + if s.NoError(err) { + s.args = result + } + } +} + +func (s *AccountTestSuite) TestGet() { + r, err := s.dal.GetAccount(context.Background(), s.args.ID) + s.NoError(err) + s.NotNil(r) +} + +func (s *AccountTestSuite) TestGetByUserID() { + r, err := s.dal.GetAccountByUserID(context.Background(), s.args.UserID) + s.NoError(err) + s.NotNil(r) +} + +func (s *AccountTestSuite) TestGetAccountPage() { + r, err := s.dal.GetAccountPage(context.Background(), "", 0, 10) + s.NoError(err) + s.NotNil(r) +} + +func (s *AccountTestSuite) TestSetAccountRole() { + arg := "admin" + r, err := s.dal.SetAccountRole(context.Background(), s.args.UserID, arg) + s.NoError(err) + if s.NotNil(r) { + s.Equal(arg, r.Role) + } +} + +func (s *AccountTestSuite) TestSetAccountNickname() { + arg := "NewNickname" + r, err := s.dal.SetAccountNickname(context.Background(), s.args.UserID, arg) + s.NoError(err) + if s.NotNil(r) { + s.Equal(arg, r.Nickname) + } +} + +func (s *AccountTestSuite) TestSetAccountAvatar() { + arg := "/new/avatar.jpeg" + r, err := s.dal.SetAccountAvatar(context.Background(), s.args.UserID, arg) + s.NoError(err) + if s.NotNil(r) { + s.Equal(arg, r.Avatar) + } + +} + +func (s *AccountTestSuite) TestSetAccountDelete() { + arg := true + r, err := s.dal.SetAccountDelete(context.Background(), s.args.UserID, arg) + s.NoError(err) + if s.NotNil(r) { + s.Equal(arg, r.IsDeleted) + } +} + +func (s *AccountTestSuite) TearDownSuite() { + r, err := s.dal.DeleteAccount(context.Background(), s.args.UserID) + s.NoError(err) + s.NotNil(r) +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go new file mode 100644 index 0000000..202a27c --- /dev/null +++ b/internal/server/http/http_server.go @@ -0,0 +1,69 @@ +package http + +import ( + "context" + "fmt" + "github.com/gofiber/fiber/v2" + "github.com/themakers/hlog" + "go.uber.org/zap" + "heruvym/internal/utils/middleware" +) + +type ServerConfig struct { + Logger *zap.Logger + Controllers []Controller + Hlogger hlog.Logger + JWT *middleware.Middleware +} + +type Server struct { + Logger *zap.Logger + Controllers []Controller + app *fiber.App +} + +func NewServer(config ServerConfig) *Server { + app := fiber.New() + s := &Server{ + Logger: config.Logger, + Controllers: config.Controllers, + app: app, + } + + s.registerRoutes() + + return s +} + +func (s *Server) Start(addr string) error { + if err := s.app.Listen(addr); err != nil { + s.Logger.Error("Failed to start server", zap.Error(err)) + return err + } + return nil +} + +func (s *Server) Shutdown(ctx context.Context) error { + return s.app.Shutdown() +} + +func (s *Server) registerRoutes() { + for _, c := range s.Controllers { + router := s.app.Group(c.Name()) + c.Register(router) + } +} + +type Controller interface { + Register(router fiber.Router) + Name() string +} + +func (s *Server) ListRoutes() { + fmt.Println("Registered routes:") + for _, stack := range s.app.Stack() { + for _, route := range stack { + fmt.Printf("%s %s\n", route.Method, route.Path) + } + } +} diff --git a/jwt_adapter/jwt_adapter.go b/internal/utils/jwt_adapter/jwt_adapter.go similarity index 100% rename from jwt_adapter/jwt_adapter.go rename to internal/utils/jwt_adapter/jwt_adapter.go diff --git a/middleware/fiber_middleware.go b/internal/utils/middleware/fiber_middleware.go similarity index 97% rename from middleware/fiber_middleware.go rename to internal/utils/middleware/fiber_middleware.go index d17898f..ab8b627 100644 --- a/middleware/fiber_middleware.go +++ b/internal/utils/middleware/fiber_middleware.go @@ -3,7 +3,7 @@ package middleware import ( "github.com/gofiber/fiber/v2" "github.com/rs/xid" - "heruvym/jwt_adapter" + "heruvym/internal/utils/jwt_adapter" "strings" "time" ) diff --git a/middleware/hlogger.go b/internal/utils/middleware/hlogger.go similarity index 100% rename from middleware/hlogger.go rename to internal/utils/middleware/hlogger.go diff --git a/middleware/http_middleware.go b/internal/utils/middleware/http_middleware.go similarity index 99% rename from middleware/http_middleware.go rename to internal/utils/middleware/http_middleware.go index 070fb64..ce0b415 100644 --- a/middleware/http_middleware.go +++ b/internal/utils/middleware/http_middleware.go @@ -3,7 +3,7 @@ package middleware import ( "fmt" "github.com/gofiber/fiber/v2" - "heruvym/jwt_adapter" + "heruvym/internal/utils/jwt_adapter" "strings" "time" diff --git a/middleware/middleware.go b/internal/utils/middleware/middleware.go similarity index 85% rename from middleware/middleware.go rename to internal/utils/middleware/middleware.go index 9b39c66..57ecfd9 100644 --- a/middleware/middleware.go +++ b/internal/utils/middleware/middleware.go @@ -3,7 +3,7 @@ package middleware import ( "github.com/gofiber/fiber/v2" "github.com/themakers/hlog" - "heruvym/jwt_adapter" + "heruvym/internal/utils/jwt_adapter" ) // todo useless? diff --git a/middleware/sse_middleware.go b/internal/utils/middleware/sse_middleware.go similarity index 93% rename from middleware/sse_middleware.go rename to internal/utils/middleware/sse_middleware.go index 03a302f..f4fed78 100644 --- a/middleware/sse_middleware.go +++ b/internal/utils/middleware/sse_middleware.go @@ -2,7 +2,7 @@ package middleware import ( "github.com/gofiber/fiber/v2" - "heruvym/jwt_adapter" + "heruvym/internal/utils/jwt_adapter" ) func (mw *Middleware) MiddlewareGetJwt(c *fiber.Ctx) error { diff --git a/pkg/closer/closer.go b/pkg/closer/closer.go new file mode 100644 index 0000000..fdfbaf1 --- /dev/null +++ b/pkg/closer/closer.go @@ -0,0 +1,37 @@ +package closer + +import ( + "context" +) + +type Closer interface { + Close(ctx context.Context) error +} + +type CloserFunc func(ctx context.Context) error + +func (cf CloserFunc) Close(ctx context.Context) error { + return cf(ctx) +} + +type CloserGroup struct { + closers []Closer +} + +func NewCloserGroup() *CloserGroup { + return &CloserGroup{} +} + +func (cg *CloserGroup) Add(c Closer) { + cg.closers = append(cg.closers, c) +} + +func (cg *CloserGroup) Call(ctx context.Context) error { + var closeErr error + for i := len(cg.closers) - 1; i >= 0; i-- { + if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil { + closeErr = err + } + } + return closeErr +} diff --git a/service/service.go b/service/service.go index bab97e0..fc1ad6e 100644 --- a/service/service.go +++ b/service/service.go @@ -10,8 +10,8 @@ import ( "github.com/go-redis/redis/v8" "heruvym/dal/minio" "heruvym/dal/mongo" - "heruvym/jwt_adapter" - "heruvym/middleware" + "heruvym/internal/utils/jwt_adapter" + "heruvym/internal/utils/middleware" "heruvym/model" "heruvym/tools" "heruvym/utils" @@ -54,6 +54,11 @@ func New( func (h Heruvym) Register(m *http.ServeMux) *http.ServeMux { + //"/requestScreenshot": tools.HandlerWrapper(heruvym.RequestScreenshot), ???? + //"/sendFiles": heruvym.PutFile, ???? + //"/sendSC": heruvym.PutSC, ???? + //"/shown": tools.HandlerWrapper(heruvym.SetShown), ???? + m.HandleFunc("/create", h.CreateTicket) m.HandleFunc("/subscribe", tools.SseWrapper(h.GetList)) m.HandleFunc("/ticket", tools.SseWrapper(h.Subscribe)) @@ -131,7 +136,7 @@ func (h *Heruvym) CreateTicket(w http.ResponseWriter, r *http.Request) { ctx, session.Id, session.Id, - r.Header["Origin"][0], + r.Header["Origin"][0], request.Title, request.Message, []string{}, @@ -152,43 +157,43 @@ func (h *Heruvym) CreateTicket(w http.ResponseWriter, r *http.Request) { return } - domain := ctx.Value(middleware.HostKey).(string) - if domain == "" { - fmt.Println("domain is nil err") - } - - go func() { - if session.Id != "" && role != "admin" { - if err == nil && h.notifier != nil { - var userLink, supportLink string - if session.StandardClaims.Issuer != "" { - fmt.Println("MABNAT", domain) - if domain[0] == 's' { - userLink = fmt.Sprintf("https://sadmin.pena/users/%s", session.Id) - supportLink = fmt.Sprintf("https://sadmin.pena/support/%s", ticketID) - } else { - userLink = fmt.Sprintf("https://admin.pena/users/%s", session.Id) - supportLink = fmt.Sprintf("https://admin.pena/support/%s", ticketID) - } - } else { - if domain[0] == 's' { - supportLink = fmt.Sprintf("https://sadmin.pena/support/%s", ticketID) - } else { - supportLink = fmt.Sprintf("https://admin.pena/support/%s", ticketID) - } - userLink = "незарегистрированного пользователя" - } - - message := fmt.Sprintf("Вам пришло сообщение от %s сссылка на пользователя с %s, ccылка на чат - %s", - userLink, domain, supportLink) - - if _, err := h.notifier.Send(tb.ChatID(h.tgChatID), message); err != nil { - fmt.Println("CAN NOT NOTIFY", err) - } - return - } + domain := ctx.Value(middleware.HostKey).(string) + if domain == "" { + fmt.Println("domain is nil err") } - }() + + go func() { + if session.Id != "" && role != "admin" { + if err == nil && h.notifier != nil { + var userLink, supportLink string + if session.StandardClaims.Issuer != "" { + fmt.Println("MABNAT", domain) + if domain[0] == 's' { + userLink = fmt.Sprintf("https://sadmin.pena/users/%s", session.Id) + supportLink = fmt.Sprintf("https://sadmin.pena/support/%s", ticketID) + } else { + userLink = fmt.Sprintf("https://admin.pena/users/%s", session.Id) + supportLink = fmt.Sprintf("https://admin.pena/support/%s", ticketID) + } + } else { + if domain[0] == 's' { + supportLink = fmt.Sprintf("https://sadmin.pena/support/%s", ticketID) + } else { + supportLink = fmt.Sprintf("https://admin.pena/support/%s", ticketID) + } + userLink = "незарегистрированного пользователя" + } + + message := fmt.Sprintf("Вам пришло сообщение от %s сссылка на пользователя с %s, ccылка на чат - %s", + userLink, domain, supportLink) + + if _, err := h.notifier.Send(tb.ChatID(h.tgChatID), message); err != nil { + fmt.Println("CAN NOT NOTIFY", err) + } + return + } + } + }() } else { ticketID = tickets[0].ID @@ -353,13 +358,13 @@ func (h *Heruvym) PutMessage( return errors.New("can not put message"), http.StatusInternalServerError } - role := jwt_adapter.GetRole(ctx) + role := jwt_adapter.GetRole(ctx) go func() { if sess.Id != "" && role != "admin" { if err == nil && h.notifier != nil { - var userLink,supportLink string + var userLink, supportLink string if sess.StandardClaims.Issuer != "" { - fmt.Println("MABNAT", domain) + fmt.Println("MABNAT", domain) if domain[0] == 's' { userLink = fmt.Sprintf("https://sadmin.pena/users/%s", sess.Id) supportLink = fmt.Sprintf("https://sadmin.pena/support/%s", request.TicketID) @@ -377,7 +382,7 @@ func (h *Heruvym) PutMessage( } message := fmt.Sprintf("Вам пришло сообщение от %s сссылка на пользователя с %s, ссылка на чат - %s", - userLink, domain, supportLink) + userLink, domain, supportLink) if _, err := h.notifier.Send(tb.ChatID(h.tgChatID), message); err != nil { fmt.Println("CAN NOT NOTIFY", err) @@ -867,10 +872,10 @@ func (h *Heruvym) PutFile(w http.ResponseWriter, r *http.Request) { if _, err := w.Write(resp); err != nil { fmt.Println("CAN NOT WRITE", err) } - m.Lock() - defer m.Unlock() - fileIDs = append(fileIDs, filename) - filenames = append(filenames, name) + m.Lock() + defer m.Unlock() + fileIDs = append(fileIDs, filename) + filenames = append(filenames, name) return } @@ -889,10 +894,10 @@ func (h *Heruvym) PutFile(w http.ResponseWriter, r *http.Request) { }() } wg.Wait() - - if len(fileIDs) == 0 { - return - } + + if len(fileIDs) == 0 { + return + } if errFile != nil { w.WriteHeader(http.StatusInternalServerError) @@ -938,13 +943,13 @@ func (h *Heruvym) PutFile(w http.ResponseWriter, r *http.Request) { if domain == "" { fmt.Println("domain is nil err") } - role := jwt_adapter.GetRole(ctx) + role := jwt_adapter.GetRole(ctx) go func() { if sess.Id != "" && role != "admin" { if err == nil && h.notifier != nil { var userLink, supportLink string if sess.StandardClaims.Issuer != "" { - fmt.Println("MABNAT", domain) + fmt.Println("MABNAT", domain) if domain[0] == 's' { userLink = fmt.Sprintf("https://sadmin.pena/users/%s", sess.Id) supportLink = fmt.Sprintf("https://sadmin.pena/support/%s", req.Ticket) @@ -962,7 +967,7 @@ func (h *Heruvym) PutFile(w http.ResponseWriter, r *http.Request) { } message := fmt.Sprintf("Вам пришло сообщение от %s сссылка на пользователя с %s, ccылка на чат - %s", - userLink, domain, supportLink) + userLink, domain, supportLink) if _, err := h.notifier.Send(tb.ChatID(h.tgChatID), message); err != nil { fmt.Println("CAN NOT NOTIFY", err) @@ -972,7 +977,6 @@ func (h *Heruvym) PutFile(w http.ResponseWriter, r *http.Request) { } }() - if _, err := w.Write(resp); err != nil { fmt.Println("CAN NOT WRITE", err) } diff --git a/service/service_account.go b/service/service_account.go index 9af6d88..e6209b5 100644 --- a/service/service_account.go +++ b/service/service_account.go @@ -4,7 +4,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "heruvym/dal/mongo" - "heruvym/jwt_adapter" + "heruvym/internal/utils/jwt_adapter" "heruvym/model" "reflect" ) diff --git a/service/service_account_test.go b/service/service_account_test.go index e8fba79..e569270 100644 --- a/service/service_account_test.go +++ b/service/service_account_test.go @@ -9,7 +9,7 @@ import ( "github.com/themakers/hlog" "go.uber.org/zap" "heruvym/dal/mongo" - "heruvym/middleware" + "heruvym/internal/utils/middleware" "heruvym/model" "net/http" "testing" diff --git a/test/main_test.go b/test/main_test.go index 638d53f..aa06ae8 100644 --- a/test/main_test.go +++ b/test/main_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "github.com/skeris/appInit" + "heruvym/internal/utils/jwt_adapter" "heruvym/model" "io" "io/ioutil" @@ -23,7 +24,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/themakers/bdd" - "heruvym/jwt_adapter" ) ///create+