From 0b96da9e2458f0b39215b47fdb192f9c5ffca97a Mon Sep 17 00:00:00 2001 From: Gvidow Date: Sun, 24 Sep 2023 20:36:21 +0300 Subject: [PATCH 001/266] introduced the structure --- .gitignore | 2 + Makefile | 5 ++ cmd/app/config.go | 13 ++++++ cmd/app/main.go | 30 ++++++++++++ configs/config.yml | 4 ++ go.mod | 17 +++++++ go.sum | 61 +++++++++++++++++++++++++ internal/api/server/config.go | 18 ++++++++ internal/api/server/router/router.go | 33 +++++++++++++ internal/api/server/server.go | 41 +++++++++++++++++ internal/pkg/entity/pin/pin.go | 3 ++ internal/pkg/entity/user/user.go | 3 ++ internal/pkg/repository/pin/repo.go | 3 ++ internal/pkg/repository/session/repo.go | 3 ++ internal/pkg/repository/user/repo.go | 3 ++ internal/pkg/service/auth.go | 21 +++++++++ internal/pkg/service/pin.go | 16 +++++++ internal/pkg/service/service.go | 24 ++++++++++ internal/usecases/pin/usecase.go | 16 +++++++ internal/usecases/session/manager.go | 22 +++++++++ internal/usecases/user/usecase.go | 13 ++++++ pkg/logger/logger.go | 19 ++++++++ 22 files changed, 370 insertions(+) create mode 100644 Makefile create mode 100644 cmd/app/config.go create mode 100644 cmd/app/main.go create mode 100644 configs/config.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/api/server/config.go create mode 100644 internal/api/server/router/router.go create mode 100644 internal/api/server/server.go create mode 100644 internal/pkg/entity/pin/pin.go create mode 100644 internal/pkg/entity/user/user.go create mode 100644 internal/pkg/repository/pin/repo.go create mode 100644 internal/pkg/repository/session/repo.go create mode 100644 internal/pkg/repository/user/repo.go create mode 100644 internal/pkg/service/auth.go create mode 100644 internal/pkg/service/pin.go create mode 100644 internal/pkg/service/service.go create mode 100644 internal/usecases/pin/usecase.go create mode 100644 internal/usecases/session/manager.go create mode 100644 internal/usecases/user/usecase.go create mode 100644 pkg/logger/logger.go diff --git a/.gitignore b/.gitignore index 3b735ec..ad148f2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ # Go workspace file go.work + +bin/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a2f628 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + go build -o bin/app cmd/app/*.go + +run: build + ./bin/app diff --git a/cmd/app/config.go b/cmd/app/config.go new file mode 100644 index 0000000..95115f6 --- /dev/null +++ b/cmd/app/config.go @@ -0,0 +1,13 @@ +package main + +import "go.uber.org/config" + +var configFiles = []string{"configs/config.yml"} + +func newConfig() (*config.YAML, error) { + cfgOption := make([]config.YAMLOption, 0, len(configFiles)) + for _, filename := range configFiles { + cfgOption = append(cfgOption, config.File(filename)) + } + return config.NewYAML(cfgOption...) +} diff --git a/cmd/app/main.go b/cmd/app/main.go new file mode 100644 index 0000000..40c160a --- /dev/null +++ b/cmd/app/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "os" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func main() { + log, err := logger.New() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + cfg, err := newConfig() + if err != nil { + log.Fatal(err.Error()) + } + + service := service.New(log, nil, nil, nil) + server := server.New(log, *server.NewConfig(cfg)) + server.InitRouter(service) + if err := server.Run(); err != nil { + log.Fatal(err.Error()) + } +} diff --git a/configs/config.yml b/configs/config.yml new file mode 100644 index 0000000..8bc47d0 --- /dev/null +++ b/configs/config.yml @@ -0,0 +1,4 @@ +app: + server: + host: localhost + port: 8080 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fc3849a --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/go-park-mail-ru/2023_2_OND_team + +go 1.19 + +require ( + github.com/go-chi/chi/v5 v5.0.10 + go.uber.org/config v1.4.0 + go.uber.org/zap v1.26.0 +) + +require ( + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect + golang.org/x/text v0.3.2 // indirect + golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0 // indirect + gopkg.in/yaml.v2 v2.2.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..84cfb9c --- /dev/null +++ b/go.sum @@ -0,0 +1,61 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= +go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +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/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0 h1:azkp5oIgy7LNGQ64URezZccjePaEGSYIHEgYTn/bfXI= +golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/api/server/config.go b/internal/api/server/config.go new file mode 100644 index 0000000..6cba91d --- /dev/null +++ b/internal/api/server/config.go @@ -0,0 +1,18 @@ +package server + +import "go.uber.org/config" + +type Config struct { + Host string + Port string +} + +const ConfigName = "app.server" + +func NewConfig(cfg *config.YAML) *Config { + value := cfg.Get(ConfigName) + return &Config{ + Host: value.Get("host").String(), + Port: value.Get("port").String(), + } +} diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go new file mode 100644 index 0000000..a2d2fa3 --- /dev/null +++ b/internal/api/server/router/router.go @@ -0,0 +1,33 @@ +package router + +import ( + "github.com/go-chi/chi/v5" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" +) + +type Router struct { + Mux *chi.Mux +} + +func New() Router { + return Router{chi.NewMux()} +} + +func (r Router) InitRoute(serv *service.Service) { + r.Mux.Route("/api/v1", func(r chi.Router) { + r.Route("/auth", func(r chi.Router) { + r.Post("/login", serv.Login) + r.Post("/signup", serv.Signup) + r.Delete("/logout", serv.Logout) + }) + + r.Route("/pin", func(r chi.Router) { + r.Get("/", serv.GetPins) + r.Get("/{pinID:\\d+}", serv.GetPinByID) + }) + }) +} + +func (r Router) IsInit() bool { + return r.Mux != nil +} diff --git a/internal/api/server/server.go b/internal/api/server/server.go new file mode 100644 index 0000000..05eb353 --- /dev/null +++ b/internal/api/server/server.go @@ -0,0 +1,41 @@ +package server + +import ( + "errors" + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +var ErrNotInitRouter = errors.New("there is no routing") + +type Server struct { + http.Server + router router.Router + log *logger.Logger +} + +func New(log *logger.Logger, cfg Config) *Server { + return &Server{ + Server: http.Server{ + Addr: cfg.Host + ":" + cfg.Port, + }, + router: router.New(), + log: log, + } +} + +func (s *Server) Run() error { + if !s.router.IsInit() { + return ErrNotInitRouter + } + s.Handler = s.router.Mux + s.log.Info("server start") + return s.ListenAndServe() +} + +func (s *Server) InitRouter(serv *service.Service) { + s.router.InitRoute(serv) +} diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go new file mode 100644 index 0000000..3038434 --- /dev/null +++ b/internal/pkg/entity/pin/pin.go @@ -0,0 +1,3 @@ +package pin + +type Pin struct{} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go new file mode 100644 index 0000000..998810f --- /dev/null +++ b/internal/pkg/entity/user/user.go @@ -0,0 +1,3 @@ +package user + +type User struct{} diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go new file mode 100644 index 0000000..c588314 --- /dev/null +++ b/internal/pkg/repository/pin/repo.go @@ -0,0 +1,3 @@ +package pin + +type Repository interface{} diff --git a/internal/pkg/repository/session/repo.go b/internal/pkg/repository/session/repo.go new file mode 100644 index 0000000..14762c7 --- /dev/null +++ b/internal/pkg/repository/session/repo.go @@ -0,0 +1,3 @@ +package session + +type Repository interface{} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go new file mode 100644 index 0000000..375ef1b --- /dev/null +++ b/internal/pkg/repository/user/repo.go @@ -0,0 +1,3 @@ +package user + +type Repository interface{} diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go new file mode 100644 index 0000000..aae7f22 --- /dev/null +++ b/internal/pkg/service/auth.go @@ -0,0 +1,21 @@ +package service + +import ( + "fmt" + "net/http" +) + +func (s *Service) Login(w http.ResponseWriter, r *http.Request) { + s.log.Info("it worked Login") + fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) +} + +func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { + s.log.Info("it worked Signup") + fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) +} + +func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { + s.log.Info("it worked Logout") + fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) +} diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go new file mode 100644 index 0000000..002c366 --- /dev/null +++ b/internal/pkg/service/pin.go @@ -0,0 +1,16 @@ +package service + +import ( + "fmt" + "net/http" +) + +func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { + s.log.Info("it worked GetPins") + fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) +} + +func (s *Service) GetPinByID(w http.ResponseWriter, r *http.Request) { + s.log.Info("it worked GetPinByID") + fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) +} diff --git a/internal/pkg/service/service.go b/internal/pkg/service/service.go new file mode 100644 index 0000000..05bb916 --- /dev/null +++ b/internal/pkg/service/service.go @@ -0,0 +1,24 @@ +package service + +import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type Service struct { + log *logger.Logger + userCase *user.Usecase + pinCase *pin.Usecase + sm *session.SessionManager +} + +func New(log *logger.Logger, sm *session.SessionManager, user *user.Usecase, pin *pin.Usecase) *Service { + return &Service{ + log: log, + userCase: user, + pinCase: pin, + sm: sm, + } +} diff --git a/internal/usecases/pin/usecase.go b/internal/usecases/pin/usecase.go new file mode 100644 index 0000000..023c445 --- /dev/null +++ b/internal/usecases/pin/usecase.go @@ -0,0 +1,16 @@ +package pin + +import ( + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" +) + +type Usecase struct { + repo repo.Repository +} + +func New(repo repo.Repository) *Usecase { + return &Usecase{repo} +} + +func (u *Usecase) GetByID() (*entity.Pin, error) { return nil, nil } diff --git a/internal/usecases/session/manager.go b/internal/usecases/session/manager.go new file mode 100644 index 0000000..d9616ef --- /dev/null +++ b/internal/usecases/session/manager.go @@ -0,0 +1,22 @@ +package session + +import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" +) + +type SessionManager struct { + repo session.Repository +} + +func New(repo *session.Repository) *SessionManager { + return &SessionManager{repo} +} + +func (sm *SessionManager) AddSession(userID int) error { + return nil +} + +func (sm *SessionManager) GetUserBySessionKey(sessionKey string) (*user.User, error) { + return nil, nil +} diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go new file mode 100644 index 0000000..eea5226 --- /dev/null +++ b/internal/usecases/user/usecase.go @@ -0,0 +1,13 @@ +package user + +import "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + +type Usecase struct { + repo user.Repository +} + +func New(repo user.Repository) *Usecase { + return &Usecase{repo} +} + +func (u *Usecase) Register() error { return nil } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..4e89cbb --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,19 @@ +package logger + +import ( + "fmt" + + "go.uber.org/zap" +) + +type Logger struct { + *zap.Logger +} + +func New() (*Logger, error) { + log, err := zap.NewProduction() + if err != nil { + return nil, fmt.Errorf("new Logger: %w", err) + } + return &Logger{Logger: log}, nil +} From b965443c646fb71fda4251206c45a41d2bfaceac Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 26 Sep 2023 11:50:35 +0300 Subject: [PATCH 002/266] add options for logger --- cmd/app/main.go | 3 ++- pkg/logger/logger.go | 8 ++++++-- pkg/logger/option.go | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 pkg/logger/option.go diff --git a/cmd/app/main.go b/cmd/app/main.go index 40c160a..a64d244 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -10,11 +10,12 @@ import ( ) func main() { - log, err := logger.New() + log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { fmt.Println(err) os.Exit(1) } + defer log.Sync() cfg, err := newConfig() if err != nil { diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 4e89cbb..4ebafde 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -10,8 +10,12 @@ type Logger struct { *zap.Logger } -func New() (*Logger, error) { - log, err := zap.NewProduction() +func New(options ...ConfigOption) (*Logger, error) { + cfg := zap.NewProductionConfig() + for _, opt := range options { + opt(&cfg) + } + log, err := cfg.Build() if err != nil { return nil, fmt.Errorf("new Logger: %w", err) } diff --git a/pkg/logger/option.go b/pkg/logger/option.go new file mode 100644 index 0000000..96db06c --- /dev/null +++ b/pkg/logger/option.go @@ -0,0 +1,20 @@ +package logger + +import ( + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type ConfigOption func(cfg *zap.Config) + +func SetFormatTime(layout string) ConfigOption { + return func(cfg *zap.Config) { + cfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout(layout) + } +} + +func RFC3339FormatTime() ConfigOption { + return SetFormatTime(time.RFC3339) +} From 5d6c2ac9590d1f536bf83591ed85b7509d732ee5 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 26 Sep 2023 13:29:26 +0300 Subject: [PATCH 003/266] added fields for entities and methods for their repositories --- internal/pkg/entity/pin/pin.go | 10 +++++++++- internal/pkg/entity/session/session.go | 9 +++++++++ internal/pkg/entity/user/user.go | 13 ++++++++++++- internal/pkg/repository/pin/repo.go | 11 ++++++++++- internal/pkg/repository/session/repo.go | 11 ++++++++++- internal/pkg/repository/user/repo.go | 11 ++++++++++- internal/usecases/session/manager.go | 2 +- 7 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 internal/pkg/entity/session/session.go diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 3038434..9523c66 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -1,3 +1,11 @@ package pin -type Pin struct{} +import "time" + +type Pin struct { + ID int + Picture string + Title string + Description string + PublicationTime time.Time +} diff --git a/internal/pkg/entity/session/session.go b/internal/pkg/entity/session/session.go new file mode 100644 index 0000000..b553345 --- /dev/null +++ b/internal/pkg/entity/session/session.go @@ -0,0 +1,9 @@ +package session + +import "time" + +type Session struct { + Key string + UserID int + Expire time.Time +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 998810f..41db263 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -1,3 +1,14 @@ package user -type User struct{} +import "time" + +type User struct { + ID int + Username string + Name string + Surname string + Email string + Avatar string + Password string + Birthday time.Time +} diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index c588314..5b1ddab 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -1,3 +1,12 @@ package pin -type Repository interface{} +import ( + "context" + "time" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" +) + +type Repository interface { + GetNPinsFromDate(ctx context.Context, count int, date time.Time) ([]pin.Pin, error) +} diff --git a/internal/pkg/repository/session/repo.go b/internal/pkg/repository/session/repo.go index 14762c7..921a6d8 100644 --- a/internal/pkg/repository/session/repo.go +++ b/internal/pkg/repository/session/repo.go @@ -1,3 +1,12 @@ package session -type Repository interface{} +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" +) + +type Repository interface { + CreateSessionForUser(ctx context.Context, userID int) (*session.Session, error) + GetUserIDBySessionKey(ctx context.Context, key string) (int, error) +} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 375ef1b..04ba85f 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -1,3 +1,12 @@ package user -type Repository interface{} +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +type Repository interface { + AddNewUser(ctx context.Context, user *user.User) error + GetUserByPasswordLogin(ctx context.Context, password, login string) (*user.User, error) +} diff --git a/internal/usecases/session/manager.go b/internal/usecases/session/manager.go index d9616ef..7d6a36a 100644 --- a/internal/usecases/session/manager.go +++ b/internal/usecases/session/manager.go @@ -9,7 +9,7 @@ type SessionManager struct { repo session.Repository } -func New(repo *session.Repository) *SessionManager { +func New(repo session.Repository) *SessionManager { return &SessionManager{repo} } From 0b16d2dc5e5d77e4a252478089691ab806e27cea Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 26 Sep 2023 17:24:09 +0300 Subject: [PATCH 004/266] TP-7d8: change entities with their repositories --- internal/pkg/entity/pin/pin.go | 1 + internal/pkg/repository/pin/repo.go | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 9523c66..1aa4c71 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -4,6 +4,7 @@ import "time" type Pin struct { ID int + AuthorID int Picture string Title string Description string diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 5b1ddab..2deef1e 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -2,11 +2,12 @@ package pin import ( "context" - "time" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) type Repository interface { - GetNPinsFromDate(ctx context.Context, count int, date time.Time) ([]pin.Pin, error) + GetNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) + GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) } From 8ca42aa710872b2b1817b3787aa374d7320fc455 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 26 Sep 2023 22:23:20 +0300 Subject: [PATCH 005/266] TP-7d8 update: interface repository for sessions --- internal/pkg/repository/session/repo.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/pkg/repository/session/repo.go b/internal/pkg/repository/session/repo.go index 921a6d8..5d3f0e1 100644 --- a/internal/pkg/repository/session/repo.go +++ b/internal/pkg/repository/session/repo.go @@ -9,4 +9,6 @@ import ( type Repository interface { CreateSessionForUser(ctx context.Context, userID int) (*session.Session, error) GetUserIDBySessionKey(ctx context.Context, key string) (int, error) + DeleteSessionByKey(ctx context.Context, key string) error + DeleteAllSessionForUser(ctx context.Context, userID int) error } From 1df35e1e8a099ce3e5748c7bd445ec82d4592290 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 28 Sep 2023 00:59:46 +0300 Subject: [PATCH 006/266] TP-5fa add: ram repository --- internal/pkg/repository/ramrepo/pin.go | 25 ++++++++ internal/pkg/repository/ramrepo/ramrepo.go | 70 ++++++++++++++++++++++ internal/pkg/repository/ramrepo/session.go | 52 ++++++++++++++++ internal/pkg/repository/ramrepo/user.go | 35 +++++++++++ internal/pkg/repository/session/repo.go | 4 +- internal/pkg/repository/user/repo.go | 7 ++- 6 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 internal/pkg/repository/ramrepo/pin.go create mode 100644 internal/pkg/repository/ramrepo/ramrepo.go create mode 100644 internal/pkg/repository/ramrepo/session.go create mode 100644 internal/pkg/repository/ramrepo/user.go diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go new file mode 100644 index 0000000..05c9690 --- /dev/null +++ b/internal/pkg/repository/ramrepo/pin.go @@ -0,0 +1,25 @@ +package ramrepo + +import ( + "context" + "database/sql" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +type ramPinRepo struct { + db *sql.DB +} + +func NewRamPinRepo(db *sql.DB) *ramPinRepo { + return &ramPinRepo{db} +} + +func (r *ramPinRepo) GetNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { + return nil, nil +} + +func (r *ramPinRepo) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { + return nil, nil +} diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go new file mode 100644 index 0000000..736b91e --- /dev/null +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -0,0 +1,70 @@ +package ramrepo + +import ( + "database/sql" + "fmt" + + _ "github.com/proullon/ramsql/driver" +) + +func OpenDB() (*sql.DB, error) { + db, err := sql.Open("ramsql", "RamRepository") + if err != nil { + return nil, err + } + + err = createUsersTable(db) + if err != nil { + return nil, err + } + + err = createPinTable(db) + if err != nil { + return nil, err + } + + err = createSessionTable(db) + if err != nil { + return nil, err + } + + return db, nil +} + +func createUsersTable(db *sql.DB) error { + _, err := db.Exec(`CREATE TABLE users( + id bigserial PRIMARY KEY, + username varchar(30) UNIQUE, + password varchar(50), + email varchar(50) UNIQUE, + avatar varchar(50) + );`) + if err != nil { + return fmt.Errorf("create table users: %w", err) + } + return nil +} + +func createPinTable(db *sql.DB) error { + _, err := db.Exec(`CREATE TABLE pin( + id bigserial PRIMARY KEY, + author int, + picture varchar(50) + );`) + if err != nil { + return fmt.Errorf("create table pin: %w", err) + } + return nil +} + +func createSessionTable(db *sql.DB) error { + _, err := db.Exec(`CREATE TABLE session( + session_key varchar(30) PRIMARY KEY, + user_id int, + expire timestamp + );`) + if err != nil { + return fmt.Errorf("create table session: %w", err) + } + return nil +} diff --git a/internal/pkg/repository/ramrepo/session.go b/internal/pkg/repository/ramrepo/session.go new file mode 100644 index 0000000..a4b0378 --- /dev/null +++ b/internal/pkg/repository/ramrepo/session.go @@ -0,0 +1,52 @@ +package ramrepo + +import ( + "context" + "database/sql" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" +) + +type ramSessionRepo struct { + db *sql.DB +} + +func NewRamSessionRepo(db *sql.DB) *ramSessionRepo { + return &ramSessionRepo{db} +} + +func (r *ramSessionRepo) AddSession(ctx context.Context, session *entity.Session) error { + _, err := r.db.ExecContext(ctx, "INSERT INTO session (session_key, user_id, expire) VALUES ($1, $2, $3);", + session.Key, session.UserID, session.Expire) + if err != nil { + return fmt.Errorf("save session in ram repository: %w", err) + } + return nil +} + +func (r *ramSessionRepo) GetSessionByKey(ctx context.Context, key string) (*entity.Session, error) { + row := r.db.QueryRowContext(ctx, "SELECT user_id, expire FROM session WHERE session_key = $1;", key) + session := &entity.Session{Key: key} + err := row.Scan(&session.UserID, &session.Expire) + if err != nil { + return nil, fmt.Errorf("get session from ram repository: %w", err) + } + return session, nil +} + +func (r *ramSessionRepo) DeleteSessionByKey(ctx context.Context, key string) error { + _, err := r.db.ExecContext(ctx, "DELETE FROM session WHERE session_key = $1;", key) + if err != nil { + return fmt.Errorf("delete session by key from ram repository: %w", err) + } + return nil +} + +func (r *ramSessionRepo) DeleteAllSessionForUser(ctx context.Context, userID int) error { + _, err := r.db.ExecContext(ctx, "DELETE FROM session WHERE user_id = $1;", userID) + if err != nil { + return fmt.Errorf("delete session by user id from ram repository: %w", err) + } + return nil +} diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go new file mode 100644 index 0000000..53e85ca --- /dev/null +++ b/internal/pkg/repository/ramrepo/user.go @@ -0,0 +1,35 @@ +package ramrepo + +import ( + "context" + "database/sql" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +type ramUserRepo struct { + db *sql.DB +} + +func NewRamUserRepo(db *sql.DB) *ramUserRepo { + return &ramUserRepo{db} +} + +func (r *ramUserRepo) AddNewUser(ctx context.Context, user *entity.User) error { + _, err := r.db.Exec("INSERT INTO users (username, password, email) VALUES ($1, $2, $3);", user.Username, user.Password, user.Email) + if err != nil { + return fmt.Errorf("adding a new user to the ram repository: %w", err) + } + return nil +} + +func (r *ramUserRepo) GetUserByUsername(ctx context.Context, username string) (*entity.User, error) { + row := r.db.QueryRowContext(ctx, "SELECT id, password, email FROM users WHERE username = $1;", username) + user := &entity.User{Username: username} + err := row.Scan(&user.ID, &user.Password, &user.Email) + if err != nil { + return nil, fmt.Errorf("getting a user from storage: %w", err) + } + return user, nil +} diff --git a/internal/pkg/repository/session/repo.go b/internal/pkg/repository/session/repo.go index 5d3f0e1..2dacb7a 100644 --- a/internal/pkg/repository/session/repo.go +++ b/internal/pkg/repository/session/repo.go @@ -7,8 +7,8 @@ import ( ) type Repository interface { - CreateSessionForUser(ctx context.Context, userID int) (*session.Session, error) - GetUserIDBySessionKey(ctx context.Context, key string) (int, error) + AddSession(ctx context.Context, session *session.Session) error + GetSessionByKey(ctx context.Context, key string) (*session.Session, error) DeleteSessionByKey(ctx context.Context, key string) error DeleteAllSessionForUser(ctx context.Context, userID int) error } diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 04ba85f..9e562ea 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -6,7 +6,12 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +type UserCredentials struct { + Username string + Password string +} + type Repository interface { AddNewUser(ctx context.Context, user *user.User) error - GetUserByPasswordLogin(ctx context.Context, password, login string) (*user.User, error) + GetUserByUsername(ctx context.Context, username string) (*user.User, error) } From 46e48be195bb088d97f077db4fa0f2df1f68e7cb Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 28 Sep 2023 01:01:26 +0300 Subject: [PATCH 007/266] TP-5fa add: methods for work with usecases --- internal/usecases/pin/usecase.go | 6 ++- internal/usecases/session/manager.go | 57 +++++++++++++++++++++++----- internal/usecases/user/usecase.go | 52 ++++++++++++++++++++++--- 3 files changed, 99 insertions(+), 16 deletions(-) diff --git a/internal/usecases/pin/usecase.go b/internal/usecases/pin/usecase.go index 023c445..5698566 100644 --- a/internal/usecases/pin/usecase.go +++ b/internal/usecases/pin/usecase.go @@ -3,14 +3,16 @@ package pin import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) type Usecase struct { + log *logger.Logger repo repo.Repository } -func New(repo repo.Repository) *Usecase { - return &Usecase{repo} +func New(log *logger.Logger, repo repo.Repository) *Usecase { + return &Usecase{log, repo} } func (u *Usecase) GetByID() (*entity.Pin, error) { return nil, nil } diff --git a/internal/usecases/session/manager.go b/internal/usecases/session/manager.go index 7d6a36a..9b670be 100644 --- a/internal/usecases/session/manager.go +++ b/internal/usecases/session/manager.go @@ -1,22 +1,61 @@ package session import ( - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" + "context" + "errors" + "fmt" + "time" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +const SessionLifeTime = 30 * 24 * time.Hour + +const lenSessionKey = 16 + +var ErrExpiredSession = errors.New("session lifetime expired") + type SessionManager struct { - repo session.Repository + log *logger.Logger + repo repo.Repository } -func New(repo session.Repository) *SessionManager { - return &SessionManager{repo} +func New(log *logger.Logger, repo repo.Repository) *SessionManager { + return &SessionManager{log, repo} +} + +func (sm *SessionManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { + sessionKey, err := crypto.NewRandomStr(lenSessionKey) + if err != nil { + return nil, fmt.Errorf("session key generation for new session: %w", err) + } + + session := &session.Session{ + Key: sessionKey, + UserID: userID, + Expire: time.Now().UTC().Add(SessionLifeTime), + } + err = sm.repo.AddSession(ctx, session) + if err != nil { + return nil, fmt.Errorf("creating a new session by the session manager: %w", err) + } + return session, nil } -func (sm *SessionManager) AddSession(userID int) error { - return nil +func (sm *SessionManager) GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) { + session, err := sm.repo.GetSessionByKey(ctx, sessionKey) + if err != nil { + return 0, fmt.Errorf("getting a session by a manager: %w", err) + } + if time.Now().UTC().After(session.Expire) { + return 0, ErrExpiredSession + } + return session.UserID, nil } -func (sm *SessionManager) GetUserBySessionKey(sessionKey string) (*user.User, error) { - return nil, nil +func (sm *SessionManager) DeleteUserSession(ctx context.Context, key string) error { + return sm.repo.DeleteSessionByKey(ctx, key) } diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go index eea5226..80218bb 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/usecases/user/usecase.go @@ -1,13 +1,55 @@ package user -import "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" +import ( + "context" + "errors" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +var ErrUserAuthentication = errors.New("user authentication") + +const ( + lenSalt = 16 + lenPasswordHash = 64 +) type Usecase struct { - repo user.Repository + log *logger.Logger + repo repo.Repository } -func New(repo user.Repository) *Usecase { - return &Usecase{repo} +func New(log *logger.Logger, repo repo.Repository) *Usecase { + return &Usecase{log, repo} } -func (u *Usecase) Register() error { return nil } +func (u *Usecase) Register(ctx context.Context, user *entity.User) error { + salt, err := crypto.NewRandomStr(lenSalt) + if err != nil { + return fmt.Errorf("generating salt for registration: %w", err) + } + + user.Password = salt + crypto.PasswordHash(user.Password, salt, lenPasswordHash) + err = u.repo.AddNewUser(ctx, user) + if err != nil { + return fmt.Errorf("user registration: %w", err) + } + return nil +} + +func (u *Usecase) Authentication(ctx context.Context, credentials repo.UserCredentials) (*entity.User, error) { + user, err := u.repo.GetUserByUsername(ctx, credentials.Username) + if err != nil { + return nil, fmt.Errorf("user authentication: %w", err) + } + salt := user.Password[:lenSalt] + if crypto.PasswordHash(credentials.Password, salt, lenPasswordHash) != user.Password[lenSalt:] { + return nil, ErrUserAuthentication + } + user.Password = "" + return user, nil +} From 695ffc758c4d263f305c0dc3dc025ee0a19a8f11 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 28 Sep 2023 01:03:32 +0300 Subject: [PATCH 008/266] TP-5fa add: handlers for authorization --- internal/pkg/service/auth.go | 148 +++++++++++++++++++++++++++++-- internal/pkg/service/response.go | 7 ++ 2 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 internal/pkg/service/response.go diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index aae7f22..f208312 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -1,21 +1,155 @@ package service import ( - "fmt" + "encoding/json" "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (s *Service) Login(w http.ResponseWriter, r *http.Request) { - s.log.Info("it worked Login") - fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) + s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + defer r.Body.Close() + params := repo.UserCredentials{} + err := json.NewDecoder(r.Body).Decode(¶ms) + if err != nil { + s.log.Info("failed to parse parameters", logger.F{"error", err.Error()}) + resBody, err := json.Marshal(map[string]any{ + "status": "error", + "code": "bad_params", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) + return + } + + user, err := s.userCase.Authentication(r.Context(), params) + if err != nil { + s.log.Warn(err.Error()) + resBody, err := json.Marshal(map[string]string{ + "status": "error", + "code": "user_authentication", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) + return + } + + session, err := s.sm.CreateNewSessionForUser(r.Context(), user.ID) + if err != nil { + s.log.Error(err.Error()) + resBody, err := json.Marshal(map[string]string{ + "status": "error", + "code": "create_session", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) + return + } + + cookie := &http.Cookie{ + Name: "session_key", + Value: session.Key, + HttpOnly: true, + } + http.SetCookie(w, cookie) + + resBody, err := json.Marshal(map[string]any{ + "status": "ok", + "comment": "set cookie", + "body": map[string]any{"user": user}, + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) } func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { - s.log.Info("it worked Signup") - fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) + s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + defer r.Body.Close() + user := &user.User{} + err := json.NewDecoder(r.Body).Decode(user) + if err != nil { + s.log.Info("failed to parse parameters", logger.F{"error", err.Error()}) + resBody, err := json.Marshal(map[string]string{ + "status": "error", + "code": "bad_params", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) + return + } + + err = s.userCase.Register(r.Context(), user) + if err != nil { + s.log.Warn(err.Error()) + resBody, err := json.Marshal(map[string]string{ + "status": "error", + "code": "register", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) + return + } + + resBody, err := json.Marshal(map[string]string{ + "status": "ok", + "comment": "the user is registered", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) } func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { - s.log.Info("it worked Logout") - fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) + s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + cookie, err := r.Cookie("session_key") + if err != nil { + s.log.Info("no cookie", logger.F{"error", err.Error()}) + resBody, err := json.Marshal(map[string]string{ + "status": "error", + "code": "no_cookie", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) + return + } + + err = s.sm.DeleteUserSession(r.Context(), cookie.Value) + if err != nil { + s.log.Error(err.Error()) + } + + cookie.Expires.AddDate(0, -1, 0) + http.SetCookie(w, cookie) + resBody, err := json.Marshal(map[string]string{ + "status": "ok", + "comment": "cookie delete", + }) + if err != nil { + s.log.Error(err.Error()) + } + w.Write(resBody) } diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go new file mode 100644 index 0000000..30ca631 --- /dev/null +++ b/internal/pkg/service/response.go @@ -0,0 +1,7 @@ +package service + +import "net/http" + +func SetContentTypeJSON(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") +} From 668ed64936ee5592917eb6940e88ffbc530bb961 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 28 Sep 2023 01:05:09 +0300 Subject: [PATCH 009/266] TP-5fa add: package pkg/crypto for keys, and hashs --- pkg/crypto/crypto.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 pkg/crypto/crypto.go diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go new file mode 100644 index 0000000..377dd0e --- /dev/null +++ b/pkg/crypto/crypto.go @@ -0,0 +1,29 @@ +package crypto + +import ( + "encoding/hex" + "math/rand" + "time" + "unsafe" + + "golang.org/x/crypto/argon2" +) + +func NewRandomStr(length int) (string, error) { + rand.Seed(time.Now().UTC().UnixNano()) + b := make([]byte, hex.DecodedLen(length)) + _, err := rand.Read(b) + if err != nil { + return "", err + } + res := make([]byte, length) + hex.Encode(res, b) + return *(*string)(unsafe.Pointer(&res)), nil +} + +func PasswordHash(password, salt string, length int) string { + passwordHash := make([]byte, length) + hex.Encode(passwordHash, argon2.IDKey([]byte(password), []byte(salt), 3, 32*1024, 4, + uint32(hex.DecodedLen(length)))) + return *(*string)(unsafe.Pointer(&passwordHash)) +} From 973673b3cb5b9c450cc3a9b27c02471a3bbb3570 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 28 Sep 2023 01:06:03 +0300 Subject: [PATCH 010/266] TP-5fa update module --- cmd/app/main.go | 16 +++++++++++++++- go.mod | 8 ++++++-- go.sum | 17 +++++++++++++++-- pkg/logger/logger.go | 18 ++++++++++++++++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index a64d244..79aa135 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -5,7 +5,11 @@ import ( "os" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -22,7 +26,17 @@ func main() { log.Fatal(err.Error()) } - service := service.New(log, nil, nil, nil) + db, err := ramrepo.OpenDB() + if err != nil { + log.Fatal(err.Error()) + } + defer db.Close() + + sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + userCase := user.New(log, ramrepo.NewRamUserRepo(db)) + pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) + + service := service.New(log, sm, userCase, pinCase) server := server.New(log, *server.NewConfig(cfg)) server.InitRouter(service) if err := server.Run(); err != nil { diff --git a/go.mod b/go.mod index fc3849a..02cd8b9 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,18 @@ go 1.19 require ( github.com/go-chi/chi/v5 v5.0.10 + github.com/proullon/ramsql v0.0.1 go.uber.org/config v1.4.0 go.uber.org/zap v1.26.0 + golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 ) require ( go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect - golang.org/x/text v0.3.2 // indirect - golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.6.0 // indirect gopkg.in/yaml.v2 v2.2.5 // indirect ) diff --git a/go.sum b/go.sum index 84cfb9c..3391c93 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= +github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -11,8 +13,11 @@ 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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= +github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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= @@ -29,26 +34,34 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0 h1:azkp5oIgy7LNGQ64URezZccjePaEGSYIHEgYTn/bfXI= golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 4ebafde..bbe7194 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -4,8 +4,14 @@ import ( "fmt" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) +type F struct { + FieldName string + Value string +} + type Logger struct { *zap.Logger } @@ -21,3 +27,15 @@ func New(options ...ConfigOption) (*Logger, error) { } return &Logger{Logger: log}, nil } + +func (log *Logger) Info(msg string, fields ...F) { + listFields := make([]zap.Field, 0, len(fields)) + for _, field := range fields { + listFields = append(listFields, zap.Field{ + Key: field.FieldName, + Type: zapcore.StringType, + String: field.Value, + }) + } + log.Logger.Info(msg, listFields...) +} From 8a28e7882aeb1250801eac6910e44f685217587e Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 02:21:35 +0300 Subject: [PATCH 011/266] added 'doc' target --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 6a2f628..708c4b6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,12 @@ +ENTRYPOINT=cmd/app/main.go +DOC_DIR=internal/api/docs + build: go build -o bin/app cmd/app/*.go run: build ./bin/app + +doc: + swag fmt + swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) \ No newline at end of file From c03124917a42c1acd30b48c7650a473bd4e269cb Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 02:21:55 +0300 Subject: [PATCH 012/266] added general API info --- cmd/app/main.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/app/main.go b/cmd/app/main.go index 79aa135..e63631c 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -13,6 +13,18 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +// @title Pinspire API +// @version 1.0 +// @description API for Pinspire project +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + func main() { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { From 482cedd3c16d57027822f0947f3faf3d7e5059e2 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 02:24:13 +0300 Subject: [PATCH 013/266] added struct tags, JsonResponse type, '/docs/*' endpoint, hadlers documentation --- internal/api/docs/docs.go | 470 +++++++++++++++++++++++++++ internal/api/docs/swagger.json | 444 +++++++++++++++++++++++++ internal/api/docs/swagger.yaml | 282 ++++++++++++++++ internal/api/server/router/router.go | 5 + internal/pkg/entity/pin/pin.go | 14 +- internal/pkg/entity/user/user.go | 18 +- internal/pkg/service/auth.go | 39 +++ internal/pkg/service/pin.go | 23 ++ internal/pkg/service/response.go | 18 +- 9 files changed, 1296 insertions(+), 17 deletions(-) create mode 100644 internal/api/docs/docs.go create mode 100644 internal/api/docs/swagger.json create mode 100644 internal/api/docs/swagger.yaml diff --git a/internal/api/docs/docs.go b/internal/api/docs/docs.go new file mode 100644 index 0000000..2ce9dc2 --- /dev/null +++ b/internal/api/docs/docs.go @@ -0,0 +1,470 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/auth/login": { + "post": { + "description": "User login, creating new session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "parameters": [ + { + "description": "Username", + "name": "username", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Password", + "name": "password", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/User" + } + } + } + ] + }, + "headers": { + "Session-id": { + "type": "string", + "description": "Auth cookie with new valid session id" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/auth/logout": { + "delete": { + "description": "User logout, session deletion", + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/Empty" + } + } + } + ] + }, + "headers": { + "Session-id": { + "type": "string", + "description": "Auth cookie with expired session id" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/auth/signup": { + "post": { + "description": "User registration", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "parameters": [ + { + "description": "Username", + "name": "username", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Email", + "name": "email", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Password", + "name": "password", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/Empty" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/pin": { + "get": { + "description": "Get pin collection", + "produces": [ + "application/json" + ], + "tags": [ + "Pin" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "type": "array", + "items": { + "$ref": "#/definitions/Pin" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/pin/{pinId}": { + "get": { + "description": "Get concrete pin by id", + "produces": [ + "application/json" + ], + "tags": [ + "Pin" + ], + "parameters": [ + { + "type": "integer", + "description": "Id of the pin", + "name": "pinId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/Pin" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + } + }, + "definitions": { + "Empty": { + "type": "object" + }, + "JsonErrResponse": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "0" + }, + "message": { + "type": "string", + "example": "Error description" + }, + "status": { + "type": "string", + "example": "error" + } + } + }, + "JsonResponse": { + "type": "object", + "properties": { + "body": {}, + "message": { + "type": "string", + "example": "Response message" + }, + "status": { + "type": "string", + "example": "ok" + } + } + }, + "Pin": { + "type": "object", + "properties": { + "authorId": { + "type": "integer", + "example": 23 + }, + "description": { + "type": "string", + "example": "about face" + }, + "id": { + "type": "integer", + "example": 55 + }, + "picture": { + "type": "string", + "example": "pinspire/imgs/image.png" + }, + "publicationTime": { + "type": "string" + }, + "title": { + "type": "string", + "example": "Nature's beauty" + } + } + }, + "User": { + "type": "object", + "properties": { + "avatar": { + "type": "string", + "example": "pinspire.online/avatars/avatar.jpg" + }, + "birthday": { + "type": "string" + }, + "email": { + "type": "string", + "example": "digital@gmail.com" + }, + "id": { + "type": "integer", + "example": 123 + }, + "name": { + "type": "string", + "example": "Peter" + }, + "password": { + "type": "string", + "example": "pass123" + }, + "surname": { + "type": "string", + "example": "Green" + }, + "username": { + "type": "string", + "example": "Green" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Pinspire API", + Description: "API for Pinspire project", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/internal/api/docs/swagger.json b/internal/api/docs/swagger.json new file mode 100644 index 0000000..bb5c717 --- /dev/null +++ b/internal/api/docs/swagger.json @@ -0,0 +1,444 @@ +{ + "swagger": "2.0", + "info": { + "description": "API for Pinspire project", + "title": "Pinspire API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/auth/login": { + "post": { + "description": "User login, creating new session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "parameters": [ + { + "description": "Username", + "name": "username", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Password", + "name": "password", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/User" + } + } + } + ] + }, + "headers": { + "Session-id": { + "type": "string", + "description": "Auth cookie with new valid session id" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/auth/logout": { + "delete": { + "description": "User logout, session deletion", + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/Empty" + } + } + } + ] + }, + "headers": { + "Session-id": { + "type": "string", + "description": "Auth cookie with expired session id" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/auth/signup": { + "post": { + "description": "User registration", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "parameters": [ + { + "description": "Username", + "name": "username", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Email", + "name": "email", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Password", + "name": "password", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/Empty" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/pin": { + "get": { + "description": "Get pin collection", + "produces": [ + "application/json" + ], + "tags": [ + "Pin" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "type": "array", + "items": { + "$ref": "#/definitions/Pin" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + }, + "/api/v1/pin/{pinId}": { + "get": { + "description": "Get concrete pin by id", + "produces": [ + "application/json" + ], + "tags": [ + "Pin" + ], + "parameters": [ + { + "type": "integer", + "description": "Id of the pin", + "name": "pinId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/Pin" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + } + } + }, + "definitions": { + "Empty": { + "type": "object" + }, + "JsonErrResponse": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "0" + }, + "message": { + "type": "string", + "example": "Error description" + }, + "status": { + "type": "string", + "example": "error" + } + } + }, + "JsonResponse": { + "type": "object", + "properties": { + "body": {}, + "message": { + "type": "string", + "example": "Response message" + }, + "status": { + "type": "string", + "example": "ok" + } + } + }, + "Pin": { + "type": "object", + "properties": { + "authorId": { + "type": "integer", + "example": 23 + }, + "description": { + "type": "string", + "example": "about face" + }, + "id": { + "type": "integer", + "example": 55 + }, + "picture": { + "type": "string", + "example": "pinspire/imgs/image.png" + }, + "publicationTime": { + "type": "string" + }, + "title": { + "type": "string", + "example": "Nature's beauty" + } + } + }, + "User": { + "type": "object", + "properties": { + "avatar": { + "type": "string", + "example": "pinspire.online/avatars/avatar.jpg" + }, + "birthday": { + "type": "string" + }, + "email": { + "type": "string", + "example": "digital@gmail.com" + }, + "id": { + "type": "integer", + "example": 123 + }, + "name": { + "type": "string", + "example": "Peter" + }, + "password": { + "type": "string", + "example": "pass123" + }, + "surname": { + "type": "string", + "example": "Green" + }, + "username": { + "type": "string", + "example": "Green" + } + } + } + } +} \ No newline at end of file diff --git a/internal/api/docs/swagger.yaml b/internal/api/docs/swagger.yaml new file mode 100644 index 0000000..dbffa50 --- /dev/null +++ b/internal/api/docs/swagger.yaml @@ -0,0 +1,282 @@ +definitions: + Empty: + type: object + JsonErrResponse: + properties: + code: + example: "0" + type: string + message: + example: Error description + type: string + status: + example: error + type: string + type: object + JsonResponse: + properties: + body: {} + message: + example: Response message + type: string + status: + example: ok + type: string + type: object + Pin: + properties: + authorId: + example: 23 + type: integer + description: + example: about face + type: string + id: + example: 55 + type: integer + picture: + example: pinspire/imgs/image.png + type: string + publicationTime: + type: string + title: + example: Nature's beauty + type: string + type: object + User: + properties: + avatar: + example: pinspire.online/avatars/avatar.jpg + type: string + birthday: + type: string + email: + example: digital@gmail.com + type: string + id: + example: 123 + type: integer + name: + example: Peter + type: string + password: + example: pass123 + type: string + surname: + example: Green + type: string + username: + example: Green + type: string + type: object +info: + contact: + email: support@swagger.io + name: API Support + url: http://www.swagger.io/support + description: API for Pinspire project + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + termsOfService: http://swagger.io/terms/ + title: Pinspire API + version: "1.0" +paths: + /api/v1/auth/login: + post: + consumes: + - application/json + description: User login, creating new session + parameters: + - description: Username + in: body + name: username + required: true + schema: + type: string + - description: Password + in: body + name: password + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: OK + headers: + Session-id: + description: Auth cookie with new valid session id + type: string + schema: + allOf: + - $ref: '#/definitions/JsonResponse' + - properties: + body: + $ref: '#/definitions/User' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/JsonErrResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/JsonErrResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/JsonErrResponse' + tags: + - Auth + /api/v1/auth/logout: + delete: + description: User logout, session deletion + produces: + - application/json + responses: + "200": + description: OK + headers: + Session-id: + description: Auth cookie with expired session id + type: string + schema: + allOf: + - $ref: '#/definitions/JsonResponse' + - properties: + body: + $ref: '#/definitions/Empty' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/JsonErrResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/JsonErrResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/JsonErrResponse' + tags: + - Auth + /api/v1/auth/signup: + post: + consumes: + - application/json + description: User registration + parameters: + - description: Username + in: body + name: username + required: true + schema: + type: string + - description: Email + in: body + name: email + required: true + schema: + type: string + - description: Password + in: body + name: password + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/JsonResponse' + - properties: + body: + $ref: '#/definitions/Empty' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/JsonErrResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/JsonErrResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/JsonErrResponse' + tags: + - Auth + /api/v1/pin: + get: + description: Get pin collection + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/JsonResponse' + - properties: + body: + items: + $ref: '#/definitions/Pin' + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/JsonErrResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/JsonErrResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/JsonErrResponse' + tags: + - Pin + /api/v1/pin/{pinId}: + get: + description: Get concrete pin by id + parameters: + - description: Id of the pin + in: path + name: pinId + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/JsonResponse' + - properties: + body: + $ref: '#/definitions/Pin' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/JsonErrResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/JsonErrResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/JsonErrResponse' + tags: + - Pin +swagger: "2.0" diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index a2d2fa3..cea749b 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -3,6 +3,9 @@ package router import ( "github.com/go-chi/chi/v5" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" + + _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/docs" + httpSwagger "github.com/swaggo/http-swagger" ) type Router struct { @@ -15,6 +18,8 @@ func New() Router { func (r Router) InitRoute(serv *service.Service) { r.Mux.Route("/api/v1", func(r chi.Router) { + r.Get("/docs/*", httpSwagger.WrapHandler) + r.Route("/auth", func(r chi.Router) { r.Post("/login", serv.Login) r.Post("/signup", serv.Signup) diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 1aa4c71..95ddb35 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -3,10 +3,10 @@ package pin import "time" type Pin struct { - ID int - AuthorID int - Picture string - Title string - Description string - PublicationTime time.Time -} + ID int `json:"id" example:"55"` + AuthorID int `json:"authorId" example:"23"` + Picture string `json:"picture" example:"pinspire/imgs/image.png"` + Title string `json:"title" example:"Nature's beauty"` + Description string `json:"description" example:"about face"` + PublicationTime time.Time `json:"publicationTime"` +} //@name Pin diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 41db263..9864fcd 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -3,12 +3,12 @@ package user import "time" type User struct { - ID int - Username string - Name string - Surname string - Email string - Avatar string - Password string - Birthday time.Time -} + ID int `json:"id" example:"123"` + Username string `json:"username" example:"Green"` + Name string `json:"name" example:"Peter"` + Surname string `json:"surname" example:"Green"` + Email string `json:"email" example:"digital@gmail.com"` + Avatar string `json:"avatar" example:"pinspire.online/avatars/avatar.jpg"` + Password string `json:"password" example:"pass123"` + Birthday time.Time `json:"birthday"` +} // @name User diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index f208312..e0d14ee 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -9,6 +9,20 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +// Login godoc +// +// @Description User login, creating new session +// @Tags Auth +// @Accept json +// @Produce json +// @Param username body string true "Username" +// @Param password body string true "Password" +// @Success 200 {object} JsonResponse{body=user.User} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Header 200 {string} Session-id "Auth cookie with new valid session id" +// @Router /api/v1/auth/login [post] func (s *Service) Login(w http.ResponseWriter, r *http.Request) { s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) SetContentTypeJSON(w) @@ -75,6 +89,20 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { w.Write(resBody) } +// SignUp godoc +// +// @Description User registration +// @Tags Auth +// @Accept json +// @Produce json +// @Param username body string true "Username" +// @Param email body string true "Email" +// @Param password body string true "Password" +// @Success 200 {object} JsonResponse{body=Empty} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Router /api/v1/auth/signup [post] func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) SetContentTypeJSON(w) @@ -119,6 +147,17 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { w.Write(resBody) } +// Logout godoc +// +// @Description User logout, session deletion +// @Tags Auth +// @Produce json +// @Success 200 {object} JsonResponse{body=Empty} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Header 200 {string} Session-id "Auth cookie with expired session id" +// @Router /api/v1/auth/logout [delete] func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) SetContentTypeJSON(w) diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go index 002c366..6f216f4 100644 --- a/internal/pkg/service/pin.go +++ b/internal/pkg/service/pin.go @@ -3,13 +3,36 @@ package service import ( "fmt" "net/http" + + _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" ) +// GetPins godoc +// +// @Description Get pin collection +// @Tags Pin +// @Produce json +// @Success 200 {object} JsonResponse{body=[]Pin} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Router /api/v1/pin [get] func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { s.log.Info("it worked GetPins") fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) } +// GetPinByID godoc +// +// @Description Get concrete pin by id +// @Tags Pin +// @Produce json +// @Param pinId path int true "Id of the pin" +// @Success 200 {object} JsonResponse{body=Pin} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Router /api/v1/pin/{pinId} [get] func (s *Service) GetPinByID(w http.ResponseWriter, r *http.Request) { s.log.Info("it worked GetPinByID") fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go index 30ca631..b1014fd 100644 --- a/internal/pkg/service/response.go +++ b/internal/pkg/service/response.go @@ -1,6 +1,22 @@ package service -import "net/http" +import ( + "net/http" +) + +type Empty struct{} // @name Empty + +type JsonResponse struct { + Status string `json:"status" example:"ok"` + Message string `json:"message" example:"Response message"` + Body interface{} `json:"body"` +} // @name JsonResponse + +type JsonErrResponse struct { + Status string `json:"status" example:"error"` + Message string `json:"message" example:"Error description"` + Code int `json:"code,string"` +} // @name JsonErrResponse func SetContentTypeJSON(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") From 69f1875e7e6dc475b2602b2e5eafde877dc036ce Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 02:24:52 +0300 Subject: [PATCH 014/266] updated dependencies --- go.mod | 24 ++++++++++++----- go.sum | 83 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 02cd8b9..2a2406c 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,29 @@ go 1.19 require ( github.com/go-chi/chi/v5 v5.0.10 github.com/proullon/ramsql v0.0.1 + github.com/swaggo/swag v1.16.2 go.uber.org/config v1.4.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 + golang.org/x/crypto v0.13.0 ) require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect + github.com/swaggo/http-swagger v1.3.4 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.6.0 // indirect - gopkg.in/yaml.v2 v2.2.5 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3391c93..d385c11 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +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= @@ -6,23 +9,62 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= @@ -34,41 +76,54 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= From 5992db3f82c3e15d54820980ea7fd7875b871bf2 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 02:27:56 +0300 Subject: [PATCH 015/266] added newline break --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 708c4b6..4922fae 100644 --- a/Makefile +++ b/Makefile @@ -9,4 +9,4 @@ run: build doc: swag fmt - swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) \ No newline at end of file + swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) From cbfdd512bd1228c3ef91e543132a0a1e9c35da42 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 19:49:11 +0300 Subject: [PATCH 016/266] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20get=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20/api/v1/auth/login,=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B5=D0=B3=D0=B8,=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BD=D0=B5=D1=81=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 2 +- {internal/api/docs => docs}/docs.go | 74 ++++++++++++++++-------- {internal/api/docs => docs}/swagger.json | 74 ++++++++++++++++-------- {internal/api/docs => docs}/swagger.yaml | 49 ++++++++++------ internal/api/server/router/router.go | 3 +- internal/pkg/entity/user/user.go | 12 ++-- internal/pkg/service/auth.go | 20 ++++++- internal/pkg/service/response.go | 2 +- 8 files changed, 156 insertions(+), 80 deletions(-) rename {internal/api/docs => docs}/docs.go (89%) rename {internal/api/docs => docs}/swagger.json (88%) rename {internal/api/docs => docs}/swagger.yaml (88%) diff --git a/Makefile b/Makefile index 4922fae..189347c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ ENTRYPOINT=cmd/app/main.go -DOC_DIR=internal/api/docs +DOC_DIR=./docs build: go build -o bin/app cmd/app/*.go diff --git a/internal/api/docs/docs.go b/docs/docs.go similarity index 89% rename from internal/api/docs/docs.go rename to docs/docs.go index 2ce9dc2..0b5232d 100644 --- a/internal/api/docs/docs.go +++ b/docs/docs.go @@ -25,6 +25,53 @@ const docTemplate = `{ "basePath": "{{.BasePath}}", "paths": { "/api/v1/auth/login": { + "get": { + "description": "User login, check authentication, get user info", + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/User" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + }, "post": { "description": "User login, creating new session", "consumes": [ @@ -68,14 +115,14 @@ const docTemplate = `{ "type": "object", "properties": { "body": { - "$ref": "#/definitions/User" + "$ref": "#/definitions/Empty" } } } ] }, "headers": { - "Session-id": { + "session_key": { "type": "string", "description": "Auth cookie with new valid session id" } @@ -415,33 +462,10 @@ const docTemplate = `{ "User": { "type": "object", "properties": { - "avatar": { - "type": "string", - "example": "pinspire.online/avatars/avatar.jpg" - }, - "birthday": { - "type": "string" - }, - "email": { - "type": "string", - "example": "digital@gmail.com" - }, - "id": { - "type": "integer", - "example": 123 - }, - "name": { - "type": "string", - "example": "Peter" - }, "password": { "type": "string", "example": "pass123" }, - "surname": { - "type": "string", - "example": "Green" - }, "username": { "type": "string", "example": "Green" diff --git a/internal/api/docs/swagger.json b/docs/swagger.json similarity index 88% rename from internal/api/docs/swagger.json rename to docs/swagger.json index bb5c717..06298f4 100644 --- a/internal/api/docs/swagger.json +++ b/docs/swagger.json @@ -17,6 +17,53 @@ }, "paths": { "/api/v1/auth/login": { + "get": { + "description": "User login, check authentication, get user info", + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/JsonResponse" + }, + { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/User" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/JsonErrResponse" + } + } + } + }, "post": { "description": "User login, creating new session", "consumes": [ @@ -60,14 +107,14 @@ "type": "object", "properties": { "body": { - "$ref": "#/definitions/User" + "$ref": "#/definitions/Empty" } } } ] }, "headers": { - "Session-id": { + "session_key": { "type": "string", "description": "Auth cookie with new valid session id" } @@ -407,33 +454,10 @@ "User": { "type": "object", "properties": { - "avatar": { - "type": "string", - "example": "pinspire.online/avatars/avatar.jpg" - }, - "birthday": { - "type": "string" - }, - "email": { - "type": "string", - "example": "digital@gmail.com" - }, - "id": { - "type": "integer", - "example": 123 - }, - "name": { - "type": "string", - "example": "Peter" - }, "password": { "type": "string", "example": "pass123" }, - "surname": { - "type": "string", - "example": "Green" - }, "username": { "type": "string", "example": "Green" diff --git a/internal/api/docs/swagger.yaml b/docs/swagger.yaml similarity index 88% rename from internal/api/docs/swagger.yaml rename to docs/swagger.yaml index dbffa50..28b4cb0 100644 --- a/internal/api/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -45,26 +45,9 @@ definitions: type: object User: properties: - avatar: - example: pinspire.online/avatars/avatar.jpg - type: string - birthday: - type: string - email: - example: digital@gmail.com - type: string - id: - example: 123 - type: integer - name: - example: Peter - type: string password: example: pass123 type: string - surname: - example: Green - type: string username: example: Green type: string @@ -83,6 +66,34 @@ info: version: "1.0" paths: /api/v1/auth/login: + get: + description: User login, check authentication, get user info + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/JsonResponse' + - properties: + body: + $ref: '#/definitions/User' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/JsonErrResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/JsonErrResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/JsonErrResponse' + tags: + - Auth post: consumes: - application/json @@ -106,7 +117,7 @@ paths: "200": description: OK headers: - Session-id: + session_key: description: Auth cookie with new valid session id type: string schema: @@ -114,7 +125,7 @@ paths: - $ref: '#/definitions/JsonResponse' - properties: body: - $ref: '#/definitions/User' + $ref: '#/definitions/Empty' type: object "400": description: Bad Request diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index cea749b..a8aabe3 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -4,7 +4,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" - _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/docs" + _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" httpSwagger "github.com/swaggo/http-swagger" ) @@ -21,6 +21,7 @@ func (r Router) InitRoute(serv *service.Service) { r.Get("/docs/*", httpSwagger.WrapHandler) r.Route("/auth", func(r chi.Router) { + r.Get("/login", serv.CheckLogin) r.Post("/login", serv.Login) r.Post("/signup", serv.Signup) r.Delete("/logout", serv.Logout) diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 9864fcd..ea7dace 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -3,12 +3,12 @@ package user import "time" type User struct { - ID int `json:"id" example:"123"` + ID int `json:"-" example:"123"` Username string `json:"username" example:"Green"` - Name string `json:"name" example:"Peter"` - Surname string `json:"surname" example:"Green"` - Email string `json:"email" example:"digital@gmail.com"` - Avatar string `json:"avatar" example:"pinspire.online/avatars/avatar.jpg"` + Name string `json:"-" example:"Peter"` + Surname string `json:"-" example:"Green"` + Email string `json:"-" example:"digital@gmail.com"` + Avatar string `json:"-" example:"pinspire.online/avatars/avatar.jpg"` Password string `json:"password" example:"pass123"` - Birthday time.Time `json:"birthday"` + Birthday time.Time `json:"-"` } // @name User diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index e0d14ee..cd12af6 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -2,6 +2,7 @@ package service import ( "encoding/json" + "fmt" "net/http" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -9,6 +10,21 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +// Login godoc +// +// @Description User login, check authentication, get user info +// @Tags Auth +// @Produce json +// @Success 200 {object} JsonResponse{body=user.User} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Router /api/v1/auth/login [get] +func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { + s.log.Info("it worked CheckLogin") + fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) +} + // Login godoc // // @Description User login, creating new session @@ -17,11 +33,11 @@ import ( // @Produce json // @Param username body string true "Username" // @Param password body string true "Password" -// @Success 200 {object} JsonResponse{body=user.User} +// @Success 200 {object} JsonResponse{body=Empty} // @Failure 400 {object} JsonErrResponse // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse -// @Header 200 {string} Session-id "Auth cookie with new valid session id" +// @Header 200 {string} session_key "Auth cookie with new valid session id" // @Router /api/v1/auth/login [post] func (s *Service) Login(w http.ResponseWriter, r *http.Request) { s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go index b1014fd..ccacd52 100644 --- a/internal/pkg/service/response.go +++ b/internal/pkg/service/response.go @@ -9,7 +9,7 @@ type Empty struct{} // @name Empty type JsonResponse struct { Status string `json:"status" example:"ok"` Message string `json:"message" example:"Response message"` - Body interface{} `json:"body"` + Body interface{} `json:"body,omitempty"` } // @name JsonResponse type JsonErrResponse struct { From 37b5c62a1aa87448cae390d414a268e9fcfe802e Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Sun, 1 Oct 2023 19:51:15 +0300 Subject: [PATCH 017/266] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D1=83=20=D0=BD=D0=B0=20?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 66f511f..ab4ab67 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # 2023_2_OND_team Backend проекта Pinterest команды OND team +Документация API: https://app.swaggerhub.com/apis/maks22036/Pinspire_API/1.0#/ From 1c32649e8a6e25bab4757278894b058828d3f628 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Sun, 1 Oct 2023 22:42:16 +0300 Subject: [PATCH 018/266] TP-363 del: endpoint /api/v1/pin/{pinID} by method GET --- internal/api/server/router/router.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index a8aabe3..02a545c 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -29,7 +29,6 @@ func (r Router) InitRoute(serv *service.Service) { r.Route("/pin", func(r chi.Router) { r.Get("/", serv.GetPins) - r.Get("/{pinID:\\d+}", serv.GetPinByID) }) }) } From 063abab4568e85a16c8583f34adc635699305ccb Mon Sep 17 00:00:00 2001 From: Gvidow Date: Sun, 1 Oct 2023 22:43:53 +0300 Subject: [PATCH 019/266] TP-363 add: handler for get pins on tape --- internal/pkg/repository/pin/repo.go | 2 +- internal/pkg/repository/ramrepo/pin.go | 23 +++++++-- internal/pkg/repository/ramrepo/ramrepo.go | 25 ++++++++++ internal/pkg/service/auth.go | 14 +++--- internal/pkg/service/pin.go | 54 +++++++++++++++++++++- internal/usecases/pin/usecase.go | 13 +++++- 6 files changed, 117 insertions(+), 14 deletions(-) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 2deef1e..50bda7a 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -8,6 +8,6 @@ import ( ) type Repository interface { - GetNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) + GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) } diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 05c9690..afa7a00 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -3,6 +3,8 @@ package ramrepo import ( "context" "database/sql" + "errors" + "fmt" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -16,10 +18,25 @@ func NewRamPinRepo(db *sql.DB) *ramPinRepo { return &ramPinRepo{db} } -func (r *ramPinRepo) GetNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { - return nil, nil +func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { + rows, err := r.db.QueryContext(ctx, "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;", afterPinID, count) + if err != nil { + return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) + } + + pins := []pin.Pin{} + pin := pin.Pin{} + for rows.Next() { + err := rows.Scan(&pin.ID, &pin.Picture) + if err != nil { + return pins, fmt.Errorf("scan to receive %d pins after %d: %w", count, afterPinID, err) + } + pins = append(pins, pin) + } + + return pins, nil } func (r *ramPinRepo) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { - return nil, nil + return nil, errors.New("unimplemented") } diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 736b91e..4ab99eb 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -28,6 +28,11 @@ func OpenDB() (*sql.DB, error) { return nil, err } + err = fillPinTableRows(db) + if err != nil { + return nil, err + } + return db, nil } @@ -68,3 +73,23 @@ func createSessionTable(db *sql.DB) error { } return nil } + +func fillPinTableRows(db *sql.DB) error { + _, err := db.Exec(`INSERT INTO pin (picture) VALUES + ('/1'), + ('/2'), + ('/3'), + ('/4'), + ('/5'), + ('/6'), + ('/7'), + ('/8'), + ('/9'), + ('/10'), + ('/11'), + ('/12');`) + if err != nil { + return fmt.Errorf("fill pin table: %w", err) + } + return nil +} diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index cd12af6..168b521 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -7,7 +7,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) // Login godoc @@ -40,14 +40,14 @@ func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { // @Header 200 {string} session_key "Auth cookie with new valid session id" // @Router /api/v1/auth/login [post] func (s *Service) Login(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) + s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) defer r.Body.Close() params := repo.UserCredentials{} err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - s.log.Info("failed to parse parameters", logger.F{"error", err.Error()}) + s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) resBody, err := json.Marshal(map[string]any{ "status": "error", "code": "bad_params", @@ -120,14 +120,14 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/signup [post] func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) + s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) defer r.Body.Close() user := &user.User{} err := json.NewDecoder(r.Body).Decode(user) if err != nil { - s.log.Info("failed to parse parameters", logger.F{"error", err.Error()}) + s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) resBody, err := json.Marshal(map[string]string{ "status": "error", "code": "bad_params", @@ -175,12 +175,12 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { // @Header 200 {string} Session-id "Auth cookie with expired session id" // @Router /api/v1/auth/logout [delete] func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}) + s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) cookie, err := r.Cookie("session_key") if err != nil { - s.log.Info("no cookie", logger.F{"error", err.Error()}) + s.log.Info("no cookie", log.F{"error", err.Error()}) resBody, err := json.Marshal(map[string]string{ "status": "error", "code": "no_cookie", diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go index 6f216f4..06e3d0b 100644 --- a/internal/pkg/service/pin.go +++ b/internal/pkg/service/pin.go @@ -1,12 +1,21 @@ package service import ( + "errors" "fmt" "net/http" + "net/url" + "strconv" + _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +var ErrCountParameterMissing = errors.New("the count parameter is missing") +var ErrBadParams = errors.New("bad params") + // GetPins godoc // // @Description Get pin collection @@ -18,8 +27,49 @@ import ( // @Failure 500 {object} JsonErrResponse // @Router /api/v1/pin [get] func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { - s.log.Info("it worked GetPins") - fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) + s.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + count, lastID, err := fetchValidParamForLoadTape(r.URL) + if err != nil { + s.log.Info("parse body error", log.F{"error", err.Error()}) + fmt.Fprintln(w, "{\"status\": \"error\"}") + return + } + + s.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) + + pins, last := s.pinCase.SelectNewPins(r.Context(), count, lastID) + fmt.Fprintf(w, `{"status": "ok", + "message": "download new pins", + "body": { + "pins": %v, + "lastID": %d + } + }`, pins, last) +} + +func fetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { + if param := u.Query().Get("count"); len(param) > 0 { + c, err := strconv.ParseInt(param, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) + } + count = int(c) + } else { + return 0, 0, ErrCountParameterMissing + } + if param := u.Query().Get("lastID"); len(param) > 0 { + last, err := strconv.ParseInt(param, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) + } + lastID = int(last) + } + if count <= 0 || lastID < 0 { + return 0, 0, ErrBadParams + } + return } // GetPinByID godoc diff --git a/internal/usecases/pin/usecase.go b/internal/usecases/pin/usecase.go index 5698566..2dbec72 100644 --- a/internal/usecases/pin/usecase.go +++ b/internal/usecases/pin/usecase.go @@ -1,6 +1,8 @@ package pin import ( + "context" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -15,4 +17,13 @@ func New(log *logger.Logger, repo repo.Repository) *Usecase { return &Usecase{log, repo} } -func (u *Usecase) GetByID() (*entity.Pin, error) { return nil, nil } +func (u *Usecase) SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) { + pins, err := u.repo.GetSortedNPinsAfterID(ctx, count, lastID) + if err != nil { + u.log.Error(err.Error()) + } + if len(pins) == 0 { + return []entity.Pin{}, lastID + } + return pins, pins[len(pins)-1].ID +} From 8e8d2f59302b9535f2683d9546aa36a286045974 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 02:49:05 +0300 Subject: [PATCH 020/266] TP-933 add: validation for auth handler --- internal/pkg/service/auth.go | 128 +++++++++++++++-------------- internal/pkg/service/validation.go | 70 ++++++++++++++++ 2 files changed, 136 insertions(+), 62 deletions(-) create mode 100644 internal/pkg/service/validation.go diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index 168b521..2845459 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -2,11 +2,11 @@ package service import ( "encoding/json" - "fmt" "net/http" + "time" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -21,8 +21,38 @@ import ( // @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/login [get] func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { - s.log.Info("it worked CheckLogin") - fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) + s.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + cookie, err := r.Cookie("session_key") + if err != nil { + s.log.Info("no cookie", log.F{"error", err.Error()}) + err = responseError(w, "no_auth", "the user is not logged in") + if err != nil { + s.log.Error(err.Error()) + } + return + } + + userID, err := s.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) + if err != nil { + err = responseError(w, "no_auth", "no user session found") + if err != nil { + s.log.Error(err.Error()) + } + return + } + + username, err := s.userCase.FindOutUserName(r.Context(), userID) + if err != nil { + s.log.Error(err.Error()) + err = responseError(w, "no_auth", "no user was found for this session") + } else { + err = responseOk(w, "user found", map[string]string{"username": username}) + } + if err != nil { + s.log.Error(err.Error()) + } } // Login godoc @@ -44,46 +74,43 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { SetContentTypeJSON(w) defer r.Body.Close() - params := repo.UserCredentials{} + params := usecase.NewCredentials() err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) - resBody, err := json.Marshal(map[string]any{ - "status": "error", - "code": "bad_params", - }) + err = responseError(w, "parse_body", "the correct username and password are expected to be received in JSON format") + if err != nil { + s.log.Error(err.Error()) + } + return + } + + if !isValidPassword(params.Password) || !isValidUsername(params.Username) { + s.log.Info(err.Error()) + err = responseError(w, "bad_credentials", "invalid user credentials") if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) return } user, err := s.userCase.Authentication(r.Context(), params) - if err != nil { + if err != nil || !isValidPassword(params.Password) || !isValidUsername(params.Username) { s.log.Warn(err.Error()) - resBody, err := json.Marshal(map[string]string{ - "status": "error", - "code": "user_authentication", - }) + err = responseError(w, "bad_credentials", "invalid user credentials") if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) return } session, err := s.sm.CreateNewSessionForUser(r.Context(), user.ID) if err != nil { s.log.Error(err.Error()) - resBody, err := json.Marshal(map[string]string{ - "status": "error", - "code": "create_session", - }) + err = responseError(w, "session", "failed to create a session for the user") if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) return } @@ -91,18 +118,17 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { Name: "session_key", Value: session.Key, HttpOnly: true, + Secure: true, + Path: "/", + Expires: session.Expire, + SameSite: http.SameSiteStrictMode, } http.SetCookie(w, cookie) - resBody, err := json.Marshal(map[string]any{ - "status": "ok", - "comment": "set cookie", - "body": map[string]any{"user": user}, - }) + err = responseOk(w, "a new session has been created for the user", nil) if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) } // SignUp godoc @@ -126,41 +152,25 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() user := &user.User{} err := json.NewDecoder(r.Body).Decode(user) - if err != nil { + if err != nil || !IsValidUserForRegistration(user) { s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) - resBody, err := json.Marshal(map[string]string{ - "status": "error", - "code": "bad_params", - }) + err = responseError(w, "parse_body", "the correct username, email and password are expected to be received in JSON format") if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) return } err = s.userCase.Register(r.Context(), user) if err != nil { s.log.Warn(err.Error()) - resBody, err := json.Marshal(map[string]string{ - "status": "error", - "code": "register", - }) - if err != nil { - s.log.Error(err.Error()) - } - w.Write(resBody) - return + err = responseError(w, "uniq_fields", "username") + } else { + err = responseOk(w, "the user has been successfully registered", nil) } - - resBody, err := json.Marshal(map[string]string{ - "status": "ok", - "comment": "the user is registered", - }) if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) } // Logout godoc @@ -175,36 +185,30 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { // @Header 200 {string} Session-id "Auth cookie with expired session id" // @Router /api/v1/auth/logout [delete] func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + s.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) cookie, err := r.Cookie("session_key") if err != nil { s.log.Info("no cookie", log.F{"error", err.Error()}) - resBody, err := json.Marshal(map[string]string{ - "status": "error", - "code": "no_cookie", - }) + err = responseError(w, "no_auth", "to log out, you must first log in") if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) return } + cookie.Expires = time.Now().UTC().AddDate(0, -1, 0) + http.SetCookie(w, cookie) + err = s.sm.DeleteUserSession(r.Context(), cookie.Value) if err != nil { s.log.Error(err.Error()) + err = responseError(w, "session", "the user logged out, but his session did not end") + } else { + err = responseOk(w, "the user has successfully logged out", nil) } - - cookie.Expires.AddDate(0, -1, 0) - http.SetCookie(w, cookie) - resBody, err := json.Marshal(map[string]string{ - "status": "ok", - "comment": "cookie delete", - }) if err != nil { s.log.Error(err.Error()) } - w.Write(resBody) } diff --git a/internal/pkg/service/validation.go b/internal/pkg/service/validation.go new file mode 100644 index 0000000..56f3920 --- /dev/null +++ b/internal/pkg/service/validation.go @@ -0,0 +1,70 @@ +package service + +import ( + "fmt" + "net/url" + "strconv" + "unicode" + + valid "github.com/asaskevich/govalidator" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { + if param := u.Query().Get("count"); len(param) > 0 { + c, err := strconv.ParseInt(param, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) + } + count = int(c) + } else { + return 0, 0, ErrCountParameterMissing + } + if param := u.Query().Get("lastID"); len(param) > 0 { + last, err := strconv.ParseInt(param, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) + } + lastID = int(last) + } + if count <= 0 || count > 1000 || lastID < 0 { + return 0, 0, ErrBadParams + } + return +} + +func IsValidUserForRegistration(user *user.User) bool { + return isValidPassword(user.Password) && isValidEmail(user.Email) && isValidUsername(user.Username) +} + +func IsValidUserForLogin(user *user.User) bool { + return isValidPassword(user.Password) && isValidUsername(user.Username) +} + +func isValidUsername(username string) bool { + if len(username) < 4 || len(username) > 50 { + return false + } + for _, r := range username { + if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r)) { + return false + } + } + return true +} + +func isValidEmail(email string) bool { + return valid.IsEmail(email) && len(email) <= 50 +} + +func isValidPassword(password string) bool { + if len(password) < 8 || len(password) > 50 { + return false + } + for _, r := range password { + if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r)) { + return false + } + } + return true +} From d120068a1f31b63201850e45f4c31258bd11e2d7 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:11:03 +0300 Subject: [PATCH 021/266] TP-933 update: move user credentials --- internal/usecases/user/credentials.go | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 internal/usecases/user/credentials.go diff --git a/internal/usecases/user/credentials.go b/internal/usecases/user/credentials.go new file mode 100644 index 0000000..9b5646b --- /dev/null +++ b/internal/usecases/user/credentials.go @@ -0,0 +1,10 @@ +package user + +type userCredentials struct { + Username string + Password string +} + +func NewCredentials() userCredentials { + return userCredentials{} +} From 3ad92c568cf981bfb933dc759461fa09767128e7 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:12:09 +0300 Subject: [PATCH 022/266] TP-933 update: user repository --- internal/pkg/repository/ramrepo/user.go | 10 ++++++++++ internal/pkg/repository/user/repo.go | 6 +----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index 53e85ca..5db8b71 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -33,3 +33,13 @@ func (r *ramUserRepo) GetUserByUsername(ctx context.Context, username string) (* } return user, nil } + +func (r *ramUserRepo) GetUsernameByID(ctx context.Context, userID int) (string, error) { + row := r.db.QueryRowContext(ctx, "SELECT username FROM users WHERE id = $1;", userID) + var username string + err := row.Scan(&username) + if err != nil { + return "", fmt.Errorf("getting a username from storage by id: %w", err) + } + return username, nil +} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 9e562ea..90cecd5 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -6,12 +6,8 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) -type UserCredentials struct { - Username string - Password string -} - type Repository interface { AddNewUser(ctx context.Context, user *user.User) error GetUserByUsername(ctx context.Context, username string) (*user.User, error) + GetUsernameByID(ctx context.Context, userID int) (string, error) } From b5dd4bfb829a714103b7a8b8069b15d2d3998ccd Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:15:15 +0300 Subject: [PATCH 023/266] TP-933 update: user usecase --- internal/usecases/user/usecase.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go index 80218bb..80f88fc 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/usecases/user/usecase.go @@ -41,7 +41,7 @@ func (u *Usecase) Register(ctx context.Context, user *entity.User) error { return nil } -func (u *Usecase) Authentication(ctx context.Context, credentials repo.UserCredentials) (*entity.User, error) { +func (u *Usecase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { user, err := u.repo.GetUserByUsername(ctx, credentials.Username) if err != nil { return nil, fmt.Errorf("user authentication: %w", err) @@ -53,3 +53,7 @@ func (u *Usecase) Authentication(ctx context.Context, credentials repo.UserCrede user.Password = "" return user, nil } + +func (u *Usecase) FindOutUserName(ctx context.Context, userID int) (string, error) { + return u.repo.GetUsernameByID(ctx, userID) +} From a96a250bd98892b7bb910daa5f046c8830ab5fa7 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:16:13 +0300 Subject: [PATCH 024/266] TP-933 update: entities --- internal/pkg/entity/pin/pin.go | 8 ++++---- internal/pkg/entity/user/user.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 95ddb35..218c827 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -4,9 +4,9 @@ import "time" type Pin struct { ID int `json:"id" example:"55"` - AuthorID int `json:"authorId" example:"23"` + AuthorID int `json:"-" example:"23"` Picture string `json:"picture" example:"pinspire/imgs/image.png"` - Title string `json:"title" example:"Nature's beauty"` - Description string `json:"description" example:"about face"` - PublicationTime time.Time `json:"publicationTime"` + Title string `json:"-" example:"Nature's beauty"` + Description string `json:"-" example:"about face"` + PublicationTime time.Time `json:"-"` } //@name Pin diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index ea7dace..a081d77 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -7,7 +7,7 @@ type User struct { Username string `json:"username" example:"Green"` Name string `json:"-" example:"Peter"` Surname string `json:"-" example:"Green"` - Email string `json:"-" example:"digital@gmail.com"` + Email string `json:"email" example:"digital@gmail.com"` Avatar string `json:"-" example:"pinspire.online/avatars/avatar.jpg"` Password string `json:"password" example:"pass123"` Birthday time.Time `json:"-"` From 726d78b2e4726315c299ad66202bcd891e0964ae Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:17:07 +0300 Subject: [PATCH 025/266] TP-933 update: handlers pin and format response --- internal/pkg/service/pin.go | 68 ++++++-------------------------- internal/pkg/service/response.go | 33 +++++++++++++++- 2 files changed, 44 insertions(+), 57 deletions(-) diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go index 06e3d0b..2bff1ae 100644 --- a/internal/pkg/service/pin.go +++ b/internal/pkg/service/pin.go @@ -2,12 +2,8 @@ package service import ( "errors" - "fmt" "net/http" - "net/url" - "strconv" - _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -30,60 +26,20 @@ func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { s.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) - count, lastID, err := fetchValidParamForLoadTape(r.URL) + count, lastID, err := FetchValidParamForLoadTape(r.URL) if err != nil { - s.log.Info("parse body error", log.F{"error", err.Error()}) - fmt.Fprintln(w, "{\"status\": \"error\"}") - return - } - - s.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) - - pins, last := s.pinCase.SelectNewPins(r.Context(), count, lastID) - fmt.Fprintf(w, `{"status": "ok", - "message": "download new pins", - "body": { - "pins": %v, - "lastID": %d - } - }`, pins, last) -} - -func fetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { - if param := u.Query().Get("count"); len(param) > 0 { - c, err := strconv.ParseInt(param, 10, 64) - if err != nil { - return 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) - } - count = int(c) + s.log.Info("parse url query params", log.F{"error", err.Error()}) + err = responseError(w, "bad_params", + "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)") } else { - return 0, 0, ErrCountParameterMissing + s.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) + pins, last := s.pinCase.SelectNewPins(r.Context(), count, lastID) + err = responseOk(w, "pins received are sorted by id", map[string]any{ + "pins": pins, + "lastID": last, + }) } - if param := u.Query().Get("lastID"); len(param) > 0 { - last, err := strconv.ParseInt(param, 10, 64) - if err != nil { - return 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) - } - lastID = int(last) - } - if count <= 0 || lastID < 0 { - return 0, 0, ErrBadParams + if err != nil { + s.log.Error(err.Error()) } - return -} - -// GetPinByID godoc -// -// @Description Get concrete pin by id -// @Tags Pin -// @Produce json -// @Param pinId path int true "Id of the pin" -// @Success 200 {object} JsonResponse{body=Pin} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/pin/{pinId} [get] -func (s *Service) GetPinByID(w http.ResponseWriter, r *http.Request) { - s.log.Info("it worked GetPinByID") - fmt.Fprintf(w, "{\"status\": \"ok\", \"path\": \"%s\", \"method\": \"%s\"}\n", r.URL.Path, r.Method) } diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go index ccacd52..df442ed 100644 --- a/internal/pkg/service/response.go +++ b/internal/pkg/service/response.go @@ -1,6 +1,8 @@ package service import ( + "encoding/json" + "fmt" "net/http" ) @@ -15,9 +17,38 @@ type JsonResponse struct { type JsonErrResponse struct { Status string `json:"status" example:"error"` Message string `json:"message" example:"Error description"` - Code int `json:"code,string"` + Code string `json:"code"` } // @name JsonErrResponse func SetContentTypeJSON(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") } + +func responseOk(w http.ResponseWriter, message string, body any) error { + res := JsonResponse{ + Status: "ok", + Message: message, + Body: body, + } + resBytes, err := json.Marshal(res) + if err != nil { + return fmt.Errorf("responseOk: %w", err) + } + w.WriteHeader(http.StatusOK) + _, err = w.Write(resBytes) + return err +} + +func responseError(w http.ResponseWriter, code, message string) error { + res := JsonErrResponse{ + Status: "error", + Message: message, + Code: code, + } + resBytes, err := json.Marshal(res) + if err != nil { + return fmt.Errorf("responseError: %w", err) + } + _, err = w.Write(resBytes) + return err +} From 4d5f9298bda3a5babefc1749965a4f258a646110 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:17:54 +0300 Subject: [PATCH 026/266] TP-933 update: module --- go.mod | 3 ++- go.sum | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2a2406c..cc719c3 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/go-park-mail-ru/2023_2_OND_team go 1.19 require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 github.com/proullon/ramsql v0.0.1 + github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.2 go.uber.org/config v1.4.0 go.uber.org/zap v1.26.0 @@ -21,7 +23,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect - github.com/swaggo/http-swagger v1.3.4 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect golang.org/x/net v0.15.0 // indirect diff --git a/go.sum b/go.sum index d385c11..aea59f4 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 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= From f5089b9dc03211c9fb890e0a638beca4b08bba41 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 03:42:34 +0300 Subject: [PATCH 027/266] TP-933 update: validation --- internal/pkg/service/auth.go | 13 ++++++++++++- internal/pkg/service/validation.go | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index 2845459..d13bbf4 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -2,6 +2,7 @@ package service import ( "encoding/json" + "fmt" "net/http" "time" @@ -152,7 +153,7 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() user := &user.User{} err := json.NewDecoder(r.Body).Decode(user) - if err != nil || !IsValidUserForRegistration(user) { + if err != nil { s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username, email and password are expected to be received in JSON format") if err != nil { @@ -161,6 +162,16 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { return } + if !IsValidUserForRegistration(user) { + fmt.Println(isValidEmail(user.Email), isValidUsername(user.Username), isValidPassword(user.Password)) + s.log.Info("invalid user registration data") + err = responseError(w, "invalid_params", "invalid data for registration is specified") + if err != nil { + s.log.Error(err.Error()) + } + return + } + err = s.userCase.Register(r.Context(), user) if err != nil { s.log.Warn(err.Error()) diff --git a/internal/pkg/service/validation.go b/internal/pkg/service/validation.go index 56f3920..5afc516 100644 --- a/internal/pkg/service/validation.go +++ b/internal/pkg/service/validation.go @@ -46,7 +46,7 @@ func isValidUsername(username string) bool { return false } for _, r := range username { - if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r)) { + if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { return false } } @@ -62,7 +62,7 @@ func isValidPassword(password string) bool { return false } for _, r := range password { - if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r)) { + if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { return false } } From 0904399bb2274b7c01243ec866745bcb7d6ddf78 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 18:05:14 +0300 Subject: [PATCH 028/266] TP-933 update: handler Login --- internal/pkg/service/auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index d13bbf4..04504e4 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -87,7 +87,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { } if !isValidPassword(params.Password) || !isValidUsername(params.Username) { - s.log.Info(err.Error()) + s.log.Info("invalid credentials") err = responseError(w, "bad_credentials", "invalid user credentials") if err != nil { s.log.Error(err.Error()) @@ -96,7 +96,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { } user, err := s.userCase.Authentication(r.Context(), params) - if err != nil || !isValidPassword(params.Password) || !isValidUsername(params.Username) { + if err != nil { s.log.Warn(err.Error()) err = responseError(w, "bad_credentials", "invalid user credentials") if err != nil { From 6151ec92cf9d924eb9a11c7fafbaee89aee0e3e3 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 18:29:24 +0300 Subject: [PATCH 029/266] TP-6d1 add: middleware for cors --- .gitignore | 1 + cmd/app/main.go | 2 +- configs/config.yml | 4 +++- internal/api/server/config.go | 12 ++++++++---- internal/api/server/router/router.go | 10 ++++++++++ internal/api/server/server.go | 6 ++++-- 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index ad148f2..22530f6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ go.work bin/ +cert/ diff --git a/cmd/app/main.go b/cmd/app/main.go index e63631c..09adea6 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -49,7 +49,7 @@ func main() { pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) service := service.New(log, sm, userCase, pinCase) - server := server.New(log, *server.NewConfig(cfg)) + server := server.New(log, server.NewConfig(cfg)) server.InitRouter(service) if err := server.Run(); err != nil { log.Fatal(err.Error()) diff --git a/configs/config.yml b/configs/config.yml index 8bc47d0..9c13b42 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -1,4 +1,6 @@ app: server: - host: localhost + host: 0.0.0.0 port: 8080 + certFile: cert/ + keyFile: cert/ diff --git a/internal/api/server/config.go b/internal/api/server/config.go index 6cba91d..04b7005 100644 --- a/internal/api/server/config.go +++ b/internal/api/server/config.go @@ -3,8 +3,10 @@ package server import "go.uber.org/config" type Config struct { - Host string - Port string + Host string + Port string + CertFile string + KeyFile string } const ConfigName = "app.server" @@ -12,7 +14,9 @@ const ConfigName = "app.server" func NewConfig(cfg *config.YAML) *Config { value := cfg.Get(ConfigName) return &Config{ - Host: value.Get("host").String(), - Port: value.Get("port").String(), + Host: value.Get("host").String(), + Port: value.Get("port").String(), + CertFile: value.Get("certFile").String(), + KeyFile: value.Get("keyFile").String(), } } diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 02a545c..df1b2ee 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -1,10 +1,13 @@ package router import ( + "net/http" + "github.com/go-chi/chi/v5" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" + "github.com/rs/cors" httpSwagger "github.com/swaggo/http-swagger" ) @@ -17,6 +20,13 @@ func New() Router { } func (r Router) InitRoute(serv *service.Service) { + c := cors.New(cors.Options{ + AllowedOrigins: []string{"https://pinspire.online"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, + AllowCredentials: true, + AllowedHeaders: []string{"content-type"}, + }) + r.Mux.Use(c.Handler) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) diff --git a/internal/api/server/server.go b/internal/api/server/server.go index 05eb353..8574437 100644 --- a/internal/api/server/server.go +++ b/internal/api/server/server.go @@ -15,15 +15,17 @@ type Server struct { http.Server router router.Router log *logger.Logger + cfg *Config } -func New(log *logger.Logger, cfg Config) *Server { +func New(log *logger.Logger, cfg *Config) *Server { return &Server{ Server: http.Server{ Addr: cfg.Host + ":" + cfg.Port, }, router: router.New(), log: log, + cfg: cfg, } } @@ -33,7 +35,7 @@ func (s *Server) Run() error { } s.Handler = s.router.Mux s.log.Info("server start") - return s.ListenAndServe() + return s.ListenAndServeTLS(s.cfg.CertFile, s.cfg.KeyFile) } func (s *Server) InitRouter(serv *service.Service) { From 9a05cafbf037b0daf373ab45e0e8587361fd0a1d Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 18:30:00 +0300 Subject: [PATCH 030/266] TP-6d1 update: module --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index cc719c3..81a6cf9 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 github.com/proullon/ramsql v0.0.1 + github.com/rs/cors v1.10.1 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.2 go.uber.org/config v1.4.0 diff --git a/go.sum b/go.sum index aea59f4..9501ffe 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From 7702e62aca9003a81ed999282be164a8d70de1d5 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 22:03:01 +0300 Subject: [PATCH 031/266] TP-933 update: results validation func --- internal/pkg/service/auth.go | 12 +++++----- internal/pkg/service/validation.go | 36 +++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index 04504e4..d5fa2a0 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -2,7 +2,6 @@ package service import ( "encoding/json" - "fmt" "net/http" "time" @@ -88,7 +87,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { if !isValidPassword(params.Password) || !isValidUsername(params.Username) { s.log.Info("invalid credentials") - err = responseError(w, "bad_credentials", "invalid user credentials") + err = responseError(w, "invalid_credentials", "invalid user credentials") if err != nil { s.log.Error(err.Error()) } @@ -98,7 +97,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { user, err := s.userCase.Authentication(r.Context(), params) if err != nil { s.log.Warn(err.Error()) - err = responseError(w, "bad_credentials", "invalid user credentials") + err = responseError(w, "bad_credentials", "incorrect user credentials") if err != nil { s.log.Error(err.Error()) } @@ -162,10 +161,9 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { return } - if !IsValidUserForRegistration(user) { - fmt.Println(isValidEmail(user.Email), isValidUsername(user.Username), isValidPassword(user.Password)) + if err := IsValidUserForRegistration(user); err != nil { s.log.Info("invalid user registration data") - err = responseError(w, "invalid_params", "invalid data for registration is specified") + err = responseError(w, "invalid_params", err.Error()) if err != nil { s.log.Error(err.Error()) } @@ -175,7 +173,7 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { err = s.userCase.Register(r.Context(), user) if err != nil { s.log.Warn(err.Error()) - err = responseError(w, "uniq_fields", "username") + err = responseError(w, "uniq_fields", "there is already an account with this username or password") } else { err = responseOk(w, "the user has been successfully registered", nil) } diff --git a/internal/pkg/service/validation.go b/internal/pkg/service/validation.go index 5afc516..9356faa 100644 --- a/internal/pkg/service/validation.go +++ b/internal/pkg/service/validation.go @@ -4,12 +4,30 @@ import ( "fmt" "net/url" "strconv" + "strings" "unicode" valid "github.com/asaskevich/govalidator" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +type errorFields []string + +func (b *errorFields) Error() string { + return strings.Join(*b, ",") +} + +func (b *errorFields) addInvalidField(fieldName string) { + *b = append(*b, fieldName) +} + +func (b *errorFields) Err() error { + if len(*b) == 0 { + return nil + } + return b +} + func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { if param := u.Query().Get("count"); len(param) > 0 { c, err := strconv.ParseInt(param, 10, 64) @@ -33,12 +51,20 @@ func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { return } -func IsValidUserForRegistration(user *user.User) bool { - return isValidPassword(user.Password) && isValidEmail(user.Email) && isValidUsername(user.Username) -} +func IsValidUserForRegistration(user *user.User) error { + invalidFields := new(errorFields) + + if !isValidPassword(user.Password) { + invalidFields.addInvalidField("password") + } + if !isValidEmail(user.Email) { + invalidFields.addInvalidField("email") + } + if !isValidUsername(user.Username) { + invalidFields.addInvalidField("username") + } -func IsValidUserForLogin(user *user.User) bool { - return isValidPassword(user.Password) && isValidUsername(user.Username) + return invalidFields.Err() } func isValidUsername(username string) bool { From 5351adbff583abab8f1cfaefd7b73b2889983c29 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 22:20:19 +0300 Subject: [PATCH 032/266] dev: rename function NewRandomString --- internal/usecases/session/manager.go | 2 +- internal/usecases/user/usecase.go | 2 +- pkg/crypto/crypto.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/usecases/session/manager.go b/internal/usecases/session/manager.go index 9b670be..a7a3b8e 100644 --- a/internal/usecases/session/manager.go +++ b/internal/usecases/session/manager.go @@ -28,7 +28,7 @@ func New(log *logger.Logger, repo repo.Repository) *SessionManager { } func (sm *SessionManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { - sessionKey, err := crypto.NewRandomStr(lenSessionKey) + sessionKey, err := crypto.NewRandomString(lenSessionKey) if err != nil { return nil, fmt.Errorf("session key generation for new session: %w", err) } diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go index 80f88fc..af0ceb6 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/usecases/user/usecase.go @@ -28,7 +28,7 @@ func New(log *logger.Logger, repo repo.Repository) *Usecase { } func (u *Usecase) Register(ctx context.Context, user *entity.User) error { - salt, err := crypto.NewRandomStr(lenSalt) + salt, err := crypto.NewRandomString(lenSalt) if err != nil { return fmt.Errorf("generating salt for registration: %w", err) } diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index 377dd0e..aa12ffd 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -9,7 +9,7 @@ import ( "golang.org/x/crypto/argon2" ) -func NewRandomStr(length int) (string, error) { +func NewRandomString(length int) (string, error) { rand.Seed(time.Now().UTC().UnixNano()) b := make([]byte, hex.DecodedLen(length)) _, err := rand.Read(b) From b215fc67b8644bdfe57f2390a4107d9fec8de7a8 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 22:38:26 +0300 Subject: [PATCH 033/266] dev: update: package crypto --- pkg/crypto/crypto.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index aa12ffd..e7ebd3c 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -1,16 +1,14 @@ package crypto import ( + "crypto/rand" "encoding/hex" - "math/rand" - "time" "unsafe" "golang.org/x/crypto/argon2" ) func NewRandomString(length int) (string, error) { - rand.Seed(time.Now().UTC().UnixNano()) b := make([]byte, hex.DecodedLen(length)) _, err := rand.Read(b) if err != nil { From 14510517691a2b5013442958eeeccb8c3e0b2a76 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 23:27:58 +0300 Subject: [PATCH 034/266] TP-565 add: fuzzy tests for pkg pkg/crypto --- .gitignore | 1 + pkg/crypto/crypto.go | 9 +++++++++ pkg/crypto/fuzz_test.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 pkg/crypto/fuzz_test.go diff --git a/.gitignore b/.gitignore index ad148f2..53452f4 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ go.work bin/ +testdata/ diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index e7ebd3c..00195fa 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -3,12 +3,18 @@ package crypto import ( "crypto/rand" "encoding/hex" + "errors" "unsafe" "golang.org/x/crypto/argon2" ) +var ErrNegativeLen = errors.New("the length cannot be negative") + func NewRandomString(length int) (string, error) { + if length < 0 { + return "", ErrNegativeLen + } b := make([]byte, hex.DecodedLen(length)) _, err := rand.Read(b) if err != nil { @@ -20,6 +26,9 @@ func NewRandomString(length int) (string, error) { } func PasswordHash(password, salt string, length int) string { + if length <= 0 { + return "" + } passwordHash := make([]byte, length) hex.Encode(passwordHash, argon2.IDKey([]byte(password), []byte(salt), 3, 32*1024, 4, uint32(hex.DecodedLen(length)))) diff --git a/pkg/crypto/fuzz_test.go b/pkg/crypto/fuzz_test.go new file mode 100644 index 0000000..8e23843 --- /dev/null +++ b/pkg/crypto/fuzz_test.go @@ -0,0 +1,35 @@ +package crypto + +import ( + "testing" +) + +func FuzzNewRandomString(f *testing.F) { + f.Add(5) + f.Add(0) + f.Add(-2) + + f.Fuzz(func(t *testing.T, length int) { + length %= 10000 + randStr, err := NewRandomString(length) + if length < 0 && (randStr != "" || err != ErrNegativeLen) || length >= 0 && (len(randStr) != length || err != nil) { + t.Fatalf("NewRandomString(%d) retured %s, %v, lenght returned string equal %d, but except %d", + length, randStr, err, len(randStr), length) + } + }) +} + +func FuzzPasswordHash(f *testing.F) { + f.Add("password", "salt", 5) + f.Add("a", "apple", 0) + f.Add("", "", -1) + + f.Fuzz(func(t *testing.T, password, salt string, length int) { + length %= 10000 + passHash := PasswordHash(password, salt, length) + if length < 0 && passHash != "" || length >= 0 && len(passHash) != length { + t.Fatalf("PasswordHash(%s, %s, %d) retured %s, lenght returned string equal %d, but except %d", + password, salt, length, passHash, len(passHash), length) + } + }) +} From 33551abd90116d10af3b895a06d472def20e764f Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 00:03:06 +0300 Subject: [PATCH 035/266] TP-565 add: testify package --- go.mod | 3 +++ go.sum | 1 + 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index cc719c3..00b0c3a 100644 --- a/go.mod +++ b/go.mod @@ -15,13 +15,16 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect diff --git a/go.sum b/go.sum index aea59f4..c701cf0 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= From e1215d83521ca618cb388acb1a9a4c4f964ce57e Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Mon, 2 Oct 2023 21:58:53 +0300 Subject: [PATCH 036/266] update API documantation files --- docs/docs.go | 164 +++++++++++------------------------ docs/swagger.json | 164 +++++++++++------------------------ docs/swagger.yaml | 108 +++++++++-------------- internal/pkg/service/auth.go | 35 ++++---- internal/pkg/service/pin.go | 14 +-- 5 files changed, 168 insertions(+), 317 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 0b5232d..56854d8 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -33,6 +33,15 @@ const docTemplate = `{ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", @@ -85,6 +94,7 @@ const docTemplate = `{ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -94,6 +104,7 @@ const docTemplate = `{ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -107,19 +118,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "session_key": { @@ -158,23 +157,20 @@ const docTemplate = `{ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "Session-id": { @@ -218,6 +214,7 @@ const docTemplate = `{ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -227,6 +224,7 @@ const docTemplate = `{ } }, { + "example": "clickkk@gmail.com", "description": "Email", "name": "email", "in": "body", @@ -236,6 +234,7 @@ const docTemplate = `{ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -249,19 +248,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" } }, "400": { @@ -288,58 +275,9 @@ const docTemplate = `{ "/api/v1/pin": { "get": { "description": "Get pin collection", - "produces": [ + "consumes": [ "application/json" ], - "tags": [ - "Pin" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "type": "array", - "items": { - "$ref": "#/definitions/Pin" - } - } - } - } - ] - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - } - } - } - }, - "/api/v1/pin/{pinId}": { - "get": { - "description": "Get concrete pin by id", "produces": [ "application/json" ], @@ -348,9 +286,17 @@ const docTemplate = `{ ], "parameters": [ { - "type": "integer", - "description": "Id of the pin", - "name": "pinId", + "type": "string", + "example": "2", + "description": "ID of the pin that will be just before the first pin in the requested collection, 0 by default", + "name": "lastID", + "in": "path" + }, + { + "type": "string", + "example": "5", + "description": "Pins quantity after last pin specified in lastID", + "name": "count", "in": "path", "required": true } @@ -367,7 +313,10 @@ const docTemplate = `{ "type": "object", "properties": { "body": { - "$ref": "#/definitions/Pin" + "type": "array", + "items": { + "$ref": "#/definitions/Pin" + } } } } @@ -397,15 +346,11 @@ const docTemplate = `{ } }, "definitions": { - "Empty": { - "type": "object" - }, "JsonErrResponse": { "type": "object", "properties": { "code": { - "type": "string", - "example": "0" + "type": "string" }, "message": { "type": "string", @@ -420,7 +365,9 @@ const docTemplate = `{ "JsonResponse": { "type": "object", "properties": { - "body": {}, + "body": { + "x-omitempty": true + }, "message": { "type": "string", "example": "Response message" @@ -434,14 +381,6 @@ const docTemplate = `{ "Pin": { "type": "object", "properties": { - "authorId": { - "type": "integer", - "example": 23 - }, - "description": { - "type": "string", - "example": "about face" - }, "id": { "type": "integer", "example": 55 @@ -449,19 +388,16 @@ const docTemplate = `{ "picture": { "type": "string", "example": "pinspire/imgs/image.png" - }, - "publicationTime": { - "type": "string" - }, - "title": { - "type": "string", - "example": "Nature's beauty" } } }, "User": { "type": "object", "properties": { + "email": { + "type": "string", + "example": "digital@gmail.com" + }, "password": { "type": "string", "example": "pass123" diff --git a/docs/swagger.json b/docs/swagger.json index 06298f4..70dc722 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -25,6 +25,15 @@ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", @@ -77,6 +86,7 @@ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -86,6 +96,7 @@ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -99,19 +110,7 @@ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "session_key": { @@ -150,23 +149,20 @@ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "Session-id": { @@ -210,6 +206,7 @@ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -219,6 +216,7 @@ } }, { + "example": "clickkk@gmail.com", "description": "Email", "name": "email", "in": "body", @@ -228,6 +226,7 @@ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -241,19 +240,7 @@ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" } }, "400": { @@ -280,58 +267,9 @@ "/api/v1/pin": { "get": { "description": "Get pin collection", - "produces": [ + "consumes": [ "application/json" ], - "tags": [ - "Pin" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "type": "array", - "items": { - "$ref": "#/definitions/Pin" - } - } - } - } - ] - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - } - } - } - }, - "/api/v1/pin/{pinId}": { - "get": { - "description": "Get concrete pin by id", "produces": [ "application/json" ], @@ -340,9 +278,17 @@ ], "parameters": [ { - "type": "integer", - "description": "Id of the pin", - "name": "pinId", + "type": "string", + "example": "2", + "description": "ID of the pin that will be just before the first pin in the requested collection, 0 by default", + "name": "lastID", + "in": "path" + }, + { + "type": "string", + "example": "5", + "description": "Pins quantity after last pin specified in lastID", + "name": "count", "in": "path", "required": true } @@ -359,7 +305,10 @@ "type": "object", "properties": { "body": { - "$ref": "#/definitions/Pin" + "type": "array", + "items": { + "$ref": "#/definitions/Pin" + } } } } @@ -389,15 +338,11 @@ } }, "definitions": { - "Empty": { - "type": "object" - }, "JsonErrResponse": { "type": "object", "properties": { "code": { - "type": "string", - "example": "0" + "type": "string" }, "message": { "type": "string", @@ -412,7 +357,9 @@ "JsonResponse": { "type": "object", "properties": { - "body": {}, + "body": { + "x-omitempty": true + }, "message": { "type": "string", "example": "Response message" @@ -426,14 +373,6 @@ "Pin": { "type": "object", "properties": { - "authorId": { - "type": "integer", - "example": 23 - }, - "description": { - "type": "string", - "example": "about face" - }, "id": { "type": "integer", "example": 55 @@ -441,19 +380,16 @@ "picture": { "type": "string", "example": "pinspire/imgs/image.png" - }, - "publicationTime": { - "type": "string" - }, - "title": { - "type": "string", - "example": "Nature's beauty" } } }, "User": { "type": "object", "properties": { + "email": { + "type": "string", + "example": "digital@gmail.com" + }, "password": { "type": "string", "example": "pass123" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 28b4cb0..de01d6a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,10 +1,7 @@ definitions: - Empty: - type: object JsonErrResponse: properties: code: - example: "0" type: string message: example: Error description @@ -15,7 +12,8 @@ definitions: type: object JsonResponse: properties: - body: {} + body: + x-omitempty: true message: example: Response message type: string @@ -25,26 +23,18 @@ definitions: type: object Pin: properties: - authorId: - example: 23 - type: integer - description: - example: about face - type: string id: example: 55 type: integer picture: example: pinspire/imgs/image.png type: string - publicationTime: - type: string - title: - example: Nature's beauty - type: string type: object User: properties: + email: + example: digital@gmail.com + type: string password: example: pass123 type: string @@ -68,6 +58,12 @@ paths: /api/v1/auth/login: get: description: User login, check authentication, get user info + parameters: + - description: Auth session id + example: senjs7rvdnrgkjdr + in: header + name: session_key + type: string produces: - application/json responses: @@ -100,12 +96,14 @@ paths: description: User login, creating new session parameters: - description: Username + example: clicker123 in: body name: username required: true schema: type: string - description: Password + example: safe_pass in: body name: password required: true @@ -121,12 +119,7 @@ paths: description: Auth cookie with new valid session id type: string schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - $ref: '#/definitions/Empty' - type: object + $ref: '#/definitions/JsonResponse' "400": description: Bad Request schema: @@ -144,6 +137,12 @@ paths: /api/v1/auth/logout: delete: description: User logout, session deletion + parameters: + - description: Auth session id + example: senjs7rvdnrgkjdr + in: header + name: session_key + type: string produces: - application/json responses: @@ -154,12 +153,7 @@ paths: description: Auth cookie with expired session id type: string schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - $ref: '#/definitions/Empty' - type: object + $ref: '#/definitions/JsonResponse' "400": description: Bad Request schema: @@ -181,18 +175,21 @@ paths: description: User registration parameters: - description: Username + example: clicker123 in: body name: username required: true schema: type: string - description: Email + example: clickkk@gmail.com in: body name: email required: true schema: type: string - description: Password + example: safe_pass in: body name: password required: true @@ -204,12 +201,7 @@ paths: "200": description: OK schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - $ref: '#/definitions/Empty' - type: object + $ref: '#/definitions/JsonResponse' "400": description: Bad Request schema: @@ -226,44 +218,22 @@ paths: - Auth /api/v1/pin: get: - description: Get pin collection - produces: + consumes: - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - items: - $ref: '#/definitions/Pin' - type: array - type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/JsonErrResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/JsonErrResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/JsonErrResponse' - tags: - - Pin - /api/v1/pin/{pinId}: - get: - description: Get concrete pin by id + description: Get pin collection parameters: - - description: Id of the pin + - description: ID of the pin that will be just before the first pin in the requested + collection, 0 by default + example: "2" in: path - name: pinId + name: lastID + type: string + - description: Pins quantity after last pin specified in lastID + example: "5" + in: path + name: count required: true - type: integer + type: string produces: - application/json responses: @@ -274,7 +244,9 @@ paths: - $ref: '#/definitions/JsonResponse' - properties: body: - $ref: '#/definitions/Pin' + items: + $ref: '#/definitions/Pin' + type: array type: object "400": description: Bad Request diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index d13bbf4..60cb4e0 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -16,10 +16,11 @@ import ( // @Description User login, check authentication, get user info // @Tags Auth // @Produce json -// @Success 200 {object} JsonResponse{body=user.User} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse +// @Param session_key header string false "Auth session id" example(senjs7rvdnrgkjdr) +// @Success 200 {object} JsonResponse{body=user.User} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/login [get] func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { s.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) @@ -62,9 +63,9 @@ func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { // @Tags Auth // @Accept json // @Produce json -// @Param username body string true "Username" -// @Param password body string true "Password" -// @Success 200 {object} JsonResponse{body=Empty} +// @Param username body string true "Username" example(clicker123) +// @Param password body string true "Password" example(safe_pass) +// @Success 200 {object} JsonResponse // @Failure 400 {object} JsonErrResponse // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse @@ -138,10 +139,10 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { // @Tags Auth // @Accept json // @Produce json -// @Param username body string true "Username" -// @Param email body string true "Email" -// @Param password body string true "Password" -// @Success 200 {object} JsonResponse{body=Empty} +// @Param username body string true "Username" example(clicker123) +// @Param email body string true "Email" example(clickkk@gmail.com) +// @Param password body string true "Password" example(safe_pass) +// @Success 200 {object} JsonResponse // @Failure 400 {object} JsonErrResponse // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse @@ -189,11 +190,13 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { // @Description User logout, session deletion // @Tags Auth // @Produce json -// @Success 200 {object} JsonResponse{body=Empty} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Header 200 {string} Session-id "Auth cookie with expired session id" +// @Param session_key header string false "Auth session id" example(senjs7rvdnrgkjdr) +// +// @Success 200 {object} JsonResponse +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Header 200 {string} Session-id "Auth cookie with expired session id" // @Router /api/v1/auth/logout [delete] func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { s.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go index 2bff1ae..41332f2 100644 --- a/internal/pkg/service/pin.go +++ b/internal/pkg/service/pin.go @@ -16,12 +16,16 @@ var ErrBadParams = errors.New("bad params") // // @Description Get pin collection // @Tags Pin +// @Accept json // @Produce json -// @Success 200 {object} JsonResponse{body=[]Pin} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/pin [get] +// @Param lastID path string false "ID of the pin that will be just before the first pin in the requested collection, 0 by default" example(2) +// +// @Param count path string true "Pins quantity after last pin specified in lastID" example(5) +// @Success 200 {object} JsonResponse{body=[]Pin} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Router /api/v1/pin [get] func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { s.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) From 5ae67138bfdba0eee6ef7f65d6e63e1adab561e1 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 00:13:24 +0300 Subject: [PATCH 037/266] delete Empty struct --- internal/pkg/service/response.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go index df442ed..df3121d 100644 --- a/internal/pkg/service/response.go +++ b/internal/pkg/service/response.go @@ -6,12 +6,15 @@ import ( "net/http" ) -type Empty struct{} // @name Empty +// type JsonResponseNoBody struct { +// Status string `json:"status" example:"ok"` +// Message string `json:"message" example:"Response message"` +// } type JsonResponse struct { Status string `json:"status" example:"ok"` Message string `json:"message" example:"Response message"` - Body interface{} `json:"body,omitempty"` + Body interface{} `json:"body" extensions:"x-omitempty"` } // @name JsonResponse type JsonErrResponse struct { From 5516775df205fb79f312dc0a4a4d764f4ad28b7b Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 3 Oct 2023 00:22:48 +0300 Subject: [PATCH 038/266] dev add: default avatar for user --- internal/pkg/repository/ramrepo/ramrepo.go | 156 +++++++++++++++++++-- internal/pkg/repository/ramrepo/user.go | 11 +- internal/pkg/repository/user/repo.go | 2 +- internal/pkg/service/auth.go | 4 +- internal/usecases/user/usecase.go | 4 +- 5 files changed, 153 insertions(+), 24 deletions(-) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 4ab99eb..04add3f 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -42,7 +42,7 @@ func createUsersTable(db *sql.DB) error { username varchar(30) UNIQUE, password varchar(50), email varchar(50) UNIQUE, - avatar varchar(50) + avatar varchar(50) DEFAULT '' );`) if err != nil { return fmt.Errorf("create table users: %w", err) @@ -76,18 +76,148 @@ func createSessionTable(db *sql.DB) error { func fillPinTableRows(db *sql.DB) error { _, err := db.Exec(`INSERT INTO pin (picture) VALUES - ('/1'), - ('/2'), - ('/3'), - ('/4'), - ('/5'), - ('/6'), - ('/7'), - ('/8'), - ('/9'), - ('/10'), - ('/11'), - ('/12');`) + ('https://i.pinimg.com/564x/e2/43/10/e24310fe1909ec1f1de347fedc6318b0.jpg'), + ('https://i.pinimg.com/564x/91/39/51/913951d97d3cc3ac5a4ecb58da2ffdf5.jpg'), + ('https://i.pinimg.com/564x/91/39/51/913951d97d3cc3ac5a4ecb58da2ffdf5.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg');`) if err != nil { return fmt.Errorf("fill pin table: %w", err) } diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index 5db8b71..e50cdca 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -34,12 +34,11 @@ func (r *ramUserRepo) GetUserByUsername(ctx context.Context, username string) (* return user, nil } -func (r *ramUserRepo) GetUsernameByID(ctx context.Context, userID int) (string, error) { - row := r.db.QueryRowContext(ctx, "SELECT username FROM users WHERE id = $1;", userID) - var username string - err := row.Scan(&username) +func (r *ramUserRepo) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) { + row := r.db.QueryRowContext(ctx, "SELECT username, avatar FROM users WHERE id = $1;", userID) + err = row.Scan(&username, &avatar) if err != nil { - return "", fmt.Errorf("getting a username from storage by id: %w", err) + return "", "", fmt.Errorf("getting a username from storage by id: %w", err) } - return username, nil + return } diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 90cecd5..b6b9447 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -9,5 +9,5 @@ import ( type Repository interface { AddNewUser(ctx context.Context, user *user.User) error GetUserByUsername(ctx context.Context, username string) (*user.User, error) - GetUsernameByID(ctx context.Context, userID int) (string, error) + GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) } diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index d5fa2a0..f49b7e9 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -43,12 +43,12 @@ func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { return } - username, err := s.userCase.FindOutUserName(r.Context(), userID) + username, avatar, err := s.userCase.FindOutUsernameAndAvatar(r.Context(), userID) if err != nil { s.log.Error(err.Error()) err = responseError(w, "no_auth", "no user was found for this session") } else { - err = responseOk(w, "user found", map[string]string{"username": username}) + err = responseOk(w, "user found", map[string]string{"username": username, "avatar": avatar}) } if err != nil { s.log.Error(err.Error()) diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go index af0ceb6..b9cd907 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/usecases/user/usecase.go @@ -54,6 +54,6 @@ func (u *Usecase) Authentication(ctx context.Context, credentials userCredential return user, nil } -func (u *Usecase) FindOutUserName(ctx context.Context, userID int) (string, error) { - return u.repo.GetUsernameByID(ctx, userID) +func (u *Usecase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { + return u.repo.GetUsernameAndAvatarByID(ctx, userID) } From 41a39153ab3442a589cc32a7ae71b2db46816d45 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 00:33:42 +0300 Subject: [PATCH 039/266] TP-565 add: TestGetPins, TestFetchValidParamForLoadTape --- internal/pkg/service/pin_test.go | 151 ++++++++++++++++++++++++ internal/pkg/service/validation_test.go | 80 +++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 internal/pkg/service/pin_test.go create mode 100644 internal/pkg/service/validation_test.go diff --git a/internal/pkg/service/pin_test.go b/internal/pkg/service/pin_test.go new file mode 100644 index 0000000..c7a1f25 --- /dev/null +++ b/internal/pkg/service/pin_test.go @@ -0,0 +1,151 @@ +package service + +import ( + "encoding/json" + "fmt" + "io" + "net/http/httptest" + "reflect" + "testing" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + pinCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/stretchr/testify/require" +) + +func TestGetPins(t *testing.T) { + + log, _ := logger.New(logger.RFC3339FormatTime()) + defer log.Sync() + + db, _ := ramrepo.OpenDB() + defer db.Close() + + pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) + + service := New(log, nil, nil, pinCase) + + rawUrl := "https://domain.test:8080/api/v1/pin" + goodCases := []struct { + rawURL string + expResp JsonResponse + }{ + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 1, 2), + expResp: JsonResponse{ + Status: "ok", + Message: "pins received are sorted by id", + Body: map[string]interface{}{ + "lastID": 3, + "pins": []interface{}{ + map[string]interface{}{"id": 2, "picture": "/2"}, + map[string]interface{}{"id": 3, "picture": "/3"}, + }, + }, + }, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, 3), + expResp: JsonResponse{ + Status: "ok", + Message: "pins received are sorted by id", + Body: map[string]interface{}{ + "lastID": 5, + "pins": []interface{}{ + map[string]interface{}{"id": 4, "picture": "/4"}, + map[string]interface{}{"id": 5, "picture": "/5"}, + }, + }, + }, + }, + } + + for _, tCase := range goodCases { + t.Run(fmt.Sprintf("TestGetPins good: %s", tCase.rawURL), func(t *testing.T) { + req := httptest.NewRequest("GET", tCase.rawURL, nil) + w := httptest.NewRecorder() + service.GetPins(w, req) + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + var actualResp JsonResponse + + json.Unmarshal(body, &actualResp) + fmt.Println(tCase.expResp) + fmt.Println(actualResp) + require.True(t, reflect.DeepEqual(tCase.expResp, actualResp)) + // require.EqualExportedValues(t, tCase.expResp, actualResp) + }) + } + + // badCases := []struct { + // rawURL string + // expResp JsonErrResponse + // }{ + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 3), + // expResp: JsonErrResponse{ + // Status: "error", + // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + // Code: "bad_params", + // }, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 3), + // expResp: JsonErrResponse{ + // Status: "error", + // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + // Code: "bad_params", + // }, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 213123, 3), + // expResp: JsonErrResponse{ + // Status: "error", + // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + // Code: "bad_params", + // }, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, -1), + // expResp: JsonErrResponse{ + // Status: "error", + // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + // Code: "bad_params", + // }, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 3), + // expResp: JsonErrResponse{ + // Status: "error", + // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + // Code: "bad_params", + // }, + // }, + // { + // rawURL: fmt.Sprintf("%s?lastID=%d", rawUrl, 3), + // expResp: JsonErrResponse{ + // Status: "error", + // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + // Code: "bad_params", + // }, + // }, + // } + + // for _, tCase := range badCases { + // t.Run("TestGetPins bad", func(t *testing.T) { + // req := httptest.NewRequest("GET", tCase.rawURL, nil) + // w := httptest.NewRecorder() + // service.GetPins(w, req) + + // resp := w.Result() + // body, _ := io.ReadAll(resp.Body) + // var actualResp JsonResponse + + // json.Unmarshal(body, actualResp) + // require.Equal(t, tCase.expResp, actualResp) + // }) + // } + +} diff --git a/internal/pkg/service/validation_test.go b/internal/pkg/service/validation_test.go new file mode 100644 index 0000000..aba04eb --- /dev/null +++ b/internal/pkg/service/validation_test.go @@ -0,0 +1,80 @@ +package service + +import ( + "fmt" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +type TestCaseFetch struct { + rawURL string + expCount, expLastID int + expErr error +} + +func TestFetchValidParamForLoadTape(t *testing.T) { + rawUrl := "https://domain.test:8080/api/v1/pin" + testCases := make([]TestCaseFetch, 0) + + for count := 1; count != 5; count++ { + for lastID := 1; lastID != 5; lastID++ { + rawUrlWithParams := fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, count, lastID) + testCases = append(testCases, TestCaseFetch{ + rawURL: rawUrlWithParams, + expCount: count, + expLastID: lastID, + expErr: nil, + }) + } + } + testCases = append(testCases, []TestCaseFetch{ + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 1), + expCount: 0, + expLastID: 0, + expErr: ErrBadParams, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 12312, 1), + expCount: 0, + expLastID: 0, + expErr: ErrBadParams, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 1), + expCount: 0, + expLastID: 0, + expErr: ErrBadParams, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, -1), + expCount: 0, + expLastID: 0, + expErr: ErrBadParams, + }, + { + rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 1), + expCount: 0, + expLastID: 0, + expErr: ErrCountParameterMissing, + }, + { + rawURL: fmt.Sprintf("%s?&lastID=%d", rawUrl, 4), + expCount: 0, + expLastID: 0, + expErr: ErrCountParameterMissing, + }, + }...) + + for _, tCase := range testCases { + t.Run("TestFetchValidParamForLoadTape", func(t *testing.T) { + URL, _ := url.Parse(tCase.rawURL) + actualCount, actualLastID, actualErr := FetchValidParamForLoadTape(URL) + require.Equal(t, tCase.expCount, actualCount) + require.Equal(t, tCase.expLastID, actualLastID) + require.Equal(t, tCase.expErr, actualErr) + }) + } +} From 6b6e1a356f3f40f0ab8431b6a7b045617614b76a Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 01:54:13 +0300 Subject: [PATCH 040/266] TP-565 update: finished TestGetPins --- internal/pkg/service/pin_test.go | 163 +++++++++++++++++-------------- 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/internal/pkg/service/pin_test.go b/internal/pkg/service/pin_test.go index c7a1f25..f7c6fe1 100644 --- a/internal/pkg/service/pin_test.go +++ b/internal/pkg/service/pin_test.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "net/http/httptest" - "reflect" "testing" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" @@ -39,8 +38,7 @@ func TestGetPins(t *testing.T) { Body: map[string]interface{}{ "lastID": 3, "pins": []interface{}{ - map[string]interface{}{"id": 2, "picture": "/2"}, - map[string]interface{}{"id": 3, "picture": "/3"}, + map[string]interface{}{"id": 3}, }, }, }, @@ -53,8 +51,8 @@ func TestGetPins(t *testing.T) { Body: map[string]interface{}{ "lastID": 5, "pins": []interface{}{ - map[string]interface{}{"id": 4, "picture": "/4"}, - map[string]interface{}{"id": 5, "picture": "/5"}, + map[string]interface{}{"id": 4}, + map[string]interface{}{"id": 5}, }, }, }, @@ -71,81 +69,94 @@ func TestGetPins(t *testing.T) { body, _ := io.ReadAll(resp.Body) var actualResp JsonResponse - json.Unmarshal(body, &actualResp) + json.Unmarshal(body, &actualResp) // после Unmarshall числа приводятся к float64 fmt.Println(tCase.expResp) fmt.Println(actualResp) - require.True(t, reflect.DeepEqual(tCase.expResp, actualResp)) - // require.EqualExportedValues(t, tCase.expResp, actualResp) + require.Equal(t, tCase.expResp.Status, actualResp.Status) + require.Equal(t, tCase.expResp.Message, actualResp.Message) + // fmt.Println(reflect.TypeOf(actualResp.Body.(map[string]interface{})["lastID"])) + expLastID := tCase.expResp.Body.(map[string]interface{})["lastID"].(int) + actualLastID := actualResp.Body.(map[string]interface{})["lastID"].(float64) + + expIDs, actualIDs := make([]int, 0), make([]int, 0) + for _, pin := range tCase.expResp.Body.(map[string]interface{})["pins"].([]interface{}) { + expIDs = append(expIDs, pin.(map[string]interface{})["id"].(int)) + } + for _, pin := range actualResp.Body.(map[string]interface{})["pins"].([]interface{}) { + actualIDs = append(actualIDs, int(pin.(map[string]interface{})["id"].(float64))) + } + + require.Equal(t, expLastID, int(actualLastID)) + require.Equal(t, expIDs, actualIDs) }) } - // badCases := []struct { - // rawURL string - // expResp JsonErrResponse - // }{ - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 3), - // expResp: JsonErrResponse{ - // Status: "error", - // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - // Code: "bad_params", - // }, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 3), - // expResp: JsonErrResponse{ - // Status: "error", - // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - // Code: "bad_params", - // }, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 213123, 3), - // expResp: JsonErrResponse{ - // Status: "error", - // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - // Code: "bad_params", - // }, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, -1), - // expResp: JsonErrResponse{ - // Status: "error", - // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - // Code: "bad_params", - // }, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 3), - // expResp: JsonErrResponse{ - // Status: "error", - // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - // Code: "bad_params", - // }, - // }, - // { - // rawURL: fmt.Sprintf("%s?lastID=%d", rawUrl, 3), - // expResp: JsonErrResponse{ - // Status: "error", - // Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - // Code: "bad_params", - // }, - // }, - // } - - // for _, tCase := range badCases { - // t.Run("TestGetPins bad", func(t *testing.T) { - // req := httptest.NewRequest("GET", tCase.rawURL, nil) - // w := httptest.NewRecorder() - // service.GetPins(w, req) - - // resp := w.Result() - // body, _ := io.ReadAll(resp.Body) - // var actualResp JsonResponse - - // json.Unmarshal(body, actualResp) - // require.Equal(t, tCase.expResp, actualResp) - // }) - // } + badCases := []struct { + rawURL string + expResp JsonErrResponse + }{ + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 3), + expResp: JsonErrResponse{ + Status: "error", + Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Code: "bad_params", + }, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 3), + expResp: JsonErrResponse{ + Status: "error", + Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Code: "bad_params", + }, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 213123, 3), + expResp: JsonErrResponse{ + Status: "error", + Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Code: "bad_params", + }, + }, + { + rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, -1), + expResp: JsonErrResponse{ + Status: "error", + Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Code: "bad_params", + }, + }, + { + rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 3), + expResp: JsonErrResponse{ + Status: "error", + Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Code: "bad_params", + }, + }, + { + rawURL: fmt.Sprintf("%s?lastID=%d", rawUrl, 3), + expResp: JsonErrResponse{ + Status: "error", + Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Code: "bad_params", + }, + }, + } + + for _, tCase := range badCases { + t.Run("TestGetPins bad", func(t *testing.T) { + req := httptest.NewRequest("GET", tCase.rawURL, nil) + w := httptest.NewRecorder() + service.GetPins(w, req) + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + var actualResp JsonErrResponse + json.Unmarshal(body, &actualResp) + require.Equal(t, tCase.expResp, actualResp) + }) + } } From 8742675a9f2044793454259ad5d835dc4fe75460 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 3 Oct 2023 01:53:08 +0300 Subject: [PATCH 041/266] dev add: default avatar --- internal/pkg/repository/ramrepo/ramrepo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 04add3f..791943e 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -42,7 +42,7 @@ func createUsersTable(db *sql.DB) error { username varchar(30) UNIQUE, password varchar(50), email varchar(50) UNIQUE, - avatar varchar(50) DEFAULT '' + avatar varchar(50) DEFAULT 'https://cdn-icons-png.flaticon.com/512/149/149071.png' );`) if err != nil { return fmt.Errorf("create table users: %w", err) From d19803c71de2f356cb7c6c82e229885c08f1c844 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 01:58:51 +0300 Subject: [PATCH 042/266] TP-565 update: TectGetPins minor updates --- internal/pkg/service/pin_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/pkg/service/pin_test.go b/internal/pkg/service/pin_test.go index f7c6fe1..4297fb9 100644 --- a/internal/pkg/service/pin_test.go +++ b/internal/pkg/service/pin_test.go @@ -22,7 +22,6 @@ func TestGetPins(t *testing.T) { defer db.Close() pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) - service := New(log, nil, nil, pinCase) rawUrl := "https://domain.test:8080/api/v1/pin" @@ -146,7 +145,7 @@ func TestGetPins(t *testing.T) { } for _, tCase := range badCases { - t.Run("TestGetPins bad", func(t *testing.T) { + t.Run(fmt.Sprintf("TestGetPins bad: %s", tCase.rawURL), func(t *testing.T) { req := httptest.NewRequest("GET", tCase.rawURL, nil) w := httptest.NewRecorder() service.GetPins(w, req) From 435961b1da853483c70973c83aceb6f64d1f052e Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 3 Oct 2023 11:45:49 +0300 Subject: [PATCH 043/266] TP-565 add: FetchValidParams test --- internal/pkg/service/validation_test.go | 126 ++++++++++++------------ 1 file changed, 62 insertions(+), 64 deletions(-) diff --git a/internal/pkg/service/validation_test.go b/internal/pkg/service/validation_test.go index aba04eb..c675316 100644 --- a/internal/pkg/service/validation_test.go +++ b/internal/pkg/service/validation_test.go @@ -1,80 +1,78 @@ package service import ( - "fmt" "net/url" "testing" "github.com/stretchr/testify/require" ) -type TestCaseFetch struct { - rawURL string - expCount, expLastID int - expErr error -} - -func TestFetchValidParamForLoadTape(t *testing.T) { +func TestFetchValidParams(t *testing.T) { rawUrl := "https://domain.test:8080/api/v1/pin" - testCases := make([]TestCaseFetch, 0) + // testCases := make([]TestCaseFetch, 0) - for count := 1; count != 5; count++ { - for lastID := 1; lastID != 5; lastID++ { - rawUrlWithParams := fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, count, lastID) - testCases = append(testCases, TestCaseFetch{ - rawURL: rawUrlWithParams, - expCount: count, - expLastID: lastID, - expErr: nil, - }) - } + tests := []struct { + name string + queryRow string + wantCount, wantLastID int + }{ + {"both parameters were passed correctly", "?count=6&lastID=12", 6, 12}, + {"both parameters were passed correctly in a different order", "?lastID=1&count=3", 3, 1}, + {"repeating parameters", "?count=14&lastID=1&count=3&lastID=55&lastID=65", 14, 1}, + {"repeating parameters", "?count=14&lastID=1&count=3&lastID=55&lastID=65", 14, 1}, + {"empty parameter lastID", "?count=7", 7, 0}, + {"the parameter lastID is registered but not specified", "?lastID=&count=17", 17, 0}, } - testCases = append(testCases, []TestCaseFetch{ - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 1), - expCount: 0, - expLastID: 0, - expErr: ErrBadParams, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 12312, 1), - expCount: 0, - expLastID: 0, - expErr: ErrBadParams, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 1), - expCount: 0, - expLastID: 0, - expErr: ErrBadParams, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, -1), - expCount: 0, - expLastID: 0, - expErr: ErrBadParams, - }, - { - rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 1), - expCount: 0, - expLastID: 0, - expErr: ErrCountParameterMissing, - }, - { - rawURL: fmt.Sprintf("%s?&lastID=%d", rawUrl, 4), - expCount: 0, - expLastID: 0, - expErr: ErrCountParameterMissing, - }, - }...) - for _, tCase := range testCases { - t.Run("TestFetchValidParamForLoadTape", func(t *testing.T) { - URL, _ := url.Parse(tCase.rawURL) - actualCount, actualLastID, actualErr := FetchValidParamForLoadTape(URL) - require.Equal(t, tCase.expCount, actualCount) - require.Equal(t, tCase.expLastID, actualLastID) - require.Equal(t, tCase.expErr, actualErr) + // testCases = append(testCases, []TestCaseFetch{ + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 1), + // expCount: 0, + // expLastID: 0, + // expErr: ErrBadParams, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 12312, 1), + // expCount: 0, + // expLastID: 0, + // expErr: ErrBadParams, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 1), + // expCount: 0, + // expLastID: 0, + // expErr: ErrBadParams, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, -1), + // expCount: 0, + // expLastID: 0, + // expErr: ErrBadParams, + // }, + // { + // rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 1), + // expCount: 0, + // expLastID: 0, + // expErr: ErrCountParameterMissing, + // }, + // { + // rawURL: fmt.Sprintf("%s?&lastID=%d", rawUrl, 4), + // expCount: 0, + // expLastID: 0, + // expErr: ErrCountParameterMissing, + // }, + // }...) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + URL, err := url.Parse(rawUrl + test.queryRow) + if err != nil { + t.Fatalf("error when parsing into the url.URL structure: %v", err) + } + actualCount, actualLastID, err := FetchValidParamForLoadTape(URL) + require.NoError(t, err) + require.Equal(t, test.wantCount, actualCount) + require.Equal(t, test.wantLastID, actualLastID) }) } } From 015e3ea7c628b20e5d5d89c6d01c06357f99b16e Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 14:43:06 +0300 Subject: [PATCH 044/266] dev add: ramrepo -- fillPinTableRows, fillSessionTableRows --- internal/pkg/repository/ramrepo/ramrepo.go | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 791943e..1c78dbd 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -33,6 +33,16 @@ func OpenDB() (*sql.DB, error) { return nil, err } + err = fillUsersTableRows(db) + if err != nil { + return nil, err + } + + err = fillSessionTableRows(db) + if err != nil { + return nil, err + } + return db, nil } @@ -74,6 +84,27 @@ func createSessionTable(db *sql.DB) error { return nil } +func fillUsersTableRows(db *sql.DB) error { + _, err := db.Exec(`INSERT INTO users (username, password, email) VALUES + ("dogsLover", "bf62b19f2f755d892f0ee1efb591795c198bcd0eecb69e58b153064e7ca11f384bf2e2746d91bf36", "dogslove@gmail.com"), + ("professional_player", "2f45a4f97b2d849448ac28cf95d4a55ddbc146f607e158e78b25a1906b469fe9ebde41b8127dd50e", "fortheplayers@yandex.ru"), + ("goodJobBer", "ade1af872d23126858c289e0c1bfc8b57502f7f0237e35fc64d08fab2d6b667358f04ac4174b736b", "jobjobjob@mail.ru");`) + if err != nil { + return fmt.Errorf("fill users table: %w", err) + } + return nil +} + +func fillSessionTableRows(db *sql.DB) error { + _, err := db.Exec(`INSERT INTO session (session_key, user_id, expire) VALUES + ("461afabf38b3147c", 1, 2024-10-03 10:52:09.243860007 +0000 UTC), + ("f4280a941b664d02", 3434, 2024-10-03 10:52:09.243860007 +0000 UTC);`) + if err != nil { + return fmt.Errorf("fill session table: %w", err) + } + return nil +} + func fillPinTableRows(db *sql.DB) error { _, err := db.Exec(`INSERT INTO pin (picture) VALUES ('https://i.pinimg.com/564x/e2/43/10/e24310fe1909ec1f1de347fedc6318b0.jpg'), From 99a88622adca78918a6dbaf573135a19eef6d5c9 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Mon, 2 Oct 2023 21:58:53 +0300 Subject: [PATCH 045/266] update API documantation files --- docs/docs.go | 164 +++++++++++------------------------ docs/swagger.json | 164 +++++++++++------------------------ docs/swagger.yaml | 108 +++++++++-------------- internal/pkg/service/auth.go | 35 ++++---- internal/pkg/service/pin.go | 14 +-- 5 files changed, 168 insertions(+), 317 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 0b5232d..56854d8 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -33,6 +33,15 @@ const docTemplate = `{ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", @@ -85,6 +94,7 @@ const docTemplate = `{ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -94,6 +104,7 @@ const docTemplate = `{ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -107,19 +118,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "session_key": { @@ -158,23 +157,20 @@ const docTemplate = `{ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "Session-id": { @@ -218,6 +214,7 @@ const docTemplate = `{ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -227,6 +224,7 @@ const docTemplate = `{ } }, { + "example": "clickkk@gmail.com", "description": "Email", "name": "email", "in": "body", @@ -236,6 +234,7 @@ const docTemplate = `{ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -249,19 +248,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" } }, "400": { @@ -288,58 +275,9 @@ const docTemplate = `{ "/api/v1/pin": { "get": { "description": "Get pin collection", - "produces": [ + "consumes": [ "application/json" ], - "tags": [ - "Pin" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "type": "array", - "items": { - "$ref": "#/definitions/Pin" - } - } - } - } - ] - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - } - } - } - }, - "/api/v1/pin/{pinId}": { - "get": { - "description": "Get concrete pin by id", "produces": [ "application/json" ], @@ -348,9 +286,17 @@ const docTemplate = `{ ], "parameters": [ { - "type": "integer", - "description": "Id of the pin", - "name": "pinId", + "type": "string", + "example": "2", + "description": "ID of the pin that will be just before the first pin in the requested collection, 0 by default", + "name": "lastID", + "in": "path" + }, + { + "type": "string", + "example": "5", + "description": "Pins quantity after last pin specified in lastID", + "name": "count", "in": "path", "required": true } @@ -367,7 +313,10 @@ const docTemplate = `{ "type": "object", "properties": { "body": { - "$ref": "#/definitions/Pin" + "type": "array", + "items": { + "$ref": "#/definitions/Pin" + } } } } @@ -397,15 +346,11 @@ const docTemplate = `{ } }, "definitions": { - "Empty": { - "type": "object" - }, "JsonErrResponse": { "type": "object", "properties": { "code": { - "type": "string", - "example": "0" + "type": "string" }, "message": { "type": "string", @@ -420,7 +365,9 @@ const docTemplate = `{ "JsonResponse": { "type": "object", "properties": { - "body": {}, + "body": { + "x-omitempty": true + }, "message": { "type": "string", "example": "Response message" @@ -434,14 +381,6 @@ const docTemplate = `{ "Pin": { "type": "object", "properties": { - "authorId": { - "type": "integer", - "example": 23 - }, - "description": { - "type": "string", - "example": "about face" - }, "id": { "type": "integer", "example": 55 @@ -449,19 +388,16 @@ const docTemplate = `{ "picture": { "type": "string", "example": "pinspire/imgs/image.png" - }, - "publicationTime": { - "type": "string" - }, - "title": { - "type": "string", - "example": "Nature's beauty" } } }, "User": { "type": "object", "properties": { + "email": { + "type": "string", + "example": "digital@gmail.com" + }, "password": { "type": "string", "example": "pass123" diff --git a/docs/swagger.json b/docs/swagger.json index 06298f4..70dc722 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -25,6 +25,15 @@ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", @@ -77,6 +86,7 @@ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -86,6 +96,7 @@ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -99,19 +110,7 @@ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "session_key": { @@ -150,23 +149,20 @@ "tags": [ "Auth" ], + "parameters": [ + { + "type": "string", + "example": "senjs7rvdnrgkjdr", + "description": "Auth session id", + "name": "session_key", + "in": "header" + } + ], "responses": { "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" }, "headers": { "Session-id": { @@ -210,6 +206,7 @@ ], "parameters": [ { + "example": "clicker123", "description": "Username", "name": "username", "in": "body", @@ -219,6 +216,7 @@ } }, { + "example": "clickkk@gmail.com", "description": "Email", "name": "email", "in": "body", @@ -228,6 +226,7 @@ } }, { + "example": "safe_pass", "description": "Password", "name": "password", "in": "body", @@ -241,19 +240,7 @@ "200": { "description": "OK", "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "$ref": "#/definitions/Empty" - } - } - } - ] + "$ref": "#/definitions/JsonResponse" } }, "400": { @@ -280,58 +267,9 @@ "/api/v1/pin": { "get": { "description": "Get pin collection", - "produces": [ + "consumes": [ "application/json" ], - "tags": [ - "Pin" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/JsonResponse" - }, - { - "type": "object", - "properties": { - "body": { - "type": "array", - "items": { - "$ref": "#/definitions/Pin" - } - } - } - } - ] - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/JsonErrResponse" - } - } - } - } - }, - "/api/v1/pin/{pinId}": { - "get": { - "description": "Get concrete pin by id", "produces": [ "application/json" ], @@ -340,9 +278,17 @@ ], "parameters": [ { - "type": "integer", - "description": "Id of the pin", - "name": "pinId", + "type": "string", + "example": "2", + "description": "ID of the pin that will be just before the first pin in the requested collection, 0 by default", + "name": "lastID", + "in": "path" + }, + { + "type": "string", + "example": "5", + "description": "Pins quantity after last pin specified in lastID", + "name": "count", "in": "path", "required": true } @@ -359,7 +305,10 @@ "type": "object", "properties": { "body": { - "$ref": "#/definitions/Pin" + "type": "array", + "items": { + "$ref": "#/definitions/Pin" + } } } } @@ -389,15 +338,11 @@ } }, "definitions": { - "Empty": { - "type": "object" - }, "JsonErrResponse": { "type": "object", "properties": { "code": { - "type": "string", - "example": "0" + "type": "string" }, "message": { "type": "string", @@ -412,7 +357,9 @@ "JsonResponse": { "type": "object", "properties": { - "body": {}, + "body": { + "x-omitempty": true + }, "message": { "type": "string", "example": "Response message" @@ -426,14 +373,6 @@ "Pin": { "type": "object", "properties": { - "authorId": { - "type": "integer", - "example": 23 - }, - "description": { - "type": "string", - "example": "about face" - }, "id": { "type": "integer", "example": 55 @@ -441,19 +380,16 @@ "picture": { "type": "string", "example": "pinspire/imgs/image.png" - }, - "publicationTime": { - "type": "string" - }, - "title": { - "type": "string", - "example": "Nature's beauty" } } }, "User": { "type": "object", "properties": { + "email": { + "type": "string", + "example": "digital@gmail.com" + }, "password": { "type": "string", "example": "pass123" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 28b4cb0..de01d6a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,10 +1,7 @@ definitions: - Empty: - type: object JsonErrResponse: properties: code: - example: "0" type: string message: example: Error description @@ -15,7 +12,8 @@ definitions: type: object JsonResponse: properties: - body: {} + body: + x-omitempty: true message: example: Response message type: string @@ -25,26 +23,18 @@ definitions: type: object Pin: properties: - authorId: - example: 23 - type: integer - description: - example: about face - type: string id: example: 55 type: integer picture: example: pinspire/imgs/image.png type: string - publicationTime: - type: string - title: - example: Nature's beauty - type: string type: object User: properties: + email: + example: digital@gmail.com + type: string password: example: pass123 type: string @@ -68,6 +58,12 @@ paths: /api/v1/auth/login: get: description: User login, check authentication, get user info + parameters: + - description: Auth session id + example: senjs7rvdnrgkjdr + in: header + name: session_key + type: string produces: - application/json responses: @@ -100,12 +96,14 @@ paths: description: User login, creating new session parameters: - description: Username + example: clicker123 in: body name: username required: true schema: type: string - description: Password + example: safe_pass in: body name: password required: true @@ -121,12 +119,7 @@ paths: description: Auth cookie with new valid session id type: string schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - $ref: '#/definitions/Empty' - type: object + $ref: '#/definitions/JsonResponse' "400": description: Bad Request schema: @@ -144,6 +137,12 @@ paths: /api/v1/auth/logout: delete: description: User logout, session deletion + parameters: + - description: Auth session id + example: senjs7rvdnrgkjdr + in: header + name: session_key + type: string produces: - application/json responses: @@ -154,12 +153,7 @@ paths: description: Auth cookie with expired session id type: string schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - $ref: '#/definitions/Empty' - type: object + $ref: '#/definitions/JsonResponse' "400": description: Bad Request schema: @@ -181,18 +175,21 @@ paths: description: User registration parameters: - description: Username + example: clicker123 in: body name: username required: true schema: type: string - description: Email + example: clickkk@gmail.com in: body name: email required: true schema: type: string - description: Password + example: safe_pass in: body name: password required: true @@ -204,12 +201,7 @@ paths: "200": description: OK schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - $ref: '#/definitions/Empty' - type: object + $ref: '#/definitions/JsonResponse' "400": description: Bad Request schema: @@ -226,44 +218,22 @@ paths: - Auth /api/v1/pin: get: - description: Get pin collection - produces: + consumes: - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/JsonResponse' - - properties: - body: - items: - $ref: '#/definitions/Pin' - type: array - type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/JsonErrResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/JsonErrResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/JsonErrResponse' - tags: - - Pin - /api/v1/pin/{pinId}: - get: - description: Get concrete pin by id + description: Get pin collection parameters: - - description: Id of the pin + - description: ID of the pin that will be just before the first pin in the requested + collection, 0 by default + example: "2" in: path - name: pinId + name: lastID + type: string + - description: Pins quantity after last pin specified in lastID + example: "5" + in: path + name: count required: true - type: integer + type: string produces: - application/json responses: @@ -274,7 +244,9 @@ paths: - $ref: '#/definitions/JsonResponse' - properties: body: - $ref: '#/definitions/Pin' + items: + $ref: '#/definitions/Pin' + type: array type: object "400": description: Bad Request diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index 04504e4..043eded 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -16,10 +16,11 @@ import ( // @Description User login, check authentication, get user info // @Tags Auth // @Produce json -// @Success 200 {object} JsonResponse{body=user.User} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse +// @Param session_key header string false "Auth session id" example(senjs7rvdnrgkjdr) +// @Success 200 {object} JsonResponse{body=user.User} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/login [get] func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { s.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) @@ -62,9 +63,9 @@ func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { // @Tags Auth // @Accept json // @Produce json -// @Param username body string true "Username" -// @Param password body string true "Password" -// @Success 200 {object} JsonResponse{body=Empty} +// @Param username body string true "Username" example(clicker123) +// @Param password body string true "Password" example(safe_pass) +// @Success 200 {object} JsonResponse // @Failure 400 {object} JsonErrResponse // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse @@ -138,10 +139,10 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { // @Tags Auth // @Accept json // @Produce json -// @Param username body string true "Username" -// @Param email body string true "Email" -// @Param password body string true "Password" -// @Success 200 {object} JsonResponse{body=Empty} +// @Param username body string true "Username" example(clicker123) +// @Param email body string true "Email" example(clickkk@gmail.com) +// @Param password body string true "Password" example(safe_pass) +// @Success 200 {object} JsonResponse // @Failure 400 {object} JsonErrResponse // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse @@ -189,11 +190,13 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { // @Description User logout, session deletion // @Tags Auth // @Produce json -// @Success 200 {object} JsonResponse{body=Empty} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Header 200 {string} Session-id "Auth cookie with expired session id" +// @Param session_key header string false "Auth session id" example(senjs7rvdnrgkjdr) +// +// @Success 200 {object} JsonResponse +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Header 200 {string} Session-id "Auth cookie with expired session id" // @Router /api/v1/auth/logout [delete] func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { s.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go index 2bff1ae..41332f2 100644 --- a/internal/pkg/service/pin.go +++ b/internal/pkg/service/pin.go @@ -16,12 +16,16 @@ var ErrBadParams = errors.New("bad params") // // @Description Get pin collection // @Tags Pin +// @Accept json // @Produce json -// @Success 200 {object} JsonResponse{body=[]Pin} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/pin [get] +// @Param lastID path string false "ID of the pin that will be just before the first pin in the requested collection, 0 by default" example(2) +// +// @Param count path string true "Pins quantity after last pin specified in lastID" example(5) +// @Success 200 {object} JsonResponse{body=[]Pin} +// @Failure 400 {object} JsonErrResponse +// @Failure 404 {object} JsonErrResponse +// @Failure 500 {object} JsonErrResponse +// @Router /api/v1/pin [get] func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { s.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) From 74edd2adb11bab1d577c922c5d0be32d5d9cdb64 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 00:13:24 +0300 Subject: [PATCH 046/266] delete Empty struct --- internal/pkg/service/response.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go index df442ed..df3121d 100644 --- a/internal/pkg/service/response.go +++ b/internal/pkg/service/response.go @@ -6,12 +6,15 @@ import ( "net/http" ) -type Empty struct{} // @name Empty +// type JsonResponseNoBody struct { +// Status string `json:"status" example:"ok"` +// Message string `json:"message" example:"Response message"` +// } type JsonResponse struct { Status string `json:"status" example:"ok"` Message string `json:"message" example:"Response message"` - Body interface{} `json:"body,omitempty"` + Body interface{} `json:"body" extensions:"x-omitempty"` } // @name JsonResponse type JsonErrResponse struct { From 83f6bdf485a25ec60a7f8323a57f122acbcdfb50 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 22:03:01 +0300 Subject: [PATCH 047/266] TP-933 update: results validation func --- internal/pkg/service/auth.go | 12 +++++----- internal/pkg/service/validation.go | 36 +++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index 043eded..074f94c 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -2,7 +2,6 @@ package service import ( "encoding/json" - "fmt" "net/http" "time" @@ -89,7 +88,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { if !isValidPassword(params.Password) || !isValidUsername(params.Username) { s.log.Info("invalid credentials") - err = responseError(w, "bad_credentials", "invalid user credentials") + err = responseError(w, "invalid_credentials", "invalid user credentials") if err != nil { s.log.Error(err.Error()) } @@ -99,7 +98,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { user, err := s.userCase.Authentication(r.Context(), params) if err != nil { s.log.Warn(err.Error()) - err = responseError(w, "bad_credentials", "invalid user credentials") + err = responseError(w, "bad_credentials", "incorrect user credentials") if err != nil { s.log.Error(err.Error()) } @@ -163,10 +162,9 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { return } - if !IsValidUserForRegistration(user) { - fmt.Println(isValidEmail(user.Email), isValidUsername(user.Username), isValidPassword(user.Password)) + if err := IsValidUserForRegistration(user); err != nil { s.log.Info("invalid user registration data") - err = responseError(w, "invalid_params", "invalid data for registration is specified") + err = responseError(w, "invalid_params", err.Error()) if err != nil { s.log.Error(err.Error()) } @@ -176,7 +174,7 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { err = s.userCase.Register(r.Context(), user) if err != nil { s.log.Warn(err.Error()) - err = responseError(w, "uniq_fields", "username") + err = responseError(w, "uniq_fields", "there is already an account with this username or password") } else { err = responseOk(w, "the user has been successfully registered", nil) } diff --git a/internal/pkg/service/validation.go b/internal/pkg/service/validation.go index 5afc516..9356faa 100644 --- a/internal/pkg/service/validation.go +++ b/internal/pkg/service/validation.go @@ -4,12 +4,30 @@ import ( "fmt" "net/url" "strconv" + "strings" "unicode" valid "github.com/asaskevich/govalidator" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +type errorFields []string + +func (b *errorFields) Error() string { + return strings.Join(*b, ",") +} + +func (b *errorFields) addInvalidField(fieldName string) { + *b = append(*b, fieldName) +} + +func (b *errorFields) Err() error { + if len(*b) == 0 { + return nil + } + return b +} + func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { if param := u.Query().Get("count"); len(param) > 0 { c, err := strconv.ParseInt(param, 10, 64) @@ -33,12 +51,20 @@ func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { return } -func IsValidUserForRegistration(user *user.User) bool { - return isValidPassword(user.Password) && isValidEmail(user.Email) && isValidUsername(user.Username) -} +func IsValidUserForRegistration(user *user.User) error { + invalidFields := new(errorFields) + + if !isValidPassword(user.Password) { + invalidFields.addInvalidField("password") + } + if !isValidEmail(user.Email) { + invalidFields.addInvalidField("email") + } + if !isValidUsername(user.Username) { + invalidFields.addInvalidField("username") + } -func IsValidUserForLogin(user *user.User) bool { - return isValidPassword(user.Password) && isValidUsername(user.Username) + return invalidFields.Err() } func isValidUsername(username string) bool { From de298fd700db3ef13a1708bc2d56a10d6bba7e7d Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 22:20:19 +0300 Subject: [PATCH 048/266] dev: rename function NewRandomString --- internal/usecases/session/manager.go | 2 +- internal/usecases/user/usecase.go | 2 +- pkg/crypto/crypto.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/usecases/session/manager.go b/internal/usecases/session/manager.go index 9b670be..a7a3b8e 100644 --- a/internal/usecases/session/manager.go +++ b/internal/usecases/session/manager.go @@ -28,7 +28,7 @@ func New(log *logger.Logger, repo repo.Repository) *SessionManager { } func (sm *SessionManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { - sessionKey, err := crypto.NewRandomStr(lenSessionKey) + sessionKey, err := crypto.NewRandomString(lenSessionKey) if err != nil { return nil, fmt.Errorf("session key generation for new session: %w", err) } diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go index 80f88fc..af0ceb6 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/usecases/user/usecase.go @@ -28,7 +28,7 @@ func New(log *logger.Logger, repo repo.Repository) *Usecase { } func (u *Usecase) Register(ctx context.Context, user *entity.User) error { - salt, err := crypto.NewRandomStr(lenSalt) + salt, err := crypto.NewRandomString(lenSalt) if err != nil { return fmt.Errorf("generating salt for registration: %w", err) } diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index 377dd0e..aa12ffd 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -9,7 +9,7 @@ import ( "golang.org/x/crypto/argon2" ) -func NewRandomStr(length int) (string, error) { +func NewRandomString(length int) (string, error) { rand.Seed(time.Now().UTC().UnixNano()) b := make([]byte, hex.DecodedLen(length)) _, err := rand.Read(b) From 80fcb245629acac70525df805f60db0d1a9ff1e9 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Mon, 2 Oct 2023 22:38:26 +0300 Subject: [PATCH 049/266] dev: update: package crypto --- pkg/crypto/crypto.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index aa12ffd..e7ebd3c 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -1,16 +1,14 @@ package crypto import ( + "crypto/rand" "encoding/hex" - "math/rand" - "time" "unsafe" "golang.org/x/crypto/argon2" ) func NewRandomString(length int) (string, error) { - rand.Seed(time.Now().UTC().UnixNano()) b := make([]byte, hex.DecodedLen(length)) _, err := rand.Read(b) if err != nil { From 404ee82f240251378661bbf88e6d0a44741a21a2 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 3 Oct 2023 00:22:48 +0300 Subject: [PATCH 050/266] dev add: default avatar for user --- internal/pkg/repository/ramrepo/ramrepo.go | 156 +++++++++++++++++++-- internal/pkg/repository/ramrepo/user.go | 11 +- internal/pkg/repository/user/repo.go | 2 +- internal/pkg/service/auth.go | 4 +- internal/usecases/user/usecase.go | 4 +- 5 files changed, 153 insertions(+), 24 deletions(-) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 4ab99eb..04add3f 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -42,7 +42,7 @@ func createUsersTable(db *sql.DB) error { username varchar(30) UNIQUE, password varchar(50), email varchar(50) UNIQUE, - avatar varchar(50) + avatar varchar(50) DEFAULT '' );`) if err != nil { return fmt.Errorf("create table users: %w", err) @@ -76,18 +76,148 @@ func createSessionTable(db *sql.DB) error { func fillPinTableRows(db *sql.DB) error { _, err := db.Exec(`INSERT INTO pin (picture) VALUES - ('/1'), - ('/2'), - ('/3'), - ('/4'), - ('/5'), - ('/6'), - ('/7'), - ('/8'), - ('/9'), - ('/10'), - ('/11'), - ('/12');`) + ('https://i.pinimg.com/564x/e2/43/10/e24310fe1909ec1f1de347fedc6318b0.jpg'), + ('https://i.pinimg.com/564x/91/39/51/913951d97d3cc3ac5a4ecb58da2ffdf5.jpg'), + ('https://i.pinimg.com/564x/91/39/51/913951d97d3cc3ac5a4ecb58da2ffdf5.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), + ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), + ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), + ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), + ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), + ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), + ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), + ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), + ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg');`) if err != nil { return fmt.Errorf("fill pin table: %w", err) } diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index 5db8b71..e50cdca 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -34,12 +34,11 @@ func (r *ramUserRepo) GetUserByUsername(ctx context.Context, username string) (* return user, nil } -func (r *ramUserRepo) GetUsernameByID(ctx context.Context, userID int) (string, error) { - row := r.db.QueryRowContext(ctx, "SELECT username FROM users WHERE id = $1;", userID) - var username string - err := row.Scan(&username) +func (r *ramUserRepo) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) { + row := r.db.QueryRowContext(ctx, "SELECT username, avatar FROM users WHERE id = $1;", userID) + err = row.Scan(&username, &avatar) if err != nil { - return "", fmt.Errorf("getting a username from storage by id: %w", err) + return "", "", fmt.Errorf("getting a username from storage by id: %w", err) } - return username, nil + return } diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 90cecd5..b6b9447 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -9,5 +9,5 @@ import ( type Repository interface { AddNewUser(ctx context.Context, user *user.User) error GetUserByUsername(ctx context.Context, username string) (*user.User, error) - GetUsernameByID(ctx context.Context, userID int) (string, error) + GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) } diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index 074f94c..e146083 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -44,12 +44,12 @@ func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { return } - username, err := s.userCase.FindOutUserName(r.Context(), userID) + username, avatar, err := s.userCase.FindOutUsernameAndAvatar(r.Context(), userID) if err != nil { s.log.Error(err.Error()) err = responseError(w, "no_auth", "no user was found for this session") } else { - err = responseOk(w, "user found", map[string]string{"username": username}) + err = responseOk(w, "user found", map[string]string{"username": username, "avatar": avatar}) } if err != nil { s.log.Error(err.Error()) diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go index af0ceb6..b9cd907 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/usecases/user/usecase.go @@ -54,6 +54,6 @@ func (u *Usecase) Authentication(ctx context.Context, credentials userCredential return user, nil } -func (u *Usecase) FindOutUserName(ctx context.Context, userID int) (string, error) { - return u.repo.GetUsernameByID(ctx, userID) +func (u *Usecase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { + return u.repo.GetUsernameAndAvatarByID(ctx, userID) } From eb4d757b17c0f41f6a9d443481949376051defa2 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 3 Oct 2023 01:53:08 +0300 Subject: [PATCH 051/266] dev add: default avatar --- internal/pkg/repository/ramrepo/ramrepo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 04add3f..791943e 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -42,7 +42,7 @@ func createUsersTable(db *sql.DB) error { username varchar(30) UNIQUE, password varchar(50), email varchar(50) UNIQUE, - avatar varchar(50) DEFAULT '' + avatar varchar(50) DEFAULT 'https://cdn-icons-png.flaticon.com/512/149/149071.png' );`) if err != nil { return fmt.Errorf("create table users: %w", err) From 4c3c9b67ba04559966fe2bb4acc360cbb93b1ed7 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 14:43:06 +0300 Subject: [PATCH 052/266] dev add: ramrepo -- fillPinTableRows, fillSessionTableRows --- internal/pkg/repository/ramrepo/ramrepo.go | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 791943e..1c78dbd 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -33,6 +33,16 @@ func OpenDB() (*sql.DB, error) { return nil, err } + err = fillUsersTableRows(db) + if err != nil { + return nil, err + } + + err = fillSessionTableRows(db) + if err != nil { + return nil, err + } + return db, nil } @@ -74,6 +84,27 @@ func createSessionTable(db *sql.DB) error { return nil } +func fillUsersTableRows(db *sql.DB) error { + _, err := db.Exec(`INSERT INTO users (username, password, email) VALUES + ("dogsLover", "bf62b19f2f755d892f0ee1efb591795c198bcd0eecb69e58b153064e7ca11f384bf2e2746d91bf36", "dogslove@gmail.com"), + ("professional_player", "2f45a4f97b2d849448ac28cf95d4a55ddbc146f607e158e78b25a1906b469fe9ebde41b8127dd50e", "fortheplayers@yandex.ru"), + ("goodJobBer", "ade1af872d23126858c289e0c1bfc8b57502f7f0237e35fc64d08fab2d6b667358f04ac4174b736b", "jobjobjob@mail.ru");`) + if err != nil { + return fmt.Errorf("fill users table: %w", err) + } + return nil +} + +func fillSessionTableRows(db *sql.DB) error { + _, err := db.Exec(`INSERT INTO session (session_key, user_id, expire) VALUES + ("461afabf38b3147c", 1, 2024-10-03 10:52:09.243860007 +0000 UTC), + ("f4280a941b664d02", 3434, 2024-10-03 10:52:09.243860007 +0000 UTC);`) + if err != nil { + return fmt.Errorf("fill session table: %w", err) + } + return nil +} + func fillPinTableRows(db *sql.DB) error { _, err := db.Exec(`INSERT INTO pin (picture) VALUES ('https://i.pinimg.com/564x/e2/43/10/e24310fe1909ec1f1de347fedc6318b0.jpg'), From 0f0ec496f6b0af868cb7ff944171319e16d7c1fa Mon Sep 17 00:00:00 2001 From: Gvidow Date: Tue, 3 Oct 2023 15:35:52 +0300 Subject: [PATCH 053/266] TP-6d1 add: choose https/http in config/config.yml --- cmd/app/main.go | 6 +++++- configs/config.yml | 5 +++-- internal/api/server/config.go | 18 +++++++++++++++--- internal/api/server/router/router.go | 2 +- internal/api/server/server.go | 6 +++++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index 09adea6..800a6fc 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -49,7 +49,11 @@ func main() { pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) service := service.New(log, sm, userCase, pinCase) - server := server.New(log, server.NewConfig(cfg)) + cfgServ, err := server.NewConfig(cfg) + if err != nil { + log.Fatal(err.Error()) + } + server := server.New(log, cfgServ) server.InitRouter(service) if err := server.Run(); err != nil { log.Fatal(err.Error()) diff --git a/configs/config.yml b/configs/config.yml index 9c13b42..4d1fda6 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -2,5 +2,6 @@ app: server: host: 0.0.0.0 port: 8080 - certFile: cert/ - keyFile: cert/ + https: false + certFile: /home/ond_team/cert/fullchain.pem + keyFile: /home/ond_team/cert/privkey.pem diff --git a/internal/api/server/config.go b/internal/api/server/config.go index 04b7005..02fe563 100644 --- a/internal/api/server/config.go +++ b/internal/api/server/config.go @@ -1,22 +1,34 @@ package server -import "go.uber.org/config" +import ( + "fmt" + "strconv" + + "go.uber.org/config" +) type Config struct { Host string Port string CertFile string KeyFile string + https bool } const ConfigName = "app.server" -func NewConfig(cfg *config.YAML) *Config { +func NewConfig(cfg *config.YAML) (*Config, error) { value := cfg.Get(ConfigName) - return &Config{ + c := &Config{ Host: value.Get("host").String(), Port: value.Get("port").String(), CertFile: value.Get("certFile").String(), KeyFile: value.Get("keyFile").String(), } + b, err := strconv.ParseBool(value.Get("https").String()) + if err != nil { + return nil, fmt.Errorf("parse param https in server.Config: %w", err) + } + c.https = b + return c, nil } diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index df1b2ee..fab4a0c 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -21,7 +21,7 @@ func New() Router { func (r Router) InitRoute(serv *service.Service) { c := cors.New(cors.Options{ - AllowedOrigins: []string{"https://pinspire.online"}, + AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, AllowCredentials: true, AllowedHeaders: []string{"content-type"}, diff --git a/internal/api/server/server.go b/internal/api/server/server.go index 8574437..4cd9d35 100644 --- a/internal/api/server/server.go +++ b/internal/api/server/server.go @@ -35,7 +35,11 @@ func (s *Server) Run() error { } s.Handler = s.router.Mux s.log.Info("server start") - return s.ListenAndServeTLS(s.cfg.CertFile, s.cfg.KeyFile) + if s.cfg.https { + return s.ListenAndServeTLS(s.cfg.CertFile, s.cfg.KeyFile) + } else { + return s.ListenAndServe() + } } func (s *Server) InitRouter(serv *service.Service) { From 2368a832576f6cecff1927e6d0050543e38572be Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 16:22:09 +0300 Subject: [PATCH 054/266] TP-565 add: TestCheckLogin --- internal/pkg/service/auth_test.go | 124 ++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 internal/pkg/service/auth_test.go diff --git a/internal/pkg/service/auth_test.go b/internal/pkg/service/auth_test.go new file mode 100644 index 0000000..982d388 --- /dev/null +++ b/internal/pkg/service/auth_test.go @@ -0,0 +1,124 @@ +package service + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/stretchr/testify/require" +) + +func TestCheckLogin(t *testing.T) { + log, _ := logger.New(logger.RFC3339FormatTime()) + defer log.Sync() + + db, _ := ramrepo.OpenDB() + defer db.Close() + + sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + userCase := user.New(log, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil) + + url := "https://domain.test:8080/api/v1/login" + goodCases := []struct { + name string + cookie *http.Cookie + expResp JsonResponse + }{ + { + "sending valid session_key", + &http.Cookie{ + Name: "session_key", + Value: "461afabf38b3147c", + }, + JsonResponse{ + Status: "ok", + Message: "user found", + Body: map[string]interface{}{"username": "dogsLover", "avatar": "https://cdn-icons-png.flaticon.com/512/149/149071.png"}, + }, + }, + } + + for _, tCase := range goodCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, url, nil) + req.AddCookie(tCase.cookie) + w := httptest.NewRecorder() + + service.CheckLogin(w, req) + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + var actualResp JsonResponse + json.Unmarshal(body, &actualResp) + actualResp.Body = actualResp.Body.(map[string]interface{}) + require.Equal(t, tCase.expResp, actualResp) + }) + } + + badCases := []struct { + name string + cookie *http.Cookie + expResp JsonErrResponse + }{ + { + "sending empty cookie", + &http.Cookie{ + Name: "", + Value: "", + }, + JsonErrResponse{ + Status: "error", + Message: "the user is not logged in", + Code: "no_auth", + }, + }, + { + "sending invalid cookie", + &http.Cookie{ + Name: "session_key", + Value: "doesn't exist", + }, + JsonErrResponse{ + Status: "error", + Message: "no user session found", + Code: "no_auth", + }, + }, + { + "sending cookie with invald user", + &http.Cookie{ + Name: "session_key", + Value: "f4280a941b664d02", + }, + JsonErrResponse{ + Status: "error", + Message: "no user was found for this session", + Code: "no_auth", + }, + }, + } + + for _, tCase := range badCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, url, nil) + req.AddCookie(tCase.cookie) + w := httptest.NewRecorder() + + service.CheckLogin(w, req) + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + var actualResp JsonErrResponse + json.Unmarshal(body, &actualResp) + require.Equal(t, tCase.expResp, actualResp) + }) + } + +} From f915c855ffb31b736af3d186c699a792af2bee81 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Tue, 3 Oct 2023 20:51:04 +0300 Subject: [PATCH 055/266] TP-565 add: TeshLogin --- internal/pkg/service/auth_test.go | 173 ++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 8 deletions(-) diff --git a/internal/pkg/service/auth_test.go b/internal/pkg/service/auth_test.go index 982d388..7bdd100 100644 --- a/internal/pkg/service/auth_test.go +++ b/internal/pkg/service/auth_test.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "net/http/httptest" + "strings" "testing" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" @@ -14,6 +15,34 @@ import ( "github.com/stretchr/testify/require" ) +func unpackOkResponse(recorder *httptest.ResponseRecorder) JsonResponse { + resp := recorder.Result() + body, _ := io.ReadAll(resp.Body) + var actualResp JsonResponse + json.Unmarshal(body, &actualResp) + return actualResp +} + +func unpackErrResponse(recorder *httptest.ResponseRecorder) JsonErrResponse { + resp := recorder.Result() + body, _ := io.ReadAll(resp.Body) + var actualResp JsonErrResponse + json.Unmarshal(body, &actualResp) + return actualResp +} + +func checkAuthCookie(cookies []*http.Cookie) bool { + if cookies == nil { + return false + } + for _, cookie := range cookies { + if cookie.Name == "session_key" { + return true + } + } + return false +} + func TestCheckLogin(t *testing.T) { log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() @@ -53,10 +82,7 @@ func TestCheckLogin(t *testing.T) { service.CheckLogin(w, req) - resp := w.Result() - body, _ := io.ReadAll(resp.Body) - var actualResp JsonResponse - json.Unmarshal(body, &actualResp) + var actualResp JsonResponse = unpackOkResponse(w) actualResp.Body = actualResp.Body.(map[string]interface{}) require.Equal(t, tCase.expResp, actualResp) }) @@ -113,12 +139,143 @@ func TestCheckLogin(t *testing.T) { service.CheckLogin(w, req) - resp := w.Result() - body, _ := io.ReadAll(resp.Body) - var actualResp JsonErrResponse - json.Unmarshal(body, &actualResp) + var actualResp JsonErrResponse = unpackErrResponse(w) + require.Equal(t, tCase.expResp, actualResp) + }) + } + +} + +func TestLogin(t *testing.T) { + url := "https://domain.test:8080/api/v1/login" + log, _ := logger.New(logger.RFC3339FormatTime()) + defer log.Sync() + + db, _ := ramrepo.OpenDB() + defer db.Close() + + sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + userCase := user.New(log, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil) + + goodCases := []struct { + name string + rawBody string + expResp JsonResponse + }{ + { + "providing correct and valid user credentials", + `{"username":"dogsLover", "password":"big_string"}`, + JsonResponse{ + Status: "ok", + Message: "a new session has been created for the user", + Body: nil, + }, + }, + } + + for _, tCase := range goodCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) + w := httptest.NewRecorder() + + service.Login(w, req) + + var actualResp JsonResponse = unpackOkResponse(w) require.Equal(t, tCase.expResp, actualResp) + require.True(t, checkAuthCookie(w.Result().Cookies())) }) } + badCases := []struct { + name string + rawBody string + expResp JsonErrResponse + }{ + { + "providing invalid credentials - broken body", + "{'username': 'dogsLover', 'password': 'big_string'", + JsonErrResponse{ + Status: "error", + Message: "the correct username and password are expected to be received in JSON format", + Code: "parse_body", + }, + }, + { + "providing invalid credentials - no username", + `{"password":"big_string"}`, + JsonErrResponse{ + Status: "error", + Message: "invalid user credentials", + Code: "invalid_credentials", + }, + }, + { + "providing invalid credentials - no password", + `{"username":"dogsLover"}`, + JsonErrResponse{ + Status: "error", + Message: "invalid user credentials", + Code: "invalid_credentials", + }, + }, + { + "providing invalid credentials - short username", + `{"username":"do", "password":"big_string"}`, + JsonErrResponse{ + Status: "error", + Message: "invalid user credentials", + Code: "invalid_credentials", + }, + }, + { + "providing invalid credentials - long username", + `{"username":"dojsbrjfbdrjhbhjldrbgbdrhjgbdjrbgjdhbgjhdbrghbdhj,gbdhjrbgjhdbvkvghkevfghjdvrfhvdhrvbjdfgdrgdr","password":"big_string"}`, + JsonErrResponse{ + Status: "error", + Message: "invalid user credentials", + Code: "invalid_credentials", + }, + }, + { + "providing invalid credentials - short password", + `{"username":"dogsLover","password":"bi"}`, + JsonErrResponse{ + Status: "error", + Message: "invalid user credentials", + Code: "invalid_credentials", + }, + }, + { + "providing invalid credentials - long password", + `{"username":"dogsLover","password":"biyugsgrusgubskhvfhkdgvfgvdvrjgbsjhgjkshzkljfskfwjkhkfjisuidgoquakflsjuzeofiow3i"}`, + JsonErrResponse{ + Status: "error", + Message: "invalid user credentials", + Code: "invalid_credentials", + }, + }, + { + "providing incorrect credentials - no user with such credentials", + `{"username":"dogsLover", "password":"doesn't_exist"}`, + JsonErrResponse{ + Status: "error", + Message: "incorrect user credentials", + Code: "bad_credentials", + }, + }, + } + + for _, tCase := range badCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) + w := httptest.NewRecorder() + + service.Login(w, req) + + var actualResp JsonErrResponse = unpackErrResponse(w) + require.Equal(t, tCase.expResp, actualResp) + require.False(t, checkAuthCookie(w.Result().Cookies())) + }) + } } From eb29292f183e0b70b939b42dad7df2ad3982fd45 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 01:58:24 +0300 Subject: [PATCH 056/266] dev del: block else --- internal/api/server/server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/api/server/server.go b/internal/api/server/server.go index 4cd9d35..ad8bd44 100644 --- a/internal/api/server/server.go +++ b/internal/api/server/server.go @@ -37,9 +37,8 @@ func (s *Server) Run() error { s.log.Info("server start") if s.cfg.https { return s.ListenAndServeTLS(s.cfg.CertFile, s.cfg.KeyFile) - } else { - return s.ListenAndServe() } + return s.ListenAndServe() } func (s *Server) InitRouter(serv *service.Service) { From 09732de0ae9df5011e22cf43269f62693e1f8949 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Wed, 4 Oct 2023 10:55:15 +0300 Subject: [PATCH 057/266] added dataSourceName as a new OpenDB argument --- cmd/app/main.go | 2 +- internal/pkg/repository/ramrepo/ramrepo.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index 800a6fc..12cd406 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -38,7 +38,7 @@ func main() { log.Fatal(err.Error()) } - db, err := ramrepo.OpenDB() + db, err := ramrepo.OpenDB("RamRepository") if err != nil { log.Fatal(err.Error()) } diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 1c78dbd..22abbca 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -7,8 +7,8 @@ import ( _ "github.com/proullon/ramsql/driver" ) -func OpenDB() (*sql.DB, error) { - db, err := sql.Open("ramsql", "RamRepository") +func OpenDB(dataSourceName string) (*sql.DB, error) { + db, err := sql.Open("ramsql", dataSourceName) if err != nil { return nil, err } From 3926040910541e6ae19b212775cc655ce4755b79 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 12:51:28 +0300 Subject: [PATCH 058/266] TP-a49 add: fileserver --- cmd/fileserver/main.go | 15 + internal/pkg/repository/ramrepo/ramrepo.go | 286 +++++++++--------- upload/avatars/default-avatar.png | Bin 0 -> 19456 bytes .../pins/2ac2ad104cdd4ca0981bac73777ad368.png | Bin 0 -> 19506 bytes .../pins/369cb5bac5ce4496be9d96c2517d45f9.png | Bin 0 -> 51770 bytes .../pins/43b8b6602f9d404ca3510f28fb712026.png | Bin 0 -> 19995 bytes .../pins/5d3c0b4625fc4b73b60351be053e46af.png | Bin 0 -> 91130 bytes .../pins/60ce511226c14573a0151f5e5893b15d.png | Bin 0 -> 120601 bytes .../pins/7b92db4d365d4c409e18b7869df1e448.png | Bin 0 -> 65561 bytes .../pins/9921115ac96c4223ab36a9bb77fd1e3a.png | Bin 0 -> 86915 bytes .../pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png | Bin 0 -> 122799 bytes .../pins/aa9128758dfd442382f41ef5bf3c55d3.png | Bin 0 -> 42407 bytes .../pins/aed4d0f218564a5a819ac2348fa97d27.png | Bin 0 -> 25449 bytes .../pins/d7dc22616d514788b514fc2edb60920b.png | Bin 0 -> 122269 bytes .../pins/df4f0038efe24e86a471444f94bc863e.png | Bin 0 -> 21931 bytes .../pins/ec66fd27b7f74524894740c5c830327e.png | Bin 0 -> 65892 bytes 16 files changed, 158 insertions(+), 143 deletions(-) create mode 100644 cmd/fileserver/main.go create mode 100644 upload/avatars/default-avatar.png create mode 100644 upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png create mode 100644 upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png create mode 100644 upload/pins/43b8b6602f9d404ca3510f28fb712026.png create mode 100644 upload/pins/5d3c0b4625fc4b73b60351be053e46af.png create mode 100644 upload/pins/60ce511226c14573a0151f5e5893b15d.png create mode 100644 upload/pins/7b92db4d365d4c409e18b7869df1e448.png create mode 100644 upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png create mode 100644 upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png create mode 100644 upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png create mode 100644 upload/pins/aed4d0f218564a5a819ac2348fa97d27.png create mode 100644 upload/pins/d7dc22616d514788b514fc2edb60920b.png create mode 100644 upload/pins/df4f0038efe24e86a471444f94bc863e.png create mode 100644 upload/pins/ec66fd27b7f74524894740c5c830327e.png diff --git a/cmd/fileserver/main.go b/cmd/fileserver/main.go new file mode 100644 index 0000000..96da550 --- /dev/null +++ b/cmd/fileserver/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "net/http" +) + +func main() { + fs := http.FileServer(http.Dir("upload")) + + s := &http.Server{ + Addr: ":8081", + Handler: http.StripPrefix("/upload/", fs), + } + s.ListenAndServe() +} diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 22abbca..489bf85 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -52,7 +52,7 @@ func createUsersTable(db *sql.DB) error { username varchar(30) UNIQUE, password varchar(50), email varchar(50) UNIQUE, - avatar varchar(50) DEFAULT 'https://cdn-icons-png.flaticon.com/512/149/149071.png' + avatar varchar(50) DEFAULT 'https://pinspire.online:8081/upload/avatars/default-avatar.png' );`) if err != nil { return fmt.Errorf("create table users: %w", err) @@ -107,148 +107,148 @@ func fillSessionTableRows(db *sql.DB) error { func fillPinTableRows(db *sql.DB) error { _, err := db.Exec(`INSERT INTO pin (picture) VALUES - ('https://i.pinimg.com/564x/e2/43/10/e24310fe1909ec1f1de347fedc6318b0.jpg'), - ('https://i.pinimg.com/564x/91/39/51/913951d97d3cc3ac5a4ecb58da2ffdf5.jpg'), - ('https://i.pinimg.com/564x/91/39/51/913951d97d3cc3ac5a4ecb58da2ffdf5.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/49/23/a9/4923a9a174fc87ab806121e79fda51e4.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/57/21/90/5721907848655c918c667d84defb99f8.jpg'), - ('https://i.pinimg.com/564x/f8/bd/0a/f8bd0aeae74e94e12eb57b6ae3280d6c.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg'), - ('https://i.pinimg.com/564x/32/80/5e/32805ec1935f0e4d2e4544d328512e03.jpg'), - ('https://i.pinimg.com/564x/f7/f8/d4/f7f8d4200cb60af122be89a39fd45c57.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/bc/07/62/bc07626808f2f1385e6d38765ff115cc.jpg'), - ('https://i.pinimg.com/564x/ec/b9/ca/ecb9cae2e1f174aca65d5d369f9a71d9.jpg'), - ('https://i.pinimg.com/564x/43/67/15/4367152cd5654e8e74afab54823732ef.jpg'), - ('https://i.pinimg.com/564x/30/da/d2/30dad2f5d5923e7a7715fe25ea590d35.jpg'), - ('https://i.pinimg.com/564x/ff/03/1f/ff031f62ad3e9e3733ed78216064978c.jpg'), - ('https://i.pinimg.com/564x/b0/17/fe/b017fea78ff90de1187b857166f12af8.jpg');`) + ('https://pinspire.online:8081/upload/pins/d7dc22616d514788b514fc2edb60920b.png'), + ('https://pinspire.online:8081/upload/pins/ec66fd27b7f74524894740c5c830327e.png'), + ('https://pinspire.online:8081/upload/pins/ec66fd27b7f74524894740c5c830327e.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/9921115ac96c4223ab36a9bb77fd1e3a.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/7b92db4d365d4c409e18b7869df1e448.png'), + ('https://pinspire.online:8081/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png'), + ('https://pinspire.online:8081/upload/pins/df4f0038efe24e86a471444f94bc863e.png'), + ('https://pinspire.online:8081/upload/pins/2ac2ad104cdd4ca0981bac73777ad368.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/5d3c0b4625fc4b73b60351be053e46af.png'), + ('https://pinspire.online:8081/upload/pins/43b8b6602f9d404ca3510f28fb712026.png'), + ('https://pinspire.online:8081/upload/pins/60ce511226c14573a0151f5e5893b15d.png'), + ('https://pinspire.online:8081/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png'), + ('https://pinspire.online:8081/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png'), + ('https://pinspire.online:8081/upload/pins/369cb5bac5ce4496be9d96c2517d45f9.png');`) if err != nil { return fmt.Errorf("fill pin table: %w", err) } diff --git a/upload/avatars/default-avatar.png b/upload/avatars/default-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..7127aa8629ca756098a3265d8f08f00d427133b7 GIT binary patch literal 19456 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 QD0MRv%(c1#tTvzj+3@$6p8x;= literal 0 HcmV?d00001 diff --git a/upload/pins/43b8b6602f9d404ca3510f28fb712026.png b/upload/pins/43b8b6602f9d404ca3510f28fb712026.png new file mode 100644 index 0000000000000000000000000000000000000000..3279dc419621fe3b236edcfa7f8affd77088415d GIT binary patch literal 19995 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 Xf+c1lOD@~yIt^{}23vPfEGPfj1&AWM literal 0 HcmV?d00001 diff --git a/upload/pins/60ce511226c14573a0151f5e5893b15d.png b/upload/pins/60ce511226c14573a0151f5e5893b15d.png new file mode 100644 index 0000000000000000000000000000000000000000..ccb25cfd5a0235df456414a49a1f42e2c19c8efd GIT binary patch literal 120601 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

(QGK6K8#i8!w^jLA+| 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� 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
SOG)B)+Q_u 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 literal 0 HcmV?d00001 diff --git a/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png b/upload/pins/a5a7a7871ead45b9a0626b2a1e1f3a44.png new file mode 100644 index 0000000000000000000000000000000000000000..f290c6ab30d9fc8794fd5ddbd84b1507395d9555 GIT binary patch literal 122799 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
  • 3_=wNc(rrTDrK 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 DX0D=j literal 0 HcmV?d00001 diff --git a/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png b/upload/pins/aa9128758dfd442382f41ef5bf3c55d3.png new file mode 100644 index 0000000000000000000000000000000000000000..825fc14db9056b8513a0de03d0b0c47fe5665589 GIT binary patch literal 42407 zcmcG#byQqU(+4;>!3h$a03kTR-QC@NAh^3jaEB1w-Q5WUw-DStxJ$6XA$xhA_x;Yd zdv^Dn{bQRxeP_C>epOx7x25R$yZCnlgdrm#Edhdpf&xhaf1tlBAW;xJEF3%>EId3M zJOTndA`%7?(wjF(*l6e|81Jz0@ZMqL;t~*3k`fS75aHs!r+-iJk&1?v2A`CHiGiAl zlA4D46$AO^gf(0PN zF~vY2BpDEZ4UoWLisk-G0j-QJjsdh90g_-rNI*N35OloI{}tm^27rK93d#jYs6i^P zITCu1yN!eB?2;y zp@fhCq+BRzA?OGp={ksdu?RMTRzhKs9zpr=bat}iZ zM>rDLuR?O6zW^#nf~X@Y0VM!M-iGIMzX;uST7PBfB_s%)&ucpWiu)&cBNaX@jm9YOuEIo;tAhyj+>_=5ZQ|3?`culCR1^EekBj1c@D)mcN zf`bwQbfv~lgd_igL>?go*zL8SQQO&^cU(aY+xt?U4g>3c+woI-4+h%d*etC(rU{N^(}E(ouSp4b;i&;mK<+IycRK-;w8I%ZdZJyL%Bsv!&fTo zK0dKQh!P4iNBBK6?6?Uz4i5Q>wW6r&qw9*L1l0gT=E-p1`%#}>QsT9Z+l2;IXbMn? zO>#EI+zK81xIXGrkm$4*WgT(5hzjA*_k6n>e#2+wmv!XFf4x8ZV)(GWXP(8{KWO@< zUFy4e?r|S+7>8~nA&2F%X;%)oCT03c2E-8bfwV$NNlBeV!VpcL+gR<8G1@f+ANrbT zyk~GNw-SC3#zdcEjiSOGUgEbxM@jZt?gWF&1WG-#iNfy*a$MzDZc&{634UIA@7n%@ zK^yL7_AGpG{M_EH75CdO)JlFerr6k@&=vRhq6vPs$7J5Z5IG&Pd2w6tI$S}rlP(XB zPA*yaNo>8*80|egKFLln3)`{%+Td~8{kg#FLQvnE;gFVU*8LN)7>i!2_FyhfxWFyl z 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 literal 0 HcmV?d00001 diff --git a/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png b/upload/pins/aed4d0f218564a5a819ac2348fa97d27.png new file mode 100644 index 0000000000000000000000000000000000000000..e177672ce033860860c7913a132149e4002dd720 GIT binary patch literal 25449 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 literal 0 HcmV?d00001 diff --git a/upload/pins/d7dc22616d514788b514fc2edb60920b.png b/upload/pins/d7dc22616d514788b514fc2edb60920b.png new file mode 100644 index 0000000000000000000000000000000000000000..590663b220fcbbce6c7acfac508d4429b23cd17a GIT binary patch literal 122269 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 literal 0 HcmV?d00001 diff --git a/upload/pins/df4f0038efe24e86a471444f94bc863e.png b/upload/pins/df4f0038efe24e86a471444f94bc863e.png new file mode 100644 index 0000000000000000000000000000000000000000..e08182db817832829dda683c8cedc581f776f190 GIT binary patch literal 21931 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 literal 0 HcmV?d00001 diff --git a/upload/pins/ec66fd27b7f74524894740c5c830327e.png b/upload/pins/ec66fd27b7f74524894740c5c830327e.png new file mode 100644 index 0000000000000000000000000000000000000000..3e0845ea3aaba45957e7de69fa8b2fccee5866b7 GIT binary patch literal 65892 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%0DryTOM2}g9imUaMRf0+dbBl& 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ʻ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 literal 0 HcmV?d00001 From e63ba1f28b5949b22b6b044f96eebb0c668405d2 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Wed, 4 Oct 2023 13:11:48 +0300 Subject: [PATCH 059/266] TP-565 add,update: added TestSignUp, TestLogout, updated previous tests --- internal/pkg/service/auth_test.go | 230 +++++++++++++++++++++++++++--- internal/pkg/service/pin_test.go | 11 +- 2 files changed, 212 insertions(+), 29 deletions(-) diff --git a/internal/pkg/service/auth_test.go b/internal/pkg/service/auth_test.go index 7bdd100..4578a40 100644 --- a/internal/pkg/service/auth_test.go +++ b/internal/pkg/service/auth_test.go @@ -3,8 +3,10 @@ package service import ( "encoding/json" "io" + "math/rand" "net/http" "net/http/httptest" + "strconv" "strings" "testing" @@ -15,22 +17,6 @@ import ( "github.com/stretchr/testify/require" ) -func unpackOkResponse(recorder *httptest.ResponseRecorder) JsonResponse { - resp := recorder.Result() - body, _ := io.ReadAll(resp.Body) - var actualResp JsonResponse - json.Unmarshal(body, &actualResp) - return actualResp -} - -func unpackErrResponse(recorder *httptest.ResponseRecorder) JsonErrResponse { - resp := recorder.Result() - body, _ := io.ReadAll(resp.Body) - var actualResp JsonErrResponse - json.Unmarshal(body, &actualResp) - return actualResp -} - func checkAuthCookie(cookies []*http.Cookie) bool { if cookies == nil { return false @@ -47,7 +33,7 @@ func TestCheckLogin(t *testing.T) { log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() - db, _ := ramrepo.OpenDB() + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) @@ -82,7 +68,8 @@ func TestCheckLogin(t *testing.T) { service.CheckLogin(w, req) - var actualResp JsonResponse = unpackOkResponse(w) + var actualResp JsonResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) actualResp.Body = actualResp.Body.(map[string]interface{}) require.Equal(t, tCase.expResp, actualResp) }) @@ -139,7 +126,8 @@ func TestCheckLogin(t *testing.T) { service.CheckLogin(w, req) - var actualResp JsonErrResponse = unpackErrResponse(w) + var actualResp JsonErrResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) }) } @@ -151,7 +139,7 @@ func TestLogin(t *testing.T) { log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() - db, _ := ramrepo.OpenDB() + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) @@ -181,7 +169,8 @@ func TestLogin(t *testing.T) { service.Login(w, req) - var actualResp JsonResponse = unpackOkResponse(w) + var actualResp JsonResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) require.True(t, checkAuthCookie(w.Result().Cookies())) }) @@ -273,9 +262,206 @@ func TestLogin(t *testing.T) { service.Login(w, req) - var actualResp JsonErrResponse = unpackErrResponse(w) + var actualResp JsonErrResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) require.False(t, checkAuthCookie(w.Result().Cookies())) }) } } + +func TestSignUp(t *testing.T) { + url := "https://domain.test:8080/api/v1/signup" + log, _ := logger.New(logger.RFC3339FormatTime()) + defer log.Sync() + + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) + defer db.Close() + + sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + userCase := user.New(log, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil) + + goodCases := []struct { + name string + rawBody string + expResp JsonResponse + }{ + { + "providing correct and valid data for signup", + `{"username":"newbie", "password":"getHigh123", "email":"world@uandex.ru"}`, + JsonResponse{ + Status: "ok", + Message: "the user has been successfully registered", + Body: nil, + }, + }, + } + + for _, tCase := range goodCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) + w := httptest.NewRecorder() + + service.Signup(w, req) + + var actualResp JsonResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) + require.Equal(t, tCase.expResp, actualResp) + }) + } + + badCases := []struct { + name string + rawBody string + expResp JsonErrResponse + }{ + { + "user with such data already exists", + `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"}`, + JsonErrResponse{ + Status: "error", + Message: "there is already an account with this username or password", + Code: "uniq_fields", + }, + }, + { + "invalid data - broken body", + `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"`, + JsonErrResponse{ + Status: "error", + Message: "the correct username, email and password are expected to be received in JSON format", + Code: "parse_body", + }, + }, + { + "invalid data - no username", + `{"password":"big_string", "email":"dogslove@gmail.com"}`, + JsonErrResponse{ + Status: "error", + Message: "username", + Code: "invalid_params", + }, + }, + { + "invalid data - no username, password", + `{"email":"dogslove@gmail.com"}`, + JsonErrResponse{ + Status: "error", + Message: "password,username", + Code: "invalid_params", + }, + }, + { + "invalid data - short username", + `{"username":"sh", "password":"big_string", "email":"dogslove@gmail.com"}`, + JsonErrResponse{ + Status: "error", + Message: "username", + Code: "invalid_params", + }, + }, + { + "invalid data - incorrect email", + `{"username":"sh", "password":"big_string", "email":"dog"}`, + JsonErrResponse{ + Status: "error", + Message: "email,username", + Code: "invalid_params", + }, + }, + } + + for _, tCase := range badCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) + w := httptest.NewRecorder() + + service.Signup(w, req) + + var actualResp JsonErrResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) + require.Equal(t, tCase.expResp, actualResp) + }) + } +} + +func TestLogout(t *testing.T) { + url := "https://domain.test:8080/api/v1/logout" + log, _ := logger.New(logger.RFC3339FormatTime()) + defer log.Sync() + + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) + defer db.Close() + + sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + userCase := user.New(log, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil) + + goodCases := []struct { + name string + cookie *http.Cookie + expResp JsonResponse + }{ + { + "user is logged in - providing valid cookie", + &http.Cookie{ + Name: "session_key", + Value: "461afabf38b3147c", + }, + JsonResponse{ + Status: "ok", + Message: "the user has successfully logged out", + Body: nil, + }, + }, + } + + for _, tCase := range goodCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, url, nil) + req.AddCookie(tCase.cookie) + w := httptest.NewRecorder() + + service.Logout(w, req) + + var actualResp JsonResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) + require.Equal(t, tCase.expResp, actualResp) + }) + } + + badCases := []struct { + name string + cookie *http.Cookie + expResp JsonErrResponse + }{ + { + "user isn't logged in - providing invalid cookie", + &http.Cookie{ + Name: "not_auth_cookie", + Value: "blablalba", + }, + JsonErrResponse{ + Status: "error", + Message: "to log out, you must first log in", + Code: "no_auth", + }, + }, + } + + for _, tCase := range badCases { + t.Run(tCase.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, url, nil) + req.AddCookie(tCase.cookie) + w := httptest.NewRecorder() + + service.Logout(w, req) + + var actualResp JsonErrResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) + require.Equal(t, tCase.expResp, actualResp) + }) + } + +} diff --git a/internal/pkg/service/pin_test.go b/internal/pkg/service/pin_test.go index 4297fb9..2dd7998 100644 --- a/internal/pkg/service/pin_test.go +++ b/internal/pkg/service/pin_test.go @@ -4,7 +4,9 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "net/http/httptest" + "strconv" "testing" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" @@ -18,7 +20,7 @@ func TestGetPins(t *testing.T) { log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() - db, _ := ramrepo.OpenDB() + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) defer db.Close() pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) @@ -64,16 +66,11 @@ func TestGetPins(t *testing.T) { w := httptest.NewRecorder() service.GetPins(w, req) - resp := w.Result() - body, _ := io.ReadAll(resp.Body) var actualResp JsonResponse + json.NewDecoder(w.Result().Body).Decode(&actualResp) - json.Unmarshal(body, &actualResp) // после Unmarshall числа приводятся к float64 - fmt.Println(tCase.expResp) - fmt.Println(actualResp) require.Equal(t, tCase.expResp.Status, actualResp.Status) require.Equal(t, tCase.expResp.Message, actualResp.Message) - // fmt.Println(reflect.TypeOf(actualResp.Body.(map[string]interface{})["lastID"])) expLastID := tCase.expResp.Body.(map[string]interface{})["lastID"].(int) actualLastID := actualResp.Body.(map[string]interface{})["lastID"].(float64) From c228379d89bc27f83dcbf624add1d9284934c85a Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 13:13:51 +0300 Subject: [PATCH 060/266] TP-a49 add: log fileserver --- cmd/fileserver/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/fileserver/main.go b/cmd/fileserver/main.go index 96da550..3f41b57 100644 --- a/cmd/fileserver/main.go +++ b/cmd/fileserver/main.go @@ -1,6 +1,7 @@ package main import ( + "log" "net/http" ) @@ -11,5 +12,7 @@ func main() { Addr: ":8081", Handler: http.StripPrefix("/upload/", fs), } - s.ListenAndServe() + log.Println("fileserver start") + defer log.Panicln("fileserver finish") + s.ListenAndServeTLS("/home/ond_team/cert/fullchain.pem", "/home/ond_team/cert/privkey.pem") } From 17f5304c2ba47a84c4d674152cfcfec03a06edd7 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Wed, 4 Oct 2023 13:29:42 +0300 Subject: [PATCH 061/266] minor update in test --- internal/pkg/service/auth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/service/auth_test.go b/internal/pkg/service/auth_test.go index 4578a40..384296e 100644 --- a/internal/pkg/service/auth_test.go +++ b/internal/pkg/service/auth_test.go @@ -55,7 +55,7 @@ func TestCheckLogin(t *testing.T) { JsonResponse{ Status: "ok", Message: "user found", - Body: map[string]interface{}{"username": "dogsLover", "avatar": "https://cdn-icons-png.flaticon.com/512/149/149071.png"}, + Body: map[string]interface{}{"username": "dogsLover", "avatar": "https://pinspire.online:8081/upload/avatars/default-avatar.png"}, }, }, } From 96b1087117e35e8a332c773bb2fdc6569a513adf Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 16:09:55 +0300 Subject: [PATCH 062/266] dev update: passing the config from main and return from main when errors --- cmd/app/config.go | 10 ++-------- cmd/app/main.go | 17 ++++++++++------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cmd/app/config.go b/cmd/app/config.go index 95115f6..ffaf467 100644 --- a/cmd/app/config.go +++ b/cmd/app/config.go @@ -2,12 +2,6 @@ package main import "go.uber.org/config" -var configFiles = []string{"configs/config.yml"} - -func newConfig() (*config.YAML, error) { - cfgOption := make([]config.YAMLOption, 0, len(configFiles)) - for _, filename := range configFiles { - cfgOption = append(cfgOption, config.File(filename)) - } - return config.NewYAML(cfgOption...) +func newConfig(filename string) (*config.YAML, error) { + return config.NewYAML(config.File(filename)) } diff --git a/cmd/app/main.go b/cmd/app/main.go index 12cd406..f6c1ffd 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "os" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" @@ -29,18 +28,20 @@ func main() { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { fmt.Println(err) - os.Exit(1) + return } defer log.Sync() - cfg, err := newConfig() + cfg, err := newConfig("configs/config.yml") if err != nil { - log.Fatal(err.Error()) + log.Error(err.Error()) + return } db, err := ramrepo.OpenDB("RamRepository") if err != nil { - log.Fatal(err.Error()) + log.Error(err.Error()) + return } defer db.Close() @@ -51,11 +52,13 @@ func main() { service := service.New(log, sm, userCase, pinCase) cfgServ, err := server.NewConfig(cfg) if err != nil { - log.Fatal(err.Error()) + log.Error(err.Error()) + return } server := server.New(log, cfgServ) server.InitRouter(service) if err := server.Run(); err != nil { - log.Fatal(err.Error()) + log.Error(err.Error()) + return } } From 04d3c4e37adce8d440eb973d8940e9649fc72f07 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 16:12:21 +0300 Subject: [PATCH 063/266] dev update: moved the const variable higher --- internal/api/server/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/server/config.go b/internal/api/server/config.go index 02fe563..9763638 100644 --- a/internal/api/server/config.go +++ b/internal/api/server/config.go @@ -7,6 +7,8 @@ import ( "go.uber.org/config" ) +const ConfigName = "app.server" + type Config struct { Host string Port string @@ -15,8 +17,6 @@ type Config struct { https bool } -const ConfigName = "app.server" - func NewConfig(cfg *config.YAML) (*Config, error) { value := cfg.Get(ConfigName) c := &Config{ From bfd13d75b3cbbdb734b2f1a23ef2e65b7a540681 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 16:13:44 +0300 Subject: [PATCH 064/266] dev add: wrapping errors in func OpenDB and prealocation --- internal/pkg/repository/ramrepo/pin.go | 2 +- internal/pkg/repository/ramrepo/ramrepo.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index afa7a00..9cfc33f 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -24,7 +24,7 @@ func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count int, after return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) } - pins := []pin.Pin{} + pins := make([]pin.Pin, 0, count) pin := pin.Pin{} for rows.Next() { err := rows.Scan(&pin.ID, &pin.Picture) diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 489bf85..341de69 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -10,37 +10,37 @@ import ( func OpenDB(dataSourceName string) (*sql.DB, error) { db, err := sql.Open("ramsql", dataSourceName) if err != nil { - return nil, err + return nil, fmt.Errorf("sql.Open returned error: %w", err) } err = createUsersTable(db) if err != nil { - return nil, err + return nil, fmt.Errorf("createUsersTable: %w", err) } err = createPinTable(db) if err != nil { - return nil, err + return nil, fmt.Errorf("createPinTable: %w", err) } err = createSessionTable(db) if err != nil { - return nil, err + return nil, fmt.Errorf("createSessionTable: %w", err) } err = fillPinTableRows(db) if err != nil { - return nil, err + return nil, fmt.Errorf("fillPinTableRows: %w", err) } err = fillUsersTableRows(db) if err != nil { - return nil, err + return nil, fmt.Errorf("fillUsersTableRows: %w", err) } err = fillSessionTableRows(db) if err != nil { - return nil, err + return nil, fmt.Errorf("fillSessionTableRows: %w", err) } return db, nil From 10ff286eae766fcabd3aa2982a5092c7bff07363 Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Wed, 4 Oct 2023 17:35:33 +0300 Subject: [PATCH 065/266] add targets for testing --- Makefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Makefile b/Makefile index 189347c..19c7ece 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs +COV_OUT=coverage.out +COV_HTML=coverage.html build: go build -o bin/app cmd/app/*.go @@ -7,6 +9,19 @@ build: run: build ./bin/app +test: + go test ./... -race -covermode=atomic -coverpkg ./... -coverprofile=$(COV_OUT) + +test_with_coverage: test + go tool cover -html $(COV_OUT) -o $(COV_HTML) + +cleantest: + rm coverage* + +retest: + make cleantest + make test + doc: swag fmt swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) From 7482c79acd6d200732adcbce1167f913689a8cdc Mon Sep 17 00:00:00 2001 From: wonderf00l Date: Wed, 4 Oct 2023 17:36:25 +0300 Subject: [PATCH 066/266] added tests for usecases --- internal/usecases/pin/usecase_test.go | 48 ++++++++++++++++++++ internal/usecases/session/manager_test.go | 53 +++++++++++++++++++++++ internal/usecases/user/usecase_test.go | 51 ++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 internal/usecases/pin/usecase_test.go create mode 100644 internal/usecases/session/manager_test.go create mode 100644 internal/usecases/user/usecase_test.go diff --git a/internal/usecases/pin/usecase_test.go b/internal/usecases/pin/usecase_test.go new file mode 100644 index 0000000..fa58f07 --- /dev/null +++ b/internal/usecases/pin/usecase_test.go @@ -0,0 +1,48 @@ +package pin + +import ( + "context" + "math/rand" + "strconv" + "testing" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/stretchr/testify/require" +) + +func TestSelectNewPins(t *testing.T) { + log, _ := logger.New(logger.RFC3339FormatTime()) + defer log.Sync() + + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) + defer db.Close() + + pinCase := New(log, ramrepo.NewRamPinRepo(db)) + + testCases := []struct { + name string + count, lastID int + expNewLastID int + }{ + { + name: "provide correct count and lastID", + count: 2, + lastID: 1, + expNewLastID: 3, + }, + { + name: "provide incorrect count", + count: -2, + lastID: 1, + expNewLastID: 1, + }, + } + + for _, tCase := range testCases { + t.Run(tCase.name, func(t *testing.T) { + _, actualLastID := pinCase.SelectNewPins(context.Background(), tCase.count, tCase.lastID) + require.Equal(t, tCase.expNewLastID, actualLastID) + }) + } +} diff --git a/internal/usecases/session/manager_test.go b/internal/usecases/session/manager_test.go new file mode 100644 index 0000000..89a3633 --- /dev/null +++ b/internal/usecases/session/manager_test.go @@ -0,0 +1,53 @@ +package session + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "testing" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/stretchr/testify/require" +) + +func TestGetUserIDBySessionKey(t *testing.T) { + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + fmt.Println(err) + return + } + defer log.Sync() + + db, err := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) + if err != nil { + log.Error(err.Error()) + return + } + defer db.Close() + + sm := New(log, ramrepo.NewRamSessionRepo(db)) + + testCases := []struct { + name string + session_key string + expUserId int + expErr error + }{ + { + "providing valid session key", + "461afabf38b3147c", + 1, + nil, + }, + } + + for _, tCase := range testCases { + t.Run(tCase.name, func(t *testing.T) { + id, err := sm.GetUserIDBySessionKey(context.Background(), tCase.session_key) + require.Equal(t, tCase.expErr, err) + require.Equal(t, tCase.expUserId, id) + }) + } +} diff --git a/internal/usecases/user/usecase_test.go b/internal/usecases/user/usecase_test.go new file mode 100644 index 0000000..9fabfad --- /dev/null +++ b/internal/usecases/user/usecase_test.go @@ -0,0 +1,51 @@ +package user + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "testing" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/stretchr/testify/require" +) + +func TestRegister(t *testing.T) { + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + fmt.Println(err) + return + } + defer log.Sync() + + db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) + defer db.Close() + + userCase := New(log, ramrepo.NewRamUserRepo(db)) + + testCases := []struct { + name string + user *entity.User + expErr error + }{ + { + "providing valid user", + &entity.User{ + Username: "valid_user", + Password: "helloworld", + Email: "gggg@yandex.ru", + }, + nil, + }, + } + + for _, tCase := range testCases { + t.Run(tCase.name, func(t *testing.T) { + err := userCase.Register(context.Background(), tCase.user) + require.Equal(t, tCase.expErr, err) + }) + } +} From 6dcc34e5f180e89512d4f1efb777df16dfa6f698 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Wed, 4 Oct 2023 17:53:51 +0300 Subject: [PATCH 067/266] TP-565 add: error test --- internal/pkg/service/validation_test.go | 70 +++++++++++-------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/internal/pkg/service/validation_test.go b/internal/pkg/service/validation_test.go index c675316..f2b094c 100644 --- a/internal/pkg/service/validation_test.go +++ b/internal/pkg/service/validation_test.go @@ -9,7 +9,6 @@ import ( func TestFetchValidParams(t *testing.T) { rawUrl := "https://domain.test:8080/api/v1/pin" - // testCases := make([]TestCaseFetch, 0) tests := []struct { name string @@ -24,45 +23,6 @@ func TestFetchValidParams(t *testing.T) { {"the parameter lastID is registered but not specified", "?lastID=&count=17", 17, 0}, } - // testCases = append(testCases, []TestCaseFetch{ - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 1), - // expCount: 0, - // expLastID: 0, - // expErr: ErrBadParams, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 12312, 1), - // expCount: 0, - // expLastID: 0, - // expErr: ErrBadParams, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 1), - // expCount: 0, - // expLastID: 0, - // expErr: ErrBadParams, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, -1), - // expCount: 0, - // expLastID: 0, - // expErr: ErrBadParams, - // }, - // { - // rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 1), - // expCount: 0, - // expLastID: 0, - // expErr: ErrCountParameterMissing, - // }, - // { - // rawURL: fmt.Sprintf("%s?&lastID=%d", rawUrl, 4), - // expCount: 0, - // expLastID: 0, - // expErr: ErrCountParameterMissing, - // }, - // }...) - for _, test := range tests { t.Run(test.name, func(t *testing.T) { URL, err := url.Parse(rawUrl + test.queryRow) @@ -76,3 +36,33 @@ func TestFetchValidParams(t *testing.T) { }) } } + +func TestErrorFetchValidParams(t *testing.T) { + rawUrl := "https://domain.test:8080/api/v1/pin" + + tests := []struct { + name string + queryRow string + wantErr error + }{ + {"empty query row", "", ErrCountParameterMissing}, + {"count equal zero", "?count=0", ErrBadParams}, + {"negative count", "?count=-5&lastID=12", ErrBadParams}, + {"negative lastID", "?count=5&lastID=-6", ErrBadParams}, + {"requested count is more than a thousand", "?count=1001", ErrBadParams}, + {"count param empty", "?count=&lastID=6", ErrCountParameterMissing}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + URL, err := url.Parse(rawUrl + test.queryRow) + if err != nil { + t.Fatalf("error when parsing into the url.URL structure: %v", err) + } + actualCount, actualLastID, err := FetchValidParamForLoadTape(URL) + require.ErrorIs(t, err, test.wantErr) + require.Equal(t, 0, actualCount) + require.Equal(t, 0, actualLastID) + }) + } +} From 1250abb38d94dc230d7975bce96f2c033c1f4d48 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 19 Oct 2023 00:21:42 +0300 Subject: [PATCH 068/266] dev update: configs, Makefile and handlers auth --- Makefile | 1 + cmd/app/config.go | 7 ------- cmd/app/main.go | 10 +++------- cmd/fileserver/main.go | 2 +- internal/api/server/config.go | 12 +++++++++--- internal/api/server/router/router.go | 8 +++++--- internal/pkg/service/auth.go | 4 ++-- 7 files changed, 21 insertions(+), 23 deletions(-) delete mode 100644 cmd/app/config.go diff --git a/Makefile b/Makefile index 19c7ece..212a845 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +.PHONY: build run test test_with_coverage cleantest retest doc ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out diff --git a/cmd/app/config.go b/cmd/app/config.go deleted file mode 100644 index ffaf467..0000000 --- a/cmd/app/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "go.uber.org/config" - -func newConfig(filename string) (*config.YAML, error) { - return config.NewYAML(config.File(filename)) -} diff --git a/cmd/app/main.go b/cmd/app/main.go index f6c1ffd..bcdb514 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -12,6 +12,8 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +const configFile = "configs/config.yml" + // @title Pinspire API // @version 1.0 // @description API for Pinspire project @@ -32,12 +34,6 @@ func main() { } defer log.Sync() - cfg, err := newConfig("configs/config.yml") - if err != nil { - log.Error(err.Error()) - return - } - db, err := ramrepo.OpenDB("RamRepository") if err != nil { log.Error(err.Error()) @@ -50,7 +46,7 @@ func main() { pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) service := service.New(log, sm, userCase, pinCase) - cfgServ, err := server.NewConfig(cfg) + cfgServ, err := server.NewConfig(configFile) if err != nil { log.Error(err.Error()) return diff --git a/cmd/fileserver/main.go b/cmd/fileserver/main.go index 3f41b57..38132ee 100644 --- a/cmd/fileserver/main.go +++ b/cmd/fileserver/main.go @@ -13,6 +13,6 @@ func main() { Handler: http.StripPrefix("/upload/", fs), } log.Println("fileserver start") - defer log.Panicln("fileserver finish") + defer log.Println("fileserver finish") s.ListenAndServeTLS("/home/ond_team/cert/fullchain.pem", "/home/ond_team/cert/privkey.pem") } diff --git a/internal/api/server/config.go b/internal/api/server/config.go index 9763638..b2e4db5 100644 --- a/internal/api/server/config.go +++ b/internal/api/server/config.go @@ -17,7 +17,12 @@ type Config struct { https bool } -func NewConfig(cfg *config.YAML) (*Config, error) { +func NewConfig(filename string) (*Config, error) { + cfg, err := config.NewYAML(config.File(filename)) + if err != nil { + return nil, fmt.Errorf("new YAML provider: %w", err) + } + value := cfg.Get(ConfigName) c := &Config{ Host: value.Get("host").String(), @@ -25,10 +30,11 @@ func NewConfig(cfg *config.YAML) (*Config, error) { CertFile: value.Get("certFile").String(), KeyFile: value.Get("keyFile").String(), } - b, err := strconv.ParseBool(value.Get("https").String()) + + https, err := strconv.ParseBool(value.Get("https").String()) if err != nil { return nil, fmt.Errorf("parse param https in server.Config: %w", err) } - c.https = b + c.https = https return c, nil } diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index fab4a0c..6191ffc 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -4,11 +4,11 @@ import ( "net/http" "github.com/go-chi/chi/v5" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" - - _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" "github.com/rs/cors" httpSwagger "github.com/swaggo/http-swagger" + + _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" ) type Router struct { @@ -26,7 +26,9 @@ func (r Router) InitRoute(serv *service.Service) { AllowCredentials: true, AllowedHeaders: []string{"content-type"}, }) + r.Mux.Use(c.Handler) + r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go index e146083..3188b14 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/service/auth.go @@ -74,9 +74,9 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) - defer r.Body.Close() params := usecase.NewCredentials() err := json.NewDecoder(r.Body).Decode(¶ms) + defer r.Body.Close() if err != nil { s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username and password are expected to be received in JSON format") @@ -150,9 +150,9 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) - defer r.Body.Close() user := &user.User{} err := json.NewDecoder(r.Body).Decode(user) + defer r.Body.Close() if err != nil { s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username, email and password are expected to be received in JSON format") From 48c9f51e89720c912d5c1c4a02e462011062fceb Mon Sep 17 00:00:00 2001 From: Gvidow Date: Thu, 19 Oct 2023 00:59:01 +0300 Subject: [PATCH 069/266] TP-aad add: er-diagram.png and docker-compose file --- db/normalized/er-diagram.png | Bin 0 -> 218733 bytes deployments/docker-compose.yml | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 db/normalized/er-diagram.png create mode 100644 deployments/docker-compose.yml diff --git a/db/normalized/er-diagram.png b/db/normalized/er-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..f96dbfbd7652cf1aa2c34d4ba81dee95ec89e9b9 GIT binary patch literal 218733 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%0AC1hklcnlKv+^3XXm`-KIwoVmr=b=@uP8^x75v! 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 J%Q~loCIFvLcRK(8 literal 0 HcmV?d00001 diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml new file mode 100644 index 0000000..0ad67f6 --- /dev/null +++ b/deployments/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.8' + +services: + postgres: + image: postgres:latest + container_name: pinspirePG + environment: + POSTGRES_USER: ond_team + POSTGRES_PASSWORD: love + POSTGRES_DB: pinspire + ports: + - 5432:5432 \ No newline at end of file From d11590ed3931bbb3edb91832b7409ef4a47d37fb Mon Sep 17 00:00:00 2001 From: Gvidow Date: Fri, 20 Oct 2023 00:55:17 +0300 Subject: [PATCH 070/266] TP-aad add: migrations --- db/migrations/indexes.sql | 14 +++ db/migrations/relations.sql | 165 ++++++++++++++++++++++++++++++++++++ db/migrations/triggers.sql | 37 ++++++++ 3 files changed, 216 insertions(+) create mode 100644 db/migrations/indexes.sql create mode 100644 db/migrations/relations.sql create mode 100644 db/migrations/triggers.sql diff --git a/db/migrations/indexes.sql b/db/migrations/indexes.sql new file mode 100644 index 0000000..df1c8e6 --- /dev/null +++ b/db/migrations/indexes.sql @@ -0,0 +1,14 @@ +CREATE INDEX IF NOT EXISTS users_profile_index +ON users USING btree (profile_id); + +CREATE INDEX IF NOT EXISTS pin_author_index +ON pin USING btree (author); + +CREATE INDEX IF NOT EXISTS board_author_index +ON board USING btree (author); + +CREATE INDEX IF NOT EXISTS comment_pin_author_index +ON comment USING btree (pin_id, author); + +CREATE INDEX IF NOT EXISTS message_from_to_index +ON message USING btree (user_from, user_to); diff --git a/db/migrations/relations.sql b/db/migrations/relations.sql new file mode 100644 index 0000000..f2410a4 --- /dev/null +++ b/db/migrations/relations.sql @@ -0,0 +1,165 @@ +CREATE SCHEMA IF NOT EXISTS pinspire; + +SET search_path TO pinspire; + +CREATE TABLE IF NOT EXISTS profile ( + id serial PRIMARY KEY, + email TEXT NOT NULL, + avatar TEXT NOT NULL DEFAULT 'default-avatar.png', + name text, + surname TEXT, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz, + CONSTRAINT profile_email_uniq UNIQUE (email) +); + +ALTER TABLE profile ALTER COLUMN avatar SET DEFAULT 'avatar.jpg'; + +CREATE TABLE IF NOT EXISTS users ( + id serial PRIMARY KEY, + username TEXT NOT NULL, + PASSWORD TEXT NOT NULL, + profile_id int NOT NULL, + CONSTRAINT users_username_uniq UNIQUE (username), + CONSTRAINT users_profile_id_uniq UNIQUE (profile_id), + FOREIGN KEY (profile_id) REFERENCES profile (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS tag ( + id serial PRIMARY KEY, + title TEXT NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT tag_title_uniq UNIQUE (title) +); + +CREATE TABLE IF NOT EXISTS pin ( + id serial PRIMARY KEY, + author int NOT NULL, + title TEXT, + description TEXT, + picture TEXT NOT NULL, + public bool NOT NULL DEFAULT TRUE, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz, + FOREIGN KEY (author) REFERENCES profile (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS pin_tag ( + pin_id int NOT NULL, + tag_id int NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (pin_id, tag_id), + FOREIGN KEY (pin_id) REFERENCES pin (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES tag (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS like_pin ( + user_id int NOT NULL, + pin_id int NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (pin_id, user_id), + FOREIGN KEY (user_id) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (pin_id) REFERENCES pin (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS board ( + id serial PRIMARY KEY, + author int NOT NULL, + title TEXT, + description TEXT, + picture TEXT NOT NULL, + public bool NOT NULL DEFAULT TRUE, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz, + FOREIGN KEY (author) REFERENCES profile (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS board_tag ( + board_id int NOT NULL, + tag_id int NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (board_id, tag_id), + FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES tag (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS subscription_board ( + user_id int NOT NULL, + board_id int NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (board_id, user_id), + FOREIGN KEY (user_id) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS membership ( + pin_id int NOT NULL, + board_id int NOT NULL, + added_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (board_id, pin_id), + FOREIGN KEY (pin_id) REFERENCES pin (id) ON DELETE CASCADE, + FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS role ( + id serial PRIMARY KEY, + name text NOT NULL, + CONSTRAINT role_name_uniq UNIQUE (name) +); + +CREATE TABLE IF NOT EXISTS contributor ( + board_id int NOT NULL, + user_id int NOT NULL, + role_id int NOT NULL, + added_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (user_id, board_id), + FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS subscription_user ( + who int NOT NULL, + whom int NOT NULL, + create_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (whom, who), + FOREIGN KEY (who) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (whom) REFERENCES profile (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS comment ( + id serial PRIMARY KEY, + author int NOT NULL, + pin_id int NOT NULL, + content TEXT, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz, + FOREIGN KEY (author) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (pin_id) REFERENCES pin (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS like_comment ( + user_id int NOT NULL, + comment_id int NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (comment_id, user_id), + FOREIGN KEY (user_id) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (comment_id) REFERENCES comment (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS message ( + id serial PRIMARY KEY, + user_from int NOT NULL, + user_to int NOT NULL, + content text, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + deleted_at timestamptz, + FOREIGN KEY (user_from) REFERENCES profile (id) ON DELETE CASCADE, + FOREIGN KEY (user_to) REFERENCES profile (id) ON DELETE CASCADE +); diff --git a/db/migrations/triggers.sql b/db/migrations/triggers.sql new file mode 100644 index 0000000..a4b4956 --- /dev/null +++ b/db/migrations/triggers.sql @@ -0,0 +1,37 @@ +CREATE EXTENSION IF NOT EXISTS moddatetime; + +CREATE OR REPLACE TRIGGER modify_profile_updated_at + BEFORE UPDATE + ON profile + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + +CREATE OR REPLACE TRIGGER modify_pin_updated_at + BEFORE UPDATE + ON pin + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + +CREATE OR REPLACE TRIGGER modify_board_updated_at + BEFORE UPDATE + ON board + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + +CREATE OR REPLACE TRIGGER modify_contributor_updated_at + BEFORE UPDATE + ON contributor + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + +CREATE OR REPLACE TRIGGER modify_comment_updated_at + BEFORE UPDATE + ON comment + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + +CREATE OR REPLACE TRIGGER modify_message_updated_at + BEFORE UPDATE + ON message + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); From d05df615bcf527becb3aa2aa7d10dbc87a9f48b9 Mon Sep 17 00:00:00 2001 From: Gvidow Date: Fri, 20 Oct 2023 01:09:12 +0300 Subject: [PATCH 071/266] TP-aad update: er-diagram.png --- db/normalized/er-diagram.png | Bin 218733 -> 268784 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/db/normalized/er-diagram.png b/db/normalized/er-diagram.png index f96dbfbd7652cf1aa2c34d4ba81dee95ec89e9b9..7de47b9008683de8b6018a29fe77033367059427 100644 GIT binary patch literal 268784 zcmeFZc|4Tw+dmv3S)v6+mhy=rd?bV{EhuHlGGZ)cjWMIL%-E^4D6~nkh3v)%GxjZo ztXak|!x+hKjBH~Yo^z_tUC-vlPM)%0`~5n)Qzz9p5gVG8GQdZViWBsvef;ae#_j%JR;Tm9i6Pv zb#5oib5G6qi=q4yp3hZjYSJ}DuecA5&)n9OKI7#|6UC#UL;G$W3f;!aDe=F4LD@+} zgc25%9YX)jA4-IV?vgsG`VdMW_zXTrEshxgtR`K$``8$4{(9l@qzr7v*+@ozQ z7-OzafA9WZAIp3xRy*>)dpmxeT4`3cO08r2>d*h%XLF+bcKq*yW&SpqlYegQoe9t0 zfBS4;K+%6Y2)6$i@PD+?e+>99*Vunn;QzQw|Febvm6!XE5B!(==tso*PZ0T6?)N`Y z_+Ra#AJ_hbF@c8)96p^*fA`nALmtl_mKyGlA3tVUV^KB6cfU*icO(|JPlCehWEHpX zCr+ya5R2E&Fzy+WofB0B716u@-vJ!|-2ARzm@ZU-jT04gMT+A9KMN~4bl-Eg{|5GS zB*2}vTS274f80rD4*bVmOEZ^mwg4>Adhb8;Gm}ufh4G!~n`*05%z3!eJj>*WLr}Nk zpTLm~8XBtFx8&^m6C<+efscQuI~~^Im!K@2dA2__lkGUgwk9||v*F{l!?GstE-1z* z7}tvQ%Y4!J<34o)U~~w0+<~9h!uEiV=4x?I8o?`S+DY6WR|D_V5%bsA?T0CmsGa-& z9DA4`_+xLsxP!k8wHtgil8bvpXBx_0Qg1>I{I4gzo;&RjW$CQI{_X#Ndu|gt6#4z@ zCt?K>^F|pa2&oKQou3>}4uP1OK3`j==DvLS^7vA$88sni0fM%k%*9l9WnD@09*I;< znp!Rhs)UnpYa0t4l3Gy(hF>Ik_s;ph)3EJ3+&5Ofzftu}3XsaBI+fRN4PK}f z$$4Evlcp>o8T*4~x%ubDs>=uge^HkF!}p8-F)y27PMLqB`SY5eo`mXl@?ZrrSNE}C zQW2b3oL$XWhH5T;Ifqb&BkZzSXb*QwdM~u8j=K;V7x5W}B0=9j3FT3Ey}%-<=efKP z#@s$O7mG9xv64OZcKx}2I1|o;ZA#ewYk+}=p7w_l?qsjeRv0tZ>6Y21>BVl{QbDVu zvHt7S5}z#wy@DDp9<+F+qzz-MWJ~_@Anpr;6o6l3x0*j8E&zE-Yw@0m| zfos7Si4pxD*Ce7jom41Ti7$7)98hv8y;fvbJZrM`qs4NrzCX<3`C6N{^Z#nqpKa#T z-uS{EgPZS#1QHFdR-E05g5o@~MKyd&O?n_R)f@9Ih)+F>lie}TpQj>+f-IARmaVKM z+!;aff)IiR!@3yGa4FYN~qNZ>5csX6`s}`%e=vG#nCF+w;LJz6IDDQ`D}0Db_x? z3~CYI86#XPZ(ZEPUgB#N3Wx97|F_Pk2mvqIS8|orVA%~cg5~a*c&p(+`7#l<)F>Wu zu-gO7-+G3t1u+`>Kpd2l67uMFR3Gt3Mf(Xwa`OAy6M zv29bWoTwt1Mz25lkEaJfJZ(LCi|?1WI&UKZRvvE;YkNGR{Nj;Iue?coB(5vm`<1v* z<8ASoeSLk7xEth}kQ!lC#LSpS?hU5Ng4QN85#jmlDLCF+V9^ZZm~%gq3&dV4wkf;q zK3I;0rUxv>2+w8Qf0-@ba+HEaKbAY%o<(bAt5m33d`{Zz$IWky#@gFx)IZ>}q!O{V zZ()wbq4WufUe<43?P~f28?Q(x{)eri#59R;UAU#YeaWD2e1xFfRKoEdrjHqHq*`)E zZhemqnb6)^jplBatHdhy$=C)oKqQbfvZfpbt8OKc;BWZ)?=zzxsW#W zw<8o@TIOFYo~R%hc`@Fg9y(~kp?aGxa zv$Tp9`HI#cTP%EmI$ahN^ev)t8;j>RSS{xt2^wc1xb`%+W#H#E&Zjy@SulpAc0Bgx z_plveU%JvS4f@vN@R*>xr+s{K;Y%>rN@850esUdb7!t1nA5bgLmUWP8VvK8VO`cCn zO9O0|D#7T{o+ATsMZjl2Bkfw75UBJ`PHc-8o+Cn97REb_bDJMQ=7j(A<58Wd6kAnM1G39sY5wiI=jc`6Vcr1K;_Dx&KP;h`vZM;?~mYM+{ z$o6${ysiNZsa*zk?DVDSy1h*Xwei81{MY^6$BRk5tIOw3*_M0X8m}S^vEv-iu7wh+ zmx;bxIM_Wn($hYG`XCyF_-S8#N<JvKdyy^{&1aM*EM{m zzO{dmGky2aBi@nQLQbMM292BQsrK6h@q+|c_89g6&w-4|TNQqU@AQyG!NW1=n^=K& zf<{EO%AGrR7XFPzegB#tS;L@u%In~iQ1DNA=nQL|M;v$fIbIZX!;1SXywZ0Q6B$%@ zhrN6%CwHK%xHli|fW6g+(ce+&%aM;Bz+UVB(7CdLDv2yazr_})`;1^UkfF%26@}Ov?BfAEJ z*Qv8w&_S0oK_FxSb8h$<){3}Xd&8HY17RgELc5?BU={j(gogei*9z(y2n{eg@)>p8 zGcAf?)zD^sLCubt9nmun!e+$R>m>4TQ`0!&xblL+wC+K z@HmdB$B(@ZjSrN04RT47BKptDt|PfGQysOpHj21!z#mi6Ea!--ZE_MgbmHEQz(bl7obnB;Y6zsK-b{O`$%tYTe8u9e(%%m;=ZO(F`;FsLE zZ%{>+1v7}MlQkRjmn?Om?69!+;l=CARIi1DDjt1`ESN!8@shs@A8etAT&VprwlIYX z77P|fGJ)#A##gj9=IY<5`{alu!;iVwcmfEpUWJwk0KN$8OSMLF6rfQ!0XG+SFZVBR z!?Xay$CSE`IL_zGBd@Pn4-1VE#+QG5Q6*XYuMH=Mnf8H^*K@@$6z+!VPO zw6>h~V;;4?I6!Hn@7sjzP$@30ub<)W5P~1N)P}M&vR9tF&vVNa2c3gyo4@q$Z|7Rx ze#+(<$SorCw5$HevG~1#6Mt*@di5s_&d(oKxo3_jLvm?*qnqcBJKW!1skv4t(2S{^ zH~83azOhUJ0N(Yn#R<=_1Wptjxs#M$=c~D2)4y09IXlx%!44jpXA52X?1_=_wxGU)Q1lSv;>I@iYJc zo-$WM<2wGPN1=qjju%%k0WfVzIf2LD9!@epjudn?__%H)Ko48f_29aEYp3* zDibwB79+^lIM+P|*(x`44@m0$fuoF>I4VcbQTHc~;^Y1n9;(V08=D<)IW&~u$bS7) zk`U(+%F>3@{%g!v8RL|U{4;5+-3byzKjmNEx>g9>uKMWSyF3rUp3vd?eDby-h?<0~ zpzhr)7*B&wI)Ch&xpctOE3`Dc{duj4IYK|oSnH|V;D={LnXVyVudNj!h7T&Uc%Jfc z6ZykR%!mHNwEyR!|6exmD)&AUb8~XMs*;|>3D#r8*@aO1kz2>?yQ z+{AuQZrEvgu(2BLw`D~C$jCzN!E7^3k2M_N0UPvM&oirUufXa;TwKo-XSLhviFkJS zH&H^-2YJ0VBvSF`#(ug3q{FaaOKWTanwudIEvogB6ugTJ@)#YyCBU`NLt zmcvh>tULImj)iQpmf=MiE9WKD)ILc|I=}uCB~nzuyVY1c`Tp~LpXxK;?Z$AN?>)9C zUew6QbPkdx_C_-xf2t=tTlev+sp4*fTnJhi0A|a>C-us8Yc*IqJ3AxV#HSNk6rVrj z{!3bVTBj@QVf0;=Klh~0-{8h~tt?%BaX1uUzwMT$Qs3Od9JiC7AGwrelt`5Cq2hBZ zR1gDs7hg!h)|V4O!hp+k{XpSw;ss_NFj=+~2ks(ADD9_f_~9RFJ%JH|bN7$@au?BH z6303}C{a0gI#ud=C2Lig7vH#|>~YoDvxeb`wy!yKb|ek@e`)3KES^hK7Cw!lB@-av zndL&{dJ3JrH0c8^1{W_bZ+PsNU>#<%JS7ENMC~iJFpWbtAq+gR;Q9-#%@w6B!za7{ zaPFa@(!dIuS2#Grex>=DfKJkk;TPAJkY2$>;MCA$P{w60t({S*WVV+Q}!si36QErs?e+T^|0prXv9! z$`|_{OrmuOB5luJI(usV7?T5GY?2Fw*?PiCUTB}jXm2H?`Y(MI z5E9a_ovtcWJp4yhG*A+()_7Azlb_e-aiK%3WP=zPcM31TeL10QZJBiFfFSLY5C?3i z`n={^-`f|0Ah$g`)t~klWRKarA}U!9pCUktT2{R>U&B_ld!zV9Z(d4stU~Vhkc<$&u@AIgtW_o$deCt*-cnHJ}z;Fhn#P#v3t%%;MMpaz+9(xCkRf5f3u4lf^)Bq zdA*UZo(OD9t}&_EQdEJm&;b5V`53gm$as2P{tMQkGE!K@9AvhOy(HRdYVetZPJx5g zfZHg}Fib{&{K$xV;Iy(>3Wo({T+3-r$LvD4p?h zqxi*c zKZo^XWk5HB)5hANZ$Uc$ZSItMF~Q0;F?(Zm0ruhJMfEhs`Dg*hhiU+Z(CxK$<7LTX~6mtH9tm{ zaKrKg0{KhKUkBX z?}NA#FT50X}AYQBBV88E@ke47bhu@O;GrkO^rM_0cbVko*iH#IdiT01}4 zbp${BL}hk*J~^blsprzA+W2Eqh(8wGwy@)1#kuT`YWy|t5=i#jCn!}kvW9tepq~{* zRyW%ko3l2TW-K6ZMDym>pbR9hG}{>jldjO6}e2mFs`}Gjgm9hL`}TKZD1`t zH>VSM4t4FQRL-&NmMa;^diQSon-k7Cz#yqP7P86nJmgCpHd%MVxzfWw>LXpCs>X3A z3<1y7G3f9mes2@t2WsiHVcV^XT&j#@&4?gN3iVk|V3*H=cfAW+U)f;4G{DDym114x zFQDvZV-GDs5Qsh^=0z^b0&=ET!Pe~z$BA}$>H~bYc^Gk1m&;R2Fy{;d}?TLAJ1niQ7(?8_7H}ZP%mI7EpHF@ z8sv9PG)b=S*;nL{kt$>i>3N0EL_}!tH*II~1!`sCTWXAT8TtIw`_cQ;!RjxU+vw2D z#OOU)u`tUpYS}(-mFzGLF^ow__=@Y-#_QGw$Q;s&`t5$Vv6caU8c+JV_~6$?t36Gh zL+LoE=k1i)CvioK>l5#-OEzy(ENX%^ZIRR%1fJ@e95lQ+vrIx8UacZ zEivfZ+!%}Zm>v^4VRI(oh?5xCMap>=+LJgh+lTIFJQ|fqWfb3yd&eRkJ%S_`Rg}TY zKtk0&`#zxYN#eVE_XcxJsuMuUoT~dcwk#lC#qDjGmyr8v1U^T|9_lmQ+w(G8X^CJD z#aCDg2~nc@kTdPc7(Gh-%Us)zt~T0xgeJe>a5-BHOf6To6!mQ{kTkcPGsY=Xk= z{8xSGbe;P9yxyNfxd-@;+n%7L7NZ$T-Qx5F8wnd0+5<^x@m2rw>&Kk`fKI>}pJ2{| zn+)QYW+vT{_}&K^kc-XMK{OE0h#F zd=g$U-c7>ZX7O~t5O5-Tk%cxSFZxzc?#;Rd+<||E2c?DDWXcgg`ilKeOQ+(7Z$sVSRItv5EUQzA+$uF9wl?N zfO1R83hR#UMGs9txJVcr-fOzI%{+g`O_;IHR+65DP3~0oA$kBP(+Q0G9NA}FbN^^e z>b(|wp6>?`9f~a60Z0$i#peEqIJMJ_Z_-odcHY}FH`XJ#xs1F6CwaQQC;6PBRGD~I zG4i3GV7dOWa?st?7snl1mhZ~o&Yc-8<(j1#t`7^X3zs=fMwlVILQ>IFe6*W4FKBPg z$H!lck%{T@H96&d)C%m-?in?1Y{*t{xDrLPZ``+EaOHiyU5DA6E`vP{s3*KS_t%Kz% z0BX}d@Opum-(q$#z6?3!6ED&{;I}Y{Yd6cd>^h3LH^kV>UYYGWpTUQc+crhYj9;c1 zcg^jt$&A$UVW;(E7uno+=6_^%NI@gBM0cSf$EGYbAXYBVw&2<$Xd7(YkUrJlk7i9i z;oPez?8||v>X_XAr1gb!(x)W)Dl+P(m%+X>H8gneZn!k5YgZG!)reBKj-a?QjjFT0m8@H2t z{Et}X$+40X){egu{R6qpGcy))j%v|Q;9Oh68pn%*-{I4`84XR>Kn$=^i7{`JG%L!A zE}L1-*&zq<052v&o%?ZyvY-^ypLna{X6K$kBrQ?Ks_=c#&CV_U#1pzMNYhlu&Y%Dth$;o2nU+B>`E8lZH!lRaFW{rAi?9 z%R5%wM`o(tC?QjFEF#UiGkCF%jq+&Q3cD+vowGCKPnTX*(9>L_jS35pjmwF|63^u2 z*-PHJUVH~ny%yZGrd+4Ns`~?&Vp7-|G&IQg>LY1~y!HwY4q@WgzZGK`*}^{2dc zHrrR_^d=SOI(FJVTA*FJUd=o+@;Njq=+sQiOB&sHUzK-+v0gI$eq3Uxsw{M+1c5ho zP3qUGm)n3_&}@-|z8|PtjH`)-&c@x7RBIVU#Ve166p-UFq$y~H5w4Aqs@q~zYQgwi ztwl6CFj(Q^O&TLb?XAVp7fD4TUMKz7wV2kU+!8RPkbRbXBWJh9)W@CvvYkT%nnun|G>l8Sl}!< zTWFsBe5yOMWF57+@m^j+OvyK)Au91HI+P&OHJPV0C_`ozVM{tgj#eY!7Wp=-_heDA zavFg+H?+0#(!4AN##rO6lg*2EeHjx)D!mAxY(~%Ao5jRPJ$V_2!?{1> zIIwgCaZ!y4Sx72v7I{}Y9pCxRUVe(+hZDDX7>!9UH4w0g$GIaH?4bj`?Y_=xlt_S& zw2;TrQT@fiX^;-H#reqyE454NufL5nufirCSTDX}I9hH-(_)|RVJL^4K0%yet4u)* zCtoRrrQ|c_^r_Z({8mD>@Cm2aV{P%`M}y*_RHE|0P}{|eJ5D+_97N()KI6)gl>;_+ zmgH!!O{c+GY%6@1iZSWSSY`X_{Zw+1Wer0`zJ-NGX1U91arcFxl#Jq;wIQ{yogwHe ziyyxGxy)YP_Xibl0Z>kQXA`x4(MA$Z%n#Sx$ZHg`FmAprRmk*z)dgUFtMoiYxBR0#GslJ|OJrQvcc50V8Jw7Fjw8p{b zOs0d?dx|R@#-gvZ-t#=MzMnpts^iN_Ho?u6)QIGlt#MT?&QBxagSma-M=*&k2*Q0{ z5mv0S*V-N;p>_(Nn{$St?v@KP#wH25Vfy_W| zGnw97u5)W^h1AiX%KAXV&lG+e zYa=I0nS!+_17#gGmpl><-n|t--!Ah|2ghFSt5*4y#QI?S#T^!@=N0kUW@Z&gT4&#_ z7e#}ck3=qXl7-;{RqnXdQNOu*?U+>ZJmY(IYV1L~)wWU5Fy?G~ z413a-XF3hH{HJY&W_@X$x>y+AfIF~C@_CVD_Cd=lqs=j$QHd1;eC0ilG3cH|9XBX*`|1a7@&UqVQkL3bMQ6U^z&+} z^a;GzOV5uLYU^je2>-DcIUk0`#{QGg1sBT|3!^&ua))rEcJP#%m}OqJ%FwRQ$R-n{Hg_zwQ^qnMyaS(C+HN*UFy&WCpHdjchGRrl0>wEcl|6n_w z3NfiA2Yj5;PqS(P+0fGU&i#ea%w+|s{8ow*oe6rJx@n!>cSykxj`9mWU3Oq@%+Div zo0Ak}>9cvEa_ikejOA^tRI}=;hHl4!L^KYy|M$*hVf#f)|D@i2D~t96jtdiWBK_(C z(fxC;i(ND4L%wGb?#GTdo~w-?w+|cH1{jeSf5oEn?~V_kv(PYs^}&V8qkr;bPtO6C zNsaBI(yxpBc7TwYTkBG1Z3H&-{~mdvd{bF9Z-X{C`)PP|QgQd(*yOzHDL+=Suc4L;v0z0b;TWQUrNU_%-?ai638F>W^If{_S!68?FO z%@%x|5O4`51EOjPqPZ*jekkFw1&}KtEDmd&0E69pKwRujks6HgY&OyD5EkhF@c)ue zu`mnQ7Y@a7{2cBXA@I>ZJTAEea8gwkd|<@ABNK}6b&CkHFV{yTDVj1AcqbG@0Z8j& z@}RcOAFn3yGzd(=j7S&jFVlUy157C|(ap~4#vE$kfva`XpK=b0?Pb9rVSBTm?Vb~R zR&p~oRMph;Fyl8p%Vq(_lojJ__iKot#-MtQ-C11>yxWpP7sq+?`4r`WGH7{Y@V42 z0sJ8T%R)!Z9jH85)MId32gJWdbE}MzqWzI zbFc)S^<;Yf!n;kyU~Ob%m+lTb$bU+OB4<^24-neavx9b3<*<>4wl^o;3W3Uq$$E_I zgq(c#Q#+wMCI*aN8c=2lRyH22^7;7YzTZUJh76!S3C)U#U)1Mo@C-58Q{9mTIt|d1 z^vUi^Km=w{e}8FlnY7irwj^@+bbBRU6D-GRL$Cvh=iR{$1h45&|6>D7crpzWsjlz+ zDq#7q^Ti?R`VtpdbtCq1cX_|&W<3N9asT6|Hclrq%1UU7|58r*NECFj$E+jUB3ID8 zzpxBQRGfi!)3r!JtbPk`Cwkw=A``xJ0=eoAkV5iKpec>N5cwpO zxshpNe{AG$%o;)EmGOt|&qGyHH@w9pz~^lOFS4C3f(S-m-3qW?r%O+EHgY&;V-ox) zXsSbRL4jdn)Y4A-dLJ?0R%A)7BDQ)x_+f@|vX2dcx%k>t2j7-02u{@VX{$|sk+I0h zViH_{TXX}pY@lIE)q8R<{k@~~x1N_b#*c=%vW!%ypk1t@wW4Gj1Q#iS>Q`h|E9{B(}VwG)Y?)Whm{Bd10^kpQ6-$`cG#Oti$ zX*hG);5*QJ=ESnaSTM;$P7S}kBgFzZFbqUWXXG%2*F-VS;5>5$H5-qM@pCFu3@`Md zFTU99VY1C1KL!aaepO|tIp@S*+}v2jXBsOg!@Eq_x%PDdSiRWYQIGYWyiNb^j?c-m z#E9jXzfW~=bR1l6J{PibCD#(;BVHo+R@x{bFEU=GOrWqkqpgQedvgZosI^tOi3ra? zH^_la0_@_`6NvqpXTbGGO{G4dYdq0?Ly?YnK6|w)zrC1Z(T{ zpL7*E%MR>^mbgzs=7Lx8s{Y@@>&ijueA?vGHQp4)Bzl00tlewkC3y7hU7)YEbICo%qQaL~K;C9zf9;T7w55YXs<6sK%XEWLfxUav(11vvO$ayWt7;r1 zVLXv&n=-wMJ-)eifpQ>4KeI5VpyhMO!2w%A8#Fiuk)o2bcrWd%a(LzN>hh~yPKZzQ z*&1J?Ou+P=>ueqj*opEYkoc-VT+ZIZf)OWQZTMNCVy?Ddb;;HTBcRaFM2XU1`ZTQs zIbt5O0;&L%907@(2vox9I5@PWx^oR=s&4i74a;W=S=`Ba*^s=e*IbMVn_UnD!#+nATBKMKXX`uwxq3t_%-DrEWq>jGqR$@(H?d|sxEPac+_=010WI^co zEmvRb|6|sB_Q9V2ne^5NY475+=Ll1@%SyhxgRflNysu2Q4$cdA)L8Oa;URw$Zi}%g z!9LGDsXpIS&Gsqpb@s%k7Xn!4rR?~xQ3rwixJ|M7py$I-)y9in@9M0Tb7B4SG5v^K zo3ayy;?4bx8u6shm+yj5@{$v!w9s(#AAun=vZpY^)7Ym$E1;@xV-=2;cpA2iMFFF0 zCj``D>wP1}FdDf^y#mSSGud^q{y}t4->tnTCqCq2><=75YpQt*ZQe2os%- zCucJ4qtb%s!UAk2;)zM@m4Xsm>qFx1bsy?;4~g_(G65&p#UO7E++3h1$%WE|Q5yLAh>gB_0Fr)o*)}ib4cnqQM{oa$#Q^g|d)xdN zn$4eAlaEpo6}|GgugE>5qFgkD2BMPx)>TyOobvKjMcUCip(jb2zO>ixUpUEj$i7s_ zd?gouqw}ltEIGqa_{Qu&xOKGycIp!WGFYyx>^yauu|$9@&Eh|?mrCkIJ$sqT!TZ%B z&0m0!yaZ#xp!t(d`<1J}$$LI5niZFvA&kr#=Ogc%atRr)FHHD6-iMu>Yo@=ozw&5j z1o6EUan%|bzVs3P_ugsDVakFj=i^D?E}hnfXKlsiB%afrvEbWRX_GwenF zVY!(RUZNXBm$;0t4SCF>kVVuu|0xJilB*uZaNiT26FZH%z^A!U>ImyDkCCrl2{e3t z%F40f$qROzh$^D%u|n097P;6>()QNs&;X>xK5!Ka^&@?6-K9C>6d zXYSf=b7i5WoZA5k7D*RmFOgKx-krMRu-3ls!s_0y*B5GR)pY=!4yFkR9GS$xEzS9f z7xDud)Z*^N=pD4re^c6y4q&yXQ7!@0O9PoitUJITXZ^SC8V{=AZPa9MriLP0`#vK+ zA)g%%$73(;fDcuhIW`(|8=%#2)z&#gDG@?Ns@h!PVH**vm}br%dG*z$t0F z%F+P8b9=Pe4OFy&ZrBX~$B`|ZuYCrcysiU^E%`LB4l2-XTUQJo@pw$<(^|O-$~2}^ zML=@jdq9+!d*j*RHTWXUx)PN=v%??AtfsW+L;mZUNB)|*4F|f%yVt^GJ(5ul1-`U?YCDNKke#L% zwPu*6$H;Kq!FL;aoX>yCirz--5vW}L{(VGx`g^08_wbZ-a$pJwGE)su)5fJOpoMmf3!rW;+#A8dAf(&-$ciUUQ6W`4&e(fp6WNBM4X6 z8)%VhZN)3ntKq`#xfQI5y^R-cOVS()uNq&Zp?6D$AXy6F(VmTmc^KDAL5emO<@@^Y zVPk#oSv6N!nTz$Qf3yHy-?H;4ZC|+o$)bBo?2k?f%3r`ARf=5)DWRs?_iR?Xb(gt# z&+5r)E3ZaT5v$_`pz zcB56FFFD&0eCm+$252>V!F_c8)0i3Ph&l-;dRQg`t`oougP1k-eM5Gi@$q8W%SoaU zRw}xkmoi2TvL^9_R$Q)DPe!uoypZp?R{Hdg2s=f|ym}yVm-WO6cNo+Lj2M=rhv$L* zBa4{kTflEdM~mpE8^n0iG)tyu1H`;pXcX6^FSR0`KKAEk3KPYH8SrAM%dcmGhf>Z3 zC?k4))CAR$>X!`Tm3x8MUeCIsV{1r>sS`LEviZTIt$&&?lMh9mcHi2pTAXwpddK+U z@n9Dmi|o)cOun@O8?$o7T2{Z_-_J!kbS~r;ltR+2jjwXwJLl37o%x5*vgrZ{K=i{i ztiimfbY^`y?*x&`C|>IvjF4xBvC|w- zy`dkqrc8|=djJ0Y6uu}PWO8{RFN4)LL;{)cur+RG&fq+>GeGY_w}nJVFc5V#!9Vq3 zVVN)TDcJF;-t08swR3^+%$>ZkN-T8FEk{o8F|CC;Y5>sg-jkKWy}-oimlVn_T#A+J z_?uF-meYa_W^lyFFjFY>mxsG++)2yS>p84#VscMoLQ4!{3_u0u5=a}qwA)skL<|h$ zuj6wqAdzqI=I?GE5>fBu(X{~A8*^(*iGJ1THG7NVrdv`YxV|p9?D(di7 zK*@ucd!ed%mHqCgfekzL#lFBxhFN&O7%hJZq7`dmtViS-z(Zq{HX($m;nyr zx^%`}6TsGs_2be5r_m+e+wrJCv{;Jy};ILE-`7(_DP&(t2t`dsrFP z+XKq*K(u6C=II_KtZZPcx%6_+o;@#xlZYVc^v}()$jtz}%p|jC&BD?)U8&=AH@tI| z2*XUaDAagU(-EDgQFGU91E^R850Gg;V~|-{p^tp(c~vJ0Tjaflu33SWGf;1HCU{OQ zI5)q%kUiLPT^WY)HX%TA=u6x`6hybhx^?S;fJy+5Uj*$_ zFk-P=e%q(VLQFvH-kWDdq%}S{kXGh3KGiIX@fHsNO^;n;wNRnUAmP5&8)M~c$E4vG z&}-{PQ00R{mWSYbrhDPv@@zw57fU;lvpwYSQcw=OmFu@K`uIu(P7a9GE+X^u!ws&N zrKY~T*}o7$tTMbJ>muVndR%ur2K~BdoB!MlG{z=7-(mA$>g)cLBxJ(suAL|)&lnQ& zKIrM;a{~c*eMu9#G=;~Dcvg>W+NZkXo6D!$Nk#kSd;x-A`Z_&j@5!hcka~m)vB+)6 zzEZ#D27q$TKv4h^KW-lwKioI7wcx)r^ELQXOE6{Zf%yFjOoeZvmyst@3`6C4-v3w$ z6@3AufLpy_ci3FK_+Usuo{hgLD2$bTW-ARO`u0g;Km5jRQ$SEkE4#^_3o`+|3|q^X zn$4~+Ddz^`ia^s3l1ijK7L?18w=FL%x1*%V9?|yO0cG<*6SCpbKVlndE8A zZ7lbi@%6Dn&%29zmEr!V$92cdGHw90RSsGl2UYb+z(-I?C7JA%0R8qT)NG`JdTPZN zHJ@-ad&ON2-rF4^ptG`k>C2#<}r2(1QBz-6ZNw?kxOIeK8-puk} znsOU!jgxl^f*35mJ!;@XCE_EQ$zgTBhHLvlAy#{4H=udeQzff{ zYb>4)Iw0ZQZhbW)#*IaLN9D5edUXKpG$J3Tj;y;gM-zPJ=7d;G&>_f+4yZl#8k7GL z_U^9CmDJZvoi0e}Z_Jd|liuGSH~6#CW-P?wj)$m=m#$nS^{{d!b-`zCl3KWL&N@pCN`4}M zPO!1jrPF-Dva$eLVl@{FT6V@|K#M&LrLUBPhtkcH&jcO}n`0KI2(i6CXP;T1LMAMD zD*#Oa4YZ7wxG&GQC?M;m-d%h#5Mjmb4aeu8d*GlS1avy&(a8ZF;gy0DL}rI$wl9@9 zJEBTPrRAY9)oab@HLSL{R$wXgJF(mcc0@|5+yo0e0wM+-o}Vp?+S;VoR0P0dXb;4L zvjBxY!EB65L5ws+`Y`)jDfkapp)4L-m1|SEZXKkY5zsP1jy#6tg_f;@7TSuk{{1rs zyr>$`_FK|SIJ=Ge&f|T@Qkm^T?kne6JVDX<^EbX`hYhm_E8i{zjF*kj=4Hf@9E+P4MzHn&v+3-Sy#zqyu zFEf1?XQyEuVu{L&5A2JPG%fl%xvJd#Q!`$^2TM&PC6kHab$g|ZI`8ww8?D|4+xpO9 zisq)zA&TZ`Ccml#KLmKQg88l=sfW%lQr~V?3N+@p_qpdf$ejeDg^s~jjlTUk7NEPb z7oB@azU(9XZbBy>@I1Wx|E>irK(RH&-PO|SYNbd2iKU6odQBIW_w1j1fn3L|o7I~8 zCK>~rto!0br**k^HnThR4!OtTlRiCOy`d*`1w4R|XI0dNcpvjgb`}xT6!rBtQ(}4h|XZ=-7x)b!FrPn%%#*hRT?grnK!dJCEQ3C8VZp zlP3vadq+Rr3mo4Xs1>ed{v~?&<`ck`DEz1d>hJ@S?5>G!bjKlCKrJbIkp9l(s1$so z_5I^sLsF)F=W7&;v;DlF7{EU?3q) zTx9mlC9-<}XfXxlhQLSB=qAG|oJW5u2%7KMKW2NK^1mF;E##Z~oO*h=r}mT&yXVw? z>SiljC9aFU*lkPz0t?#!z+`z}NBQrFt`Xx=U<32XUr?(26vXSWqyBiCQIP{l3r0Ip=g9?oUKJ@QI z2?3fD7lXq$;Bw-WP?GZz(Zc|YibF(`AzS6{>vV7MZ=km{;LJ{xCS$UNGS95)iXA=iqw4YN!|ivO``bea zV?fz94k_@R_;Ln!wii-dy|uY6Ki3BGWzq?fV6$%q9Q5%IlvVf&79>7^#iGAe0Y75_ zv^dBpOgL7MNQ@x&$i0cSs|#p2;95Ek&-?uO^X$RlGj}=M$d!JLk)6bjRxUfz*ed8k zzBQbmI1US220tE{yxkqL9}K4c;ic|s=9w3|ar>RFfmK#y+I(|I!7xHw)@v-To|%~K zoMf)ehXr@&D;=ODTkRsjZw2PLX|4N5=~AbiS)qC&iB32VAc;^@>(0Ko%7EpWD?sS0 zMBj#FXKn_5XQw^=OcIboEc!4%vst#u>G=oI+WQrZyw*6^U4UTO28#*QD4+tzmQTp|3Wq*NR zUi#vFmwB{XTk~W7*=;HVR9ENr(_UAZ9M_?$ZJ=hpo0UAGt<8yAeG{hhvy^1~vxuw4 ztT`z~72nx8I57Yi_Sspbw!I+q&EJ-I8V{at)w%1xo_YwJ zz1GAWRw&&mS;USDwPV-8tnVX@FfjGf4DWm(>1KAQM~Z1$Gh<{Fdu7ntVrSjm9m;_# zH>(pQp2ENlnd|$<)2}lRZULQ<;-F(?)KKTY?7bli>Y77D#oO9Ux-i{+c-OW1!C=T( ztI`AZt9Ac1YFSkD*2bJcEveYeEC?Jp;Xje`V8DIABp+-{Nxdhab~bddIn9ke2F?I@ zA!XP_tJ%u0pjB&Jg3s}@S^*(ITZC{2lYHVt83Log1a?wT0PDBR0QRF}h7?7Q$e$;7~OJhBugw26!4&SMsi-7z4A{l); zHBQMjM<Um~rK|j6Fs)VQpr*O#IgBt8VRqP*STjpLBlQ%NCuU9Mb(%?k&1+Yf- z%2^l1-NNn-Rvbw`@4tUBS|^GBVMK&!=V9H2k2YSnpZ}aoBkONi98lv}e=^pz2aKQ| zTV-R70+73dk!JwVnhqL|Vc=8>d4B8{?D)&Z4#92jcrcF$x4@D1eW*50$hLSnas;;7@=A%}X|P2d}XAl=05qaJVe%H*a0f^EuwUo11X=M_y}5X?Mz8d381Xy zCxAM;n%fB=jS*JD%a{8~9G3Hn+PlC}2=Y(?7z+(7F4TZvo8X*vfj<>Xm;#96rfo)k zpQy6?%K)h*I>=>iv*g(dc3BT?=ivF0pyp*HJZF6qK0FVOqIi4g-2Hd(P7lW*Fa3qc>_ze~IC@584`>mWX%0LL z`VB>n`K>KQfZkV2nYvT+Od(Bg&cUCa6yAHfz`+mJ;7o?93OS$4xD#~nqozCsoKx~v3`Jo9vg=2zWUybaf~}Xw?UNih zf02;Sa8Fs(-u%uN1j;N3cFQ9!;+8g(+zw?*&Z@4)ghoF4sp;7b zrZjTDrf2|=H4!AkukH6^#wwWeFAu8%OFF^Aje`4tY)qf~YE#P*W@0J`j&T8kk#{8Z ze%xW4+#?`{*3;LY2Du~>da!2h12^-uooK5%80nNMoVfT}E(X4D5@e{f<&1d5?eVX~ zSD&+E(G+Yopr~FgyGnDRqIV_3113O!I+IZ72C&sRHvm4PBDgN>mK&*1K9M>CM9Ug| zxI|pbnA`Z4^)~pKVq1`zU`oGofkQ41$x*1_=o;>4W&E5>@shdX?2jX7=79VHNDcNB z0JRuL-rLL44!{<>(=r{IafNwQlB6;C!3Ev=(+Tvtw3z`T)qZA1d-`XOT9|Q0J_HNo z*(aH46zL5L99M!EMclq!sPR};ON$JT-bmis(8mRNt?Bd<4aDc{eDD_jGw&OV0T5>Z zd#o*VXb)n58xdrKPPoek!*j<#r%ow4mQEW|fDVE_2n(Sv2x9R?=2-}!gx<|cW@gBJ z1=kHOT)26?c+inf?5_aCE*^mFZ+C{Zg4RkEiI{BHIU<)G03vHn%;DzVKK(;4MFENw zI|U2Lw~mJ>yf?1LLGpnE98*^l_5Y_$oO$3@o3&s4PuS7+CX=b%0V>)dree3yxn=5m zqqyaRU83ftH?Nn-^g=4+?W+gEO9jWQODn+PH9{-a4`C@V7gq9~BL)*-=$RErQ0hR) zI&s^a+lfMNfwMz$J%_4iD~CX;*vH=K+n)?Ve>WhX-hdO7CO=F8RF=+O=^NdK4%yn2 zUtxfZRT*$g7CGjhv5vWZRM0#X0docV*;#saiYh?Po?`S&X6%>$5f|CcfZ=jlEUp{` zVUT&amu`W6whEB@g{*!KG3*65z;38yK%S8SqHgkILL1d| zU8YPaY=Ei53R*Z#a6J0~cUsj|>WNM%8>mT%=4ZGGv2*|v`d6Rl`CEs1Us;#Z?R_^( zlCf3`%){M)o0!r7Uvl=-C7%|7KTw;#2>an(pMhu|`6M7|N?9c&FEuS~p(i2kIO{Nf zK~c=EzeG9pyNe(FR%0^HdCLNRK@FM-oWGMn3+>W_jq(6f279M0_2Z~iE;orw;yXYXr-@*>x*;+2h=a#!F3 zs<;1g1Zn`z#1|zM|2T(^{{XmoQ|WlN-A>ekd0Cvtes3;cvxO-FnNm8DG+JMLPR#gQ7l<@;^>*gbgp% z7^WFDQ|fP#cfENv7Y8O4;Qs#<+ua+1pveDB=mB749xy%G9BtOD59Ad3UF=#5IZUZw z64LjB12;_|Z4d{Ao&;{uBt0 z=r)WIaTI9hs;+~r`KRv-4-94cc6Gi*G%X4j?@bmn%CEA^IXhf9oY=S?yg0=jt{E~| z2ZUgDb~Zu{xIDj%DF+kOj=vR^+E3{+0n2B5%3U%u8sD-{ld`>Ndg@A^z*@b{H5Xi;TTr>{BRI#hq^3 zgTf~ha6#7@_4`OMwJO&qA}KTSB!(m)o8Cs2Bdm|_FJX+_`mIBzah-1R%T%L&NTR+h zb>$6RNziRITNOSuEIDd0bMgTe`{&W=OKE{*p}>Uc-mB8BbMJKTkj);6eZQGERom9q z%eA?BKY5#&H`NnqDSI-w73;?AIYTxxWll;4te5WI7z<-4zPcEZv0g-?l-`F$`VKtfPh_~+4d9p-#KzLA!R z$vYTqMw~f8O zpyO9kWYCKuM&x~xOqdX_f3!LZvkVIJQ)(14Xx>~W+ZItKY-#T{@(_`Y8oV!=pB(Jh z4qRR+$dlVmr~B>t^=C#yc%5v_PtDN|$e{oaw8U#jg?aDz%BYqv#}Gm9^ii7mk^vE3CKI{I7|=1wbMW$*g?e1X~Xjx0VPB^K~u zy8@HW*5qc~HhQ&x7oDxs)W7(Z%#S~3jtb8pyi#tSeEUf%$i1UaVVbT!M;SjnrH<2W zr@_O&Bq@oNf+r$L!q3+iT4FOm>vMD}(r;U_claRQLd!%q_;+x)^Pw*8`!rviwOh)X zdV2XOigrQAgDpIDR~#4B4rK{H<;D^}=yjUZ&l97#U7YZ9Z{s((by>WeC|9c_-RkD# za3wK1FyNCW2q(0L{azEW$1#oll`1B%v~9weedx*NahUbmlO`Aa+u-iI?oTO&kNyAg z0@SMnps8f~g7r<>&w?#E;$tT#A)!M&PhDP<+(rg}hQN{w;VUb>2uDKaO~S-Z4wJD9 z(xVla$WeCpH2(J9P)-nx2mSP1Mu`y_mnjnhdlyL^LgQFXuW_;TvHKoT(DB&r!dRt! z{&oc`y}$ky{qXNAo^@zF6n;Xgk@NK{j?26?lepJ+GT-^?MWE;`{35)6=iH3LV=_v} z<%i(O$q6i8LwfWbceKBoi~Ti$>40@}Pz@F~L0n5MF#C!6ic{`fugK%fJSQJfhC7T^ zlUrF)#>~+3Td<|l(#4kUWM0&r9Iuf_t<3H7Hppsf^7f<)k$=z~t|eA5VszCx%551Q zCYKM#`w@VA`98@kAR!@mZ?>@rd*b(w|k_VXJ3$<#s&Pm2)Skl)}(p<`)pJC)?i(Q=;mwliC{_es{Q{>Xp=n4*xC5I(+ zN)~ume2v%j6CL>ayRVA!|2&cZi>DC4j=odvY{~CGL+f+hN=+*YFC3Uk;;E+&#!#G} zcE4iU>8U#)R(AT)o6a6{^hiZz(HlYxZ3v*g3(acU%wvdkjWY-f5!n}&wiF-?MKY#wT@ zv04+1PgR>a-86ww7#$3{Y#qqyc)Kj+J%Se!PS`|gRy?w`_=3KNsM&jZ(2j^g7>WJ- zsGG=ADMrrFuy%V!Ifu0?T8WrSKpcbm9mP2z?O>h1lP0fCIrZWS<@$67Ek3(PuWXv7 zAqlcCI)q4vw*meZI_b8M$F02=X#!zkVc8>?d5G@#s7KS-f4ag0I07{FJxsUD$t1jR zar)*GjT~guT=Ady`)VDS)GcznWsp{9^(3VtQ>Oc4_+M_! zI*E%=1_mN~?D59TC7H!dl0zZTJ(PwM3vvPm^`zr^FaYk7jh*L1kY3NB^v*S|hV=fO zy}doIFUBTPNy*7Jp3tMCt-7VLG-7CWWA*vb!50D1*P`;pj3Od>r@wwERow5ZdBN)B z;lb9@Dp%q>Bz&+rPB)aN-X=18g;in5?S7)9j6b;5N$$S(= zv0Z)P))4F524pn5xPd`VB%{>}|nirZ&40=BI31m0|Q@0!S z_jXw`7zOrKQc4ugW0}G%yW$FOyZNO{=tcB=V}kD<+3LXw=&0WnLTX9K$;oYOUN+6i zuSG;)B?al--(UV=Ir43-ps1+b=WNF7r zsE*$KQSN$@vuzmpR{QpE{`b9-=d7C!Tdg(U0k&0KbN zbG{cd{h8^$la7V88n(VZyEA>s;+e5cKYz3?+iW*wt83WAK{s3dZ9EFG@g`Vhn!Uvs zSgl!kXuuW~AhmgEBe;(zdLQvX!JpUBNa&6$CnZ(%Xn4d>ckV_yMn=YyvlK^u|10C_ znp#Go!`mZQ)uw(CV%{UK+d6)Yycx}Fgj}4sfUJNo>2=U{y1%m9TP59jjy>tsCOS7~ zj@VR7&OiP9{PexHbXP|qkc)l+X=#R%5=(J1DB;Da>1meFP;8IC_odaTHKRMQi-dmQ zlpYQyN=c4uYC$i{4H^ILoPx)Bm7bEqDjhY_!l7zft!FtM^gAK#GvamUS z=ejPA5c_?ACsqXSs_^g86l9&bn(Ssolx5Kvuq<*;EAMkgri z7aNojVAsV@kU{5HClY)yVQXvbI}N0D*{0W%(i5q_bAzhZcErS3mts?Mlw*R*%5=hm z^suJG>HXwHqN}6yHes$V?yclel&fw>uY6zA1&*LWPTm_%`ReQIhrE}Pb#irXzPb$8 zd~6&P5r|4eJpnRO!UCv(URgiU(!vH@D2DUv{Vzk-T3V)oKyUfEz+L4q`3^bDmzbP9 zaDi1_m7hk!E#wg&|BHDAg1;xa6Y>KhqPa>O#@GGbg^6;<_kvoeLK7B-va@i8*qNEp^h4H3Js#rWkBHLhd zCQZUR%osPNY7-?>@$qA3x$|RL3+>2@{IU8v%N2Uhw&gZ#%SGa38WH;>yW;wW`70$x zk5I0e8oL-{_cN*99oX44yl}tjMI`v=`#n z3}Zr6t|WZ8$T+5DPpcrZ?}(w-J{xq4%jvOo?hM`svZ5MT4K6tRTl?kkd4W5J>+N#P>17fCPaCEUuPOwM4Wk&226luIFg%6Db@ zI=iwmuYE)AY{qw#VRzg6dYEqdr5U-1V`UMxDWlTpb-AwkH?!HNZEXq*C%f^To%bic z!sWxhCc=Yf=6j5WMYY9wawvgaY9fY^gQbDB*@>=_c5Ykd} zl&NMrW9JVP-&H$AcNJBx~n;_LjQq8>_YZftF_qdPX&?2pxn4BwWMgFoT5 zzodcDp}#8N3FqJ>j>Z&z{W}2?AJ7h>sA6o;$wF8qAZWBYo8w=0;CI$$Z!kA78$glq zaWN`sKU!_=BlFdMMleSQ|IHik*O~vW0DKE8`bKsf_q@~6+1D4$VE^PgHr+=D1vQYT z=c&H)F&)t{Lu5|o{{AmUVYl-8%QY2rP-kZWtI8A+vngVZv)Y;(^WF*9y=oKUn3z*6 zDh>}{A1J^5x<}0g@ROnhxxO}-d>-q4|Y@6ICgeXb)6n<2UoNeg`6Jk zxv_~(@ZO#(85x=C+@vYU4vp&AGhUGG##a1IRM$lhn@g`XzdUhow6Cd;GxKl^cN_xT zk;kO#DcN z82V{qQUNkz;yD2ESoik#W7B*|LPJ+v{|^>zy1|zc9MH&mdXyk{5lLUbbD)!t-_Swj zI?vACTLxRjJf#$jzcmtGvl!sdtTyb#&L1sv%A}oVTKVAl+-B1{L~bz z?efyEfd>Fa1fV^V2QMN>dn=a?c=%}?~{-= zb_IQ_?`*HQUL9FUdVR;n)+Yc5V)qwxYYRkTe^*#Ci1K9Z|F0wjEq0q<+% z;HK4@(M-x3ohFTd9(pQcV@50OM{?_<^1H}F4c3Y>3ER8Vnenv*ONwW&?FN)K*5CYS zCzRP3qKn2hYuWAGIhMIa`<6~6GYVT1?#Ay#U_FZX^qh_V&8G*TT*1lH3=Ut`1bpIE31evD}$292+9`f=(mFe$S&BX6rpGdclueGvF7G3 z=jP^+-zjnL%=Jpf$E2nX?Gc#f(mN}-$otCYx7uarXFFdE%UEHjHu_-`toL zKs&fC(_DzjEX;^ZN(_<^!RP2^%Y9-GP!Uvp6${x49z|J7(IjD&(B zvTmmq^~Ksp{hGZY0b zVS5~4qzD7`n@M5@BPidN);}43Gbtlr$<5U!;597=pqJn_Bcm3t+sof#D6z!m2;_E! z8m?(%l&{AJO6ikxcX_uX_WFsGLSl4CO{a@7B7NUR){YZ0|B~`F;V6d6);99t^1y8+ zcxeec9naB}%^4khK?3#nU*HCCdv#d(jgmX}HB(C~=4)Hl-2mh)qWQ`kI?&Uql`L*` zXU6jw^<(5%7mbMbT`9xUTg66R_~~9}o-EqW9+477yraW5V{inydkAeH0av!Ukk&}) z?w>MbRU9m=NymhPayG1Da!|mTl$7raEUlXCQ#0vYwZ9~-j>t!kJUnHTpMYx94dcv- zld(etxDn<5cC;A6&yK@t&F}_-s>Gk0-8pPJDjB z+7gsQuab(&%HpS7X-Ea|goK1donW`YAoQ6m7`dF@eQ+tlhikGH5h;hePjCPaB6MQj zEB+Ca!3z5km9*hDY`Vsd@aIpC+gID$+hI8!rHpRJkrjH{+L6D13;rG+wOs7X!ouDz zN=c~hTVE-{c6#+n;q_zYpdhanaM-uAh1g#{QhZDor>bW(;LxzxhUzK}Sqw z`)0MVlC|Ns7-|X&pK~?oGy;dBqj#iA$uLqe|H4fDd&qxw2lq$r6(zt=*Q0G}!EMh7 zLZ8E^Y~;c>a8@*puysf1PiLR<46|S`4EW5+-2x0s$9qPt)sbGp*=slx81M@Vqe--A zgHtUorYbbo$i1Z&ll$Sf&v4yR;}VL6hu4JPT`cdV6h`(+AWtJS;&A>UikjwJd5(X9-(l18ygu12YM8g%OwXBMtwrM zzJzSIvF~iBUP{aS1;e)N#>(%g!(f@FqONCL96vevJm4~bdU?80ISt?4{gKdB!p6ZN z>-B^9R1w{|XTLdkty40dHP_k5x80gq4lWD?0$$z(KxF4+wNHtAv3W?`X&b%CgWm!{ zS`~WS^>am~*=`Aa@qjxl-~qbhj+0-*Y`Wpl?O>lPL6aLKcR=)3OY)yq^WlbptpCPlw?HcUUblYRZh zoSA1V!ZNz&qWNXx@RP+5!3^l|G{%{UhzP6(jmpj!C)nREP6X~>Pw5mC7Iu{Vs%H+J z`1Wy#-@5PR%h?9p4gU#`ajF=}v&@7B1O@iBurL>fPe^DtAY}``J>@pp+u0)?Nhod1 zZaL+>W3oC5Fi6nx)%7{4oLmrmWOApIt~E9m)wzSg(OwT8$&?%%6d=@09y9jCXW*lk&G_zfVjI-Ao;#BD)nG5rM2S?oPDwi?V7` z?=2@9MAY-H*eOyUImKEUA39{FVC?-3zZnE_fT5G~ODkJ4Xr=apWpf8a*;0(u!Fuxm z0E}Qm6w<%W;$*C`3z#gjwr*jZ9ESX;!h|i4kH6p4KoNe>wBOxG#pTgn(p6JqotsJ? zcVukND0Q}~bGi}~+q|Ut%R<4BBFl$zCIo&7ncum zn>8zDN{WiP>Tux)`ov6bn>JhqugKk4m@T#@4)1N3ZX{VOc2WhS?dWJoVVIdQ+&}&8 zqJ4&rh6V$zt1ornm3Q4ke-GOR0y*kljINpQJ5ttHFJGDH8*1BQ&zN9$y`rqn&&Pb; zmmD%Ucn>IP4!UsRiWl6#szsR9&|pe`=_3r0p+CI;yvONal|EogA}({-*6O12YAQ?YUAXUj;XB`zkl7r zX=7vadkZ$bw=c|m_wK^Se1`Z}heH~T3eFGs6K6f^pnAvPxVTtdDkzYx-Wo{(Jm;I3 zC#KNBm2`6VJA&nnAbNit&j!O_PobyFjZVV6gnzjR0P*ssYd{LMuW=b|G!o%5z0V*k zE6c{s-9BD+x@T%e-#b+!WUI1MiRL(z22H{-{h%6(zHi+{x0E8h^IraqcQOEcNq0n8 zmPRRBqa-}qo%@)S@f1~59NAyDuh4(|*sU(B-Kil9PK26oFIBGTz{Fe3~H|tEH z3f5T;*Ye>I$gR1y_b6QuB?GH(wAx>CijGthK^`F+cBw#83POXFwtqX;8pz;SmcT%;s}(iijabp8~-vm=xy=3-a}!lmsF20u#ydB%q-nnh>-r~svP3F|4#7(f%8;Pq=wIKdNUsjavrm!Wc4*9$N*|4cpGtI}N`n^t? zc1JVjl1oSWw_iH4qHTg>=!4$I9Io0mz@mh~!fbL^WA7zAww`^I*`!XtXBEU_;>;|t zRJ^6l(0c{`9!{@61mPZ$dC75hG_@JTQ!tK1}#KVd{|&uI`z-kw(&~ zYY;-cgX7f%SCOuce?a3(52)4<7w3?RG;!{5vA3tg!NGCf^dNY2h<-eB8$6ziK^WRi zG4cm%ABU@ zGp(V8XIxQ;cRvKw(>I}?<6{f}dY%`G3H(6>M$73e{eM*j#R?*q62s;CI%F!UfGgHo8@R{v#-RbCZ*3?_pkyb+)M zUM&CP1qiRL^{U^?f(_%6(je%6@FzeP?FLG*83I zR^l*?fk!8f_U;}2$$ZrC7J+KB-KS4{IDaEgko8ZICsp_W<%~p=Fa`T?tljB;IexUa ze}5ItA3ug(T%e?i#tu1%;><+b?7RNmq5V2hyF-4pry~0 z)6sawm=|zkMYMoST>_h$qa!i)CTMCA($i>3bQ@y+zoKAeo* zLqeeqQg2g6_xU)dR(C7`IZ+mGjpYQ3@|f*8hz4f)rlIk()3!{x3tJPADg(}=9VPo)bpD# z=W%e@rn>ix@1o9C{=o}&P}OM}84a0B3!kI~s4fM7t61f7o>;CFn}Isc9!JA2XmkvX z-Qyighu`02>KDG*Y6eCm{YhYz8a|Jy6T!S{eCR~$s%0*YIT6odAK{B3bpNY@fzt>$ zl$N?|iip1JHq%g11=uAShrWHwwK?|D$zVAQ%XHuf$hQ%`=h|92MocV@`eR5w<$F41 zkq&mo{gz4{GEl8qf7tt(Aws}izIzgUaUoZKo}9yo4iPJGdGt3R`!j9=^33{Y;*mNC z$Qiu=^HZ04YR*y{2SGVEH}HiTNUiy%H0Oz9Dm$$vibWRSMMXvT!CjH1Xxr|9-~($qnuqc*EE&V=n{N3nqkn>d^K8$_;KBVol*`nRdjHy1Tv)Mq9`lN2&ijf8i_oU zW*X`V!sBoH#S%f|x$#gBv{G7zpII_V$uP8jiv?nE>`YtFEjh7#`LIqF4}UChE$Fs`Gbw zrJkJ$qg9HbM`wIFSr-g|iNuXmujK5EeeYfw-%A@ZAKy!$z00jeTJJ9x?PXXfL*Dzb zsxNlZHKhn&b6xi+OQQhw4HFZyh8_nuifE7(t9U*N6#%+|YBXY0%-e$HygbHi9Y7b> z)YW~LolWwVS_p4`by+9>S9UfO1&n7?@byKl{BM-vRAY-lse}(7swROSNf~D*EKG?# zvim0|SeLt?7!-e`S@cR_I`d0^NKD`et7fHp0Z(x2+UtVSxJ(Z5Y4f*v7-Ued91Hn> zc67?k1My=08Rn(|cJy{qfGkqADNl!Dvt2)lg(V4}7|3ezOja^CzafA)c2Hk?(+pBI zp93ez!)6*{ej@i7XU=E_>A>TeYocrSS;^HQR;qslPYKtZ8f!8245?r++HvJx`NKrx z(6N%7-7Z!$SpS@0S|>t>8M!Xks-&zFHS=3D0Kq;FFn6Mad=lvK^=!r> zffhd@e@16#VD&;wRV62YYCZ0=hst0d7=6ySHWt2ayuXVz<9m#nkdQbrS;{Xy3xvI2 zMx>f1Cc$vHroxS+9P7F$>UDtCRy`Ig>T=i2U7>8OUc$W(uZBX{aU5MHs-Qq6v|8?B;T5l#7yxv69hvYs? zg01D7*?hQ#4$*q}CQYU38OTg(VR3;szukin5Z}VLtZXbn@Ct*WN*ggpA_V*n2vt2JkB1Uq9~3Pmh-fNtw^fwYFILslm(X z9zSJLcf|U6b8>dxwImAM;qK`a5WKzm+}nURt+!05nX_&6EQ4qJ{WkDNfsuK@IKX`G zUQ8tiB8&66{P_GNztr)X5%gbnr8R~VSl{7=ApM61O-gqNG|%~9)_IytW#MBXuNTf~`ix|Hes^BM5>23xxq@V45m>D(NrPGxQO%8E z+~idI9GsqPPOPzuO-P?U3TX}F( z%h7`$iwWDYK}wLm+{C>&v!lkqk8ERbY(xj5t;fnV;bLq%Dn`isR@H3lm!W)D2@i0P z%+2mVW5OBEq=Cy!XQsq^a||0;ep({t_>-__L}q-9+#yAUg`B2dySF?AclLRqTsvpv zar#pkb`h}4{mbLqqN4h$7K~AtVd3Vddy|t>phIL|!xHdL;x(KhJM)VBR+4QQ$6F8E zfj0~k895&BUcLwzL37-f@SG%pTK}Tx^mcA%ie5=4m8Ls9JXCpbT7gdQj0t&C*u40l zWUtSzU67@^Cma*}KxtXJ*2D=AtUs}gZJ>^c=$T4wHmDh`nS)@@Bj7bA}WL>T8 zcBaZQ5^l#=sl@+Xd49yUxmgQKdo+_i(hVA6Bfoy-IKB1bWOw0xTqz;Am%TYl{>9$J zm%Q+xs`#kL=?9tdAigEA-%sCEuv`E!K@=03D_PCoBK#wA&quU?502Y?Q~1pjMmxu9 z^Dp;HMFsY;Fo3tm9SlBQ8Xfb$O+K$;>1n-lYzGRPU`1TYPLqbDZJ0fe2m0k*XRhd= zzF*SeV`Brajv+uBn#x7UCpf<-8JU84Gn%95M_`1F1*RYTf zI5HGuWwXc{whlqc+(>{G9j_Oo=4{One(K!qKRfEb?8$@}$~PX5PI`weGCf zHC?CP=wnU$^5|Z4K$3KM`ZSWGzfJu`7It)kbI7Xe)Bz8zEyug4?mu#=49fExc+xPA z{0ilXd{cTz+Avvr5UZ`mhN91ddgYkyh&Q()BO;u@&{xva)6>pAsW&Yx`?VZea6@>| z((g^ga+@T4d=Q5KTD^cEtT-h}k(LD}D*piKhKpB1i6)JY>D}vY$dQ~W15SEppS|B- z;&VH>x$6U_-#k!Jt@T)0g~<|E&qpc(3O@Fl`0yd{QPeH26QDyH{YALyk8BUNXo8>@ zD9kLEL0}BZ$nEBh&CR!k#l|6w^4VHCI!x!L{?jD1(y`O)#Jy_ZB-*ZI3F^nODlfGWCc9xEsMZkcn zHpVvH59D3VH1**16F*V1w86rspyK3xWHP8Cd7of?0Qa?J&BYF~%+MH%r3=wbsv9Et zL*31m3sUtaVvogtFIy6K({(tiG=J~Q0+S+y(`h#54&_nee?LV;H4H>5iN_q-IDx*({6}U4gj>0;weVy9G#? zydr;U0}$Kqe;r?Zd+U!vk-&VT|HQqdnykbcOZi?_wC?IF4B&-8m2mUMYC%gd;S8ax zCDV+@{??Q0Iz3}~p{FthK9an=jnknNbabI7)gQ5N0Bbp@{W;(2>Cm!0aK)~jq5mYF zWajhn5CrpSqT8muwG4Fabw0Cs*n^uJ!VupH;AV8lbYjB>Nj_{#e1x{E60jZUE1+1}giHB6J>wat9&gH0W@>r_^ z6H^Nj)oRQoCP7wZr8*?x^1=tUGmAeyo_}ELSgZu(nmfOoZ%+0{4?yLTAb6`#7sQe= z)jth2Hfvhc#eOIg4)w5$g%l^zB+Ip-rHh;R{Y3m{_hqy*Bl}`&j;%e6+(JVxrgX-P z;{0vSl(i*7_NhVsMn<>P!Cfp~@>lRU2_SHXH^483JdW!L7kSJ|f{z2GXQO(JD(~EX ziR8AessOlFHRqD5XBL*qEu$SYz(rWT`kUQ@yY_x6?M+5Y2Px?gt^N5AgkX|R=N%{G z0zUIm2|e}76uw&LQ&Ia%qOYlDTTCyW|EUdx`XZg?(Q+%*I|t zRXed^cdL*Jto`a$>__qSjHCXm&uqPWj^nJ9$JU{U7VyfmD#rXRoK4eBtIyWk_ZrMt zS;_k{cz=YwGkOomA^K_55|3TEkg%h0!y+X^FL5y4Gv?DLL+}@^lAMX%bTx9t(Nx62 zzZy?)Zrw5{>e~RVl?m?H(uW88d#=v3`+tOUp%{8e3=t6#vGAj48w$DO+T77j;rZR2 z5q$rX)^}s&o`r80fB@c`QnlV5`YwT$&-}usCfI$YSP~de15WA+1_mt0iR!#fjEyz_ zcX2%V|3Vy(`*-3v!M_p50U@Qzl8~kh)bW12PgotF9bw5O567FS{`N+E{>%Qb<6)G& zKsn;td|CS)hs8Q(Ly=S%XC2PpfAr=o&DNfrHYQ)j^);_C2kxO%b`4Xr_tJ+bKIazE z%~^jUkT12DaUK|YG!TKy`;egZzTeFko_lzDT1@=z0%Dgu=t|QSZ{~akA!_wJML#*3 z6tbVmm^6k1Gu`}~4u)8{ZJBjhQ3)~2)qb0gcHTkmdy8+V#F=q+py$n2h1@(2c^9C} zmurkr3b9v%z=Na`%W*O%C5Ne-CS$$@NJLf-a~Y`YB01AuUWR< zY5l<9af04cI4rOL;@C7|UpEIz6!v&at;dgp4&E|F+@iq$@5S$y@$p+$OXPbtHvCo5 z^nhx_+Wkv4+947ZzHaHBm^R$d6IskR3Smy-&((Z==ja4EIa}sdD&i=q{R`hsA|mAt z#E{!(Al23K80{^{kAQ+Yj8W;7M9$LYr2V{+PX z4gu_fhRpMicBf(mzYo1iR#o7ADKz1?1oGy9&+z1Q#U9AkC`C`AfKz%7TyhD5{jSvY z#Y3?X-{YBJaOK>>E^B&XT~IrLX-W6qJCRv%1FO|mAF#^4qrQ&?8aM^+l||3D66*c* z1Dq%2+2Ph?9=KJ}iws=G2$4vyIej`$KZ8ksLi(im;+tCYH%czyd+dQg8C-7`WY-Ff zNW14r8yWR5HaP)BS($wKR|?C{hQ0VdrsIU~DOC}?e0IRO{igrQ_Zh=(J219rsXXkK zbL)G77m25=(1!GRQ-aMs2M96ZY|aS+#YIWLy^Y|OP@9wo{K0qIelS0^Lc8C15j7B0NH@Uc z#$)Q-F87vRNE@dTA$HFyXFP>9IdRo2|9sMQ|9pybb!OTOXJ$FBQu1vtx+9p%qF=hm zv8k$(pez+-J%0F*|JAccg+Wz9HJs6Q6dmH;)pd+K4ixICoz0&rHokoWzc&rwb#chE z(@-Q04Z@yOe(c;_E1;xIeTCWE4pD=_w*QlWy+}!}@LvS%cyncM9r(o2gw-GY`YA6s zgtu}BpZCNg0IyiN8lccCzPzgZ`1XqwUj?DzO=jbYx*;zT730fFo}r}qHfFmzhkKo= z0Z-}R9kuZFdzqzVnj-qI&Qepr@Cnq+dJsBac=@#bP zU<1yx5es_jzVv}D7`cRxUhze_d>^+PTnWF$zG~*}!NDV+1EX2*CsXV(;SO67%jE8} zfoXLlK;UNVFo4=11D=UJCZV|ai=ygJziB%<^$`5pw=zbcEkPYLRzXyATY^r@7EO8) zV)g!OH#Y#_*R&V&K3_~&Y|j&zI;ZY;63Y{l3>x8>?A9nhU*@k#(aUI{jInuQ+4>{V z4$7oL2BjmO-?(AZMi7=!>aWk1Tt34p_E27(Q6TJ}7OGjR3gYL1U$WyZo&Hp=fo^}@ z6?+ItKz!dX_X~vfVNRIk&lrUGB5%dtpE(;_an={V#T6`LJrZU0>}eBMs!1j`4XW#j zjg5Ecpq8o|lvo~t5&qf*>Y!n5y7qJpZ6^rbWo~gb zqGf+&pwY~~CQ6u*kr)_ULV%Z(0d&0pc7_=A@4$BtV^P$mTmV0+u?-Va012QI(& z`tMscBLw+YYiJIpGh#Ia0K+X#*PS0T1OWncejzWbvY3EU6%_S4Ou3C?Y2aucb1>tl zM~4LA24wxks_CE%YftJtV+DSpOQB~NiLziN@2zJZaBZ%m^5vS}lS0%Jula9ihdL_C z@G=XO9}Q;2?i<1vbB_01hEL<2rY?{HqWhMNmS0SWY&jYwRl;5Ku5uKqKwen}q?W>b zhp+!hw*J&G_OWjBjAeDUk>Tstues2)`<_r}#zFnDbX)nCcz&y&3Lrsc6&DjI$D}t^ z&0YqquiLT*0RvuCXGEZrv$K+?C%PNUrjneTc0L*;*`uu~5CAuH?c4d*&oV(dU1r^D zqXVZ%O2{l<3?hTtjT9$&!b@HPXYh=aZxGQfVhrGFPWX^ydAwb(J+Jf!h9zCpj8zzU zG^o&bZO+X_kCiaNwzh^*lne~e+tlZd&LV;AmslSrDscmetU$HO(VaBV?##v0N7PXc z2ZpNE8=12#apvd@Il1C@_&&l05O{=jBTKC#V(=~4-1KEc>&fGqRty0{l^@OsI^3S9 zH;o-yEWOysgB-#`^ZfAyFT-VN$Qb>%Yt|psqeHlzBBG<^oIuaFs+t=2QZYB#u!7j5 z3}IQ-DM0Vl2!n1XejYcni_6PJFs-s;LV>9Qua)T&dGpWAzw9%x(NskRPA)hZ+HCRL zFF!(wBmxZAgL&KkELKl4^WSO{MBBVXDLRon-2LXeHlp32|TAHkU$3 zDgxGzg5u*zLBwWfH4ry6%utCsA#rdPzW**U{=0Q;UJK3@}#)6HOWWV*UU=83yp;DXWKwQ-k?8;+Ki9{*Kkoj9F(C|!nj{;(XM(Mxy z&~kqStHz=i?sC%+1>{V02#_=TY!L6BW2sDHgE%&&DA1&l%3?@GDyRSP0*DJd1fOj3 zFUq!m5^BIM+th!|Nd{GY-0WpRU2FP{Rbasy(6#p^j+l{k5V8ci;*USJZ4y2auv{*B zO$0ykVHLr51loB(IXlUc+$)6zg6$s{9N(v#I8b8ki*{4Kd2zQY1w-bSt6n9Tpr`9d z(`wnK3zQ<H}DV$}>ZiZeF?u9F3pqP~7+$;)0HWcd!Kv z%XNm2$`{2MpAPtczbN+B($ll%TZ^`!p?we95pKxSe%0E2R6b62viv_gsEFtWHY>6K z*bJUfXpo*+Y2|nycnAmXlzb>_-*yY)1$hzqk{$=J=8?_`F&wd+?(QN9iKeTiso@HQ zL1j?_+B$^pmMMM?uFM;JiXds1%gKnD}nX z9JzwgFfHft8hRVLrZ}ecdZrPw`^xM~b>=KLG?8+zRfLYw}!?pFTE%X6!=iHqOs4L}fTcJGlj{%X9s+-dA zLb5D96EOy(Dwa-<`GOT-x|!Q>9Lcu+wM`{yE9z%l^T zUT=kNl6R;_@#4Ov6~hD5sz%%E5@h!)1YLeSV~-@vw^~0uS8Oh%yj$qlM%eTXTuO*Y zdC@n>!LcLPavkIEeh&<=|5gL``;S3KAvzpW?}B?S<H*7c1F0wRi_l+q?8pwbN@V9?zlN~d&42`HhU0#ef5Al;z?B8cQhT9oeCAbsb5 zZ_o2S&-Z@!yYKzRIO7aG2iAYBx#pZ}&fhE}q12P-FS&BkJ^ma#1%(yQ^-cy=?iC2V zc33CpwHe8?+#1T|gdY9s51RJ6y!VN14}Y=07k1OG=XkslXIuMx%4L+zcs#vOA(}MA zTqX1F!x1Gb&v08&10wyq4~G*bD4Mr^Db&AJRu?`s-TFnZ7KZahcr$=8^~}%x5p>Qu z9ZVYtRo#F@#8=y2uQEA%E_@3LGpV8P$L;odRRkuUET1!%TjedxOibhiSK(J~zgQc? zsV24-%jy!1z(;&88c?Eq-C%ot{Zljk+8_IVm;7?7b6XiSQ4`RtQcBPB!1#1*%NdeW z0i@FmI79>?Lzp^T!E5{Cg*4d>DJ zPY?SOh5YIo`&C6nq!wq-`=s6vSP!= zqZJ5+I^;C|I@Q%+r<;y#hLDo-woph=upM1KpSxnL3x(W{6g8h9qUqpl?|7%Z-fw+n za?s>rf!NU`jozMITzT^5?HC(hw25gRYVi^K8|L2=|iW~|iimiT>xh|FQ>Bzl!hN1+kCJ(V{cr?Ns zu*C@2~q(@3g3D?-}%=qfE2gAhoWNv}Ny=5I=$mh7%R5Q6O^knhRNvj_p)c9ti z4BROpH=D_2HMqX;Xo;b%Q{=M2_SRO*u$;|Ug=xihBhUou`6imq&i0qo)HYPq$&8E&J!*Me6|O;hKuaS;1To+0 z%dwB1OQk}Ci}ftii-!jX2c&!+?%@7ahHrPo2n(^2Opg*`J%0hA8L(em7x-0n$=;s9Kj$cbJ}RJZz!{C6#+RD^lgH@Hqm%{ zQ*7_gbwMa30HTM&+lwmDJYywo;?*lUi0#h&ifpJ4g*2DbG`x67N{FM&>X`jkVmk+? zkKUqx2(|MG9u!m&ZA^b&Se~c@UC~`)O5&DT7|40&{a}@ru5Qo6#Q{bq?s2J*>5Es= zNXFb|JU?I1nLp+*-SH_~rAoOpc%}yukXJ_THAT-R}HCQ4Ca*fP`zixBG<#_^W1%Wv(CRmFNn}Se3%@6u6A;4f=(#> zclURr%;G_*5y#Ax^wZDpki0Dn@pw)Q^%}%iP2!i9=71IRNKG-xh}QqG;(s^aI>{m9 z;QzhgIUAz74v_*be`Ai1&tUy72)7&$CWCfL!9mxf#i&xHUq!TD54mg~koJ|6`Sml6 zz?#n?l!ky!+s_zFOJ5rq87YGC4OM=qkE6%=MxTmYx=e827pi)AI}-U$D2SrpL)f&f z7@E7>Zij05M9=L5?VVRcp+*(>*>yWa*?%%Umfs2m0T3CK$ZMtizAT-XF;pVqhSy7B zA^~#+B8Z%l()K-AZlTsr4;kPyO2PQrR(IW5D_|v`APo^DbJ0~^g zpcTi&{B&^e`u!`5hf)Q4Yr5@>He1}P8mOe?WF?IP29&kM6I(kSCqThUDGEBCF2Wrc z6W(G)4DskNtU)+^x%Ex{D=dfS=Qn4>7rIYqW@U~Ms!iMQUoGIZwmbFmn&|PrW!5>h z_h(^FM1$2k2$pK{%YP|AB9CfSG1rkb2d{%e633oDb;TrbqU^nv!?i0(3^*+QeETmy ztP>pwI;K$I+cHgPDQQTG-6%`x<5xpW(;_#lUq1ZQH#+GHAx1Ny9?$0AdR;E#&3)bd zSk8zezx9U$vVwP<4a8GZY7m83S!+I}@UTC}_I2#8WylaP#gy`Nnsp#3EMgdW410O) zLx+U=e@(im%@1(6n#v|VgHG`V&lmiTy!@M?A60YPlj{+U=|WzS@2}w(zI9}0Do!lpac00e7Xuft-I!25&0Z^9SCJ^HX-NmW- zn$EaG6|;YC$SE+8B;NcveINHe5J4^W#;?D*#`uu+S&(Ek^9XKj2iI`-+GR*Xye4Vh z?R)y5u`eb3=Wj$^HGaclQnsd3M%D2cXPanDzOnpG!K*cw+r6fQv?SKPzs4A>udw~< z`Mybe4AsM>m`(fgRFX{LH=?mOmmdL$7R0Gh=4E;dzmQ(4sOcKyJ2ln=xRvrD=i?$2 zjQ^aQ^PQaRIWruaiu3Quw?BH2_NyA48jl7=yS@{$k3|$MCnI4(X2E)qpPcHe6%qWlH8r;cBf>=JHOhI;7Lj5CG##(Rdw}; zzXOw*Ias6=R}xk?*L~DerQBPTpx?zM<}9Y?XD(csxwf1r>NJ1aUVi%a;yfKa8GD0p zl6vlw{ZIGai+ZY;Z>+6JcN8&a(WKB9EJ1z zj!a5&adChZ#OdCTPWWEF!Dw~;&t+h1!SNf_5+;|tSn9CDL zElg^pu~UEW;+WY@uMsoE4;9*rh^Y2EAu};pY{l{>V3Xo#VKy0Ta8d)TL03conuw@=(?CjoH!JcfIc~LvL(+ zCJPgL*_!Aoy_{mTWX|FGd3n4^$#1pc$TP*V`tcXFto&@KsmXm--aD(@q@$t|(vkP5 z8gJ%XjPH`rImjAxiJ73NvCovf5C%_ne;I?QFm)M^Q=6bY?J25`7RaAvGzvDA-*<%C zRB1Ie9&matx~<^2375LwzU`h0JzK9_OTXJR+~94nI8xTi_#HC7e?sCg28ug{bS+E{ zC{8zr8V+Tim=FO3hW=f-oa%1d_Rk|Duvp^3d6<8u&UjDN%y`u4ed#E zpsSgC!0=VtF3uyP*ApUc^H<)!ee0B`KH_=eEi|c{kbrdJ$r7{fr*3@n)lM|vNJGFa z$mf`M-@pM!M1YkC+EG#(uG`WP7pBiI^*SdlU1gKg{Kmkexqc({3~mu(_{THhkU%~skN*A z#gCpp_{lb~nG*0sd6RePtC|3RTWVs!G0JCrMIOhtzK+)Iih*-mVuT=?@XuF-CRJs{ zF)=_BS(cc<7=$KO$Mm>s;hlBvF6BRs1qA7_=rQWD6m<2thsCCJ`K`}F{@}5v9^$`- zk3-G*YwwCl8Ja;VJLi7miPuMa%PrI9Zi|}cuO~XOuiyOb>w4}y->4`b4sXR*Cyv^0 z5c9u@_ZzJV*Cy029^SsQ6-4>0^75xn{1yL`#ewI~hek4Z7c4mUtA%tc8g$Xs()pGa zqjaDV;z;4}W+{_vU7hgFhm+@_`D>~gD+D8=Z+M-Cb}&lg-+fYL6kxzdFkS8BvccL= zIyAh6hAaDG9@{v^YED^C_T%)kBv<0kvIMIZAG`F7QA$eUx*PSQSL|6gZ&K5|GrQh( z=TV<9k5XQDAdxcpYyC8rv~7LUXi?jEx$_UBA1+rpbk)ryY&2LFlba^$d3l5{mWgdu z9eH{>OiaL>DIKMAm;oqt&FZ;qR~_qePk{J8H&u!R~v za@FwIURIHneF?)u3r1@mc++hK;n-?xtG)WuDY83EV{1NKzLfLsT}I7vwcaVp>sKbw zoz0@Wib6QH?q5xEbgSM@A5>=D&E5;pEAwo%+3>6yV7hrzVgNNYJw2V{wBR^3{X2GL zhW)VEOZ_5`6!fE3(brFyoHUAzh)4r$Zkm7-r?89i(nl%e)XXr}#P}vZ2Tbzeze)TD znRmfJX1(3m$H4j$p`l+23LROI;ZqA=zz~i>VFK! zmefqxNC9Y(K$U)$Sq>qJ<;P{;Y%$gISCJo~!<9c#B#&aCYv1f4#pei_bU@Ve98wIdU(xO6)SA4%gn~^z#PUK@icR+5=s{j2` z2~a1x$@~oPhMnKB7gjdW0dMyVfnn+{C!4~~eU9+npD~Ot829FC3G|~Vsxf%Dav_hq z%T2BEo6D+uzt5A4cDjtBqA&=uUt5h;L)pvAcjqe!j0)f_H2e1L%M-ld|Kr#2arL*q z;ff$r6P}UT3HmryA5KVjs(6vZsIp&qT+@m~Fj!AHyqdHK2J%>rzhpvYdEkvO0fW~I zB+DQ>`~~_Tl0Pgr}*g_1@*k!B(lDjAXmC5B2q(sUK5#EC(*oi z9`_)?=tn!Xklsh;KUgSJ;cnp*x-P}7VaE9R?7`fsQ9h$Txy$SU@mQFT@eyIz7G(dp}VVLis5uBUxTk%@wYZ!IwAy@U+PQ}Uul=Dl3H z+;~yv@yK!WeprvuNtpn?A89gBKP~hwkd_`7TS^RVl6ao`hBronoz z?S!9;2f`60@$T_pxce*x3<6;mdvzIRaJ)G?@tXbCvTvyctoK@w#SbHe^jN(^5_T@% zRRYV=R3`B^{gLUe5wU#%KAte+WY0v{7y0Q4QTWSt=Sh*)HQZPk@lZT^+lgX%z19Q^(8Iz}FWa zS?>yM*hSJyaM7d^%ETCMxC6{{xn1EG!fB83_#cP7^kB>%g7wnm+c6C4FN4(xA5-tJ z>62ola4ta0^mHT#NNaa^uX8FtCv0emZ zxX7^?mGN<>VE7CB378UyOoFRK#&YqfNXQ_gYF1Ce1&LbLu>TObQKlm{h56!pta9+0 zd7Smb5-ZPj<+$e++<{-0FM!hpR$a+40>gt)GKNSH zc~blp5K3wdTYrHkWOSD$4D-eJ*b5h8lI+wc!(LDsMWbswOrg=ymq?IFdJ0QSZTvFF z(A@<-Q~ij379Xx3rlX$u(cEsGd$Z<5MmcNd4TL3pQn3QK!h9yu<35(bGcyvK{KbL6 z@d8zS#RWhBzxuYOV#r+plX#puRllEw)fu1S!kf}HWb***p``%@H_R7j{@%2}1&gg% z>`fQuA8S$3hYMLJS{KHhf+1P#i3|#e3=>vL!iCUnh;t$rQbYt7qHd$}3b_z9fdI^f z&{ANuwU^A3tPz#J3YO>K^ff$0?!?oG@Y;b#bj}FVV_r17S0FK#8P7ryZvII&5oM&c z_D?RrYxbuD&N?4F3@s|W+A&|}XPR(4w%~a1?Tcaw3U6^)W6;Ch8vjPZI*9Q*jn^}0 znLz+;clKSWF_%LJpInYU;g0AIe>J$ABKu*B4!E5Bc9xfz%gOqCIl+=s3FokvlRam8 z<07futqITVcs{4$B^Nd!p_)$#@6a2)#4Fnk9^#K$Ls89AA(Bbr6ohx=f$H{KrI^)7 z1%YJWwUo(_yW;^`SGnW>>sPy6**p#VSxv-#Y|pmk{F5RqW&9$i1qzA&@Jz@($juol zvoxP-@7IslZf$`<$i&7b^!;xxU&wd`b5?UC4yt3l{0sIfC0wy?VHuyoX+@AbVEdG+d!zo1T_2D|WP#GxFRkH);FxXqn@W_7poF0Rb(T z*f!K~BtFPIRBoQ`{|S#>v*?PIb;6+H6TfxGXFe6e?!qA$DpP{cRdnDTV1$ktgzkK? zHLo!Uoe7#8qmk?s{|a4NvPIZmp&P_CT(Hh)QXouYThmtR{BwM#wufLw=)Xai0oT~Wv z_!);dQD~K}gwf6wDo(8&cc%rj{XZ-6G^;S4P{Qv7TEocbEes;#ao^;N<@J1C?YNw^ zP{?32FJd?GI%EHa+PEiQ_Q*T0tfWV+CB^}{1H@yHyDgV^Q)j?>b{9Qw*7JMrx#8^h zrdSb=`fujr>J+SL7JH$3+C_%N)y{KStZJ-m(2>43qYHBRJ!IV$c$e{KT-5qYS=+n5 zqTXI*D|PSG-Y9v;9t)N(7X<@@saJzp_pOcnsu|~74Px%x4|3Iy(kR#fUHbJrqqDM- zpKJUS6-R$;;y{eONVQAvH$9KOJo87rFJ8Wsm5UR7V69!DJX&2w0pk$^Lf*mXqeR)A zhmxGA4lRfN!$s!|Z*^A@&s|!&FKvqV2>Nr=vtb(j5r^+>wcK|Z-3FeyfTBiLT?c(~ zB?Y-jA8~SI{8rPIZe(~EV;`bVw7DJ8ApgXAU;7ZNfl*N(`vP;W{Bau-!?O873GaDx zLtJOvvA#Z4HDGmioeGw#SEVk?94gt?p+ns|*U%`vN6+d}M_ccRPMzY}kIn6`ap4^G zY8AOQ!M-q~VIcC!zr3*S&}C!ak7OisXM@UO-+$#Xewk!p{p~K{y=O4yH|<-z&}`_i zZ9lZT@EY=|kdGf~Z{yGT_<|%`DOK8i^UVv#*7$8!Ksixo7&1CiV@4*3tzzD*&aXEe z@K|ourqI;R&2=gexfWh<4k))BPtNW&iJNe2G>(e)@$MK55`6k65GF@uPj|-7#@!^7 ztpi7||M{!9GS@YGvWzqIeG0#rWCtMV?{wg#F$V!>Zg2C(-3P}`o?N@uw=|UQXK(MI zP~BvSUjenF8F>^%XK2669JwD28k{S_9Rt)nhByD#=(nXSx#Q~0b>_X4>1z2YzQ^}0 z`d%c)p&!0#B1+0f$?J5v%-X%PK;@XX7>oIfy93@+${p+jUU3Q@ z63^$e3~(hAK48F~NqU}jA0Cp67VogO<84N)GQG%{M3hK~ed)e58%E=t8a(M5q5<aXx$#6!sQP;<#g9R5GaW8{-Ab zHSF)d&5(<;X3f!s?AV#CNIru&I zgyl%(iktx3gvgWA>XlDFqBSi0vb@=Zm@;8pD|J_Tl)jbCinfF!BLhtFj$p>vrxVVI z?&=~vKpYWTQm{C2`bgEx!GgWh`PW7x{ObYW;i>*4!zxozt@Ee33~|7fN2bJ^kB=el zJP}d)bK?00m?3SK+adAi6VSF9LWACBw>*0v|RDqf^46JRh zV0}kUMUyM}g2eoHo7k7#`q7p8#1sIuDPAr7eT&4b8;7%1PUzWr@i}aiDBct-QLCmu z{P-(_&SsUM_KrNu`MZrm5FqNXwp@UDn@i*jRGC#9l(FMFh1TWse+>Ifb2JJ=Ip#(s zWp3RH87gkqPKH(+r(n1a414cVEirjjX1#Z&Grs0KUfosvT9`VVQ8_8_y)d%k^KR)loIZn1pAS)$-u}r8C7Iw9trAAmd0$5o7%ew}B zVBsm-H`EyS=k`@B3n$wr(XX7svhZQ7q5l>hF0m`wxpSNIM~7P89>3Vx^`9YE<>1h!gY744#XBZ+0!!&@o@b~AJp7M%)~ zJP(^)q{KX?=UUNUm=dF?S0VXdx1oN+S!81oCR-Gs-ImHUHb0Sd(VlWGq|4}#)$g{l zEu&=R$=wSWd6fI1G?I5Vc?0n`W6d; z`USSO0@dOauV}$uN|+7L&OwAV)WCB^rISfkAcSH9Bfvqq;;GZH$PIP_OROynE-D7{ zfgamVZaP>?vI2EHwS<$@tkKf5VeSFv;k}0X1wN_qC%d11h0XVNiSpsB9PM{G-Iu>V z5NBJL+T4r_U|8h*c!O=X%PN>izzJE-OWM;yp7|?^Ww<;G8LMM;dwpe=ro_~)F$tc_ z_;dnqlS{r?u}S`HF_m@^f&%bV}OF1TptSSwcnqo^x*tyLRp4DmOU?d;7OLruzEzZt)vy zagFTS+-f1ya_FVX6{oEq)*ai6^0C~Vn@!a}hcldVQ)sHt3n;a5QL4ETNIr0Etnt{U zmrJ-M2?b#DcHgu<*c{Lwof(@X;IUBcl@j6n9+le4%EW{kAi6kF_F3v7hC!pi?5uV) znx#Y-G!0?U`FDjj2!mR_%EXvrw^{x&XlW9M?Oz7nayN9`!UqP;k3FF~pa53fjlzfJ zxB1wtM(ZEFIO(dQqU`jM^ZCNd&3D(5=)`_{=nWhxi>%L*O-EK6AB?!aT7S*P$@j1T zU7WB3)j!i^9RsTfjz+npn)rY*~Fxg;4;}w2*y%!+FDl0N)E-(|B~N9NM+AbL?>} zECTmlkkEDanU{3LR)~Hk{$_#F?$6C+;o6v48I9-h(C=oS>CGp_1-{sMSPCYIj|O;p zb%G@?!P6z>e+9R&tVa(H&Zy8i_vc`c{87N#mIr)~Uh->V^>fqxjrSV(i+HvAJchc2 z40zAZGJBm8h!({4eD1@i$kL9W z1ca4ZAgG-slH-V=zCb=h$cxvH`1lgxsuIA zl+QyGTbPvYpK=};5!O5c)Ww(u`I9I)yjXSNHvbdc4z`iY^|dlZ%7>=5`~8!AB? zJ@^Bic=~;0LEoZNtHIMn;k}HoDN;A^zb}cMNJj%gm+c{Qy9@U$7;1hew(*rCZo#Hs zi-%v!hhP4XuDwEpeSMa-4IeiFLPs#uqC)6@AG|}=w|!Wo>mKa(?tCvVvhqb_WjqG) z64?9kthbjq00nkh`~96~94NMHl5@d~5KDyTq=`Ag2&fWd{+Lt=321x{mqB54OEBzP z>omfgB#3%nNdx7M&{K;MuFRKNi2%nGxFQ~%?ri&abJK!`s!i~y64@L%Sf-W128xnDJ-QwV&U#@+T+{Qr7%nsooXBqB zg0CgS+$T7f4u4P*eBwxh;hQV)JCq3MIUxyW9TA%QE0UCf;Gr|#kC14DvA+7Ip4p>u ze^HyNGw0P-VU4EYwJW&E$OzzoOHK$A6W-B=t@dz<@4;ck@emm7n7fZ0A|gfF!Bzh} zbWg7WnR4e$iY{V3`3sj^bJk$qZlP($Ti-j`@Yx7?+EEP1!w>V4qwtZF|4{@Gc3ZxB z^tj4)sDA=JxXv_z=Zs11)Il7y3@o-Yu#kvt!EtGdT5)L;=~a@AE4YLaI*iiF78U{a zC`o(_OA_8e5XQH=r3lV+=PrN^giR;D=K-d6EGI261a3l4SirH94-Qx#V?jWD8>@*f ze#&H3Ahh#Ck@dDQrf7obDG9qd(jg>Fx(l29jGX`f-zMRptIonf;>^GwlF4`JFxz_r z4EAdC^mkE&`*0BM^Px8kN4PKVoe>8GxY>uhsD^>SvzNK;f8VX!_$G9grn_!?EZ1YX zifQs7@cc1i55PK54l3vZ^SG9@57jasjisR%Przv-UwT`WTqx_a`@oxT9) zKopSWe+dv=>z$xmg!}-1E#z^Qd8lCZ86OHKl}H~b=qi+Ohmjnpl&qL`1|yPpfn%lJ z3CtK^u|UL=IV(<5bPv=pj{1X}QABuTtD-x&Dy@VQ>*Jy{; zs5I`imw(bU|2ujvGjRYMF>bf@$LRU~2gKCeH*i)>6FoBkY4C))i&!!5`jh@EwHxv< zkoaDdu6+=%)u1BzD!s6)qMMtWc7sDJ&wuC^dc7;uT7bGu%m)T39p_}|Ugeq=z8=8%F zr$~i3PDN@Y-0FbCs6$60o~Grs*RG!e&t`msX^j$9RaHG1zVN2|M}hdF2XA9_l!l)VnM?2fTkPAk;q+i@H0Yw1aBi`i^L$-{_vdhbxd$0 zBk3z8K)LXB-2UPTA&U92H{VOd@t%R%g}T~+9?;3yo$Jlam5&#kAE_#--<@aMSWz$X zm}tZSM(Ok~6LMbszB7|(YqNhi&DYQ!W13)Yvz(EZ#(eRj1XQ^f)*tRJ0Ak_2+ne~f zj$mk)+h#;_-215R9j8G7U=XoKCZqG7(=n!C7{nO1-w6PEWqGx81pQ8sVi-kT>VjW}N|`ysL+dWilnGCiCd>m5ZWai^t#%@aHD<(gyBChp5@^bJ)xjU6moEvvS>4Es`pYEf&3E~^r-D^jV zHJx8he#uw}CA@QM!n&VT4|SR*r#12XT-%_09`}1|0nzi+s}aCUnHk|t$g@$8*bYm5 zTy_AxmLO(fyysur?bIE{Xdjfj-g@-SM;&q_TT8>1lb?dBm&%7_HTT-t3e6zn8if2U zNA>Kb4Z2zq;Z>jdKa;0m#1cu4K|?*g_x=+)LC3pc!s~%mQy$d-9(>GFN(C=RgN&$9 zziy~@;D@`@Mv#OZM`xHhm4r-hF1oP5|*IBr<=RNN)J^Pn;Aow{0?C90=AGS|zJho%1#Pg{B2nG{>>*(xk76eZK$daO* zSHUW)Qo>D8qEUxl1R^q^Bke+f2s$RuybvNh1Ot%$2iM3VN8c6ITc9jJ z*VRB1qtxYwBd?_)r^VNwtFt{eI{D%lT^0uN5q-J|k&sa0q#x{y~=YCm;fi!adN>6#Q+3i(&7$!f8$(3}DGnLE^Dds+L~ z_bx%L>H9YYKT&wjeigQfWB2dLb$cIfs41$LXXd}94t({hKlf-a1*&QIB+6FOU$`5L z=sk$azfPnP9v&X~_H7=divaFmlQTu0Tg|mK?OlRp-IE3hTj}$*TK}_Fg5EOLp z18#qJYD0JF99LhugN&Z{ft?DJ3BBN+dXc@mTyyWff^D|t3lfz8qU`pzwmz{VwC_N^ z4uhi3RXKUcIk(yAm$Miw@-_FK`KGtK7iY`H#-_79;+gqPFgKsqddP_0b8+nr>-Fn_ z!2)NDnS`8$b<`^@MkhKuJ9{@Tlo*c|6c`kSUXx3(iKP~FEP~?|It*3tIFxv{asYa- zsaNBu8p#?N3FTqty{49^0+EbbCz|bN8#Or=+7weWQ!tI3Lc7i+e@E*pWoKXcQcQ4I z7*~TqsacHM;fRA!%s7KcY`R*mcTX@yF3cE`>^0HVeIL^ha4HIblm*!;`q3J5Xf0zg zP+C0aSEipKmzd6*uaj*vIy9YYLdl_>qn7J+Yw87Yoi3MYr`lf0aLl3BPxR)TgX5LU zl2#zMEq#Q&56O!}U0uaPv_hk$O4li z4KtH&;<5HfwuK+23Ez+EcdOM4^;4a#brz=MYA zZ2PC#b>!l*S+d;^&>D`v@!Kh) z3|t~b4}U)}Ha2Dv71jU$f~i#~$jPI9D%{Z)8FIO@6%ImQ<|`=f>m++>_p?&-7Zpl- z{;ARu8#teLY{-476#Wz$J7zoQ$C{iT$gFG%vdY&mxgEFrubA;ZW`!&Dk;2rDqrKgBzkqC zF3Xf8ezZPb-)*W;Doo@kGZPIH6nhRm_pB?_daiHJ_p7kS^7Y2q9x&jnoD}!Ps}0YT z+WWLY#MDoXJ^+I$p?x4-6 z;6iVgG?fVN=6n-1-?E3wHzHRBT0aVoal3cLCC#C7Kg@s3X*Xu>g`J8FpWr&OW}Q*3a@2%CDZ<1&Gp`GFR4XRsLv z^*0qYqIfbs46&N4 z=j${bC)iZ++eLTecb1lNTMw1~7`Z!5u+n7&RDRTOAP*Trw@Bh0$xeMf~-Xgo9L=$Hsg-nyHrE5C*NR#ntdQ z4A6GIcU!!cfflJLtlfXMTyN@Mb=ABsWe2A7WPc6`8+}`<{}I}c7S1MktN;qjK!2FO zTs>UfdmBU{~?b107GHMhS$Ay?M2-cXyO z)icp_!0oXWmgy&HLC1sTPKuf*!!g^{dM?jVpR^PF=9)7qDC3bFlrA`ZjyYkbM{{dtV9GQ`> z4y~`WI{%XkFqwkQet3X5(t`0n;AO9uwe z>UlH@ii&OU{S-vkvc_D0S+g4y_v}|7!Ts%Jk?C;-l)CxG&SHszd0$po>EkcA976sy zomlCuuiKMrv_v0uHXNyWKeb9Lx8B=%{viq_ZT_f4U!wNOFB5PGhsasxgj|mUc07~6 zQa@VyB!rSv%clA0_wOqkUC_6FLvZF7pRZ1-Sq^1q-2Cv9@rA{b;pmkg@!aOIbAC=A zE94f`rTFX@P=&emo*X@>O&+d8o9vwZvEGR$qwryE3KgP+t_Pc$FNzTbHHJ7s z%OkgL!ObP9B4}I<|G$6@coC3Tr%wrPZOV`T26*|z`&8tq=V@?@9qoq<)ju-TlG+i# zEuj1hm^Zr!3-v$5)z5sKO@;6U<9pzXpM4qZ2#;JvP~OLv?$18|AA)Ob$H%Djd$yK@ zo~^w8sGwb_?=@Soo!HZ!)1Qm}VQRoXBG@kzBEF{6CchQugSw|TkHPW?qrlauOfx@$ zXoNT5=W)coG}0^nm%|PIuTsis+;0%85nOn%G={bH=Q^Dlvz}3{>NL{Sk}K>29*9yX z>D0sqZ2)hjA(EO0ZH|{Bi@^t@|A$9lK0Y?#(G*VjM+1<&!*~yAByrRgS^YuD7fl3U zK8EDukh0=_h8vJh3gNzR8*YHqNSOjx0(MAnvh`6tB^F~?m};JQfEYenjHG~-j^m3xLH~DAQR&1Pov%&pwX1vmUs!HwidH zm>g0@$j-t0e%z`DWZ9=*xg#C-$=Z|PkPrv+6$o=3lC}xPrX!&GY2F!)!dUln+Q41Eo~I zy-I~q^kRY!Dqcmh@adJ;@sV^Nx&^W6x{O(E8-KIO8m^pAk^}HK&X8P&@e}O+B0;(! zaNIH9KqTJaA1{p<|7}Qe8q7#5`zQF}3;z?79`PP=$Ooc^>R7PWPuzl!5b#v3B|9OkPYQP@qV{Hg%F8u{{ zpJh^a!PnePSTEd-fNPbW`_yfX*VX}zYXaxvt%+hHMN8WX?V4V`sZGbeZ~cl1`5ySb{AeCDN8{?ClNeOsG3p$ z`?)u9Y}(G5V7&{?1nBR`0Ede?blyeehZC?!cQ@w?G(bVcU<7#4qsD^&-rLs&1nz0* zKB=nTUuQd*r21H9)~yVoJkFESD@MMPI4gl?x0$&%vY_Wf*5Se4ISBtR&uah1g16E( z1!h0ufz-?IsobX+fB}v=crGK5M=f}B3sXOgt$?n;*?sxG&rIkdPz%zZZ8X#V1RVud zANKxK!MABRFoB>10WJq6^OBJ39(v&s+XQ=_rd$ zS4!Zs3$b|(wFc`6)OAo}(x800@IawIV9ZZV2t!K6#Wl@i0AXf(+&Te<{~ zqKD~+Bx%Vhy~8AumouTUL|ao?DiND_Ymi;v@DY!bRAB=>RFpE18khLsv;eOMQFv z&?v{=d+q(4;p7SL*P-;zwx)Ah;>uHoS-5s^;ZNyUnUIGjZOtP`EKKcsd<3N%J0 zPX@OtovAJ!5_)Lslrc*?9dc3sgb& zT<`SkU1^gTITBjCAc!*aKw8V!>mIe@ZMb7+4jy}QnkN4-;K1VIeTkdS6XEcp=oeOq`MU8RQqYKYZj z``Xo~JEkr!@5UloNOOEDwz~d|MDS?2_S&OEYDM&-$Z?|-(to=Oi01smPlzBu z2|!%U%!ec|wkdQ?uj4J7_Pt;ouGUey3>T)8ntXpky(g!`)Ut5LK16fQjXv<)+v33zmble>d#|1KVlfhf`mE?tn&a+o>rBOE z$&sA>e=#o}?my@`(A))~`H}84x)uaZFfNo}_rQ6XAyUlU48L}OlWm09xY6U@N=RSD z%2g>G8;Dx)Sj|RSj8wh>2sC|ZxFXjQ_05=|Ry|3`X8%(P>ZT5#x5`@;FkB1KGgJxP z$4NvN!-9+=LJ7I8G!@u&cvUR!f3SCOC^KF00-n5sDI59<_@-idFa?si@QS9w4B)EH zt{xp;F0nXamOz5gk3ew&DCV9SR>ikczM-doS_@& zF=lg`XuQZp55(%l&)u2POWz+DmD>wi5n-H~<~OobyWhBQo}JuzD`t$M+TDth{C!8TC zYBN&<6#_M&nps>MwD+)WjTR)qpmxzTrH@+W!QWK^h$j<#2h`25^G~86N_C*3|Jllu za|MPrq+}+#i6$K?68cW!-n$nx?{JS~>i4uo=e*N>D8#bTegE#dRx;pNP>(hPi7+y` z>V<0YLp*X5HD-|gxJggq>fo@Us;HUt@^TEM&W3(l7??}*skj}W>a=M0&Utaf_;9h* zs=M!L*rtDwSoE_0El9(AdKEtxU+$Z_enoB?k(z9GjVDX2a zEm&s<4R+XT$RqDQ-J{phUljSr3;7p6F{?m||6eUNMh1Yl`!sih9_N%xU2WFKn=)Gu zWVYC*+eAOp4$+AmU(-?u8=qje91IddF?-n>CLaXhtStU;KzHDNR zO0GT?ni-Im=24-QKapU*Kdkqt_|pn83Usl}`nm+;c^o#oC6_s=>F&eAb!rW$wyVX}XCXcB z-gJxI4IJB+#uKXNqwIz*>Oh`y&=#imPcAGCeq| zKnIN;s@zaG^c{;&T0D_zp+!ov3oc0tmcUOXMe5Qt4zugpjVBl&e z%WSujYY>@J*VV19Dzouyd6j6Tzeg+E=DotJ=qYwR)R2kBUa==2;IruL@2VJEs#)<_w|8)Axxju%vnsEy zto+v9Q4Hl@vM^w)?4_Te%$640U|3{5{LKd*lJ~;sWzS@Ld;2oGpUP@^s!I7?R_1nJ z9sKx&*lIdX+JAK!7aRzSxX)TNM%kTFL$pwFi@vEoxI$Kz=#R1wls#s~OJEWm|5_zJ zeqaurnN;RM4ullWj0kdVem#ZzlgKp}Vg*IXl6_f9#}R~(%cP@c)$%!<2U0L4_O_Qa zw9B~jb!zmFcGhY1TNeZHXHsrnxq3CnGU^QA$vv!IC#;J1gqV5 zN)u;gO<`oeIFnA{>FwId$w^v<60~Ox> z-0HgPNn6vTVEA88m z2?n%`!G~I6J;v8QD0VOSYyR4FqD$nu4s<{3`4XInuF@f#m5CrK+J4y+lsf!xYchiU zfUA#^C#FLZso#T=?iA3mkcRj&P9Yt8`=tp;)Uo|i5J1rrA+)J9w<>zoi?`>(Qw#Jf zAkerh&v8s>^DlvXZy+_0Cu2zyc>fO?}25PRH1 zvAxszYeaV#Gr8f)g@ADBD2$i?*A!{&2fistpcg~}_WX$b#8uE2?4t*}tucy_VcdlO zogO!>?_5E&Q2viuoZOlzJx&OfVV$a6ivKQEgyC?E=p=Kb_R=mK)TvD@@$RH4EGE_^ z`p5+$v$k_wZ&1GoC`uRe{@hz(ab60uiMh3VHUM`~B-O5G_SSz~1mr!T`gi;3? zKO&K1zo!(O;)4H&tDnfe6L>nR&h*ZpIi|4fpEp7najkxSF*bv{6gLN6EaoM(1JAxt zXxdl*j}Bxg`Jl$Vahti^w-SCn$)Nc6@2-Zy&v{SBzCl1JDdcz0|KI&C2F=yI20Qt1 zGFhk|N`S9Upr@KiHXx<^<7s(^|KKw>I8T?t&ErV9r98#N>sM;NK0+W+qc=%5o5$hQ z;Nc#UktTG5u@%OZ|6gnmww5>i+yNKrB|-w`bMU*Leg3b07rby{#)(_LfBf-2x3pB| zX*iz1@jm(c@ywl09|tdV&Yb1s@V0+?*ACt8mt6`z@4TJQCwvV7Yxo~t-N=N~ROxq? z#FKV@6`LoSCeS|^2u{5CI@T+poAV!k`32R>p$;jxyLf6B2SD*^|4 z1SF!oD_g#eaq0GyViFbq(F0yc9>`Z&E_t}q2|UoKnrHs2{BNHf`ptSk9*9ND%a~XJ zhfqr&SE&-DkZ%yA%$)4y3yr|wKH@B*xTDN=!nXaOGhI~2KDq=zhtHM#ibG{YU-zVc)$3NsCBqh58mYhoAa^79-}V17 z_&}cTTf96Um);DX3E?CE>50y11at3#XpSOs=emBsf52<0bItk8=NWO2d)y<94+mJe9s=5? zzHZK1iC;8$^^3ZV9VO037$39zvd8jsG#;bDlKc)WCf^e_MAQg9TR z>WB~tTo>V}D~dJ-M8bM}Te7%b3t{XUY|uUkero^7SSNsI!3eeN!O{KZG|g zh&+Y$xRjD^h@;C%^21|qbh@#~t$)C8aLH=^6s!QKn@X_y{-foZjttMNZfrVs7XlT-UcwrV&!M>7Ut-M;ZHA@o1;ICi zdbT5A636Mqwf5d+0Vl|Gm=2_%!K zJ2^V(SvGOnH=iWEem0qE(x#D>m!}MAukhh2f2QU&wTBNM%E`$+QB=%bXxB=FQ6*m= z&YrVGRejV#`dp;%X^?JH=+;fTmx*cKToHJtvcl7657ZMLvy&^`_lRy!z{4U6{3+s>=`El-XX>-TP=S!QQv`ChMT&d<%6*Lh$qNCaKP zwt?WretWfB0EdwUURkm8NRfq+^>E&gSNoR*bhFk(UjM6+l?zez1d8hFf$NJ2FT6_) z8xJtGO-wD`axgo?=477xS4)e`&AWH?Ys)u=w2K_0ZYV@2!@(q?bxy@(Jo6FyF}w6{ z<{WO^I+^R^E`pg=C$)kv+E`aPFJE+ol4s6H;iA=EeD#f>wULw<(+)%;=$vLK#%TlN zWb+{up%X@W-!^Q!>2hQ!LpS3WxHk*nKUj_}Txe=)vFb@Sl6;Qz>l~`0zx=b#Qz}z0 z>-}i4h$=M2s;R4ocRm|X3A?GmJB@@=Yb{!LZMV@nC0JA4SD7D|5 zBJ%m0&AbcUbWJuzUMQj5@&Wle>fL!o@BdKn_}jl@nj&_7s<0~6jeUL0gdy5uD1IpC zm7EVaRfC?BR>*2oY$lPwdRw*n8g{g5|>*d9Af*vFjg5BC8B>(*VQYI+ICM!$yEosb)Xh1e+W zntc_(1@P#vaF*XobgZ*FmiDyhTO{Afd6{0QU{Gn&wo7^zRrAK#ayRz(jiO?4qO1LC zvob`F@Wyj=WUWzE9imo{Ut1#eDqma8rsXUTPjkE^bX*%HS}=oN?BzF`?+#4c9Tp~Y zZtaiRh4|V;o%anJ$@C$oc2L>$YF>MySuNS)?l)1Pa$98Rytc{n`K=e^h3-C2iP!k2 z3r-HrHomoj!%LaZ_zq4%#*KIh>z~o6qdY{TI@?re^Hwr6rR{Z60mrj+thkq+e+1Jn zf5XcLA|6c3W0P;#D?8-+4fA5`L1=ikg3RRV&U6d&xhj~{RA|~6o6|QjHIZlN?c~3` z*)P~)l5Qw*wy>E@-b z!vBbm(Fj_J&e4H3O3r>5gKK}{6sMMS5Bin@k5kXCgoH*WZm=js?7WU3BXvGX1zJ9x%y#Qud?dY`3BT+40mNefR2qjYV4I zHn;n4zuvw+`{{R=K`KeXc`jHMc(vF(R#_TI{>jBbXst?~bU*pMDEw+E==}w%rzc}e zJt;eLAuTpnHzgT9gE0#3Pq)SyX9s&xy)zh7c~l=gnJRLaD=JGOaa%6e?!k^P2SJsZ*-QEOOj+IjSQVSW?J;fVf8@qKEs zBcB$(le^G_J6f?FF!pPSK7rR7($mSwBcF1;A1(UW>F25jP7f>Qjfh!rtBS3nH`84ILeA18E969s0(C>Kh}Yg$^J`H|*N_a*#cmTd=ke zFrTL`cjPMSv)*0@buNz7CwF$5R>v$G)MfADKfIZ(u8%v|F+E2htp1MyjeoFwz z7_@O%HyxB=!~QoBaU8y5OCY^%O~?>|gVjK0YK%?A!$Pwz^(^(AUtj?(H&E5iI|CX( zH{jND-E49O)~iCdPVXY-)V5~|FfmC%8SCBa^>K@anbp-*QsAPAU=L+$rLd`{MQ*`= z8v+ZHVq5dOIi05?Z{lx7hBA2|q8yG*26Qj==v#r>!$QeSPU*NY!p;g8!=io+9f>RR z8Yh+wljn$*f1+I6a@?y=lYlh7v0i;rVd%WB#7$9MvkQ^W6YaW6+f%m*yF+BrGl1sM zjYUS2^)5RkCZ?uKZ9Od~cf&k}^sLxotMkdw@L&TtHIr}dD*QgJb{ea&uekXo&Ah(B zq_wS0RaZ9~g!M3N<6_GJ<-#<7UaQ?B)%Q;-Un>+_#M!u+h82qsI{c;+`fOO^Eieq* zfEcl&+MQfTp#551@>soPlTmbm{ZT#7BL7cp$W;WO{98!yB3r106z+)p{IoIF-6 z9e?2$@3cTweR^`3sgmjoX2qgGf91XH!-QF^4WNn*)S~{Hq|@oV!yhj4xmjVGeX~O)Y-Q42won!c|EwNFN zk(zpZ#(CML<}}duaGsb(`NfL@py60zBUToN^aMb7fw#X|LFZSOdw_}-mMzq7`5e&_ zuqQM?w@ka*+5dM9$VTXTKd4*DEhg_oLA$-t)2IDOQX*>t`rB>(5jCenU3ur{!E?r`V&cVYYVpCfQFdgN3UGILE7Z$`&ASs-zd*Zs zmiU{4P{Fe;XHh>wSTF=veiH;x!u4Qca|JN}`Apj*F$aYNFu;q- zw#DNt-mm?Ym`c>?fmQ^S*t39zUcyORp^Xj<+dS|LZW3A9L?**Oc-JZzWwMQ zN=$!cM*k{TEAjBNV51JACq+O z?gLR!pn*(cJC`)iX5;m$V>rZKhBiCe^eeUYKV1WEJUXhcavT2`xIRV{ac=wO)o`4*(~?=Quq{kxO~DB)$W_BD|Us^@>+ud}ND{2PMHzOxra z6Ghg`8eKQmp{=XGz(n4kPHeR5&KA%yd6F>71RYl4IWc9-ZxoN_m+8|&uxvruMf~`e zYMlL`mNRr3?QFKtXF)s3ja9LegE@s5{+}7?@pmSh{xHFyb${Vyo!PM)beN>o{N7%S zk;SA}jw!E(|7t<>frN|pT$(cuHpz!@kJ71j!cd13WgbRs)R`OMjJcrkWtD6o>DbTa5`AVGTEEe)2 zOWd`8A|Rj)1x5U=iqaRQ09oSG*4E3{t)TescdY_iI=Y?Ai~TpEBR~kVUKs^(nAiqEOK!1fVBzRC5vNab=N#id)U-kIxL)lAs;86d_n5QtHVS<8SqkSs; zo1cB3CzBGg_3gG>qV-)#@St^4KvO6l1N8oT1>`X3?9W!rk8eDyAK;c`KE=Pk^t>1r zJuBU(^EXIR!m_c*izPQ+|64Qe@0|TSyiYN~49^Q6|KvLVTWbjc?{cU2*`086a3#!B z{)5HT3dc95{L@B~k$?Sn3~zyi2$rYkmOSyVV2>fJCz!u~1)<2U+f4s76rO0z!5bRm%ejQEFul3-K?#FQ-08VMh7|)B;;&~7@Hh*9 zfGPwBdnN~~IAc^enaFN+BJm{1pdcqBBco_+ z|2>eEmhh+NUHD&n-v9FYsj<3%Fe#D7ZC6P&5m4Ye%14<1*2;aFM-=owobDU{111C; zRf!k={)FVMVI)qKp+p+~KlTg>`p<^r!^L{i{~m3VsDphTl1kTONcxwS-`^Xr4pSAP zi|wy_|0MC-m$^D$6#^`rR&Hg@udy_HRqw;3O16~P_!$08`of5vF;8($63qORfZE94 zayozA6~l9=BG%=Is@=IEBlk#dt3UDzba+v+azJA^&Csj=8^!zsVr8dHZV{_0to5+;7iG3)P_siWpo zm6jD_1Y9;IC@2wNM@1#^-`TG>P-G=NFTPXqTVB04RRrJ1K?0;Q%%V~MR$DrzgDS=e z4bQ#59uRYA@sO|`9jo>FuKuabwts)>m4KtZ9}Py4xnL6Q zkI8!^>A`BjOEQR(b^Wj^hCw&~3?nV zi^x<;@Y~rLG8)L$(}B(#5Y1J(*SBP0ISm?*Pp?WaFu=NdM6$m9!^(l3g@(z>S16EVV+#S9}RA)A6L|aUkYvfbNV0Lz`(g+bq0M#dTvzFaK#OM&JpW??l8c ziAQKy(y(x_NuLW(z4@KGxN}6v9d&9w4QMzsB*Nh}ceX@+h3v4~oM>+QUBs@3^45j_ zC&I^Eoh)?Yh77RvD)C>5yfR1LJ^sl&KlgT#uLDZY&)B+eVYA>56FFAKjj2Je6IbT! z&)~q7;-N`^`o=>4-5BWSrdZeJ<#)~#I7RPQxdr%a2k%=AS?BRR!<21w2;VnI;=}#; z`uo7f@+8IG-Iz>;;PMr0u1DBxY!pv2R0eJp%kaQ5Nld}p_tbI9y#8mCQ0iUcYW)=;vtP30(GQ-h?XA4o20r8cw}660jev7y~e5GO}Fua`s= zHt@3m+wY45ic2(~l_fOJ0cakAI%%mCrkmn_rmR1te|Nmd0Z2OvzYQ~M4sBF=x^gsl zTUuJIhP$sVcjs3V5PtbhNVyETgqozJWYqDOGuVotd)f4J25~u4(%X5obi>19rme>a zgPwNAjb=TbJ*SdXWYn;hj)6(!xr9Df^p=&}S|1gIIvn@I42F|L9k11gO7 zFjU$Xy-QzjT|J9n|4_XXu5Y;sf?Y@3+)AD$hz*C9wNmq+1r)%BcaM9`2-OhYqv&fG z-DP}-oQjsfQ^i>p*UPkIOFalU3^H&S`hQtqI_>&YQ#jbB{O8UH8T)+^w`M{YN+bez zi5$r zj~W27aLAVVZTcku%$zxAi)R+yQv*ov6SXdY>iR3_Lm`U|#J3ue7!UCUM1Ld&M8Qmru-Q>tm=aJw=x00Fc!8$Iq3})$~u5Z6yYO6>`P>@TWQ@o!lG~473{8# z>`lJWCW{M3C9{?(WNAykInXt}H<19^|4F5_9`d{2p^!i>+n%xD3|t0>I-K7gj-F_VmHgF-{s>AzgXkx4L*j##v{lT z*-Te5KkP(Fl|Uml#%8@`pDISI9J!x5NGb=%5Y=2yfIGG`Evp|%{c{k?Fu(d9)KNRD z)((D0^&xq>$}UON;X{bY@+5=Ivlkvx3sfwC)d5m$ou3+y3&_wVmSMU26Rs(goZ8s{ zkOco6@fScPmRNW4BAtqA?iQ)06GOLHdSTBA? zCkfTt9=^-kW9T+za$MY14@T)Hm&!DXzx(oR*gM%U2@&pYOfA zu**B_5!e!Vd~XUy)z^|L2a-9i>qcdb5m zRDGq{hNNz7{Q1qo!k1F9LNR#36D!aa)b&;DH3zYDQxhH$(fd*jYI{`yTS|k&fH%*d z=gCGS2B3dEV=jHs-r6ZS{t;(xelo~p?9h>kq3*K(RUwV5&7FFeSUEWQHHR7-iO!z? z6-?FK5#v0sj^{9%is`|d5}WWDAWIkXm9 z9v4T7aGIW8H!#2!#z%FNd?Kb#b@E#{ztPeW6J2VKsCky3-S%>kh~^1Dk_-!og)k-L zi<}PcT|pCUew$HGXhPN)3r{z*`n57d9Yq!Oy{*!7$tb@-xAHY&>*UpJ+M2Mxry1I^ z8AcNJsXU#&iibvpw0UF3rTmhG@uuz^Bc%A|Fz#G~T8={s`?>)EjPyuLv^n=?Sru(+ zY&^48ijp|-ryedh5*QzkZs_^(Fg|1~O~FcZFTpGbhW&NzuaNfROG?IVj%~QlOw}`4 z*6S!Mf9NexF*Y@RXrViyriRX(d#Uvw^iu#Pu+syH)~ z`h?A&Z-8R{?Gz^)+q-*)J}c!;f_99E)}>Ld0jjF`8Zl+MVd{D7$`jR8hKsuoZ!5!20!xOW(_1)9!tuhjC388N|fMfP`DueeEoI#n%0Y3$`^k z0ixpzt7EaAG=~N?7RqH%=EEdraFfR_4t#3X^!a+?MR=VDiQoC*eP6)L6efAXnW*rF z0AUxtHZD|XBD0=5;bQWBeP^ZobHi}H(XChC8M05Um_RNR$IQ&Eito_=`i&cgO=j!^ zUIJ`vR0;0+70o_&YPd*}78EL8v&0pbOSkN80IkT>4;DJ)W3(@4WE1msn*jhP7+^0a zp(;kO8_eyc`mZ|%Hhp_o&yKS)CHW>eEX@1E+2FiVjj3D_+171mH632R)rs2<7i29g z*Z{{Pl=zqkr_Xv)ebCO%;!}`~WP)JfdEFVn=p#FW)s@zfuNOd98Z zzeCA=!`vloOhCf01*?>TdxQ5(tBgW_0y|g2dp+&sl8(Xh*xshI^PYwhZj`HCG#M{N zvNe^kQ=9VI$_XmRFAUF{Vz+KN^M79smyx(!Z)KKV*Vh*>A>lz!N*1#DBRxR_=GKXB znVCNF&dOqkCP#1on35+4sQP*=GL~`gygU@C>f^_k+)j;PHe3n^5p|1|3C=fp=Qxq` za#58hNY1VJJ4Lbr`@esGkMgbk+0&x*X)Vv%L&w20|j_&>XtO3%e*EsQ%(Uw>rr7_c_B;?_}YI{AsP1;QcGr`0>jd@_O zoij|q&T{w?-ed3vccb?7_=>Ot!4umaj+0Xe?Lp*aL8aGbtb}aLVYR{gqN0p>;CiX# zL#3{|25I>Lue}YTn0%MgAiHQ}rkBHR_E2|`1zCIaaW1(lupJv$kkwxwqt#g)T=tef zdU=6z%$^j)#+xIH>`~QL4b0gO(d!cqnx;D-w1UU<{^EVbPW#m{_cHDJtedJ6yvYI0 zP({_WId&D=$sAuUci31rA4#Tyc5?=LR=-_1l~lI(&}pe=Gw5maxCi+4E=H}Qam!Z< z@<`=|>pO$=@~rjn@kU%YiF^s|@&Zv`tgORJm9Z=E5QK4zJ_^{TN?^iXeqeR%aQm&@ zeumAF^TrbzRyYc{Brw&@gTd~DI%d>*X2XvkK^|(C;IquFl;?Rg$iYlaI7~Ky zl;86BP{TXI+pm_CKkSl{5!H%l$DMEW{d2;7LsR`{4+YG!XFNaU(bB^~#>7ygTxU(n z*CxU_$4tU=EnQ@*h>l~N>9(o`T9-w zw4eR)}dfoUiNCKNQH;x*G<+wmf;lFDJUp}n8^zZ3V(ljBaqHNY;oV8nBKP`nNhc` zJOH8%w%@9%$ksx8wXM>M3OYI)d&(+N@1fQ37%k$j<5^6tE`d@`+a)hteZLqzA2u95 zy=EQG@XTD9yo#Uvnc~1Rd}Xu>I(luUDA^NM_VFzt3Ruw)vOKs!P!bc}=~vaPaTVHK z>sycX4+S4>)E4MCb=&#$r4AHE!{}}!oWO_Efa9%29F~TPa%5WC@|T6B|NXxK|)=f5)zqr zOGc%gGTSg9D^f(}$M%aRVDEbGteiHFyj)yic7#AHRdao`*e~=IBcWsK*^uY!u8cLm zO%<34?k$Uptv&pbmewltK|Z#Tz2JjW)J4wF0Iz^lmB~9qfoY<=QCNuP`~b#l_5+?(?$h#bg~8 zSH7V1{W6<(*X0T;+Z{`W{h*GoW%ldY9LCpModZL5Xx!h>gYfV2_&IjXb71${K9I>; zOu$m`wZK*%tlsq(5<$6K4dFjz#3IdmjonOH1v5KAK=!q04o6*D)z04shSW*mWC z+FPI4gc?yY=!9*}@2RFMPVWrq%R*>8U(!a+|EvzGDeNN0F!hXr<6_$w|xJ|I;-;^}yq z4!5-5%GP|_6;a1qP-tU%hcwq{F9t>N7b}amjhgEQ1}HW+H#LjQFA>uUUtjsSHM3s| z$Qw?V*dERJ#Kb&}G;MI5$^!t3O;~Ql0wg#6G5B-}LR<)9rBUaNn3&ekN@2S#M#iyW+Q|lrLrE z3NaPQQs?i^El$?Kj65QKm3--!k`nr?DvEXMG6D4mJtcV8z*!-F^#mU9h%w2oL$o#A;UhLW^Ikvj$STg;!>pG=2{y93Oa@QdG zrY^@Vlruooy6O3Ly?u)Vlh<{w@ITWm_R<(|fS4sTBe^;~!R@MQ+M{5zvx^H0a)vIm z%|uSGZPn@zdt~V!j!;ukeT0o=c(lK{6&1Aq=qUT9``Req5tNk*0uvJ%jzG)nh3{#*n`C`|!oE=LeB6Er!(5x8qLkWgJyMWLUPDJm=eYHQWguCPjQ(1vQ&a%Bke|ba zW~2a9|)O&9s!uj|NRtH8mP%V$S1a&mAq3}pQjbm78pYGOV|{SLgX zw|$~fqwDuHrdwnb+_pm$snbf5b_XOhyT|6qkMne-NMX|Fb-fY=>`^$a1MMHZG%ESN zsT7k<1EMM#9al|b9FxgOKWD@hd!5=GpvsM-VP{B!yT3eFsYcV5?HV|ryghx zlz#t4K~?osmOt2^Bfy&M<)y_{_3`BL$c5P#{`h3dTsDV%;Sbe?=8YOaI8QdZ4+c7f_=2S*&8xISb6Jo}j*Vpq*cZVtH zBpdf~u=akQLJz5s1-#Xx_Jx;`^u0ijv^yUDWU5YHHHu^k$4u$w*1*He(pw7j;PTRV zmc{WUW0+V=mvKe?**lK6W@`29okg^FjK5p!@VbN0pUSW=J0s&g(*@t0h!|cA73(n& z*y9E+n3yXtoEP`m#M>Qo9hWzF@evAd{60;L;vh*X?`8Y0B{flY%v1nF zA>Z})izux_uv(e8$SW_OJ>zh7b1ohCh(c-N34-^^$B~6P7Z+5j(FCZSv8s}x=Aio<>=tyUADR}L{q5N!y=eIhY zA{&@_NH!+nxBZIe&Yii}eVJKL&mn^{?_m>CakP?CP-O5pHVKvAYgV7=)uOIvf4c@^ zh$fYcvl{L4d7xJESsR_%+z0xUP7C?Nq}tC8t6cVM*xJBn_<@?Bnsz6PVyv?q)F=lh9{70SExiivk% zfpYOkZ;8S0j@z&Uj}MB%FTr}sUl+OvA388>JPBR$C83?vIJf*8V#KQ^Jif?28%ClE6xl^j` z{li4eLh4?zXI6eO^oVurl}F=y6J=c?jUiLbu1B!bqAd&rIbs+k>gDULR0&b{LsF;s zE;+uI2T&yp=hdYuM)_A$b)I+5^7HVJ%~TD+m#0)!>Ba}L-!Xf4ouc5$5sO>yO~)qw z#y{LzEu)|MGway+crG4gzGBY%dEcujMyeSbchJ*~7?V36RSn6H-TSj8 z5(=%#8PZwOr^Qa&3o4qEP+KX*9|OVB8PLg$SUmQ1@@*fJ_M`l<`5}voswz2tI9dDI z8_wob(k2L!9up#N4*~gBH$cJoV12Sy{|yqXS%aQE8G8QA3j+Bzzm1W zzCG3!z%oT!2Ec0DBi|Tx26zoc7L8nnWVV7MgX;snMu6S0MantrK5ru^p;B)e-@O%|cmr(dkdp95+ZnO$+ zFIq!X@ahVn2Uf?o7cM-kQ^6Qc%eiGC8v>Lwwy`~FcQ9NepiWa)0rOTbpZ))EMG@So zrQyW*oIGK8ph28aInbQ4(i`u_{T0By+aQBIeT>o`39@K|#zL z4&XL3qk?}18`LOOdzTe{i@4E$MFp>+o}Nqc;jSK_nal)J0$S8p5V03WmODF#`Pdx& z6~@bsAEU2E2)HnLF8{F>TLO(}9LEg{$L3W`B>5UX997H~Qr(8bU`1y)#R^MdgL#)J zSzd5H|5ywO1*$4P%4#32LX`C5;LXmZc`5!7PTV*+O#@U>(rWOeU!U9MsmpnIMR*O# zMU-eM`0d4;QV3@od9$gW^F{69u-qf>-=Af|iuvQQ5owPVQXrV@8G#D`~iXgwanq%Y{MKB!H|9jI}A8xUJcV-_~Mz zjybf#oL4Aw$uJNvJ~ewVgeMZkIIUr+%B-soec%N>zyS@Ssk!0q2i5u1uy%h&e7o7% znb&cVYVSfq^3Pxo;G?D+eGI1R+#*FBu+o&=NvWu`G%I%nQrh2N9Qov>#T}=NUdBxs z;aAZI$C<_bbbG;Crfq8H*RT6-ZsH&aVP!H39#-_%lyUp%l$8BQUELV~hf|7*iUtoj z45)FwwJBayjQT+Dc2tZa+eJT@oy?0LF0-gqw8o&AYLvp~X6_y?DQw+ETvyh>uG-6H z7aGJ2WcM7y7Ze#*Ve5XnpZMhq&b-OI%(DLkCKCG>nr))SZ2b`J>|7#gxlEDe zOV48Dgp)~D1w&8nK_C$n8tTPwt)24qD;`Xbl8K_BFQCFLL-!q@BD=Y_ zuL!@o1&rQHE{w$S*4jouy;ngC3kpu1{2;qDS|}1aFYzn?%F(Yr?t*M6ZO}QR-2lzJ z5)cp|=yrrtqqFTj*PG;hH%;^w70_ivOlptU&(ny!rje)Qu474XecwXoa)nc;Pb|b`M9%{orAdc@MUQ#m}MQZ>(?bTLo-d=`KBH2b{k?2 zjgVcB2nlIcFH`H5N1K*x&egqQGO)D`ywrHUer$|vWMs&A-hM?uGD$hn0l99zI7`Ip z8A=%OeC`2ns2sEN-m+&imw%p%!&?se;d*))EB`_8`4uUj3m$%cgdiaUK)pY70a@@d zKol4WMFs799A9u@AO0_a=tQvQ)z#S0+CkGQC!cTp^Tls;4p#(8zLMB)?%#C>dy_+I zW8ZxIP_mMe*Gy~q(g(R38f6(=`|)6B{Ww1s{8%<0Mp={;69Q{blKxFAIQXI z+@+U1@sCNPdh6+&qlmuxl|3gKOj^#ZDCuhih(bK?`g2KsQ)?qUHd5~yws~#7ohOqsbyQ^>mS3X`m0>XSOB&Uen3rc(nt zp{G}XPW!fWGK&3>)Px&R^+8Ge_@0fv`$qNbR9`M92+pW_H+d6LCrF%SVG;e+{bql8 zg?=DzZ+U>$Mj^&vG52_@P(9fdjykW{4}>)X6HIDI|S1D6H%8w%vkB` zq}GFpJm-*fjSuRxWFz@eTa`J8H1PlTo5YE_b~Nkmo~wrD(l5E<7vY*s zhg_NsnU~+3o=M%}>8bsGAm+3cx>{qcW83a(dKKYhAI|(P(kSPhguRzT>shiq>q8r3 zQ&j81?w)O6fG3uv-OBnP`Sf(-mwqt^aeTAR*ob*>PPOCkqpM$CXRbOuM<#yz1{Ud8 zc28+PRAaW*M)KFM`z1Hv4akM;`yo+Z4Vz87KFtG~yQ%AvhDCij}KJRJR`?88(-23F-o!z33y*RfhVK;q!RW+b-ee36efNl%reo#)p)FWkouOXan|KU^s4Pr|x zjTY(d%gAKN?jKlj8}_B;yx8+Ys=EjL`t>R_Y;yErBO*z{2Enz^p~F!4+J}hx)1^iq zMi!Rycag_LfZ#Q}b!lniv{oDA(}=Ag$z~KOowPy4<-l9XRLz5>#WAh*7jY&S15~$6n9nnzO zKOz`}(HO0FS^}D;y6oMp#v8N-l*WHJx^8z|)iUd@JAtB8(D!ZT17^!D}^tH7~;n{LKl9%iKens`R>Is^CVdyX+w3pEy2&DQvL-=Y5}>EYHt zApN#cJt0ErUc2E0u?wN*5&61Svf7kWQv95fQIfX_5IlHZSUvzG#GU+76?9n_1y$EY z+u_5FYVTqzcYyR6nO>iqryq1)cTGTvZK4}h(Fu!)<`0(|8LqE=PHnS^F=<*0Rnu3e zr~;qW5L>=IVBb{IHZwT^h&cB>DXCk+uA$AG(RX!u=@Z`6kS=zyWUlyKKwYGC{Q&HF z4Pm4PIRySw_jnEa6cqy;_mdumm6*FdZ^d~wUUU{(HjuB4bC*f^Q)Bf{{l~PW3J6oB(Zg*SgcQInU0L zJ%?T(R`;(o5N%SPcv^iz8O0-X#SMJ%Ec8tA~(a?{Ccg* zO3ZuD^Ke5-%FL|zdhT%4zGCd{ucPZdDl;bRv)PJhf@yY}FIJvrYi&;ojf_x3JnXJ?oDN18Y!f%64havZ!npgUr2hnNc@6!?SDnCps+$D6@9kF}?^uEDlF;jjjnj-G)ur>D)0sv<9+#~IpY{n8f%F~69 z5e46No9{*Y7FsuHm2WT9_)~R>2Ew2ggU;AeW9oFJgkbxt{I6sXh;?f(HFVZZ$J31- zpSkKf=y05#p7ty?tqtI=(S5hS--mbMLS0dY($v6^X`6d`liK5}lP|j_(7Rg=;KJ_# zH{tz7darBV=VvB_wm}C=vEC|xaPW*#g?)d^@&3z+8^ztgRK(P{@@yxsmB0Qbe|F{_ zcmBF`^Tz(B5rkPdFLL!-#AaIzx=?2yD0SV>Qs)HEbmr(_On)n0VyrDS-%r$Klr(pS z#ishxbkVX_$_2>BSR<{I$xtq%_alWK!Uhu~A=$gmW2P8#Pk^^b1jamg&CIlp@v5Si zv3O$`uxKGOTR!6_4Oz@5$mfQ>mUW~TLY0nPqjyfrX(syiG28w@NlQ9=4G2v5%G4jC z4he%pUJ}9Fm*(Nh^}5})$~>pG5=+S^6jcy9FcmtSqVlLJ9X6!B&k@~PoEX_^R#!cT zBq@uJx6Yd>RJ-F+Hp47_dGT`1-bwOU4Nt=N;oS$k_$Z^>b}DFsdSORpePfe(h8ce(#P*1R5 zdV~n_8k)OyYv~MZtr^2WK_j>BM66Q8Ga-sV3_kWfm^G??WopXfBiXtqN&U>tn>QOa z+_NLNAByd|Ut}uNxlB$$nN(IrPR(zlZo4y{Djy`;T9;!1qLE><(Ebv!W)V_9RxD4_ z+S=BQYLQ(pV0hg&Twq_hs{f4e_*W4N3CWbtw{OPfVK1Bbn;bW)&1jr|;d#d0^Mn?$ zDW45@8THrU+4{xqrs%7$*rJOzZR#bL#)Xt~S9aX3gSiX06%xM}Qa-vRAV;0Qa7b@= znVsnx${9W*4izf|>;Dhdjq8KDMUov~b{@|26? zH*VZeV|Qea|DkiaUSxe;p+MQn&(Qai+Z`z~9G~(h88@73v zqgG?B0Be_iV$56c^5!J@5EgCLl^~BB^;yCprGT`Lc?4EleGxr%Y8#w*VH^-ZTxRs^ z7TXIZra+CWIfC9Anwjw1THEXCQOno6@Nu4nB5~ixw`mtp z1p&t%%{I#=-lR*}0nQ4cj7wcd{~W0m6Zs^7v}f6d!?U zi2c)_5EVLP3U8x~Nk(qt_hY~kQxAa7pnW#?MxBci*SSBC33@CkeAHtmAH!cuk2Oo( z&H@Lpd(z`{=<~3a?W=vfvdYSo#Kbc$d$b0m?@??qY4vFedOZT!EGoz^KT_JW*OI4- zOFq01ziuD~k#G)6xNyf8h(}+MBs@RI+$6m;$a$r_+=)czmLE?iC08U1bBtV-`f)ke zAM_iW433g||4+gMw%kfU#j+Ij*1$tWAl#&0M4tQ#DR?G5okhk#$&Zf(G=M8#h6A)d zjD&{Ds|?0}kXI%g@9*bz+Wq34;w|d4wbY~O6fn7jPGaS_*-#w2vgQ6{oP+hg<+7Sn zNv_<9x&}}}b2_~+C5i%Tvrzlhz^&bDmd&q$BoHfp=8(t7lX}R;NPlqACe)rf^<;c= zOv6AU`FOW`05{~~$fJU9EB$P^wsCn{kC+J%e)tsUEt}wR`Obqk@V#6UoZjrB#B|oo z0pbUBg*sAPq#&itY8A_8L0}xo{a`mn3R*c%9MtB-g?i8Z#RU*Xl-v>u4(bv6LfG4T z2WTAygQ~=M=&-RM{o42}ds*oI9p&>|_JW!~9 z`mU!C#Wo;~F0o$u7~pxb^=V__|A!xMa?IA|mzze)lV}pYAIx_ojl6IDa+aFEs5Z3h(s{;tVY}yf5o6!+oGJ}x ziKBvT{^FOktwm7$xUGD>xsGdlkdU-Ow@Ha3q={DG4MAmFx_yRr1_yn?099NMy9)X$ zZTVvnc$qhCUdvz)0E|6Qb&J*Uw|ojWplwK447xUBOidrjCQUy_DrP-0@4lJaZU3T;^ zfwpK1S(oXBeCDE@917LWQpSChCaY3MM98h?C`&8AL_L^X{UIGRMeX3IZ=-qnD`a^>efJWu8`_Z$9_TtM0>e>753pKdB-!u|dSNM?nehXQ4Np z|H@fliWo{d6l2qnQOzb}Sx!S%QvT3gVN2)$vL%ieu1MIlS z{0D{fq}`7&d?0E9#L2>vAFzS`RfrzO#)`^Wp~g;iXKmFf(ooV0zCzSg{ytF4~ISguWpu~TB=GU)B<1n9hR~+>Md#q%`J0yh3OV~l8Exg0-XD@}Z z$sz+gJdle^)PRnz?x#Mp&J_DBN|jN%Krv=%+sQm-EtWgWg0VETB&5B#BSJ$Y6cp@Z z*%`TVXy4h9Z^U5&TeeT#zg@~nR6$$5`}hvh>qioBmT2E+`0IAsBfkJIvf-Hn1?r^ z_<&Q{BMe2_tCp|tV_(Jidjt)6)lpN=WdJyrKYj_}3AN!&Dst#o^?rsagJk3Jn!kNf z8&AUhI*Y4vg-T_4R8K7Zq&(88i4P+rZ?im;`oE#%%gM*%5%~E$~Z{gX= zaEEc>XR;B;L^EWa~XO3$}m`0vVmrwVwn+^6zo`GCmhQ?->Rv z#5lB?IYY4jZhYKz74`B=@qijWqGzyPN_O7w!YBfmBcGoNf%`VX6vg@Ncz!AYNBaM; z_SRuhzFoVpG)RMBPy#9-AS%)lihzQoQUZg3gwh}|bcnPdN=S={ba!`1igXR#-3@z< zKi_vh``FL>eed_}{m(duj`!Si-Pg6^T<2Qn4e{CAkE_2C%s*QXBeuV!Pj^^txfGsV zMyASg%c)s~#pFiHqZ(t1WyvL|5g8>9k`owRLz68m(PRrLZKz?X^L=XRJ5|~a;WNd- z&lw)5FeA>Ph8Rnk+^G0~lHUL|bho51&9IaH%Vzbp61rTn^59@8#pjvCkd~)tzTp;RUJR_zoZHVbJR69s5n}M~`Uh8C*}VSH<1dxqrAv zST|HqHTC$_^TdhgEOs=I80zR?)Z0xpjlPeLHdeZ^m1}~g;Wnd$-}1kZyOl3>8+iTA zaSd}&LHYITCDO5=gxG7q23#IK5$!K2vo)}Q7_h3%aa>7=hOUI1n;T{lHA_?7B;7wm;yd=K-aXS_cR6i%6D?#{`5QUcVNZxg zQ&Lh?r{{FjX7N_z@S$1YK>gM8dz~nrBjuFQb36p7wAYvUt){=2d`}&RDOFg4*!xVp zyB9t^Gt02anToA)b&M8{^32aCvYAWN*9VMF{PT^>OZT!#msiYVs-j|?iKPu@80X&`mZ3{BqM`CdvMfbD0r13=YB_3ka z?w)C~`2F`bVv6t+eQx=XkvmuxwxOZRluT~huLks!9tPVh9(k#u>-kIsAxGE=_rLPB zIG3e0ncFQ~fGz?LraO`@v*Sit&^?lhd%Cqu1T%k>VgczM0xCb9a^6u+xgt<6RG#r>6+m-X`Kegx{@uK zUm4C_<>4Ww!#W|qG|v+Bf}P`LSW~9&T76+FL{&U}iQ_Yq*MS}@2_CQSk}~Hk^cUb< zqh`bqrhxruO6m5yEcX3qXr9GLcDY=_w0NPQ4)+s?LO;Lcdy#aZTvEI}@Ua-W6yEOt z&Lg~v|BwlS`;{luqB&2gRW$v)Pin?q+6ikNwQxWr`H}}aL{=ld+!?<`Xxrm=#!iO) zjA7krn+|^riqtz-H)BrFVf%5w6#-h+%Ku5bgtrff1VbkjQcPP*W*>XLij*9Nj=U?ypB>=CWt zl9JkmgoaPl`(l6pfsn{ESe@^=bI#06cKXKk<|eRHoA+ z=y1$O`>Bjo(2(A!A1mGNrIr(Mop2k1Kv#~QObQvO+^X?lB-YYPkA2k0?Jx37j zAO8|ReEHiCH;~9X$1lY9+^)iAiR2m4JLeY!7@fQsztU9k;efuY|9*irR16TKUrBa$ z(L!+?qI^9KoRP#5VJn}`5%h$g!!dUzcwq!68X+;Qw`mR>4^G~cHCcGKxjQ=k_6--U zv^{l^EKc0#_mDx_2d=&M54Sw{t?WZI4w&PwTzzX=LD~-x5xi4`Wcbnx-lss}k%#5l z!8S_i1*Qd$6>ZU9N=1YVP%3@|xid;B{GrzokYVnOe$-+?_|v1{e-2cF%J>fE|{H{Z{y0zrh2`Nfik(lh%=xXP~fH+HIwcfdA<`=_WuPuLzySv$ ziddM%j0rA&1GCc%{d<7Ipux_x!~sl(7|WUTHh4x^2aSy*Qj#^stK_L;`%uei3MzOa z#sCkC4Y`vOI3U>$Z;iDXualNzCHmvhRyh*eHt-o|RZ~E&$BV{i48%KqqtInI-|9RK zsGg&_FnxZ_f`mZQFw_)a-$D~7Ds%4a1w)AA=()%zavpWKp=V0*3lHw`JinT{!DG%j`-;T2>eGLs9#=0LMoCEm-pzEi&0 z8tgjdb$aEM4BQ|OMRrk9()i4k6yS%U1aiys(W;-l!DC^*-A3 zn|GoTTiidoNuZ$^pWBunRUX0e`FZ?I`REo3gr9<3w>Ls`kmx?ip(U92hVOIXAJSz9 zt4L?_@3=j;VY@iuSZ|}rYv95G5Bji!CRCnYvi7?6G6j9!c&v74`EbmIF>(b7<{v{q zFZGfm6V1^b^#@|%zoEkcAr4Uk6r?ZzvD|tmaGea%#%M+YwVSZ)G9NUBn`CP=f-yW?Zc z$;YrTchBxQqTiLbKA^vLF&Cj$h%R^<&q|8eXsD)3U>2egZ58HD`zZAONNLW~h7&LZ zx7#P#EM1i}Uhuox>V*ONn3#y`?P4X2^Bj@&8oSNj1U(}{af|-UeXE&8hxVXQPV`x{ zZ_M2H)wg;nmln&@a<27Zli0B#L&cNRCV%WjX*bL~1FPw0{hz{iH;>(}6zakHxtE91psG%TxsBnI0jE+pt5Ye^t0QFa?V5&_D?4$sSOE z(y#-Ak8b5@w}zk?&8kctB2aj#wBA=Kjb^lqX<)XE_l8|)4R&+Ezx_^Cly>kuM0lsL z(--4XB=R5E7?bNvUd=#T11g;E=4XJ#-L+u0OY$EF^_8pT6&1gdgSjz}PbL7|Zr3Q( zIoge}>Or$HcGrTcC+dP1)tw3st&itQ!as(GlQLRlX;yg1(Fw)dZy5lvRZlv0hECg> zH^BnVr$NaJsQf)6t0#0klYVX&%52 zJk4^8%Ti21@#*PLNfMzAX}-G*FFGbh@x=?j&q;F3j1%b&U+9EzuNE5{vMy7xa%R&5}9v%;-!#z3lRqVZ3Vz z%94S6V@N7!lcgXaFwCbYUvVDRJ3W?w@K&O!TI~K-+r9F`p{Qc}Jh6mv8*5`fH2bSl zRNI%*GHD$8(Iqq#6@w2jW_-3K#qd$Sl#SByj+?JA>&J3{(8DOqOnK*yrBP2Z`Hc}r zmcq3G_QVWmXAdaR!~T|_7aVOxZaVA`Z>}Z})k``)vyl)kJkOvj9>D);jblHLFgKUq zUK%EZA_g><^L6DJ;*X4uWIH!ym;lBj{PDB3mfs9$n$;x56q%@Ml8(4?<>pyuTlc%L zMZ706L$zTI7plgkAn4FspjIyrRr4w(-Z3&9gLWA=CzDfE&vTEzQea3)NpbuOhh!eO zuQeatEMdp~;V}LSHSK!|v?0%q!GzJJwigT@McQMxY41PZyVGO8ZlIE*-#WYL?&@+% zEO6J=RU4A7n%D}w0A8iw#2fUt({h~y`Zmr4Q(L}@oIG3a^Nuy`PaH2;-Cw$i#M@r; zKVWc5_PV=JNkB&G=5oB&`GNqIpa1r_WR=V2gb(406IEpL;O(~M2M@db>>7L$lE$W1 zk%HBi5c}i%R8{SvtXcY;&ivbksKX7TROUxnE-dw#KAh`tU*CN5ZiVX68-+PBI!z=BKgu=*KeD(>kbCc$g6UWDyNO ze|Y9{4SNmcj29f~pnSSKF%dXXhdT+p0QI?o_hJ7_PZ+H2_hzZ#(0Z$DFNLtFIUPvz z3@ZG;eVZJ!JCqsg~AtrM<&o8z?+u|TcGPFc~fA_ z?H6%WLg+Pq2X38IcsLUx{$9P54rtZ{9{)WqbZ$6DX<%h8(g<(uSW9($`f2^)1mXUl$*tEdam+3J3F zx?R>5LbZp3M{=BZ9a(0Jny3Fw1qncaP*cF8+J(HwPoF9l4JXO7hRjGmlB7$!hLjk* zA*-pNI32(0+H@gg8-&joY_j0ALnUbvb-gyN2x~3{&4Uw6x79 zt2TC3PqKN$hU7tq?+%x9UUFiy-ZsX4ByvmzeS>8G%KGs14yqn=HGgPfp&AkvHr~Y0 zpUSAXZdLJLZ0Sr7)$T#Zf5Qj-7`+;IpFCc;OkADh5rcn?yy$4lDcP`~$U*&-(QqHl z!+F1_-_S+q+FGXjLe_%NPwPi`@zzgXl#3yKJ-;_GoyQ$L$(jy1_nsbhwUgc=E8{1Y zHcQkTM--bI(0!*Q85SPi3#d?dI7>ZsxS5&R=5g(pFH`%c-f`m#3%(2TqP{Q;q}fud z*a8CpCOu`#e%rgc#*Y|PC2-B3JYi!Zq4g>&Ls$Iu`d9G>1NgUAp%Z(19!15}J=@az zmR5+`KU+k2rcT*%D^GOt_U~?UQKt}#-PX*I1%{%@oua?h0Icj2{nI1FK7G*FQJ?tdJVoFne zb+9^6Li!GOw*h)x>lvCv;1W7?v9!{>QuF}9!N@hwWWW#_nPVc zaDF3H`#*|S9sjoW2@I_1GZng42khNwVMj#l7@ywZvts1J-BTEhbkEAnlZsuWRxe02 z(SXx)jhR%`XAhQ_{i@sfQgF(JKV2?Aw|d!?g@i6p$#5MP_YlS^ zFzHDi2L%Ajv{fyz!Z_r@%H-2>&}xQOT@2tr=jcrFMoe%|pvtDPv>7KFR64Qnb{n{= zGQ)FR_T!J%w}xzhIT-SLw5#iZ^k}w7JnJpCl#m*B`jPVwI9krvIP48r?Cb`Y20Ynj zZz06sER3P%y6Wj=E1nj{*)w;jtgo=a;107Utmrk0R{9plfYqOIJ4b;B9fdop&;R9$ zj3q-AtgfyO%e>u1tCE$9!SxEMFj91Rg{~)}FriNBA^y0Z2jA6dhP8?+F#{o7o7HQb zExo7nCbT~FPmT?WE{&G2>VHOj$`UkrG)fhUPB4Wp~7w6`^g0A?9 z_M3j<`lx&X<|8|_gjd&(J+~xfTxEZB_HYQMozYxxxb73+7YxkV#alrilE0v-_QgC9 zIr0}4_T2>!L(amWar9x_=?TQKmJIi-XYaAS6&EkltMRzbdMmSBmK!!5+5gj~lgCH# zjGbNWTz#g+;JVEi=8{hY6~bw$lOu1P`HwY7SD=Ml0^#Tj@H`>>R_#5HN=g#;{XTuc z1XZrwNA+;6PGW1Lr^oQN(fB#fx1^0y49?%o_OydU{i68HVNa!z=pJsT_z01ER~#|1 zBH63EB-d9_l=0Nn6%Mvi;&ZrAOYA#nxOMBpOkbcbO%p`0)nyrmiPncVM)^pK$XTTOTnnJw)D1`Ac^&WUsT_!@;cJ*ST^mYyfoPtQqL1}ta6Kl5p+H4X z%#_Duqlza3g(RDowKjgei1Pn4DCZd+gn>(li>2qG=gdPv1>v^i_hen6VBHkED#VR!BB*kZ83b>F*P(mJ!h6A3gvo2MMXVv#Ng7H@~o)iaSTkL zu-si%FVWf>ufggnkTFc(H%UCsgefW`u4j*mc})FsbFG|?R`z@Z=#58fayAADYKF4R zxin>Eq+a5=9$O3RlOKN%z>zo8m7wrv=*I*+L1 zRDqYAf`Tx%+s1+X2LnUq*mE+Nkpw`a#D?=&H&Q})+xhUb)m$E$P4e8`Bb?pm-?K>o z!{mvnV8LGdMjGCd@wwr8VcW@@ow>*I@>1$0N-hRj3Rn5KE^f_oLV75B+jjX9($T&J z&t`RX6&UhNmHSpU1s;1J`n4s2>!P5rAGB=^jJPH9^7lY5ZP~pUOMvVYwrk}c2G1Y2 zHJOSlXJTXBWMtVF#a>S~?BF=DC)%g8hB~=WPWAFNGm>3Ify!`0U|=|n+oZm*{Ts2R zt+r8+**xt9)}l!jEjeKRTa4qwgrQIBVv|n7K*ITsDDi7Ze(pjT~^Shd{ z?t9;itFC8ySXS0s-?MvdpX$X$5j@-xJFEn?RS?9HC^ph9O%$LY(&ICP_>W&yk zvnejrn+umETEks`oUV3B(T8aC8&Q<&YW&n^aAjo(^v`=*zq6dzO-@PaD6{V6_44gL zt63M(%j5k!{7(BuA2jY6vp;KyZ~_*tLflx|Lim%%zYrCr36=?x10}``wY3f{NWYA$ z4ZQ#@5;5>THrr#pYum8%OMGu4tE#X^ecjL6gG);~)YNx}vCyAdz3HK(s{Yj${9sPR z-GQ>TuX1NMAWQc2f6tDFe^zI{rj2KOQ8pfZ#VG{wDDeEU3>TRAs8zf;|z8W!-k-mSl0Xic=f*D%W|1f^mLv(^gZewTxo=Jger z1Lk^CA#u7q(~Fx>=l#?4%2H%aRB*5_)ZLp`Zbp=}we_ zX8Q0UJfNAeABXKR9z`z4Z56yYStU|FQ+epttH^|>HO7Qh7>yCk;H#p4Ht{cggy_3~ zESsuE$Yn$gXePR?eRRz~f4YY!u{3@tBOG%olcXQ6X?4Q)Y1>+5)Iy_5)^nc==)WLw zSqm9eg(-VVzXcNlP2w_t8T23hLbvnvC8wu-1s+kG=AWm8U^HWqE5-vF>h*g$F>OEw zlzR2*0`#)Iob+@I4iESK^qV5uN0==!m8^WS)Vj?mfL7pXOTXP*TeuA#=@qv-b|{QF zFJgrkFTCo!nG-)!slu=&h{Z5H@5{)@NIBKMzGrEZV(CAdWB1`fXKZ|PYilSU*<^Gr zyK0v7znMOzp*n8!FiDV1+It1%!c@{RKBqjR(v-6bHH}kX`4ubnL zXLN@PN0qjNNEpsTT@LaVXd$@dO~MeWLFP+S4@kmK&S?2K5thz*I{w>4w?5Fca$7Y3 zRaGA|r`K8wXU#GPS7<-}K^<;$YkL!nznG3BQ&q+t^?3IyJwnI~bv!pRD(;=oqu#+Y z0gV_s8#zYT;YsyXTm^b%pR7k7Ge$SStG#Wp9{q2Zeh)ePfB^T~w4Nz@>=q-&3A#Kf zRU)5#oHbEXV8L3x^Ql~fOBr`9Zt1k~^aO8vFu%jp-l4E&DoEI)K_)CAJ6lX7oR&`H zp~1S}=(#aR*Z2<8Tn~G4Xv*<`139_B@%eFy#b7zEs{TD_{HLQQ3B)!*KhqCh+N#h6 z`Gwz23EFV0GFy-M+1bbN32oHigt4p z2Wqu9XD>aezW2W{Wk|mLVaf<=Ex9>4JKxqjd|u*@DjGYvYFFvF(tA$~x&DYP%! zIk6!#ges$B^udVt(z2lY$m_yxOM}Hucq9^*?I$3KHs=4zkg-dD9N;ync`Z8gS~Ut4 z--qF=wK!WYBopSiv{GV~B%HI7GazgV0!=XPjt}8HgHv2q_H)gf2()_4?d=5uX?;EX z)7U|~Hvp#PIAy=|9dTdzxBQq-QrZMH?17V|$c?*7N=m=7w6zC@*}snnIbEZ`I6XD$ z{z0|7>R%0`ckgvP2*~@IEyjstr;dh#JDGaph4O-17ztFrt|1%F{qE+5F|QayN66L< zli^BI=tRI@9Kir3?xsW;)sLYu@6y{$SeIAyRm?3d@5sdo1odUSc>LP&{%**H3g^0q z`Z3MPQx;ZlKT4J9&uNN3f1YlV_oMV?4Kxqy_^JIp!&0&iwTEv-} ziSgkdc1(WTM=B2_YGiP8ARS!P`|4`3<0I`LCdP?-&?2c4Xt?<7>>!?nsb`5+ zBt(^LiF|DOCcL@+P6^X|FqaP4NRM86lOPvUnt)M*3xLNn&UnzQ(f#%H6fo?YCBoJX zGA5(Pz|FpMlp#?1Ti+gw0v-koG=rugc%l z?I2E#ziv(%d=T}?iX1va@fmoq)zt-j_{n^`Cd9ug4Cr;w#$@N^)s5;&G4k;-lfnK=9%{a1e4s4?GFA#_7N*3HM~-TqW()$|_ajR2`L7aeFDwxjh8_=Ki^ zYldCyulL>isy!y6wsj&8L)oYy5uFh2>_GQdY#dxnGpBK}u`1FLH{1~@pK2O=54HR8 z>7UOEWMjr?f`Puwo@+-+7Z}D&utH8`zQ((0xM6SO!S_#Vk{OLXV8F{GRE$OL)XpnI z&gX#FygApJqH4Kq#PKE#s6R=Qos%Oa*J1!_Mu&Uowg;%Z_4{PErvT_}?T6c>O9<;{ z%GlxsF+`S5aimNdV5SrVfpuYM^dt~^R5{NK=IvH5l=Ez*2g=*8-wyE_)hN+bXS*Pv z`jvyN$J}k;3e*eK$^L^`6ZV%`qxoMmYsiLa{X0P<(ub6qL(;$??8W-|%jFPDE|+~9 z-`RNW{rXv)g5j~>n)2oHhkY<%2s?|PPdQWve$ZIP0eMc2C3L+7#~i`@wwD{9W|Zd7 z=M0zGk(OlS7sEu#Y|vVc7R`!^=Jp?dac~vbc)bzjx`)s|H`hV;i?RA*PEOLtXe;9k z|2)f+li{i5!E4_W^^;RV!$M(GlA;Hs{;C!s>l~;L{fB6ysGy)`tQ89QRgJIjP{q~w zD_|l*xn9ZzVRL+ew>WrNBH~GC6u9p?#wfb})DggQx>vQd7D=EVMPf0KP2|UZX3{Dl z(uXcQ3QjhwtMQ_P>)uMH+34zTwt+YSYzf_ff0znb;>_urEWSh5K!T zdRbzkJ{;QN&9)KXZmAU;rEsLSjQ4_74J+&v<)egzymn*CQsJTRDq{^a+*D6qZ|yN$ zhe3dc5I4Tve~eDEU#ybQ-&5dpB6f?CUGx=$Hmo>vp|>kT1WZDzKI6;jwDu}%A8VW$ zJATSOEp^NNt#t3r2)fH0xcK7Gzlx1}II-CK_5w8(ges3+SPHdlZER9Zj3N_b(TA$--$>sIU&018&QO;@Bhs`{g z*7Ol@W3x+qt8&=;M?VFhJo#zWI=viCdtmK+7x^CcMyYPA5my#3*gT4*esi%`J&GA~ zEgnA@6@c7asJ41}h?t&!m6e^V&X?8d-UUflpVkU z&oz-}OtH^^-d!?)G*sO`fN43=N1Vuev-_QY>WglHt!QUKiJ?E4{Q9^_qt+_LrmR3<2A_)}3OdWrOacU&;827UaX{4&>m3F+g z3v)Ss9dmN@*qQ&EfKqOc|2fbShw1I?^Ib~>NMS+Cx5Ig$O%NMX`o%8Vjc@iDSr-Wi z#Ds*mkXu|6tJb(piIJ6X{ShM`d?-HH3JVqIA9^saeKW@{3QOG&lnp*&=NjKrP^se% z$u{{RZMc4G^=iAYUg%<9hUwT8@XQTNQTeh4corvzPf``9F0{wyi2-@o?-C`b;2Nb| z!oaS*06H3!ztX}f*BxwT=ZjQvKqFSrI%ka!!+d$}>L+0Uou5e>NM4JA24ynVN7dyI z<6r&!6l)<}rSpBour{qO;kPy>D)~0vH*y%>-tFX6t?_2zZD{0)<<&l%caP4wPZC@J zS*Hf$_i1E4To~3I*8bG8&$G--uV1@1ZzSnO@|`w+ZINef`olELL;qCT^8wZfG&Q^u z<|d3`jqcmO9sP`6qv*oo`Fj}Vnoq^+1#3u9K3z(8FavQ0YBX+BVPs9|KdN~MW8%fJ zX7)#JqpdrvYDR#9KF4Kh;6(+L)1%aiJ7S-znNN1iHqDCzE z7y2*X`B#`Q%%kN3WJa0BZ3sTSA;TAU192X{fSI~P5?GiR>~aC2t=4jzAnET(f+2~Q zK%YPnM1OqpI=<+EG7^o9^N{2@cLlf_h86?7#2~%sKr#sRrOUBW2QAO7(~JT7$&N0U zWBA~136K>zN)@9rn5c)Cs6)=sF1;@y5%He=Qb@<#1z}A5N1AZ)$@+V4)dX-!4t#7f zA62*-GA6=jk6mbz^B4TMX)4x<^S(5s%_GTmp*0Fm;^C9;DeOznf!0M*P{$Ey?G%ms zPWu?dacuHm!rhCFXJOpRw+^*mol$a#q(GIjlNbctf~|C)kZipXt+Xl%JM_>}GL+C;nclT*GY^GOEbhnn)`@elLA37^l5I8v!)5}Bb{d_R zsZv*i&S7xLU}9iTD#Lmq482?!L0SU`|DQ{K`8p3@BiSUNk^w(ZI@XqaA2g-I-NMJj zr*Vq+Ky&H%W&#ej2QyqPy2Rl723jnQVM~mQOl-U#7Bd}wy;!v#rrG_g)(Ip*IrfER z=o2d_MaMF3Q~!t&o9U()T2!VBM{-8R%~2i`;c^~^_Q%2la1875bfZMi^Op{JaL!!~ z?xXQx3@<1fx!=YfL5j^v(&-Sn%lKb~qmIo%N)77nxdst9&z61B zdxKdRYcyF^=``M>KbWNg`J298!@L8^Q6L!A1_VMNf_)Z<8=69l2{wI_j1ZbgPc ztI`~C$&ko@8`z(~EqA4L@xLJ#NvqPVx!loo@IK>{M)K2#mpL_y>v!`X`BJ2+CzKYN zb`m%xY^11UqzdMnCs2Ru?A^^)N>qy%2=h<;N;yVqaM? z7Z#-6UK6fh4OgHBD@(?zS{U6D!bT}ctmy}*_K_q!z`L0}f8IZ>{($$&tcN@YLumtj z&`%L$5+IOcyYdZ&2h4xp^p%#8>1GZRfm?WyD*0jYrh4x8j-}t~&nhF|)T!rD?j0_< zBz!Nmd3#S-e5z6QS6fz9!Ph5GY__A~Ra2!yZ{E}@&8Ox(j6>Pm<#)X9mlXF#yl!0{ z4*zzUyQ)uBTb58=wZv-X-rIR9X|uuKyRVNupA@cYnA4DN`pkmdZ`z5#%5#|5dgQja z(?9kbs_ixliSJ%LG{}3$$GPHzLT;IY5^diYVa>$6oT-{NonU@JA}NDhcZ;&<8}#&! z;W?%XzC*pEJ+02AWKcy;=}IAfUSf5fPB0|ZyfP+ZI*juLa~+N;WoRBGaGKlJ z*@_fZq(09`Zwq+Jsh+EurgHYGN@ynJv*|~TLK6@8Rf)1ug+_y$%BADS)}UgGL&tBS z0KLpz{bOw^pkLCPBQA9PMp&Lc&}Guq^X3K}KqVv{ba{u@o?bE)fx&pH4boJ--|Q*N zaAgnH4BC}#ZEaUCUExhOv|~?7N-_p5V%vTz7hEB`Ri!y=q3N5ETJ3}vIlF!*w$lBi zSf_4#RJ~+GpxEq=-m1Z%y2rtU_Q;uV4*fCQ=fQ|d_DA)B=_mn9V9hSg1UAW%al8@h zF5jXLVZgb!9&Y_(pEX0=_S(4R`^~)&iA|>`-(doIiXRm_2t;3qu|pUVmuyowAI$JO z^VpQ$<>oehY&%xr+I5UW$YZ;VP4i2{;%scY{_|NEfG*0FPWH;6s1n6%uBfZ~jox8i zq++vW$+l@(Srw7lQL7_5ov+%$KUGfrd>jx2lEkNwpi3-wt(^GD9q zjiu(`w>Hba`f$^`f4HU*)9|Le32j*%t4flr*vkQF?R#Hc^{6mHs^@PDJign0Z`Sv;Kr7v>zBlJzrh4uCo;J>)@F`eCToFjpw+xQgl}8{OhYo zq=0o8vA*ol4+ibP7?UL)M$`Vxn0%8#>GqhSX58|Nr1Obul}8ATLbDH@3L{`SQ{G(bD zSkip%;$O9R3QnTHP3$fBt%N06K$)u4wnH%e;CZ!QueRHV8XU-u&0BB%82>Q~QPf=D5| zcqJv0Y1^AsZhYoJT*Y7Wc+H32*kMJv^)Iu=u3JYL6dl;~BjWc&_j^`v^Tq*>%xErf z^hJ(U0WtC!32hs3PW#@;;qI?b*IV;l>V9BiA@Zz|VrPiRtrI7hCEUJoh6FSDRHAwb zd08==3}aPT9E*H?lM>{vzJWuemhjKT_XJ%wno33IJlvrB_RtWFj!7xvo4s$qtA8vh?sozR{#j?p_Wu!o8Z>{A< zNLRGL);3HU0lg~R#ooL!;Zt6AjZ{^>pSV@rhAvxaZhX(R7y=F+XH^_}oFb33Z258y zzQn|yIj=N)6hg{Y_ovNCNXv<|aCsVp}-MX!dYYIn4?a^z!TsAf0 z^fbHNa1Y^#olk(YTOBby+sX+V3@wAnV<631TJ2Wke8gMWndd5UWoY5d0qvk*<=;{% zX5L1xJR$iyb)m;TJqC8*MbJ_bzDlMA@x#1B_Z3Whe)t<3Gl?e|>Xu5-%mw%pxpevhg{LnclYi8Slu$j5eTg}sC7S#zF;D%#q z8Y6Cd3X3}JQAMmp8dzMbrtl149@z}f?e}d*?}cnk41)|rsIOe){h>DoI_CfP7BRLv z^8*Y7OO|4Aly=-*R(yY+WpJ+_YOx(zn1}>}KqJI$*LY&jyxiR9Il4Gy8hxIY@**3_ z!j%G?oU*+j7LT3lU?eZXSOOij^_0(FDvj?jd1_@ZHAFJx0IPg6_l2GG;Q_wlgCdg58 z>B)S+$(p~dcukCf9SwgYSoG~0TIlrRKQVe>aW%de3p(5QDM5hz@Til1w70^MFO)Ot zYfn#|!}6GX3Y2LNaLbibpZ9?~e4b|@8xkBG?|k%Zdoj)22>1t!{Y6EfmS-BIjH(iq zxRE?sY8pk1lufo<;;dVi8+qMrFyO}jTx*81b&0QEGiA0ta;O^ldpc!tI4S(pb`1-s z@?F2D9pCY)APp{PExqYW7r1gSxoWj?4k0wbsS*2QDYN2*y858QR)$@^(}A=rmnTy| zhiB4k@no)k>zkPth61w_$(}TrrNZ zm&}DbGVFIF(C_{oXyhs_odbsZhU|$!`48=;DpkHL_d#E$UG4EU)N&tyFt$XxGhj5j2O*$$HOq53i0Wu*M8>kVV+e~g z6_v;_1HnDe-BHBHz}5waK!f!*60KEROa4!^@5zmuY4cBR?|=P-ejQ=AqHz8C^_bf{ z2Hta&@*-0W3*R~wDVoa&1%9_f49D@bnr5N*V)S9j9+7ZAc6rSK=WQkI5wdmT>Z8Ub zuBAquA}Zwhk2JPD?qZhk&Hf3EJh0J-!DbqbY`}R^gSO z#tKVbWP^`+=y#i6dR_GtX@mT}FP{ux!p$~!BTvYZyWzWi;xOJls^)P=5+j9djj|w2 zz2F!Pc?sw~_W^vDZ`{ACyH~??5%d5RL^IUGg=n`HL$%8G22{%*Ej>$UWZNxxINQsf zvp>y)pG1mXJ~3~}3CV>LBp3f6Dfo}X{w5dh@rwE)f07Go$S`8HCFh7SY$45*$1X>y z)gk7u!+IO~y7d#LeGy?I6UE%PwB84WFI8=u2zr<44PEi~H0!69`<#*duX9z>)s&Y- z_9tp#Bc_TU@m}F`kzTb(eV$@sC&?fZOL<95;GRSPl*F!L{ynt#L?3Q$p@p&ca?vlxcLmMS(ZPRtfB$QSbT>=Je!7cA zx%BPplZm~4h2tHFR?D9$l3BMT5Z+oIKJwr-oo%Rk?Qj?}J>3p?Bs zA9LZ80-y7&e@igsO>cBA8yns|=f)yIt7}@JHH(VO4-!7Vqg86Eg34^=uR z0g73OHvYpgB2z+T2&)#mop%; z_RN4k@-iku>gffD^e&;Hk8?3M(5bn_-`HXzz}-0JkNMuzeS(SL>bT;(8->PtxTUL? zihAkGHZ0aI55>Ci*&2TiBVYd}rih8+?!2#6b=e`5*xc@aCQ7}@isE}`Cua}>>sD;7 zveNWL++M46Jo^UfrEiu82zb9bkDpd8__CZO>@4-CIUQ`KsucL~nxekd*FO@5iJJ~H zq5Ug?gMEf^i6A`}m;DZcLh-E1+P<@6!mI(?ta4qSQoFSuI()>Wm)Bz*-e7??r-W1x zy>bx!#Z=j)JnLbnb@!utixpk&V>QG7GlN0773p~w9$GEGqB$2glwAq@>F@fjDsNWH zC3wdxkYZfUd!u_pFir~aTIOdi%f)Qcva-riyoRZ7LT^TH_7WY%$=XwrMFkP4%4VC0 z+)#)SHbfB!A&vXeGwFnE2iMBLi#qPEcGtd_F?lJp|H&m%*sv0GKlPXH^skMS$N)l+ z0>y3`lBm)-u6=E@gM+QJr0X)_X`qgKSIDbqZQ%7%Yv=fYdBxL+yH}8w=IviLVr-Y& z7G!i7Fj2Lh=s;2TI@nou`ymOivwm#<5Uf`FpifACmAZyD+1?}u~impJT_MLcERds6rGFei{I+gFn4`fH^JRul(JSC1@gP{ z_x%K_-1hblgjVtmQe9epf)U}M89>}k&VuOtWJIeD5~1uer0XHuoK`IuaV4dgA=~~e zHc@n_w6*HZd(0D8`lO(@aAojtJI|643w0|-Xn;r5wMMt;j0O^%FMb~2U-ZFou)gc2 zMF%yRe{t6lON$D$`n;hWf)gV8-iU-T7r^m2k-P-Rll?BCR~@T=PI}rsQnDp?=?4MwY}eK9^*+N|+sP4BDvavs>Q=f6nnd&|TZ}rQ?H$hE zk@kLlfVHzYIO=7rKB!bt`h<5sMqn#S8>c*e@$hAvlmqZ!wws36a?5<%n`lpt#`+ON zB>)BlS2!TPPfvH7;D7n}@nz8aO1*lmLJ~|=w7NCL`id z7+C_;gS4ZX1=9LLt_D*3vBHk`^bF*xhl@HT9L%?yN=(1lo)zumnWD;jPqjc9r}wLi zux!k30Vn7l$V0GNH?ld)K(M{qE1Q5iDu!&y<>Xp{$PtnAX+!;pYyJ52o_zJvBVWk> z$q3;gs3mvbKtBYpzYigR(c(2ZIu3A~sriN$Sie`gEQI#*Ng;0icCEq&Sd%F3d%_sz zZDBWvna7{F!-tAS1>07~>G)hG3G~{z#(DmvOZ5ctYdQXHIfcw|_L^Mf`+n41h&N{r zkS(o{ck+&#I{$FkEoR*-o>s7lFqYLigEEroNb%1;<^O)NM0!l#exu$b)agYtb0lPM z2#W`Xrl0`~P%X)UBO1>JF66uh-FY$#@qdDliAJ^L1hkNR?T2zrfWKPvq{dE|0Q_Em3KHu2p-nEd0HVfX zVWI}I6deG6!S2aVdC1+=}=2(+VLB_tUI@*?s0vo`h{CN`^8}E)^p0n z2)k(Lyh+&IH^?*Y_Oypyx2#5|{TyOs6p!&Y9gxZ`TPx}BV@S;eRiFiQwnWg5_$OI< z-JBSnEFTvOd1$0DNfLb@o}QJhJ%V?d!e)7Zk#D?WDU&>s&)ofoT+C|X!rO|hS>d8t zwHZ>;Lnb>ZGnCyr!1o!dS%HACzW97YzG$tQr!1qLWa#AR+4B-52|9jcb0$@uB<}WF zBE)$^3Pq|}gezuC?WTuD2P#Sze$|LW#?&t4IM=Qbazugx7@^%6 z&)fxflxsN^RKSfP6!$ZctJZjR_ZB!*R;U+95=0uH3n@i^ovYagZ-mU!;s6%e*wjjAhXwOJ5ruK^XOJ7xxY)%Ie+11O z2$5)suk?(0waj+s&b%&N9ZCaH>I}Xy2dUQZtbn*D{<`u{&pD#A83bUng;H0%Dc2)2 z`HY9ulH$6Q^rgddE5Skbmmm1|l-pCrSUSk)>l?CjGe4u@Gwm3S*GQ7%v{@YJIK^@Y z)VCns46x3jgZAdOo>^H9<-A!o^wOJ6D5tq7B}fvl459Mv{SDe94aHZ=ab?18F>LkhzqQ5;6rzQ8)82snBMC^J@zACmsAQb zg}9j(u5Phi31pqWg7N1ILk>k$_7L2SS)!P+P zS1YlP|7SYnW@dXJvN6rQ!9%`%!vNg)orEOxkwup(`J>26-=T}9H~zZ%^f;%rK!C7? z0ykdx+baYIN04WYCTQbhKdh!DHwp~cs%1Ix3NDNy$RY`nwi~RWp_&!DS6g8?|Jnf0 z{HhBs5d}s+`pZjkB_t}TAhuuWxqBN*$!PmPUc(v)-&OzOZ<1o{{WTv9@LGfcURKpy zc$dAn8(le0Cse`t`m!%N{P{f>``DZxRkOogBO=%i7cOE18tt||y-R|9%mNOyN_Lb&W!?8We5dt?`5fx*Ew2 zW#ZcZy-e))35`W+{w*g?VltnDOlD{A8s-09$XykNl5T|u)NVJ>QIq!81M_2mHkZx> z3_S6XP@U{g>#3F#3&E4CrqN$#<&%OxEF#6PGQeJeBI2dsi{q%zZhV_* z0n}vp@C3{)W+Q_ez2S{h8&(CJdu=ota`V4YKIMX6e(_SM1|Wb0$OAU0#yZ0Ct*R?g zV#afQwBHZ@nNMQ z&(nW$6$&Esl>a2|$bj~|Q7C^d_XsTVV`MSkIL}C?h+YSj&#l`xi~c;O&1YcW_TVa` zdEakfM?*#-sQ3-)e)SI_-KAd4_l2G0`M1D=uZOSs0von7(@XnoR*Wn>mJhd%HOVFwU;o%sUN^i;j7VeTv>8Mcfa&~s59#hW|m^gyx3mIBy#D>dMGS^ zP4`a{gf{2S7ZNWhFr&WQ61yT*Z3q_^iBj;Q+=Dlkq7rY(+^eDLgloHanI98&+h3on zESj|gV7@W8O8%(>+xW^re1$=K?6Z6fdR4&@x1!hGjYDO2aiEx|^(Up@oS{@sQIY^J zQ%91d+i)I0^Cc`(?-IA4UoW>ICBy?mh0kVjZ;BOd8)e;1QXtLfG6^n3?^!9h>%cFpp6K%*^w*fV zJUSkwMBA|}RUky{EG;dj-RiCjx{p{UAmAH6nOfJlXZiaT0NtrjT(0}qaxF>H|3SGH zG8^&g!|~IPwyWiA{B5Mv->_qR#j3dD~9=4WyRYTHkKtQ+O$iQpko4F z)6&@vt*p~f)8&UGe!1 z1IUZ1{DV23rk<3rB2l<`$6n^@x(j@hIMy^ z@s;Ta#SJ5rQ&m(9B!7f)sAnptx#o^V>=<~Ai&7Fb}3tzbB#Ghj}A{i-94PFtod{puRgO?__mOvt%x@MqVvZ7%@wK( zU46OarWGVZ2Yfa$ z28=Wzni^FA+Hlvm4^5_hj!SMM(N~_95t;9-RowE6ywK@I6l2Ea)FCx)gF!R!q-ZIB z{>&lUR}&K)oDkZ`V!T+nx4xNo-E;fV#Fq!WVLbCOE>8Tmd6~V>3SfF`N1i@AkKG6( z4My`?LS*I6f}>e~*)SfeJ3aob?gR$|~LH#_fCAg#+_;6kSAuR_^Yq(O^8@ihM4HYf! z4GQtPY2-Y9`6p`NVjGY8+A&IxcI=>ZhV5s-lR7zb!TBG-Mn;-7)A*8 z-_EYur=A}z)}nJ=e(-Xzk)&zvbmfX%9nUv8{zXP6bn`tcGR%S;f5J__h=FCBP~&vn z23rCGO1fnX+*~l<5uaGLra5TYF&&_vt-683u?_Qvo~YJbUEC zk4!f`_!we;T>xZX@7@RN_ty6wm^@OS++P~`<1u%mI6&$ZXl<)^CPCTlMV^gz-8Kq% z@5IAFHD9FWJ?*XE#A7yar(%8P==RvW9YS5$spx;X1>nG!VSvMaA_`|-#DsHnr^o2k z4McHQZ7Eg{=II{|DA6r}IZCmEO8w6+M{^s-37=fK_r`3TFa6x=Z1)NyGfTWdokafG zuG2@Ik;JPd06IMXB=4$EXEl9DChD^Ii*a(BdmC8-o7SI%4RS%z;CJwSnOnis?!+dmC%TRx1MnRo2%2b1JR& zZy)#tMZ8I>9{8Q$Xd$#$i%*Rcy4Wamv41YBVsha)^?rlk3aw#E(efaNDckgDc93ep zqxXjKj#AzvbV_^MHZ~3Ob*_Z=bSULSiHi6gS#8N`;R||VcdS=2wa;(QO)U$)?Ki46 zS)W^dj*@@^;9a2vt(-Y=9sRYW{uHQqS$ndMBO}sv- zYj(Ck`e@FTZ$Ed6RDLX(61TXdTl zAP?(Thn}uXPU4|}{3Z`&;!l=8E846+@*1ICs|3W-d`JFI7{T%n7~vrR@7Wd>g+`qN zXTG9bU44m+i_HfO4r_L<%hZ|Q4Lui7V9+cev@_%fbi_O_P3I&cR{$FdEL?pxo!5wv zq1SCi%XOFBM~JG*TkxKpIu!+cu|Pbc84Gd!6{G&;D}wtO8KxCmJ%Xy0PDy8Vt0*sj zP36xjG40P&EnSvG_)X3`>hCrrCUM3l>~)h7@`zfc(_7#Srh_oBb93KL^(L8}%{DX{ zt=tnR?&OzFQ&T%7$vyWPs(#g>4@YqWef(%)=$F?~iDAjIJF13pFEil#`wR8O`U?uq z4#bDZ&$;zxh$vK@%AW3BTI_B%d1%WR@2te0^zQVR*aaBznwTV#@%YLf&{Md9)TAm# zVMUJwW=z-4E?`8*#nrHzt1NBh#UzyrA$^6-s7cB8FW?ec3j z5P;Cc7K1lgTz%`0Z+m^dGF9z6z*d!#7oVF)T%6Rm;IUHtIF zDviVdC%Go=0-ntq8*n(JZ(w;N^i3~d9h3yKdi{0`4`G*ruW6$+0*j&4;pZB582wEZi^_y@SN;e|7^<%f8@2@eZVG=VI-co-z%j=k8rPw zJ;dh}q4-m^v8F*pc<%!%@F585Rem8Sx?a^+5acBF-0$S+@*6CRzqkOvsn$($D33(& z4hKiv&OepqH#rEP_a~m6L1@zqxIc->cNCsLsGT@=I$eptUYA*}oM0D_+S}Dliww&@ z!f^?g@OEr)8-9Awhp=lP8$s0L#H|V<^$VP;!w-u)iH()b@;!!t~^Be;}xVoN#rdwa4JaRI(1|L z64&sPX3Z+%xBL=6_e}uU0;>4mNW%I=Z~ctp@H;7J!NZxl+K@&G(EjJni`Z2FB9LL0 z9eq$gg7=qN`UQWz4)PCkUGX2>J!e{Jt^I#wbTfC*eVasCx<_xEiQrT>FBPfsfU z^Vn;y+?0f~6KKgbe(uJnv|Qj<&G}dAQ*(!?Oed&VkUG$6S>hNpRzBj{#J0J^eXV3 zHPIFlP}U>-3o+Yt>mU~gc(DEoWjVmIAKs6N;i!W!GNWzGF5SE5{u9Ys`Qs^vu z4kuwDmwWL!^2?U~lM%qg0Q37Oy=Kc<3WXSJ>qp*i{B=Z#a*Kz>>^2h$iNU#x{VHyF z_5R-~=lH!E4+M$L*fJ;pSorWSSeS%W=n~vJxjPEEf~U-@cOo4Zx}e)Y!IZ0?`1)7W zia@&JvJo&!m3@bmlc|axwck-zcZphXMKdHv-X(T_^+QL0e1e7d&JuxC59f8ALh^)v zoyI*x`%v=`Av$c~*DV`5MN~)uibCyGaB)=-)FJ?w14Uw92$$x9Iv@5(AnhOoD*PXe zM;EEWIpb4+;%+=xL~ENu$*y!aJ8t;OL|tp5!0N{U>X~Go*|!4|xgVFQpA8a1T!+i8 zwh^(VU|eIr<4uD70kIne>i<{m1Vv=j?C&iBITHnG!HPEZf&zs7zeHD@dtdT;HO+xF zg)B)Ok^N%nj$AaCc3!%mz2o{+R{ipeZ5W{I%ToZ2NTwUveXBf+foDR!^@Y4_tw_+f zyK70$~Q4JnS}C1ZOfuHjH_$Ww95Sw2xXrRNd_MWLsptPV;#O$# z?;vh=&tLtnj>x12nT!IgfrH@)KPepR6*8=B&zm=n|7j4e0`MJ)i`Yr~U-d6vFY&h8 zf{M#mm`oW1=H_65QD&3Zv5u-U`^D>va`(z@Ga;`i^62}gNwaD}y-yEmHib8KZ}T$k zti%?Z)Og&CzOt20jy(bwXZowFP=cX^a5eNYg!QPREMNU z70p(WgMzH`eUiXqo=Pcu{F&h{tB)3lHObuoIg%4Djs6%IlW|tE1Ika-XmVs@_A`l^ z)0I*)_V*l(yBz77zKpu<3nAoA4o%b-uL>wy$dn3A9z+*ujrtYXf4*$im($-8x`mGg zGt1;Xw{5~-4}E(E5qygrTYL&6>eCUT&XtrPgO=C&;tx72ryC-5Sq2?sZb1i778p@2 zZ#~J8hlLbbOqVZK_C{n5npLY+Y@es*+hy84YkwJ*)_kUCl#|ae>ojIc>^JQzGHssBRgM_w&B?7jq z@ldVT_DbR$9NzV04nC|}>`JX@S#*dadKNTV<5;t$VKea{d3HKEUMMThbtZssv>qhV{gR{kBqsO*$8UMQ1f*(W$#A16$gipF`nSuWdi^(8}~sX@xuqZI8!uzwRV! z7}X%t25rv5L#*hF4G|2k+_?rfw(x1EQaMc@4$44s=~?{XmGYwO^7SJX2Q|sI-nm;( zraP_++RgR&hNqiV$5^2sn){elE<)dbfWP0b(eP59#6-u|9p2vPc)d7j(iCA9=;HaF z6cRc2=g>ANe^wPA1lx4)t7(!$?DgLU*cWOE)$U(vL=5|-6TeE>UNLYrBgw`zh{~cj z=3RJfye3+j`PX}#_N`iDsYP5ugtE4(vMW-66UNHT&XU5enDsIz*AGQ|s|$!K zy#JE3s9Krgs7$-dug8Nc9mxg-(LE*$sOPr5=1}5?p*?d&!}*35m4f!oE&WQL zo98bsWXzdD_0k8g`gEmn9F&xealb}=gK$;FJ{!c`JB?qy6u9bpj0=@-44h4nsV=vk zG~bC2a{iJv1l_NVKKORy;-LVW&vlO5TWN2TJ?Qm>K<_-ZW^%fZ)pH~{rRoFJG0OTs z{R{eFWza!9|4Ij0R8Etq?v2SsU)xF_Dmz$f-C3)_ryHb29h};F2KfL>01noa&FGLl zi!f_PMa8*(9e3KX?^cW)y~{K>2tWW;52Oh?9`+wCHhmozW6 zoAhLL!i0JA!y{03)s;#_Gl7E|CtusOZhd^i8jJnYvsV*SU!Nb1>?7NvZJ)b>=Hc#1 z-gv%VGEplG%jqk2Yu<6Kh@B8ZBPU1n`L7Y1F4wNW}}s{z;j9WrrRoZCYd>9 zSkN)=isIRzMMY}xs(?JSv1g;QAk^47bDw-a$}a8<-4BWhv(L zS#08292?GY33cwxBAjme{}ekYXQ)8^(0Y4$qHxFgw+t13|F!=S1yh|U7?~BL}%VhEUP>%2*7D_s%)(4jO}&9fKw}_f$x&pv)v!eo;wsL zyr%|LO68xLo`a-bhDcK4?E5y*$UZ~__8PbyR1BqIuqoWELM&0QeYS{bS&-UDDe^DA8pm-i@c* z%SD)PhMtqXm#rxz21SFA3ag1c*5Q5_-*o5piGu{l-}SlmI&H5mq(b!@pq7V8giRZ2 zStP6fNp(a1(caS*b5HQIk!;5_@%$hp1ZUv7%g=nR+uok-A1Jn{YMFl$7&6nN^s)Vz zK2%e{@Ii!KErhqVn-!}kreYno+-0OS`-M9M=!-&vhUda$ZVT%4idzDc%eDUD4Br^f zp1^)Z*;Aq~vfuMtjg!K{JWHFt|Ey`?KX7}KLjsyv4C>}H&s5s6A!3hc4)zyTzg_yj z3j+Uz!$PZgivfUwU~>7z4loRfm#Gl~L&}8$H@;dV7O!^2K1TP4w(o_wPlF{%4D>&J z-mkdtQ((wI5Co^&jvSl(kjC*{telmrimnb%SDLOP5n0-(zg(7sv%DZioPHUfiX6}j z0hMzUgbIk?KDZ& z^=gD`k0S2)t5)IuiKCKNB%kyDhtS^N+&(0jHw+R9IC^f6!R`D(oQ+{lj!F zL!h9qjXe6lO)B~Ql>Zls|8IukQq^jv0vDkm<_AX6dxD&jk`+O*I7LW)9}&<=!EEXaG10hMiv@xA1ziPxws&}cUI2XD zcFRCT_Wu6TgfabsdS3*cFY?J0zRf(D#7JN@aJ* zbqK2R>uL0VlK?%CHYWS1@*fMTr6c6I3S2Flw>M6~N9j+(-qgG@6bbx+sflEnd_>qg ze_?7DK%MEP$_*0WK${A_T`9XC4H%w@kQqM)&+ zzh6oPR{T*uzUVkX0$@Lzp$k9C|fnn=-uF*zsi;UY<{x)@~-FK{XDR6JP^v9|I_s`)*tZISee0{-^(DVN6N4w zwhID855dmV#XtE$NtE0?`v&hfFN^>OQWAV8SLPb-SpnEN+hEiD>aX2^eepqxDlV)U z(YPOio$(Tx{voD;7)ddr-^@Ky0^odFa82|FCH_DLgJz>IA<)yWwUFM1ZBUu%%C8uX0%H;Im(^cZ~V;g@_m$sa6Bx?UdLKLsQLwn2MJWW7&U;9z&f<^%LS zZI0&A?y65Bz(FAB-BR2C@VLF+xDJ{Xr|LEYmW>4p7C7&$a~Xe=|JjdaU;Tem5WU*8 z3XQ`d(oRjf9XS(il64O_U2)Fq4+72z4QQ^d4_c%vB+EjhgDS{Po>SN#3ldS1miC3} zUehM0{jEs{Obvc`Y%Fcl)AwdL-;jP}ZS`RIQNdd&yR z3UBEV(Fv==7TY|XNg>9v_#sOc|Dh~t;$vCF@R}9C{M?4g(ynD@!afW;{lh4t6$VCqO_W9>%T zM-TQloB$GN(sJ1v-|ns2Pm0uZkO~r7SGh15Rdn2k22ldZwf~_KP`d^3gZe^WZX$H% z(6$?k)+lYgw&E`lcsj}gN~Aj-dmb3}s}kDlmKGQZYW}$Ar~boxp6%&o2TnIoE?(jh zpH6{7U@s?ppzdCXz+@Pz# zf2w$%LmsZdbAy4mPw|quGD= zzp1hRL)T%K@$vUh+DY%`JaNGEv)(N-<%iw?JYGTxMqq-Az_=ECFd}hxLoP+tp`ZTM zzhge#$q$b-$MlLpLgNy})$y>ij(VBo@1JP@-+D5T2uzSJU-0&RMhWBuH;~qE zX+u|ly8*k9{foK%@Az7f5w*qSQIq;qz&X!Nx}BjXHG{Q2^6NZ9qi$Eul?xo&ED)$u};k-zRwp zTNB=Et@c8D!!GdKn9$>qfKl$Bc=&fw4CH}VZg3udtN=c-jLZxaSc~B<scv=roda?ITaBtpVJ!WU{*MBGtW7QJq(SexmarxDpK8$cQYSg#5|c-npc)BtRljF=1wL-xeMHMYWS$MEQ-Ku>lT4W{%5`1-Hy6jf{}(zP$L z<-wV~{9J-WGMG#dK#Sy_jl*Xljj^`JXy`_O10CV`&_~_AHav$9s^2*rD@vaR?{;te z{UsbE0B|MJi))vOnml$tbx}ki^@A2=dhHkDNY?!BE$!}m8D6C+pWx#+&y8V2a8QkG zOJ%I;D2YFkir?^ijm1cclry?<9`d52LiX$u(GzU<6nHa<@R*OJqKMJv3GSbk*Dtyz zmz0c33z^sY`kJBTLi_pug zNIH!54dNS>ZO^1DLM0n?0Z~F6wfA?T-pVIQPq(`6=i?nanE+VEV1#oD__pt>cE>r9HV79hR^>Gu=L?Pda^7@wb^?-nVc7Lx5Au_d z(vcU+^%qvqz%H!r;Pt&CVd#-7z|W$7s=NW$e13zoVKy1b0Gf zrrnp?(QNzTe@tkr%XO~##z<>3-=1awb*ypKXr8{wr&D)i%@L~V{aAs*Lc>MP=9qGr zB12^{sbLy%Bops?g4MV$G)4(1G9Au>9t>0h-=Q-_%1Y_vqrvsq?fsqS&m;K2H>17u z{6bj!n1p_}oKjTKnR_oP)O;1cwx=JY&ei+=>+|MYr7--nxFu+@_wZpTl{z4#0QC3> zWDoBf7`)$Joz}g_Yd(}B6?)DHgRVhZADW|(804_>7#5!ql6MstDbV0WUU(o6mF`70(W1Qt?d5hB z&2d75`_8R#LT1CR+nw>Iubf(iHm1UrHQSZ7oRfMr9h=t|8`-nFVC=}F9X*c;mN@%b zyz2M6&=#M^Y4h=c%*C_I{%}_e-}?sspkrBJg-vLAX*f13Hh%ilCAb*aQXJOlR;)3| zJJXRAD!j9MdvCQ<6|ysmhc7N9lQs6tT2L(RMP+3kViN;!Bvh6yJHO>l4>ZGC8Y~#{UBhFb8uNkbJ8|^}D7Z`8u7J42a}I z8MWr9jL}298`U%pX~3yiQFxd@u&I=A6>AYHfx0u*_{s(CT3+)DY<&+kbK8c?Fc=KU zEQ=o9Affl*@_)w5PL6jeLtJMF5mFsD7Hk``xAGs>U{@@Cq_Le`aNS>_b6)Q^gdPip zJ#;W*tVMVSQdb|S9JafTxi_gQ9SBmGRylWJGB@}eb!r{7oVw$sVy~9C)Kkt zlb$^gyrGh?^O+?cIb&m>jY5MvC&AWZ7b-!(#vEOyXt7@+nnm2EFMtm|Bk_Czz1QHm zF`b&cuWPpuO^Z6aP$CoWEUc7VY@GTc^nG7jBneb~`rNPHDPAm^v|_rml}%;D*fW`^ zd@W(_h@v7w>qt(CpUY!8Klbf-W7hqOJ{yWcnMQ8}z>1?&MshPqD}c35Q#_Lk&?tB^ z#*I&<3SYv{jSknB90PmSxlqp&5A$gTb%UCh8YWJD*A;x!+%gDCTz1rXBNqA-ZLycZ zW$jN4k=fF&^Xk{Ag8eJRTJk@|J-5_EBD5MTU_19Hn@7+=(*LBk9cJW7rwsJd60{^I zBfAgH7opHsf=c7bOAhNMj^ZgO`p6fwZk8R($he@^N~x@_9TeHJXq|E3@>=&J#Q`YF zB@xM0QmkUwGI1Z_^(k?rU}a-uBY{IdP`6UA?GT(G{FkW&X$+_%9t+0GCVYagIV9Iw zh4Ia-n;WTED0>rqb()=-xx2`f&xlNtr@SJ^Q$Bu6IWRf4=ROo+^vARV4MVlcB~Okcmra== zG3f@)Gqx4b@i0p!rr4-IPmzC?Urh4KXnB@z9yysJ<6cf9%Es>Q`}{V`AN7z83&2z^ z7HgVRt_~3J?dGDQRD;A#-xGBoaJM(1{;=0hf%+BJag0}-G4@FHF2zY!>`F>jXtrfr z9X%G(JgEXPrI88t0CYO&4MjxB2tK6i|_K_EtMYk8dK$<$pWuwFl@Z~c8a zSI){R@66Nh$II(_=E``qGx>Y^Pmi09l$!)v>~D?*Ss&IUUk^OREdgLne)jxenN9iv zYDKoZvu{p-SCTKrZD4LyXg^=?Gnp-B+cv!6pc;hOHE@an+Z=IrNri;bP`;M2za3eA zAIQuIiRN-r)6{Bb=zRB^k($Azrc&GFg{RX<)L8m|i+FSe&J<+SHiQC3AeCrZDK*{fWby>ghk!vFs z$djfwane2;T4=>TV!h-vIZki3vLw=_=Iu?9M|B^Yv+z+31`AU>L1dUS#s2xmacLRW zoC->XOgqI%EU1)_m}L>Al!Wg1*e>-bvmLf{hdQ0!959FNq>a^0KN zx{Q^^Puzd4>GI+LPq|}rd-G(xYvta;yHME)#0Oo0*uyPT(!+5FN2>K1_(yo>m}F|1z>z+x8( zQ4ipJF`&wM8i|44H)Cn+;S6^vKyAK#bngU~E3(8_4P%E#B9aT+ELzLqhPxHppJ)2s z1ax$C6c~3?n+>44EBOZIM&^gpV~8FD(f<5-HJDq{n@Ti0J-9ewys=Qhi*A0}`Wf47 zG`GpNIES_rlC(;*`EtDjg*4E0%olEf;o|}&g(Mz+7ACnD0?pQkN9H)#6R)4rG7FOp zCTl!jeuqEx$%9m#!q@vrwDO;u+25VzFV7KomS$pVJl&A-Y+1hFsxd3XT1WIn-FdcL zhrqCCc@;`0Iu`6Gj`CaKY3bkdqS#)Btf&QaHOvunT5gKwrgLhK5LUP*OTU{$E&9mK z;ohk(cibrIvwH?^{X=($HEMDUp8ftQ2ZZT46%Nr9CoWX@UL>7%>)m{{v7%`=Y-8M( z;OEiIi#LwzS<2kl8R<4xz1BWMbp2TX&sc*~t-uI|>8%cVdW zfURV9e}Ku%<;6Y_3Gjq0f*$coUaH#s=C@upCIUrdWkSpS1^Tu}V#=CZz5-XHkEV<*JUZ(JYQ%1#18at=*Y>(jt4Q7IBA$lyj z`BE`-pG%btT38=0k9XeJpEHP$aAs(GXDQBOWR*rn*4Rf|22B#%Ys6E_a?~cB8u%>s zb&!6Xc&Lewc5@|Kfds8In27TVsvL4A?-gWI4GwJdQstTFtM028yM#hN5+#&d@jbyL zo3bxd>&Ku{;0Wo}0xjkZi%5CfwE7kzwnD2D_mNi80mrX3Gd_L|5m^%!NM>yhypFaQ z(ynjm;$66GBMmyXW6@)g$+EG=LHe@L;V4tJaPCsa#FSxvC)mCv1UCrE7mFwL0miEZ z!6>;crpWXS^t=pk+0-u8bR9Eu?B?J*xDFj^xklYK=txHFYFmc=@K+TEi!$d0Y2=}N zbzx|0`>Xd`+Fa`XB++@z6I_C}G`tzv5y?E=kegm>E*?!+Zao?n7nfCPePCDH_C?S> z=#0?z%JJqXY7uSYg$-!E+9@Z}ys*vFa4mbwDE3R>S7@(QLW&`_tk$SqxJEq3va^{> zHOwH$uEy*ipUNW0K1o7napaY*(Cx60z%wuNxX%iDg(M}(Cnb?FyBe{uiwhLUF%vIv zpym$@7q3;nEg9h5HBCI?LBOhX;|8wd^^DwNm5#wk&i8gQm%1O3b?$cFxuX-kb>~ur zvTlF`Ztfc&U!n{*Y_=1k5yN}g{8JJ{wF{Ta1@l+F*jV1|;|2lWJM$!e%hYb&l*YC)SY> z>diP;JPZ2w*R}}W<=gjWjvn%aB7F@3Uk>whLC=kDWzG8Ul5rFjw+-isX#`3><~t0l z8LTg)<3#fs`KP3KT~)GDH*Ff?eZwOyE&Fh5)JTV2-{`jfwCTvVZ)drp{lD!Ft<7@d z2ByKxj1wpg0hTup@=sd6x2Sm|##Hb^Tt($kEKYV(hp)5|O)vGuxSH8??ue(QV%6+0 zcGkN_DI1GTki;|d!A@F^9IfYQWasJIHQ({oDhO0!iae7VPKsKPZk*!%LZyYe2HVnW zoxx8=sP2CVmva)2@G-81nYEioF|U|YEY2;_X&fghjC4klVp+$7ijIipWV!eglXKF8 zg09=s$=@Nu?Sj2(D%ifr}q znsmz>cj9qc&L-g0u;c2@bCHIBj=Zj1LiK)Y@et*>tYV|V5x1?6R9F?q-;0cRwctw4 zb-9^dfG@UU40hXq{cxzl^Z)U9lPoOU?2Sp*v-DVGDKsGdWlLkjO!W9LqlBL|kA&N=b-M*Ae|C(XKa9+}$QuTHeOo6B`S9cfXIuyxj+w&ppe zcHqUZCTM7`uSSJzK0#pdOE-Ub8U1IUv+mzU-nIkY*4CK&F}y7|^0t)!>}}y{kCD4g z3(AOI@}P2ajv4Q=(C#a%omMv`(s@|9YdJ-1o~?@erD-0XG938oaIk15{A6;o9*-J^ zf8DN0cQ^X!Q zYC0^rR%xheN?M*k0imzTYk9X#a&$6){f${UN z_$ei zya2OOTghP|*Q^6?VN<~e#$Us5?Bg&rPO~}c5}|$gA>3@`CF{qOWOqZ*g++^gjU&o+ zIo3qSnkGwcbUM{p=iv5uk0u9UQR-h@tBEn<+B#Iyp> zAfEdQQFP6>4}RyNPWrrBS};8Oh~LLxeHLV=*mvIPc!SdPg`qU*DOOx317J1XkG@6~ zy@%x*m+jt(!?`90I%VYx|JyP`Rw>LtbfNdtO^r6XFJ6}WrjgZ3fsO<{pCY*SmZ_h3 zZmq7?Ql;cZ7-Kao_9k2wv@AF_W6Ke7czgCnNPK!H9 zjJN2I7Gi(EA{XaRV}Uxpw6vr%J`a&IOtBkF(p4BPj2j<^Iym0+G$;vdion z$M1%ahL6=fU>+Zl=zjJz>{!Bov zoY7unZ&l$|x~Zp!M?DN&tZ{=bEpMPwR0nr4SGR%7Vf7IO1qA_Xh}(SIo5L5&=Pv6R z7`#<0Hs1tTB*&=poH}GgAB|QDx$Lbeze`RYgEOYuc&)+VluL;++&L}cf+8&gfCSX8FA=x`Y~BT{WT9kt1i^WC20Ym?}Cd*{-!F9A3O*Ap^O6yX!nW=u^Nq(pOZ zObG~6v-_q_o`eZ%h=h(CHlBeYZN~6!P1D!0TmCp2b>^=A0m>Rjr84PL*{X#{pgKkj z{I52Ma2f2A6ibVTto&#{_49`Gg@3p3#3QP2RFgUcy{j9TYht3KeW#|TT(2K|^W)^Q znYnLs!ts>y7MZgR67@8Pv%tAt=Ff8*TSe3|7Pn_3h6POs-8ocwR8yiZcQ}PShJvP#z`DfU9 zg}Xp<%+RXRWQu$B%BLk(@MsNttf%8-tlfaS6JSPD{rN0esAXwAJ-t38Ey$CTT#(e_4sJ=fRQH^=qB<-b}s1!^FLJO4g1TTQ>$yZxn`Ac#$wC)*Aa z7NYe|(_=*uuuAzfE`7}iS$>&%!*}5$WsTZ{2j`140)v7i!Mn}@tZT@+RcM{to|6Kl z`_aD!t~z+X#ApaUYOCE3e>jDLL0umK7@P`0hu35LI4gacWBB}p-z5hR7Mc0kjXcFK zEiLsmXhGjXi@{dCCTBYIY3WRDB)>dn+sY=2hVYRmY&`GGmkZ%CV0rr2w4WfG8cmLJ z*>_n$e?m6)G}FWUfrq>?w5H7laFzcS>H@BE_2VeOn`i+`@;Skw>XRA4d{0e9#r|Fm zyJgt3xy<4b-*Yn2XmpL|=J!xJ4A_aI^;xfL*oPr1>(#D(M#vbryliDUTt*2B3jLJb zq42KwM2ZV6bS9gDJ4FzSe?+`X=*ajwDCnU9uFGIM8jwr*@kQpX>R6D_VW~*xP`As3 zk7P01_?ztcWd6Q5SH#?4V)eVWI|{tN*Q&=J;vr~t6C=DaWW5PE3gv4`@v~Sqh(1=X zIGO24fb5FU%kzoKa9UmN_h3+4i7cpo8(UN;&UY{n>{L?g;@C3`u#z~{OY&qGLRh8n zJ&AK)umAp*Ybp}iMB~0_XCRAQvRhEk{_w?Jsv2>CwpDLN1+o9QfQ==_rQROO9N{{z zVK=G3?P@B$jy(bCoye zu-Z4qHy8faW7w)B{DX0BB^bbSUo$YFo#FS9Y%K7z`(gYkxRmIq`_lC&se^#_MC(-U~IJn3H^OQS@2ypv9ya!ScP!O6ivoY z@)4$lRX_JAHXFpbar5R|g=Auw_sRpEgaUVWoxXpctNB{@oUJ)TJJikT_X~egW*%R^ z@l)vLk>P%_nNiap2!C9it>0J2joYL6X}*E|_m}uNw(|~7+x=AC*xU9C_^ccpbwV4m zX_OD)8giNTk>cRuy@k=v5Wv?|?(LXV1QFe;4#i=lex|#LasSjj>|M}3YfM|FkbL9G z>;U2P^lYyF?7HgUZ6a2C+S+;vCbR;auXEb#J6P4)yleP zeHP1%^sxj+nq+I#p-HB%a*@3rGY56|YGr2-JXrEk#wk3>ja_&Hcz}< z$$x`h8=tw%N7jaiWr&k~_Qu5iqH|bycyEbEv34E79amfGb8;?XpN6wE1a$jyYQU-z z@u~ZzDkS^GuP|cqAOfM=eUh8dJ`Qx=WR&#{vbHPkGdLEB674A&85C;M1?IHgdpjFr ztJ6nhtaX-|9!LD%OpY&?!6&;P#m^QC|GI;{e)HOO(ZCJTIG;{6UD#y5YYog0SowiW zC=0~RLg$bV&~}MyHq-6~0dQwNemY)OWx=Giu z0exk$xc3(obJI2&;$5!Wt`nEa>xodGB_$O-s23tm%Gtd|z$;@CdCmwC`v$gUjx9^KvdOt*{)%?Q_>s)pZD z?Zyq9g{np3Yb$fNTSgk!5ur>YM+t}1hcaMAfq(p$Ei1k6I~VAo{8C^0E;XpP^jr__ zVdI~dr~UWkf$^OdPEMx*cX(yr=K7&g@e$=9S*GfT9tXVa0?PaXu?`{Gxu`%lR z-0uYFIo<72=B6;s;r1*I8b}%7O9q~yylf}_(I71BbOU51*jk)Mau~?>_xGpT-q2cE zZ+y$vPoD!vuPo%Gu+{gI9UVQjk1oft*xLxiZtl0e5eo~COtenE@#@Ywr-M3+t1i1Z zxY=rHjj~eJ8;Gq=eV@yr=2L}9ay|48hFZ!b@N~>q-*(U)E|a-xxqLtK6dwVJ_|q+; zu!!*PfgTznrWzAs^Ftb+87z*qUXl{E`K?b<%rBRaa^YqlZL>d0_~{NJ=j zylJebJr?cJd;PAd1U6HG3bxI0RIjErvxNUiFN?~pGp=8>4jGMq7qn=lsNINjWKoRC zFHlt`Lvb6Re4%zVzV{p}7eO{^oAFH4E>rO%r8)n5E}#``#F7JF=oHrk(B$VFh@LU! zrHSV8t7&71^a(F<C0yzc8#g$Obz`SRhem z)<%D}I;@I~3@^zg*Xg9vdx!<~OCKFOR%lBffk& zP55l|)^?MQzMhRou|O%il!g&cc7gs1jArtL5mrj&dCD~14lJ)%g<2$pi8+&r!jAF|d%ZS*O-@kR-$RZ#n z{y5^igQ!ZM?uZUbnn5z%i?q!93|ukII%*+ZT~C%&ty&j+d>$Q&)#ThqSt(MVxTyZt z{d^+@Gj^R9v8eHY!0T&IrFX})oW8T_P)nXI(IhohqJf0EyN5^JquwGO)k5PJ;C-oF zIAPj2vr20k;sk!y}NJga<>E23bd`zcY)kE=*5~cFGBGfFT6co>3AV7km=}|qyMT&2> zpMtTj+H=vJqT!2|Oir2daR!lRw*hB6GJq}sgW0|%qv46#6`vX?Og!m{05gHpp>Cld z`zD7@oa!glPa41abcQLqz8(!J(%LSg&U;X`o`??DTM#E135s{!nnzKhplp_Ye^VDL zftu>C4Z&yuQ8sOoE27VdNWK=~Nz2;!6o8SWKzs1cQxa0JS^w z?fu%cu5PhfsX;MJ2cG*;TnkShu6J3gEJfN8-;IM7Vz~}lt8n~p)W@~M~uq#rjMK?k~ns%gT$5NHl<-qfoVHMd%)Y^IL$SB@dKBN;CNR8 zLA&uI{elUuoEfsMq=P|z$zK?2b1cVB?5G5Ok;=VkJ!Y?>y5Yu?6;t6RV9o!;kx#tp zEbTUmc7XLlJ`)7Wf9rnBLsc^|w@AFUwoWO=d z*&s98^@Jdcc1H{5d%Q@1i6glb!e*x%LCGcfn;HV``NnBL+~K$PjN`FS|!al2L5QDU)!{qvi; z`y%Wg1np#RJnc8qvlwN@$u1BRGHJ3+=2F|8eZ$B)V``lfOr@VW(8wccB_4WHq<95&DKl&cyH zgPu{~&zl(kIHJOdgn=dgc*Lz zZ`tj&qLl(o=Uz5;^)6cZ~n9$5{Ks;`X zeGG)dA36mjZu#javDj!nU6Y}pqAF*yU#wcZ2)@eE_2UZi4sUO+Ol;1~E`l@u@Y%Cx z=$%E^ysEfYeZ=B;BxHtnZ+Y-l%3Pah-s!ll@Vw3;6e5O=sdn8@!}+6UIxyOYb96pX z=f2$#s~LnmhmLN{*C)^WM@ICn#PwHhon-1S*ST3{|1HBt{dTUC&HsQGQDZ&mM9I* z>*FXJ=F3a_j>k`*W-aTFsED;H&JulE)k;c$A#wYxw;bj4ihwX!6akJoYgvo*j}F<7 zt-acVXhpy}Ys$Ja`C9;=;n5n$!A!mvE2nu)`7Fp%{^zSse z95$xiagRnQI*#_|yR`JEWjdv0t0VtAO zO=!ru*X)&uD??t9X(GCNWO3r@DZ#V$j>%7`CMV)FzOuKp&;~Q&Slqi8&K2JBBKXXW zl<4x$pBe`)DvfE{OJ$E!QRqyzoL&lvkC)0Y-~-11*J9~q;$tO@v>`@GDf;=1{G$28 zCQc-MyB>4mv+EX=gWR*$`~C8&W8;R~n)DC-eB6^1NmK-hP9)Mqet9n3D`+kMx71O+ zkC+qJbe!*=>SQ^^&+o{UAFX>DG8&RC#8TBp2rvQT56`ts3|9)_TU6}@itkzZ8F+K$ zzQjg6eKB{V&nEydHD&ET4gf?X?6?ErHE(8jYxv1ZAlPjke%E|A(P4|HtYLGiwyyS` zlBO3xTW=LjZ#(S_%ZXvUo^{oM^LE%(MC3Iahj`1v>D8HT^8Ar#j`{~qPA2M4KU`JG zWc`D93iz{u744FODmLN;K||$;iZ5??LyTLCd|wYxt5(L zE(J03zYgmsa{4m>29T^_ zKdWVX6WfZcLu)+5VoCs+4=U!#j&9_Bv2#y+1z z>FNPyzVvkYMs5tYGKMWh^w@Kp76t%#VtMD!-;nScYnRjlI)>cWBbqHIWPYGFoStug zhww)<&AOr5uK?C9wC+M_lSVPxwiyntGHx{P6zay*L@Ji~F})vS??TtUd?i6f9tk zEkA8r%oymdQHumr1Uz=F3cis|kt3R0xp|Ug!2zN&hM3B=9uwlDnrctjUo~7iq>_o* z89ki$L0LgEBI_)J^Uv(^t!hQ=^$i6zM3j(jX^?IakW@sFM!G?|LnQ@XK%`Sj5IEy??e*<_&)(lT_q+EWe3$F>;d$mW z=a^&sV$7N24ewVz<8AT7R|Or=e2gU^+}#231j-x};CS_w7LWf46~;!uBN{MH;$h{* z`c-_bsYoxcV5+TQEJgjyn~IGo*sMu z4wbv4x#Nje&Wus!OoOXVL?UJNnZXg`=P z47;qB;)sqj{n3ugRY8^-%7qCFyN@Gc3i zw6#Ng;Xd{j8pzR2YYYn**E6&6s2jE%h?`__fFVpfW8^1{Mw~Bb)c-I`F{v6*&;yhEze7uqX z-Odne@4Ot1Rc`jGbpl#)QwHu#*o}Z%KcKafOn&0M5VsbJENEu=A3gsA+-9P&$6;K> z$EYWp1r}>x%bwX>OZ_DJgTBQi$aKaICeq48)Uzitt?L+hcbsn}A(WMDLKHbxW1#c3 zSyy!WMR>n@D9L8gzKaHOxVk#(UXZx$jk@WyGWGYy8~k>VT9DZEA1 z68vs-wA;6m+Tz{Ajz5_87?3&gbcdormG}TDb8%rZZxNRVAmBo7CZZooE8 zgIs6o->8(BQD41eGf|+T`(DiGA?BSf7NSEjZZ{~cif2a~6EJSQGDNBSP~GS7j`so1 zcfy8Bi}8G|+gr8uha?SWCkJ;Ek~@)Up>z@v^S97?Ix{I65cGVsPJ#2qbZkk=4)rUr_m`gFYPHJmQKWX4}SC)>I%EF!cmT5nOa z^_`&mmU{>;dFXsw$O1@GNZIU^nHf5x8i~_|U8ME&$RVW*rI*4OtCJ~bbRI7@v@`xN zo$V#S$XrP}Ev)LXr`~Y76(X9TGl=!3tm#wRIzyQ^>e@J-|5q!LX+}E$2{d!>t8)rs zDk#{FWZe}6Olj{bt|{~x;Ywv2VQwOssNh94eOfDjQ~>j7kd;N5{i^ zYsM!{K2L7$dx}NVO86JDPYQxIhHjIi_0-dY@{;gGK0!lb_ErcVbJN;f*0zz~`2>B8 zR(im=d7if;-P6ALUIMq)>yrTmZUrAo`RbThePKh`aRPa9RHw zK`2#(#u_q*VqN|&A8(?O&LfA%Z^*FrsnRLU345PdctXH4RfQ<50)vM@0 zl*YTfJl_jLOjlWG*vXK=@AQkwSUXCdQthXVgw>xcZ`eduHHF5Q$bZ&cF^!0J8IJpygEQmg9-uw}PZKSX@>vUw>-Pc!bH}rg~1wyQ>l2W8@PahIa7%s8<7+ZJc zCw8f|CCB~fOngHbIk_9PmZZc~tnDWUsqI522b;lgvfc0VF0+d02og4@h=`aQwg;YN zCpvY5#T#O5?wpa(Ro*y}#BUNOy^^prIelG9&z+bwYQXA)r?0UOc3rr}P??xIj(3kK!aYvz;^LkI zx`|{yBVA(>cbdmdNZxcTUXHZmoMQno0W-n-?n~by2LA#>H}uO_C0cv`e$A)ldEV0^HU#W3qkD z)I|9X^j;=q%0l6i;r@Hx3f<1dQRhTYLqh~5S4zVxC?v%Eu--|{$mqR+Nu<&)8sp?t znYoBGf8mLUAm;`Cojd#yAq2uMHpaHU;jllsh#$&92sm?@#dWi{vbtVvhyNK>aE&%Q z)b*DFTb~%CG@H!dNfS4mk4K0t`60AK-7(|=3s*uNJwzYuHnn*CW+!j3!FYdT`q?gB z=GGG$oN0|W(@8n@vN$nlml}<(BxKSeDO>kA$(&lk>k%Ch1olrLQ}|cr5qOAQECkys zrLtClz&m=IHw2}>&^_3BQhV@r@QP3Nt~y&&SsAxlU&TSWNBPP1M~~xE^ojuuy-LD| z4}UEresw5L>?dM#r+tnv;(a=b24|Q=1$uad+EIsyY&TRsY^5Y6WgA@g_Wq%NgMRhf z%$qihHw}dbg)3soNt}ZROOVqO#L|ebe7mKd)=}@{{r;FP+j59Q@OcfUi0k(>f&Q|1 z!c*+ot2%2LZ0>k8w_5;oXisSb1o_j4Q4M;>>Eh@!8A>4Za>6UfQj_1_wV}V(?n}SPds(ts;%X;4ZH=YU;eFIQK$0h7 zG1k-j3qy1$x-2?-AC(n$--6Lk+%|-2Y>d9}z!Pzax)81BrHj3$-HK~tp&xT9R&`$!V4Y!6;1w7YqjGihknMz$* zv^OQFdN%XoUHjpa39f~=0g!Fb14t195Pc*T(K*AQ_>yb?Y}&0RAy-F79bkS)mly6A zIJSLSGaq=3L&b5s6V%>F&g|Q_MxbE>ef=b0qpGr-^ByTm9wKQ#{}sahNL~WCu`&nq z>ubSZq)uITQ6B*r)v=0AAg8S3V!6ZmCO|Y$2TRKYhtb{vwJP>S={}XYZw$li^2QwM zV!%QN-ZWs6tX05jI-j(JJCDa#qY}Aap#l`8NFAf37!~DCB_SQivo@|)(p*0VutO``{th~PN@+#SaN>Wx%gdM=YD_@c!s0y z_Jbk4dK!)_JjIubq00W|!eW0|#EKeS2#wPC6~8}Xmq5Aj&?HWvh0^ssF-{Pg6_bfI z{^g%bF%hnH2_0PCrwb_zH?0~IucgtMta4+#Qjs#9I1#}4Fel_-N&R~5T3(IC`;`{n z3+j~zbytIWdx%d3#t`y!z=!t(*0(Xx_}w3v%|;fdpe5)8H5d1 zkJ^$IS~St92pTfNJ0=}&x~DUB0u5`3nkd%!Y3gaHAok?`%+=vUs=a5D;(~Pp#Fu{h z8lmCHk|hlV`YbnB=ex*FX&C05tX*|&J=Xs!b+p#(&7zknCjj|Z~H^gh?VFBaY)9+ucoS8`TVEEOAQFrvU_qEVk}x4 zE^w2WL`U^E{1@SE_BMeC079(?2hu5|@WAr1Tqqhhx3qA6R7>G;< zWdV1n3GMx$=(`*{5d>ANI3zQBWsg)D5l>O`K{iDb#3!s>P8+1Y<9SjYv>4aY42X%N zHgda&m+VmHy!`$WnMQMn>AIsTZc$2EVNqR{oJRZUveJhS2IdkGx7-cibb8548X+HH z?~xts>U>08#yr;`1PyN*T>iZ0(Y}O%T6Ln%Wqd&F9PDL!@{3O(Qq4OlG3gto^*RmL zyVs9w82{D+T%fi6$f2Q3x*X@%YJxw_YiVgGCnVrCR$uptHw+Rv!HW z0r-mch1uQq?ty93xzLIVjsI$a<%5CQugF_v>N@?G(UwH5B;tz6K+|6oT#3MKIsV*N zIoEQXgMjP84a4R!&+n$Rwb8TUZ;w{FFkgM&g$gME3Z-J_RxkM$TeLKihA2w8MF~K= zU@IRh)`JT4*Kojecw00dGMC}WdHj~q1a7pi2U$X?!}!p247AEiYNJ{aH8{4ju}N|| zLoI~9LKTn3fz&JBx(nY^_#01nnHezGz#=MC@xJp;OcO1@$2+M!Bjz4I8;iQsoajg~ zR|2HwErm9B(vfB0Oud_VNB9Fs-6w4LL$)oXK-M9trbegX>CzUT!$!UK@vb{rZ=Y&* zMPdJ&!;ELr|B$LTAjQI`d?sj&S-0R0U$Y7%9B`vLKF@PLY~xzoF3%d8ThhMg*uFk? zNo-gMz2pj=6DDC^Fu-=X0L=TbV9B$I<|Jl}TBJ!(o&-*2P#N)Q!7vVhHITHwQ^QF>dk(+0q~t-o$CEfes!uNi#>;s^;75ZH1s1aIPWl;u zhEyosHk7+}63=1iK%*ovxbuDIH96?9kZyTbR1@DCPLDnLn$OhR z*JoO$TYAfhEt-b?Zr^of0@87f{%UZ0n=QUC67;UK;`6F^-F-q#jkQW;{j{=_H@Ccc zTUKSzNLo4wSq?+tmY2t6?li_fGB+tWRy-Y~l0L;CcoW~3Ll>jq>qG0fzqI&_2q%bo z7Pp^XoO{Q;oWL!gJ5%d4nk2zGz7BC=E}x}*1o`3MYifXwH=lWX6VkhKWYrrfFWG}O zNYbE@2Gyvk(y37oeZd`mY$L5#QFq7V{Mg^#Dl9WJiP}cdw@+SC_KcNow!Wq`nl!Mn zgBhcg5d?w}0X?Cf z#tF3pG!R9A%AjR;k%g{DOJRYTv7Nrq=jQ16qfi4*^4Nd@y-C1VM%1s3ypa+qG^o@L zopZHaBp`fTp0EA(J(OeJ3R_4UPRcqagZdh;Y4Am+hz z!ZIMQN3_EizkJZJ`Bs6RlVgEvXR6X#aB`~$rsq>wWPEfHF)?k>2wv^MgWC^t-Z5rN zX%LDZ?=YMmJ$<@*NCSIhhHpdbRkU|3o%f`q)YR;22D26IB;N^8fOg)ohVvh)bU3#b zy=39OhP5PdL*)&#%Y^nK@P4Wpeq4eeyo@i%=6-Nm{b{PO(Ot%^0sqD3)0VP(E73GT zTkZrYU*j&J+fk$JxoQ!VwjcPhH>U7o4laaX?l$ylr4ThhbDQ-uiuc+1}_Y)-0{> zZ*o2Ts>#!uU5Rn?x$`sKrsu(DmKSH+2NU(xYnD(08Mu>0kX|qusmh0TSuA#$bzU?+ zwv$-uk=vaQxoDfeciy;c@4)+|RJb)YE-v^J4~OK*?+Upil|vp+&@Rl>xwe&M$hy(@Gbx+0Kp;5UVq8Zi=oYhP#g z&^5H}E41f$A-HGdWb+yIc5+q^h5zVlkmOsL)}~$a0BL}Ln<@t+-J#=)3ufG&UoiVU z*A2Z+yJ)@0XnpVCp`(XTMMqt$lmdlf>Z>YTmciSPKIm|gHOAMQ?Z)Sa;J_63_B@ib zx2I!ezp%;N*tYyM+N@1~yHI?pI-)B)(Hc@>8ZqJQyiziu+SB2`dEpKTwmxOq?1@9Y zhp`M;v!Ltjcr`R=K-@4mY^G1Ra}DM!>4!z|rM#GLdl5>~u(XuByUUXK2IT!_SAXD% zr$vC^Eijroq10{;Wu)!%kZ6q0nP+CTj8iKA?!v%}{g~j2qEES*-p?dhJ_)?XD_5iR zpLjJeQlR5T;0hoS95@hcJM9|E(tC2KAgk-{t;@bAGTWa>zHX-31eR^(5@eMR^J(u4J}FVbA;@LwVIza0%SV> zDN@CMPvo$UP$`vZZyA$3u|m~>Brk}|hGnL-_3G-})`Q+I zFiezN`c{5$*&b8BeLdk@fb$TXW!mA|SH_$JyLo4v%kSx4m>Ta z{Uo!vfr|-#@3G`B=IA!hvpWlf)!%iBf<*O6*d<>(F-_ZNBOBy{R5Ijfh1|9#QR{Fp z>@&X}W3(I*58{)}r^A-HcXhN;g@Bh=+quu?SW>rl$Dy;hBEC$ooVn)U6&A#I{hPOh zAYw+YzFE5oX;!0V9fQOLzT*(4;nQ73vr~TE^rpMF{o*(juodMdQwWq1ZL-R(S^{ zU2LP55xTFB*~DB=35;v0MQ#-bQc_&58<_p4zeRS5Q=AVrQzJ(;Ehb{s4;Ux?P#Tft z<>3v9i#--rHnxtTTkb;D@{;Vo$l7l{+fhe;sIH7d4Ry3JII$wl$rVX*(~#So1Z_F zhwEH!M`DN-@>AjhHdAFQ5{#f8ZcfsbXRu{6(tAo^c~{YL@BUm!$T+D80|q*Ji&bS< z2pMueY`W#HC742X2ao{RC9WaXZ@6S8~6$oKM zLELMlB>D`RVVss8%^X+J`2K;O$%xAJJMBXhyqI0b4h%@O(!}Vm4Vl(Hav9VqqJh7_W3i;grRsN6D>vRT>SPX?M?GTfZ#Vv5(-&eNtaa=sDzP80=z#DGNKB9Sdkc|mOf5r>)*BOGwwBSH zKIsCdp^3!7krTvOHTUM~a$HiffJ!Bo*vTOEzihQ)wba#LF^VqAw&hfF=-DEp^Wr%!uSNQv?I^; z8tt0Hx&nL>axAADau1IXE~g*2fo?rz($E(k{Y4mfV{7`+G#Yakpbfj$wHV|Uw}wVA_9;ADjOI| zsnmdRo;0CwPh?e;s~!0Tsiio1W}89|e`e6z0|GUO0WcgVl9jvh99J3D1+4S>a(+fN zKuld77GlIQkc2C0{#7ge->#^!vsdkhMhr#_9Rdpzd`1Kzyxyczj|1j^U_W$74*SL* zU;?>xp8$dXf=5mr559KrB|Pqg134Xv*m)S>Pi&)eF!0`hf5G||2PXi{0HVM;A%Dp% z6r)3j8`UiVnDdLHBcD zlk4(^Lp~(vx%Oq;AY7wHu2+Jrz-rAMVa)IW-oJqXZ19Q~AQrfI3GIncW@71+roMft zzELmcuorY=(M*joiv)iA!wC2$@gcVwKU5;0YM}-DP_VXtS^7%`NHlZ=p}(!6G}WV$b6bs%=sp8 zU2G0qaFhuk$BszXRT1)7F?dabID~4@WC51*pkAxUPCN>kkmF5fVX?ivz1@)u@z&kN zE&>vgV3B9dph158R&{PRkCZAh$Cte=n!4V8lTYUc<2#zYJ?xz=FDLtjb_W!z-^bkY zTa%YZL&S2~Sh?J`azQr4W|RujclTe3<8hhB$lc&+&g;-?7?&ZX4|%U_4$UyR366%E z`$GmEOHBv4Gcr{MGy26)i$o_DXlEA|Qi;{M-L-F|yRBb!zrv1)HiX}Hp_G-6Itgbb z5DN5pP(}f$05S(1k@kRqae5^iNYm+v;IUF9$40NgR26h0J^*q+OSOpX!!~lY%uL-oj=(ioK{3d`yZH=P7WE^1N+ijZ6Lyl_aotD`g44;wjNErEs6RdJhk{&m(8f7C6K8DAK{=p8|gR3AlyN&*6qFJ6-G5um>w=~meak`2^!qyTtpm?HaaqWIfz^DPmeU6 z^eWaY4xWNr`EtYYC!U2N?EvBaZko`GCgvZdaS*J=80wRLcHLFUTVqA|vpEo_=wwaKmcX8Vs z?0{J%BZ58q>tWix{**roi*X(%7$_Xe=(jYe7=bhxf$Cx?Jm7DZvy%m%emtvWp~;M#^T~{X1JXh>54O@Hiw)}#n{Km#MkQUL^HOoi6Dl?#>&wf? zeM$6qIQuO-G9m=O$0$+RiKY-jd-LYNq_|>25AFDL@s!p_%EP1fcrxqm+3-zaLSvtU z?Bupl8D#f^55_di1rK4sGj37;Juwmg6+esrU(U=~r=j%j!NE?*2`)%dL8`?Q;MSj$ zJzQl=*>E;^dXtS!3X;NI#APA~tQs!3jvOcw5=7@tZCcB=Bck>u#^0A|p-LXKGVN&R zy?6gU9x=&vSVo!?v2nMi;7n{Zg|N*U8J=9E)B6AaoC0}Ai_5rC)VSr~CvY>GkrY?F ztjow|ae67RV~BzySm;Q2zH?0f*fE`w&yNl`bpC6Q^5cLlRBH)(Qz|+3)QKRN@9f$jTAAryQM? zn6X*Pl_0X{Rt_-aQrYsUW#ZoUwwlPd(4+otbive22Ly7AoL)qY-amvkBk0Q)-gh}g zzjXGY^1FRg>QD&Ym$;4wvovZY;qW)?+0jwW%6q1lA#fZK46hzw;$S+@UD`;2ecKj7 z?#zoZTWS!>=FlPKxN+ICbX+a8iHXP%BeDeSa{zfPT14%B#Y9T(-Z<*vHVXxA%TUSd3Ii^rq<@3X{sI*o1P@^)WkQ z)T8e`nvkq975&)!T%U{IKI#o2k)7uG&D*y-0SgdphXmq3M?(vOn#E}S*w=LwdV2a7 zB#_U1wCZ?Lp#Xb-s*B~=6f-bfeV|Lt*y*1=MXUJ-Y>YtCKsr4h+v1kcq~yj@HzT4R z9@x9Osy`C#B~k^&qXi~NSgZ@)s92D0JAUVa76J6)O(c+p1&4-WAdL@woI@2?^SsLj+Adnx!NC&^D0y204<>yy;%4;{;Tu5mKP_Clgtdh9X?$xWSPJmUO9;+CT1z zaEAiL`m`x;!MEf*u%=y?#EJaHYGix9w$Ib{8b zq#=#xzw;JDVA{oM+1ElE*}a#t|LU}{O#rL^?;N_cqr%_gi}&#MB+lXjULsX~zBzo& zO@Gg1ZABy%;spUEq?oXGYdpgfAy{9$S zcFfCL6zG5RIm1 zUVcMwV^dv zh9bS}10)UtIgyLCX0HZmM_arC5HdABP~Z&#TY4EF*Ai zq0K~T&-@`&a9ecXxC#XWN9m*0`yq99_%tu}_CAB3*eS0u7ec~ctk^dEwgaDQWFTD* zhbMa9JMhRG8aS1lVBh}_kMAo0i3M;)eX&l!307QLM8CC(J~o?D!Z= zKBI=n0xIeLW0eJ2$5C4ifrXj_RX80NtbA{3j`O6X_BTez>zxjTzsv&JS3g&%MJ${L0Ltt6qqd=TA4JJz`itanSzqhgAC+FFrtH>{+A zi79>q#F*B7(LmV*RC>g(%&0Zc-u-F$RXE-`16sif03pE-X)o!fWz#M$4u%*Ua~2&< z{qEcbO7Ir2(FDZDn^zw8U@#(B{SgSd{msvp0eB@f#GxLgO-ZeOV|$MZq-1K^t`y|t zso+JuHC1UF1^vz5HZ)Mdo}YTBdSPXR5NY*taA2qJS@=&3$J1HGf?zf_HV~u1xFUqdbj7CCRLi%Sc=_(%Ovi923W~q`dF_Yf z-`xp^hj0jnY+ESCvCV7}QihnTiVa|0SHHmQi-E^ZdB*%VP%N9R7&?u?OjO^zM=6QI`q`?kDF}n+1V6tr9xbFYpsg#n>VLA85k>Q#tM5Rvt`xq zVP734mW#`j|Hj3JaM3@*jsNfvkEgZ z%?IL1IIj5ck7A&#hLwRC6-fDFr%$7l_^VN!Jqfu~TVO{1zau0am}qEiqWicLZ_Q<{ z&a`gcJ6ww)xg>L%#W|(w$^k*xg@DRVlLTUzICdi5&FvfAuO(*wc70R{E<~< zO8wGeanJPd#QyP|m-Gtm_sP@b^9@btbbgV3#1n#H!a!&rE9nv-z`$@d*iu|SNnB!c zq%q+vau%G5)y32fh2I5Tw`Un-ad2>2fCkt(rBwthO|i_%ZLyPmp>ks42PR%Y%r)}V zj{G?=!O;f{83C{$^Y{lpcNL>M4^Q=5gxZ_Hlm~p-e^p_As!(URyp4q2&wi#dLmxW0 zC=krm@l|cxZ`_(zAlmMLQ$SPCu>#b0fNLDIp8`YLF)bia;@ONiOnfHK$( zsWnaUbMRtC9Y0^e54Z|W{>sh1izCtZ5=Ze93x^vAh`^)yAqi6R=_=N+{s^@1%xdWf zWkvlEBFpO^!RQ%j)MHrY-h<7(4?0uTZ5puqO6M$PZYG?aDe0b!vqDP;IglmkmN`c0 zUi+3UX9@Wu1B)B&6mx~FjSWv)@v-IC`kp7m)8dL(9T{6wt31y(5CF_hrPT5e9UNMI z=|TiMr$khY#4jA>RQ|gfW+MGQM^X;Hff)10L)XT$wF<76>atIi`Y<&8I=qrR@HlRf zx_U0M(t3O`UhyP?B^pSLC?TgG_fG7SlkD(AZ6~Q3JzYq9pecm;X|y2-n?l^*7TRS# z3JM+%$r%lR;v?)t(ayOun%rN4^frl;;=i*XY_^%@Q2Pc$Hb@ool(n!QL$;ajqB6wT~#(OFbL*#o5jl?MgnyxfGej5_{jb6 zP5Bl#z*>yny&C;&J|xaVQlRk}8VcP&!@yW*y|NXOnVD(0qmp`pHfGn|vfX@vrEn;I z+OO%pc~BjjnHf_(=ShvH>VwLnl7@e}rfA$1L)*otV;q*8-08l3CWNejo;=Aru4Qxw z(!r5FAnCRtbH+wkWTdF-&fxPii;dazz$1xcd8%HY4w6NJIbmGR0e7uO+-H+d0S7MG zU~|tA2Q?nt7`pZNvzBe&-?yY^pkYu@0*lJRPtK<60dAd&pLf^od=bzFf}}*^xW&8s zTZ7E^p*}ZE#-ojJ?DAjWtv2t%>B?w4?Kq^3-=G$7WrWJ*1+<-9@b`6J)zm}A zt;X%Ux_*OOr;7GxNQ& z+0GZun8htGW~`LkShFJAb;gA&iN$ZEu4LwjU0PE&t0c_*lQ z=HpP_$5JfeTu6qyiA~Vjhas#s$G+iK4vL+ zCcqshva;_w9tydRTmBv|`>QM;*kT?e4~O-WXPS?(@3VBQ>@hnrz)&A1mXc z*e{*35p*xTG+nhxj3h0M%;=-O8fkRjqlfRW}%PW!ka5BQ+S3+6bfD& ziW*1jc1=?D_FS@2RMAF_o;R%fw4rU?ky*#qg}5i)k!o5b5leKYfa6!I=|iNg_b^k3 z#ogFz^BR?4vZdY6!D*h|PZ699oN(@QD_e&T-~tO_Ebphb6SbL|MNXB!d5nAK8B5@3 zW_scrsXm`LKgsex8(}Rb`O*Jtg7k;5lc3bUQd2ytVG09`DXCB^TaR0W*1y|q1Ur5h z@ZIgN1}y<$P+=n+{%We(Iq9ZAb zkvNP!d;Tp#OK)oM5E}kVy6ck7rksg0(w|PPm&Ya~c+IktKFht`sGE*RKi$pk?(Zi& zx*yQ%);Li!0OHr!VTI=5yo%@;Sun^ko$rE%$SL=pRQgosaHw=Hf5d ztOKo4DQ^ufvfs>`(&&ndu-p;87iKFjnS$JK^pak`1+C-%vj_tN+@GQz`iKq|u6;#+ zK}UCbF)4yq!&-0=<2xZmtaewr-5=2scKJxs$xhShAgs9&R;y zP3#_VIB!nrF}QaOc7ar+r7QOZadDX2c8_Bx78X|W@fMn(u<#9bcIAxLddII6An8&J zUjTd8TlKaV(5X0SF7<3lvS6uGZOn)b1L8;*G5P#sQ(i$K(b#Q$ieS7rMDYV# zuSm7c$b())n_M)*oxKsMYrlY@L?-}`ok;`C_9y{?p>PnHcnF!duOX@5rZh)c0Hct> zoKF(f1F!~1nOMmlKnvJ^Q9+|TE@Y!TDI(5K?mvGFc5X~M;s4qmMBH%h&{xQ6ht%g4 znJ2CistmA0`j3JzwgEjubPLg8i6T3pMF3^zhxP@Ti&ZWBebc0Q?@d0JQs{ z`LOF*Mk;~dz%x+1b|>hBnS?4jYsHIvY-SQrpF1NtES`^q&He$^?V2r@KYCRq5K`yrv4D2zr&ZY&X(DSWS2Q5?Nw z&#^P~>)(%v%wgb$to_jTkntm&&OaXXwEBBOAXha(M$s3{gS+xA?Qn~)k5Li(BuE&| zV-@f*qd~H=6Znn!oVjUW=D&n|5!axVF6a0qEHY#RSo1#2MS7Gocrjlk%%kC+k&b81 zq{~=0kpRG+qa{uN$d`PngglizcF4-CFx@@0q@Ju)nx(|(9OPqOt1s3xCTo6l7`!Ob<+n|1yu%F>@YrO za&%I-YNV>FibE-k$z$oQb>ru97Ng1I$1mPUVVHbftCV@6URB8R`c$BE%(mWn4TP*L z1~y7Czy3jL@N&yvhN0p6(;^v$|1$ia3*=a7z+G|Ogu4n{_+i4RRtRq`f+QDGDw&;! z`ugTy3K-ODKVIn{7=UhTfgOskTKoH{oSIIkwi`sSdU}p5zNx50ADfL?x}R@tS12&NbS#xKLC{amc1>Ru7dH}gJ|>&0 zwno2l8_X(vzo@#7N9z>H8|H7msc^w9cJH&p-{HeCyKgTW0( z6HgcuWzOX^ZmgFuP_u+cGhvoIa-f1rq@Rt+?A%XT(!8uw9CUy6pmSo}{^yu2poHW^ zoz4DEt*xN#%1o0RFHTpl%x8f{q8MIYUN2o-KNU?Al$MvbMUsCmbzXH03NmsM{P)Tj>6z4F}K+Tn_wX+rBMav4d=z=LVMR8q;&MJ~jvvaa|3o`el4Xv9d6Kkljd zFk_YhawQH>GqAwiSt>#?M7P@_#{dt-wURa6kNa}Ght`ZaTb7x=^r=c0k0^XD7FE%% zWcs4^!s(<0&pF6W@u)oW5#GOF*ewsW=JGtIXJ*#Mo_+WIQK8XU>YfRYSvKPiZ!4SY zLT3jHo#64?$l3?DygVdrX%lk;e;cD z-~9lHOA2bs$(>4cY$8|haqPnObyEm+!BlHEf)t3URakU<=>|^k8BK`cfun9*1Dydj zOoD2)iTnG^;FwCeV=-KADd7IVQBGU?dRlrq0gVq)MAC1%i-hBYhRNjfRV7T*!$A=u zuH8mFi+WbA{4DDvm7ALHc|U&i@1;pBeRd|hG3UGUA)s1;NTTA|*L$y=24BF;-fNXh zkU$zbi5ql^jlOOWbEvyh8IYi5FuT{g5v_kkStU<-Ei^SXff#b}oo@}ivgd_@VvEpj zMQ<+FE18s0K~jO?wAd;T=Cl5ONpEkUrs$=7yn_waD*t!}6KnIUY-|PbSN#}(+b2td zP}LgeBuU6+3vcaxy6>RTBs_Iul7u1JwinUdy@|?oT{wM+xH?_LO}a8)D+fU`Uiv1` zVcUq%#@3cTWBB6d(6v2@n4itRu;(}*hL>9gTGRu-4{Lmbp>=)ylA*9f?yZrH&COte z&gj-<9?L%J`h|z329gW~ji)I-Gju+BX;aL;$Vm<1+Z{NlBsTy5 zy!SD)xNB-`QNHE|iwZrsqJ%H4W)MGuz)wIH41NTpWY3bnVBb*!b50>*IDUU0swCXBF|V+4d=N6TqoRS1PSfyF+mJZO62@0NR2}`)Efe7cjeSVvxEVWvz@397c#3_VsEFB%v_Rda_r(S-@ z4SO**5hVbXKrlm)Vr4bN8M_3t;M%1WsaQ!A_*5$`-?ERHoQ6=ouX3mHwo@mgi8NYG z;CN_!tD3x|96Y!%Do?x5&C*jB>sbODzhn3SZQr8Zirh+eg0@KK{2M|QTzWXt@$ev&4=Bu zpn(@U9{^78VvCyaLjwvB>CAoSc3V#)SWs z3r0p>8ba`W4C10nfLz!*{^{~NWn{Dp$MpJ|a@t~?Mdxv3q1;y<`H_Tn<9f*Gf^?C*#tdH+S!P{K?$-(ofzXzG8tW%8wP97~Y6V zL&Lg>6qh+VcE~Frko{wN^@-reKdU3&qeysP>BfVD$M8?0L-f9r=-U1oJC1J)ReJ5m zjcHU??|UxbwDY5;I)aHbBO`;1-x>e$;7Pvji8;#}%kT$teY8wG5Sb{&FCJBeEbkaQ!Zo+s@B$EkW-SoZeS1ojayd>W!Wxlgl3x?jE1TxX7!jh7=UA z8By{PcEw~$pB~zpT3SZ*>D()`nRJ{4g|4}JvyWj=$XvX_ujUv_$jhh#uUzvpUVPC# zh^mr~`{^~GZ}%baW-q*C1SXPLgCFvFn^;%`x3rv1)U0yW)YqGDtY;$@76MjQ$dF$9 zNjwSeNyZ%|RFKI^8qPg*ykh6+iGfQk{sIyesJ`CnX-Z5?=rXWx(nnK?aWos(eiZqg z%GdcKY8p4tRP*t4hvWhhvANDIuG zMlV*Jp96AzZ~f*HChU%ZB%IaG8$!rGr~Bt`JOE3$`3Q+DLZPPEYW=(*o?w;LJ-GYC zrn5`8;>oQ+bYs8DO1}2!j*1H2=X<-4pl>m@e$}&(;9&Igqh`J<^t!i>a377gq%iw)~h zD6Oq=C=W$_44NLUm&r(B8>J)VhDv0lQpz5c7lN6etgZgP}% zbGou)nWy5V7XA6pq(puFgK>Yn)e~qy{MNa}f8= zuURM*Xr-m4TYGy68@=4x(u1Tyf?_gv&hQZgSM=qbW%829#3>?Hf;N)^^J+au-%Zi= zwb5E45IKBLumH<38D^*>z4n+ktS^JOwp;y^gizRa4&uuHqijF9hX+QXC(aFVXyFjd z?aJQp_C`Y{bB7)0eg|9{6!i@aBw~D!fnNA{BM=BblIFr*p5=#?&$-OnBaV04KD^$Z zo}8RN4W2XEPZpS8Sqb`}Dc(}EyKQAe`mv@?$K|&E;b)=%owT0QMZ5@nfUNrq<(6#O z`v)92S7{#iAK!z6fn2=H?B~stec#?LpCEX?gD{1%w4^&8>)Zjdxjb>J@m)wfpT71h zEGo1=-jS%;wY#+|>|zCohoqzOVChgMv=N?EBP z#QPM_1WxPIt#YQHO&7#++n!A*!BQ7p$eB+TT{8c5@J+}V0exh<`Z)2DmgHusj-r2` zmk&m&EqpTXXOhZ#sHMtlH!F>s@%rRa5{Gd|M?@06>p|7CB|H{Oxkry5Eqtqu*rQ6& z$k`aF*U_=L@QQh!K;JvJzd3FA3ipFr>(U2HMPgna?AZX!T^B+wq*b{yHPsSHj5VZj zn;pO&q-Ct)G!z=3GxKX{BNC|vKrh)Xx-ZL!xpKo@#06XO%+rP2ZT7e5PH!SxL`tS= zS27$g&g0mzG_Q_D>wQeq*|huWccgRg|6=W}qpEDTeqkvoB?Y8ax z0Re%9G$IXxQX(O(5=%fzx=TPnQo6fS>YK|a_OthS&$rJQ=ltVX18~8;?(4p;Ip?ot z_IW+)WIH@uF_D$D^HjH=Hs^c)JuSk93Q z#7iYhjLukOrqUFaJ|5Wf(Z`}#xhS^tLjvmb?kpD_gNv2^czN7|#U3S`Otv;QFDL8I z7swX&&kRLis=&y?PNIwgU67yu0;-j9StPjponlO&g8%IEJbA?nY;i)D{b1cM2ucl2 zP4!F_%8_yxLJ(`nD?L_%wiFB3sN3$@Gk#BlQ+(5h4`+>64ihO&zP88m@mxSVGxtQq zB_&y4+pQZJK0RqtO%Qjx)ix$kS#HwYDb>)-^8WcQ`v+qr0jl;>jpvUdR3W=iEgUwmXd~0uo$XdDyQa=rhmuzkJ>@uYp9aL(G*YtFtl=N)l4{>2PmqZ^$8w$$^q$)J&k0q6)VL*W8d;X3D_qy*p8I43HAmmyEhF(`Hp#>PFnWSm*XT| zFGa=Ht?g4#(Owl{KR@rwld9S592g$dSmsDE^Y#aW5mo3#>XNIxn|b}$UC$%?KMY1- z-UH^hVDde@?yrc}8y$yb{r&yAJ16e0;p+z-%XLuk?{SjURh0W(Oc8$S>pN z%aPV9`(9^NJqMWmq=&wU@a{(S#PRnmX>nXO9)|^ARFtxVnpwxrGA#8Wo>AIuj2G9h z!V__+g89n`nSd62&~b4{JK76$V&~Mt!vR{T>ykvuet%ntI;~7(jHS%F+FCl>q&E)n z!8cpH;mjIuxR&GB>R2wmh||&)vo)4pi)(N+Vf@;ES?#6S%E!fZ5vYlEAiTx9RAFvD zhw%88{XsqLt61uKIabJ}Xwvi62=uizTg#!;kH_x28ovT0I8w$(B;fJ*y?oeXn22OOv9n-t-gd6f8$BYTh`l2CGmy4ss=_LNk-3`T znxnZ=t@S|y1Shbfw6}+kq;N)r9jlsPP7|+R|M3Y7EiF2U|EM>YjG*3J>g2Jo zaQ}TogxP7`xOPiQr&jqf%<&pQnqPA8791b1JOBR6ZwL0zO6JxK71kC+CngHDPg;j2 zZ{%@iM7UuiiK-pvIu&#Wi^RksN9-~io2xQEkKS=E=X#{$)8~$7=HMUiMpygJvDi+{ z0`+*WgoJKVQlNRAHEp#v_hxmq)->de090?fi(F2k`oN0cQ>1)uYHCV7_c_^${XrQs&yg9Nx?VSGG?asciA z+Zl3u)px&Wz^Fb&CgADmv8xly>q1S`^}(Q=FGV0HioSetpLAFvFglH>+@<{4nm1%kGcYk}d5-24J@l)B0>XDuN1ZbC~DlA-R73_uX&$Oh8$W(lwQ_-+n)XnvS~@0H^XYO0_4^ zkWrH;jvcAq?ca$Hop~SpMlGAi!n~xvn^{K9f2;0O@y1}rh1zyjf0i@@+vd7i+a*ja ztX5lbadBR!rF#cQNA1X0G{U}jBu_4t*p9w7mOMM&zRYWi`AIvMokJmL0BA%H8yXv1 z2Y8IDLLvEk5{~*fKqjEf_kPP~(dO1*YoM>MfB0)-vffPKqeZkO`D}Xi(VA#!X{lxA z(dp6pG^B2Q6Fk59U~8uJO?RQiohUASZAWAvkA>Qj5phCd6OT+v{0kuI5KpU% zvLxRRF{~h@6O&pWt3>H?;PG#tVnS-XP7;g+XE`1thRNtno%$=+C7N zrxVAMl$1R5xM~l1Scb0EL>2q%;{h*TU`Rtw4`d=L2OR@c*^Z|HX>TtF-*R})x}3Lz zL*}ZVEhGe)oq-oY)Wcwg(5(=YA0O1?_^n9bWDSO21_~F?2>XkaH~h?!`cBdwd>&GV z%cgpgZ+|5b`sgLUkT69l9=Pkfzcr2O&{%Y^hyz|%=}eg*4cE-hZ-3z(CImjR+fn{! z6UQDpQTc4~^`(+2lDZ;@i@z*}_$=0MF;rlNbKye6+3CLI;jdsa4pgdJ#a0`~V2PbE zU7W40?UuX2Ri~FbWU&W@MD=Aqy$&V=5W9Ukn}Cu(XnoDZs`crTTA~P$Zhp8c^W%fv z>COVYkfeipW4k*|f$&fnXHLMn|GiajrsYT}F9|7WU*%;X(HC&IW=sgQNKao8f$Qj_ zq_n%@V~RqlR&Ebn#l4IYRHfY^#$fz2xJyEqx)+6iRk;8A`W@ke3xm%Nc`0uyp3j2d z*WPLf`1H3LmZT>in!rO1VlFB#z^B-X-R`Y$(3U}@*N5Xs%YP~;o5@I1G*3;ZwZ6K5=22iexs zHg&(+6HF+PuT1LwTak^2rAPbg%`K4(vjZ@aGEijZZ929m*)Ptifj;N@po`c>o^=1wqqHz#-_H<35lnh-spl3BU6En?v z>dA{U$tnBg)hj}XStO>U3<@G;nHtQ0`2XbELXy2>bp-tUjW}Y$D?jPTSq?fSg50hT zH+G7OLVg1ejl=nSaCMs8$GOr{A=iEzbMs6wH=P#X=9ehlVs~El_xEobhs!Lq=!%(* zrr^}|P7-&6SQSq!JR@)ufjRd5q%w|~?7`8W;8pG3H9-~xo0Q*35J%H^p#>qNw9yNR ziq2EZbm{`XE!F~CK~}cKuyN{%39qCPvp>wBU z`A$F6NJ@r{ARzFyhIx4%&KM+>wmMq9d|&Ch&CCpmp1^3heQs@SZEJb1<6S%QC2Jx? zaoUc4WpWwoj2FO1LqkJAhLqTNF+61(J3D|99B$H9q$a~7-0J8mr#IgiLXie**J1vt zCmed=laLok1yw}k{BE#AxAe{&#S7NP3d`B6*PLphmv2t67(ma427=L{>&3k`?vtl z#H6vXUBmUQX~VB84@^va!U6yJbF$R{ldYM&78qQpm)LUQpe4j12(bxi1+|^g(%;emAOVwAVCN z1J3Xzq6TIGL>;gL4wgBx$m%`L{~1StdkG?Id9du%2Z+`OP+HxSr)EgT_xSM2&@!J3 z_X8?nQDmz{FT&dt_5yZhtqW0@Nx#osxs>b)+RdKp-xBw^I+Lru%1GEV2B{w!*LUcb3>-k>t?B}`} zWe;zfakK_v^j8>QY!}QuPjRc>RujeY7~iV=>aXaKRdAL{A{+&&Gw#3(Ds}K3(LxIC80Fl{?ZWTEel^_YU)=;;^hz8`x7;m zv^RIWk#<~b69U6eVpSd}QB+Ks(5XutZd(7^Ssa9&SMBc0Z{moJ_hJl))8oUf`HwKn zDELu0D8SjKynW3l73zJ>g$F%y~-YCx_)CyY;W7gg>x zrU(1H2BSJa<4RbckyY6I{8AU%RrOuv*+}Vt{Zs=Rjj+Q?P%HMokX1H>Yb;_;Ui_=}_xkU*VCczXM*c_!QDn z*zv^FTs~R2M){Y6xINO6_v%BR zJrbLF5ig(2dj7*>^wgf1+}zwx^DV7_+{^$esGaP82i0Xy2btt;U zfMCt(G72$W7hxip(ct9lERQ%)-Fe?(aoibJ4YW=CIrH&m4%e2;ikfXSS&j- za)ud279eE2y9F(C$&6zS&h47-a8rMS7yp8kIG|ufZuMjpPVDA}y&UK1Q{gX}@UWZG z$0>KF1;FgJZ;9i4_y_Hx$0;yvIN|6#G(0#VnZ0G!5Pa>yAH$qEw(gM>8EaX2c?QOr zHqH5||3BDNF8ACCHo(PRO*B534p4VwOw8W#vq#nBofX&FQ5A;u{lmE@f0!eS{BO7* z9BQ?qj&oVMmCwUQBCV~gg7(MU@NqX?@R$&Z2{gPXZ$83UoRZtX{od;`X)GPU>8zB* zs4ir`jdk4wjrpEZLW!wMp#}hU;x43uutgoFijRgaZ$+L=EjVn-c zzILr!=>D*v;9d-pHFtl3m)Trw#SCaCP(1h1>+h+cH!0Q3VA?6Qc|7dEn!bki=E?(e zTLMvi(Km4%EQ;ulQIxETP;v-E0uUGG5i%MYBt3jR_G>Gy2M0e`qbA508xJdaE>Pm= zP*4mRgYzXEUeL2wn3?bV1#4ar7pHx9^DHbh^uhtcEyd_W{Z zx*j`$1ia}#G=}brFYklSbTip75TS7uG>;5 zF=}6odS?J;C<>2@WC1;(lmvV+x?&wfx( z+lD9w;kp3CFiOd!20&nOE{RW&4lc3j4ZgfIxJ@xNJ6kkzConf_S?5}*3{HacU~7VpfOyFNQ}O?d77EdgE#iN^IWswP4p&q zg#MR}=17KSys&H`OgKbOg&e+Y@Lm!@V2vzjRkBOh=UHEa)l4}xMf!B{wWlOUOIXvt zFjd*W-fO zl?CO5#D{<8V+;9EFfwQN6BH7Mi{8YF=;-L+J$omNa2)yhQmrSQaIA4}RTRJS#XxT_ zDOmT_ebX~06FFZrru#2W9KYr>2M5{v_o*~A=`TMSP82DY+c+eWm215G1O)ZRYQ{^cKV?Ay-N7K=>j8E4CJJOToO+e%9E?(XOWG-AQdHu_gY zM98nk=G8Sf$5jp{3MFQOZ1~I&X*26f(#Wx*B;Kw#0jN9vHNln^l%*v9-+ z-X2Rd-HAK+L5%m@@Qz8oJ_GZWvC?}EF7;zIGS7pyUe=y`wmGtev+EAeiSW|4C2bV+0xf;tR@HKkyuCh4WcD@s(?(XhBus`Gp3=4~%vYn_w)A+)zTdM1M zGS*P?bm<-u%Kgp+j;Wexj+BB|f=yNa?@DYR*86vSE-7rTducp98;P`fKRci^-Q)>5 z4fGPphZ)B!IaZpI6%um--;%4ZgEhRJ-K&C5wNfWhbZ2MhINpa;DD)#DqWy2Ec`S?b zCwmgz#%^x31@`(huwJ*B53JkO*u1l~c4f<*x$GzFm}n%48L{k=#|)tyl?)6FjY+-t zW&D5PB5rl1RuVN#u^ax1)Fsy$ml(C*84MB=MR44EdP_wm!Cm~lj5XtkV#8wZeEMgw zB4Y`s6AcQX;A!`4ILXhqA1rc{yurtpK|}75EaoKNdpgs~Cq&^+OioTtBtbIBooBn4s}8+i26r9~k@Dw#v}N4Bg70PYe`M=+$WyH>Y*6hg!j;y@X7h}@tBl9jP%7i7>ZvUtAV6PU z=O3oO4Smgh!PwMaGan1@pUvK?%@$z4$%xhmtx%sP`^%cd=X=dfOa>w& zBU7>h2XpgDiF|d$B3Z}IEldO1!Q-x3GUJ2$zqeq!e{8`w9N97Lz-)D5WhK;k^?v)7 zH&|ufGBv%%ZGaO=Um65#=(mSvKTsBAPz#UrWv{hB5b?>;Ze)h5&T+vKF01s0=V6~1 zApdOxem{;j1LOjOf^M1jr`@O^PkmzBBMxo}SZz^kFCbvGHtlseXEx{QC;?%0P@}RZ z0>o-RVukEn_LDpyW!aB8kB~*R0bKO9$SSlx-MclwotXZO^r|+H6`Ym@P0voDVL9Y< zvw^tHXaZwO;d^a;G=)+g#3+ZV=EXKH{0`bzdw>7Sj*qp}%LYyd|>5cTs!X^L!zE~P~LmN)xDw0jy&8<8$Ws0fMpf~(Xb}!SR-G8c*w3RQ$ zA{}*MsL1-&u~bb{|9K1F&dC7*FQ|_VtCGt73K1B5KnB=Se5!uKbYlAiGEhi?ED)(w%}4j4 zt5>h8;43I7B=($+;naJ*oyk%%LB@0|{-za97dxHQ&8G~<ndhqK!yFbBzm=w}=z|8kL}1{>+fwVO4Ln>IiE(g% zNV6MH+B$PL6gP`#E_3TZ?yR=lg)s@v@CaEMhx1}F?hr%CO z84Rlu98<8?w=tf#8a4>O`zj_b{-||fce&(Wyj{Hn3Sb6Mtc*|wf)HwH2LoE6zj=c%wpSe>eK-`?nq7pAQ~Ib}ILb<2t>o^GxT&rUHJ{9>hC z|~@CU4{`3x>0P3ZV}8~FNt() zYdK`|iHcH@34%ORtwq}u+^xuDr+&Y{e6x_u!F)a#%c(oCyg>A*x^s2bf1iVM+Mfgx zIR9*zXDFhHDhPXfqbP(Jn9<09zh4wf*vPio_NgC-l2)PacuQS(&1>t)b*4_m*f<*- zo9nJy*js0IQ%{OuL_e_&E@j(QvA#MKsZW&A z$;J&>3pk38t*ryn)7`c%j3r#WE^JRg#eWV6SZ$6rjcO|0Mk}G0l4Uv)4%xJkMhx_I zB*~Rk`(yeJ3Rwr7IxB;Q1qY%kN7a<^44Yfa?c>#K&-a&n-WFEL!f5gb5)M061m8Fx zg7zOQ=RrTQpxbH}!6i;bmg z2o4=*g~CkFEj}gt^LYnP?&XkfMkF1G39ubjx#LJx@T`w!bD;Qok|-o7ZKUH}VXb+M zw~^5E9lw!Cj{ds?d1$4{?$zIcr!?d?B^xn!VQgNcs5haO!<1&T=fRV~(u+YsuO8WE zg|8Bkk#Py0EIbE6nNsK~iVVVv*ltZhXoAbY4L?7z;agTVHeqt(UCr-6L^_%s^A{K3 zMbwWdbeE0btP1&8lv#%gpVU_GBTQPPRNn1bRl&h>xZ9U-h(qZ0tQeE0_sW$StBc6T zu1N;gD+A|Zv}3ju3sPi$wF|P@+n)vfFp)`a9v9ua!oWZ({#-1eFZ=9A0@_ujpq-T= zy`*grpxD-m&b&MwEL*Owl0A%wmk`(rO_UMe*CV|qPrtr|s+3eU(9wq{z@r-HVr8aQ zDj>vl4f2c+qTq6X{&jf1k@hc6DbxBy2e&@%;)$fUYXg(6t;be^*sj@fD`np&EmEC4 zgXps?Qc)ppgVJ=@YOed58}W7Q6EtC{j7%&fZ>~bq7CnjvQ`$aILU5jt?i`B(ke|#a z=aY5X3=8g%R!EN!ER0h~*93F?!MJ#x5w+a6-ROOK5)l>Uw-fp-LDbDiNefV-zDj)x zut>SpHgG4iE9xvsGMne>&tE%hK@ftgNa;3NCl@#xb$0A{ZJeY7T=`|7!qm8n|CV*; z>7#ovY4|6HBOb-!D1CCubOljjH$ak?#|`ytEK^v>HH2J=#F)_sooa#{POgaP=&;Y9 zsfFxs-~ft-qEkI;lu;MnoXh;D##g4`0SOjxIcULsw%$*Si|f)*uA;c4W_k^( z!koUZK=?%w%Dq29ML73HZ8OufGXXhkTp>K*yFNEr*k#dPaOKY0#BNhytiP2K|DAWg zt^)V0cy3H7dStWqX#K&lUl452-QB!fPlD{HS=t28SEvMdM`TTXBii(5o+ml0Dc5>> zYoe+i;o5+&yZ!TddZxtpiZNgdC5SnlUtFZL>}#Xr#9Z3z}X~i+t{k7E&<8LXyn4#G%+2cSH+y1|*(Y!Jhy3K)ZwGmGlPp znbpg=3s|C}_RjSKmnA0+^_EOpr|isv!bDw2%y>FZ-lgau*Qjbal=wqvD}l%LG=$>G7jSvE2CCMSLbPJL5-pcg;RM<%8*&cD6Q_ zT_|nX#5Tod@15#Pwl$_-m$P{!V+q665^tWo6%S6-GR5BB$B(_jyw^Gs;z%GWIY12R zv|1mGvy-z4E85`1`#cxuyjxmW4;>nV$@1q)iiEsQrFlC7M`xJ7e@;P)InRHu^#V1o zoYvFd{LiBITKtqn_+#D#Hr=UmAuKnFpA9)QG>D0mM^oUUJWpe-LHZ*#j@kyVa$?%) z>Rzrg{-Tmp3)MM_`jRXBV!wB>_p{ZuwjPOWlpSI^tov#9-CGO@3ol-oq_WRu?apkA z%%SugT@$09O&qJ3;(;l{+CWJFoIsq-+8m*G(m%IVcPk$ZR-fYFxDJ0Z<9jmnRa0pM zMV!pGpg;*!A|tyke|(@zNK8ndA+wPG@o6?JUczYb%b^{cQ#rVrQ%{yWJ#`NhyOZj~ z;U#uk`OR!%W3)bmo)6<>D2q-2fkcBKWY2UykCoxgxE|1(ezi-wF{3e|&=KjFt)_q7 zp05)fmg1N7AcpLC*h+WJD6a~W%tU@@mV@EvD7AC!cI-Q08CKN|i2*jLm?(pj<@JJ@ znd#VKU!9d<507uGzzX`9MA>PUxKTIZ1CJm5efnJuzk6>-a;MIMX4_*MTQw~< zCvL@SZ@W0KGjxStn&k{TOhpYd=XR~y@;@4G#5>pu@7KX(kV{N-OcBI0P$!PL3*pw5 zA0l%t2a3sPltiyEix9MX>gGk}j*S^178^}KBOK{;cqdT;V>5Z8^=$3Chy4MOLD_Sx zT>T>7+}w+5*oj~fU#*lfx3m;&u?o_n69UQ`L}64n{a(HlwjHZ@Ib__~%boTlLl<2- zNhFFLYx7lgwzX?uA*)}`MG5+A^Nfs7()@QP7WHt==jI&tSM$?YBHhob+zoMt0gP2I zHqd)yVK(V3dr2)xjDcS5cLnro%laODU$#ypGU#TOT5^4nwcsP%Ig3OAXv+p?8*3E* z5|zDhmaetFJ3M*v^oZpwY&<*f?-ess?Pj!_AZ)Hg|fQznr@vgPM|%d+u(ic>r?J$6~mr ziat#IAjo`GD4z`X1Il|`VhAiM_Ev-gLxZ(7JB2AoSk=+RUX;jgIzA;~1ux>UYm*$2 z5Za`SDT>YHBZe8pjc9yyLw`ph)6PwpC)=s;K@5?JgI*{1v@a*U7n@UI`&Y?$ug4^u zJapcpFDvhFwh&jOa@w0~lRqB#6f`&}lyl;UQCDx*Cea4s!?8-Yn(XHShr7|)P|j}h zFhZPqwe9c^LICsqln=LBT3Q<56VJCjF$SK~Yh(z!2FOul?mgrG`fM%m4a@>_nr3Mz+k{O;nW4{pAW*@S}db3f!BIhf;$^YRm1;h({{lVQv;7 zt{iMdU)RG@%6Z}ECo1G2a@mTIzOD;NC&sSzBSJ}0u`zEH8zzKk<01l3k>8SdN|XE) zyLMhWBIhvcqvAb#V0)sz)&ZbmYtX~xaxc)8J8!Y?v=f=0vHqEw2%Vj9p3b`)P zq^3Q@|3;3&k9|V?E-LxKeF}O2y5$JyDtRa{apS-Pe9YZ}8*4q*QD7eP?by}?h@Vkp zCUQy5z1x2c)Efl}ApAWaQm^jnqCY08FkK@wPDI{%;Au|5$+LpJ#zIaWV!GD8wIdhf z;`m7GBX-=o_R+Gmmo zL@*rr@cAw8QfzK+fFU)~o%wxA|=i=%YtQp{2d~dW|W?*~YFc+<6`RitZHIOus5E#Z0JvihFVCNIJ@u z08ixAyOL|^{GR=lsfk1tP-0vc6s#Y0QxtMpOO5Kr0($~5D99zQGP(ihh_b(^0+Y`O zyegwZDd$jSkRzT^?~%_$=q}Jpbx8=Ukd;WhfJ>%QIut4VAK$Cn;5sbnU^ z9c(P+S`JKW7&{ZwdtnC<(8nss>@JKc#;kU-b#pqeJ1Q)1k$u&@xswFSB>H^`Tjeue z&KU(?TI-I{gkdIuT|?}$FQy7=l)N~bz{l)E0^ih6JrEYcy@cB9A~&L2z_o(OH}S=5 zDLVAWBo(HnL&XFwPX@8{=!(!-bl!9oyDk|<{c`lzrg2c>H?FwsOYdoLb)9mn?h3!{ z2vK#8!9{9o{R>n-=mQ97YkysQH@kdtv=5n4r>IdwLAt$^lqmm`yYv~#vHl>~0BHdc zDRsb5;e6YkGz93FPt?DX=x^YE*7D%sP{5lgcfQLwYCKuQ>@{a^b}o3qxdjrkREGQ{ zCXN$a7aa=lDrCMa^!ZE^YrS_S@4(1#Tx4sES@nd7{P{&|PyK8%(VIPCHHDb#rx?vM z;hU%;gZI6_8fZ1ZjzwH+WA2OVa0`oCQZyk>puMevY^z2WFfRPEm4%_MyKwcX+*#LG zohUnUUqKOO0+{yqSUI2;w+Pqen>SG|-7iw+q)-AgusxvpZ)$$Peu!6SqA<6J?!KGo z;1lMcekA-AJv^_4ygGZO&Ecc(O4}>H^wMvpb~VRyFU1NuzY&Zr#9ekjoE<1g#+`0P zF@ZSmSEzW3gHWLK*&<#Hw?21u=TL3ALmb{E@NKNW)a*W zs{k%*)aLqNzWynmbxK@@#Q-aN`g9c0t!b1bw?*!Gz9>wEyPK`>Na)*hp4o zx5G9qkn*(gIlp=HW@BjHSn1Z@p8oX2oHm+8Cnf{+kmcZeI#W|qUW+w%SEKcbT8yVB z&xM><$3o;UE~nW~%L9LIlW*f!oO;29Jfa+6lYstFAQAX;Owo-VI!J%%sJDCjcGq0c z>_)!K2Hj{c!$L-scR3M&4SRrIxL$U&#*X|~JR{yz)@IIzl`!g(nfqcCG^xa;#Khf1 zug81ZG?FUawr2Enb#>n>&+j23V*DFKT_WS-gQI?62L=YdwpgBP8P6KkFU}#wvb)-2 zK(pS6&&z3d6)hJ&)yFS`!fs0;G4U7>X{mL)RHULTXda|b9k+k%6UmQCVj;nfoY*d~*Il^_hCvtZyDo?a>jvSL*$uPw=s# zk*qO3SRLLibzb$a7;SCE1v)*-?_p~hn7u^-ZiC+(=0fQYo2&^q z{DT%$$^_F6aX|LyQ93CnF6(RW++6XI(QNh`dsBGvz9pr%3+O>?|MxAbVa2`F9H0a$ z4b@yw(66*n-F$W(ru0lqb#M9Rfk~ycvGplj$Cx&VP%_~Yu2BmND#H)VW|1hM7r24Y z`G{|=v@Cqk{dVfWJ@@Txkg?|>*~zcH*A{Yk!khI4$wZW7Tt|yPODI6?`m(HyvWHHh zVQZFy-&ba&{P`~ZLT|Y!6dVB&%CY&jqw*K1f1!_z7+mdKBc0a)?r3WjSXNx$nx#;0 zt*2ci6>CLGd$3T9${u4tFM(& zn6+)nRHaIZdLfREL7z)cZ^r}U@5%3z4*{JbqoJXJK@q1*0WHu8Icgw)!bC%xfn`7; zb)Zn<8(n6d=Xjb!Tg55ZP*?{jMW2ptUbys=`}vKVbm+9hB~c{%-zTTH*6%1Q;~x@* zMwEaPi9E1hjeO05%wsJ3+v`lT{xmjP@x}~>aMqF*oFSh&csO7S$J76EAr>Vq`v!}EgYD` zAV#zYgM0Uq2l-;vNaO0U4^w)S#$JU|zL!LYK#&+w?jy_A)#Mp=-LYS1OTgalk6MH! z&=D<5#S?WV*5>e3z0Od=Kp##itCo~V7S=CLzPT#{qZ}&z;wP~M`(PIFpmW3yhQ4XE z_7y6z-hfv7Uv#6BMPg+B1&hdu1(O}fhG&b2YQ zq8UGOrxwwS$M25-2c~`TBVSjZ0d;ZyE4rKNQ%wEGVBPWj+OvF){i#MGCbb}5>OiSx zZw1l1E-d3HLLFg|82=I%Cq|a>W}g?$J3!op@!%Lag(&wQ4bgN5<6TZD`9iQ5MAeCY zGh48@bo`ajmrw-Ga4`q+OqAV=t1EoEr|Z$gth0CVs!xi^VayfZ`nd4Leu!T3RG8p+ zOabRS$!l$#;q7^z7CWuH6(4)M@Y*AK6<751@^Vq;Dp5Lc3sS6NUOVyZ)-b4Ym-7_g zI6MW@t}8Bw!P&rxh!dHWQ9)Q=WB;mE^Tmq3b+hrU&lHvL7So7oMF^l~>^xSXrMkFc zsvE(HrS_Y+I0RL!&ForjbT7{D)dm}T#)@_(B+_)3IbyRpA^nZ&XWLUiuxpo}DKC)t z_U)}@K7a^3)zx`R9d_e6ujV0tS7OH2At&TvH7MfZ z!lp&FW%s5n$@PoqQLIWhd93xA~idNRWM7j!G1B%2dbo}-Xro)RF{ z=3AG@1Qah^{!ZtzHUi?Nvmg-Th(E-(BNU2ZOG6cogW<{7c?qvvwW>Z|vIf+#WnCv{ z!9Qrd>ZJ>d+Us(Ma%We9s;Z({TVrc$Yfz1#!X#pC8Y@%3IX-$6b$ma{vADRPZrv;A zjd6(Ml`O9t7wy8`^poTcJAY0SQ&S)*$D4-npc1OFtEM;^(V8_&B%WsmJDs1E`Ycgw z)80FwXKew0I?S|gjsKJ8SSH^H(OX5kC_^p-3dRSzzlVVShW0e7`sgEn^XeuDes=&U zX&2D6gmjqGuAz~kh*S0s!UBE28C1c5v6fj3$Hnz1tvn^a;h{Vt?n3b7X*V~I>S1nL=7dF<@#GP8vJ2>XEW+BAO zFF<%t=1)i+2T0l9=$3+L1AR5{SiYyqX*M)AfklagTA~EUJH&HsX~Xeh&hDI7vTUVy z@QkonUZrq=Zvs39*C_IEF;#1=(G}+|g`oz5-C2QX91)&vuArnnS7ee>GlL15`>LA>e30bu3eoZSXo`+_4su!LD;eQyRS<_ zpvi4j#lDpmTK>iO$^VFP!ewXsU<{OOYaW*LJ`^QBU0=x6=Ni1g37Ct24~Tc`5n zdM%Wb8nBLeOrud-XhzWNm%nLFjga>=fQe z0viRp)_k6o7SuZDC^7OdP8YvjUmrjT`byDoddaH+1UQxfYP6ygor$ujv0uhJWl^C^ z_cy+N{W{TiRv8Ke?MIIuO|?WmTo^!7fQyb+c$RXIdACslhLHOp5zttqB|W5|I{C9gYpF+qru5i1XIUFulR zz*NvKJEkN33i*B!01c)mO@I2vpaGose@3qi0oFSb3=Ps}(oGFzS3+UhDu`PA)I7z^ zXb({|Kb4{H+~k05~x^R<&KnC21O+iuj!La;hPj9c;5uxNU z(d6Xh2Ov&lswD<9tH!qsIIj-BL@E0~V!Gv4eX>W*tPMKZXna;UCnoE6NM5m4 zHVZw{+40{k*gPMSAv6|3&8bW9$N^L7{y!O=yPabZ4;MXd&`4>azGFLHMLRY&rg0Ge z?p=LX7a;_N_=DTorLTE;d2t~3YK!3t0VRw{dkoiQNzV%T4H!LAfJPh#7q|9{RqrFo zJIi<6J8MHQQ4CyI5RCu1AR0GQG&%!JV)W%l1NE-fZOS*TFM0M@-Vqe|CWFgG^kAfjq+yh=(xQp6k#p zcm!igjy#YhJ^+!P#ui=*N8ax8KpK4igJxFv$RUUG1~W5rs=_O}z~JE5Hc5TP`8B32 zD9a+0A==AtiV@zgVSTf+>4!#x85S#n2`IV;wdDRWSkW0>_Wd8c8=}5Uz$sHcSmj~i z101Bhy2J5rwZO*Yem3=mc3BSt@%*{z;P+c`!pqsx&F}rG+hd&M9nYEuzuLr_i+};* zZDnN{5a7H}&DU%$#*v`+{qW)Pi?FwR5rab_f742DewpxCEgIOU#iqAZ;@8Q&PsV5d zEy66Ok;!?dYP(Yu_CtD!qm{y}PW31^@_m37?8YTf(!_Y@uBAIU#72y$3_YL=viN{+ ze+_H~7cV5#M@sQR2hJrQcYZ0@cR=dB@CDo%RCwHBdz#C7(o+Za7i1jTlDw3#8_mb^ z-9?!e;~AOd>h=a{Q`7=0h!$BTLPGB;q=oUpWqs`_=X9X4_9=UNX}ISqS%Q!&9zYec zA2s%bXrG<0KK+I5pj5c`2fGy9WXL;{@KH8DD=>e+Gg6;&`ok?UfRmK^53@*cuj3D; z7bJ(=i2@pS`N)2^#L`| zT*k!2;NXe8h|cb>86Bm4XiSeqK|w(oEA;LeSO&fAls$OX9&i2b}rC} zxL*K6N{ylOJT(}@zXHX{jrfp08Tpw7s@yv+V+1ThI!gd zDg4+pc&wZi+xbvW3aZyrI8x97XM_+o*xXyv>SOk0(jzCArKeG=5uS#etmK(~wut;Rdj<5iVl9r!|^nyjl= z;*)os;$<#aFlnk6F&KQ%VdEwbY+0a!(T}0h8_lRVP?h&Ci;;JV!3TL(Lvv;(a^J%tEevH0vHT*s!dg#&P z_-!%oYj_`6g*e!8*xz8V_v*Wm3f}T}^TFRY+S6Okjdh z&W(k+fj7~YN2~9VlWSQc>NUohV^~fH(6Dz>65SZ$L@Pnmi;nu{6W`ca7N{8g%C;rs z?%x-ws1)Da@{9e9*t{W!i7a$pEfZw>Och zcs3+>A$QDU{vuTBP>GEyb!bJ!g3RNA`D5Q@!6_3<%RpgA^9P4p(Sc#&*j>do5Ne{k z%7Az`__Ma7BSQ+)&ZK5}{wIN#-B|0nQaw$#XNx^Bt4feN$Y(uYfq}gvZMmS{z`}Tg zHRJSM$x7V|Y{H4zKB`P+W)#!)OmLS)N+s9xMC`K)7vcdu4X&v672kojHgrYBlEddx zf?kYS>DO*-UBdW@3PZm=Xy4F~Fm1nZ3SzK_18W!%^=2-l@{s(IYjITn4_t&5m^JVFUied;J`` zS$B$&Vz#BDXA8!sGXcJzZc|9_X+ILjL8~wuLUVY<`koRGXQJX(@geol$ooaC$!o0K!Cav%dE{JVjht<8>~ z%{$iRGA#*k7TUVsjB}q=-L5%moX3Z0WQhH&63wfDloy0wofp5I)9Wh>-tKG&nkM#O zr@|)i$C)@$U^F|2lqqBehaST5Tisu}B=;|P9(iD_jeFHdF8^qyD0#hhWpBmx*=kSZ z^1i%6^M*k1F`dMdE1t)TF8=j&ba^-zFE)2H_WJEZ0v@fqhTE+sCnuXjZbmDr8X ziIjspZ^jY|g2p&W*M0 zz5m97cP{}Q9l@d2-bST(&d!XDYda9Vk>iTDj;ISZ+L(5(fyv{?!Q6(&hlgIx;o=Z2 z+4z<(|F|wJ{+!Y34pKL_64nY?O?G9uP6?#4Oss~SZ)ys@)WMfoZTwy>4|EL7)kn`f?3MNMK+i& zE;^U{d8K4$*bG32cXa7|#m+Z{kMC4fsjfFLU4EO85PE{Wbq-1f@r`!m@+HUb6#$ul zR~+s!pF(q{rRn%(w>`R<(~~1CvJd&zk01MvRib~Mi*;&5H*#~+M-=bYfU=nEV_ozi zrn=ySR@3=K2E2Qj@S$UE#FUONV7PpXi+uWct>px$mw}Q4Coea7PADSPStjRy(sAh* z*MKJFK-se-&+t2VMuE=*>E>TiF=N%J+a<}n& z$_t_&d=VyB`+RkG0pJ58#h$NqZ)ksFD7Q z6bQL5kanWQub1S(6yK-U`-{yofk>;o2VY?U5ebR^&yqJ~;~SG0Wl1tWZ{{8}+^_3k zl$B5NyW_^gZ}}kf+cy;&JY3CmYD!+QxK+#EVcJVvDo4DGGpsl0Bseu*gwy`4uP~8G zQoe({S-p`;YTw3>R?+r>(L)xk1GUGP|H@#@#@u+E+|^(@stI3kKKcQUdbPDDLRgG) z+y4G^Y@e^7RSd5h=NldUu|2|^eD||1SzqBK3m5IxE z!o0N99Z}l!%QqeoWvU+GkQ#g9&hr>FbS9RQ?Q1ryuU8`<)+n>Z@EBJ4?k)@7pjjR& z1ofp&JT@ZiLjIFQ3M>uq{;7!QW$G;-smuLR7QDqxOQL_=i0;0->yRKu*x z3p9i8GzI4^R9DC@)i~KPTvNL-k-^_HcTu(d0wRE5v1?srJFR=Mk|!h7sM?cnXLt9y z`Oar_w)jPlGjFt8AVR0t(CE4M>l|@S_G^P4J?!5fOG$&x@nk3=#04uJ7x5yWx{QAH zH}Fe5hkT>y2nP~jV32tG0L+4}JAY$6wp(Ly|K!#Z@nCRx&~)>4hI^%6Av1UcCe>1j z>mpo>wT`bQ3pk$dKicHmiJuXZOi)M6ho#7RNGyG_}foBQAxIXd#4%G?=PGyRM)S#7fo6B|W97TUR?iQ1cXqs^GqCn~KGI>1st_ftW!xwEpB5U9akK&Jp?BV8j*_z?PY+ekA@>foIQwn? z*s9lPbISX((GeeQ7wfRLd1p@+fwVIDVOXy)b6a%K{r_X^t;4F^x;;=DX^};jh>`-* z-2&1L(h367DBYzZsYpt9N_Q_>k?v;E-Q9OC-QRc4J#p{(XFtzY_g3dSV~k%7=+(jX zHn6IAK|5on7HljF$VS_qNk>TjY5}&bIGapIb4jsUN+veRalKD~R-wsjyuN2N6MP>}Nim!oL|?HDm{` z-`rpzHgx!WV?=h<%xi=VwPHAj*_2p~dv|04%cIzfb=4wjaSP$rFH(z#D|{VoI8IM7 z_eCr!gJh9Kyri8Na`9nVx_B2k{r4!w*}0%;lCM1*!I+T5M~_9bUM$=mBM3S7Jo}+@ zM1Mvt?Ecno_|1g_uZuMr(i|wMwMLQ|cJ}-c=7u2Ow$go9nZU#A;H92u`k%nTZ;TN6uk8zQ|8>`7cPxz@8>i)M!h=>OFCmQt( zrQ2k_*zZg^^}UM4b7jPgeU)2m=b;qgaj3ziRebx=k3qcD5BU;s-Qm}JoaV-SxeM!q zCKN65;(U^5c?C*pf_kUG6Gr^XYge4+B*LliV;lXC%wF`-*>(Kq(b+Vm|U)FNQmwe zi|I;vbYM)eHs7!(9;vgvKn-hE~&m91Y_oA3?d>ELbiWc z>(_fr;-^`en1tba&s}>GyYnTc6~&rc&7tc|PS(5=M%2<;H3fUP=@iHt?CsxnPf`@m ze+Dy9x*&UAzgY8+L4aWvs^m~4GX53Q^!$72jAHUcBNNCeSJin*~%^F7?H`r3U8nuw?e1e!l^Yri|q$M zx6Xp#b;NeMH;&HA+6Q(FFA(^_$O`P5*f)%zf>r`D$X%VQS^f+bPX!i$J}LN#e3M$w z&Gv+M#90Rpg=|`xrTKo9n14{VU^eW3yOTc zEUMwR_7Bi5sG0s`b$D9?N=)t_?mxL^Jtk!qEmQ3EO`nCD4G<=O*#QNFwwI`vk%i(gh{q{=~ zkQmZ64sO-glAJxsCp4@5M!q#xGqSP^diA_1}`wC=90B8C~TTp3a6yp$*R$IXO1<67qVbC*@tlqgH>2Or#i=jv5Y z{a8*5X1Lf3Exz&FR(+ylX>8mx&(D~YMaPTS0M_5lN6UOVpB7fwcNh?xSU+3*ORk)P#3>?{qV}*AGFQD?{8}hb$B8{NJ@NHHA@z+aWLITNg z)BgM#|IjyY?{j++G@Q*ZXW0sC?+gs|7BAMM9c+Ao6fG<+uDl~$>V`4vu;g_BKZ~8Q z@dIPy{?;1rMo{d_p3rBWO8%mzQ67^3@&rK&ig@T)XMBcz$fcyBlcRE?5U5}txW4+^ z*2G!Ks+3nelkuQdE@F|ugMuNQ@Y>M_SYFpdfS&fiFzvWIEo}kbcjRd7QVn3Bp`j(# z0knqC@$k&_A&^M~0BVB2oON4N$`e;E4_v)6qcYxHC?m> zU;`r!WBc^+*(s7!K!`dhfpzZQ{PIc{8wIRrEd`-nhu z+c)?_LqpHJrmhkh12^Z~Jv>^+tA!;b+rWQVWjwqm0)W0Bd3(xc_lWUHQ5&b+Q?Zpt z|E{nnxeuhL~^rMMH8em`;v82}Y8FKW;pq7#?CWWPqPG)ffV!C|tRJQyVLu zXr$^Q7z%qWk5!A=BFitw>u!Z+IK)?q7j1~DhLO6cZb|Tcy~u@LTD}oht3xYJHnizU z5DbwojfzVD-hFUhVHZg4qaTnE6cl&8oA5bYBurEE8arn-oJ91tna_~{LVkN$|FSRz zrJ;z$U_a$fC?sye;e5AV)3e{ISW%3?n&uAst91R9a+EM*;@=!^!4H16D9hnSG^nMYG-T9ifriL0WnRCI8d>` zM+NY?rhEB|E;&sx1iP$mmdK!gmQC6Tj!+=XZC0(BHl~Y3Ge5*{(A-607lSaAgT4Lx zDS+k}e5<=NV}qm+KO)s(x;mVMeO;Qx;}1*xYGtfxBW%b?i{o^cm@#r0g4_^Brjwzq zf`^t5v60_!V5HpLO!1LY=+W^!UeJKYj1HO`wF19KqsK`*m~{gTd+EcRk?hJy;LnN@ zhDU+j(ixU8Ii^Z01061Ritvq1cVHAginOl=O~JDt>Vd=8RhBEAdEc{ci_~n3zbSPf z&LN|>eT|R*ROb{nAPl8uWVGOOW%%&|nlPV}i%KEr{opuM{AlKL^RK+*#>Q#rP&&=& zE*2`HK3Q{(2cs7!;#_;?^gXN)68rHKUnyl_%mX<*mv+`>X~iiZ%Z4XHWX-iha3v0+O{^7sJ5lXu{m=UC?OZLm;24Y;-xn*mZ$l7T;yCsk;Y4r%IRbL3@7k^Vcs% ztxY8Sh9cJU{u=XlmyXz4w~@9N(Lro!pp}15SPNEt-ZypUCb zr{+I?uieJ$_Rg2LnGTQ_dgW${$6Mq-of4||Cb|kTkTDwl>)3zd;EAff>d**?9pd+> zatcf`pq)#opf)k?bKtVGI2$WAibdS(ZP(*-=PDknC<7n^IEZJ##|8TEE3;^hBY{CB z3hXCSBMsg^{b8Z4hv67ArbaFJ!Gal7V_yapGw>5JI(1C-s_KZvDaClW)ELa=tShJu z+1YdeGXTI-Eyr}~Z(acbu8t0QZHPPtWQN$|jB+uzxZ4sU?0T@c(jC8>=Z0yeLM7>l zvu}oaOU$pRSN@p$L0^VeyVi(8jS5u|*OeiG{2Sh*=iwRj?-g%lMU#>~>YC$atk%-z zx~-8N<6uV_wr%Th@?r8keVV8~Uo>BW0on-cOMMVlO3OiI_Dbx|JMor>dS8j;4oT)L zS=1-lDtc&d@jEAbjaR*EoM8!CF^x#vC94cN0Y#B!InPwpiC7pJcQ9(h?`ccV6tiN(#=eqM4aXlyu+A>}#lECBBVz@DnHFkaaqvVd?&kAQAVKQWa z%P{NBH>Gr6T|x#dSfk5bvaZdy7AZt0W|d-FVJGFZ!KLwg*xRu#*fsGL0JhMIjPB-; z`WJ{P_2U6*lxpfDLnhNxFPz%2L=}%C<4R<|;1X?81FqUHbZ{)$Bb17p;%JqPq1+LC zDDZ;6e*5M#4mnTsB$`3vU{4RE3HSudoMMfG0E1lPB}h6sIT>AM?XQ#}joZ?1HKx_= z`H`McF5r+PUEY^>dgczZh~0+;8Caze+3`uVz4zl9Jga^L(_e5 zsB{RvbT%^+Xe^18uV20LtvGh15O9p5u?-;#FR3hDO;I1Q9V(no{5A<136Y)YbnhE~ zw$Y)X-S-Bw-*yS1dQ~hm{Z*#Jio5v~@S_mk#}UGy@(sxpvEht*hK-#$lz|oVZ5h&W zgT-Y0mepmXB2e!R_>$WTgI^OTBll z_zYR=@@(O4eFh%SJkMV}23|sC5rg(Yv!!HeEImPT(5AllMMc)bV>W`W+uBAR1?`Yt zL&IbezEr&96CANT`n=ucw67`^XQ!=k?6i}8BO{L6qV^yte6LWy>+QNV9Uaa9hzUni zj*^H;uf(Mi@HdynZUqA?Ngq3HYh5K6i+?A1VA23b4qy%N9RFa|s6d zJb2jBBb)5qK!bHf=Pbr*&%G-UP89}oOkQ2ZA*CZHSso>C^cJRiEkipXoC*fASbt4S z>^8Zse8&uC?Jbmuv*!Iv3$V7;8}Vi2Y^su+&vTi?$i>+iWm0Jzn9tA6U%&2Bov#tm z*X3FhesLp-7kJb#l5$$y)I{}^rN^y7eUSZg5P#PaWDMASDXs-CnHr>@(Q<4;fYmMP zFtZO~F1|8?-;wEkSZ`YOJABK>H{>gYQTOXcezkMp9Zz|rb}nI)>=@-$jpAVLe!->m zECu9pjr$3iPx8s;DDrHbb7%0Lx0vM1i=KJ?pb zYz&+=FEtRkbgMw`TJQGTwvrO_8>IJJy3Uo4hob}NK%9n)%b0f5;6%fP{1299aHBqv za=37b2z`k88HtGzR7X#0;SGT*98_U&VK5B^z$v`DrsND>uu&Ug87a04t#=uI4U)pX zh+O+_i51`xOe2DzTx^5D#9m?oc=_q<|bkL!x7TjX6x~gy}UxHgdy|A`WYoEMn)-kRGFFC;>yZ+0ca<{z#Ry}#vb%5J6|`t(MxWa zDeeqr$)f{VC(%MzI>|mR#Q;`Xnhbu**WC4Hn=7ivbVG*4flXfSU|!IDs$vG5L!7iq z0^hm)ajhcxJcVIGXu=BTJ^tNUKFip2m#h`WuhTV8HfQQGdH8{F4Ljv@XMeo2`5h+> zK3z6ACwkf>AO#y4T>`f8J(g9l`3XJ!()7wAbtV6EG%W^D*rbRWZTMnS9$OBiiU1=0 z+~FFQRu=o3LD3jqw#ZM@zHhXImo40Nzkj4dBjIJsjmrO_Y|#Yx{Fqwr@Q&cEVz2xs z0@#h~t#!lUW&$vJA{J*wR1%u?#C3~i2zZTvnMyO-aKIX zD@vQrJrPB&JlTu%gO=W5oO)NS6M!icxWFcxolCS=z zTzJOj)Azi+=K#(x-H{4LM7E#qc+_aE|1?K4Su65NT>KuNVWM7PZ`+4Z8`;cujB|`n zMHO^>?DIPpg6fK;H&d7(YOAjdtvw}E0s~VtY!w4c%-NU zXz%Fv7FNn3xAT2`>Tmo$3mjCUeNT5zIgE(mjZ9C1jIBli(wB0Mh^)?euSznufDj~s zbObNFbnKV;T(W~df^~Q|AT#1=&mZ!$-?3o%+Dh2x{lppl@cUmr z3k5l>9Rip$Fq2{N`ZVQT#y(uxp!VVN>gwTz5@sKex!gKe@qp;m)O4TUF?}UfWia+& zFe_KY(E2FCM@^FXDkzoN_v|39RxHVV;K~HoGV=*zy=T&1jxDs{2&o%M3X$*+z@^>6 zLOaVW3rLK@$7!Zys;MZbkNvf)Wd2(t1iBuBhiIRXOu_2gea|W=BiBT1C~g$+qjjye zz9AloSdc86?t7JeDhkL@a~N1JPoCl$Q< ztux)j;)gP^XE?A>9lZoXIdrL|7)Z<3^73bGIwk0gTZ(-Ula3`yEb*88d8VN2-**<6PSEtDo7xMd63^=(>_UNK^O1%IX zEBtolh_t^+3OjH}L8|2+FuLIRnh}r2gpqBUgt~9}y{%opP#fMX8WzsC3)CSXHVACK z)QQo8KQIL|Pdc@H!G1;E*MjnVqj`>~DnBaA&CB3H}ZW622Wp`Lwo{v=Gw={niNmTNtPX*3EzpDkdV?O=|wN)SHS zA9MFMNk8k9uN|C&X+o}7h2!iOjTYFh=-BJXg7uv*tG3Ba-~7<>0ZdGn6RJ)@tTd?Lm3KN9H%Lo5VP>$c9KE#Z4T38GdKGvv(RJWv6a+i%+v5 z?FRLZzJF%L0a-`rZ)Bhli#6B71LVYOT8rZ6&y!nMerpewN_tF8aZ6~K5tWsdy<-yi z+&VzIFSNVuq>?`pycyj&*ho~u1N&r`xz zYO$=u@_tTNS$sS`_P|VI0|86xM)8+*2(HYFN6lPG6(@BFhBhaO6NQCfk%6A5&_jEC zhX`7*MHO>m5dc0#WsAJA2^@W@CPRYLzFNyv-%8;-UkZMsNVR0vGo*@RYP4hB_B0jK zR7Vh4+8Kt%V-Szj)qy6e5KXHUmX9pytQb}XLNh(dz zs((Io<6j8EeaCf2rx56z4x(S?*Td^MgZb|6VEFq0q;0eJQJ?$C!LO?)SggZANQAj! zQV8mZ4TS6WpL`l}f?i!am;jreo)|{(zJAR*){~J4)=^DFqy|WfJhvBf1_rDUJG#IY z7r<>fjXnCpe+dEiUC&;lN(95+vxt|uSD3j3`ww0uaIVq5F*P&p3@oqhQCc{?jdB@C z{|GeBabbXQk%P=My)!|<+w8yD1L5)$6Alw4AfOHQEt?#3z((s= zzQ+7@Z&VfhWWGn^Bau7J6ivZhz_U<#KVWu}`GXHSne%Rq$)W6T#)z77Pv!XvfCE{l zZAjX`;?#8M?iPW>h~jCnvRU;cZ@)}%rn5bd$hSVD#w(3bkju7ZLDLbDJr5 zFmEL#Tzdx_M3bdUpmGH*{sVvy$w+rf-h;7Bv*JVbcxIPb;n!aVSkd1Mi8FMVZ{ZIT zHxz>@9{k1s9!E6znSY&Iom%C<> zG81SDqIP4s1_WGXg!>_S-`LpXO?iRZ*7I=V(c{N6Pb=-*1b^sWuyiWB;(VbJX4+GE z-^`HejLHO#NDrLd87b5w4*c>#+(;P@^v(^P-z@jl+*i-C8^W{=-If@IuWtka`*+34 z=0cH7P{RYR6G&Z1R2Gi28)YOJa z_Ib*xmdz^y@u(LsPh5dk*zhsTW~!YEgO}j7sYRbfEt_G3{|&kX5cLh7{cBM`Jp&M5 zyGh5$dVo@@!0{c~;MMMvOn@$o>X{Ls2E#i^+r5yP8H$;DAERnw#sUIVt`~l~m6m?# zUio$4lCZ@4c^#kan~h{BYDE1>+0^FWAlJo3 zFkO+ue$Q}JsGnberwzc4%x`L=F4Z)H08Kvvx9oBcp7V5C|b(N>E-oX0i z>q9|YN=z@y-l)0@SlYm&V$IKD94~?}C#HBLRQhe$J>D zc4lVF+#D`wu%EvfVFb!PV<@;;;R(FrK!x2Q^pVyzg)7z8fHstbs>j#$=CT(oPK_v7 zCRM&#MR4bI1Cs2YGZmi-P&CDTe@h()&}>(>($dP*B_YBde#0DV$a7%{RZAcp4|6LSrJ-WpV%w~Do4=P+ zco+=5r(wBay}fpbM402a)IhK!{TMk7p14$N@b<7I*uIZtNX39ZLnv&yW&sF>qsQOo z(tQQscMq;6x48l+`NX|li zKoAKmVu6J7g-B8(;U3;kt#_=yUhrW7b9S3w0F4mqB!^iKr@e#I3M2!lo1hcyv2wQWIKUxejuxy`T>ERUur~oIc+ZW=t)Ic#Vh94*6@s`cV0CWZ*dQAK;G7U zcRq^R+FH-qtd9h`mG2ka(=sp8zS3fc{HngCoIY}0rVSpOt+k>S5}Fz4?ge|3vC{g* zTas^)>VsJh=t|1+Ucq^oMZ{_5yZM4Sf?e01#^n^h@iuWYz3+g$vfU zIEoltSXCBFe!PZ0gb@++`yGoy4De1uvVBetvnq$(rr1{6-vzvI-9*#e>HJ zx3?!p2i%u))454HKDNxr3gH}IQdCrIt2;@Epl+!LIjy%|zwC~$q?+3`dcO+IcL3mj+u+(ictmx{t8d^ z&rwBTIcsd(uIS)D(O+h@8(2QQ~V)GqkpbNdS6iB;4gbG?u z+@Iho>l`wjf41y#mc$E!Vmt2tDz_mAlLLu9!;;uG zx>v)rIG*{RAumAn?w_qDVf!Xq7?)CPQ|nN zeXlyE>Kl`N=-foJC`7jU(3-JOlMa^k9s`T0#{@ySxxRwOX#Ny?9I${}yw=N(D=01j z=%-zPy@x=nD=L3EoiK|r4)1J}Eu8MWS>M=dDKA8C)Ag@%gtazj;2G81A8=56KjY+V z2_nb=g+UPn=Jfu(7`=}XcoHQ>f`);B&VMz$7jQ(wmEgW+Ol7JrKr#R`X@ILl_hF`i zfde70t=pDHu5mHl8H+xa->r@L^yd&CZLN$7)Z_cAgc}lX!w7ZofY{A+hwQryB-a&e zBCUq(O)6D{;yS{Z11Y~Dc>Po-JvAeVP|V|@Y&|ahw^Hl$(xbEn(kd=Ts?!_I$khc*l&=kf*0pFJIPu!X87@7} zOZ?6ca^^{KJzQ%ZadA=b5@2N;v%(dkPH^btncdGk%Z>lVKSKA})N1&1hIigwDoo_% z&5`b>=8^RzkWye%x=_plwlmV}W5IVqi86H0Y&L2dt z5Yz^gRz@#2#JP!b^U~F|`3r;(T)isL&dzu|*?|^Rfn|wjvACQAh**{on!U} z2Y}$W4sKQ`M+d>OB#BVqc*g}o&!hwK;6P7Q#vyoMC(q!|vQ^Guk|T%Ir2B(nWp~tR zm+fVB7E_D-9?g++Le-q~r_NSW!cwCWsTEX?0zn9#rrdJG67=Tlj@qWx!A0m`MazU4 zjzDh&gimw9!)0aC51@KFa=(Sr#SY*P+0N|$Sf%G97`mIws@6q0^EV5VLO5Lq#2oRq zJM-QhB}Amrc%Q%K1}PxQg9>@E&3C(2kDolD5u|66vSX3U^Wdd)SoORH+u6yfMbA!Y zF{=8aSIh_xun+qnZfCKc&}iX=m(JlaE4Mqf^inKQ%XCo^J)j%jeR#`iX>U24NA20} zi~(c0J(DDbT3MNdHC`R6y;Z;ZpX?-*_e_@DfA(}E=Z$QVkihz#d>rcD=uJ!8ySRLO zO^FO|phpYFYm{F@<@Y>6@?oAV@+W-_7cPE&(kr}W#5sUy!(xrb8CO%?{*hjtn}>%h z4z@S3GeonT?9H>~iiH^n@D0>!BLMil=p|GhAvB#nFWKVuHHxW-#R!gTbZJ_R2t?Ix zKC^W3Fnp&TbvH&vrIvS(vssHph0D#!7U0$Ux4|pF_fG}Y`FaxLfiF+!Y@5XM*$(aP zS^CGL(#tg?FxXeLZ$X)xR;x%qOz@W*9%j~PWNILB%IO?jqi(&9~&RYM!%;LSOXs$oN>jcPVwDqxu(N_IEGCtlF!WIgWi=90& zgQ-HBE}T(;)dZrGlU{d|K7jrlXfzL%j8}wTFvCLvjOxIydYSFT;# zXc??GAO~@X2R_5HMERns&4$9~msKVgjKw!#r}E*vn6BDQjkF%&&`YiT7exQ)*YzvgK94IyMh~u^X>7Z)p?S7v2|8Fzd zy*U0whyfe5x$EBKQbtsCi-?^(<6h=@?|&OC8%?JsB^06^U&^6csV+tcCBHxLk@ha; z>4jX{aJh+$UsJsbik+C4c1?wSXIXI*i&_`G)xKKAcEbhMxv<5;>c%9QxUdI<8rSs1 z#H(%(yVpZodl3HJMK&VXFlx%uROOZ@NHV-kJ&+HEWnqUFD?{JWk zw2C;VO3PPg)AmVm)K)pF>Xm<7;f!ufT)GY+bQ)m!4<0Xcm&KsDZul)OcF3QvPVb#dx&splAGgagdf?Ee>_A0tJa@~<)&cgk z`{_0=TYpt>V7Lya`Kw;GW~N83!a};QfmZT-enuInP`Vu^|C&#LYjlPKLpKH3O=%YX zWj9UB$Q2;8ob)3Tc?nnssU_=ApCH^DIa-ZaS^4vpxN!Wl9W$Ai?~J3#+x`0xdo`t8Xwt{ zhwEnkFh{o0Zz|aUYLb2e9AIy=kq8ge81GD9?(~MJaxVDm66@lh=8r<Xd3H5nu zU~v9|qywo&BGMJ?@<1qSdciF1{kDRqq-6+^$rz2uqh?*6uwI3!p=kGQwL zAbCCo;;;DSA~$2yo}pzyvRsl0I-7mr>Qa~D5e6xT_JbGJlkvo)-&2B*=NhkEcWVb9 zikEgoMa5J_QcRj}ie7I=Gen|!$c7dz`{J}fJZ1_^mKql?=Zsc6xkYOsc5J)lKoy~cj^50OB z4m1;^*A3L`$g#-(7h8#%m`1T7VvWz9H^6@zc}1L`%Bcoa%e*3%!Z9p6s5O^))n=pz z0^-mekn3GqdJ}>^izHQ_X<7jBk*fUVraw~rq5jKgT}&8@xR)tl=NeXXRI@)%6gTUJ zY8!cB9ium8xcX=;Jqp$Kd=euUiDF@Q0kd(c@@r&dhCF{AaMF%iH>Ebhf31W>vnZy0e=+PN2uI3UVNT1`*f7nkQ>0Plw;P*1dDTY=P03UM_gl1_aq=F> z6y1*Mya2El$P~uj`_k$gOXUv$Llof;15TIEGSn@aZ>y36pS+SE;1+G<%W^0(C?Enh zC?G1HR^X=dHjF%jDck_ha$BD4;qsnKoCwK|~WRW~5jBLKf@tu=o{ z8oVf*K3BaDl$OL#UgTX|R><7ldSN2WMHhItg8}fKvjYS7f5&)-%N94|*x407*pfZ| ze|t-=um0^V0cjbE_>@9`Y=^_X{mDMGqDP8sh0ap%#G5YZ3EC?WY4D~}EgCv3Z zv8gs3AlrPGZ!WN4YG&r2EL<0jwXddVC`YOkrZxp$(PoMMLB2^$*{p`poILGlpuI^*vv!K|kS zVaW9@LY`YoAxGBJn??%BW|;pN()6|PAtnSal%AvjE`Rv4N}LpxEd$APOlm(+=9Rn~ zCQ{lG$FDAK;Gi1Q(n3Yp137*BqvOncg6pwTUiEv#KjmsyE$v%k7EXN zsd3~i4tMMA;4C!qT>7Sl#)@=der-tpM-?il)tfh;VSbIJc1HIW2_CrV{5SUql?L)- zCMGiQ8@uog5vtEVQ|;=yVS; zA?;sYT*CGv%BEF_>bVc7uF)Qs zAFBEiS#y_VMqAx*8C=v~h*t~^G{nXci&-xf#xlL(z`2ph_cV9rRL1^~nj5Hy4Nr3S z0CR2L72XL_Eco`kpD$w^-@|4=aOJ&eL^Tj_dER(=@-@npoViIaNYLe*+sL;~+53rJ z3(frw$ELu~;&Va8l`ATPt?uAt69hIv?+KFk`8#f7sLWec==6xWb^b=7F{N~SgMGNM*M94x>FP^x&w zC_d_qP+R6_cq}ek%+y5e_Y&YNh-R0dZdKd8yek z3+@&Pr|;_U;{f2SIecK!+IY4PYU{<(@wunv=Pk%r0cDsg;}RN95C3W|6Y)f~&xj@( zIn$N9m|&Jk0#4`lcHY?~-2;KR>zf-}j2sf0or%hQsxJc$dx{y^4gqY^74p8A6|R*h zZe+3=;(?n+s@xQ|S&ILZ9CJ=lYu<FO38$jcq5 z!Pv*-5ZDs%43S8$F29Mr=L(<9|ss34 zWoAxW~j{!JwSU>ONue3n3zglBJFEizzDeg%HYOX7PMzjtVDcF5t89;7olq8^0}JGMuMyR(!A(rC3rTd~xpPia{>mlUcTY8B_7f z`>K2Z)dK*nq3!SqH|X%_eXF^Yf1PZ?35(6ucRIy?OpOGMk9hLbK?lgHu|_6Ot_Sy; zMcXGK?&XAh{c2xMk&%0*g&t^jjgj?a?<;sWoEr+h++wD(gVR=i&xR2 z3uA?PKYM#SN?uF80>rj_tXJEe_g_5qBc-5Axk8lJ#`Q&rQ0G4lQpxK!Iyj#=S=N7W zT@wp=|6rd|&kyi*WQ`Dgsm*3#2o$Svrud-!4{?APzgMuI7jZ-XW zoL&tmt-Q~T`U2QV3d(q6AUveF5Oh^B0tS|^T^Snvx3^7jx!UOoFkv1{@A>#`N<45J zA1>%s8P92?r!qGK@bD8?1>d;K2d{SKi|M^}lU#ho2ony+#Sr3$l!kAj47$6AKynvw zm?_^T*Vk3SoVF*vYbWPdWdA**tBkdVaM_;TtPtzg=yS0Cmg(B#KHuq z=oHf)6j>p|Sh^0E$1F3!m$zKD*2!m3!3DoOMzR3}t0LGDVz3PdBPqjqm+I;OCSTrE zJ@cCS`lXN#`IHAAoAhogF3q1mPgK#rT0BBM-#+T7dH*EbXY6m5O(^j}^r-~R zNImnc%kLg5R>7h8=Q-2t)EmkXfM2)*ZhB++d2pmpqyMwPrka>~ zGS+CNh-oKKc!bIg$-pHC3UG1QGe*;gs;Pev;f-&x9unYgj8Q=YbftKETPmWsiW=;b<|t)LGdlbS3>fR9RbZx=Ij`82$WPriF+p;3dGaZ# z5JyHKK0f{t>n>T-#cBN`#J!QT`Hw*Q!a;S5b+b8M5O|i;CwAsrBs3T4jEa6HLmBYR zYM;kq2&K^<>9w^!^&4XoLA@V(cf`09e2;z4Fz}d>U8`-U!zwC3h?UKBU50a^E-(aj z++|nva&-c&eJcRmb?XEin0BwTi*o`81~O{MTquD-(%x?L0WPlM{SG?j!d>W(8JWo`n>WVYt@r zI04{TTcVlAqi%)A|J2OU&fgt>(1F!1qzjKRwXA>>@>9abiDVFXS;zfUaf1f+pK1m; z8i5|cPAZqd1jr$PlWQ)2uu-U7iyP+U(=9kr=Y*cFl=!rtBdV^NV@<95A*eG!DG7fs zF+sl$g(A4tRJW%06Nx^NQ=LqUd20BRG5jcPFOpZaZdE?E_|?s27`7`Y`@+o>6IKyH z_SaLrODR7{FT{YHoE$Krw_(>k)NBt^v4Hp(_zARw8IvgmF^_pOYwj5e3kyHJHAV!g z5q_oQ(ABH|l9cil(u;tGyA5WUEj`J8t3{MuWxat7Jl@;0#A@IbZ``&7BIWfX^ZvPj z{euHzA8G02#jB?*z4!x#1UX*W03?Iwa%C${gS3=9rG0=l2@&n!$q_56cA;bdnUdGN zH^Vdm$mn>t>m55@sbMYx%8rJPZf{Rs zmD?34DpB?TTh-a*Me|wt#qz;h<@=dWQq%<2u3x}bdlS-XO*pNmo6!H%@gOD*)bWso zt<_C`MIXC0pXtA9gqK%q*&N6qLc`Vsn8CqFY=_L!%F3*CG`rV0K$JL8SRe`~U%yZn zREhq@Hn?wxeh%ouJ&lEq2_%Ikm{F+vB0pVIO_+YZ2i8*QPtv0{zfi}3d|0>Mh=>hS zqY0cJW+R91s*qd;@X58JzD4sYX(G=t?lp$lOgqPr{h94NnDHr=c}RY;HdtLW*Zd(i z$p3lWmfh{}6|L_%0(87f{y;5Rp`xq|x-wmENh2?`~AHf+QMM1D72MQ$^ZDY z6=cCtlkm_(rdPKwsFM ztp^N5dtPJ?Y5oIP)>w|qeI(?1cF!b!E2{bFT6LM@-C?XxPS}5^|JkFJjR;LwwE%Cp z`2JI1Lt!s9f34qa7{u)g0&;NJ*p$&mPzZs5|I3F2Uef(B5H|H+5i2;$EI+?}@Ghv^G&i3MUXj5_G3bETvTum5Ma_=2!Fc*_B7 z^LAnYf=6|&=W_O9f?X=gLAK~VR1gC=&m`@R9&qoxB-Mg2z|F$HvlC{XU}|LkV*pyS zeg@2fHX86#Tc9TR7ja<0)R2UKUCEr^-<;s)*Qqr*@T7yhMn21}K8XqsvS8hbj_FNx|U@>vic|Kt_QMT0cxD zjK1g6NfM5Zp=!aP3YMcQ+{t8ujZS?GUPu?iz}%k?Dy;t+?(FEB-7 zfjekp_!W9aWx-e5%+xbgeZug!Nq+ple$eh9wCw}U&#;CA= z^?#NhMD;~)@~An+rPv9gWozJZuMpsG}2&kzc$Nt3F8k*jSeSA_&w0y=q&Y+(My!my~pFq8^#8vr^(}sU4WR+g< zcCH~Z&9OZDbbTlbxi67FWZy&;$g*+QJ0dKihPawVvse@r8af6Yy{?X-O*b?`hN@)C zb4|(4a zZfMW-r&rQWGK?v$glk5p*11b{#^Ew0E2`*|D4=B%B91Z35h(sw@AD8-2o7sa0=nZA z-ZNi$Y7hEWo1o7l-FKcC02nk}GHM878(58MgTd9y@ZoZK>Hp#EEuga8qIF?GkPeYX zq+38h8Yv|N1w;u+DN$*V5|9pQq+38Ny1SH4k(O?yK|25W;@IP;viF zh}vS?;Ve}!1(6xr&FWp)#)iBSR>FCJ>^A)0OvWUi{2wM`9>u;j zZ?*dGCu0J_&dQWtJWs_1koci|Ro=1)@II{v>_mfQCfXlV!l)@ni)wtP2)m?*;K#S^ z&X45WOuEuNY_a>N)))V`p%xs7w~KYzb^}mFa>1KazY3h*db~#V--$mhJMX zCeB@YL1K_sX`YpB(}CwS>4xL7caoSs@~uYAQzQ`j*Q_00gqlDNkDas-Rbn$9)IJyV$O@BBOEvUKTTPTQO)o6r@z(4*0jpHCo8#7@*M zed$*cjV?z|hT-JqT0OkLaQ5C$eRoguLSCW5b5jY}#J>d#v|P^7Y2fCnago1H1`Yqv z1G|p|e1%lSj}ZtUTLBM-GBOW&%UD(Lkp33DWG=-)3UqeVIP9sMu~n$7toDgwMt$9f zoSi*5Ny19tRHsZ2idJT^N^$%yDKm$y5#K6a|6UrXdot6(J$>vO1f(lvGlyemZa$Ol z`u=^UhWYzJ==)2{^Fq$N+*@`(-oFV56p6?9cl71uje+^(tqJY8?L$!+Z1o>HXpuoL zfJ)oRWCpD>c@Il&MOC|U+paT7Sdd^Nl(Hebpz`Ch(y}rcjoaK+ zL6VSFyG(YEp^hR>FYlw-Z($48T5})nuksijJ_2^dGVJ=Jv7!u$uX^Rym!Ze(ok+!` zYZWRFkLmR6YzWH9k>SL#az*GDe>slo&BH7m@4c;P4g@e$syK#HXk2`};euS>X3I}| zT&LvZM5BKg@t`x>IF0R}QfOuJYMRJr=ra^^fLj{F>37Pz6i;l)Rfx&TCSOqlp=7g% zW4(uXevw(QNk?t3xSYnjCYZ_o^)r)U5D4{9cyWfN)X+Cxxs;hqQj0nHxGCz_$jE>6$|Q&ppr&wC~)Ny0Q+XqJ|sM^*45CfFLukm;6x{^rlS+If#Af^oV`J>1pb9 za#Qc@wbZaXEiv$A?@PEWqEM@C?oR8!VqhdxmG_YR<-^Rmji080@|-@^o#e7gVBpQ? zUU+v@h-LEAX}x~9L{9`oN&X_lMEfDnJsIDS9Thch+)?A`zIQdOZK8s2U2HuOeYdSs ztV`0|ZZOBp1xU#k->A(W8h)G_h>a2%eX#L`&nj4}1}$n!>Kc09E3<%z$O~9NkAEx& z8YTnpo}wxoxTG;rXeCeGmv?%0cj9UyT6T&@NDik?l5Td&MU-$4N?^eL!fobk0ZhrB;({O@;h=^kH3z{61k}f@>2YZ%4_=?ra3QTMxX-74h;yWm1ah z$Y@cT-kQ&P)}Mb3Re@Me>jfo{#@H-vKR&1||DutFT3}K5iK*a+K)Hep+NGGNI@<8K zIG^|LjT;-A7qmA5Ig^{zdzz{P6@J4sjdRlJVp47ljVWy@wtMbVH7HdrD1+rBTMS?RbY zF*)0r6Xm}`)oC!>VUoPS&J4mCwR05oPJa?~`#ywR*2yd!dm2xM^(o#%$a|=+oW@~g z%qZr$%0>cuQPZWgsS@&6_~x1atbQ~p?Z^A5jFPC7pyiWQ;G;G2GHPp2S`ju&38P!+ zeN=UM&_cnE>}E4V6v7TwpPM&ubU#f#i!MZ8SMN`xS%0qlOtpN$vsb{JL+< zd(ddRdx&hdaQICm%*hW2ae;GvC-Vm-{NlpKfUf)SQLJa0oz4VZAK8n6N^htbkHRXU zN;`%#arMv7Y~4P{W3%Jo$N5bsh3vSg9#Kxm<`S19^Pj|AE1{!h3_ELzZ0_9sqED{e zd_TpH^2h-+jKdMR8+8nI}>3++@QwYBc+Cq3e4E4|*QO*Lgz5 zcXz?=S#8Bj%%Cj33aSw8|EyZH232st`Kr}h!>al5&0aaX%&Jw4x#Zv9T)Cr-9h~{X znn_4VWNNtJ;j6vOs5-JG>U=Cp)s2J;>{W~?M6K|#xwE{{`W5TRzeaftRUdqu;^(#s zl|>!73|hYqx*&;%Xq9YX{x#$Lz$FHH6i{L#x{>5T=jAwE_J`+Hr)FbgW4kA4!23&a zZEbD)mc4uBt_SQZtE;^q9hLjP=w6AZsP9c7j@SIR z7r>}xbJVJifq~&7U|oJ;VP&qh&m)^*qLONN?UsjzK1J|t|Il~Ne?9;?tL(6^^>v+c z@)Z$p8U%SO``G!sUzb4nw z_YV#vuTF1|r_%t<^xaZ!)y6B{`ji14M5n;w@vYLHD&MTkIrI;Ym{GekqJ+emASj(C z?F%jKVV^!74WI124u~!fq4KRS3c&c$2S>F3al8byT)zB0hEFA6`OO=nm(cy=@?g$I zfB{i4TwEaal~dw^JbLjP*UZXDz8FgutiuAuCoC#B9)w&~%qT=oF~~Z2!8*Y1bgRgi zQq%7yFW3dei5J`-!+PW~C;YLnex@**%AsP@(~4hku(v63xCVrZHBYHw#Gr)OlVOt5FY@Cl!U8m(-4TV=2#Sl)R~1-l zp`GTCvv}jrH|6=7o~Q1?AZ=hY?Kr7uBW7?cYVF7!OdxSc91-EzTWG@9Pk_}B98c4+ z8_5h&vs{?L>Q6k?U8Q(2l%l6S-v+Y6e0cJbu+U=IJ*TD=l(VN+e+Uz6H4^f3O=D~9;vG8!wRpVdasNrenRp@xL1gS zK`5zVfAcv}KB63>_@ifXn7I02wi)I!;y}71-NY9kx(b+bfs<#}Hygfz2F|=GI6hUrVYJd7=U&3i z`$H2gEppfsLw>lis2VyJKZ~W?h)^_(_ghSvSJv0VfGhM#|F43r6vAqKw-e5 zeI>c(^yG85NHhuz_2Pu4Z$S?SIq}|~ruTPeH=6S!RPP0uZA_6}zCsNuB4_O6Ivh@i z&l{E>3{&e?7JuuhyB^D{uM2_}fnpc>f`-9P~=U6C}21)t3L!~k3gsnP|1mcJVd z+tUkcVvBs+(7i&YsNCn-YJUMWyLz?b z`yls_T6l?iL=ST!Od>ucAYgRMr69Q|!~0^)Nv-s2?(UFe5fdUmJeoJ`YU~D(E0?=) zD+tC!W*`Ucx(R>Y7?v2P)5*!n_Yd}bjS|OJ{tR?>+`5&4xBbI@?>jF4ymqup^V*Q= zDfYy%8Wpo4Wta&IqoQ@`{Qd{DE??330yqkGr2$QFz^{3b+504raBdOCX->YBA43RqHn|Uw2p`>Q5GTGjPt>l%@A?q zUE6jF#cHB-akB?8nq&0U}M9=$H!;Y8P-i{OQ2~GeWv2h$jrE;KDWCz za1oEPzbAHzM3`9nCe1BdTrfe@=uI3cPW4MIEfW3Gf%io)^!2T7S`A`$oN03QD3fX| zcBhp)>|6pIgh5zDRJ84kh2`Trt&(Y$OiWg;I#P`g$_88eo!wRUWoo{2 ziN+>+$^?ZCfoFtXZb$g!#2vFCIP6YMRQXPkw;Z{yPx3rFXec!4pcOkVv<#d%B0_0G z=pTyiYhRV;I-RO=6a8UsBrg8F4;PyV)ZCW-{<0LjOiEHMiVz3oKM>CPj+y({c-O5@gq~?_e4#R zj_!4?+ojJ)SDU`{L7I~;?Q5&5D%aB3p7YLXyi!f2I>BA9^?0F+QhIJ~ZJZT02Gv)i zeuB>zB_yoge>A0~#LCW6Wm9+q2yyCQL4(F3Czo`K%nw?x)Ahy|=Sb1KmP3he#A86zzAYWZ^B=dKn+v#)UR*S1)r98E5&^B zqoW{&To>iQoYhD`5%KPda6sex_vREgUpyjcs;-_Z!pGlLbvoPcgBlHfp#C&j%w%1~N!~xVYfYJ9O|3AWDBh`;T8c!jm^Hd1CzoN`V~M#z0AwKgUk92WY!-D%b&_k!r@L`w*4w{q z%zsvsl$Z@kgPu5#(E>}rn>RA!l_eX8;Tk-SYsWh1fPQfVl5RGJB$mzTd{9nh+^ZoQ z<=tBzm9vbkC#mj<@g^)iIg$vI35xw?ss2f27pq~kNR9<~yzVGy6v(-kb`%h8pAf)o5Xm3VXeOaud&_8 zW~w3}Q#Eso6_OgWA&ZzlM613lB%?|}`d5JzQ^eJ++T>`3Tuezp-gKLst*t?%9Ac3l2ky^w=U?B4tPlI2SIoqD2`&msxuN9|mP#C#KT>#XW4|=4o;!m_P)A?qUOZDK z1Zl3f0!c^4oxASlBZUtIpXDu9A!wn~{tXmRTW(JK)?$6I#XsIKmu}$TwYT}H=`o5S z!BJ+%X%sh(0Y`I(NRdW>ktrDqu<~RSrQnd*-dpss`}gmr3z7d@nU- z?6FZShxR3Dr@YC0HNms$Q~9*1t{}qoNYHX5pS^gRP4I1HsN2_~Q6^GpkbE=YrX=6X zZj$P!h^Qbf0VI$eM07|%w~8codP>3!h+E7@G%5$<>{mPE&(F&X(#2K$R|OKyA@2G^ zA5=53ozi+T_?)sHef9wg(5bZ=)^Rr$ISe@E5u#Lu+f0a#omHKMF*;errLwftK5n<^Viih-;0lFn$sbobbtx)L?8 z_5N%vJYx)}hHGUI9l30t{l{?0gCF9}lICnP({AEw;%Z^_qB?h*V46LAKRhxHCz?0v zHhLER_^X*yJ9~RA!;W$W5Fr+>&TjHKHN6;tB+Fp5^i192=`F@?(d4uFMP>6|F{`!f zoyQvwoz>U-hQ1H!H~9ulL)9s3Z7`UC*=VGYL>OBS0|TS;1eF`~m6}wtC2BZnS zKAvM}7|jb4BZ$+!DUqlkCkGTflN>{w33!*Qqd7D@QuE5h>IBOalg392O|DeD)i6J~ zN~B6PbNkS!MX9X?+_miHOs#E}sZEMyZ+gnJ(GN17$ja|EhiDhNKj6D~D8b!SGF*7@ z{F3yIG8_vw4K{yWOu=8u=-7JR)&)FRtbuSvr~gPpvXoZ_h73eE*uZL$GbW4YiO6Ln z5ozd_`X}NBvGJi+$&uv8;4cv0U~R~>y8S#d-}#S&`DDSvl$xVK?Qe7O71A4lisrqp z+pRp=+I0^UZrGfD32)fkBuLe4(9V$M-*!tAeCjSh2FWSiAt}czDC08Jojz?Ki`zbZ zIaawNpS!a*exA5o|D$3U{nyOnbeR-?fs=8dd|(JJ3oyK zJuj_sI0Y>VQKb~F_%29<>PfEp_fw1o414;PxAy8&V_yJEy*IbwE6)MmJB&F&?v zbc~&A-rD5|7bxsyN?n|qS0m;rC>Sob!2xRbtSi-qqT~NWk%$K8%X`WXN$&0 zpj3jubDv%6pAAhTSkIsT7P6iSC(fQ(`Yuik8BzF_eS3erYW~hj@BMZi8z9#+YHC-r z)qYAh3c_s&RNEGv2pWAb#K8wiRMIkOSF%W{9Eh6%48u{jN-}d}X4itv$yey=ZZjct z(HR}>CaY@xISlg{=6#%9O;W9qGZFCaog_pjRNDpDSjh7 z%?n+kD%qM3ZdyK~+1+)py&mxq8cFDcnPe>b-sc!^Il>+ z%9~CLJ62>|vc|^mKPxdbp4_p>Mr~boj086pItVE*W z0&Z(Yj>s{??%@ahO10ZD#+c7h~LnMUR?-GAwJpZvTdDpSo zohGS7FD(yQecimmVwYSU7Dm7v?2f@Oan*=aevf=-ixfQE-bC2$REaz>yB? zd3z2ZfiGg`;|>ly&)w<~p>MxULo*Y&WI@wYU51yEC?6I6r+T=t(u1P&ch_RVo}iZz zn_gb_wp;wVg;)H}tIYMF5P~sE$lvEhFL6p}3Vgcxm0P#>et}C_=y2_<)g8H(IE$Yq z9Whc)`zA#pd)t!Ae5~@cppc%{Vu~!UWIQ}OI-X2c<~$C5Lhk9)i1zK;6Eh+eauf|R z4PP1K32SpLpdx=hLB}bo1(3eU#i?J6Pv+t0a;h%~xv3$t@%o2gjFwtaN|5dpSi+w8 zUYavKv*aalrju;-4xOahD%9yBm7oPzxN*1R2DW?tvtIg7G^$jPj`zweEiZ?-?5#1} zx;1{i7lu;b=})HYo3z;$1@CRsj_~|{e_WK@DgIzGZH}M|8XLCgK%e@yH{62+c2owUqSW~xd? zf>uC)6q2;->c9VM`UY&v7P#c!HN1ks3v}yN71%F3Jo==9FsykdCURU8zgHh7apI*I zdo5?;rGrDW2MRZBxdI7vF&Lcq-3P{9Zh)bp!7Ghd+RMt*IcV8a~n_`bvsV?XCNxJoV=4N+l9jo`1v#BrIADI=X1_ke#!G*UDLyC2~GX_8^V7DIB$A!!{>+Z5u&-S_S-Q3 z@1-IPi)r%w6H;(M&r4X2Er|4Bhr{!Q*8(>I4MAylNW35?hkD$v@hs7&oyT_qt1Ioz zrg!M_4A;gER5#S~^bnquZy0>yvM*oWgyCPm3uhOgT0PJAza?{l#cYu*PXo^)iz?-uCV&1{mQTyRsW>BER2L%@r|D!G8?qz)!Cq12{2J>enIo{=D1bh zwFl5F+>ekY$=cdtQ}*XE`2OCdb^(1YJmSr5Nr0m?x0bQrXJ)1Wc)hXB8f-Co7L!qe zWAO(19%obe7JEM%Q6-7lUmD6Ck22K6Jl)^-x3+zG=j^n{sCR~`5|f&mdKwxWF?xBd zt*s^Z)~G>#Onj`CnyrP*Ci^i=zW(OTR0M8$KOwsXF??!qE_ACQu2m!=9t$kbFsn0| z6&M3C4C!HqmG75C9o8aFOj>9;2zeao-hqtGt;U{MaflYgqlWzuU)GPTp6-ER@!!=O z0qR&*ACI+2#?H10R46=SYPdhPu_GNc++mWY3&p-<(PE)!(H#E zOcxL9+G5b%$mZz~1BQ8pCpLK3j_D24jq(lW^?NY&3fRkKyI#ccEe5qTpGf9r-?Ce{ zDxUg>O+1m03?n9L>rV<%+MGI`iLHJmzlv|^;^9oUw(rQ9}QBgEUfE|QsG-0|TWx#+WXAK}y%_JeK(~fMDA7b8j zlyF0Qxl3}9=^Sxy^@TIiak~pVGn>3d6>xpirXV zhaIGIi;Bh`m>Nz`KEjS32zLiHXZu<_q4oeI9$V}7K?ne*Cf0(vSX-^ z)>tbN1go<2>%>(TOe}iG3Qg?5v!eW|J7gZ+wUam_PIVQ*0E&e0z47sJ9;>x%9gPi# zwSJUncC~skeJ)8uLwYC)ct%)->??05B?mAm#z518c|a^Xi3bGucH`-f^&2=!AFe^W z-(b#U;IChCI_@5grR@NgH{{jj_;`EYy0pEXIhy|XIOF~{!(7&|gSJkN7Ju~O(gK9& zMa~*Ir^*ULSsIGD$0S<Oj`ugx*@j=vwNk)bAX?Up-N!1TI|dLx@zQ2LjdAO5eDHSY9T!o0;un-P zU;ZLYSO&cLo}n~7QbF1ss?s31`AkKh#c9&nlo+2z9BC8$#Qm8$ba-fp3MzVEwj5i5xkdc`_#xYD>#~?^Iu?P?tud=V_Ry=iz+9NxXk7sYp ze}+T@9WOE6p>$<-A}yBjI1nw|_y>D}GkP{PdDY2e8HaGx^G&EEk4IBC#fhl-ic zWNe<3><0F8(Rc3p5d7djfBxJyYr+?&t_3o*_x)6mSyd1@B8e4hEC-bj+2O8NSec+| zpQ_FBAhql6hz{GKj;;>RMpNx4%`~yMZ{OxW|0D2nSa$)CKEhHW@aGErhvrtfVA3B(X?Q|jwdf&RU}o|?%KyQ=WU!z%6x>Q zArcxz?1o0O_Wi)o_}(m4gZ*zBbM7j{5m(Vc0T3pVE&nanOC$a{2P|LlghKQ@^?@4U zTZn&{elJ>?-$t`Km7RLX_)Z*k23PNF=JjyFpE^F5`@%TbdJlof40-Gm3V3YHboYaQ z@C~T_50#6JBs#--Lx)D$j1c8N&;5kd9uMDsG&!FpaGaz#xu@Fg?`r@uYDOgt@Tw>{J%W&!3OvnhWz?y?>$AdjJ9KT^_O=7lDI!7{2oqY`INh1aJ=nTLE(&C-&b;{4-z zoSr{oCog72I|h$EWJ6H{`zM}`uC7XXzNqD7Xnz$q6j=0Xf_(A*zNmct&;N{A((}t! zgMwXZWKQv0kw9)6D=5d{xq_sQ zT>o9_i1fGYW)p1w;jV+}XQd731M5l@EO6TF38K(|{#X-1{GcvB2TUZpl7|=Sd zG0m$UDt~YribAAN%G^5QHybn8jsnEer^jm>62 zU}f02`+6*g4V56ar|D&a_a8H7`#%?9RuSU=rw$g|QtdK)0Bta@uB<+&drd9;3Pb|C z{dF5hio$H;Mr9ziPt*w65Ql^x1*@@1hAbaLTnUv?MFn+jEd_m5T3oroj5zq|s*l%D zcDXP)mN9JZSYIi`t^GurzG{+eWzY9$2vY4#?K&l&o{r6*N?Z?*iSbLhRh8yiQ5bhH z{gEg+HI*Cku{%0C*-?d8SgM1D{A<}v*2zHFcsp(n;2;bZ7M25jbMxzvvXem~THl+& z^2EpphdYlRQ8HQRO7^|Gt{nLz-(`>M)8mYUsMipa`c}CZfCAP{Tq<#*fh-MBr*PYt zmMgLbVhFZixmteQb>&mZ*W74U2V3)yETCaXoLdsN-5zN@-<~BeJrTx%hQh-M^Bi3SpjYb#7mBE;3)&_18wwwZt7RLW4JxrZbpOuK>m4>RRL|@=l>}>~tSI+fhKnIsf02}KKR?pOxw+=tI z32gQ(iN%wI(INQ*ieK#wXmHgXjXkp>9K)_K`2AzP+-|Y1NW&lB4G(4szrtn9W3j|| z{pf@M_v&}D^=eU+o!d)kz!nVi5sCp0pv&9rhnM)$4d2BmfVgaipU!D^W;T<&yHX2M zbV~j#gYG2mmw6|XQ)Ch0;Q%8|5j6C94Q1quR=#}hpX&ZNw6jz0X8{CtjAHkYuy1}3 z_?~=fl9P#7-E)4@x<8iG0egQvD--^=g#+al_=F{_z%KdvJ75U8dgd9_tApKqmkmrD z0fAaMz9F&<*_j+8n$!Bi53&cz{sfeVJe?y=?VectEMmGq?`%q zY8?v*0d5S}(M1UBB%v?z!DmC6Y|R6f=HS;jMw5vFaMY)l(I@F|!u6zAgTx;*NZ<}5 zLQqnXCwmAJMG+Axo|zpniVZH)GcqFr%S(eLeAr}M*UOv+YX)=pl9HV+<)zu579$0M z+lhl+gl8KP)7-{u$W-pH4W5x2p|UdVpR5zGEGA4ei9OeYyC=RyUNoy29j^#TpW*#kKirF_L$?}anzI5nfCr6zSMNUF2e^@&Zf3WXEnz} z^p=y&pc3@b4Ua|u&9ukYpd-d23~k*P2SWVVwbvanU66*L|5zg_Ph za(k}gE+kqtmF(EkQOmCO#iM~6uMt|(XaPtvX-6}J2C1#%2?$qOST{7d4FTyfpr2hdg5ol$ z&GZNetk__doyxp>8}$HOKZwM*o};BvP|wk58!<3PHk>5tA#xCN^@{KT1(T-RlMbl1 zBfzQC4u;apme@ptH^$ejPqAWQd+pI#pH$MsYR^th5elBpUxL%q)1H9NR`3G}=rMDd z;p^w$FFeY+7CiUf`2G9$2CJ2LLN9;iZFE!TafjdaRRe{;I7U5aX8KxRxck~2k;7xWsm>QU%0n?~wPdy?f0_N0lc4@tZ0 zR_`b!2%2HS$ffh(R_SWhG)RIU(%BL84gY|D z{z^(nOLN|uhmM3{PgKc?{%*boWb@Iui{V^8%WdR$x*;x!DtnH>o6Ws7bOZ}4BN)#8 za1WDzo~gBK8lDmP|MqYF=QxY}y3^k_yupA#J9Sz}@38gIW0-E!qgWs%WOZ?2svY>E z#IhkV{&Q`DNh9Ms8B_s*r4%*UO);G>?m*ce?gasRv0F^Dt9TDsb& z39rfK6r!fM;twYd16TaM=ZS+dsxu&tnN#xwScrXyqo5^J3Tw+5{z(q<`L$$) zX^?9+UkQHiV7y~aX9BW?IZ8U6!#xbaMB?!Y5Jb{*a^g`5T0S!GCXXvU_XIDp(uJwu zClwKN8qGcwdb!^j!!s1R@r@YH6NK+b=TYtZVWl_cFBq*E8~g_CCID)CbuUFkM1c3} zWVpZxLpFpm^Rt*8Z#$sda8Hdb85_f!y?`=JR?IyfCHNI~@Im>>o`>E(PY0hUz;F5R zzN7X&^rXjn4fXRkpWdxs!Xl@40svuichrjE=1t<6W%#&HW*Xl>^mYO81ExBBEUbuo zd#j`V&-*^K3Cv*Q;CyS3=GcULe*0JN#m&u4(ElKTSM}@7kYk{wy#UR@(fX5Bjs*5g z{S7}d6*n6&Xx_m%*C*{VF3XAPHx+h^P6blCinDxvxW;dP74)Wdfl^ldE%t2iy6s zOem}!7KF>l9x-mHm|YB5ZxGLl7}wVBTkF>O=1z2s&W9?yq$ehr0qE%! z*_4%2#Mn%-!_%y4516lsIe9=kd75$CTg{qR{Edl@{`k}|hN;Hp4a(-3aJ*AR>=gos zfGHCg>aSs8VeOynO+v)$_6RD23hH$BMQC|(NyA*iw{Gd~vgIxh<#lZOyx-a0ZZylQ z@#EAkl|SGp)g(3CKR)`WbJT5rzKn;gZ z52!W?3I<=df}KEI?W}>Njcn}zk6HlChDlU4Fh?%oQMR6$>T+ro*MIfJnICNCt~)Mt zJ!qkEzmp*wvf+NV=dSwUDHiZRQh}JIXq=!`3UR63LT7|NR1L@h`@0APPSh93F0s{wm!H#T7D`qKnFM0|N$zNt}{{v4euSVZd%TIwFEj;}2j8D(%Lb=IXd$ z@aQa0eYYL8>Z7%){h1){jS~Xfa5c!+2!g`YyKC7`qPIJk`(qiY``g>^GB-JMEQ4v|jO%5%FYHPC^ z>k~B~%5jekBItN}t2bu^MG z=dh3N{O-d5eU*AIbe#8$AEx*p_S(d;oO_5Zx$}NPwyiKKU!-+OkT18c_l-RIR?4VR=$@_4(*RQ5UQjHd3v`hymoCU5=&sJ zNQ=(GSBInrBwPMqH2^RA4IrBnmuuj7UpVY_VAH@cmp4p$2>)%qviWAnAD3baZM`Fw zyCWwdHI$B}q3UTDWyQ`-ZlW8=76YK5pP~gxG>{HRi^bR?@Lo5Pz}nND`)J7L4ENZA zkIl}_5-jh2zTplvaFA0t7ldrMm#2No5>n;`*Sn`~fn2vv2#0zdwkJpTM2<=tDliTC zcXr-$Cbe{ODnE7~q^SM?J;1I{3f`81)E4ih0w%lZuf8VttZH^*j3Z;GavptttAlmD z)R}Z~KQQNgQ+$23e0|ip`@H{HeB-l3d67fQ2mG0a$;h_>Fi{0o}w%_Hy656t95?Af^)ZjbYyhAziDfqSB_6D z!dPZdYcuP2JkihLaFTdn9HmeWKcf<;0gP<{m>ABbq1vX#u3@-d<=&Ar3@d;UELf`G7?nlpzh-Dw&R6H zM0i|7`=Q#m{Va2wSs+K-vAut7TZj+2Q?3APswJWxs*I|2{?x3d%7r9ACpr;usJdDh z5Fw8(#0yBs1w)t$m?5ndYN%2u5NNek(P@W!pkf&t8`JYM$#_P@b2CCSQo({e!@{qySI)a?=JEF|RTFlly)#)PKkF;*iQYg@GMw2AFi7v`dzo z-9a9eaRVvTsiR?OBJ(Qfc_ti40P*h;Z54(+LkP&>tU{>d}N|)nh}b%`XarpoyZfof0;ouosb)6+u-Xtp)`%Cz~e{ zPA)DCD_>=fPmi#v^&!B(rP*a?R*rwpyO8>t_u_1`vaEtaU|aK(mJg7HzfBX0VZnFnRlNjrRykT()$jy{dg={khnka!j{eTU zeP=~s!kK`Mr8J!pUd`i^9;7&%71I?FlY&GC-^ji8R(Y&u;Qnn&Zf!L$G7Ir4llkK( zw%-aAspBc)%g?4fAD~&q^Lmy$tX}%j*H?5uogaH5UFOzhYU-fi;9xPFDsvUu z73vk6Q&*?|pPw!8O(y?}Z!r7)xvSK#DF_l9g(7=mFaF&RpZp~UQy=V_Eq;yja-8vT z%(tC?QT$?n$QP$-76KCh1r+YcNr+r|4YlG;ke-}6SgcBmizS;#J@$01x$Up=C{LY4 zlG##bteM6=Tl(I;n=m2q#t(0qfsgN@!bGb3DG42&eMHh*FKDb6XFwYDGKT()PcJS8 z_4nU|hQTWk&b0CKTww`r^0KPEZ9Jz74PR+4W!RInqnt`fn*QRDNKU)34&{ltq$p2FCOKgYKv+U+uHdiCsQNU6HpQ!hOQZfXly8p zd*3sjak%sTs^XkP{-X;3rav(<YOz3%WMoS(9;lEe7 zKAO2dVKP4(K{qz86KT`ACxrj^`sc+Y->L}8h|D0iI@FPl(W`O5t2?d4g?_$gleu^b zvG|S0d9~4DK}Vn&82MxG^yEl&%>MU}%P@#M^D)&uTD9~+#wE{`_`e>3<3MMen)-DCp~>C- z+5$dfOHuOJmz7DFsV+=nGYMvT1?Ole(eZwgZ<5};B(97&EG=4Cg8!AxD&pwz#BPUM z%I6KLDs?PO*bg_}`O17MPUu1)P~aFMtoyG=rbqo<@{^&+$vJ?e+F7@o=W>3Vd`DJ% zHMjBaxB9X4Fp6}nmlS{0u)W9g`-C7oDvEVIPv?M4>C8@jXmW0^1tv3tY-XD|$ITvo zyjL$c$@MnxH;0p3b&x@0fZz?AACHpvfj1mu#&9;@V0Pi^th>%et;F-Iwc6V?T%>ew znoewHS+q;0?gRTLh$_6q9W66^vnskv%f@y<(V#7okLK!HW3w^EqkE*zNi{pyRs^GY z*vX)&BT>X$<2=5XcG*l#J*c=i(3k2KjuH}I=s4=%;W?+ zn#Cxd*MxZnipfkI;%52{M^jS8I{bxjdHr+zk{ssWtM(eLx0aX&{HqH&1B2OmVSPb^B>fM@dORgti0@%5^j=9xLvv z5m_!fb%<$p@<>aO*=;fu>T94MY(l%7N9$>?$5ObBCgTvS2 z&yhEr+c8wfZq;fDm{r@%PBe2%a7Sy4n(7*&uWRVL2L%L(zQWPdr=h`hK5>uZHSv=Y zc7tyn&2DrF1`%Gi(cYyi#UbSg&T&J}3tyOV9na^b!2@yUX|Mf}u%5RqLDcy}O$AIAD^*9e0cW!|3 zFvXYvJD0xi`33=E-j3aifAGY;_qs0M2AT#6AOw0SzPzx2ZFA2|T3R}E#6@QV5=T6x zP$ih=m;}mUPkd_W-gM`OSrXoYV1zVRLToaJmT3kSMkBhNM1C_`^mPbq=gsH|q&I@H z;2MB=bm-*|E8Tp3Otd$qqiemgFHuMe)M9kq+xG!Zn4NTWk{*;P-4_xOVPfc_{I?fis(pY8a3tP9;!VkcDFthL zdp}Jn4v=;ga=NFm_wJo!MNZN1+sCe{;c-5EreCcWyLE?u{!CZRw8h-mi~LMf$7p@( zDr7lPdA}iRPkFBhkIf0^LRc7*#{!NfXeDbd1u0y3@)U`?r?}GwAs__-9v&Bs}>3C;DlXl&AP5qOEd4tT(2=e?B6 z873N9bV%=Jdo*W=w|{6`kv-(|Q|s&gfh6sQ%cXVqdGA~Hc5){CQ+%J2K%(|b0DyXq zbx&Cgd=Z8OtM3~Uj>=1tz!IrNFkga9@(D7t^VFv+4axpI(C*3BKWRAXC+%d$>GTLq zT_3@#eO;?87LgPc>^tdZCCP2BrGg5nji0}q!PQ%@6mlm@3JRp+c{8VGSA9<~LP?8{ z%u_!?gvo!`px_~(_h<07g_QF3h43r?t)&&2(+i0pF~NB!LSSUeclcEIq#-9|)Er^^ z8!W_QJ(GfdYzqGlPmh`!>Mt63s9|5%3PXXZ$|fS!SQS{8}D-+ndx2nQ10 z7lM84^Uy!DpMDlnMip-O+s;BLLJt#%G_XG}FVp~k4}bm;L2{fR$P4Tg|0U6X+%Wo4 zS=pKSm&TvEwp}rQgrC47QGMTRKx+k@bWeGE45xRYlvJVaex@QOL0!@!pSdLJ0<%id z(;KdCH3f_7-U04$;fot^Ye_+a%%Ih9jH&i{Jg$)^ZE!v;^|vq28!>tc+&&$F=b!3d zh`RUH;(1z8Gtd(s=@E~>k)sfh+Rl6B&kdQtB%m9!^lh|tMQ*%1z4R$UCp{D+$}k@f zpM`P$1~-ukktb}6bePH$vfXBcc<>Kyh?k)9oytqZM2AI&2!VJVEg)fSS1MxP*80D9>3`6nQ49Gz(`L|Pe`OHsN5j>V$S-w^kv1$Z5={>}pSQn&`e3i3X=aI4U(wr zNDLj#7-)dR&>^rpM^x-+|L)E!kf!m|={Q&*XajLIGdC}!VuMQWXuSj#_w{OB(Ln=4 zDp{mtdg=)rx0CHi3i3a@Lez)pGJ&mm$+}6sIc6-_dS=}1sGa(lKT9nV=$QCP$i8wl zG&NC_IyeZ~FI`uN<$AL;zz&_`D4>n`{q9eg`=R6F(qplG_^>bh?OQL=h&}7W?Fv&g ztIuyySoOx_0*wl~%J&bOLol!2+LfoLdbsg4?fag$i`(eDUz}Ij1@u-q#J$u@qXloC zTBUs#zJ8vn`@oys%`XIRfSfx%fPnbE9pq22>VRcD}hBWlg;x~Kxy_qHsAv=M4g2#O@A{VBJ zIVX^l*nV2NKFneDU}Gs&7#r(fjC@YkWBp?`0rJ6}kj$RrFS7?w6a3eSlBe|H`VB~~ zw%7dsu=W;US#Djss30vUASor?7_>B!Do993hX@ie@C=F8bA{`PUN_UA$ zgLLlk;0n)w{ogtN-utX;U2Cyi`o7OI=a^%TagTf4BeJDSw*o+F!#0cS^LyF~Xp3#78OPEG#6lNs|-YLAn6 zy*qz7DL9;{u-Me(q_CDDhwdSvVcO3{Rl3w@F=@E*Dpgq_$7T8rol}Hep8RtWU z++IX1Aa0GUP!R?A+OQYj>*Sy!2VObgstGw5c@o=C|4=Ii~oQiTx+ zix&F1OGr^lDSXmn*UDBaM@bth6o_tDR#vthH&LR1!IONM_%fdFbhD>wsa(;hfX7UB z8qS;TRK0rlomJeiw+2^K)Euq?&o_N2RG2fQ^`swqzWzppOW1)4>C<##>r5=hR5`tJH)PY^H z#(c5w`{TK!eCzjr@R|{T^T*$~K37 zJ0eAisEl@F^IKwc^i@F_#Sdi5MDeOS`f8x-aFZBC8+a!3ewWc`Egg3{Cy@#@^9_s`TpmP1uEQp?aSUTPI>P>X%F=0=^ZYg_Ck}V>|#mNJ+fpF35dy_kPu+a0diWQVPSi=cVjt5NL|$&_J(XcxbvZ zeh5_E6;UXUP>_9J#Z&DJ!w8o}&{FzM%lfe~0uVod(sr~i-$dnZ&ZisLiIT|~l9g5O z+cdrx7u&;KMgn))1~CepwSz`cdb%JYYqm731Z9E7zOFJ!UB1^$uV+IgCrU{-ZIVWe z?$QMf6{ekmIiW2#l*$h&7!BgE+CH7cK+G6BUTcNE^KMEZA7^iEQeOLHR?tu_vWip zdG2k*>jOh{Dz~sUYm^&DMLd@hJvli!W3uKH+y;Dc>dGf1#7;y8Y_zFN#K3Z(r)}eG zz*w?=(MYvk_4#j|daVBP;+86hW6_|+D5a3_P8-N9qx)yq0}>#tlO{tS40A#L>F$!`4KWQDp{ zu*yoINof!(!ro*3j#=4VxOIK0(_j<+a{6Xn^!Z~8ZEUKPf8FUNex`g(8AuIG>|*hW zSNZEHeH4*%L2^k14g*vm3I66v=#--iL-_l5pw*soU1vtTEvFa9soFbmY}d0LFB00- zXp#8Ir-|jCC4?VXVB4(JP>ZRs%O@%oi6_N=t0BDEAMn3*!13k=a&cIl1%T)Jfw}Xe znu91%(L+Lu;j1k!e^}JNjg2HoYmVwqqsxd)9$?mZ9naMfs06{%8Bd!zt1YXomJs$L z^qQ_A)5U)uCxZZXYq*WkTnarD9wT?K_wr>YAlAnLr?T3egOvhP zMjBB=vf#qNGl*dB#e03d-YQV>d;A}dWQ-VjB4N;VbRC4#Sr+afZi)Qh1t>_pe#o&4 zSl`oNpNa|zHkb5!O7&LHhfA|n0i0kT8BXQIEEM?Fe)o6IdE%6i;>cTe-S7mAI|v0i zP|>t4<%HOg`^InXo6k_guXuQtEFbXHt``1UwJ>h(<40o{N>E2Suhdp_$YyymEi~!MllD*O# zza4RhVX*}Vqtb5xhj1uR7K}@WL;@R(>z{Lc3QqM^_%CRDhYm<~0|+x+i6`+43MRWs z6$ZhROya`hk)RG@{;9xZBPONuVG%6+xQ>z&(5lmkkj`Sa)a$QIJ%JujEfqR+bCIaL}#a|Hw*b)DXa~psd_u zb-!iD2b3x=^+H>%>Gb(Ok*;_~Fn6-xVOZ3+V*cSkoBDe-dJ%toC_Ia=AlU;SO`1lF zWsZsQoD2ob6uFPF%=YWE58l~~;92$O$(LDbz-a4GXs!8n1GvgpF&jxwDEaxoGC65I zuHC&4UC-kE)F1iavy>VWYJOMvetV2R3cS%sPt2}Qi@ZWz2=ZlUXlOY2TuAkh zZ-LOil575UM0o1XA1{VlIhsSG?-wCHAb%xZ!K^nZFLFv0@fF zId&xfVc9x}WizXX#{upSubB13bTEZJdW@_KW0NVtn#$`T?95`*q>R{~i|=urGr^CO_k7A5GYJOa9W-BpsZ4tS)1t zdWdvyM^fQS7kddNR@RxLLu^-N#m9aX2IjA{Y)|j&-!!5sm+0PQFZ2C?sNa}qL*sy- zzyD+PyTvA$<6iqVqQ?inDEKT0AeK!wL2vsZ@d}E0iq`+hR14FO}F3f+Q9jWd^G5ishkT|YI zD%4+Ans7~vf$~mB>8dpoFDwRXoRUs{g0(2XFl&V?ER?PIDJadJ`yZxM71elNsr_2M zE=)z2PR;OInn#t{_n{c_HS_PETSWY(>rSb%5Ivs^peQyIle^vo8t#k@8H$FgO zB01kmcI!j3HWJD>Vm_Pw1-a~_Yefx;j6Cm&GP6^6%#rjyd~yvJOShIXNx<^V5C z({uRaI?K?6SF-G)o?$^tAiVr~5}{L4i#h4ZgGv*#?8~~#R(A^hemvFIt8u$t_~Ql! zNo&M2BDXSrXGSYhRI=hjf(p|{zc&oc6no3RW>ui6st3GYum};3N z^Wu;@TVXa?6*K`z*L*j2KP0g(Oyy9)x74YB*1t!3FTMb4gp<{%_FFZ2*T|Q1uMo+t%eU{n^$CM(-tur}g3^gpitrAMnv@}Q9^Jmh?cCK{QsP~ayR`4X z$vINmyjAneELl@iXL+9|v~m(_IV3N1QKa!$IJkoerxU-{cj!ay_n%jiOKZ}%FK-K^ zyLL-)Mrmq)IJnI{Q02H>zbuBNLaljsd8dBj6qt3Vedg#+Z5Yvnlm z{rk-KtXgQdR*IYg5o{n7*45Tr;=4dF9ZB0d-)E(&qk{!_QD^EE0?^cNsVp{)w%^73 zaPXysnMm-}5r*4VNK-;*Zi$~@-2^$e4qvdp|7o3yt?2Ez(DYW;RA0jS8n?&SOYifr zv9U#K)B;TnXo%iktf6Krd9}^aar00kaqQl;wuRJL)q&r z4iPymZK#t?jjAu+tL#@ZgKHQ#ik$uQbEcGe{vr^r;5ZcR zePUlE;(4mAqnEDjSmy#1UbBT>-ql=D_Eatyo~jsl(f;-zt)llez2Ejq?QsiZlqkb> zGL84?9_zRW>u6<+)LnbDOPAS2_L=j9CpoyAMECrnB9$ZkJHOWH(R3jMclYd%0B9!6 z{mGR9g!Wn8_yta?jvH2$*S}^_B_<0HtuQQ$@Q^(XSHzrGmS z&vjP0VXnk5$`cV0MZUO>WuR5>e&oU6Ww3Xqy}jLTrj6*wXZlkhPMUUaYcTU1LXCzw zBhTWkE5@&3?(Sl07f;)=yU=;gqESnD5qlSCKY97mgk5}R5X(2kPw?th>YkorwGGf8 zFd6)OG)U!12qPJR@=jGRh1SR65{$nb93RgI1#so%Z!fOzJs1i5AicxFoTY#!YGFhI zaBI-I13`m`bNp4|xjLd#q(3GK1>r>6!{itC$0E*Wo8PFL+bLG(`6Q|Ha%i=4v#GO-*y5I!mts>a9W>=> zYg52f;6Rut1tf1MWIt&8^r;1oBh)IwZc7}Jz>OGpwc^I=`^rkAi4UHjZ7-{*hp+(v z26{!)8arga#PpdER(n95w`Je^xvn#yA{+Nu(0{kK)#88dQ+?Q_ zn#fSR2#*?@5doJU^;Is$iV^+E#Hzrxeq5Ww8qB9eDj z9;%`66m`{CL*I_lgHqFGiqi1bShajcP7wv`%x%+yN~&V}uV`p~tb$K@ORz@;f+*KUYx$D<#yJ^_=}^3QlB70qkjCrLrTrowKhTl> zhKIFbxz{U)_~i?ZeD6Xe+Auvc9(w;_j>S?i-gfzC45#aX&KcnyJP&ynUmm~N5yVMJ z<`)3r^Q_v9FE^iFHulTAX88UQI~k8&NX59@8AiGA=)UP`^Qoz+)$Q#hdA|LgwpPOZ9zaStND8zH8Ci zbhMV^K%8n|kWb_|SwsdJfch|_4f6^4C4E+x8XfnSwiC2$m2+WeTTYft`Zt=_W^hYV zwqf34WweVT?fG%mx78#crk}3j&d4kF=xT5lXueU=(__FaB1Glah}uhPZqFa%K>LJ) z@)Aw&*72Rq#01F%`xoQBoYeZKZcAjmI`x(_L0*|^?~cnJdSsUmr=m&%$%Ww>o&C^6 z14+kiONsNOALoCNs+bE{dxnzrDx?vRqJnr#p}mSw0|iXhXbse~*ErA`$-rgHK}<0l z(*^lbB1D}A={9%m!1Nht+lcI#J8EP4 z7jpC2Pv7%PU0NP0ohAAxy?Pcpdh%;@W(Ews2MT7BU(>awlr4Z7HE|E+^&tHB%OP`@ zYWK(hsxRp-^Q+L{bQV4AHFa6uO)6_0Ew>uL+MW=^{$=1HOWSks+5fnu(d^>= zj~E5ysOQHb0##SvTKZK)ko)uK2Y5)Rr4yfi7#V8 zUbMgtO+=>V_Tg6G!5t*D5h~)%?u!E_)R5m`MjL<=uKM4;mz9~Geva042aW4!&79q(dF*}Z!}Q3C zaWA^8EVI~;DDvmB`hHp{uZdCbslm0l!4ID1-)KOm_hrUKI!?_3$U_$N`*y;3Wmer> zDsN6zKlr8W8dcyDgufgX-;3{RSl|MJnHLM}c(}P`U0m31+`RGa5XDcort|AQkC4lc zrzpYUPs#+)hDky~(vCMC1oB4bj=i%T9^fhsp0e;b5{}eL@W692H}ze7=Qd?2>iOcQ zAn999#iEh@?j&yfj|#4}>^Z9&{%$J{IaJ!1di?1e5>#(f+b`K^|N1)UhY*H$y1kG5 zLqc$J`Yc^-S6U*dAow+$obSu+4^J$Pk<5q8q_9=GzQd5r6e(5+s74MTY5ubmq3^#7 zwZFr`9FWmSa!0tqswb<9*4-UP8orB(*{0AP4T+W7OyZmK-_l}+hKAbO z+xFsmZrM&NTX#;KoEk)63iAy0(y?MI!Hc#Ky!teLkpRaLKmyjkcd$^!!vGvlTV z>#i)Osy=6=1DU+w>nt;cDl#%Mbi?%WSXEb7;pv&~Ni~PJ4E4H;)ATFjeF2;DfsSW{ z%Q?33?wFsgTOOrnx88I`a);uT$v*S3#Bngq!mM9Ydx~{~$O5rihHrbDet$A^7ok^n ze4^7j2X;vQ&+&yHo~A%n{)V@1SFx@g=_|>n#%yFn3o)&`?svM__E^fbqCh9#FY31L zBRqmc(z=MXJA)z&wk-<|7WS1T(eIIKI4#QmEvhMl9YHuT3#Ho3K%1;FxfmOl4Ui^x zF)?T)=lqDfuR{mVu+Z@)Usk5x%8Jq)^)F0iGa8|JxFgk$%fWq4dl}1Q4+AEE+bpJkVUI z%rA3N%6{d+Ff*kK;`l~Ft?6mata=4s9n6ED+4GYP!kTZ%XuI<7jzakYc3wj|$`gRt zNAUalo`et}{!C?6fMcA6aum9loVJ{lu9CBY-0P_uH*daRA0TYzhxe|c2}i0JgEGPf z>abEameZ+Gf7o9J(q6ZW3kV{zY$G`?!a}%!QP=05j~Nm?Sy+2tO^C0y_X1#7hFAps z?ckVR#MSd-fTs*yE$01qO{0B?V8!+0p#r531qeZ3MT&Q!!Ekda5MU-=M^;5Frx%ZE7TY-M<8xWvASH38xA9ZE? znCh`F_2@KWb$acUlNXT1Vg7TCZy^iSOF4Of2;^zkTYba5?{fdP56J1JGo+n)^?T9l zaxg6M3H?H!{#_ewWSu)Y zskD^P$&jj?iCS}@4x)S7ahnT_?t3(mHW?DqJFgf}Tzhm8BsKBrymkam4yla=uRa{o zWR{?gFR-eG<2gvP@;@#lBL-ecfLQzNP2jpAcb$kYvQj+t3zL&R+Y{br3Q_AUSFiFc zlqr}}_k3hb*_77LCqf#22K~XVa#qg5=moEC0nnL07avhcHSZW~RQt2`#%jqWTm|Gr zu8>s2>Kr2XQX4->-k8DCh7-@XvGPMc}$Y)7+r zHgEbY16ng|1}Wq0DHh}`PGi`){p*o0qJbAU4_<(e1OBjFb4R_RBr7ZxwCBWFOneZF zq!tcU&3srl;aw^x0!xZZ|!NQ&3cFPUij2=JFVgzkVSIXB^r~j2630S6kCL38~z7-uGc)JJ)$S zt~7(XL6Jp|Nm|d&&b#YLA$CJNzveYymd^ZQBNo%=rx=i>_7vJ)VtW0Go@Y7W(;I0K zEP5_34Ed<$nZ=7+4oz@q|NEIXLiCDA*R~nc|mU;BT zA1~6uTKC_0J4qXPzT7(;q8p#}*dGCo7zrZvbj-}}qaO6{JeC`Jx}TwZ4Q;U|ssv!G-Ho$ss|4AMECg4{G@k@#$JI#7tuhlb)2 zoC^?B0Nu_MLEE6^F$Um?_@>qO~6eW$( z{ZyP_rRKOezKV!wYIXAg_PHocwXFi}-HL)YJJ)cZV!~s6d%#9@XlpU-?7MehJZO8M zx$8sii*X99lj{F7s)u{@v0J4O)kT$Olcc0}7@Pfi*|Jvrakyc(hn=3T;b7(`XpL;neD@BYmUfwfujt&_CR!=0+vj%n%A0)$gmLjbv-jy*I=S*G5)gLY=20X?nKyEvvTQRa8+`!kp=hB*)vkDky4N-Nl7CL$(!&JfBv7w?nbxjM_NXx zb}<9nIy!9Z_-L+SD11~kFfdr4AVCqxS!vMd>+;DfK1ddQuRSo+|3!oL+Z^@ zum0MMNvN!Q$sKfTfll$ z&z3A;ac4c(7L-NkqXJV!ABbLa!jEA9q7}_kz6a=C z*K&8oyqme*RQa(OsnglEXS+ex**7H4JvO24U31Ui==|DOHoU;>j3N-Ks`L6hRftiq5q#9z9J(u?DLjl>JCn!$(zb%o;~QR;`%N?FrWS0y z>auPoFyxni*QpnIFf9beJJp)*6(pxlAH_t&S0wS=+gKd4FkRnNaT@%gpGTT7LK7kUDjZQKb9N_ zFWazP8n}pZlxL4?K8&0Gx_NZy#>mW1W8Q5LtybaqT_yF}W3um`4CB9O9b_&LapQ@| zNS!AR3>Bc^c$P%cZb@AoSN-Bfz9F$y1D5o8fm12{OE1KWPj6Lw_3@k46hc)#^7fNS z8vc2#@b_z2ugvvY)@E2gGczDJWj}|DtnL~#+bbTiABnqsmgLgkq5=gcH+|W{shFZ* zZel`1onrOem}f~4E-bJ9vSOY18ksH>NYsce9WvqgohKtBgHZXC^17#zhig_r=2NR9 zk68l$Z%Kwtho~$wvk;sWris@HIOAWws9Ci0Z)SIpEl-v;AGSaB#Y;+DzlLQmQ?a9d zeo#M5mZp|`OC*s3^E_su>SYJQ-@IcF<&z%22nb;-V$d92h$|89e901|Z{c+AkbeKT zXp}Qq)aiV7m&})LGjX?TKNlEn=~v7sjH))Z^y}-piphBTi{k|umtXSpqz#6tuzQB!C_?q$aozEp8XvM@64g7T*z z@{mAbP;M=})k=NYqi0bH{g{!Em^vimqPMH1+}*pBSFc_*E6b10k?sWL4x({`d}H(1 ziLD`FUvUFY^=qw`uRV-tYy0GI0O5h2!mU_VHi-Dn#QNCdSR7o-M72^ZB{eNN9Woqy z`+o`c(_)v}5=1)E)y;i=ApnTv4AgFIM9Zm@F)Sf**C=NN$@Vo>D>TnS{x%w#sl!+uESrUlTG0 zUf)#9rd{;bcr{lYbO-(}L8M>3Y>2=~s8v((QV?+!}p#I9hD?a-G)kONMcn)3oZ z&I(I$k{C&G|444j5D284`~SIo0M=@kx#c_cw$i#2J`;GDkWj_X>2m%0^`f2Dtl@IN zV(qb^{X#6b@Wb9EH8r(w6w?-?NrrCENl~MHl?JPBM=OJ~H-!2yY^g3`U}IZGS&(h7 zpQWG(e7sQJf8$G67s!Om(+dioW6sJ_P!P(@A0#D3Rc|-74Ol{5%*|E9Fj)Kzv7g{d zr1RzjouO>$(|vt+a!S5rwBHESU%YU*Ki?DUr*%$&KZP%0lHbD&%&%Wjk`B5UV|Y1; zbJ8V;et2L1`r%Bkxv6ojYYcQ#_cFqrJXv)0R9FsLYsA?~mMO?2tJ9XTBhliVUrFRpz@90)SGM7Z226I{t zNwpbsMEMzr=jDN8{tp0~@Y&SRisfQ~4coJgc>Er(F>)0{(eX(;b{DGfNSZFB1tFQq zuLEu{0HnYUMsQSwnQ(i7Dxo#h58K}oRf8a4grFLB-=IszNb;0UnM0i4LN{ry-F)u@ z;jm&(n9Z$!*{t0K0~_cyIi+#wkO7-6IY=A!7^6x&H53eM~fCLu-{!wM0ybXL#zg3 zSmSa&?evNnX90$2FUP_xyy~?Goxr#$pFIlxypg`402?IUneW39x`7;n7p))ql_kI0FtxvAk@R#RUv%cNva3Xb`_{<&NSE3z;(9X#J(gsI z(`18@Lmi7T4>px+Z3TaNrga?7)-@ao7;cSG-|;Hir;50!qT>2M!IYiVPmYxZIOjt{ zRqG*h)3Gg8+nOP>u@0vqTnY>5QLs)gG=?CG`mfJ&evrV7am9CUT6M5+|84!0+NuBr zjYtYETnD_07)d88M-h?`SU-!7_JI-Lk59GEL=qp%i1q@?`SriC-nc;k&c?w$IXI@8@f^Rq^#UMh7!kyPKbD zj}L7Zhs(|Yp~-6S)S}rHFvf(e(7*TgGn~aF-2gxsVTIovL|hR8&W97GhlSxW9E7 zfmqCXp+fWfas*Tw-u16yK@FLFeJ(kR00UYk!i2hTxy zKQ7@1NpM{0R1cqTd@JL8Vu$u&m<`bjL%*=LRA9~8Hv_dTJt}RZ~(jm&SoyokA zloyZ|0zH7@@+nG6O6@9#ER=2~$cdFi?YKh>H!Ip2Mc2BsHuJdU4v?mfmqsc&%0h*k z88J$YE(R<|(_dfxp6)$e=X-jv+WFF_=VDehRG(THVVrh3+8@CJ*rKdL-8--rrH%=T zQi%Nh89)f@Z&4{51Xx)5mP#}L<*Ou-lZMZ{Bp;~r!+K}grHOl9`*5^ph1%@bB_3a$ zYFz6QoKX0v%lzngXXac$K)^*&QEKU?evg1#q^65QrP!tSyWVb<5WWP-^QWS&R{^E= zgUn$n$BOIH|uUa zCq`k;fqt<`V5;NM!LHhSAgwN`{h$?dyQ4`L#=j8y2DHpd{GeMdtR#P@a~_WGC1}Uv zXeG19t>@&3b(JaIJc`t(qJ+diRQ+GS2SIFDw9oIBpxP_uVEObJ zjF(v@t$2y$}X)NBlGAMNeEgAY=1oG!T>Ah8Y8`09ZTAjeIi z`*ww}lj-Ck-Cj+_9n)SDx(};l$Afh>AeYG}EZ30dtKAy5TIZSMfOzXZE${vC)DJxH zqaj)AWi4C-)uH|Mj!PZl1Au#9(a-<_mzio7=kpZ5?KiKV$L1NhOkq-IGprHlt%(U^r-EI9)#L}tm4{lII80qKxdts z>D%}I72@^o1NzQW;9KGonx6^Uwg9hsHr*o+(F`j=ft1zXSMh%`$s1bP$$xVJ;0+K} z$bZZ;`*(%rBAPDaXKg<%I}(jI(`+X8hRf$~i=r7C8jAkvH34P4D% zAS%(O-lQfXqUUtZ%*?c3soB19g^S~Hb5ENAL zXU|CbUfLKNzd-$R?t80IUQS@c0*%*ZZ?gXlOA*yH`oAWpd7E}Z4*`BCwLiuNa+gbR zmcK)Ey33Os<@7|*AZs&6f)k%jq`_V15~iFSx)M|dwJ6QuLk|W)olO$kcO8!anYaD) zsZZJsn`zPmUeAB)oD`pSk5^) z#5e*tR3b>9h7Y1`Ru;l%AEOCTr`BDD|LYhAzC#J7}F|}7e?L+mk&q~Z%23pVI#fN!2cmC!J$!#0f)F@1gn zZE>k z(Y@UP`ptf0RdD*xMf==E>V69wKgxuzV-(ibN<6Gx(nRg{R}U9MPH(rh=nC~+$t}>mI4tmYNW+ zS^XZ$zkU;cdHEp)uN%gZK^ZMT5+F>2ot%8v;Vx7LrDSCTKQaI2;|Ks2I1m)q{W+J& z2$uXr(epnp`HzdpX_v73_WQKMRWkLk^X?`nl0p>q-gM(f43M}B#r$^5lfSB}XseiG zuDpAoKJ~SLTl{eGCVRtkw{^e`X&d$ko`q8qw=(#;;oeGd;oXvJwctc_U9Om|Yr}Bt z>qnj46*O1`^TCGRx=+^E9q=g#F(b0kdwQ@hdyTR+(TF_(thDZ94-pig&m8TK5&MTg zcoq;AVT0^V1asvc!c;%*QR6q{ZyFz9Od)}a-T-}pg5 z=AsZAGCD|$+8$a3EQY@HWtiTCwj)!)-BU{JE)oZm`dkN-jHwdoY%{c%kwJeneuCp3 zt+UjXr$*>i)K`NZ&*Ri?i4RuVKO<{+r^{kAu?XyE|Z-nl&buJduz?uBip?bgxqC>DWOg^2mJ!n9+QoQI4xL&Ec@$nez3(&e*;X64P0hg8pIs&?F&%i1%a=aCUkrxmkgk!FtF0oz^u*P|K z!I2U41!LmUg0nijWIa5xjM9FsxD%$^;d#Dvs~;KB)m%YvcA!Xdd27OMzIZ`HAIV_@ z8olek&a5_0r-K&|79MwBsd>^I=G`)@I0G!)(9T?~wI#u@=-8AX-G|Be^x@6Ht<(N< zvJ;p3x4uT<=nfI1*^|$%8lQ$D)0x@1Ie;Q=Ee=+ZLkTxzevIY_%}WyVRmNb&CC!(V z?!O|09`A8Y&)n{)Jx4i87bq)RnQD&-!yghQIJvl_JsKXQ3cc9B2}F#<+8KOelvp*m zB&r4Rm%HUZ+1PUSL*}9++-`3vDJvIy*oD`b;ND7gi4%Qzm%5ef`KPq&J>?PP< z65&V)tNck?;7!W?tDmIN1(Y*hqO-9deeW0*h-{o(1%N`Xw9g^@dj`d<)t(f@EsygK!=P$#i*z}|lzk%ooqeh$42Zw}2M4I^s=V{r_ z+3HD`uG5XHrgBkrFt!oubtqB4y9(pAM+VsTD3`+LW3#nk?kxLTKc4`DTNB!@yspBm zv7jKM3_~w{!if=am{oa|mCd;P z12-IH%=YCAFCn=z_4FAY=P_X#Xom66%d@UY>J~Yf=_i0}6jt1)XsZ*_;R)FDSC>df zTPKoTmZbiXsRPeeVsBL+wLdPea4QeBUp!xRd?Z|Q1BAFYhttJ&>hz$5p?P!*XS_qJ zz-V_}qO8z+>nFC$QDBl;md#0>Q037A?UQrjJ2E3)Fwwiia&)Hk_6^GQu8J_b#X2YwEFLHJY zS`UWi9-luC&{A~8^AiBw`J<=fabR@x#Z)nO+H{XSS$@bI@X1%wryu!2;BUN{^0?1{ zoRuABwBdU0%PIOaH#hGbMkNf^O0>B7TqQ1Lot*hf2A7tiYqfi~7mR19AIat4+gQRK zAmdgkV3*g_jP&p(SxKX(rzx`B#2OlJmc8?9JRss(`N!?Sf|#iibZw6*<8OuwDw>+v zyuj*B3mL3KSmN{)@o+6s*vz`Ub98Ba!zH zM+*&zI{Ast65<=Hae)845t#RW?n(Tt!utB74X>qc^G_(!6bFLD!}Vt*sMpHr^>bai zWrb(%9<=A$wug=b_pE*PNu}|u-RvyPE);aMuiu_H4jJ?hezoZGee7IxYATnZ?LYzd zbB}3N(Y4vq^%Z!+tsuf2c8I};0DEpf)N<`$&(M4`d!^I+SbvuptLOe)_(z4C9d+4V zBJaKewLp5@Z5Zk(`K*?5(&*Vp26t^Mb?5-OOuQp28?ph+8{tb8KA{-$VWCM#7>+uP z29P)l;~`4RrRnh)>15Zgxv!okAGGy@h&{^A$=O+cVQhBW! zr5ZSf$)WsDZm(#+0c-Wfz`F<^l2eJR@hTz?mT5G5QBxAm6kco_A%J_MI0P> z>{gk=Fojt~jhGmHyV*8XKr7`t{QU;z%CSyEx(oXZ9`N598xhKR+@1>H=IM^YBTnYT z*RO+7+gG@_NSwProVG>no-HYL{QTnjDO&GEW7N)<+;yD$`hFF2X>ky7xtJY^daN}I z3VBVx3YQ!ePL8p^J3f?qa~0H`XF%R7U-L?5ge3Z7BJeS18iUR@*>)VqZvO~n<#>y& zr>|?&8dji>do%Lh5ZuPxBHnspkb2k<=g)*7WlzA)&c5!J3DZcpNdUqbr3)WMn%EQUU;mvnL7*hFij1U~>wNH|r$%IP zvA5FY3htTlH=;adbv|AYW|B}-(?qAI2Lo71gNyrm*6KZmt&OvC5C*n!OXmZi-Ag~X z%N`r|wa9Kt-VJwrb@kIz&w-`S796XG=OkbZh0F43;+!}i!aP`$=wqUo7<~^@sAY8X{wxU@u4o+HsH3o$Wps=aqo$Ut1ZP93IHHOq9h_1 z?Gv_T2DU-_o#EaQRO}J^mtH{V?E1U+_*%f*TIoHf?sCA5k9bdh;aEZFXhT$Gr@K!g zk%~y`M8KpX_f<_nBtgtv|12)atP-=p;;zAA zyO91sJxZZ|q_;^pJs*x!*(Och&H^0AjzcN*1dMj9TmCE_mlp>-ur{;W)nDu&X%-}D z628KYN9TlvccEYQ9H7jiT>5)RNTX*n1=IP`) z97<3>5ec2}HdN*8b)IlN4OGyl+tZxaUbw%rMCM9Jh9i<&hgXW%Z;NL$ey(xH7=E|b zP>{ACon&bAK9H}W6Y{T&fAa8aIR6_!Hpj5D+|M8Tvxn7ZImi7uF2&^_r2rWD1&V~H zQx`Q(HAy6{y?eOyMEA8)J+M29ACE-pkmnap5}aN3q5S&V*t-)g73OBr=8Imo>?P2y z^@=;7r29(vgXBVZf4-RA#Vs6&95!_Vs@-q8xV&o^Dka~rw0$cFPu%u@0;oQd9D4OS zZC6O zwp{)BBR&LF6?GG}Vl3fwXACFG**G~{vu?8Q*zD4+pEa^A?0+g`FM90}jtulVMbe4{ zfn>qQ%y^MUICZDDyX&JQxMdaW2;DY*1f|1FX^r>%>2J&BqtfbL$|ek8;nVV+x*mKM z2%3yeD??$vr}sM22UuNXHYB6c3?Iek z+KRl%v2xFI;QZ;2mZzt~1Jupw-@3~_=jAP&hWalX4?7etV^)9Jyw<-ZB}Fgra2Rck z(ij3J^wWovi?abJ6nd(tE8d$2lqEJsK6KL3*D~(%CRzGgkC&W*&gso98i{4OR0(e) z;VM_SJHubSBB2&}o^c)rX;ax0_`}~oqW(WS`FptW$}ZHTdij?VGePQlrxws+ob4KS#_P7CCqztF|gG#{u4Ds#PYQ5jHDQQHYMO+Sr(Q2b+` z?u8m!x9Fh|TvH}p!7${bP@I<(E50qXa+D_thsB2J56!U(7+lYq3V zg);#pCq2|t{O*3*5*;m^enxxNS2;_={CXd}4w~3^G12MuPM;phw7O@mSLHyen50HX zLgGW#^~`voTny%EH%8Zc0yfVQNfRYsQ9wl4DoO{ly2;~Wi7vL}W!gS`NEiQ%S*AGOHl%|Gua?n;@os;;Cl`iPmkfL4^_tvFeA+> z|4Yr*k2$&9B&?N@lKOtzlP@yWUg8mn+_RHlf9Sk4eojP$zhzuhjkd#ciKy#kghjPy zs{s!CNO^>y+va>IkSD!I)1w|&p>{h-^BuwdcBf4&zkad7x$OPl^vsOjZ+oPXSt zE3XBSYc7J>Z?fO_!NavcXFzkdwa_U?8y%6HnIjMq)Dq&T{@qc>$BYIEOirxFe*OS7 zi-(iX)%d}g8@*=3vA6Lvixj1?u(i3_yrKJAvaJ^|l0^CMM-#_mPXJ9SGH#r+oMP6$ z@kMjhz`gLHKE5U1IPQ#XOEkUztmlUj=fzn0xlkA??D5dXZ@W*+Al_!Xl{#8IBs0bQ zRyrOzc~ZM+vh#Ao-e!>NCaebZv@G7a5@X`>y|{m+H$Z)pAsmDieRXH4lRzHhU6n8ztWf_jhBcYN*d z8!O|4|Ndu01kI3tm28Vn2JAdQ)X{AI7in(+6?NaO4ND^+p>zt|3Q7q|$WW5E0isAt zgD6N0NJ$MMAqc3HN`nfwG)PDeT}p^F3?(5objP`)xSg`0CL+oe&t(yYkOzb1T(*`bLyig}6XrXA;9V zk@2k1t3rm9Y({;GtqfbSLLBcgbMc~*<}e7K^gO(kUob~>HMmB-if^p+-Y3#Pvsxvm ztX^%T%5^SKFBvLPItf7W<;zX#pe?e10f#fOk5gfIJomZebqGK+w6ye`7cbI51NYiG z9bE;WZ+)6LP_|X~^6#F`v{$$`!OmL798l~bFwQ-5lo2;8i{_*QX*w(Ugd9K;1ScVPclirG5#>wHy&#%|=t=oxDQecWQ%(B9tWNq&F(&ak0;G!2qB@xf!e9<#%?5z&T5oE zxa1)qqgYf3NIY@@_+`ruA%iX?rsIiP>Fq_=sftSq)978Dodp%&+IxD|HNotNcYQ+t zn-vKstR4FP8g1K7xQ0K+_LndH)p)-v8n)-9?m#a1`Sw|?1sz>XM5$Q#wv`Xa7078N zVEfCBv{w3|0oZ8RaB^cw-_IncYH~KDE5FB}47Y7^gT{{bNE`M#r z&(^;aIOvEt{?NPq{N}HQ;SOsguDj8c`}jF#9A1(1O%A8c*h3cH{Oo520$+cY))a9G zxXpSZX5FGk^Hxb3Gu$z9<^KHJ};CYS^dGcgNMPD7yskuOZ<*Y zW}#o;H|-qiN`ZNKA`Y;e=V;&|Pxy$BeVF8R-P_wk?${KKeJ;Y-*uS|#gyp7^MLEri zk9=!-3{S)?`Ox))d}T#>&cn~jHWC29OwD;l%R9P`bQVh^N;1O zOUEH+3L{%G(mGNt>7)ovfYewHUEPblAFZ<^>bSyuiPxWxKk*2(8iE8a*}8}H!4uMO zx-#%hy#)wnC_MtR8_2is@5h9P2WJjA17)00K)=Maal{M2BqmNa-;rNdMAcAqG zoqWJ|!QC$woL@_MV%dA>*A!s;YLR5(=hW_zpm7E#&8egkf_C$LC}q0{5NfjV#6xX4`lf^#n8;C*tH1yELENxvRv2w0b&f+ zSzfsr1AxThhJvD^4wwUI`0}pAY`S+5pFtrAx6P*8@g%&Y*muk*FIORu)r>ISfC?%V z;A(yDR$8qoLj^t&*DE!#5Q;<1y&2~HPSS@xOPA*lRw3;w?+}JU?!bp7`G?yUTib!7 zzR%o5$w{c{FPk8onkD8!WKbt~j(?L#_ExU9xIg%TZDL~LCS`CgRV{mWp{)JLxv|iA z6lkU?*4gVl^k>Z$Bi5|cp`@gIiL03m6taA0inQ)n!vS=)JvOhv#tK*_c(C>tC* zB1G!^l@%QKmY)i|#UXbc41%9IAg85OH_?obqFq$F%aK-?abB5C4ybf{?b%p_?^iIb z$fzhVU@~3>(=yjf%tSouQQw~I!GNgKS?jjgivdov2N$!n75^f{4%n3kk+6GQyMkEy zU_C%ARIwSm`wCcJvZc^S`aCW&WcWY_$riYSP4G=Ys#y5hfx)uQpptcEAeY@FooeH0 zM%L{tR&OvEw+;r(Kz|~`(vk{xw5td6Vi`8%MX1ap@ic`a-B|#% z?aSxQrPSd!yL4d*c8Jx5Ap)pD8Dx*JNu?zWu8^39P4Bh9M-o3P_TduLM8sY`@CU)b zAF-eqju2CSywzS6ry;KP?+9+fByHx1O0_t7VMqTwGD8fTuK4!r z4GFSKXF};R&zxY6kdj6)?Z~J|r*?^*P>GY2kZf$e@D?DZn~&aM?E*(U7i6!S)YJ<= z-RE6@wcm*6=vL~|BKLJGZ+3Qe{{{|s9d-l#HRwh-DJe6b*ZXs<3w`|pAp4AjnqY^p_9kyHfBO`Kzn?((i0toY+rE%r))KirpbSHFl@WLI! zsNG%nxJNz1!wk~y59*pLyYE%jCbrpKAj+1B=&i@8nf6;vGC?QD&!CGEZ^?}Ua$%o$ z+~8XJAB5}nU8612ic$x)rZC#r}cd+sP+%G63KenU9yxk1nxSBfs>p6z=xw%G=! zB4~#>n+~rCBY6W;+MD9e@MjPFPS3=L7)kiV@VSC`leR!s zKVf$9)r~$&_UzN1>5@;(Y;eU{kNvtH35RcKyGu1x!$M)MQICo&#h&X5`*YVFpd8XP@30~WHlWpE9Hy0~JsIErw zTQAUHRys39AT63puTzY%a1-U?UR@Q)$Wy~p%zW>_{X|&ES4wi>Hc__W?G0(v5;%^ zcN!{EU%;syFA&!+|aIFC-{jUlA$h zdl3d7`o1^ETI|$ybONIU^tqsolMHjO=vo!1ZPdT}tZz~G{275=g)j$)*DBNT<7%9F zS0#YfZ!5L7>UeXp%f&9`Sv8R#Q9qfX@O1;TCsZ${Dy67Oov~FVPAuP~BY<;24Kia+ zWEe^|{5NmtXPKZ8b9JUqfltBp!`&g4)H`%KpRZ&yb@FhZIz z`S|*lM?l+J?NKD3dH!9#+WJjo3bcwDS!lkbE{l(6cTnq49yDTNX6EOcJbohFoM>Qk zDaGRLt6q)d@@Q@A48#1(>}+Sxjy=A$KhYC;bA5fj^8~bp6=*H`2e`aY2@hgibpvc2 zSHOcg#A8?aHB!~82gvE0t(xcsUbk$V6c^u^g}&0SV}X;s1SD^%>D_f%p9q@AZgSP^ zV9N!y9ahP|S3U4Vp+Q@iACQi$uJM#DO8Zd=8(Sp#n`>_8Hzek18Q_PhA7 zR0)O*Q{kC%Qth_uM3?x)=JV$?u479oi>v2_{;t|t?F{2SeVRT-e0HPM7^7>uIa4cL zG^R5#9Ch$EvoX_+B%2c_B+0S*WaiAUS*`nfS1KB84aj&x7Yt6L?4x!q`>S(a6zg+C zLT?vd3X3;d86s1&T;Hj&g_XOTnJZ;&-h@HP+|LrChmEWF7Un{pTzpgRyHWpK0UA2H z>y5u1Q+WRRwLg&an1OxQV{ATWd>Fq){=#kUM?lOFays552FJ$~nfsRz0k}jrM%NbH zIXIzq6k7jYg%tCgJ765h#9jOMH*=DvyQPnFcq~3yw3&>a-tAd!txU58oz#G(%@b|SGPGq*DV-{dK?tVw z_q7ib zioHby7bHMdt_z3(Qu4`xGX|W=YyWb7xQ=rwsf4oP!L@@3&$J3bKEc3f#ooc+Us76X z(mj9vZ4RB0Ok;CvmH05fyn};CeMtGyu?vLAjJ#*=@?YP*d3U8M{pLSN5CP=;>n~Ev zw)h5~1g{%dozJ&lkA@;FaY)aZr#*JPPjkh?s>oL+d*D25a(xC~J>GSy>sxPQSEhs- zJL%~Kwr`OAn=0>$nmKo-+HNQni=$Y>+P|?}?z|DdcXMWarcZUUD5H!u`o+!mcdE0* z%{r9NI-_@-5lIkn4b@$N7v7^IKM*bZHM0O6L>k!X_wIUv*1TiAbxq1hL9+RIT3lL?&ad<@Ck>P~=d^A#=Z1P`WImrGFY9|;6Eyu8~w%^ccxV-t>5#5yXn zjAbd)NCX-festSvrc~0U(Na`as_E}%WAN3laMs0yiQ3mi%1AQ zIZ%aR->IhRnodAaxLVg*Jo5{Y23h0SBCD<$LHFF2jg&B1(Z;orL@Of{p51&*z6#l` zQ)pB)mY9fF4v$P7g}>``NjnsVxdeYf}q$7Jhg0#@|phQ36-VO0&~5&ETtQBkq9Iho}!6BcL>)ydt$er<1Is1#@!FH2^93+t72kU&;D*Lf5awi5+eI9Fy|q17TCsJ)M#Vuga|R zO59nL?8R0-ra})73UWrMHszchxXDXPy)hNUc8ANpwYjx^JtvM)M(RyJN_&@N{6Ko2 z_sW$k=FeQG)d}mvI6}0IVub2kUCg_LTFW;ipuNw`d|KV@hvR0Va&lP=T6T6WoTg!Y z3}H!1#xx6QmUtKc%+! zmn-wF&&qH7v<1AzpYX@reH^D+m2chpFu0g&TGzIr8jY7NT!|MOlXUdlNSCb=EWIA zF<%%?C7n0%8YSd+@ltWX-K8IUug-^mU3$&V+;VCZ2VN-Mc{K0j1cNhnUYBQ7h* zqn&$Z@h80O-ZQ&1gV75o{+4hgp6xkkb@9d5B)dge)&p#3 zE$kHv*EO|_4NAtvnYfp1>LY@9#AP0wyy^w0R(y=V8t<#ZBX@cPt7h7$F01{x*tmzW zQc6@JXK)MBSFU$*b$1_Ko@iNCEJASnch4cjkbwW1?A*wj&%~s~x$7O?=*;iTOgE*8 z%ltnuvj(3bJvSoir#Z|SyfJirR@FNzM}cUixEyt;E5vVbug0DKV z_IeY7-%%D-B`)h1;Q6yyunXCwL^E0_gzId#5kvX;kuE>3fkf0c{-IN7w?OMlJ`|%B zjL#XYq@P@RU)f~&us>G#(C8Hi8s8(bzIy)1n2?OTF_75)z9toruAQw#F7lx9B&?L_ znouQZo$plSi5_*GDw7}=x4pw9vdl|HPTpQ*$8iDaP`fO2z~gOabsb?ObiukC6!+p3 zO-!Eab-Fyb|M;|OERfZ1WG*{t2EtA+Q@9qanAwo2%lYAK1Q%_ZKCF(@Y<4h)X>msW zxsAGae~s2hOh`2+|Yh}b6wo|6EE^v zw#b76$8jUQkeXX(Nl082#+501E6lPl;SRfVOASgJA=~}#2+=Lr<_SJ|pAtG_w0_Zk z$#fiEwjgaEG0|5BV|022-PPCeC_w_lByGtBI^Nd>K3gACat6z*SLJ{&{Ocyn+>+}M zOXTWZqaftZ{H8OfT}gai>6PBNgDr+4MJ1rRriS3o9XTsKX0J^zI)@d9(b-vFnwkKZ zT|y7qQF0F+P!!%jrf~W4Q7Ngx#ligRFpB1LUTkzU!gjE+b{*vH#s*obq^L=)tyypy zZY*NEKe|QOM5uuePgi4XZgS?d0_ymN-ywom*R5iYm2RSze|o^yJ~<4r^-T*zb8DA<@ex z(4Dn+?D-89z5=)He;Ovr>p6t>C_pLB{=tLVh^7-Glil=CY;b`7rXfw@vNoBO$E-CU z9j7d0?LNiudGC!jM81@qxYSqik97G%;wH8-_YF3AEiKAgEX@A3KP`OJ)Wy!1_?Z28 zy((A@`5Q>#5~ta~?#$bf*MKS1yX7KLyFpr4I=O&h7VGW?Lb8g72iMKctfssL+ssjL zdI-coX`suz%*o-9nVC>_QCjHWQ+8Gp-0gRRMh4p0Q0Wwhe;60v|Mk8Q=hJ_gpx?I& z&u(aYHra)^tVG?Mg;pH`hwN=F{(BGcODJ7i;`EZuP3sm6R=%Cj>8+%{(?>UKJf`s5 z;H+}Bx6@cG7$hzpm3PZ8Y!>{~`NG)wG`=%K*gsvFq}PnVwT6aHbAA+s9aM zohS4Hw-?9eUIE!l!ThW62iu|Jwl)J}(;atr^X|ln+@@oztBv)SzYz+aw9zZusM4o1 z)eO4xd40ub?#IiprBb-IG}B73A_;Buiu`*`_~IcAIVmc7O!}eJ$pO9MX-}rc&o70K z8A!TBl-g-%+!QudBwb!v$=&n~GdG|S3FhJCr0KC6lnWbKSl+-^6cMhk9}^aNAkkm2 zQq_^tfE*noibcfS`o33^+**!@%^V(Pj(Hgt8Tqyu<T#Hzy`_uh!#ARtHmZny`tI}V#-S1}V`-)nJih&Eak~>!xyUEW(zHJ5 zrS2>bW_sCsvWIIU)6XofKJfKaK)`SrQqcCE?h$8XViLEP6vK-{5XrHwcI!p|*Spb^9(Z-@Pj=rU?;>rhJ!o>;KDL&LS}6Ok11|`cf5kMpz)! z#!nZcdUE6Rr@ZVMex91u#6Q>ifuK6(JzH~Gslq55;mcRrAF_FU-Ksft{$hQkTW_&- z$~uPFYN%><7(;cizftqIlshGKQw$l5v%&03;#F+$=a?dsjd)Ly)rUKoZg<yU% zR|xr5wzr!=eC}NUjyj1iUevm0xM&~ey`rO2m#6QVGR+294%}Vr0 zPb*7BMSS2>$hjclZ>*zrIu$0Kp7|O1)1KqS z3(yowr-Q5{k4lX-z&C*Fn{cy>49r_rHa2E(i(PJdsjGvBOf{VXnSt&Q>cVkP!$R@Mq1v>N(8ytgX#@;yGrbCjm> z(a69Ep&T%6|a#P(P5 zRgiEyS5vZx5h?^wR7VMz^cJ{d4G^wI2^)pWuHQ6L$0HCo?TE@u#j_*~KfXt8{0gG{ zYq>^LuU@V3mfgLzJ;<~e#2v#>wsIOQ++q!t?C^w(05^f4;Aac8`xN8CF;~0!lD+vN zS^Me5=T{b;t)t)X*^5P99M zD2uJ5q#)fRKuL#%A)40(cwCx8LBQk6aT>^C7N8-(Fe8VwU;fD|jnOUIkMW)O(5NEY zy*|Xx)k41{TxDBi4yT)vKMaISj!tg}-eF;}{^ahNRmKBK1Tk@O$Dp?X#I2_8eodn> z^!iT36v2jYX3feH0G}-0u(x{11)v9*tS)=jO9NAt-iE>rSZQYa$fFip4Q|@l+L8}T zvcEM|5E(j(K^5607&0Q9BL+`-NQzNTg$te(6Rh~+SH3}su}pBo2M}1oeVrBXcxqPG z$Hd2&Z!s_WKcbzI3+TkYaA6M~`pA_La*>Xzs4x>o zfgBMzpO2ilBJ%0e=ZT?m%j4nSko`6C#Bz_zUy0|dtE;1SAIbm4lbZbI&4>OH(*Pj? zOv%<18+=7tX49Tyg23@~{@2(vAFu$T?S7*%FHpYvnU~1{{bx?yr1O){zg24JHXkS|q!E|T|6X=O@C~|PPau)g=D3A}FED)|r=-F@I0vj96 zpxcn8th_Ae&b(wS!o_gD;N0XR$j)r~KPW=9t)s8c&4*yw+0dRtJG)pPqq;7B;X{5T zB0WFf(SB8j;?w8P)Vp%pAJ6Z$@&JR*G}Ye8sZP3$!gY4?m^oTsd|@sU|1>um)ngv@ z`h=)DtRHd7Iad0gCoPd3XGjBr>p6cRO1$cCQOPz(Xcyv=79o)3%et3qw8#93*5X#W z5+1U__Q0z=Qrh2UxXHeVeosl8fMoTNpC1t=>(hu*H-IAAxh1IK<2JiHwKT7;SUys@ zU|rPy$D+u2U6KjmmGhCL&&e-HAEy~ys4)M=A5prEc32U4nHnql5{W!{2q~n|L+ITf zc%)24ajPpAo;ePmeFnlr40Ob|%eAyPcD+1!$k-I1GX4oM%Iz9ZR9yAGTCq~)rQ;Oc$&^FT*jez_BS5(oqHU491hC}GH!Mc;LO zyM6fH#n;ha$hxEM9H}+(;5C-ShoFvT1k2}ZuvA`KUCjlIFYcu|H@A%93Un~;z>k=#_cVTkSMa(B zIXqujPwV!UnyO!b!A#^40?fmL+pPSDHkui-psph!n!Jfg@O;Nc0WBc;ffhW(mrB-^ z=7~y1Re%GYxf(Tp^Jn^h?K*_Z-!m^BW^Cs~;l1A#j>Xy;z`^QLOF;blRIm;6%PZIa z1)uAsR=8u*&(EcvC<3qGA;hch4og>%f9KHi!#Yx`qZ45rnOB$X?SCE;pND`^6d4LI zkX$`9l*I67qH<<*WOsSdC~bve@Atxon*py&!fV_Pzi{lKn}o#Ag_A3X<<`%R3if^b z>j~rtaO;yFXG1Pa8tv+$eB1+lRd zziy@)?#P^xMf^H4>WTEQ^xgO(rHN=++|szy{RVTM6PAHAf;|$c4B<*h_bmpz?65Ef z2nZAb2nne-=8uexi^1udgOswv6-vr=Mw$E6&_b`1uCcoOpyA^i@`C=U%+|TN`yFY| z32th17Az#-Wp_Wm{|n=h^YO;TU7EkJK6P|2ZX0&U9KHNu`?O2DV3`i%vl#~xyHe+h zmtvabY-Kg_{^;&jdhgxb%PFFnmyI-?7OPj^PRd;eUhQ5p48+*)N<cNFCY*f3^l%ql*=*U+&Khdmn+s zxVgUaV1cjf+>Qlwtd4-(=v88%@bOC6OwYv*w`}AUSn=V;LMpiA72_k59%A+lQu0r6JKm<3UA=z8e=SR z{(iFOG)Mt=bnwvZ%AI5uw|)HL#ql{=YLGyn&^4U%pYKF4hN^|!(9-jN`BEbGvJ{W( z%F?=G0Oh>Ny?dPA-ea}*-Wau@(Rk%15M>OP&i2@y^f)jn{#fwbQ%^^yabs$8N#k#+ zn7?M%ustX4eP>!+wGTI=#6Zio_gJZIMV}N~r%N z6$ZHLuTIna)j|j2D5IZrBlu{kOKbV!9RiO$9Diri*jSw8FvXGP)%ufdJXj*b)ZS_IJ7?q6toja$2 z0*pR?UDhYs=v4p+4D!2L%22XOJ z)Z@zdqdVN@_L5fL)O&k-8;1(Ho~;3kO~TM^C5p}Ao0&3IjbCsyav9=w_l8g=pLxXk z$@AyQ&%cumEU4Un0xWqQJ-zDY=9~WSZEf_qPol9|Y-L=Y6`2u!#02{$u)$}UZj?%j8CnBzu;+sd8ZyfwQmU$RWS zkIm{^Jx(kZy)3(O;7mD44l&`xP%w{95jy^d-ff#7 z%(P54dxPNt7!s@eJ7B*$%)oley+vgaU_hw9H1tON!K8I>nHx-XU&NI9Q>n(F zRK0DYlk!OK=jS4PM$xbkP2@SI?A-lx=x97aH2Ex{+Oxvfd8Z}WhijS+=BID~x` zeD7JJ2W*S+i0H?0-dL@5g!N>n1RD!Lx*Cg(HJ!rPkfO+hC)_rvY9BpG?9|o8SHBNw zEPUk;VCvu5GR3f9PfAPgRiIp|H**=K%uhm>U&MT0U%zQxkY-~`n|7bJ#knz^VUWV* zI+<8b!BV#{2IKt3>n=K4Ac@Q9tY!$y^o#aFK;J>P0)__Xe8`bA|BBplN8ZoBJ3iGl z+mUBMPQmtt16M^oMPs3q#==B*%-Y)e!CX6ge7x=7YL}JFzag1cwdfa1N{gPWNCrF6 zlB|7r;8f*5EZ*Xx$lej_d~E4}wQ4Zg?|cb~Q{B7D>WDqHhxQcE5)mBpN;hw}z`BQq z#YK5*>t{8UZfWUBBypKT(Q;Z4^=7qS%h`jup|h}LHfQPI9J=*iVDv91##o9A!Hi5= z+j4#!N%lWSQce{gb5W?ZI;lp*epTyrP%b-S#7!1uJ#SLX{uwU`BC?)_}GqZ;Favv->UCgE4EjHxC91GndBZ zrtC<{$fwNKW(t}Sewt&<_k3Vt`N^?_ox63ke28S-AwW$OeivKIOMVtxDNCILQfvma zrm5QvLF_Q!@jL}{uQq!Xzw=y><@PE`?mdl{^>}uMUUms(vOY6@4U>PF>5 zzkJRZ&MtA81nX-Woa?=F1Kf5>Lkax)6?jAtB!`~LG9rPJnT#G$S}w>O6Wk(OYl*ZANk~XdiH)mZ?Wr^C{Vs{mqu|O zRFqV_mv{U2{5x->5ITGIY;}E8_~}H;Hg>={7X~i|21)uGlzuJM*F5DVEpRgQWQFUJ z$k=}0(0i!ElGY9tMGG2MHNDhf+8^lEX)(HbwdEVKwa6LY-Q68M&_Xa!?Oav&_98%l zA#0U3^18aO21i*&u&IO3`4&F0(Tk{iWqqeKw49pUv|e#``iCt1jWXcbl7O8-;k~TsKW{#|;l0zS6yWXP|W*s<^zigT+T_f+(kl$xT1k-e`sb z#pB}Ao(dBMCbVItmjdxVsL*yuz!Zz(yGE>M1nTLK-*{hyYfP}#_0&H9JVyHc@#B2R zx}Re`y!PkaU8qzP&tN&T7NjBHdg^^ef`{y@H_niIAG_$g4Vie+eg>V&m0yvK+z=p_ znLDB*q}MJ(E+v-!8Kuo4-3`geEwZnFjc9iDYFw8IJ#X3C%vR#TtDZn~c46+boQHfe zCbbHsjjf%77`|jJ$jdii<-%_}tCh6kteisIpBIPNNcGya z+N`0STkHDTM784WhH*PzJcoEDjgK`+6pP zLev>jZyj9fqe}%z)pV{P5pUv6IjC?qx9Y$u6kmr5>DEm+`u>pm^-yd3nTcq@y4RQF z#WEbGxZBj{YEeB64dmi>cVG}!iLyqw5_%xt@*E{wPX&tAV4Or3y< zQlmA*Fq$T$KvGrcx=ZZU67_UX%*>o2!8|c6Ka1?22Z3k5!bP8mGK4LdiHr_`3;H|h zNc4eiIM%LnnC|0+)4^kG_)R4!^scjkjz>yI$p`DH;CmXP!&TO{wxJdYUXNep8-B$- z(N|71mDaHgYBo0R)KeAMNvH$#A~`HUv5lPy8rz+9*83c4>+8)%Q2UI+ro5j%eR>`n`v_ZMK9fOIN2&9(0!;(~Ex~Y9^ z_4Ghtc+xwsZOT$QSS?O+uEj-4VeFS2FhoRtz$l>C&VFYM zoDL|d`R;CiYcivuK~fl$K9B>j{O`-EDi2mK6UtHt!q{A}ZiG8jiVzVgL@|__?5u)h zf`(Z{WEAJ=)0CFS+nU-u%A`Az6x<=QZX8aKKr(;t5jkfsX9r_XaSgOk#gg0HuYn;) z+&dp~a-Pk)>~5ab&O01EAU6m&)6juR`tN zS)SwGhR4*8xF}XX-BAM{@))mx07xjqxIO{>17$BS85ph*nY5feE7qK|Vd#r6BJ)0Q zzB|;c*?OrQn0F7=BBM?JgzLEaGbFRqnqVdj-TEZJbR_|!0CVbU6c94-UP?NheevR~ zm;5VSJw+cNjbY+LNP{=A0#$;Soo-EDwl!%Dnwp^$9Sa_c=$P=BVCb(qTeRT3(UyeDpj)8#O&H4Du$lFJ1_aJ-Ck#E;b@1jen$k zl@Om!(`_cS7&6ZQ&&MMr$VfnR@ z8YVxEjlJcADwwHEO#oF?sf!YICI(m=(y7f?0oOYWWhh)VkLMmCTE&I@*=6@*!6mAL zW58zU=C|itUN74m3;z4+)lPdwT;L)MXUyFrzAnqaJu>05`yQPHLR{t2=zL-;uo1J{+{xYC+yGGHg{hY< ziPz@}L5=aEVeEPB?n?2I%&g2P0fQw-%COAQ5;Ab0XRsqSP_9u9VfN|H)N4lfH#HrF z)VscC*rNfp5lW}UGcr^%Z)a|y$yz>CV6CE~r*l$drg*npqR?%ySADnap|oD%eV-(S zZEkTPqds3=KKY@>w7XCxNHGgohhn+{6d8cO(UMq{hw2$B50Uz!%i6}DJQkHD*t@uM z$Nb<#@4L4K3cIo@=Zfx*K3%wEko!IRoJD0gJF9OTVq@&iohS;|Y=hF!-r@ll87lU# z4BFuuG#m*!_t1PUd|~fNeS~fo-}aS#4-8C{lhKX;!2<%nr^}vnL(_e^iDzjhJsT!b zUMhB7O3r$1fuK#p(Waa#2w8vSXu!|>TW$zdgd??D?_1+!6C+E7>{K;~%bq-e`e1^? zWSh(mEsPYvl6l&BO5jMSHLE~ERlaly4@x>A43@Iqh!Fz`qXpv7ux!%7_tsYl%YcUx z{3^ee5))*QMD>75@$Ph>mCItq^78$^;wn+Y-ov~EhRACl0!rPL(AJj06Gk4OynqJ- zZ<@`5fOz`G<|mm|Qc_HLIUYO;n#?+z5wNfLh0X78Ar8-z== z9;tFJ7!tl$GJBWq_tU;ZqGO_sP-))s_c}1ex!m8R%el(Rd*nO>AKhOfAjf4^i`M}G zVtzEF84KR~VFrNNX}ECxMIe|7ZCIj*6BgqTDf#1Kc4+JKg+lx&XyChUjs{Gy$t8I2 zU+m7%<#spv=x_>@O8h!fRJg^&=#zJ094Qo?L%`}ci$JVjTkS`V-KlTM91V5`mV(n( z_;OMF{@zYYLTJ3Kx6GqQk4C1a1A!Q3y0tv9xwkQFAq*HOzut$x7fu%D=WBEimARdd z_uh9q=A;vXkK;t8QaK8)6oqXDHk7^ zAN(ygz; zbN!mg?0Y~(b{;lZrFWj|4Hwqx1adMQvZ#TFRk>gFU}D4#W0V=?7MQD0Aw(5=<|PUE&H`~Lr@aZ9%Q z1#YEMR=RT+*kLp%kbMJ_$4m#U+cR`eP_VX>c-syJjg7rvuHb-6L+Y#zFv?;4`ftO= zuNOw>WOG?hobUyG4%6jtO^~ld`q~huT4{?wH|oR zrRovU-OhK%q67`jR-RdbsSn98Ag^+FnH>1d{!gD$NbSt!@IF`vEd9GWZ*%Tgs%BvD)wDc; z8-|0UJJa0f+rbH&TUeU8_o`8Fu-~*n59FF10SqoIHkLIaBI@Miw|kr04_i>$Qc2wY zCXLU0mJgoO^WXO9iOZ?qQy!E?AZzm^vUFGzpgDpbIBqy-d|Az43G_GFXBI~2Ch8WZ z|5Hu~-TY=i?cF-47z>>Q1qaqpi6FQy@*dfCatDUXMqxU zt4aqKRKA>tge+bG53nbg=HMotRcx(I_@2XiSGWSa7qB{DW^xl-QBkNNeW{m{tA5wY zfL&!0sI@?>T;{rD%5+Y4W6%*o02VGTE@KM|Kii>_OOKmPH|*u*Rp{F9A94ZkCs^@t2{6 z2|WO5t6V2TM};F&|6>t1!UV}mn zr7a)_<^W&(Q@nI(lu{oXu#=%cT6E~W!Eg%U$i*OIDRp`qW7!ZICvn{Emgiql7p#1O zP#er+C#=SIoM9*2Ucu-31xVp7{N4$dKe-$3=}XP(q>H|P{|>X0wI{L$K_!tE#3|F^ zCxDU-6_4EzA2F!(k}h$cKO1JGcIC5S=jT1`d0wq=!nkalA?uS zI+csTL-jj#ty{lR6Bhew$7q82KSZPdU3e>}N`QfIxq0XV*C0qn^p=<2 z`OMt617of_(zS({HtXx_o89ffqY>caFD4x9lEC~~h|+3O!eq$cQt|8L907@`z~JC; zPp`!)62SkT@Weku1l7q}V?E*@&YYPW{HUS8rj>%iRo7>_!!r+SL&k&f=ppW&7ZD23 zFq8f{2y);5E>ui>2N0-Xyp&Aevv`<4Gaf8d@yrJcqkK>}J$9JPf?3;{FB)#nUP(f6 zLXBMXvE~TlA<&s71aCImas?S%&xr%+oy`B|WkI-nv+8}D(OvHD!mpe6t>X0QYcG%F zUM)V|)|+d#wA8>c-ecrl2ia*yG1AO!*sjuRZ?V@b%w)L2Q}kY_#hFBLTu_i=QFFLA zjJSe5_w8G1O(<9kt9%LAx?cqr_`!ch0MgP?+BKtEdY`MS@f{~W`H()05w$*zomTz( z?`JL>%dU3}uH*~Y;9iQaar^L1UcP|W($KSMEm-*RCdB{Kn_Nbxn=oWRe6S0sS9CW1 zp{4oFvb~Hqk9#Gv#y{re-x@_6pCOl-=r_dy&>kF&Z=459uj>5$;*ul{?DH`MfuYT+ zPBe5FoPnJg45xs<|>mtAg;({|i)((G)#9 zKJFJIejXYI4JJ1E_!5IrMV4*Gtdg`zHxB9zoBBTDwBDb^#PFof{3|}?^e_0-r`A^b z5ax@(3O1PC|6MPa#?dwI=cbsLrEF2+*l_$itK6Sr*OHa@Slr)o`tFQ={qAZ}dNyfM3a-(N)U z!7R3D|Eoa<8Mh2ClPD$VWTA@T98ie1CSaDd5`>$%|DBd z4olG52O-K8*Tt$OMtTv&g%JUM{^~G3t>$bh)^-kVWeUGnuPy@ayEwGL2Vu29q@>df z?+)E~>BF`66zp_CdAI?Y2c{3l4%ii9#4Sh6P_^4%T^6ey4 zLIPIS?(d6=N6L|%V&>+9>Ra8vE_I?D8yvw*f(Op`4sR|5J-;q9j-`tZgoDVuH0EGM zdyWuSCIUX4UwC**nMzSv87o9o)gZ+Oz1wXqD3>p9p4|Q3k8i{eC7yjCfgwTG{r>RM zh5?=(jLyp0+#}k2(B}|)F+4DoZFdQ1&!dqaSIjqy?5p&3_F9;6%_4>S5Xrh?_f7$g zwzl@kc>8+`KK`9K@{92s!}=(gQb!B3u&WB7dh|Ian1F=xpEBQ`o*tGbPo~U^H=sph zY&kL=URV{#Y&FXh2_qd1@fRMS5-Apb%EA&LZI%W|o#l%;G$u(0p2H6vMs_LiE7&)Kq-ORyD zJ-quNFE1kLz5YNqDd}RVHAyz9tx_+4a}dgDHH|vsw&oH6G1myN2A|AnM11e#CatJ2 z>@A-{Q7p%?vcS9&6ZqJ~pghsM{5eLvCMwgLTUM6i|GKbW00gw6!n(eMGf=M(o#>hx z{Keq<T&Y!@FR z!S+VYPVj-dHk!|Q+^%`)F*XJq^$>q9>GGH%7e~ z#+y>_B-DtgiMJ;*$6c?D-pgJflc>EbgVD5Bmt3sq8FS$|n)%H3tL^$&Q3YWoFV`H~z% z_0sT%x>l_H^krScReUo~nMLt4^$Q{pQgN}d_E4V#Gv)#)TUpHS;(yS_oByf!ZwM{a zGs^$2g_^khcMJ8H>zyUy!e7Pzjcfnc#s3}lX<&MeSvi~?`~TW|&!DKbtzDQTK}0|V zm8?h>P_i^p(pCuqk|c{D86-71NHPH`Ad(RzNzOTmN(PBdY?>UJ(16fn?(Dsf+s1R= zect+hd{wvV`omg9cdxbPoMVnT!t;zVO~p;-HfuMlMtvC-L>fJCLg+c?!4UK4=n{`x-Jd++km{v>;aSLKx510o_K zFMv4#+F~93QflWO{0$)2g8G&Z%X5q%K=*9K-QlF+jSt5JV#6(bwudt)n3%#1s<+|V zKHrm%z(#F;oaCK#usrE2aW%Xr*N!M(R8nm5;1p=`sc;E{oen-3cgl8C1IH=po%eX>Gg4|YBQcvC zTU2McRC0~J|7~&=X^VyHvkWy6?5cq;UkcP6ZWFnae~OBVI&++7RR079)bY|U4)xde zo);dlJhPSpm%mf-T&vfAj~~fu{24!rK!4(Wup=zFgOJs)^11{v=3ha;QS6N(*3ml4SAMAWGL6R@4{%*X~-v;%c)-VUKb;-hAbJN#fTv(eRjf(x1tb+qIi?yz75*-Sh zzAA1I#d*GOm!T3zFhAF+rvBjR%#7eQgDNG$nxmtYb->#Aa4*vIt7Yswp!2j%#k77RrG9!> z$ryQZHW9rBvLt>th-$tSDa0$TfaCucSK44dyPc8KxC&28OY1!ZE$@C@e#c;P(>e~y0-zi`JAdJ!&b6@c)(XCxgBam!GOA*ST=tvsLusNs9Q z6wt^LP|e{kU#EhuC8wm}-mtfS3CG^DfNDcupR7+}e7w4?6d+pxb~#^(%_0c4+E4Xo ziN;GZWcLi|WVsTYAjOxjz06C1Z;g`z&^>9f_urfBFJj+Mi;C%#k`h?x=RBer)(&`t z7f=sIKg<|0;jqdVjBMQO$TJWW6*&70#9NE~%GyPdmTn5Z$VJ^qK#stBc8;GDZp%+Yt`5#Nh zb|pIa==1G!==n}@+{4EWvM1*l6T8*JhI3scT8KfbHpP|n?$IXixpQ5Jwt+_gdrJEC zYtT@k6Zu&t$t$C7fJ8pIvXXYjP~M#~6HtDcIXLdUdmbhkCUta>-J2S1PbSyT4b=ES zIZYei^Xd2Oub5b%7Kt|_i0SC!s`s*N@UDFKo{M3}(2i%CAByA?djX2Op03ya_NZ^Z zFdpfWL9%u?b7cT@txmS3t<8U@HaC~7>nfYf@~Z9kWL$vudcLrrfjNZB?EqabD%(;2 zC)2zhT3a(V`Hxidxe{wME@0YTX#nWwGG~84f6AMvc&>3{y`{A?!b~xo_65Md0dxF< z&q|HM?48pp&53GF!MORfe_Z=X=liM24FPW?SG)adqk8fY=F->t8zo(|pA2CQPMcUr za?U=-bK56gMAyRX@$cMyMVeIn0jT^)5v$;$ZEv+zILYi;<*0LWFy037bz+-t`A@X~ z*^m!t2o+wtT)g@f&k%^!f})}?y{eMY)h67eZt}Y&C4yg?lVr*p@*E1vo7>R!7Ur z&z!l0qMc)U)rMzCxdQ?ih>h7tg0_QltAo_I$kz9wm-=0oX85GSL+{Fg)8LH$`1okD zM)P)s&mmi$zOp~4rnF2qV$uzlwRr$A5OHh2V@3 zg1WME4c#sk?ml3wlKS|gR`t{;uijBGrxxq5AC zyJ>lZb=SojNoY0E=*f-n#u$%DNdO`i{17Cfh)GBN86Z$?ka#pG0r0Yim~K%*j-mjx z9BOL<`J^8R;*-bQK0D}oCI$V2OCSLkUeuMNtJ;)nI||&zLOpa$#xXg_JH0`#!S|m* zyg*$U=5V_?Q#U-#Q#C!!s;v~71cirt+pil}`~%)#%H_0tzr0Zghk&?~ncOh2=bpA;yFt%ot9f1K_&!hOU`l){9IXUM6NKnwWhsXWCcFPk z0uf131m2EdGe)m^57n6IMYRAbdil~asgz$Rok+VFCO8NBziZQ?v@Wc^V5l)|4o%11Y7M{tEAiFf8$~f=6=CnRVt*@*S zr6~2%=&M@ycs11DX)_(fMUurO^-GL~sa$?s1u;FqvUlCEgRA$M)`4M%m%mQ^-9)Ja zhx}Hq&f9JWJxB-y$*Fj|k z!n4cl;bJt?Ko2#@UT?@vyIOT6mKm_{{)bF;#nx+O`-$ zke&Xi{O8N>&Gg?H!DgP#MMmD&uW=*s_ct)H)IDUV838~Z*b3dIp?}3ok)?$jI`8#B z^=y?5x3MUd*529dWuG92|gi`e7_VPhI`f z8Q+MAK8M3SGMxvyDQYZ4^b#KGyu=`<<;nM}-ow9aJ58XDXR86x9qW8t&`SQm(k5Od z-IK@OmY^SH8<)Y_xU595%uA2o(T`N8PlyLy;UaDnE)15w9Gm2DU1gN?k`qo2XQ)Xr zFk)f?@s-nIVPSx&@$%IxBq$MnPYB|sI=wV-{#gOKe^K;cr@PB&%e+rgQjEQy@7LQ*R@du5$`~0H+fHgiwS>|fLG7H*K66m znG;Kzte;z{H5RHDxNFa&p6z9D{xr#@GoVPE9 zQzG7-&x3O??231&pED`l6-X#){hNdHGbWKd2V~6J?i($ZBmwXM4c=H|_9@0}_2h5h zN)MprfBxX7azJ2|gO|Uk_zC6k)7@<@z#IzOdk)Sg?hDwMAPHJNXP$FXeOh4B8zWP$Kok~pdkjm1=O-Nfo7ngBE{Z_;2pkWI9h+!qQla{_Yf*T^u3rk?#1D0 z64e0ipFOzE#SE3NpM;nRYpiY*{^n}o)`F150OSVbdACR0c8Ib{mn{1&+^}h{r6$3mGdHT~F_;ZFbH35_)SJTeb zd;{21>l>lef6}t@V5aC*No(0BDQ7f;;pq*ox}a zAHWTx=+so9VSS9|f_ zg7)*J7XVE<-$#F#3;Wg__x|L>(Vov#_TR@hgrdvbCWGb+EP9AQ7pLHchP)j_K)9M= zUZ$(D83WOWYi{%%)8q|~$0H;01p6ly42VZSF!2bNlS8rscxU@u$6|qq3l&m`Qh<+d z7Hn>2Z}|3&5M-XF#ws_t((ZVw1YSv&x)x>|<-7h{FanrUKy&?7#Ny{rfGn_a8(ZM! z2L;_U^sp3eyK{Gay|)|mnD?0&5#R5iLjCyhIw@PMZSdPS8elCyjgBS)h3uYO-MFZd z6ecvhl}Aps*2{_mio`rIKBkDS)|O-u}380hH0v1ZrSF}9|;TgE-3ZW(kp?9+=mvdcBaZSzNQ=f{;lygX9v z3OMM!wJDtyii1bY?Dz9OK?6U(@zS zcd%X!^O8I)(hZ~hVI@z0zAxsFhNs==P=;=ka-E4n8XrDlZV zkI~S^oamhOoS_-i0yK+nhik;X6-TF+0WwuBN7@u!FM7YIaX*FgFCE9dedwpuzu; znqfdU$)~I>xGY*fK0Sinsl;XUZ0IG4TXXN{$>_?K5Fc2C1yz>D@9vY+YsvWd2a_|| zOl+yXyQb~pQcC8=&CLy#C|zUs)V76%fUteyTmShi57h3bFx5Ps)fNdW=yb5np+1-W z!OrHVyY^}Wh)vtt*VM=NTDE85&bKd}m}ne-t@F$XTY3Fh1?;WYcdCZWO}Ue3wO;#h zZ%bmDvXiYr8FH*&yEBDnJ5nQmyuO*lb@gh;>NgkB_`UJz6n)6=@q^Jfiyyz^@Ol}I*`)ewBtBAshcF2kg9!7*{>OqONS>|3X9u^^j${Wi#D zZ6#>dUGmDRANO3Af*j?WhaYw^WK&n!_>&*1pZ}$^FjvW?1jKH7ci*{E{!5Wz{@9Qz z?Sv2i^2M;q>zknGOyRJtby&AhHH#5Yxwbk4rr8f= zY8$%e_E2L4v|uRkTN0n%2T(D$doTvt7YgcgyaMzOrOCYA)weJCzPG%|D&ti(J}pRt z@~N{4-Plm!(_AIM@$Q!z%7S3$cZk9IV~d{WF|Q?gQfiRaFp`_UbA+zUuDf~Mch zXg#~$kNCj|8qmIIM!JL8#}jq<9=lQXqse)N8Su04ev_(cy>ct$;EKmlP_#|d{+zEO zKH*i*Gj4RNT|$kUB6c-hgx^_N(?+UX;*PckJ-K*n4=(B(#+Fe7sdAZU9lefl5le`5 znKj#n&&j`($^~^riPk{ljFm70w8jH1HYe3JfK%(wP)#9oR@SNZ-si236S;QnesuTy z9DwS~HK;CR46Y1-(VsGKl5>iZ+;Mmwt|LiMlNRJSW7hfxsuZTdzU9(_=OjwVb$9m^uIQa zzc3|=6`_3RPH(7)erlq`bX1a2a{WR5{P~I0>b$x=uCHIdEG+b-KB%1|9d|e^EyZ2? zc$aT5O54y5f4Hifl&|I^k6yWFywkWJ?EFQ3w}QMnk9QAEO~EN7oFE>q#z7$=$ooEZfMfXCHF2yyobb$Y zerDI3PoF-Ot*t~OMcNmgIaUq|mwH*N;LNHCT(5^YZNe^r-pQ}udZ3GJV-2d!VO2M~ zhQ*c1-O{i$^UCNO&--1qbhBfX55hjjr#GsR{u=$CYC{>QjnTja6)d%}P=QxRP*cYh z$|$4`S_7#9w2uEf9-|LSR-8gEnRPDvB4E#UZ^1p8_C{uO+vCPRa|RHOIGQWYW#`w) zRCsEYhG`pbd?dAbR{e1H0gz@vK{=}4Z`{1RgYaxUP0ax=j=a~`so_6#lt(qGflZ`N zwQQbeA9ZB$yH48z=ds?zd&Zx}*`IG5CT&ev18j+&nmD`uSiV6^**yh$1qCji6>%~K zF-$Wbe>N4-g=j4sQtM zjzA|n)&>Ybx2e`Q-kF;FDNlaWFoPF>hVeG_z*4R_{$hB!mr!E#DxL8+m8Y9S)tsF# z05Ro2>bwKfm6aPx-`z3Aj3DJl?3NbE_MMzpPozRpV9Z20Eqe zR(-4)vmnAFs97<7bMYYs2?dI<=OI7!iNQ6Tra*06!M=~55$4Hl%8wZgfq~pqkB5W( zsq}ZuZN}g7U&gu(o}QndNC7OQ=TsaHu6;o+$EFOHF2)5y=1zq5PSOqe;H7+K(~HmY z0TnfAgo}gyDf0sOL4e)u7z(T@@dwI`ZV1`5@KhN*Ye*|{7yGSn!2N3Qv8M{5jf-bk z@7Sjs0@iK?m5+mi{V9br_<>CMMMGNuC0Dx)@& z#C|J399+J&U*85-JDSNgDP#GEJ(utp43af;GQw0E4zM<>G>(7rIdG>>vyb?(?nF>0 zEn^~sAb(4Fh2konN{FnQT8|7rUAq5?n=>0@7KR*qkiQm&7c5Mp5l;Gv!$XD>OiW~Z z+efV55MwlJg8`iajTe~H#`5$ItT_d<#EbykKWu5J2NJgz71t$J0c3^97x%p+)-n?l zq=CQQRH_3Jmx4nQLDp|%%wQ5fNyh~;O&o@r@6(9z@9x53o`mvjSC+fEFL0I$Sw zaLRZOYjT(=A~B0!nWJ?2*K+zd03(8ItkmRDBRd$Qmv!4KtTBT3{~s4W0JHe03x10% ze|wh9e$w~AAPHxS_&Kp&6i*vSt^+Li&8Z|XMpQ1R0M;16TmOe$(s&VN=)Gkl9~goJ zEeuiFtO=JLgu#3lldMZ%&l>ahBk=RliP#$tu^u>5Dh)zSph&Yi01yGM#O|#v;F(gf zf3zG}5>j50de61BJS_Y5D=XKvYejwFczFQKjuhC0+h~3tz2J3SPk!`nsVekw0z{FE zE&U}R=)sqdNZjI-|2VPL{G=N&%QE)%Iy2V8pXxDF;KSD>9Fk~wj{dlG2@)6RyA?nD z;+e(XSqw{Tan4<20aovxa{G`-BdtB%Foj1?z^X|(r0GLo;=0(ofrElRy$s;p$G#Re zT=PX0mTNvJ8Ec_zIzPy%w73`-H_nS|Urb_oV&N%>MZxU&>766Q-gOS7z!r1`#*Lm` zfo!BCU3TR#MRd&_Ju#fmSgW7PWD5Die1cA&WX zPE89>; zi8~^2hg`sObKXIj&?#tXYbUHfn*&uL7Du7pcL6!}P2b{ut-&XUyL`TyN=n+Dz;1dG z9$ucrAnG9P>%AR>@FAg%8Q)nqee_wmIG{WwMV(tKPfb_1GOy-g?9tKEEj?r~-JAtz z?hOoC;Fr1#_Lw$DCDz@xoDbKA<=yy{nfZN#+S>VG=e8bvIk2(ILvYZ`F2E7_SUWC zJ>WOUSGztQ@rX9$SJQ1)wF1R8T+e_i1rO>TmMVNatMTyRQ>8y@O{Qs9h6<|t*y^Q15c<{ymjUQu9 zzJ1%T7`Cg`QWX~B$~*e<89ns@558)9Bs;1fa$8kuQ*r;$5BCs||0wBjRFWh$!AYJw zegiUlKwBK6flmiCx2fDBb+hLXYW# zGOtjVc$Zm0x{|o@GPc(Y!ZrmR>uqbb$8QYK+2-Bf>kf%ZE&GJ&w!12=(rJ8uWC0gz z@m*YgpY^fZ-L$l{(`00tIy#wTn)_&EDkV$sz$l1L=}`&C6|os7NLW`HdhEJrYHN@B zm3yK^M>9ADo_wyP<$v`0oln&*qOQkt)_Y? zxQ(qqBFe}Zx)s|@P(a0=sJ#Y9(B&ni%D;R!+3*c09Tpx+_rf;~$r|mM`VM?adFU;_d0#TWksk+x`!r$kTy5j3#%}N85zS9UYPT z*dYI!K2W6SJQY>y1tX8NGJB^3C%K)NHH46hd1q{vK5v+f$MLYABy_^|CrO4K-}rxGnY` zbfwA}I5pP&Et9mO1@&R{66v59=1?+BqaiMS492)<6ZTkG?aA5~0v@kTiCJIjA#<~Y zd*8GbucMa+H0Bc^T40Vd%O;Td)pr25IL2{PW!Mk><++XR=+Ra}w?>A34hSW1f{0ld@9=jHic@^Rf)agq=i*a#>{c= zMVbQKwLF6dML#wlMDA{`X(=cu)P9E@&Mxa}4?&cUQO#1>U{`?PR1ccT3xV`QUyv%U z_7~u{!Ydaj6*6l}qrGuOvs-+#zSMhSuhV_oVq9Pa;k}?p&gHZwS-U$o3St}0a^!!g z9sUkB-`^`9#mK6fx-F z+qCxne51DtVHXWO<(xoOS3$FluiM1t#ilTE;It>e-5;w(;KNg<&gJfNMGCJNNIXuj z&42}#TUt)9>_;ZnRk{wbwFuW2pmvKiB@bnRg+z5B2usv}E$Mn0Sbe2V}9Fil>CDCBU!^@q!2rMGrSQ5I2Z)XBklISk$EiDCk?JovoEXMElx!b z65 zV|PX%WVRN1433XlR7*UzVg(3m>W{YgN{t({M{3-&YuC0ugfQM$am^!h>m08^k9kKQ zt*}cKLkXZW&Ga%gzQ;1Q$5v2lCp^Pxi`6=bg5%f1ws7-_56_-C>=zdmwK2{w!%5=b z@=F7sb)iZAZsco<{M*rnnIcEKVZhy9K085~)no;oiG?^lR!Xv|w_iGIxM^3n4}a*{ zUo>nfZnh!saky2ksb!c4`pgfpd;>L6?2LjKC8h-3d*wZlE9CbLS4a0{8b;q73*UfZ z2^|`9pGB}+yj=^DRBYEf?LsPf=4oJ3_Z|m0!R&&(R z-27L@2;Hm)a|V$1*Gg8#T;^AV#f6gcACxKpglC!mCwFy?5ajl#ot8`KU0=_2Guw%} z)Sfb>UCa@*1v6qP-W1?QR+K$nzNZwu045bs&%T$!Iy6CBMlLSy68AnWy5(3u*rdY} z+;dg^PMfru`D!22(T*=}l}E1BH7Zxb>+9llpY!xfwZK^qBBPeuDh8Wg)88sLT#52I zCn}vjvVBa*Hmscj@#oyBx952D%0|i{rh8X`Z$1KvZEtHU_2`UlYi*6%oqY;s+<1b{ zDRSd+Qho^(t)M7Bj3W$zD3Xv!lse8{P(JHMO8U}ff9?-Ufjl82uUL7Tr#Ltlxo*qJ z+|ObGA;g=p-5qZ_=ZF`9FU@`9h73SUX*^knp;z+^i}SV_rv!3OBtnwjq+T2F>ZungxvKX79~I0j~2 z%ZZ+GNGx@eyZ=MCeF!g)Yw>>i{Z2j+Pa=h^bYcpWuzaZ4@4q~G-xc*_QLp5U)V}){ zW0tmdh{D90kwxjrEY?va#}YS-ME357eZXHMG^e{#tg6dc5CaiB!W4Ndl*JoGVWEU^_wj%2!T)f@ym0EYSh$+jn2Th0@l8>9C`ehx2@V5qU( z^S2nvZ+D{T$4fK0Ef(pZ8Zo!PiD2l{rtO>mF=wbhTS?m7#?% z60mHV--m-a9PTEk;lYPf(}?)2z4}4ir3(L%0P@T7sTW+stNCW6Ek633BCTe4hPS)O z&C6RalKdVwKdIgST#EzN?_++@ok~j&<4=MTnqu+ntY1ACcd1^ck(b3E8QcZt6Ai?z zxXK{a>hZ^Oj>p53P!yL>jsD@|R?PyVE(X3GBy}E)9M?XsppWw(*Tb)#AeIVc_?GrV z^D1rnN4s7~+{X#uYgoqU8#9Y%;Q|9_K~Pd9I*9et$R*y#E-|0E!#si( z^QIH)tKaT#Rtdv9icKJ^yCMf;*^TZTGfVxNVb2&HD?iP0w7+ZHj2pS1x&`tA@sWfl zY}bF2EWFzd8V&okH*0?&283iRn&x-IRV{6xz$%bT>vp?4j@4fBl-!F-k5pXPO!BI zHRh3AQH}xRko`?w`9#oP{L%JSz4^3_ECZHNJvmtS`Kw3VRw= zdRP@OQm5_>UHD81ID(4@L3e8#Qwu?xP+ z=3^@TRpgfJe(`Y+yDhU7Uiaa1sgld-BoC2{Q8sw@ic<&0ACtNvSG`=wK_AGeeCRRU zBmlwa&eXEZmbYI4CGRVTe~uepn>_E$Gr8=gTi~z0P{mnlkHY5$6u_}?^R7|?~l`q6Ok|i zlJqRLnE@fZTsi4ok?7hvnA?ZiuY!(wjAShx9ogXKlgJ_A02`q$+$<2+YycaFj^mV1 zdUbe707MGD4phP^vglH(CsARBkFu^RnlP?=eA9a9XqX-uRt@0l-otpDIWOa1=I7sx zt!$jGLea*HxtA{djMgn1om!K} zvziN4Ml0%GuQoZ;SX zvD~cKzp2Mz0QKRaMWsuyeGPr*e%?!-bI#-Z(1Yr~<}My=OCx$*bOKa4+nZ&%C;1f_)r6(p{Hb|D%} zbe*Z1Ujl*IZ(-V!eGD~Z0ULuujmSHdxAPh|XNF3p!^VB{k9~fKs}4*vMFA_69?7)1 zJHCG3|9`g>;5duj70qsUJ`N4oLQp>u)4gXV3x6KK5*6FOSh84Gbj66t?wZ(&6GpP~ zR#gu^#1EvsXg7Me_Ce^_h(bt@>;v9woThkM&T*fD}V`Ob6@5C4ni_h^I_eE#T z+csFwnTA>9jpoP97cy?DAj?_9EL>TYqpW`fIMN@@%|vi3(&-Dhm+#bhQcYuXl-7FW*wHy}-9z@j4lB8Ci3G|N}ty{4AqCdhC_}U7O4{7$1 zAo%hyQV=Wu^UK};`-URh7YG@~#hZ=zLl-}!}kFk;+qKGCj zM7J$h2$^J&xOc7^LMxwUKH2sxrV^>&tQZkXXbT@9EmJaLBM##w|Cp}WGNW*Nd}YwKiF#P$PAu1~dCQx_($t%8hs84(n(1jdwWqFf z+Ev^)xIAkUp^_7kdT{^cQ7H%>tEtdpgP{Ih=@1M0zXK;uO_%EP{=1u05-NqD(v7r$ zP3U?>xV>G)bh*&p>2zTdw*X6_7}9?<5F0E(64e&1{+^+DZphq;zIeD2>9YhMj>8=I zqQw&c=#)K1#@H_b-^t9#N$SJpdMrxO5B%pRCL zpJ|qIO$9-~_Qqh0?mt@b3|h&Vo5T#E`w9_rYdO3c7U&%hmkUSCw^{ZAlLq03e2*;G z(^L8+!=+F)S5$3*yR%aJmgLE13g10oz|k#NC4Fxb_Y6nHtPDAe|$FPOhRdb zu%GsgHthWTe;6(pxGliXj4GgH#x@-xV+_f1jc_iiVO*rj>h15O{_;*`gMK14_@zehU1QZ*X literal 218733 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%0AC1hklcnlKv+^3XXm`-KIwoVmr=b=@uP8^x75v! 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 J%Q~loCIFvLcRK(8 From ab5f116ffa1ed5998d7df71d94e5e364e8acea0d Mon Sep 17 00:00:00 2001 From: Gvidow Date: Fri, 20 Oct 2023 01:24:47 +0300 Subject: [PATCH 072/266] TP-aad update: rename users relation to auth --- db/migrations/indexes.sql | 4 ++-- db/migrations/relations.sql | 6 +++--- db/normalized/er-diagram.png | Bin 268784 -> 267795 bytes 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/db/migrations/indexes.sql b/db/migrations/indexes.sql index df1c8e6..253612f 100644 --- a/db/migrations/indexes.sql +++ b/db/migrations/indexes.sql @@ -1,5 +1,5 @@ -CREATE INDEX IF NOT EXISTS users_profile_index -ON users USING btree (profile_id); +CREATE INDEX IF NOT EXISTS auth_profile_index +ON auth USING btree (profile_id); CREATE INDEX IF NOT EXISTS pin_author_index ON pin USING btree (author); diff --git a/db/migrations/relations.sql b/db/migrations/relations.sql index f2410a4..824e250 100644 --- a/db/migrations/relations.sql +++ b/db/migrations/relations.sql @@ -16,13 +16,13 @@ CREATE TABLE IF NOT EXISTS profile ( ALTER TABLE profile ALTER COLUMN avatar SET DEFAULT 'avatar.jpg'; -CREATE TABLE IF NOT EXISTS users ( +CREATE TABLE IF NOT EXISTS auth ( id serial PRIMARY KEY, username TEXT NOT NULL, PASSWORD TEXT NOT NULL, profile_id int NOT NULL, - CONSTRAINT users_username_uniq UNIQUE (username), - CONSTRAINT users_profile_id_uniq UNIQUE (profile_id), + CONSTRAINT auth_username_uniq UNIQUE (username), + CONSTRAINT auth_profile_id_uniq UNIQUE (profile_id), FOREIGN KEY (profile_id) REFERENCES profile (id) ON DELETE CASCADE ); diff --git a/db/normalized/er-diagram.png b/db/normalized/er-diagram.png index 7de47b9008683de8b6018a29fe77033367059427..4a426536604896c507ce92415b954e5dd122d659 100644 GIT binary patch literal 267795 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 literal 268784 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 From 0bc9febabaf2f76f4bebc792aae581d8929ffd40 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 20 Oct 2023 23:21:22 +0300 Subject: [PATCH 073/266] TP-ee7 update: change register router and start server --- cmd/app/main.go | 7 +++++-- go.mod | 7 ++++++- go.sum | 10 ++++++++++ internal/api/server/router/router.go | 2 +- internal/api/server/server.go | 24 +++++++----------------- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index bcdb514..ea966b1 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" @@ -52,8 +53,10 @@ func main() { return } server := server.New(log, cfgServ) - server.InitRouter(service) - if err := server.Run(); err != nil { + router := router.New() + router.RegisterRoute(service) + + if err := server.Run(router.Mux); err != nil { log.Error(err.Error()) return } diff --git a/go.mod b/go.mod index 1540372..c3bb7c7 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.19 require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 + github.com/jackc/pgx/v5 v5.4.3 github.com/proullon/ramsql v0.0.1 github.com/rs/cors v1.10.1 + github.com/stretchr/testify v1.8.4 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.2 go.uber.org/config v1.4.0 @@ -21,15 +23,18 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect golang.org/x/net v0.15.0 // indirect + golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect diff --git a/go.sum b/go.sum index 4f98d27..708bafa 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,14 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -59,6 +67,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -96,6 +105,7 @@ golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 6191ffc..d8908d4 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -19,7 +19,7 @@ func New() Router { return Router{chi.NewMux()} } -func (r Router) InitRoute(serv *service.Service) { +func (r Router) RegisterRoute(serv *service.Service) { c := cors.New(cors.Options{ AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, diff --git a/internal/api/server/server.go b/internal/api/server/server.go index ad8bd44..f32f578 100644 --- a/internal/api/server/server.go +++ b/internal/api/server/server.go @@ -4,8 +4,6 @@ import ( "errors" "net/http" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -13,9 +11,8 @@ var ErrNotInitRouter = errors.New("there is no routing") type Server struct { http.Server - router router.Router - log *logger.Logger - cfg *Config + log *logger.Logger + cfg *Config } func New(log *logger.Logger, cfg *Config) *Server { @@ -23,24 +20,17 @@ func New(log *logger.Logger, cfg *Config) *Server { Server: http.Server{ Addr: cfg.Host + ":" + cfg.Port, }, - router: router.New(), - log: log, - cfg: cfg, + log: log, + cfg: cfg, } } -func (s *Server) Run() error { - if !s.router.IsInit() { - return ErrNotInitRouter - } - s.Handler = s.router.Mux +func (s *Server) Run(handler http.Handler) error { + s.Handler = handler + s.log.Info("server start") if s.cfg.https { return s.ListenAndServeTLS(s.cfg.CertFile, s.cfg.KeyFile) } return s.ListenAndServe() } - -func (s *Server) InitRouter(serv *service.Service) { - s.router.InitRoute(serv) -} From a5308dfd1399329ae97d61a0d1da8f4e94ff3f31 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 21 Oct 2023 15:16:38 +0300 Subject: [PATCH 074/266] TP-ee7 update: divided into 3 layers: delivery, usecase, repository --- cmd/app/main.go | 34 +------- internal/api/server/router/router.go | 21 +++-- internal/app/app.go | 40 ++++++++++ .../pkg/{service => delivery/http/v1}/auth.go | 80 +++++++++---------- .../http/v1}/auth_test.go | 6 +- internal/pkg/delivery/http/v1/handler.go | 24 ++++++ .../pkg/{service => delivery/http/v1}/pin.go | 14 ++-- .../{service => delivery/http/v1}/pin_test.go | 4 +- .../{service => delivery/http/v1}/response.go | 2 +- .../http/v1}/validation.go | 2 +- .../http/v1}/validation_test.go | 2 +- internal/pkg/middleware/auth/auth.go | 44 ++++++++++ internal/pkg/service/service.go | 24 ------ .../{usecases => pkg/usecase}/pin/usecase.go | 16 ++-- .../usecase}/pin/usecase_test.go | 0 .../usecase}/session/manager.go | 18 +++-- .../usecase}/session/manager_test.go | 0 .../usecase}/user/credentials.go | 0 .../{usecases => pkg/usecase}/user/usecase.go | 18 +++-- .../usecase}/user/usecase_test.go | 0 20 files changed, 208 insertions(+), 141 deletions(-) create mode 100644 internal/app/app.go rename internal/pkg/{service => delivery/http/v1}/auth.go (73%) rename internal/pkg/{service => delivery/http/v1}/auth_test.go (98%) create mode 100644 internal/pkg/delivery/http/v1/handler.go rename internal/pkg/{service => delivery/http/v1}/pin.go (78%) rename internal/pkg/{service => delivery/http/v1}/pin_test.go (98%) rename internal/pkg/{service => delivery/http/v1}/response.go (98%) rename internal/pkg/{service => delivery/http/v1}/validation.go (99%) rename internal/pkg/{service => delivery/http/v1}/validation_test.go (99%) create mode 100644 internal/pkg/middleware/auth/auth.go delete mode 100644 internal/pkg/service/service.go rename internal/{usecases => pkg/usecase}/pin/usecase.go (54%) rename internal/{usecases => pkg/usecase}/pin/usecase_test.go (100%) rename internal/{usecases => pkg/usecase}/session/manager.go (65%) rename internal/{usecases => pkg/usecase}/session/manager_test.go (100%) rename internal/{usecases => pkg/usecase}/user/credentials.go (100%) rename internal/{usecases => pkg/usecase}/user/usecase.go (63%) rename internal/{usecases => pkg/usecase}/user/usecase_test.go (100%) diff --git a/cmd/app/main.go b/cmd/app/main.go index ea966b1..6789fce 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -3,13 +3,7 @@ package main import ( "fmt" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -35,29 +29,5 @@ func main() { } defer log.Sync() - db, err := ramrepo.OpenDB("RamRepository") - if err != nil { - log.Error(err.Error()) - return - } - defer db.Close() - - sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) - - service := service.New(log, sm, userCase, pinCase) - cfgServ, err := server.NewConfig(configFile) - if err != nil { - log.Error(err.Error()) - return - } - server := server.New(log, cfgServ) - router := router.New() - router.RegisterRoute(service) - - if err := server.Run(router.Mux); err != nil { - log.Error(err.Error()) - return - } + app.Run(log, configFile) } diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index d8908d4..b1c25df 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -8,7 +8,8 @@ import ( httpSwagger "github.com/swaggo/http-swagger" _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/service" + deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" ) type Router struct { @@ -19,7 +20,7 @@ func New() Router { return Router{chi.NewMux()} } -func (r Router) RegisterRoute(serv *service.Service) { +func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP) { c := cors.New(cors.Options{ AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, @@ -27,24 +28,20 @@ func (r Router) RegisterRoute(serv *service.Service) { AllowedHeaders: []string{"content-type"}, }) - r.Mux.Use(c.Handler) + r.Mux.Use(auth.NewAuthMiddleware(nil, nil).Middleware, c.Handler) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) r.Route("/auth", func(r chi.Router) { - r.Get("/login", serv.CheckLogin) - r.Post("/login", serv.Login) - r.Post("/signup", serv.Signup) - r.Delete("/logout", serv.Logout) + r.Get("/login", handler.CheckLogin) + r.Post("/login", handler.Login) + r.Post("/signup", handler.Signup) + r.Delete("/logout", handler.Logout) }) r.Route("/pin", func(r chi.Router) { - r.Get("/", serv.GetPins) + r.Get("/", handler.GetPins) }) }) } - -func (r Router) IsInit() bool { - return r.Mux != nil -} diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..6851df1 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,40 @@ +package app + +import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" + deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func Run(log *log.Logger, configFile string) { + db, err := ramrepo.OpenDB("RamRepository") + if err != nil { + log.Error(err.Error()) + return + } + defer db.Close() + + sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + userCase := user.New(log, ramrepo.NewRamUserRepo(db)) + pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) + + service := deliveryHTTP.New(log, sm, userCase, pinCase) + cfgServ, err := server.NewConfig(configFile) + if err != nil { + log.Error(err.Error()) + return + } + server := server.New(log, cfgServ) + router := router.New() + router.RegisterRoute(service) + + if err := server.Run(router.Mux); err != nil { + log.Error(err.Error()) + return + } +} diff --git a/internal/pkg/service/auth.go b/internal/pkg/delivery/http/v1/auth.go similarity index 73% rename from internal/pkg/service/auth.go rename to internal/pkg/delivery/http/v1/auth.go index 3188b14..94234dd 100644 --- a/internal/pkg/service/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "encoding/json" @@ -6,7 +6,7 @@ import ( "time" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" + usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -21,38 +21,38 @@ import ( // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/login [get] -func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) +func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) cookie, err := r.Cookie("session_key") if err != nil { - s.log.Info("no cookie", log.F{"error", err.Error()}) + h.log.Info("no cookie", log.F{"error", err.Error()}) err = responseError(w, "no_auth", "the user is not logged in") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } - userID, err := s.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) + userID, err := h.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) if err != nil { err = responseError(w, "no_auth", "no user session found") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } - username, avatar, err := s.userCase.FindOutUsernameAndAvatar(r.Context(), userID) + username, avatar, err := h.userCase.FindOutUsernameAndAvatar(r.Context(), userID) if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) err = responseError(w, "no_auth", "no user was found for this session") } else { err = responseOk(w, "user found", map[string]string{"username": username, "avatar": avatar}) } if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } } @@ -70,47 +70,47 @@ func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} JsonErrResponse // @Header 200 {string} session_key "Auth cookie with new valid session id" // @Router /api/v1/auth/login [post] -func (s *Service) Login(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) +func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) params := usecase.NewCredentials() err := json.NewDecoder(r.Body).Decode(&params) defer r.Body.Close() if err != nil { - s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) + h.log.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username and password are expected to be received in JSON format") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } if !isValidPassword(params.Password) || !isValidUsername(params.Username) { - s.log.Info("invalid credentials") + h.log.Info("invalid credentials") err = responseError(w, "invalid_credentials", "invalid user credentials") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } - user, err := s.userCase.Authentication(r.Context(), params) + user, err := h.userCase.Authentication(r.Context(), params) if err != nil { - s.log.Warn(err.Error()) + h.log.Warn(err.Error()) err = responseError(w, "bad_credentials", "incorrect user credentials") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } - session, err := s.sm.CreateNewSessionForUser(r.Context(), user.ID) + session, err := h.sm.CreateNewSessionForUser(r.Context(), user.ID) if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) err = responseError(w, "session", "failed to create a session for the user") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } @@ -128,7 +128,7 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { err = responseOk(w, "a new session has been created for the user", nil) if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } } @@ -146,40 +146,40 @@ func (s *Service) Login(w http.ResponseWriter, r *http.Request) { // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/signup [post] -func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) +func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) user := &user.User{} err := json.NewDecoder(r.Body).Decode(user) defer r.Body.Close() if err != nil { - s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) + h.log.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username, email and password are expected to be received in JSON format") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } if err := IsValidUserForRegistration(user); err != nil { - s.log.Info("invalid user registration data") + h.log.Info("invalid user registration data") err = responseError(w, "invalid_params", err.Error()) if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } - err = s.userCase.Register(r.Context(), user) + err = h.userCase.Register(r.Context(), user) if err != nil { - s.log.Warn(err.Error()) + h.log.Warn(err.Error()) err = responseError(w, "uniq_fields", "there is already an account with this username or password") } else { err = responseOk(w, "the user has been successfully registered", nil) } if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } } @@ -196,16 +196,16 @@ func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} JsonErrResponse // @Header 200 {string} Session-id "Auth cookie with expired session id" // @Router /api/v1/auth/logout [delete] -func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) +func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) cookie, err := r.Cookie("session_key") if err != nil { - s.log.Info("no cookie", log.F{"error", err.Error()}) + h.log.Info("no cookie", log.F{"error", err.Error()}) err = responseError(w, "no_auth", "to log out, you must first log in") if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } return } @@ -213,14 +213,14 @@ func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { cookie.Expires = time.Now().UTC().AddDate(0, -1, 0) http.SetCookie(w, cookie) - err = s.sm.DeleteUserSession(r.Context(), cookie.Value) + err = h.sm.DeleteUserSession(r.Context(), cookie.Value) if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) err = responseError(w, "session", "the user logged out, but his session did not end") } else { err = responseOk(w, "the user has successfully logged out", nil) } if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } } diff --git a/internal/pkg/service/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go similarity index 98% rename from internal/pkg/service/auth_test.go rename to internal/pkg/delivery/http/v1/auth_test.go index 384296e..6f35a78 100644 --- a/internal/pkg/service/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "encoding/json" @@ -11,8 +11,8 @@ import ( "testing" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/stretchr/testify/require" ) diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go new file mode 100644 index 0000000..ab66db9 --- /dev/null +++ b/internal/pkg/delivery/http/v1/handler.go @@ -0,0 +1,24 @@ +package v1 + +import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type HandlerHTTP struct { + log *logger.Logger + userCase user.Usecase + pinCase pin.Usecase + sm session.SessionManager +} + +func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase) *HandlerHTTP { + return &HandlerHTTP{ + log: log, + userCase: user, + pinCase: pin, + sm: sm, + } +} diff --git a/internal/pkg/service/pin.go b/internal/pkg/delivery/http/v1/pin.go similarity index 78% rename from internal/pkg/service/pin.go rename to internal/pkg/delivery/http/v1/pin.go index 41332f2..14561bb 100644 --- a/internal/pkg/service/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "errors" @@ -26,24 +26,24 @@ var ErrBadParams = errors.New("bad params") // @Failure 404 {object} JsonErrResponse // @Failure 500 {object} JsonErrResponse // @Router /api/v1/pin [get] -func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) +func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) count, lastID, err := FetchValidParamForLoadTape(r.URL) if err != nil { - s.log.Info("parse url query params", log.F{"error", err.Error()}) + h.log.Info("parse url query params", log.F{"error", err.Error()}) err = responseError(w, "bad_params", "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)") } else { - s.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) - pins, last := s.pinCase.SelectNewPins(r.Context(), count, lastID) + h.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) + pins, last := h.pinCase.SelectNewPins(r.Context(), count, lastID) err = responseOk(w, "pins received are sorted by id", map[string]any{ "pins": pins, "lastID": last, }) } if err != nil { - s.log.Error(err.Error()) + h.log.Error(err.Error()) } } diff --git a/internal/pkg/service/pin_test.go b/internal/pkg/delivery/http/v1/pin_test.go similarity index 98% rename from internal/pkg/service/pin_test.go rename to internal/pkg/delivery/http/v1/pin_test.go index 2dd7998..058651a 100644 --- a/internal/pkg/service/pin_test.go +++ b/internal/pkg/delivery/http/v1/pin_test.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "encoding/json" @@ -10,7 +10,7 @@ import ( "testing" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - pinCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" + pinCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/stretchr/testify/require" ) diff --git a/internal/pkg/service/response.go b/internal/pkg/delivery/http/v1/response.go similarity index 98% rename from internal/pkg/service/response.go rename to internal/pkg/delivery/http/v1/response.go index df3121d..1f7056e 100644 --- a/internal/pkg/service/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "encoding/json" diff --git a/internal/pkg/service/validation.go b/internal/pkg/delivery/http/v1/validation.go similarity index 99% rename from internal/pkg/service/validation.go rename to internal/pkg/delivery/http/v1/validation.go index 9356faa..ca2ff51 100644 --- a/internal/pkg/service/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "fmt" diff --git a/internal/pkg/service/validation_test.go b/internal/pkg/delivery/http/v1/validation_test.go similarity index 99% rename from internal/pkg/service/validation_test.go rename to internal/pkg/delivery/http/v1/validation_test.go index f2b094c..ae50168 100644 --- a/internal/pkg/service/validation_test.go +++ b/internal/pkg/delivery/http/v1/validation_test.go @@ -1,4 +1,4 @@ -package service +package v1 import ( "net/url" diff --git a/internal/pkg/middleware/auth/auth.go b/internal/pkg/middleware/auth/auth.go new file mode 100644 index 0000000..7d40590 --- /dev/null +++ b/internal/pkg/middleware/auth/auth.go @@ -0,0 +1,44 @@ +package auth + +import ( + "context" + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type authContextValueKey string + +const ( + KeyCurrentUserID authContextValueKey = "userID" + + SessionCookieName string = "session_key" +) + +type authMiddleware struct { + sm session.SessionManager + log *logger.Logger +} + +func NewAuthMiddleware(sm session.SessionManager, log *logger.Logger) authMiddleware { + return authMiddleware{sm, log} +} + +func (am authMiddleware) Middleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie(SessionCookieName) + if err == http.ErrNoCookie { + return + } + if err != nil { + am.log.Sugar().Errorf("auth middleware: %s", err) + } + + userID, err := am.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) + if err != nil { + return + } + h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), KeyCurrentUserID, userID))) + }) +} diff --git a/internal/pkg/service/service.go b/internal/pkg/service/service.go deleted file mode 100644 index 05bb916..0000000 --- a/internal/pkg/service/service.go +++ /dev/null @@ -1,24 +0,0 @@ -package service - -import ( - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -type Service struct { - log *logger.Logger - userCase *user.Usecase - pinCase *pin.Usecase - sm *session.SessionManager -} - -func New(log *logger.Logger, sm *session.SessionManager, user *user.Usecase, pin *pin.Usecase) *Service { - return &Service{ - log: log, - userCase: user, - pinCase: pin, - sm: sm, - } -} diff --git a/internal/usecases/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go similarity index 54% rename from internal/usecases/pin/usecase.go rename to internal/pkg/usecase/pin/usecase.go index 2dbec72..a39459e 100644 --- a/internal/usecases/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -8,19 +8,23 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -type Usecase struct { +type Usecase interface { + SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) +} + +type pinCase struct { log *logger.Logger repo repo.Repository } -func New(log *logger.Logger, repo repo.Repository) *Usecase { - return &Usecase{log, repo} +func New(log *logger.Logger, repo repo.Repository) *pinCase { + return &pinCase{log, repo} } -func (u *Usecase) SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) { - pins, err := u.repo.GetSortedNPinsAfterID(ctx, count, lastID) +func (p *pinCase) SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) { + pins, err := p.repo.GetSortedNPinsAfterID(ctx, count, lastID) if err != nil { - u.log.Error(err.Error()) + p.log.Error(err.Error()) } if len(pins) == 0 { return []entity.Pin{}, lastID diff --git a/internal/usecases/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go similarity index 100% rename from internal/usecases/pin/usecase_test.go rename to internal/pkg/usecase/pin/usecase_test.go diff --git a/internal/usecases/session/manager.go b/internal/pkg/usecase/session/manager.go similarity index 65% rename from internal/usecases/session/manager.go rename to internal/pkg/usecase/session/manager.go index a7a3b8e..af67efc 100644 --- a/internal/usecases/session/manager.go +++ b/internal/pkg/usecase/session/manager.go @@ -18,16 +18,22 @@ const lenSessionKey = 16 var ErrExpiredSession = errors.New("session lifetime expired") -type SessionManager struct { +type SessionManager interface { + CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) + GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) + DeleteUserSession(ctx context.Context, key string) error +} + +type SessManager struct { log *logger.Logger repo repo.Repository } -func New(log *logger.Logger, repo repo.Repository) *SessionManager { - return &SessionManager{log, repo} +func New(log *logger.Logger, repo repo.Repository) *SessManager { + return &SessManager{log, repo} } -func (sm *SessionManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { +func (sm *SessManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { sessionKey, err := crypto.NewRandomString(lenSessionKey) if err != nil { return nil, fmt.Errorf("session key generation for new session: %w", err) @@ -45,7 +51,7 @@ func (sm *SessionManager) CreateNewSessionForUser(ctx context.Context, userID in return session, nil } -func (sm *SessionManager) GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) { +func (sm *SessManager) GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) { session, err := sm.repo.GetSessionByKey(ctx, sessionKey) if err != nil { return 0, fmt.Errorf("getting a session by a manager: %w", err) @@ -56,6 +62,6 @@ func (sm *SessionManager) GetUserIDBySessionKey(ctx context.Context, sessionKey return session.UserID, nil } -func (sm *SessionManager) DeleteUserSession(ctx context.Context, key string) error { +func (sm *SessManager) DeleteUserSession(ctx context.Context, key string) error { return sm.repo.DeleteSessionByKey(ctx, key) } diff --git a/internal/usecases/session/manager_test.go b/internal/pkg/usecase/session/manager_test.go similarity index 100% rename from internal/usecases/session/manager_test.go rename to internal/pkg/usecase/session/manager_test.go diff --git a/internal/usecases/user/credentials.go b/internal/pkg/usecase/user/credentials.go similarity index 100% rename from internal/usecases/user/credentials.go rename to internal/pkg/usecase/user/credentials.go diff --git a/internal/usecases/user/usecase.go b/internal/pkg/usecase/user/usecase.go similarity index 63% rename from internal/usecases/user/usecase.go rename to internal/pkg/usecase/user/usecase.go index b9cd907..9d0848e 100644 --- a/internal/usecases/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -18,16 +18,22 @@ const ( lenPasswordHash = 64 ) -type Usecase struct { +type Usecase interface { + Register(ctx context.Context, user *entity.User) error + Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) + FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) +} + +type userCase struct { log *logger.Logger repo repo.Repository } -func New(log *logger.Logger, repo repo.Repository) *Usecase { - return &Usecase{log, repo} +func New(log *logger.Logger, repo repo.Repository) *userCase { + return &userCase{log, repo} } -func (u *Usecase) Register(ctx context.Context, user *entity.User) error { +func (u *userCase) Register(ctx context.Context, user *entity.User) error { salt, err := crypto.NewRandomString(lenSalt) if err != nil { return fmt.Errorf("generating salt for registration: %w", err) @@ -41,7 +47,7 @@ func (u *Usecase) Register(ctx context.Context, user *entity.User) error { return nil } -func (u *Usecase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { +func (u *userCase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { user, err := u.repo.GetUserByUsername(ctx, credentials.Username) if err != nil { return nil, fmt.Errorf("user authentication: %w", err) @@ -54,6 +60,6 @@ func (u *Usecase) Authentication(ctx context.Context, credentials userCredential return user, nil } -func (u *Usecase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { +func (u *userCase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { return u.repo.GetUsernameAndAvatarByID(ctx, userID) } diff --git a/internal/usecases/user/usecase_test.go b/internal/pkg/usecase/user/usecase_test.go similarity index 100% rename from internal/usecases/user/usecase_test.go rename to internal/pkg/usecase/user/usecase_test.go From bafd19da2189f0f7ccd40ea5eb00de8d6777f0da Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 21 Oct 2023 22:58:34 +0300 Subject: [PATCH 075/266] TP-ee7 add: middleware auth --- internal/api/server/router/router.go | 13 +++++++--- internal/app/app.go | 4 +-- internal/pkg/delivery/http/v1/auth.go | 21 +++------------- internal/pkg/middleware/auth/auth.go | 35 +++++++++++++++------------ 4 files changed, 33 insertions(+), 40 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index b1c25df..06d3a1a 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -10,6 +10,8 @@ import ( _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) type Router struct { @@ -20,7 +22,7 @@ func New() Router { return Router{chi.NewMux()} } -func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP) { +func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.SessionManager, log *logger.Logger) { c := cors.New(cors.Options{ AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, @@ -28,16 +30,19 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP) { AllowedHeaders: []string{"content-type"}, }) - r.Mux.Use(auth.NewAuthMiddleware(nil, nil).Middleware, c.Handler) + r.Mux.Use(c.Handler, auth.NewAuthMiddleware(sm).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) r.Route("/auth", func(r chi.Router) { - r.Get("/login", handler.CheckLogin) r.Post("/login", handler.Login) r.Post("/signup", handler.Signup) - r.Delete("/logout", handler.Logout) + r.Group(func(r chi.Router) { + r.Use(auth.RequireAuth) + r.Get("/login", handler.CheckLogin) + r.Delete("/logout", handler.Logout) + }) }) r.Route("/pin", func(r chi.Router) { diff --git a/internal/app/app.go b/internal/app/app.go index 6851df1..d6e7faa 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -23,7 +23,7 @@ func Run(log *log.Logger, configFile string) { userCase := user.New(log, ramrepo.NewRamUserRepo(db)) pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) - service := deliveryHTTP.New(log, sm, userCase, pinCase) + handler := deliveryHTTP.New(log, sm, userCase, pinCase) cfgServ, err := server.NewConfig(configFile) if err != nil { log.Error(err.Error()) @@ -31,7 +31,7 @@ func Run(log *log.Logger, configFile string) { } server := server.New(log, cfgServ) router := router.New() - router.RegisterRoute(service) + router.RegisterRoute(handler, sm, log) if err := server.Run(router.Mux); err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index 94234dd..cbeccc3 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -6,6 +6,7 @@ import ( "time" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -25,24 +26,7 @@ func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { h.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) - cookie, err := r.Cookie("session_key") - if err != nil { - h.log.Info("no cookie", log.F{"error", err.Error()}) - err = responseError(w, "no_auth", "the user is not logged in") - if err != nil { - h.log.Error(err.Error()) - } - return - } - - userID, err := h.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) - if err != nil { - err = responseError(w, "no_auth", "no user session found") - if err != nil { - h.log.Error(err.Error()) - } - return - } + userID, _ := r.Context().Value(auth.KeyCurrentUserID).(int) username, avatar, err := h.userCase.FindOutUsernameAndAvatar(r.Context(), userID) if err != nil { @@ -211,6 +195,7 @@ func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { } cookie.Expires = time.Now().UTC().AddDate(0, -1, 0) + cookie.Path = "/" http.SetCookie(w, cookie) err = h.sm.DeleteUserSession(r.Context(), cookie.Value) diff --git a/internal/pkg/middleware/auth/auth.go b/internal/pkg/middleware/auth/auth.go index 7d40590..09b50ec 100644 --- a/internal/pkg/middleware/auth/auth.go +++ b/internal/pkg/middleware/auth/auth.go @@ -5,7 +5,6 @@ import ( "net/http" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) type authContextValueKey string @@ -17,28 +16,32 @@ const ( ) type authMiddleware struct { - sm session.SessionManager - log *logger.Logger + sm session.SessionManager } -func NewAuthMiddleware(sm session.SessionManager, log *logger.Logger) authMiddleware { - return authMiddleware{sm, log} +func NewAuthMiddleware(sm session.SessionManager) authMiddleware { + return authMiddleware{sm} } -func (am authMiddleware) Middleware(h http.Handler) http.Handler { +func (am authMiddleware) ContextWithUserID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(SessionCookieName) - if err == http.ErrNoCookie { - return - } - if err != nil { - am.log.Sugar().Errorf("auth middleware: %s", err) + if cookie, err := r.Cookie(SessionCookieName); err == nil { + if userID, err := am.sm.GetUserIDBySessionKey(r.Context(), cookie.Value); err == nil { + r = r.WithContext(context.WithValue(r.Context(), KeyCurrentUserID, userID)) + } } + next.ServeHTTP(w, r) + }) +} - userID, err := am.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) - if err != nil { - return +func RequireAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, ok := r.Context().Value(KeyCurrentUserID).(int) + if ok { + next.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"status": "error", "code": "no_auth", "message": "authentication required"}`)) } - h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), KeyCurrentUserID, userID))) }) } From fe33158c5458dd35e5c948269b4a121671a7c858 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 22 Oct 2023 02:30:41 +0300 Subject: [PATCH 076/266] TP-46f add: repository based on posgtres --- cmd/app/main.go | 6 ++- internal/app/app.go | 26 +++++++++++-- internal/pkg/repository/pin/repo.go | 35 +++++++++++++++++ internal/pkg/repository/user/repo.go | 57 ++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index 6789fce..b28003d 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" @@ -22,6 +23,9 @@ const configFile = "configs/config.yml" // @license.url http://www.apache.org/licenses/LICENSE-2.0.html func main() { + ctxBase, cancel := context.WithCancel(context.Background()) + defer cancel() + log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { fmt.Println(err) @@ -29,5 +33,5 @@ func main() { } defer log.Sync() - app.Run(log, configFile) + app.Run(ctxBase, log, configFile) } diff --git a/internal/app/app.go b/internal/app/app.go index d6e7faa..f91ee4b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,27 +1,45 @@ package app import ( + "context" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -func Run(log *log.Logger, configFile string) { +func Run(ctx context.Context, log *log.Logger, configFile string) { + pool, err := pgxpool.New(ctx, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") + if err != nil { + log.Error(err.Error()) + return + } + defer pool.Close() + + err = pool.Ping(ctx) + if err != nil { + log.Error(err.Error()) + return + } + db, err := ramrepo.OpenDB("RamRepository") if err != nil { log.Error(err.Error()) return } - defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - pinCase := pin.New(log, ramrepo.NewRamPinRepo(db)) + userCase := user.New(log, userRepo.NewUserRepoPG(pool)) + pinCase := pin.New(log, pinRepo.NewPinRepoPG(pool)) handler := deliveryHTTP.New(log, sm, userCase, pinCase) cfgServ, err := server.NewConfig(configFile) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 50bda7a..16ff0c8 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -2,6 +2,10 @@ package pin import ( "context" + "errors" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -11,3 +15,34 @@ type Repository interface { GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) } + +type pinRepoPG struct { + db *pgxpool.Pool +} + +func NewPinRepoPG(db *pgxpool.Pool) *pinRepoPG { + return &pinRepoPG{db} +} + +func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { + rows, err := p.db.Query(ctx, "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;", afterPinID, count) + if err != nil { + return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) + } + + pins := make([]pin.Pin, 0, count) + pin := pin.Pin{} + for rows.Next() { + err := rows.Scan(&pin.ID, &pin.Picture) + if err != nil { + return pins, fmt.Errorf("scan to receive %d pins after %d: %w", count, afterPinID, err) + } + pins = append(pins, pin) + } + + return pins, nil +} + +func (r *pinRepoPG) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { + return nil, errors.New("unimplemented") +} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index b6b9447..f804fb3 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -2,6 +2,9 @@ package user import ( "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) @@ -11,3 +14,57 @@ type Repository interface { GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) } + +type userRepoPG struct { + db *pgxpool.Pool +} + +func NewUserRepoPG(db *pgxpool.Pool) *userRepoPG { + return &userRepoPG{db} +} + +func (u *userRepoPG) AddNewUser(ctx context.Context, user *user.User) error { + tx, err := u.db.Begin(ctx) + if err != nil { + return fmt.Errorf("begin transaction for add new user: %w", err) + } + + row := tx.QueryRow(ctx, "INSERT INTO profile (email) VALUES ($1) RETURNING id;", user.Email) + profileID := 0 + err = row.Scan(&profileID) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("create a profile with the return of its id: %w", err) + } + + _, err = tx.Exec(ctx, "INSERT INTO auth (username, password, profile_id) VALUES ($1, $2, $3);", user.Username, user.Password, profileID) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("linking credentials to a profile: %w", err) + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("confirmation of the transaction of adding a new user: %w", err) + } + return nil +} + +func (u *userRepoPG) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { + row := u.db.QueryRow(ctx, "SELECT profile_id, password, email FROM auth INNER JOIN profile ON profile.id = auth.profile_id WHERE username = $1;", username) + user := &user.User{Username: username} + err := row.Scan(&user.ID, &user.Password, &user.Email) + if err != nil { + return nil, fmt.Errorf("getting a user from storage: %w", err) + } + return user, nil +} + +func (r *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) { + row := r.db.QueryRow(ctx, "SELECT username, avatar FROM auth INNER JOIN profile ON auth.profile_id = profile.id WHERE profile.id = $1;", userID) + err = row.Scan(&username, &avatar) + if err != nil { + return "", "", fmt.Errorf("getting a username from storage by id: %w", err) + } + return +} From a236967a7a1aa58474023ca9965a9deaf4f6449d Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 22 Oct 2023 16:43:34 +0300 Subject: [PATCH 077/266] TP-aad add: description relations and description of functional dependencies --- db/migrations/relations.sql | 27 +++++---- db/normalized/relations.md | 111 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 db/normalized/relations.md diff --git a/db/migrations/relations.sql b/db/migrations/relations.sql index 824e250..c395763 100644 --- a/db/migrations/relations.sql +++ b/db/migrations/relations.sql @@ -4,10 +4,10 @@ SET search_path TO pinspire; CREATE TABLE IF NOT EXISTS profile ( id serial PRIMARY KEY, - email TEXT NOT NULL, - avatar TEXT NOT NULL DEFAULT 'default-avatar.png', + email text NOT NULL, + avatar text NOT NULL DEFAULT 'default-avatar.png', name text, - surname TEXT, + surname text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, @@ -18,8 +18,8 @@ ALTER TABLE profile ALTER COLUMN avatar SET DEFAULT 'avatar.jpg'; CREATE TABLE IF NOT EXISTS auth ( id serial PRIMARY KEY, - username TEXT NOT NULL, - PASSWORD TEXT NOT NULL, + username text NOT NULL, + password text NOT NULL, profile_id int NOT NULL, CONSTRAINT auth_username_uniq UNIQUE (username), CONSTRAINT auth_profile_id_uniq UNIQUE (profile_id), @@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS auth ( CREATE TABLE IF NOT EXISTS tag ( id serial PRIMARY KEY, - title TEXT NOT NULL, + title text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT tag_title_uniq UNIQUE (title) ); @@ -36,9 +36,9 @@ CREATE TABLE IF NOT EXISTS tag ( CREATE TABLE IF NOT EXISTS pin ( id serial PRIMARY KEY, author int NOT NULL, - title TEXT, - description TEXT, - picture TEXT NOT NULL, + title text, + description text, + picture text NOT NULL, public bool NOT NULL DEFAULT TRUE, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), @@ -67,9 +67,8 @@ CREATE TABLE IF NOT EXISTS like_pin ( CREATE TABLE IF NOT EXISTS board ( id serial PRIMARY KEY, author int NOT NULL, - title TEXT, - description TEXT, - picture TEXT NOT NULL, + title text, + description text, public bool NOT NULL DEFAULT TRUE, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), @@ -125,7 +124,7 @@ CREATE TABLE IF NOT EXISTS contributor ( CREATE TABLE IF NOT EXISTS subscription_user ( who int NOT NULL, whom int NOT NULL, - create_at timestamptz NOT NULL DEFAULT now(), + created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (whom, who), FOREIGN KEY (who) REFERENCES profile (id) ON DELETE CASCADE, FOREIGN KEY (whom) REFERENCES profile (id) ON DELETE CASCADE @@ -135,7 +134,7 @@ CREATE TABLE IF NOT EXISTS comment ( id serial PRIMARY KEY, author int NOT NULL, pin_id int NOT NULL, - content TEXT, + content text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, diff --git a/db/normalized/relations.md b/db/normalized/relations.md new file mode 100644 index 0000000..fe21203 --- /dev/null +++ b/db/normalized/relations.md @@ -0,0 +1,111 @@ +## Описание переменных отношений +* ### profile + Персональные данные пользователя. + +* ### auth + Авторизационные данные пользователя. + +* ### tag + Множество всех возможных тэгов, уникальных по title. Предполагается только пополнение данного множества. + +* ### pin + Пины (картинки). Пины могут быть приватными, которые видны только автору, или публичные - доступные всем для просмотра. +* ### pin_tag + Отношение служащее связью M:M для [pin](#pin) и [tag](#tag). +* ### like_pin + Оценки "`нравится`" пользователей к пинами. +* ### board + Доски (коллекция [пинов](#pin)). Доски как и пины могут быть приватными, которые видны только автору, или публичные - доступные всем для просмотра. +* ### board_tag + Отношение служащее связью M:M для [board](#board) и [tag](#tag). +* ### subscription_board + Информация о подписках пользователя на доску. +* ### membership + Информация о размещении пина на доске. +* ### role + Роли для соавторов досок. Предполагается две: `read-write` - просмотр и добавление содержимого доски, `read-only` - только просмотр содержимого, приобретает смысл для приватных досок. +* ### contributor + Соавторы досок со своими ролями. +* ### subscription_user + Информация о подписках пользователя на другого пользователя. +* ### comment + Комментарии к пинам. +* ### like_comment + Оценки "`нравится`" пользователей к комментариям. +* ### message + Сообщения личных переписок пользователей. + +Часть сущностей имеют дополнительный атрибут `deleted_at`, несущий следующую функциональность: при логическом удалении сущности данный атрибут обновляется до времени когда это произошло, сама запись из БД не удаляется. + +## Описание функциональных зависимостей +#### Relation [profile](#profile): +{id, email} -> {avatar, name, surname, created_at, updated_at, deleted_at} + +#### Relation [auth](#auth): +{id, profile_id} -> {username, password} + +#### Relation [tag](#tag): +{id, title} -> {created_at} + +#### Relation [pin](#pin): +{id} -> {author, title, description, picture, public, created_at, updated_at, deleted_at} + +#### Relation [pin_tag](#pin_tag): +{pin_id, tag_id} -> {created_at} + +#### Relation [like_pin](#like_pin): +{pin_id, user_id} -> {created_at} + +#### Relation [board](#board): +{id} -> {author, title, description, public, created_at, updated_at, deleted_at} + +#### Relation [board_tag](#board_tag): +{board_id, tag_id} -> {created_at} + +#### Relation [subscription_board](#subscription_board): +{board_id, user_id} -> {created_at} + +#### Relation [membership](#membership): +{board_id, pin_id} -> {added_at} + +#### Relation [role](#role): +{id, name} -> {} + +#### Relation [contributor](#contributor): +{user_id, board_id} -> {role_id, added_at, updated_at} + +#### Relation [subscription_user](#subscription_user): +{whom, who} -> {created_at} + +#### Relation [comment](#comment): +{id} -> {author, pin_id, content, created_at, updated_at, deleted_at} + +#### Relation [like_comment](#like_comment): +{comment_id, user_id} -> {created_at} + +#### Relation [message](#message): +{id} -> {user_from, user_to, content, created_at, updated_at, deleted_at} + + +## Соответствие нормальной форме +``` +1-ая НФ - Переменная отношения находится в первой нормальной форме (1НФ) тогда и только тогда, когда в любом допустимом значении отношения каждый его кортеж содержит только одно значение для каждого из атрибутов. +``` + +Как видно у всех сущностей атрибуты представляют собой единственное значение простого типа, следовательно 1 НФ выполняется. +# +``` +2-ая НФ - Переменная отношения находится во второй нормальной форме тогда и только тогда, когда она находится в первой нормальной форме, и каждый неключевой атрибут неприводимо (функционально полно) зависит от ее потенциального ключа. +``` + +Так как 1 НФ уже выполнена и функциональные зависимости от части ключа отсутсвуют, то 2 НФ тоже выполняется. +# +``` +3-я НФ: Переменная отношения находится в третьей нормальной форме, когда она находится во второй нормальной форме, и отсутствуют транзитивные функциональные зависимости неключевых атрибутов от ключевых. +``` +Так как 2 НФ уже выполнена и каждый атрибут переменной отношения зависит только от ключевого атрибута, то 3 НФ тоже выполнятся. +# +``` +НФ Бойса-Кодда: Отношение находится в НФБК, когда каждая нетривиальная и неприводимая слева функциональная зависимость обладает потенциальным ключом в качестве детерминанта. +``` +Как видно все детерминанты являются потенциальными ключами, сдедовательно НФБК выполняется. From 94ff1e33821eefd0090c824ab83dc2acb0718358 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 22 Oct 2023 16:47:30 +0300 Subject: [PATCH 078/266] TP-aad update: relations.md --- db/normalized/relations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/normalized/relations.md b/db/normalized/relations.md index fe21203..5c47c2d 100644 --- a/db/normalized/relations.md +++ b/db/normalized/relations.md @@ -6,7 +6,7 @@ Авторизационные данные пользователя. * ### tag - Множество всех возможных тэгов, уникальных по title. Предполагается только пополнение данного множества. + Множество всех возможных тегов, уникальных по title. Предполагается только пополнение данного множества. * ### pin Пины (картинки). Пины могут быть приватными, которые видны только автору, или публичные - доступные всем для просмотра. @@ -108,4 +108,4 @@ ``` НФ Бойса-Кодда: Отношение находится в НФБК, когда каждая нетривиальная и неприводимая слева функциональная зависимость обладает потенциальным ключом в качестве детерминанта. ``` -Как видно все детерминанты являются потенциальными ключами, сдедовательно НФБК выполняется. +Как видно все детерминанты являются потенциальными ключами, следовательно НФБК выполняется. From 813792aeee45c43a0f7f4033a6d7fe5073710b95 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 22 Oct 2023 16:49:30 +0300 Subject: [PATCH 079/266] TP-aad update: relations.md --- db/normalized/relations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/normalized/relations.md b/db/normalized/relations.md index 5c47c2d..b50a7e6 100644 --- a/db/normalized/relations.md +++ b/db/normalized/relations.md @@ -101,11 +101,11 @@ Так как 1 НФ уже выполнена и функциональные зависимости от части ключа отсутсвуют, то 2 НФ тоже выполняется. # ``` -3-я НФ: Переменная отношения находится в третьей нормальной форме, когда она находится во второй нормальной форме, и отсутствуют транзитивные функциональные зависимости неключевых атрибутов от ключевых. +3-я НФ - Переменная отношения находится в третьей нормальной форме, когда она находится во второй нормальной форме, и отсутствуют транзитивные функциональные зависимости неключевых атрибутов от ключевых. ``` Так как 2 НФ уже выполнена и каждый атрибут переменной отношения зависит только от ключевого атрибута, то 3 НФ тоже выполнятся. # ``` -НФ Бойса-Кодда: Отношение находится в НФБК, когда каждая нетривиальная и неприводимая слева функциональная зависимость обладает потенциальным ключом в качестве детерминанта. +НФ Бойса-Кодда - Отношение находится в НФБК, когда каждая нетривиальная и неприводимая слева функциональная зависимость обладает потенциальным ключом в качестве детерминанта. ``` Как видно все детерминанты являются потенциальными ключами, следовательно НФБК выполняется. From 4230710e6040d94f427b5fe31337528a5b28be92 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 22 Oct 2023 23:18:25 +0300 Subject: [PATCH 080/266] TP-5b0 add: handler upload and change avatar --- go.mod | 1 + go.sum | 2 + internal/api/server/router/router.go | 5 +++ internal/pkg/delivery/http/v1/profile.go | 31 +++++++++++++++ internal/pkg/repository/ramrepo/user.go | 5 +++ internal/pkg/repository/user/repo.go | 9 +++++ internal/pkg/usecase/user/auth.go | 40 +++++++++++++++++++ internal/pkg/usecase/user/profile.go | 49 ++++++++++++++++++++++++ internal/pkg/usecase/user/usecase.go | 35 +---------------- 9 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/profile.go create mode 100644 internal/pkg/usecase/user/auth.go create mode 100644 internal/pkg/usecase/user/profile.go diff --git a/go.mod b/go.mod index c3bb7c7..0f87da6 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 + github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/proullon/ramsql v0.0.1 github.com/rs/cors v1.10.1 diff --git a/go.sum b/go.sum index 708bafa..cda9afb 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 06d3a1a..215ddf5 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -45,6 +45,11 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) }) + r.With(auth.RequireAuth).Route("/profile", func(r chi.Router) { + r.Put("/edit", handler.ProfileEditInfo) + r.Put("/avatar", handler.ProfileEditAvatar) + }) + r.Route("/pin", func(r chi.Router) { r.Get("/", handler.GetPins) }) diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go new file mode 100644 index 0000000..4020649 --- /dev/null +++ b/internal/pkg/delivery/http/v1/profile.go @@ -0,0 +1,31 @@ +package v1 + +import ( + "fmt" + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) +} + +func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) { + SetContentTypeJSON(w) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}, + log.F{"userID", fmt.Sprint(userID)}, log.F{"content-type", r.Header.Get("Content-Type")}) + + defer r.Body.Close() + + err := h.userCase.UpdateUserAvatar(r.Context(), userID, r.Body, r.Header.Get("Content-Type")) + if err != nil { + h.log.Error(err.Error()) + responseError(w, "edit_avatar", "failed to change user's avatar") + } else { + responseOk(w, "the user's avatar has been successfully changed", nil) + } +} diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index e50cdca..d59ec60 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -3,6 +3,7 @@ package ramrepo import ( "context" "database/sql" + "errors" "fmt" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -42,3 +43,7 @@ func (r *ramUserRepo) GetUsernameAndAvatarByID(ctx context.Context, userID int) } return } + +func (r *ramUserRepo) EditUserAvatar(ctx context.Context, userID int, avatar string) error { + return errors.New("unimplemented") +} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index f804fb3..5bcce11 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -13,6 +13,7 @@ type Repository interface { AddNewUser(ctx context.Context, user *user.User) error GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) + EditUserAvatar(ctx context.Context, userID int, avatar string) error } type userRepoPG struct { @@ -68,3 +69,11 @@ func (r *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) ( } return } + +func (r *userRepoPG) EditUserAvatar(ctx context.Context, userID int, avatar string) error { + _, err := r.db.Exec(ctx, "UPDATE profile SET avatar = $1 WHERE id = $2;", avatar, userID) + if err != nil { + return fmt.Errorf("edit user avatar: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/user/auth.go b/internal/pkg/usecase/user/auth.go new file mode 100644 index 0000000..ab4fa59 --- /dev/null +++ b/internal/pkg/usecase/user/auth.go @@ -0,0 +1,40 @@ +package user + +import ( + "context" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" +) + +func (u *userCase) Register(ctx context.Context, user *entity.User) error { + salt, err := crypto.NewRandomString(lenSalt) + if err != nil { + return fmt.Errorf("generating salt for registration: %w", err) + } + + user.Password = salt + crypto.PasswordHash(user.Password, salt, lenPasswordHash) + err = u.repo.AddNewUser(ctx, user) + if err != nil { + return fmt.Errorf("user registration: %w", err) + } + return nil +} + +func (u *userCase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { + user, err := u.repo.GetUserByUsername(ctx, credentials.Username) + if err != nil { + return nil, fmt.Errorf("user authentication: %w", err) + } + salt := user.Password[:lenSalt] + if crypto.PasswordHash(credentials.Password, salt, lenPasswordHash) != user.Password[lenSalt:] { + return nil, ErrUserAuthentication + } + user.Password = "" + return user, nil +} + +func (u *userCase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { + return u.repo.GetUsernameAndAvatarByID(ctx, userID) +} diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go new file mode 100644 index 0000000..be13365 --- /dev/null +++ b/internal/pkg/usecase/user/profile.go @@ -0,0 +1,49 @@ +package user + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "strings" + "time" + + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/google/uuid" +) + +var ErrBadMIMEType = errors.New("bad mime type") + +func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error { + filename := uuid.New().String() + dir := "upload/pins/" + time.Now().UTC().Format("2006/02/01/") + err := os.MkdirAll(dir, 0750) + if err != nil { + return fmt.Errorf("create dir for upload file: %w", err) + } + piecesMimeType := strings.Split(mimeType, "/") + if len(piecesMimeType) != 2 || piecesMimeType[0] != "image" { + return ErrBadMIMEType + } + + filePath := dir + filename + "." + piecesMimeType[1] + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("create %s to save avatar to it: %w", filePath, err) + } + defer file.Close() + + _, err = io.Copy(file, avatar) + if err != nil { + return fmt.Errorf("copy avatar in file %s: %w", filePath, err) + } + u.log.Info("upload file", log.F{"file", filePath}) + + err = u.repo.EditUserAvatar(ctx, userID, "https://pinspire.online/"+filePath) + if err != nil { + return fmt.Errorf("edit user avatar: %w", err) + } + + return nil +} diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index 9d0848e..e25d359 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -3,11 +3,10 @@ package user import ( "context" "errors" - "fmt" + "io" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -22,6 +21,7 @@ type Usecase interface { Register(ctx context.Context, user *entity.User) error Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) + UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error } type userCase struct { @@ -32,34 +32,3 @@ type userCase struct { func New(log *logger.Logger, repo repo.Repository) *userCase { return &userCase{log, repo} } - -func (u *userCase) Register(ctx context.Context, user *entity.User) error { - salt, err := crypto.NewRandomString(lenSalt) - if err != nil { - return fmt.Errorf("generating salt for registration: %w", err) - } - - user.Password = salt + crypto.PasswordHash(user.Password, salt, lenPasswordHash) - err = u.repo.AddNewUser(ctx, user) - if err != nil { - return fmt.Errorf("user registration: %w", err) - } - return nil -} - -func (u *userCase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { - user, err := u.repo.GetUserByUsername(ctx, credentials.Username) - if err != nil { - return nil, fmt.Errorf("user authentication: %w", err) - } - salt := user.Password[:lenSalt] - if crypto.PasswordHash(credentials.Password, salt, lenPasswordHash) != user.Password[lenSalt:] { - return nil, ErrUserAuthentication - } - user.Password = "" - return user, nil -} - -func (u *userCase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { - return u.repo.GetUsernameAndAvatarByID(ctx, userID) -} From 5eb05b7b174218122977046e35d952318ac0bc18 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 22 Oct 2023 23:21:27 +0300 Subject: [PATCH 081/266] TP-5b0 update: change upload avatar dir --- internal/pkg/usecase/user/profile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index be13365..233eda8 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -17,7 +17,7 @@ var ErrBadMIMEType = errors.New("bad mime type") func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error { filename := uuid.New().String() - dir := "upload/pins/" + time.Now().UTC().Format("2006/02/01/") + dir := "upload/avatars/" + time.Now().UTC().Format("2006/02/01/") err := os.MkdirAll(dir, 0750) if err != nil { return fmt.Errorf("create dir for upload file: %w", err) From a66902bfd133d26a390b554c1dc31b9cca7ef23b Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 25 Oct 2023 17:33:26 +0300 Subject: [PATCH 082/266] TP-aad update: merge relations auth and profile in profile --- db/migrations/indexes.sql | 3 --- db/migrations/relations.sql | 13 +++---------- db/normalized/er-diagram.png | Bin 267795 -> 253837 bytes db/normalized/relations.md | 16 +++++++--------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/db/migrations/indexes.sql b/db/migrations/indexes.sql index 253612f..6e6518f 100644 --- a/db/migrations/indexes.sql +++ b/db/migrations/indexes.sql @@ -1,6 +1,3 @@ -CREATE INDEX IF NOT EXISTS auth_profile_index -ON auth USING btree (profile_id); - CREATE INDEX IF NOT EXISTS pin_author_index ON pin USING btree (author); diff --git a/db/migrations/relations.sql b/db/migrations/relations.sql index c395763..5fc0e9c 100644 --- a/db/migrations/relations.sql +++ b/db/migrations/relations.sql @@ -4,6 +4,8 @@ SET search_path TO pinspire; CREATE TABLE IF NOT EXISTS profile ( id serial PRIMARY KEY, + username text NOT NULL, + password text NOT NULL, email text NOT NULL, avatar text NOT NULL DEFAULT 'default-avatar.png', name text, @@ -11,21 +13,12 @@ CREATE TABLE IF NOT EXISTS profile ( created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, + CONSTRAINT profile_username_uniq UNIQUE (username), CONSTRAINT profile_email_uniq UNIQUE (email) ); ALTER TABLE profile ALTER COLUMN avatar SET DEFAULT 'avatar.jpg'; -CREATE TABLE IF NOT EXISTS auth ( - id serial PRIMARY KEY, - username text NOT NULL, - password text NOT NULL, - profile_id int NOT NULL, - CONSTRAINT auth_username_uniq UNIQUE (username), - CONSTRAINT auth_profile_id_uniq UNIQUE (profile_id), - FOREIGN KEY (profile_id) REFERENCES profile (id) ON DELETE CASCADE -); - CREATE TABLE IF NOT EXISTS tag ( id serial PRIMARY KEY, title text NOT NULL, diff --git a/db/normalized/er-diagram.png b/db/normalized/er-diagram.png index 4a426536604896c507ce92415b954e5dd122d659..856f5442cfe90d503a808e49ceff309659718867 100644 GIT binary patch literal 253837 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 M>FVdQ&MBb@09QMx5dZ)H literal 267795 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 diff --git a/db/normalized/relations.md b/db/normalized/relations.md index b50a7e6..9bb0d88 100644 --- a/db/normalized/relations.md +++ b/db/normalized/relations.md @@ -2,9 +2,6 @@ * ### profile Персональные данные пользователя. -* ### auth - Авторизационные данные пользователя. - * ### tag Множество всех возможных тегов, уникальных по title. Предполагается только пополнение данного множества. @@ -39,13 +36,13 @@ ## Описание функциональных зависимостей #### Relation [profile](#profile): -{id, email} -> {avatar, name, surname, created_at, updated_at, deleted_at} - -#### Relation [auth](#auth): -{id, profile_id} -> {username, password} +{id} -> {username, email, password, avatar, name, surname, created_at, updated_at, deleted_at} +{username} -> {id, email, password, avatar, name, surname, created_at, updated_at, deleted_at} +{email} -> {id, username, password, avatar, name, surname, created_at, updated_at, deleted_at} #### Relation [tag](#tag): -{id, title} -> {created_at} +{id} -> {title, created_at} +{title} -> {id, created_at} #### Relation [pin](#pin): {id} -> {author, title, description, picture, public, created_at, updated_at, deleted_at} @@ -69,7 +66,8 @@ {board_id, pin_id} -> {added_at} #### Relation [role](#role): -{id, name} -> {} +{id} -> {name} +{name} -> {id} #### Relation [contributor](#contributor): {user_id, board_id} -> {role_id, added_at, updated_at} From de61a6562dabba5c121db81679363cfc4f4b2bf4 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 25 Oct 2023 20:50:22 +0300 Subject: [PATCH 083/266] TP-5b0 update: made requests to global variables --- internal/pkg/repository/pin/queries.go | 5 +++++ internal/pkg/repository/pin/repo.go | 2 +- internal/pkg/repository/user/queries.go | 10 +++++++++ internal/pkg/repository/user/repo.go | 29 +++++-------------------- 4 files changed, 21 insertions(+), 25 deletions(-) create mode 100644 internal/pkg/repository/pin/queries.go create mode 100644 internal/pkg/repository/user/queries.go diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go new file mode 100644 index 0000000..94ae7dd --- /dev/null +++ b/internal/pkg/repository/pin/queries.go @@ -0,0 +1,5 @@ +package pin + +var ( + SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;" +) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 16ff0c8..7c26666 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -25,7 +25,7 @@ func NewPinRepoPG(db *pgxpool.Pool) *pinRepoPG { } func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { - rows, err := p.db.Query(ctx, "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;", afterPinID, count) + rows, err := p.db.Query(ctx, SelectAfterIdWithLimit, afterPinID, count) if err != nil { return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) } diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go new file mode 100644 index 0000000..eee2c0e --- /dev/null +++ b/internal/pkg/repository/user/queries.go @@ -0,0 +1,10 @@ +package user + +var ( + InsertNewUser = "INSERT INTO profile (username, password, email) VALUES ($1, $2, $3);" + + SelectAuthByUsername = "SELECT id, password, email FROM profile WHERE username = $1;" + SelectUsernameAndAvatar = "SELECT username, avatar FROM profile WHERE id = $1;" + + UpdateAvatarProfile = "UPDATE profile SET avatar = $1 WHERE id = $2;" +) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 5bcce11..0b16a24 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -25,34 +25,15 @@ func NewUserRepoPG(db *pgxpool.Pool) *userRepoPG { } func (u *userRepoPG) AddNewUser(ctx context.Context, user *user.User) error { - tx, err := u.db.Begin(ctx) + _, err := u.db.Exec(ctx, InsertNewUser, user.Username, user.Password, user.Email) if err != nil { - return fmt.Errorf("begin transaction for add new user: %w", err) - } - - row := tx.QueryRow(ctx, "INSERT INTO profile (email) VALUES ($1) RETURNING id;", user.Email) - profileID := 0 - err = row.Scan(&profileID) - if err != nil { - tx.Rollback(ctx) - return fmt.Errorf("create a profile with the return of its id: %w", err) - } - - _, err = tx.Exec(ctx, "INSERT INTO auth (username, password, profile_id) VALUES ($1, $2, $3);", user.Username, user.Password, profileID) - if err != nil { - tx.Rollback(ctx) - return fmt.Errorf("linking credentials to a profile: %w", err) - } - - err = tx.Commit(ctx) - if err != nil { - return fmt.Errorf("confirmation of the transaction of adding a new user: %w", err) + return fmt.Errorf("add a new profile in storage: %w", err) } return nil } func (u *userRepoPG) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { - row := u.db.QueryRow(ctx, "SELECT profile_id, password, email FROM auth INNER JOIN profile ON profile.id = auth.profile_id WHERE username = $1;", username) + row := u.db.QueryRow(ctx, SelectAuthByUsername, username) user := &user.User{Username: username} err := row.Scan(&user.ID, &user.Password, &user.Email) if err != nil { @@ -62,7 +43,7 @@ func (u *userRepoPG) GetUserByUsername(ctx context.Context, username string) (*u } func (r *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) { - row := r.db.QueryRow(ctx, "SELECT username, avatar FROM auth INNER JOIN profile ON auth.profile_id = profile.id WHERE profile.id = $1;", userID) + row := r.db.QueryRow(ctx, SelectUsernameAndAvatar, userID) err = row.Scan(&username, &avatar) if err != nil { return "", "", fmt.Errorf("getting a username from storage by id: %w", err) @@ -71,7 +52,7 @@ func (r *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) ( } func (r *userRepoPG) EditUserAvatar(ctx context.Context, userID int, avatar string) error { - _, err := r.db.Exec(ctx, "UPDATE profile SET avatar = $1 WHERE id = $2;", avatar, userID) + _, err := r.db.Exec(ctx, UpdateAvatarProfile, avatar, userID) if err != nil { return fmt.Errorf("edit user avatar: %w", err) } From f14ad69e2b29bb2d85c399c5163d09d135080730 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 25 Oct 2023 21:34:40 +0300 Subject: [PATCH 084/266] TP-5b0 add: get all data in repository --- internal/pkg/entity/user/user.go | 17 +++++++---------- internal/pkg/repository/ramrepo/user.go | 5 +++++ internal/pkg/repository/user/queries.go | 5 +++-- internal/pkg/repository/user/repo.go | 19 +++++++++++++++---- internal/pkg/usecase/user/profile.go | 8 +++++++- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index a081d77..9b32e05 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -1,14 +1,11 @@ package user -import "time" - type User struct { - ID int `json:"-" example:"123"` - Username string `json:"username" example:"Green"` - Name string `json:"-" example:"Peter"` - Surname string `json:"-" example:"Green"` - Email string `json:"email" example:"digital@gmail.com"` - Avatar string `json:"-" example:"pinspire.online/avatars/avatar.jpg"` - Password string `json:"password" example:"pass123"` - Birthday time.Time `json:"-"` + ID int `json:"-" example:"123"` + Username string `json:"username" example:"Green"` + Name string `json:"-" example:"Peter"` + Surname string `json:"-" example:"Green"` + Email string `json:"email" example:"digital@gmail.com"` + Avatar string `json:"-" example:"pinspire.online/avatars/avatar.jpg"` + Password string `json:"-" example:"pass123"` } // @name User diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index d59ec60..91df1ab 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) @@ -47,3 +48,7 @@ func (r *ramUserRepo) GetUsernameAndAvatarByID(ctx context.Context, userID int) func (r *ramUserRepo) EditUserAvatar(ctx context.Context, userID int, avatar string) error { return errors.New("unimplemented") } + +func (r *ramUserRepo) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { + return nil, errors.New("unimplemented") +} diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go index eee2c0e..88af57c 100644 --- a/internal/pkg/repository/user/queries.go +++ b/internal/pkg/repository/user/queries.go @@ -3,8 +3,9 @@ package user var ( InsertNewUser = "INSERT INTO profile (username, password, email) VALUES ($1, $2, $3);" - SelectAuthByUsername = "SELECT id, password, email FROM profile WHERE username = $1;" - SelectUsernameAndAvatar = "SELECT username, avatar FROM profile WHERE id = $1;" + SelectAuthByUsername = "SELECT id, password, email FROM profile WHERE username = $1;" + SelectUsernameAndAvatar = "SELECT username, avatar FROM profile WHERE id = $1;" + SelectUserDataExceptPassword = "SELECT username, email, avatar, name, surname FROM profile WHERE id = $1;" UpdateAvatarProfile = "UPDATE profile SET avatar = $1 WHERE id = $2;" ) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 0b16a24..39f0921 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -14,6 +14,7 @@ type Repository interface { GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) EditUserAvatar(ctx context.Context, userID int, avatar string) error + GetAllUserData(ctx context.Context, userID int) (*user.User, error) } type userRepoPG struct { @@ -42,8 +43,8 @@ func (u *userRepoPG) GetUserByUsername(ctx context.Context, username string) (*u return user, nil } -func (r *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) { - row := r.db.QueryRow(ctx, SelectUsernameAndAvatar, userID) +func (u *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) { + row := u.db.QueryRow(ctx, SelectUsernameAndAvatar, userID) err = row.Scan(&username, &avatar) if err != nil { return "", "", fmt.Errorf("getting a username from storage by id: %w", err) @@ -51,10 +52,20 @@ func (r *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) ( return } -func (r *userRepoPG) EditUserAvatar(ctx context.Context, userID int, avatar string) error { - _, err := r.db.Exec(ctx, UpdateAvatarProfile, avatar, userID) +func (u *userRepoPG) EditUserAvatar(ctx context.Context, userID int, avatar string) error { + _, err := u.db.Exec(ctx, UpdateAvatarProfile, avatar, userID) if err != nil { return fmt.Errorf("edit user avatar: %w", err) } return nil } + +func (u *userRepoPG) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { + row := u.db.QueryRow(ctx, SelectUserDataExceptPassword, userID) + user := &user.User{ID: userID} + err := row.Scan(&user.Username, &user.Email, &user.Avatar, &user.Name, &user.Surname) + if err != nil { + return nil, fmt.Errorf("get user info by id in storage: %w", err) + } + return user, nil +} diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 233eda8..256ffcd 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -9,8 +9,10 @@ import ( "strings" "time" - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/google/uuid" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) var ErrBadMIMEType = errors.New("bad mime type") @@ -47,3 +49,7 @@ func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.R return nil } + +func (u *userCase) GetAllProfileInfo(ctx context.Context, userID int) (*user.User, error) { + return u.repo.GetAllUserData(ctx, userID) +} From 2f4da724165646dda29b56ac76fadd7f8a6016a3 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 25 Oct 2023 21:38:30 +0300 Subject: [PATCH 085/266] TP-aad update: rename migrations: add prefix 00n_*.sql --- db/migrations/{relations.sql => 001_relations.sql} | 0 db/migrations/{indexes.sql => 002_indexes.sql} | 0 db/migrations/{triggers.sql => 003_triggers.sql} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename db/migrations/{relations.sql => 001_relations.sql} (100%) rename db/migrations/{indexes.sql => 002_indexes.sql} (100%) rename db/migrations/{triggers.sql => 003_triggers.sql} (100%) diff --git a/db/migrations/relations.sql b/db/migrations/001_relations.sql similarity index 100% rename from db/migrations/relations.sql rename to db/migrations/001_relations.sql diff --git a/db/migrations/indexes.sql b/db/migrations/002_indexes.sql similarity index 100% rename from db/migrations/indexes.sql rename to db/migrations/002_indexes.sql diff --git a/db/migrations/triggers.sql b/db/migrations/003_triggers.sql similarity index 100% rename from db/migrations/triggers.sql rename to db/migrations/003_triggers.sql From f6098bc46042a975b0df915af19d0f10fdfbd1bd Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 25 Oct 2023 22:31:29 +0300 Subject: [PATCH 086/266] TP-5b0 update: get profile info --- internal/api/server/router/router.go | 1 + internal/pkg/delivery/http/v1/auth.go | 2 +- internal/pkg/delivery/http/v1/profile.go | 13 +++++++++++++ internal/pkg/entity/user/user.go | 2 +- internal/pkg/usecase/user/usecase.go | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 215ddf5..e3c670e 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -46,6 +46,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) r.With(auth.RequireAuth).Route("/profile", func(r chi.Router) { + r.Get("/info", handler.GetProfileInfo) r.Put("/edit", handler.ProfileEditInfo) r.Put("/avatar", handler.ProfileEditAvatar) }) diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index cbeccc3..e981086 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -158,7 +158,7 @@ func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { err = h.userCase.Register(r.Context(), user) if err != nil { h.log.Warn(err.Error()) - err = responseError(w, "uniq_fields", "there is already an account with this username or password") + err = responseError(w, "uniq_fields", "there is already an account with this username or email") } else { err = responseOk(w, "the user has been successfully registered", nil) } diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 4020649..9566211 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -29,3 +29,16 @@ func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) responseOk(w, "the user's avatar has been successfully changed", nil) } } + +func (h *HandlerHTTP) GetProfileInfo(w http.ResponseWriter, r *http.Request) { + SetContentTypeJSON(w) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + user, err := h.userCase.GetAllProfileInfo(r.Context(), userID) + if err != nil { + h.log.Error(err.Error()) + responseError(w, "get_info", "failed to get user information") + } else { + responseOk(w, "user data has been successfully received", user) + } +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 9b32e05..bbc63d9 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -7,5 +7,5 @@ type User struct { Surname string `json:"-" example:"Green"` Email string `json:"email" example:"digital@gmail.com"` Avatar string `json:"-" example:"pinspire.online/avatars/avatar.jpg"` - Password string `json:"-" example:"pass123"` + Password string `json:"password,omitempty" example:"pass123"` } // @name User diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index e25d359..0a138ce 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -22,6 +22,7 @@ type Usecase interface { Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error + GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) } type userCase struct { From bc4a44cd56f509c88575eb4f8be3bb1a49a86051 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 25 Oct 2023 23:33:55 +0300 Subject: [PATCH 087/266] TP-5b0 update: entity user --- internal/pkg/entity/user/user.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index bbc63d9..186ea9e 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -1,11 +1,13 @@ package user +import "github.com/jackc/pgx/v5/pgtype" + type User struct { - ID int `json:"-" example:"123"` - Username string `json:"username" example:"Green"` - Name string `json:"-" example:"Peter"` - Surname string `json:"-" example:"Green"` - Email string `json:"email" example:"digital@gmail.com"` - Avatar string `json:"-" example:"pinspire.online/avatars/avatar.jpg"` - Password string `json:"password,omitempty" example:"pass123"` + ID int `json:"-" example:"123"` + Username string `json:"username" example:"Green"` + Name pgtype.Text `json:"name" example:"Peter"` + Surname pgtype.Text `json:"surname" example:"Green"` + Email string `json:"email" example:"digital@gmail.com"` + Avatar string `json:"avatar" example:"pinspire.online/avatars/avatar.jpg"` + Password string `json:"password,omitempty" example:"pass123"` } // @name User From 42fe82702f1da4475323740aebf819573a58363f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 26 Oct 2023 13:33:29 +0300 Subject: [PATCH 088/266] TP-5b0 update: handler edit user info --- go.mod | 3 +++ go.sum | 7 ++++++ internal/pkg/delivery/http/v1/profile.go | 22 +++++++++++++++++ internal/pkg/repository/ramrepo/user.go | 5 ++++ internal/pkg/repository/user/repo.go | 22 +++++++++++++++++ internal/pkg/usecase/user/info.go | 13 ++++++++++ internal/pkg/usecase/user/profile.go | 30 ++++++++++++++++++++++-- internal/pkg/usecase/user/usecase.go | 1 + 8 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/usecase/user/info.go diff --git a/go.mod b/go.mod index 0f87da6..8b02b71 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/go-park-mail-ru/2023_2_OND_team go 1.19 require ( + github.com/Masterminds/squirrel v1.5.4 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 github.com/google/uuid v1.3.1 @@ -28,6 +29,8 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect diff --git a/go.sum b/go.sum index cda9afb..c48b8c6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -47,6 +49,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -66,6 +72,7 @@ github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 9566211..a881c5a 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -1,15 +1,37 @@ package v1 import ( + "encoding/json" "fmt" "net/http" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + data := user.NewProfileUpdateData() + err := json.NewDecoder(r.Body).Decode(data) + defer r.Body.Close() + if err != nil { + h.log.Info("json decode: " + err.Error()) + responseError(w, "parse_body", + "the request body must contain json with any of the fields: username, email, name, surname, password") + } + + err = h.userCase.EditProfileInfo(r.Context(), userID, data) + if err != nil { + h.log.Error(err.Error()) + responseError(w, "uniq_fields", "there is already an account with this username or email") + } else { + responseOk(w, "user data has been successfully changed", nil) + } } func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) { diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index 91df1ab..15e8b7e 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -8,6 +8,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + rp "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" ) type ramUserRepo struct { @@ -52,3 +53,7 @@ func (r *ramUserRepo) EditUserAvatar(ctx context.Context, userID int, avatar str func (r *ramUserRepo) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { return nil, errors.New("unimplemented") } + +func (r *ramUserRepo) EditUserInfo(ctx context.Context, userID int, s rp.S) error { + return errors.New("unimplemented") +} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 39f0921..fecea91 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + sq "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v5/pgxpool" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -15,8 +16,11 @@ type Repository interface { GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) EditUserAvatar(ctx context.Context, userID int, avatar string) error GetAllUserData(ctx context.Context, userID int) (*user.User, error) + EditUserInfo(ctx context.Context, userID int, updateFields S) error } +type S map[string]any + type userRepoPG struct { db *pgxpool.Pool } @@ -69,3 +73,21 @@ func (u *userRepoPG) GetAllUserData(ctx context.Context, userID int) (*user.User } return user, nil } + +func (u *userRepoPG) EditUserInfo(ctx context.Context, userID int, updateFields S) error { + sqlRow, args, err := sq.Update("profile"). + SetMap(updateFields). + Where("id = ?", userID). + PlaceholderFormat(sq.Dollar). + ToSql() + + if err != nil { + return fmt.Errorf("build sql query row: %w", err) + } + + _, err = u.db.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("update user info in the storage: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/user/info.go b/internal/pkg/usecase/user/info.go new file mode 100644 index 0000000..c0ebdf6 --- /dev/null +++ b/internal/pkg/usecase/user/info.go @@ -0,0 +1,13 @@ +package user + +type profileUpdateData struct { + Username *string + Email *string + Name *string + Surname *string + Password *string +} + +func NewProfileUpdateData() *profileUpdateData { + return &profileUpdateData{} +} diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 256ffcd..546c7b3 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -11,7 +11,8 @@ import ( "github.com/google/uuid" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -50,6 +51,31 @@ func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.R return nil } -func (u *userCase) GetAllProfileInfo(ctx context.Context, userID int) (*user.User, error) { +func (u *userCase) GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) { return u.repo.GetAllUserData(ctx, userID) } + +func (u *userCase) EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error { + updateFields := repository.S{} + if updateData.Username != nil { + updateFields["username"] = *updateData.Username + } + if updateData.Email != nil { + updateFields["email"] = *updateData.Email + } + if updateData.Name != nil { + updateFields["name"] = *updateData.Name + } + if updateData.Surname != nil { + updateFields["surname"] = *updateData.Surname + } + if updateData.Password != nil { + updateFields["password"] = *updateData.Password + } + + err := u.repo.EditUserInfo(ctx, userID, updateFields) + if err != nil { + return fmt.Errorf("edit profile info: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index 0a138ce..fa7b429 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -23,6 +23,7 @@ type Usecase interface { FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) + EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error } type userCase struct { From ca2d93e8632029e440ca8209804632361ac03749 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 26 Oct 2023 13:50:10 +0300 Subject: [PATCH 089/266] dev2 add: search_path in all migrations --- db/migrations/002_indexes.sql | 2 ++ db/migrations/003_triggers.sql | 2 ++ 2 files changed, 4 insertions(+) diff --git a/db/migrations/002_indexes.sql b/db/migrations/002_indexes.sql index 6e6518f..795698d 100644 --- a/db/migrations/002_indexes.sql +++ b/db/migrations/002_indexes.sql @@ -1,3 +1,5 @@ +SET search_path TO pinspire; + CREATE INDEX IF NOT EXISTS pin_author_index ON pin USING btree (author); diff --git a/db/migrations/003_triggers.sql b/db/migrations/003_triggers.sql index a4b4956..907a27c 100644 --- a/db/migrations/003_triggers.sql +++ b/db/migrations/003_triggers.sql @@ -1,3 +1,5 @@ +SET search_path TO pinspire; + CREATE EXTENSION IF NOT EXISTS moddatetime; CREATE OR REPLACE TRIGGER modify_profile_updated_at From 39393bcfe8f85982d5f5c22e12ca78c5b3228101 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 26 Oct 2023 13:51:04 +0300 Subject: [PATCH 090/266] dev2 add: volumes in docker-compose.yml --- deployments/docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 0ad67f6..68306d0 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -8,5 +8,7 @@ services: POSTGRES_USER: ond_team POSTGRES_PASSWORD: love POSTGRES_DB: pinspire + volumes: + - ../db/migrations:/docker-entrypoint-initdb.d ports: - 5432:5432 \ No newline at end of file From cffc94d9682f49f46fdf612f700649381b475dee Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 26 Oct 2023 16:13:39 +0300 Subject: [PATCH 091/266] TP-5b0 add: validation of new user data --- internal/pkg/delivery/http/v1/profile.go | 54 ++++++++++++++++++--- internal/pkg/delivery/http/v1/validation.go | 26 +++++++++- internal/pkg/usecase/user/profile.go | 2 +- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index a881c5a..3aadb14 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -21,16 +21,48 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() if err != nil { h.log.Info("json decode: " + err.Error()) - responseError(w, "parse_body", + err = responseError(w, "parse_body", "the request body must contain json with any of the fields: username, email, name, surname, password") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + invalidFields := new(errorFields) + if data.Username != nil && !isValidUsername(*data.Username) { + invalidFields.addInvalidField("username") + } + if data.Email != nil && !isValidEmail(*data.Email) { + invalidFields.addInvalidField("email") + } + if data.Name != nil && !isValidName(*data.Name) { + invalidFields.addInvalidField("name") + } + if data.Surname != nil && !isValidSurname(*data.Surname) { + invalidFields.addInvalidField("surname") + } + if data.Password != nil && !isValidPassword(*data.Password) { + invalidFields.addInvalidField("password") + } + if invalidFields.Err() != nil { + err = responseError(w, "invalid_params", err.Error()) + if err != nil { + h.log.Error(err.Error()) + } + return } err = h.userCase.EditProfileInfo(r.Context(), userID, data) if err != nil { h.log.Error(err.Error()) - responseError(w, "uniq_fields", "there is already an account with this username or email") + err = responseError(w, "uniq_fields", "there is already an account with this username or email") } else { - responseOk(w, "user data has been successfully changed", nil) + err = responseOk(w, "user data has been successfully changed", nil) + } + + if err != nil { + h.log.Error(err.Error()) } } @@ -46,9 +78,13 @@ func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) err := h.userCase.UpdateUserAvatar(r.Context(), userID, r.Body, r.Header.Get("Content-Type")) if err != nil { h.log.Error(err.Error()) - responseError(w, "edit_avatar", "failed to change user's avatar") + err = responseError(w, "edit_avatar", "failed to change user's avatar") } else { - responseOk(w, "the user's avatar has been successfully changed", nil) + err = responseOk(w, "the user's avatar has been successfully changed", nil) + } + + if err != nil { + h.log.Error(err.Error()) } } @@ -59,8 +95,12 @@ func (h *HandlerHTTP) GetProfileInfo(w http.ResponseWriter, r *http.Request) { user, err := h.userCase.GetAllProfileInfo(r.Context(), userID) if err != nil { h.log.Error(err.Error()) - responseError(w, "get_info", "failed to get user information") + err = responseError(w, "get_info", "failed to get user information") } else { - responseOk(w, "user data has been successfully received", user) + err = responseOk(w, "user data has been successfully received", user) + } + + if err != nil { + h.log.Error(err.Error()) } } diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index ca2ff51..085fe42 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -72,7 +72,7 @@ func isValidUsername(username string) bool { return false } for _, r := range username { - if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { return false } } @@ -94,3 +94,27 @@ func isValidPassword(password string) bool { } return true } + +func isValidName(name string) bool { + if len(name) > 50 { + return false + } + for _, r := range name { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} + +func isValidSurname(surname string) bool { + if len(surname) > 50 { + return false + } + for _, r := range surname { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 546c7b3..0233809 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -43,7 +43,7 @@ func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.R } u.log.Info("upload file", log.F{"file", filePath}) - err = u.repo.EditUserAvatar(ctx, userID, "https://pinspire.online/"+filePath) + err = u.repo.EditUserAvatar(ctx, userID, "https://pinspire.online:8081/"+filePath) if err != nil { return fmt.Errorf("edit user avatar: %w", err) } From fafa6ddb23d13645e45b4ab00b78226438f49acc Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 27 Oct 2023 01:31:47 +0300 Subject: [PATCH 092/266] TP-e3a add: pin creations --- internal/api/server/router/router.go | 4 + internal/pkg/delivery/http/v1/pin.go | 71 ++++++++++++++-- internal/pkg/delivery/http/v1/profile.go | 2 +- internal/pkg/delivery/http/v1/validation.go | 7 ++ internal/pkg/entity/pin/pin.go | 15 ++-- internal/pkg/entity/pin/tag.go | 6 ++ internal/pkg/repository/pin/repo.go | 81 +++++++++++++++++-- internal/pkg/repository/pin/tag.go | 89 +++++++++++++++++++++ internal/pkg/repository/ramrepo/pin.go | 7 +- internal/pkg/repository/ramrepo/ramrepo.go | 3 + internal/pkg/repository/ramrepo/user.go | 10 +-- internal/pkg/repository/user/repo.go | 2 +- internal/pkg/usecase/pin/usecase.go | 51 +++++++++++- internal/pkg/usecase/user/profile.go | 2 +- 14 files changed, 315 insertions(+), 35 deletions(-) create mode 100644 internal/pkg/entity/pin/tag.go create mode 100644 internal/pkg/repository/pin/tag.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e3c670e..f8ac0f9 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -53,6 +53,10 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Route("/pin", func(r chi.Router) { r.Get("/", handler.GetPins) + r.Group(func(r chi.Router) { + r.Use(auth.RequireAuth) + r.Post("/create", handler.CreateNewPin) + }) }) }) } diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 14561bb..0ebd635 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -1,16 +1,16 @@ package v1 import ( - "errors" "net/http" + "strconv" + "strings" - _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" - + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -var ErrCountParameterMissing = errors.New("the count parameter is missing") -var ErrBadParams = errors.New("bad params") +const MaxMemoryParseFormData = 10 * 1 << 20 // GetPins godoc // @@ -47,3 +47,64 @@ func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { h.log.Error(err.Error()) } } + +func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on create new pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + if !strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { + err := responseError(w, "bad_request", "the request body should be multipart/form-data") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + err := r.ParseMultipartForm(MaxMemoryParseFormData) + if err != nil { + err = responseError(w, "bad_body", "failed to read request body") + if err != nil { + h.log.Error(err.Error()) + } + return + } + defer r.Body.Close() + + newPin := &pin.Pin{} + newPin.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) + + tags := r.FormValue("tags") + _ = tags + newPin.Tags = []pin.Tag{pin.Tag{Title: "good"}, pin.Tag{Title: "aaa"}, pin.Tag{Title: "bbb"}} + + newPin.Title = r.FormValue("title") + + newPin.Description = r.FormValue("description") + + public := r.FormValue("public") + + isPublic, err := strconv.ParseBool(public) + if err != nil { + responseError(w, "bad_body", "parameter public should have boolean value") + return + } + newPin.Public = isPublic + + picture, mime, err := r.FormFile("picture") + if err != nil { + responseError(w, "bad_body", "unable to get an image from the request body") + return + } + defer picture.Close() + + err = h.pinCase.CreateNewPin(r.Context(), newPin, picture, mime.Header.Get("Content-Type")) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "add_pin", "failed to create pin") + } else { + err = responseOk(w, "pin successfully created", nil) + } + if err != nil { + h.log.Error(err.Error()) + } +} diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 3aadb14..8009102 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -46,7 +46,7 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { invalidFields.addInvalidField("password") } if invalidFields.Err() != nil { - err = responseError(w, "invalid_params", err.Error()) + err = responseError(w, "invalid_params", invalidFields.Error()) if err != nil { h.log.Error(err.Error()) } diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index 085fe42..4aef279 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -1,6 +1,7 @@ package v1 import ( + "errors" "fmt" "net/url" "strconv" @@ -8,9 +9,15 @@ import ( "unicode" valid "github.com/asaskevich/govalidator" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +var ( + ErrCountParameterMissing = errors.New("the count parameter is missing") + ErrBadParams = errors.New("bad params") +) + type errorFields []string func (b *errorFields) Error() string { diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 218c827..9d6e1f4 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -1,12 +1,11 @@ package pin -import "time" - type Pin struct { - ID int `json:"id" example:"55"` - AuthorID int `json:"-" example:"23"` - Picture string `json:"picture" example:"pinspire/imgs/image.png"` - Title string `json:"-" example:"Nature's beauty"` - Description string `json:"-" example:"about face"` - PublicationTime time.Time `json:"-"` + ID int `json:"id" example:"55"` + AuthorID int `json:"-" example:"23"` + Picture string `json:"picture" example:"pinspire/imgs/image.png"` + Title string `json:"-" example:"Nature's beauty"` + Description string `json:"-" example:"about face"` + Tags []Tag + Public bool } //@name Pin diff --git a/internal/pkg/entity/pin/tag.go b/internal/pkg/entity/pin/tag.go new file mode 100644 index 0000000..e952f0c --- /dev/null +++ b/internal/pkg/entity/pin/tag.go @@ -0,0 +1,6 @@ +package pin + +type Tag struct { + ID int + Title string +} diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 7c26666..4e6984a 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -5,33 +5,40 @@ import ( "errors" "fmt" + sq "github.com/Masterminds/squirrel" + pgx "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) type Repository interface { - GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) + GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) + AddNewPin(ctx context.Context, pin *entity.Pin) error } type pinRepoPG struct { - db *pgxpool.Pool + db *pgxpool.Pool + sqlBuilder sq.StatementBuilderType } func NewPinRepoPG(db *pgxpool.Pool) *pinRepoPG { - return &pinRepoPG{db} + return &pinRepoPG{ + db: db, + sqlBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), + } } -func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { +func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]entity.Pin, error) { rows, err := p.db.Query(ctx, SelectAfterIdWithLimit, afterPinID, count) if err != nil { return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) } - pins := make([]pin.Pin, 0, count) - pin := pin.Pin{} + pins := make([]entity.Pin, 0, count) + pin := entity.Pin{} for rows.Next() { err := rows.Scan(&pin.ID, &pin.Picture) if err != nil { @@ -43,6 +50,64 @@ func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterP return pins, nil } -func (r *pinRepoPG) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { +func (p *pinRepoPG) AddNewPin(ctx context.Context, pin *entity.Pin) error { + titles := fetchTitles(pin.Tags) + + tx, err := p.db.Begin(ctx) + if err != nil { + return fmt.Errorf("begin transaction for add new pin: %w", err) + } + + err = p.addTags(ctx, tx, titles) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("add tags: %w", err) + } + + tagIds, err := p.getTagIdsByTitles(ctx, tx, titles) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("get tag ids by titles: %w", err) + } + + pinID, err := p.addPin(ctx, tx, pin) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("add pin: %w", err) + } + + err = p.addTagsOnPin(ctx, tx, tagIds, pinID) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("link of the tag to the picture: %w", err) + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("commit transaction for add new pin: %w", err) + } + return nil +} + +func (p *pinRepoPG) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { return nil, errors.New("unimplemented") } + +func (p *pinRepoPG) addPin(ctx context.Context, tx pgx.Tx, pin *entity.Pin) (int, error) { + sqlRow, args, err := p.sqlBuilder.Insert("pin"). + Columns("author", "title", "description", "picture", "public"). + Values(pin.AuthorID, pin.Title, pin.Description, pin.Picture, pin.Public). + Suffix("RETURNING id"). + ToSql() + if err != nil { + return 0, fmt.Errorf("build sql query row for insert pin: %w", err) + } + + row := tx.QueryRow(ctx, sqlRow, args...) + pinID := 0 + err = row.Scan(&pinID) + if err != nil { + return 0, fmt.Errorf("scan the result of the insert query to add pin: %w", err) + } + return pinID, nil +} diff --git a/internal/pkg/repository/pin/tag.go b/internal/pkg/repository/pin/tag.go new file mode 100644 index 0000000..54a0a37 --- /dev/null +++ b/internal/pkg/repository/pin/tag.go @@ -0,0 +1,89 @@ +package pin + +import ( + "context" + "errors" + "fmt" + + sq "github.com/Masterminds/squirrel" + pgx "github.com/jackc/pgx/v5" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" +) + +var ErrNumberReceivedRows = errors.New("different number of received rows was expected") + +func (p *pinRepoPG) addTags(ctx context.Context, tx pgx.Tx, titles []string) error { + insertBuilder := p.sqlBuilder.Insert("tag").Columns("title") + for _, title := range titles { + insertBuilder = insertBuilder.Values(title) + } + sqlRow, args, err := insertBuilder. + Suffix("ON CONFLICT (title) DO NOTHING"). + ToSql() + if err != nil { + return fmt.Errorf("build sql query row for insert tags: %w", err) + } + + _, err = tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("executing a query to insert tags: %w", err) + } + return nil +} + +func (p *pinRepoPG) getTagIdsByTitles(ctx context.Context, tx pgx.Tx, titles []string) ([]int, error) { + sqlRow, args, err := p.sqlBuilder.Select("id"). + From("tag"). + Where(sq.Eq{"title": titles}). + ToSql() + if err != nil { + return nil, fmt.Errorf("build sql query row for select tags: %w", err) + } + + rows, err := tx.Query(ctx, sqlRow, args...) + if err != nil { + return nil, fmt.Errorf("query to select tags id by title: %w", err) + } + defer rows.Close() + + idTag := 0 + tagIds := make([]int, 0, len(titles)) + for rows.Next() { + err = rows.Scan(&idTag) + if err != nil { + return nil, fmt.Errorf("scan a tag id for add new pin with these tags: %w", err) + } + tagIds = append(tagIds, idTag) + } + + if len(titles) != len(tagIds) { + return nil, ErrNumberReceivedRows + } + return tagIds, nil +} + +func (p *pinRepoPG) addTagsOnPin(ctx context.Context, tx pgx.Tx, tagIds []int, pinID int) error { + insertBuilder := p.sqlBuilder.Insert("pin_tag").Columns("pin_id", "tag_id") + for _, idTag := range tagIds { + insertBuilder = insertBuilder.Values(pinID, idTag) + } + sqlRow, args, err := insertBuilder.ToSql() + if err != nil { + return fmt.Errorf("build sql query row for insert link between pins and tags: %w", err) + } + + _, err = tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("executing a query to insert link between pins and tags: %w", err) + } + return nil +} + +func fetchTitles(tags []pin.Tag) []string { + titles := make([]string, 0, len(tags)) + for _, tag := range tags { + titles = append(titles, tag.Title) + } + return titles +} diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 9cfc33f..57a9228 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -3,7 +3,6 @@ package ramrepo import ( "context" "database/sql" - "errors" "fmt" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" @@ -38,5 +37,9 @@ func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count int, after } func (r *ramPinRepo) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { - return nil, errors.New("unimplemented") + return nil, ErrMethodUnimplemented +} + +func (r *ramPinRepo) AddNewPin(ctx context.Context, pin *pin.Pin) error { + return ErrMethodUnimplemented } diff --git a/internal/pkg/repository/ramrepo/ramrepo.go b/internal/pkg/repository/ramrepo/ramrepo.go index 341de69..ee369c5 100644 --- a/internal/pkg/repository/ramrepo/ramrepo.go +++ b/internal/pkg/repository/ramrepo/ramrepo.go @@ -2,11 +2,14 @@ package ramrepo import ( "database/sql" + "errors" "fmt" _ "github.com/proullon/ramsql/driver" ) +var ErrMethodUnimplemented = errors.New("unimplemented") + func OpenDB(dataSourceName string) (*sql.DB, error) { db, err := sql.Open("ramsql", dataSourceName) if err != nil { diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index 15e8b7e..25326a3 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -3,10 +3,8 @@ package ramrepo import ( "context" "database/sql" - "errors" "fmt" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" rp "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" ) @@ -47,13 +45,13 @@ func (r *ramUserRepo) GetUsernameAndAvatarByID(ctx context.Context, userID int) } func (r *ramUserRepo) EditUserAvatar(ctx context.Context, userID int, avatar string) error { - return errors.New("unimplemented") + return ErrMethodUnimplemented } -func (r *ramUserRepo) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { - return nil, errors.New("unimplemented") +func (r *ramUserRepo) GetAllUserData(ctx context.Context, userID int) (*entity.User, error) { + return nil, ErrMethodUnimplemented } func (r *ramUserRepo) EditUserInfo(ctx context.Context, userID int, s rp.S) error { - return errors.New("unimplemented") + return ErrMethodUnimplemented } diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index fecea91..150e5a6 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -77,7 +77,7 @@ func (u *userRepoPG) GetAllUserData(ctx context.Context, userID int) (*user.User func (u *userRepoPG) EditUserInfo(ctx context.Context, userID int, updateFields S) error { sqlRow, args, err := sq.Update("profile"). SetMap(updateFields). - Where("id = ?", userID). + Where(sq.Eq{"id": userID}). PlaceholderFormat(sq.Dollar). ToSql() diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index a39459e..d05a311 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -2,22 +2,32 @@ package pin import ( "context" + "errors" + "fmt" + "io" + "os" + "strings" + "time" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/google/uuid" ) +var ErrBadMIMEType = errors.New("bad mime type") + type Usecase interface { SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) + CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error } type pinCase struct { - log *logger.Logger + log *log.Logger repo repo.Repository } -func New(log *logger.Logger, repo repo.Repository) *pinCase { +func New(log *log.Logger, repo repo.Repository) *pinCase { return &pinCase{log, repo} } @@ -31,3 +41,38 @@ func (p *pinCase) SelectNewPins(ctx context.Context, count, lastID int) ([]entit } return pins, pins[len(pins)-1].ID } + +func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error { + filename := uuid.New().String() + dir := "upload/pins/" + time.Now().UTC().Format("2006/01/02/") + err := os.MkdirAll(dir, 0750) + if err != nil { + return fmt.Errorf("create dir for upload file: %w", err) + } + piecesMimeType := strings.Split(mimeType, "/") + if len(piecesMimeType) != 2 || piecesMimeType[0] != "image" { + return ErrBadMIMEType + } + + filePath := dir + filename + "." + piecesMimeType[1] + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("create %s to save avatar to it: %w", filePath, err) + } + defer file.Close() + + _, err = io.Copy(file, picture) + if err != nil { + return fmt.Errorf("copy avatar in file %s: %w", filePath, err) + } + p.log.Info("upload file", log.F{"file", filePath}) + + pin.Picture = "https://pinspire.online:8081/" + filePath + + err = p.repo.AddNewPin(ctx, pin) + if err != nil { + return fmt.Errorf("add new pin: %w", err) + } + + return nil +} diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 0233809..3fb61b3 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -20,7 +20,7 @@ var ErrBadMIMEType = errors.New("bad mime type") func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error { filename := uuid.New().String() - dir := "upload/avatars/" + time.Now().UTC().Format("2006/02/01/") + dir := "upload/avatars/" + time.Now().UTC().Format("2006/01/02/") err := os.MkdirAll(dir, 0750) if err != nil { return fmt.Errorf("create dir for upload file: %w", err) From 4f85ac1c93825912caf846026873a3add636818b Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 27 Oct 2023 11:37:17 +0300 Subject: [PATCH 093/266] TP-e3a update: reducing the number of database requests when creating a pin --- internal/pkg/repository/pin/repo.go | 8 +---- internal/pkg/repository/pin/tag.go | 53 ++++++++--------------------- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 4e6984a..b1d8de9 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -64,19 +64,13 @@ func (p *pinRepoPG) AddNewPin(ctx context.Context, pin *entity.Pin) error { return fmt.Errorf("add tags: %w", err) } - tagIds, err := p.getTagIdsByTitles(ctx, tx, titles) - if err != nil { - tx.Rollback(ctx) - return fmt.Errorf("get tag ids by titles: %w", err) - } - pinID, err := p.addPin(ctx, tx, pin) if err != nil { tx.Rollback(ctx) return fmt.Errorf("add pin: %w", err) } - err = p.addTagsOnPin(ctx, tx, tagIds, pinID) + err = p.addTagsByTitleOnPin(ctx, tx, titles, pinID) if err != nil { tx.Rollback(ctx) return fmt.Errorf("link of the tag to the picture: %w", err) diff --git a/internal/pkg/repository/pin/tag.go b/internal/pkg/repository/pin/tag.go index 54a0a37..fa70a72 100644 --- a/internal/pkg/repository/pin/tag.go +++ b/internal/pkg/repository/pin/tag.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" sq "github.com/Masterminds/squirrel" pgx "github.com/jackc/pgx/v5" @@ -11,7 +12,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" ) -var ErrNumberReceivedRows = errors.New("different number of received rows was expected") +var ErrNumberAffectedRows = errors.New("different number of affected rows was expected") func (p *pinRepoPG) addTags(ctx context.Context, tx pgx.Tx, titles []string) error { insertBuilder := p.sqlBuilder.Insert("tag").Columns("title") @@ -32,51 +33,27 @@ func (p *pinRepoPG) addTags(ctx context.Context, tx pgx.Tx, titles []string) err return nil } -func (p *pinRepoPG) getTagIdsByTitles(ctx context.Context, tx pgx.Tx, titles []string) ([]int, error) { - sqlRow, args, err := p.sqlBuilder.Select("id"). - From("tag"). - Where(sq.Eq{"title": titles}). +func (p *pinRepoPG) addTagsByTitleOnPin(ctx context.Context, tx pgx.Tx, titles []string, pinID int) error { + sqlRow, args, err := p.sqlBuilder.Insert("pin_tag"). + Columns("pin_id", "tag_id"). + Select( + sq.Select(strconv.FormatInt(int64(pinID), 10), "id"). + From("tag"). + Where(sq.Eq{"title": titles}), + ). ToSql() - if err != nil { - return nil, fmt.Errorf("build sql query row for select tags: %w", err) - } - - rows, err := tx.Query(ctx, sqlRow, args...) - if err != nil { - return nil, fmt.Errorf("query to select tags id by title: %w", err) - } - defer rows.Close() - - idTag := 0 - tagIds := make([]int, 0, len(titles)) - for rows.Next() { - err = rows.Scan(&idTag) - if err != nil { - return nil, fmt.Errorf("scan a tag id for add new pin with these tags: %w", err) - } - tagIds = append(tagIds, idTag) - } - - if len(titles) != len(tagIds) { - return nil, ErrNumberReceivedRows - } - return tagIds, nil -} - -func (p *pinRepoPG) addTagsOnPin(ctx context.Context, tx pgx.Tx, tagIds []int, pinID int) error { - insertBuilder := p.sqlBuilder.Insert("pin_tag").Columns("pin_id", "tag_id") - for _, idTag := range tagIds { - insertBuilder = insertBuilder.Values(pinID, idTag) - } - sqlRow, args, err := insertBuilder.ToSql() if err != nil { return fmt.Errorf("build sql query row for insert link between pins and tags: %w", err) } - _, err = tx.Exec(ctx, sqlRow, args...) + commTag, err := tx.Exec(ctx, sqlRow, args...) if err != nil { return fmt.Errorf("executing a query to insert link between pins and tags: %w", err) } + + if commTag.RowsAffected() != int64(len(titles)) { + return ErrNumberAffectedRows + } return nil } From 0e1774ca1152eceb7d0987b8a48eb44c58d96ea7 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 27 Oct 2023 13:37:47 +0300 Subject: [PATCH 094/266] TP-e3a add: likes, delete pin --- internal/api/server/router/router.go | 3 ++ internal/pkg/delivery/http/v1/like.go | 67 ++++++++++++++++++++++++++ internal/pkg/delivery/http/v1/pin.go | 37 +++++++++++++- internal/pkg/repository/pin/like.go | 22 +++++++++ internal/pkg/repository/pin/queries.go | 6 +++ internal/pkg/repository/pin/repo.go | 11 +++++ internal/pkg/repository/ramrepo/pin.go | 12 +++++ internal/pkg/usecase/pin/like.go | 11 +++++ internal/pkg/usecase/pin/usecase.go | 7 +++ 9 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/like.go create mode 100644 internal/pkg/repository/pin/like.go create mode 100644 internal/pkg/usecase/pin/like.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index f8ac0f9..41c850e 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -56,6 +56,9 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Group(func(r chi.Router) { r.Use(auth.RequireAuth) r.Post("/create", handler.CreateNewPin) + r.Post("/like/{pinID:\\d+}", handler.SetLikePin) + r.Delete("/like/{pinID:\\d+}", handler.DeleteLikePin) + r.Delete("/delete/{pinID:\\d+}", handler.DeletePin) }) }) }) diff --git a/internal/pkg/delivery/http/v1/like.go b/internal/pkg/delivery/http/v1/like.go new file mode 100644 index 0000000..b5e657f --- /dev/null +++ b/internal/pkg/delivery/http/v1/like.go @@ -0,0 +1,67 @@ +package v1 + +import ( + "net/http" + "strconv" + + chi "github.com/go-chi/chi/v5" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func (h *HandlerHTTP) SetLikePin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on set like for pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + pinIdStr := chi.URLParam(r, "pinID") + pinID, err := strconv.ParseInt(pinIdStr, 10, 64) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "parse_url", "internal error") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + err = h.pinCase.SetLikeFromUser(r.Context(), int(pinID), userID) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "like_pin_del", "internal error") + } else { + err = responseOk(w, "ok", nil) + } + if err != nil { + h.log.Error(err.Error()) + } +} + +func (h *HandlerHTTP) DeleteLikePin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on set like for pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + pinIdStr := chi.URLParam(r, "pinID") + pinID, err := strconv.ParseInt(pinIdStr, 10, 64) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "parse_url", "internal error") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + err = h.pinCase.DeleteLikeFromUser(r.Context(), int(pinID), userID) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "like_pin_del", "internal error") + } else { + err = responseOk(w, "ok", nil) + } + if err != nil { + h.log.Error(err.Error()) + } +} diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 0ebd635..9d4aaf0 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" + chi "github.com/go-chi/chi/v5" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -74,8 +76,11 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { newPin.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) tags := r.FormValue("tags") - _ = tags - newPin.Tags = []pin.Tag{pin.Tag{Title: "good"}, pin.Tag{Title: "aaa"}, pin.Tag{Title: "bbb"}} + titles := strings.Split(tags, ",") + newPin.Tags = make([]pin.Tag, 0, len(titles)) + for _, title := range titles { + newPin.Tags = append(newPin.Tags, pin.Tag{Title: title}) + } newPin.Title = r.FormValue("title") @@ -108,3 +113,31 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { h.log.Error(err.Error()) } } + +func (h *HandlerHTTP) DeletePin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on delete new pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + pinIdStr := chi.URLParam(r, "pinID") + pinID, err := strconv.ParseInt(pinIdStr, 10, 64) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "parse_url", "internal error") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + err = h.pinCase.DeletePinFromUser(r.Context(), int(pinID), userID) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "pin_del", "internal error") + } else { + err = responseOk(w, "ok", nil) + } + if err != nil { + h.log.Error(err.Error()) + } +} diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go new file mode 100644 index 0000000..208db44 --- /dev/null +++ b/internal/pkg/repository/pin/like.go @@ -0,0 +1,22 @@ +package pin + +import ( + "context" + "fmt" +) + +func (p *pinRepoPG) SetLike(ctx context.Context, pinID, userID int) error { + _, err := p.db.Exec(ctx, InsertLikePinFromUser, pinID, userID) + if err != nil { + return fmt.Errorf("insert like to pin from user in storage: %w", err) + } + return nil +} + +func (p *pinRepoPG) DelLike(ctx context.Context, pinID, userID int) error { + _, err := p.db.Exec(ctx, DeleteLikePinFromUser, pinID, userID) + if err != nil { + return fmt.Errorf("delete like to pin from user in storage: %w", err) + } + return nil +} diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 94ae7dd..c6694bb 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -2,4 +2,10 @@ package pin var ( SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;" + + InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" + + UpdatePinSetStatusDelete = "UPDATE pin SET deleted_at = now() WHERE id = $1 AND author = $2;" + + DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2;" ) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index b1d8de9..ec9ba90 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -17,6 +17,9 @@ type Repository interface { GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) AddNewPin(ctx context.Context, pin *entity.Pin) error + DeletePin(ctx context.Context, pinID, userID int) error + SetLike(ctx context.Context, pinID, userID int) error + DelLike(ctx context.Context, pinID, userID int) error } type pinRepoPG struct { @@ -87,6 +90,14 @@ func (p *pinRepoPG) GetAuthorPin(ctx context.Context, pinID int) (*user.User, er return nil, errors.New("unimplemented") } +func (p *pinRepoPG) DeletePin(ctx context.Context, pinID, userID int) error { + _, err := p.db.Exec(ctx, UpdatePinSetStatusDelete, pinID, userID) + if err != nil { + return fmt.Errorf("set pin deleted at now: %w", err) + } + return nil +} + func (p *pinRepoPG) addPin(ctx context.Context, tx pgx.Tx, pin *entity.Pin) (int, error) { sqlRow, args, err := p.sqlBuilder.Insert("pin"). Columns("author", "title", "description", "picture", "public"). diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 57a9228..992e5b9 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -43,3 +43,15 @@ func (r *ramPinRepo) GetAuthorPin(ctx context.Context, pinID int) (*user.User, e func (r *ramPinRepo) AddNewPin(ctx context.Context, pin *pin.Pin) error { return ErrMethodUnimplemented } + +func (r *ramPinRepo) DeletePin(ctx context.Context, pinID, userID int) error { + return ErrMethodUnimplemented +} + +func (r *ramPinRepo) SetLike(ctx context.Context, pinID, userID int) error { + return ErrMethodUnimplemented +} + +func (r *ramPinRepo) DelLike(ctx context.Context, pinID, userID int) error { + return ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go new file mode 100644 index 0000000..c8391ae --- /dev/null +++ b/internal/pkg/usecase/pin/like.go @@ -0,0 +1,11 @@ +package pin + +import "context" + +func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) error { + return p.repo.SetLike(ctx, pinID, userID) +} + +func (p *pinCase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { + return p.repo.DelLike(ctx, pinID, userID) +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index d05a311..0ab1ac7 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -20,6 +20,9 @@ var ErrBadMIMEType = errors.New("bad mime type") type Usecase interface { SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error + DeletePinFromUser(ctx context.Context, pinID, userID int) error + SetLikeFromUser(ctx context.Context, pinID, userID int) error + DeleteLikeFromUser(ctx context.Context, pinID, userID int) error } type pinCase struct { @@ -76,3 +79,7 @@ func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io. return nil } + +func (p *pinCase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { + return p.repo.DeletePin(ctx, pinID, userID) +} From 6af77bac731689d2e3170da66ab9c46b1313c762 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 28 Oct 2023 01:51:13 +0300 Subject: [PATCH 095/266] PT-e3a add: hander for edit pins --- internal/api/server/router/router.go | 3 ++ internal/pkg/delivery/http/v1/pin.go | 54 +++++++++++++++++++-- internal/pkg/repository/pin/queries.go | 3 +- internal/pkg/repository/pin/repo.go | 53 ++++++++++++++++++++- internal/pkg/repository/pin/tag.go | 66 ++++++++++++++++++++++++-- internal/pkg/repository/ramrepo/pin.go | 9 ++++ internal/pkg/usecase/pin/update.go | 37 +++++++++++++++ internal/pkg/usecase/pin/usecase.go | 1 + 8 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 internal/pkg/usecase/pin/update.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 41c850e..155de17 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -40,6 +40,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Post("/signup", handler.Signup) r.Group(func(r chi.Router) { r.Use(auth.RequireAuth) + r.Get("/login", handler.CheckLogin) r.Delete("/logout", handler.Logout) }) @@ -55,8 +56,10 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Get("/", handler.GetPins) r.Group(func(r chi.Router) { r.Use(auth.RequireAuth) + r.Post("/create", handler.CreateNewPin) r.Post("/like/{pinID:\\d+}", handler.SetLikePin) + r.Put("/edit/{pinID:\\d+}", handler.EditPin) r.Delete("/like/{pinID:\\d+}", handler.DeleteLikePin) r.Delete("/delete/{pinID:\\d+}", handler.DeletePin) }) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 9d4aaf0..036587d 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -1,14 +1,16 @@ package v1 import ( + "encoding/json" "net/http" "strconv" "strings" chi "github.com/go-chi/chi/v5" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -72,14 +74,14 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { } defer r.Body.Close() - newPin := &pin.Pin{} + newPin := &entity.Pin{} newPin.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) tags := r.FormValue("tags") titles := strings.Split(tags, ",") - newPin.Tags = make([]pin.Tag, 0, len(titles)) + newPin.Tags = make([]entity.Tag, 0, len(titles)) for _, title := range titles { - newPin.Tags = append(newPin.Tags, pin.Tag{Title: title}) + newPin.Tags = append(newPin.Tags, entity.Tag{Title: title}) } newPin.Title = r.FormValue("title") @@ -116,6 +118,7 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) DeletePin(w http.ResponseWriter, r *http.Request) { h.log.Info("request on delete new pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) userID := r.Context().Value(auth.KeyCurrentUserID).(int) @@ -141,3 +144,46 @@ func (h *HandlerHTTP) DeletePin(w http.ResponseWriter, r *http.Request) { h.log.Error(err.Error()) } } + +func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on edit pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + pinIdStr := chi.URLParam(r, "pinID") + pinID, err := strconv.ParseInt(pinIdStr, 10, 64) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "parse_url", "internal error") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + _, _ = userID, pinID + + pinUpdate := usecase.NewPinUpdateData() + + err = json.NewDecoder(r.Body).Decode(pinUpdate) + defer r.Body.Close() + if err != nil { + h.log.Info(err.Error()) + err = responseError(w, "parse_body", "could not read the data to change") + if err != nil { + h.log.Error(err.Error()) + } + } + err = h.pinCase.EditPinByID(r.Context(), int(pinID), userID, pinUpdate) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "edit_pin", "internal error") + } else { + err = responseOk(w, "pin data has been successfully changed", nil) + } + + if err != nil { + h.log.Error(err.Error()) + } +} diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index c6694bb..1ca7898 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -1,11 +1,12 @@ package pin var ( - SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;" + SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id > $1 ORDER BY created_at DESC, id LIMIT $2;" InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" UpdatePinSetStatusDelete = "UPDATE pin SET deleted_at = now() WHERE id = $1 AND author = $2;" DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2;" + DeleteAllTagsFromPin = "DELETE FROM pin_tag WHERE pin_id = $1;" ) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index ec9ba90..4da5012 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -13,6 +13,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +type S map[string]any type Repository interface { GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) @@ -20,6 +21,7 @@ type Repository interface { DeletePin(ctx context.Context, pinID, userID int) error SetLike(ctx context.Context, pinID, userID int) error DelLike(ctx context.Context, pinID, userID int) error + EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error } type pinRepoPG struct { @@ -73,7 +75,7 @@ func (p *pinRepoPG) AddNewPin(ctx context.Context, pin *entity.Pin) error { return fmt.Errorf("add pin: %w", err) } - err = p.addTagsByTitleOnPin(ctx, tx, titles, pinID) + err = p.addTagsByTitleOnPin(ctx, tx, titles, pinID, true) if err != nil { tx.Rollback(ctx) return fmt.Errorf("link of the tag to the picture: %w", err) @@ -98,6 +100,55 @@ func (p *pinRepoPG) DeletePin(ctx context.Context, pinID, userID int) error { return nil } +func (p *pinRepoPG) EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error { + if len(updateData) == 0 && titleTags == nil { + return nil + } + + tx, err := p.db.Begin(ctx) + if err != nil { + return fmt.Errorf("begin transaction for edit pin: %w", err) + } + + if len(updateData) != 0 { + err = p.updateHeaderPin(ctx, tx, pinID, updateData) + } + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("edit pin header: %w", err) + } + + if titleTags != nil { + err = p.updateSetOfTagsInPin(ctx, tx, pinID, titleTags) + } + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("edit tags on pin: %w", err) + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("commit transaction for edit pin: %w", err) + } + return nil +} + +func (p *pinRepoPG) updateHeaderPin(ctx context.Context, tx pgx.Tx, pinID int, newHeader S) error { + sqlRow, args, err := p.sqlBuilder.Update("pin"). + SetMap(newHeader). + Where(sq.Eq{"id": pinID}). + ToSql() + if err != nil { + return fmt.Errorf("build sql row for update header pin: %w", err) + } + + _, err = tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("update header pin in storage: %w", err) + } + return nil +} + func (p *pinRepoPG) addPin(ctx context.Context, tx pgx.Tx, pin *entity.Pin) (int, error) { sqlRow, args, err := p.sqlBuilder.Insert("pin"). Columns("author", "title", "description", "picture", "public"). diff --git a/internal/pkg/repository/pin/tag.go b/internal/pkg/repository/pin/tag.go index fa70a72..507858e 100644 --- a/internal/pkg/repository/pin/tag.go +++ b/internal/pkg/repository/pin/tag.go @@ -33,15 +33,18 @@ func (p *pinRepoPG) addTags(ctx context.Context, tx pgx.Tx, titles []string) err return nil } -func (p *pinRepoPG) addTagsByTitleOnPin(ctx context.Context, tx pgx.Tx, titles []string, pinID int) error { - sqlRow, args, err := p.sqlBuilder.Insert("pin_tag"). +func (p *pinRepoPG) addTagsByTitleOnPin(ctx context.Context, tx pgx.Tx, titles []string, pinID int, newPin bool) error { + insertQuery := p.sqlBuilder.Insert("pin_tag"). Columns("pin_id", "tag_id"). Select( sq.Select(strconv.FormatInt(int64(pinID), 10), "id"). From("tag"). Where(sq.Eq{"title": titles}), - ). - ToSql() + ) + if !newPin { + insertQuery = insertQuery.Suffix("ON CONFLICT (pin_id, tag_id) DO NOTHING") + } + sqlRow, args, err := insertQuery.ToSql() if err != nil { return fmt.Errorf("build sql query row for insert link between pins and tags: %w", err) } @@ -51,12 +54,65 @@ func (p *pinRepoPG) addTagsByTitleOnPin(ctx context.Context, tx pgx.Tx, titles [ return fmt.Errorf("executing a query to insert link between pins and tags: %w", err) } - if commTag.RowsAffected() != int64(len(titles)) { + if commTag.RowsAffected() != int64(len(titles)) && newPin { return ErrNumberAffectedRows } return nil } +func (p *pinRepoPG) updateSetOfTagsInPin(ctx context.Context, tx pgx.Tx, pinID int, titles []string) error { + if len(titles) == 0 { + return removeAllTagsFromPin(ctx, tx, pinID) + } + + err := p.addTags(ctx, tx, titles) + if err != nil { + return fmt.Errorf("add tags for update set tags fo pin: %w", err) + } + + err = p.deleteAllTagsExcept(ctx, tx, pinID, titles) + if err != nil { + return fmt.Errorf("delete tags for updates: %w", err) + } + + err = p.addTagsByTitleOnPin(ctx, tx, titles, pinID, false) + if err != nil { + return fmt.Errorf("add tags for updates: %w", err) + } + + return nil +} + +func (p *pinRepoPG) deleteAllTagsExcept(ctx context.Context, tx pgx.Tx, pinID int, except []string) error { + if len(except) == 0 { + return removeAllTagsFromPin(ctx, tx, pinID) + } + + sqlRow, args, err := p.sqlBuilder.Delete("pin_tag"). + Where(sq.Eq{"pin_id": pinID}). + Where(sq.Expr("tag_id NOT IN (?)", sq.Select("id"). + From("tag"). + Where(sq.Eq{"title": except}))). + ToSql() + if err != nil { + return fmt.Errorf("build sql row to delete all tags except those specified: %w", err) + } + + _, err = tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("delete all tags except those specified: %w", err) + } + return nil +} + +func removeAllTagsFromPin(ctx context.Context, tx pgx.Tx, pinID int) error { + _, err := tx.Exec(ctx, DeleteAllTagsFromPin, pinID) + if err != nil { + return fmt.Errorf("delete all tags from pin: %w", err) + } + return nil +} + func fetchTitles(tags []pin.Tag) []string { titles := make([]string, 0, len(tags)) for _, tag := range tags { diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 992e5b9..d049c89 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -7,6 +7,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" ) type ramPinRepo struct { @@ -55,3 +56,11 @@ func (r *ramPinRepo) SetLike(ctx context.Context, pinID, userID int) error { func (r *ramPinRepo) DelLike(ctx context.Context, pinID, userID int) error { return ErrMethodUnimplemented } + +func (r *ramPinRepo) EditPinTags(ctx context.Context, pinID, userID int, titlePins []string) error { + return ErrMethodUnimplemented +} + +func (r *ramPinRepo) EditPin(ctx context.Context, pinID int, updateData repository.S, titleTags []string) error { + return ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/pin/update.go b/internal/pkg/usecase/pin/update.go new file mode 100644 index 0000000..dc2c36e --- /dev/null +++ b/internal/pkg/usecase/pin/update.go @@ -0,0 +1,37 @@ +package pin + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" +) + +type pinUpdateData struct { + Title *string + Description *string + Public *bool + Tags []string +} + +func NewPinUpdateData() *pinUpdateData { + return &pinUpdateData{} +} + +func (p *pinCase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { + data := pin.S{} + if updateData.Title != nil { + data["title"] = *updateData.Title + } + if updateData.Description != nil { + data["description"] = *updateData.Description + } + if updateData.Public != nil { + data["public"] = *updateData.Public + } + err := p.repo.EditPin(ctx, pinID, data, updateData.Tags) + if err != nil { + return fmt.Errorf("edit pin by id: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 0ab1ac7..0cbf1aa 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -23,6 +23,7 @@ type Usecase interface { DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) error DeleteLikeFromUser(ctx context.Context, pinID, userID int) error + EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error } type pinCase struct { From 73f3c5061d79ea5bd534634654342f450c9e7a1d Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 28 Oct 2023 16:11:56 +0300 Subject: [PATCH 096/266] TP-aad_db update: move creating triggers after create tables --- db/migrations/001_relations.sql | 38 ++++++++++++++++++++++++++++++++- db/migrations/003_triggers.sql | 37 -------------------------------- 2 files changed, 37 insertions(+), 38 deletions(-) delete mode 100644 db/migrations/003_triggers.sql diff --git a/db/migrations/001_relations.sql b/db/migrations/001_relations.sql index 5fc0e9c..7b53a51 100644 --- a/db/migrations/001_relations.sql +++ b/db/migrations/001_relations.sql @@ -2,6 +2,8 @@ CREATE SCHEMA IF NOT EXISTS pinspire; SET search_path TO pinspire; +CREATE EXTENSION IF NOT EXISTS moddatetime; + CREATE TABLE IF NOT EXISTS profile ( id serial PRIMARY KEY, username text NOT NULL, @@ -17,7 +19,11 @@ CREATE TABLE IF NOT EXISTS profile ( CONSTRAINT profile_email_uniq UNIQUE (email) ); -ALTER TABLE profile ALTER COLUMN avatar SET DEFAULT 'avatar.jpg'; +CREATE OR REPLACE TRIGGER modify_profile_updated_at + BEFORE UPDATE + ON profile + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); CREATE TABLE IF NOT EXISTS tag ( id serial PRIMARY KEY, @@ -39,6 +45,12 @@ CREATE TABLE IF NOT EXISTS pin ( FOREIGN KEY (author) REFERENCES profile (id) ON DELETE CASCADE ); +CREATE OR REPLACE TRIGGER modify_pin_updated_at + BEFORE UPDATE + ON pin + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + CREATE TABLE IF NOT EXISTS pin_tag ( pin_id int NOT NULL, tag_id int NOT NULL, @@ -69,6 +81,12 @@ CREATE TABLE IF NOT EXISTS board ( FOREIGN KEY (author) REFERENCES profile (id) ON DELETE CASCADE ); +CREATE OR REPLACE TRIGGER modify_board_updated_at + BEFORE UPDATE + ON board + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + CREATE TABLE IF NOT EXISTS board_tag ( board_id int NOT NULL, tag_id int NOT NULL, @@ -114,6 +132,12 @@ CREATE TABLE IF NOT EXISTS contributor ( FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE ); +CREATE OR REPLACE TRIGGER modify_contributor_updated_at + BEFORE UPDATE + ON contributor + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + CREATE TABLE IF NOT EXISTS subscription_user ( who int NOT NULL, whom int NOT NULL, @@ -135,6 +159,12 @@ CREATE TABLE IF NOT EXISTS comment ( FOREIGN KEY (pin_id) REFERENCES pin (id) ON DELETE CASCADE ); +CREATE OR REPLACE TRIGGER modify_comment_updated_at + BEFORE UPDATE + ON comment + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); + CREATE TABLE IF NOT EXISTS like_comment ( user_id int NOT NULL, comment_id int NOT NULL, @@ -155,3 +185,9 @@ CREATE TABLE IF NOT EXISTS message ( FOREIGN KEY (user_from) REFERENCES profile (id) ON DELETE CASCADE, FOREIGN KEY (user_to) REFERENCES profile (id) ON DELETE CASCADE ); + +CREATE OR REPLACE TRIGGER modify_message_updated_at + BEFORE UPDATE + ON message + FOR EACH ROW +EXECUTE PROCEDURE moddatetime(updated_at); diff --git a/db/migrations/003_triggers.sql b/db/migrations/003_triggers.sql deleted file mode 100644 index a4b4956..0000000 --- a/db/migrations/003_triggers.sql +++ /dev/null @@ -1,37 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS moddatetime; - -CREATE OR REPLACE TRIGGER modify_profile_updated_at - BEFORE UPDATE - ON profile - FOR EACH ROW -EXECUTE PROCEDURE moddatetime(updated_at); - -CREATE OR REPLACE TRIGGER modify_pin_updated_at - BEFORE UPDATE - ON pin - FOR EACH ROW -EXECUTE PROCEDURE moddatetime(updated_at); - -CREATE OR REPLACE TRIGGER modify_board_updated_at - BEFORE UPDATE - ON board - FOR EACH ROW -EXECUTE PROCEDURE moddatetime(updated_at); - -CREATE OR REPLACE TRIGGER modify_contributor_updated_at - BEFORE UPDATE - ON contributor - FOR EACH ROW -EXECUTE PROCEDURE moddatetime(updated_at); - -CREATE OR REPLACE TRIGGER modify_comment_updated_at - BEFORE UPDATE - ON comment - FOR EACH ROW -EXECUTE PROCEDURE moddatetime(updated_at); - -CREATE OR REPLACE TRIGGER modify_message_updated_at - BEFORE UPDATE - ON message - FOR EACH ROW -EXECUTE PROCEDURE moddatetime(updated_at); From 8ef0afb04eefd7d3ea3408c057a7c065a7120ec4 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 13:36:43 +0300 Subject: [PATCH 097/266] TP-c80 add: middleware with csrf token verification or installation --- internal/api/server/router/router.go | 6 +- internal/pkg/middleware/middleware.go | 5 + internal/pkg/middleware/security/csrf.go | 92 +++++++++++++++++++ .../pkg/middleware/security/csrf_config.go | 43 +++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/middleware/middleware.go create mode 100644 internal/pkg/middleware/security/csrf.go create mode 100644 internal/pkg/middleware/security/csrf_config.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e3c670e..7abf27d 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -10,6 +10,7 @@ import ( _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/security" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -30,7 +31,10 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess AllowedHeaders: []string{"content-type"}, }) - r.Mux.Use(c.Handler, auth.NewAuthMiddleware(sm).ContextWithUserID) + cfgCSRF := security.DefaultCSRFConfig() + cfgCSRF.PathToGet = "/api/v1/csrf" + + r.Mux.Use(c.Handler, security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) diff --git a/internal/pkg/middleware/middleware.go b/internal/pkg/middleware/middleware.go new file mode 100644 index 0000000..ff0f53a --- /dev/null +++ b/internal/pkg/middleware/middleware.go @@ -0,0 +1,5 @@ +package middleware + +import "net/http" + +type Middleware func(next http.Handler) http.Handler diff --git a/internal/pkg/middleware/security/csrf.go b/internal/pkg/middleware/security/csrf.go new file mode 100644 index 0000000..b757d31 --- /dev/null +++ b/internal/pkg/middleware/security/csrf.go @@ -0,0 +1,92 @@ +package security + +import ( + "fmt" + "net/http" + "time" + + mw "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" +) + +func CSRF(cfg CSRFConfig) mw.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == cfg.PathToGet && r.Method == http.MethodGet { + setToken(w, &cfg) + return + } + + skip := isSkipMethod(r.Method, &cfg) + tokenHeader := r.Header.Get(cfg.Header) + cookie, err := r.Cookie(cfg.CookieName) + + if err != nil && !skip { + if len(tokenHeader) == 0 { + responseCSRFErr(http.StatusBadRequest, w, "missing csrf token in request header") + } else { + responseCSRFErr(http.StatusForbidden, w, "invalid csrf token") + } + return + } + + if err != nil { + setToken(w, &cfg) + next.ServeHTTP(w, r) + return + } + + if len(tokenHeader) != cfg.LenToken || cookie.Value != tokenHeader { + if skip { + setToken(w, &cfg) + next.ServeHTTP(w, r) + } else { + responseCSRFErr(http.StatusForbidden, w, "invalid csrf token") + } + return + } + + if skip || cfg.UpdateWithEachRequest { + setToken(w, &cfg) + } + next.ServeHTTP(w, r) + }) + } +} + +func isSkipMethod(method string, cfg *CSRFConfig) bool { + for _, skipMethod := range cfg.SkipMethods { + if method == skipMethod { + return true + } + } + return false +} + +func setToken(w http.ResponseWriter, cfg *CSRFConfig) error { + token, err := crypto.NewRandomString(cfg.LenToken) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return fmt.Errorf("set new csrf token: %w", err) + } + + cookie := &http.Cookie{ + Name: cfg.CookieName, + Value: token, + Path: cfg.CookietPath, + Domain: cfg.CookieDomain, + Secure: cfg.CookieSecure, + HttpOnly: cfg.CookieHTTPOnly, + SameSite: cfg.CookieSameSite, + Expires: time.Now().UTC().Add(cfg.Lifetime), + } + http.SetCookie(w, cookie) + w.Header().Set(cfg.HeaderSet, token) + return nil +} + +func responseCSRFErr(status int, w http.ResponseWriter, message string) { + w.WriteHeader(status) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status": "error", "code": "csrf", "message": "` + message + `"}`)) +} diff --git a/internal/pkg/middleware/security/csrf_config.go b/internal/pkg/middleware/security/csrf_config.go new file mode 100644 index 0000000..5413f3c --- /dev/null +++ b/internal/pkg/middleware/security/csrf_config.go @@ -0,0 +1,43 @@ +package security + +import ( + "net/http" + "time" +) + +type CSRFConfig struct { + CookieConfig + SkipMethods []string + HeaderSet string + Header string + PathToGet string + Lifetime time.Duration + LenToken int + UpdateWithEachRequest bool +} + +type CookieConfig struct { + CookieName string + CookieDomain string + CookietPath string + CookieSecure bool + CookieHTTPOnly bool + CookieSameSite http.SameSite +} + +func DefaultCSRFConfig() CSRFConfig { + return CSRFConfig{ + CookieConfig: CookieConfig{ + CookieName: "_csrf", + CookietPath: "/", + CookieSecure: true, + CookieHTTPOnly: true, + CookieSameSite: http.SameSiteStrictMode, + }, + SkipMethods: []string{http.MethodGet, http.MethodHead, http.MethodOptions}, + HeaderSet: "X-Set-CSRF-Token", + Header: "X-CSRF-Token", + LenToken: 16, + Lifetime: time.Hour, + } +} From aa4c773de453270ff4de0f9f806dbdc5483635e9 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 13:50:22 +0300 Subject: [PATCH 098/266] TP-c80 update: updated the check for the need to update the csrf token --- internal/pkg/middleware/security/csrf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/middleware/security/csrf.go b/internal/pkg/middleware/security/csrf.go index b757d31..0cf2e75 100644 --- a/internal/pkg/middleware/security/csrf.go +++ b/internal/pkg/middleware/security/csrf.go @@ -46,7 +46,7 @@ func CSRF(cfg CSRFConfig) mw.Middleware { return } - if skip || cfg.UpdateWithEachRequest { + if skip && cfg.UpdateWithEachRequest { setToken(w, &cfg) } next.ServeHTTP(w, r) From c19fa6dfab3c35ffcd3a69e379b2e6221439eb58 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 14:36:22 +0300 Subject: [PATCH 099/266] TP-c80 update: updated the check for the need to update the csrf token --- internal/pkg/middleware/security/csrf.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/internal/pkg/middleware/security/csrf.go b/internal/pkg/middleware/security/csrf.go index 0cf2e75..6c2035d 100644 --- a/internal/pkg/middleware/security/csrf.go +++ b/internal/pkg/middleware/security/csrf.go @@ -36,17 +36,12 @@ func CSRF(cfg CSRFConfig) mw.Middleware { return } - if len(tokenHeader) != cfg.LenToken || cookie.Value != tokenHeader { - if skip { - setToken(w, &cfg) - next.ServeHTTP(w, r) - } else { - responseCSRFErr(http.StatusForbidden, w, "invalid csrf token") - } + if !skip && (len(tokenHeader) != cfg.LenToken || cookie.Value != tokenHeader) { + responseCSRFErr(http.StatusForbidden, w, "invalid csrf token") return } - if skip && cfg.UpdateWithEachRequest { + if cfg.UpdateWithEachRequest { setToken(w, &cfg) } next.ServeHTTP(w, r) From aa62ad329cceb9ae23067021f0e18db0dc531367 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 16:31:53 +0300 Subject: [PATCH 100/266] TP-2f3 add: repository with storage in redis --- deployments/docker-compose.yml | 9 ++++- go.mod | 3 ++ go.sum | 8 ++++ internal/app/app.go | 22 +++++++--- internal/pkg/repository/session/repo.go | 53 +++++++++++++++++++++++++ internal/pkg/usecase/session/manager.go | 3 -- 6 files changed, 88 insertions(+), 10 deletions(-) diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 68306d0..574ddef 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -11,4 +11,11 @@ services: volumes: - ../db/migrations:/docker-entrypoint-initdb.d ports: - - 5432:5432 \ No newline at end of file + - 5432:5432 + + redis: + image: redis:latest + container_name: pinspireRedis + command: /bin/sh -c "redis-server --requirepass love" + ports: + - 6379:6379 diff --git a/go.mod b/go.mod index 8b02b71..3ef4676 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/proullon/ramsql v0.0.1 + github.com/redis/go-redis/v9 v9.2.1 github.com/rs/cors v1.10.1 github.com/stretchr/testify v1.8.4 github.com/swaggo/http-swagger v1.3.4 @@ -20,7 +21,9 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect diff --git a/go.sum b/go.sum index c48b8c6..ae7a898 100644 --- a/go.sum +++ b/go.sum @@ -5,10 +5,16 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= @@ -64,6 +70,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= diff --git a/internal/app/app.go b/internal/app/app.go index f91ee4b..18d4312 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,14 +2,16 @@ package app import ( "context" + "time" "github.com/jackc/pgx/v5/pgxpool" + redis "github.com/redis/go-redis/v9" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" @@ -18,26 +20,34 @@ import ( ) func Run(ctx context.Context, log *log.Logger, configFile string) { - pool, err := pgxpool.New(ctx, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") + ctxApp, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + pool, err := pgxpool.New(ctxApp, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") if err != nil { log.Error(err.Error()) return } defer pool.Close() - err = pool.Ping(ctx) + err = pool.Ping(ctxApp) if err != nil { log.Error(err.Error()) return } - db, err := ramrepo.OpenDB("RamRepository") - if err != nil { + redisCl := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "love", + }) + + status := redisCl.Ping(ctxApp) + if status.Err() != nil { log.Error(err.Error()) return } - sm := session.New(log, ramrepo.NewRamSessionRepo(db)) + sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) userCase := user.New(log, userRepo.NewUserRepoPG(pool)) pinCase := pin.New(log, pinRepo.NewPinRepoPG(pool)) diff --git a/internal/pkg/repository/session/repo.go b/internal/pkg/repository/session/repo.go index 2dacb7a..36cf9ca 100644 --- a/internal/pkg/repository/session/repo.go +++ b/internal/pkg/repository/session/repo.go @@ -2,13 +2,66 @@ package session import ( "context" + "errors" + "fmt" + "time" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + redis "github.com/redis/go-redis/v9" ) +var ErrMethodUnimplemented = errors.New("unimplemented") +var ErrExistsSession = errors.New("the session already exists") + type Repository interface { AddSession(ctx context.Context, session *session.Session) error GetSessionByKey(ctx context.Context, key string) (*session.Session, error) DeleteSessionByKey(ctx context.Context, key string) error DeleteAllSessionForUser(ctx context.Context, userID int) error } + +type sessionRepo struct { + client *redis.Client +} + +func NewSessionRepo(client *redis.Client) *sessionRepo { + return &sessionRepo{client} +} + +func (s *sessionRepo) AddSession(ctx context.Context, session *session.Session) error { + res := s.client.SetNX(ctx, session.Key, session.UserID, time.Duration(session.Expire.Sub(time.Now().UTC()))) + if res.Err() != nil { + return fmt.Errorf("add session in storage: %w", res.Err()) + } + if !res.Val() { + return ErrExistsSession + } + return nil +} + +func (s *sessionRepo) GetSessionByKey(ctx context.Context, key string) (*session.Session, error) { + res := s.client.Get(ctx, key) + if res.Err() != nil { + return nil, fmt.Errorf("get session by key from storage: %w", res.Err()) + } + + var err error + sess := &session.Session{Key: key} + sess.UserID, err = res.Int() + if err != nil { + return nil, fmt.Errorf("bad value for session in storage: %w", err) + } + return sess, nil +} + +func (s *sessionRepo) DeleteSessionByKey(ctx context.Context, key string) error { + res := s.client.Del(ctx, key) + if res.Err() != nil { + return fmt.Errorf("delete session by key from storage: %w", res.Err()) + } + return nil +} + +func (s *sessionRepo) DeleteAllSessionForUser(ctx context.Context, userID int) error { + return ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/session/manager.go b/internal/pkg/usecase/session/manager.go index af67efc..482a123 100644 --- a/internal/pkg/usecase/session/manager.go +++ b/internal/pkg/usecase/session/manager.go @@ -56,9 +56,6 @@ func (sm *SessManager) GetUserIDBySessionKey(ctx context.Context, sessionKey str if err != nil { return 0, fmt.Errorf("getting a session by a manager: %w", err) } - if time.Now().UTC().After(session.Expire) { - return 0, ErrExpiredSession - } return session.UserID, nil } From 0bb79c7b563173c946f064511469a1b86c4dc3a9 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 16:54:26 +0300 Subject: [PATCH 101/266] dev2 update: hashing the password when it is changed --- internal/pkg/usecase/user/profile.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 3fb61b3..f908485 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -13,6 +13,7 @@ import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -70,7 +71,12 @@ func (u *userCase) EditProfileInfo(ctx context.Context, userID int, updateData * updateFields["surname"] = *updateData.Surname } if updateData.Password != nil { - updateFields["password"] = *updateData.Password + salt, err := crypto.NewRandomString(lenSalt) + if err != nil { + return fmt.Errorf("generating salt for registration: %w", err) + } + + updateFields["password"] = salt + crypto.PasswordHash(*updateData.Password, salt, lenPasswordHash) } err := u.repo.EditUserInfo(ctx, userID, updateFields) From ce380dd16deb2e5e045cace84cd05912ef39659c Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 16:55:16 +0300 Subject: [PATCH 102/266] dev2 add: method PUT in allowed --- internal/api/server/router/router.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index c1779de..ebc8345 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -25,8 +25,9 @@ func New() Router { func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.SessionManager, log *logger.Logger) { c := cors.New(cors.Options{ - AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443"}, - AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, + AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443", + "https://pinspire.online:1444", "https://pinspire.online:1445", "https://pinspire.online:1446"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut}, AllowCredentials: true, AllowedHeaders: []string{"content-type"}, }) From 58834db6e464b02867879b9d92744c84b88e1fa6 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 29 Oct 2023 20:44:55 +0300 Subject: [PATCH 103/266] TP-add_db update: add ERD-description --- db/normalized/ERD-description.md | 130 +++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 db/normalized/ERD-description.md diff --git a/db/normalized/ERD-description.md b/db/normalized/ERD-description.md new file mode 100644 index 0000000..4a78d7a --- /dev/null +++ b/db/normalized/ERD-description.md @@ -0,0 +1,130 @@ +```mermaid + erDiagram + PROFILE { + serial id PK + text username UK + text password + text email UK + text avatar + text name + text surname + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + TAG { + serial id PK + text title UK + timestamptz created_at + } + PIN { + serial id PK + integer author FK + text title + text description + text picture + boolean public + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + PIN_TAG { + integer pin_id PK,FK + integer tag_id PK,FK + timestamptz created_at + } + LIKE_PIN { + integer user_id PK,FK + integer pin_id PK,FK + timestamptz created_at + } + BOARD { + serial id PK + integer author FK + text title + text description + boolean public + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + BOARD_TAG { + integer board_id PK,FK + integer tag_id PK,FK + timestamptz created_at + } + SUBSCRIPTION_BOARD { + integer user_id PK,FK + integer board_id PK,FK + timestamptz created_at + } + MEMBERSHIP { + integer pin_id PK,FK + integer board_id PK,FK + timestamptz added_at + } + ROLE { + serial id PK + text name UK + } + CONTRIBUTOR { + integer board_id PK,FK + integer user_id PK,FK + integer role_id FK + timestamptz added_at + timestamptz updated_at + } + SUBSCRIPTION_USER { + integer who PK,FK + integer whom PK,FK + timestamptz created_at + + } + COMMENT { + serial id PK + integer author FK + integer pin_id FK + text content + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + LIKE_COMMENT { + integer user_id PK,FK + integer comment_id PK,FK + timestamptz created_at + } + MESSAGE { + serial id PK + integer from FK + integer to FK + text content + timestamptz created_at + timestamptz updated_at + timestamptz deleted_at + } + + PROFILE ||--o{ PIN : uploads + PROFILE ||--o{ BOARD : creates + PROFILE ||--o{ MESSAGE : writes + MESSAGE }o--|| PROFILE : addressed_to + PROFILE ||--o{ COMMENT : writes + PIN ||--o{ COMMENT : has + PROFILE ||--o{ LIKE_PIN : pushes + PIN ||--o{ LIKE_PIN : has + PROFILE ||--o{ LIKE_COMMENT : pushes + COMMENT ||--o{ LIKE_COMMENT : has + PROFILE ||--o{ SUBSCRIPTION_USER : makes + PROFILE ||--o{ SUBSCRIPTION_USER : has + BOARD ||--o{ MEMBERSHIP : has + PIN ||--o{ MEMBERSHIP : in + PROFILE ||--o{ SUBSCRIPTION_BOARD : makes + BOARD ||--o{ SUBSCRIPTION_BOARD : has + BOARD ||--o{ CONTRIBUTOR : has + PROFILE ||--o{ CONTRIBUTOR : is + CONTRIBUTOR }o..|| ROLE : has + PIN ||--o{ PIN_TAG : has + BOARD ||--o{ BOARD_TAG : has + TAG ||--o{ PIN_TAG : linked_to + TAG ||--o{ BOARD_TAG : linked_to +``` \ No newline at end of file From fcb44565ed2d842cab4e5abe94cc2ffc5db80eb7 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 29 Oct 2023 20:59:22 +0300 Subject: [PATCH 104/266] dev2 update: cors --- internal/api/server/router/router.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index ebc8345..8ab1980 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -24,17 +24,18 @@ func New() Router { } func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.SessionManager, log *logger.Logger) { + cfgCSRF := security.DefaultCSRFConfig() + cfgCSRF.PathToGet = "/api/v1/csrf" + c := cors.New(cors.Options{ AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443", "https://pinspire.online:1444", "https://pinspire.online:1445", "https://pinspire.online:1446"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut}, AllowCredentials: true, - AllowedHeaders: []string{"content-type"}, + AllowedHeaders: []string{"content-type", cfgCSRF.Header}, + ExposedHeaders: []string{cfgCSRF.HeaderSet}, }) - cfgCSRF := security.DefaultCSRFConfig() - cfgCSRF.PathToGet = "/api/v1/csrf" - r.Mux.Use(c.Handler, security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { From a161d6a196970f7da5e7b945e8a6fd1b92996d0a Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Mon, 30 Oct 2023 15:39:18 +0300 Subject: [PATCH 105/266] TP-fcc_handlers_board update: add board repository, usecase, moved general validation in /pkg, corrected old tests --- internal/app/app.go | 7 +- internal/pkg/delivery/http/v1/auth.go | 3 +- internal/pkg/delivery/http/v1/auth_test.go | 8 +- internal/pkg/delivery/http/v1/board.go | 14 +++ internal/pkg/delivery/http/v1/handler.go | 21 ++-- internal/pkg/delivery/http/v1/pin_test.go | 2 +- internal/pkg/delivery/http/v1/profile.go | 24 ++--- internal/pkg/delivery/http/v1/validation.go | 87 ++--------------- internal/pkg/entity/board/board.go | 14 +++ internal/pkg/repository/board/queries.go | 5 + internal/pkg/repository/board/repo.go | 103 ++++++++++++++++++++ internal/pkg/repository/board/tag.go | 34 +++++++ internal/pkg/usecase/board/create.go | 29 ++++++ internal/pkg/usecase/board/usecase.go | 32 ++++++ internal/pkg/usecase/board/validation.go | 36 +++++++ pkg/validation/validation.go | 77 +++++++++++++++ 16 files changed, 388 insertions(+), 108 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/board.go create mode 100644 internal/pkg/entity/board/board.go create mode 100644 internal/pkg/repository/board/queries.go create mode 100644 internal/pkg/repository/board/repo.go create mode 100644 internal/pkg/repository/board/tag.go create mode 100644 internal/pkg/usecase/board/create.go create mode 100644 internal/pkg/usecase/board/usecase.go create mode 100644 internal/pkg/usecase/board/validation.go create mode 100644 pkg/validation/validation.go diff --git a/internal/app/app.go b/internal/app/app.go index f91ee4b..fb031df 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -8,9 +8,11 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -18,7 +20,7 @@ import ( ) func Run(ctx context.Context, log *log.Logger, configFile string) { - pool, err := pgxpool.New(ctx, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") + pool, err := pgxpool.New(ctx, "postgres://ond_team:love@localhost:5433/pinspire?search_path=pinspire") if err != nil { log.Error(err.Error()) return @@ -40,8 +42,9 @@ func Run(ctx context.Context, log *log.Logger, configFile string) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, userRepo.NewUserRepoPG(pool)) pinCase := pin.New(log, pinRepo.NewPinRepoPG(pool)) + boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool)) - handler := deliveryHTTP.New(log, sm, userCase, pinCase) + handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase) cfgServ, err := server.NewConfig(configFile) if err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index e981086..3d4e3cd 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -9,6 +9,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validation" ) // Login godoc @@ -70,7 +71,7 @@ func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { return } - if !isValidPassword(params.Password) || !isValidUsername(params.Username) { + if !validation.IsValidPassword(params.Password) || !validation.IsValidUsername(params.Username) { h.log.Info("invalid credentials") err = responseError(w, "invalid_credentials", "invalid user credentials") if err != nil { diff --git a/internal/pkg/delivery/http/v1/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go index 6f35a78..9fbdae5 100644 --- a/internal/pkg/delivery/http/v1/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -38,7 +38,7 @@ func TestCheckLogin(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) url := "https://domain.test:8080/api/v1/login" goodCases := []struct { @@ -144,7 +144,7 @@ func TestLogin(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string @@ -280,7 +280,7 @@ func TestSignUp(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string @@ -396,7 +396,7 @@ func TestLogout(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go new file mode 100644 index 0000000..b0f72d6 --- /dev/null +++ b/internal/pkg/delivery/http/v1/board.go @@ -0,0 +1,14 @@ +package v1 + +import ( + "net/http" +) + +func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { + /* + 1. достаем user_id из контекста запроса + 2. парсим тело в json, проверяем парсинг + 3. вызываем UseCase, проверяем ошибку + 4. формируем ответ: заголовок content-type, body ответа + */ +} diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index ab66db9..2500fb1 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -8,17 +9,19 @@ import ( ) type HandlerHTTP struct { - log *logger.Logger - userCase user.Usecase - pinCase pin.Usecase - sm session.SessionManager + log *logger.Logger + userCase user.Usecase + pinCase pin.Usecase + boardCase board.Usecase + sm session.SessionManager } -func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase) *HandlerHTTP { +func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, board board.Usecase) *HandlerHTTP { return &HandlerHTTP{ - log: log, - userCase: user, - pinCase: pin, - sm: sm, + log: log, + userCase: user, + pinCase: pin, + boardCase: board, + sm: sm, } } diff --git a/internal/pkg/delivery/http/v1/pin_test.go b/internal/pkg/delivery/http/v1/pin_test.go index 058651a..dce223f 100644 --- a/internal/pkg/delivery/http/v1/pin_test.go +++ b/internal/pkg/delivery/http/v1/pin_test.go @@ -24,7 +24,7 @@ func TestGetPins(t *testing.T) { defer db.Close() pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) - service := New(log, nil, nil, pinCase) + service := New(log, nil, nil, pinCase, nil) rawUrl := "https://domain.test:8080/api/v1/pin" goodCases := []struct { diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 3aadb14..6dc23fe 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -8,6 +8,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validation" ) func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { @@ -28,22 +29,21 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { } return } - - invalidFields := new(errorFields) - if data.Username != nil && !isValidUsername(*data.Username) { - invalidFields.addInvalidField("username") + invalidFields := new(validation.ErrorFields) + if data.Username != nil && !validation.IsValidUsername(*data.Username) { + invalidFields.AddInvalidField("username") } - if data.Email != nil && !isValidEmail(*data.Email) { - invalidFields.addInvalidField("email") + if data.Email != nil && !validation.IsValidEmail(*data.Email) { + invalidFields.AddInvalidField("email") } - if data.Name != nil && !isValidName(*data.Name) { - invalidFields.addInvalidField("name") + if data.Name != nil && !validation.IsValidName(*data.Name) { + invalidFields.AddInvalidField("name") } - if data.Surname != nil && !isValidSurname(*data.Surname) { - invalidFields.addInvalidField("surname") + if data.Surname != nil && !validation.IsValidSurname(*data.Surname) { + invalidFields.AddInvalidField("surname") } - if data.Password != nil && !isValidPassword(*data.Password) { - invalidFields.addInvalidField("password") + if data.Password != nil && !validation.IsValidPassword(*data.Password) { + invalidFields.AddInvalidField("password") } if invalidFields.Err() != nil { err = responseError(w, "invalid_params", err.Error()) diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index 085fe42..9d5d18a 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -4,30 +4,11 @@ import ( "fmt" "net/url" "strconv" - "strings" - "unicode" - valid "github.com/asaskevich/govalidator" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + valid "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validation" ) -type errorFields []string - -func (b *errorFields) Error() string { - return strings.Join(*b, ",") -} - -func (b *errorFields) addInvalidField(fieldName string) { - *b = append(*b, fieldName) -} - -func (b *errorFields) Err() error { - if len(*b) == 0 { - return nil - } - return b -} - func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { if param := u.Query().Get("count"); len(param) > 0 { c, err := strconv.ParseInt(param, 10, 64) @@ -52,69 +33,17 @@ func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { } func IsValidUserForRegistration(user *user.User) error { - invalidFields := new(errorFields) + invalidFields := new(valid.ErrorFields) - if !isValidPassword(user.Password) { - invalidFields.addInvalidField("password") + if !valid.IsValidPassword(user.Password) { + invalidFields.AddInvalidField("password") } - if !isValidEmail(user.Email) { - invalidFields.addInvalidField("email") + if !valid.IsValidEmail(user.Email) { + invalidFields.AddInvalidField("email") } - if !isValidUsername(user.Username) { - invalidFields.addInvalidField("username") + if !valid.IsValidUsername(user.Username) { + invalidFields.AddInvalidField("username") } return invalidFields.Err() } - -func isValidUsername(username string) bool { - if len(username) < 4 || len(username) > 50 { - return false - } - for _, r := range username { - if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { - return false - } - } - return true -} - -func isValidEmail(email string) bool { - return valid.IsEmail(email) && len(email) <= 50 -} - -func isValidPassword(password string) bool { - if len(password) < 8 || len(password) > 50 { - return false - } - for _, r := range password { - if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { - return false - } - } - return true -} - -func isValidName(name string) bool { - if len(name) > 50 { - return false - } - for _, r := range name { - if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { - return false - } - } - return true -} - -func isValidSurname(surname string) bool { - if len(surname) > 50 { - return false - } - for _, r := range surname { - if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { - return false - } - } - return true -} diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go new file mode 100644 index 0000000..b3bceaf --- /dev/null +++ b/internal/pkg/entity/board/board.go @@ -0,0 +1,14 @@ +package board + +import "time" + +type Board struct { + ID int + AuthorID int + Title string + Description string + Public bool + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time +} diff --git a/internal/pkg/repository/board/queries.go b/internal/pkg/repository/board/queries.go new file mode 100644 index 0000000..4dc71a7 --- /dev/null +++ b/internal/pkg/repository/board/queries.go @@ -0,0 +1,5 @@ +package board + +const ( + CreateBoardQuery = "INSERT INTO board (author, title, description, public) VALUES ($1 $2 $3 $4) RETURNING id;" +) diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go new file mode 100644 index 0000000..47e1dd7 --- /dev/null +++ b/internal/pkg/repository/board/repo.go @@ -0,0 +1,103 @@ +package board + +import ( + "context" + "fmt" + "strconv" + + "github.com/Masterminds/squirrel" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type Repository interface { + CreateBoard(ctx context.Context, board entity.Board, pinIDs []int, tagTitles []string) error + // CreateBoardMembershipByPinIDs(ctx context.Context, pinIDs []int) error + // SelectOwnBoardsByUserID(ctx context.Context, userID int) ([]entity.Board, error) + // SelectUserBoardsByUserID(ctx context.Context, userID int) ([]entity.Board, error) + // SelectBoardsByTitle(ctx context.Context, title string) ([]entity.Board, error) + // SelctBoardsByTag(ctx context.Context, tagTitle string) ([]entity.Board, error) + // SelectBoardTags(ctx context.Context, boardID int) (tagTitles []string, err error) +} + +type BoardRepoPG struct { + db *pgxpool.Pool + sqlBuilder squirrel.StatementBuilderType +} + +func NewBoardRepoPG(db *pgxpool.Pool) *BoardRepoPG { + return &BoardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} +} + +func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, pinIDs []int, tagTitles []string) error { + + tx, err := repo.db.Begin(ctx) + if err != nil { + return fmt.Errorf("starting transaction for creating new board: %w", err) + } + + newBoardId, err := repo.insertBoard(ctx, tx, board) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("inserting board within transaction: %w", err) + } + + err = repo.insertTags(ctx, tx, tagTitles) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("inserting new tags within transaction: %w", err) + } + + err = repo.addTagsToBoard(ctx, tx, tagTitles, newBoardId, true) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("adding new tags on board within transaction: %w", err) + } + + tx.Commit(ctx) + + return nil +} + +func (repo *BoardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { + row := tx.QueryRow(ctx, CreateBoardQuery, board.AuthorID, board.Title, board.Description, board.Public) + + var newBoardID int + err := row.Scan(&newBoardID) + if err != nil { + return 0, fmt.Errorf("scan result of insterting new board: %w", err) + } + return newBoardID, nil +} + +func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitles []string, boardID int, isNewBoard bool) error { + addTagsToBoardQuery := repo.sqlBuilder. + Insert("board_tag"). + Columns("board_id", "tag_id"). + Select( + squirrel.Select(strconv.FormatInt(int64(boardID), 10), "id"). + From("tag"). + Where(squirrel.Eq{"title": tagTitles}), + ) + + if !isNewBoard { + addTagsToBoardQuery.Suffix("ON CONFLICT DO NOTHING") + } + + sqlRow, args, err := addTagsToBoardQuery.ToSql() + if err != nil { + return fmt.Errorf("building sql query row for adding tags to board: %w", err) + } + + cmdTag, err := tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("execute sql query to add tags to board: %w", err) + } + + if cmdTag.RowsAffected() != int64(len(tagTitles)) { + return fmt.Errorf("not all tags were inserted correctly") + } + + return nil +} diff --git a/internal/pkg/repository/board/tag.go b/internal/pkg/repository/board/tag.go new file mode 100644 index 0000000..d56806d --- /dev/null +++ b/internal/pkg/repository/board/tag.go @@ -0,0 +1,34 @@ +package board + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" +) + +func (repo *BoardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []string) error { + insertTagsQuery := repo.sqlBuilder. + Insert("tag"). + Columns("title") + for _, title := range titles { + insertTagsQuery = insertTagsQuery.Values(title) + } + sqlRow, args, err := insertTagsQuery. + Suffix("ON CONFLICT (title) DO NOTHING"). + Suffix("RETURNING id"). + ToSql() + if err != nil { + return fmt.Errorf("build sql row query while inserting tags: %w", err) + } + + cmdTag, err := tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("making insertTags query: %w", err) + } + if cmdTag.RowsAffected() != int64(len(titles)) { + return fmt.Errorf("checking rows affected after insertTags: %w", err) + } + + return nil +} diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go new file mode 100644 index 0000000..8f6c438 --- /dev/null +++ b/internal/pkg/usecase/board/create.go @@ -0,0 +1,29 @@ +package board + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" +) + +func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, createBoardObj CreateBoard) error { + if !isValidBoardTitle(createBoardObj.Title) { + return fmt.Errorf("invalid board title") + } + if !isValidTagTitles(createBoardObj.TagTitles) { + return fmt.Errorf("invalid board tag titles") + } + + err := bCase.BoardRepo.CreateBoard(ctx, board.Board{ + AuthorID: createBoardObj.AuthorID, + Title: createBoardObj.Title, + Description: createBoardObj.Description, + Public: createBoardObj.Public, + }, createBoardObj.PinIDs, createBoardObj.TagTitles) + + if err != nil { + return fmt.Errorf("create new board usecase: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go new file mode 100644 index 0000000..cece80a --- /dev/null +++ b/internal/pkg/usecase/board/usecase.go @@ -0,0 +1,32 @@ +package board + +import ( + "context" + + boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type CreateBoard struct { + Title string `json:"title" example:"Sunny places"` + Description string `json:"description" example:"long description"` + AuthorID int `json:"author_id" example:"45"` + Public bool `json:"public" example:"true"` + PinIDs []int `json:"pin_ids" example:"[1, 2, 3]"` + TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` +} //@name Board + +type Usecase interface { + CreateNewBoard(ctx context.Context, createBoardObj CreateBoard) error + // GetOwnBoards() + // GetUserBoards() +} + +type BoardUsecase struct { + log *logger.Logger + BoardRepo boardRepo.Repository +} + +func New(logger *logger.Logger, boardRepo boardRepo.Repository) *BoardUsecase { + return &BoardUsecase{log: logger, BoardRepo: boardRepo} +} diff --git a/internal/pkg/usecase/board/validation.go b/internal/pkg/usecase/board/validation.go new file mode 100644 index 0000000..9208a64 --- /dev/null +++ b/internal/pkg/usecase/board/validation.go @@ -0,0 +1,36 @@ +package board + +import "unicode" + +func isValidTagTitle(title string) bool { + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym)) { + return false + } + } + return true +} + +func isValidTagTitles(titles []string) bool { + if len(titles) > 7 { + return false + } + for _, title := range titles { + if !isValidTagTitle(title) { + return false + } + } + return true +} + +func isValidBoardTitle(title string) bool { + if len(title) < 4 || len(title) > 50 { + return false + } + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym)) { + return false + } + } + return true +} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go new file mode 100644 index 0000000..bb92a53 --- /dev/null +++ b/pkg/validation/validation.go @@ -0,0 +1,77 @@ +package validation + +import ( + "strings" + "unicode" + + valid "github.com/asaskevich/govalidator" +) + +type ErrorFields []string + +func (b *ErrorFields) Error() string { + return strings.Join(*b, ",") +} + +func (b *ErrorFields) AddInvalidField(fieldName string) { + *b = append(*b, fieldName) +} + +func (b *ErrorFields) Err() error { + if len(*b) == 0 { + return nil + } + return b +} + +func IsValidUsername(username string) bool { + if len(username) < 4 || len(username) > 50 { + return false + } + for _, r := range username { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} + +func IsValidEmail(email string) bool { + return valid.IsEmail(email) && len(email) <= 50 +} + +func IsValidPassword(password string) bool { + if len(password) < 8 || len(password) > 50 { + return false + } + for _, r := range password { + if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} + +func IsValidName(name string) bool { + if len(name) > 50 { + return false + } + for _, r := range name { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} + +func IsValidSurname(surname string) bool { + if len(surname) > 50 { + return false + } + for _, r := range surname { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} From 6611b1f2da1e48b9ce05dda36df1e40f5eaecdc8 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 30 Oct 2023 22:13:59 +0300 Subject: [PATCH 106/266] TP-aad add: attribute amout_me for profile relation --- db/migrations/001_relations.sql | 1 + db/normalized/ERD-description.md | 1 + db/normalized/relations.md | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/db/migrations/001_relations.sql b/db/migrations/001_relations.sql index 7b53a51..c25ce2a 100644 --- a/db/migrations/001_relations.sql +++ b/db/migrations/001_relations.sql @@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS profile ( avatar text NOT NULL DEFAULT 'default-avatar.png', name text, surname text, + about_me text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, diff --git a/db/normalized/ERD-description.md b/db/normalized/ERD-description.md index 4a78d7a..284f34e 100644 --- a/db/normalized/ERD-description.md +++ b/db/normalized/ERD-description.md @@ -8,6 +8,7 @@ text avatar text name text surname + text about_me timestamptz created_at timestamptz updated_at timestamptz deleted_at diff --git a/db/normalized/relations.md b/db/normalized/relations.md index 9bb0d88..60732e5 100644 --- a/db/normalized/relations.md +++ b/db/normalized/relations.md @@ -36,9 +36,9 @@ ## Описание функциональных зависимостей #### Relation [profile](#profile): -{id} -> {username, email, password, avatar, name, surname, created_at, updated_at, deleted_at} -{username} -> {id, email, password, avatar, name, surname, created_at, updated_at, deleted_at} -{email} -> {id, username, password, avatar, name, surname, created_at, updated_at, deleted_at} +{id} -> {username, email, password, avatar, name, surname, about_me, created_at, updated_at, deleted_at} +{username} -> {id, email, password, avatar, name, surname, about_me, created_at, updated_at, deleted_at} +{email} -> {id, username, password, avatar, name, surname, about_me, created_at, updated_at, deleted_at} #### Relation [tag](#tag): {id} -> {title, created_at} From 99a016ff056c3fc4b9c9a39192bea109dc89afa7 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 30 Oct 2023 23:09:58 +0300 Subject: [PATCH 107/266] TP-aad add: migrations 003_alter_relations.sql --- db/migrations/001_relations.sql | 1 - db/migrations/003_alter_relations.sql | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 db/migrations/003_alter_relations.sql diff --git a/db/migrations/001_relations.sql b/db/migrations/001_relations.sql index c25ce2a..7b53a51 100644 --- a/db/migrations/001_relations.sql +++ b/db/migrations/001_relations.sql @@ -12,7 +12,6 @@ CREATE TABLE IF NOT EXISTS profile ( avatar text NOT NULL DEFAULT 'default-avatar.png', name text, surname text, - about_me text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz, diff --git a/db/migrations/003_alter_relations.sql b/db/migrations/003_alter_relations.sql new file mode 100644 index 0000000..d045ef4 --- /dev/null +++ b/db/migrations/003_alter_relations.sql @@ -0,0 +1,8 @@ +SET search_path TO pinspire; + +ALTER TABLE profile ALTER COLUMN avatar +SET DEFAULT 'https://pinspire.online:8081/upload/avatars/default-avatar.png'; + +UPDATE profile SET avatar = DEFAULT WHERE avatar = 'default-avatar.png'; + +ALTER TABLE profile ADD COLUMN about_me text; From 19c6549f31b9fe6a16af9c246ae0a85bbdd1fb96 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 30 Oct 2023 23:38:11 +0300 Subject: [PATCH 108/266] TP-5b0 add: attribute AboutMe --- internal/pkg/delivery/http/v1/profile.go | 3 +++ internal/pkg/delivery/http/v1/validation.go | 12 ++++++++++++ internal/pkg/entity/user/user.go | 1 + internal/pkg/repository/user/queries.go | 2 +- internal/pkg/repository/user/repo.go | 2 +- internal/pkg/usecase/user/info.go | 1 + internal/pkg/usecase/user/profile.go | 3 +++ 7 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 3aadb14..b55665a 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -42,6 +42,9 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { if data.Surname != nil && !isValidSurname(*data.Surname) { invalidFields.addInvalidField("surname") } + if data.AboutMe != nil && !isValidAboutMe(*data.AboutMe) { + invalidFields.addInvalidField("about_me") + } if data.Password != nil && !isValidPassword(*data.Password) { invalidFields.addInvalidField("password") } diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index 085fe42..8df3c22 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -118,3 +118,15 @@ func isValidSurname(surname string) bool { } return true } + +func isValidAboutMe(info string) bool { + if len(info) > 500 { + return false + } + for _, r := range info { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + return false + } + } + return true +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 186ea9e..97e300c 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -9,5 +9,6 @@ type User struct { Surname pgtype.Text `json:"surname" example:"Green"` Email string `json:"email" example:"digital@gmail.com"` Avatar string `json:"avatar" example:"pinspire.online/avatars/avatar.jpg"` + AboutMe pgtype.Text `json:"about_me"` Password string `json:"password,omitempty" example:"pass123"` } // @name User diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go index 88af57c..05fcf4f 100644 --- a/internal/pkg/repository/user/queries.go +++ b/internal/pkg/repository/user/queries.go @@ -5,7 +5,7 @@ var ( SelectAuthByUsername = "SELECT id, password, email FROM profile WHERE username = $1;" SelectUsernameAndAvatar = "SELECT username, avatar FROM profile WHERE id = $1;" - SelectUserDataExceptPassword = "SELECT username, email, avatar, name, surname FROM profile WHERE id = $1;" + SelectUserDataExceptPassword = "SELECT username, email, avatar, name, surname, about_me FROM profile WHERE id = $1;" UpdateAvatarProfile = "UPDATE profile SET avatar = $1 WHERE id = $2;" ) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index fecea91..c93f744 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -67,7 +67,7 @@ func (u *userRepoPG) EditUserAvatar(ctx context.Context, userID int, avatar stri func (u *userRepoPG) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { row := u.db.QueryRow(ctx, SelectUserDataExceptPassword, userID) user := &user.User{ID: userID} - err := row.Scan(&user.Username, &user.Email, &user.Avatar, &user.Name, &user.Surname) + err := row.Scan(&user.Username, &user.Email, &user.Avatar, &user.Name, &user.Surname, &user.AboutMe) if err != nil { return nil, fmt.Errorf("get user info by id in storage: %w", err) } diff --git a/internal/pkg/usecase/user/info.go b/internal/pkg/usecase/user/info.go index c0ebdf6..4420dea 100644 --- a/internal/pkg/usecase/user/info.go +++ b/internal/pkg/usecase/user/info.go @@ -5,6 +5,7 @@ type profileUpdateData struct { Email *string Name *string Surname *string + AboutMe *string `json:"about_me` Password *string } diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 0233809..ec4649a 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -69,6 +69,9 @@ func (u *userCase) EditProfileInfo(ctx context.Context, userID int, updateData * if updateData.Surname != nil { updateFields["surname"] = *updateData.Surname } + if updateData.AboutMe != nil { + updateFields["about_me"] = *updateData.AboutMe + } if updateData.Password != nil { updateFields["password"] = *updateData.Password } From 7c99937de68e487186901813129c67a507455a20 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Tue, 31 Oct 2023 13:33:10 +0300 Subject: [PATCH 109/266] TP-e3a update: select pins from tape --- internal/pkg/delivery/http/v1/pin.go | 11 ++++---- internal/pkg/delivery/http/v1/validation.go | 25 ++++++++++++------- .../pkg/delivery/http/v1/validation_test.go | 4 +-- internal/pkg/repository/pin/queries.go | 2 +- internal/pkg/repository/pin/repo.go | 10 ++++---- internal/pkg/repository/ramrepo/pin.go | 8 +++--- internal/pkg/usecase/pin/usecase.go | 10 ++++---- 7 files changed, 39 insertions(+), 31 deletions(-) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 036587d..4e77b87 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -34,17 +34,18 @@ func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { h.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) SetContentTypeJSON(w) - count, lastID, err := FetchValidParamForLoadTape(r.URL) + count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) if err != nil { h.log.Info("parse url query params", log.F{"error", err.Error()}) err = responseError(w, "bad_params", "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)") } else { - h.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) - pins, last := h.pinCase.SelectNewPins(r.Context(), count, lastID) + h.log.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + pins, minID, maxID := h.pinCase.SelectNewPins(r.Context(), count, minID, maxID) err = responseOk(w, "pins received are sorted by id", map[string]any{ - "pins": pins, - "lastID": last, + "pins": pins, + "minID": minID, + "maxID": maxID, }) } if err != nil { diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index 4de0386..8763cd3 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -35,25 +35,32 @@ func (b *errorFields) Err() error { return b } -func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { +func FetchValidParamForLoadTape(u *url.URL) (count int, minID int, maxID int, err error) { if param := u.Query().Get("count"); len(param) > 0 { c, err := strconv.ParseInt(param, 10, 64) if err != nil { - return 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) + return 0, 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) } count = int(c) } else { - return 0, 0, ErrCountParameterMissing + return 0, 0, 0, ErrCountParameterMissing } - if param := u.Query().Get("lastID"); len(param) > 0 { - last, err := strconv.ParseInt(param, 10, 64) + if param := u.Query().Get("minID"); len(param) > 0 { + id, err := strconv.ParseInt(param, 10, 64) if err != nil { - return 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) + return 0, 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) } - lastID = int(last) + minID = int(id) } - if count <= 0 || count > 1000 || lastID < 0 { - return 0, 0, ErrBadParams + if param := u.Query().Get("maxID"); len(param) > 0 { + id, err := strconv.ParseInt(param, 10, 64) + if err != nil { + return 0, 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) + } + maxID = int(id) + } + if count <= 0 || count > 1000 || minID < 0 || maxID < 0 { + return 0, 0, 0, ErrBadParams } return } diff --git a/internal/pkg/delivery/http/v1/validation_test.go b/internal/pkg/delivery/http/v1/validation_test.go index ae50168..12e0263 100644 --- a/internal/pkg/delivery/http/v1/validation_test.go +++ b/internal/pkg/delivery/http/v1/validation_test.go @@ -29,7 +29,7 @@ func TestFetchValidParams(t *testing.T) { if err != nil { t.Fatalf("error when parsing into the url.URL structure: %v", err) } - actualCount, actualLastID, err := FetchValidParamForLoadTape(URL) + actualCount, actualLastID, _, err := FetchValidParamForLoadTape(URL) require.NoError(t, err) require.Equal(t, test.wantCount, actualCount) require.Equal(t, test.wantLastID, actualLastID) @@ -59,7 +59,7 @@ func TestErrorFetchValidParams(t *testing.T) { if err != nil { t.Fatalf("error when parsing into the url.URL structure: %v", err) } - actualCount, actualLastID, err := FetchValidParamForLoadTape(URL) + actualCount, actualLastID, _, err := FetchValidParamForLoadTape(URL) require.ErrorIs(t, err, test.wantErr) require.Equal(t, 0, actualCount) require.Equal(t, 0, actualLastID) diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 1ca7898..85983ff 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -1,7 +1,7 @@ package pin var ( - SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id > $1 ORDER BY created_at DESC, id LIMIT $2;" + SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id < $2 OR id > $1 ORDER BY id DESC LIMIT $3;" InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 4da5012..281519e 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -15,7 +15,7 @@ import ( type S map[string]any type Repository interface { - GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]entity.Pin, error) + GetSortedNPinsAfterID(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error @@ -36,10 +36,10 @@ func NewPinRepoPG(db *pgxpool.Pool) *pinRepoPG { } } -func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]entity.Pin, error) { - rows, err := p.db.Query(ctx, SelectAfterIdWithLimit, afterPinID, count) +func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count, minID, maxID int) ([]entity.Pin, error) { + rows, err := p.db.Query(ctx, SelectAfterIdWithLimit, minID, maxID, count) if err != nil { - return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) + return nil, fmt.Errorf("select to receive %d pins: %w", count, err) } pins := make([]entity.Pin, 0, count) @@ -47,7 +47,7 @@ func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count int, afterP for rows.Next() { err := rows.Scan(&pin.ID, &pin.Picture) if err != nil { - return pins, fmt.Errorf("scan to receive %d pins after %d: %w", count, afterPinID, err) + return pins, fmt.Errorf("scan to receive %d pins: %w", count, err) } pins = append(pins, pin) } diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index d049c89..b4c3c6a 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -18,10 +18,10 @@ func NewRamPinRepo(db *sql.DB) *ramPinRepo { return &ramPinRepo{db} } -func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count int, afterPinID int) ([]pin.Pin, error) { - rows, err := r.db.QueryContext(ctx, "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;", afterPinID, count) +func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count, minID, maxID int) ([]pin.Pin, error) { + rows, err := r.db.QueryContext(ctx, "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;", maxID, count) if err != nil { - return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, afterPinID, err) + return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, maxID, err) } pins := make([]pin.Pin, 0, count) @@ -29,7 +29,7 @@ func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count int, after for rows.Next() { err := rows.Scan(&pin.ID, &pin.Picture) if err != nil { - return pins, fmt.Errorf("scan to receive %d pins after %d: %w", count, afterPinID, err) + return pins, fmt.Errorf("scan to receive %d pins after %d: %w", count, minID, err) } pins = append(pins, pin) } diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 0cbf1aa..b244993 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -18,7 +18,7 @@ import ( var ErrBadMIMEType = errors.New("bad mime type") type Usecase interface { - SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) + SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) error @@ -35,15 +35,15 @@ func New(log *log.Logger, repo repo.Repository) *pinCase { return &pinCase{log, repo} } -func (p *pinCase) SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) { - pins, err := p.repo.GetSortedNPinsAfterID(ctx, count, lastID) +func (p *pinCase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) { + pins, err := p.repo.GetSortedNPinsAfterID(ctx, count, minID, maxID) if err != nil { p.log.Error(err.Error()) } if len(pins) == 0 { - return []entity.Pin{}, lastID + return []entity.Pin{}, minID, maxID } - return pins, pins[len(pins)-1].ID + return pins, pins[len(pins)-1].ID, pins[0].ID } func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error { From 17095db7c2ca49c5cc1f8a55c2ed29d500ad2619 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 1 Nov 2023 16:29:36 +0300 Subject: [PATCH 110/266] TP-e3a update: select pin for tape --- internal/pkg/repository/pin/queries.go | 2 +- internal/pkg/repository/pin/repo.go | 6 +++--- internal/pkg/repository/ramrepo/pin.go | 2 +- internal/pkg/usecase/pin/usecase.go | 2 +- internal/pkg/usecase/pin/usecase_test.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 85983ff..da7eb5b 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -1,7 +1,7 @@ package pin var ( - SelectAfterIdWithLimit = "SELECT id, picture FROM pin WHERE id < $2 OR id > $1 ORDER BY id DESC LIMIT $3;" + SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 281519e..74153d3 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -15,7 +15,7 @@ import ( type S map[string]any type Repository interface { - GetSortedNPinsAfterID(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) + GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error @@ -36,8 +36,8 @@ func NewPinRepoPG(db *pgxpool.Pool) *pinRepoPG { } } -func (p *pinRepoPG) GetSortedNPinsAfterID(ctx context.Context, count, minID, maxID int) ([]entity.Pin, error) { - rows, err := p.db.Query(ctx, SelectAfterIdWithLimit, minID, maxID, count) +func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, error) { + rows, err := p.db.Query(ctx, SelectWithExcludeLimit, minID, maxID, count) if err != nil { return nil, fmt.Errorf("select to receive %d pins: %w", count, err) } diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index b4c3c6a..e0e6323 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -18,7 +18,7 @@ func NewRamPinRepo(db *sql.DB) *ramPinRepo { return &ramPinRepo{db} } -func (r *ramPinRepo) GetSortedNPinsAfterID(ctx context.Context, count, minID, maxID int) ([]pin.Pin, error) { +func (r *ramPinRepo) GetSortedNewNPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, error) { rows, err := r.db.QueryContext(ctx, "SELECT id, picture FROM pin WHERE id > $1 ORDER BY id LIMIT $2;", maxID, count) if err != nil { return nil, fmt.Errorf("select to receive %d pins after %d: %w", count, maxID, err) diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index b244993..39ff023 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -36,7 +36,7 @@ func New(log *log.Logger, repo repo.Repository) *pinCase { } func (p *pinCase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) { - pins, err := p.repo.GetSortedNPinsAfterID(ctx, count, minID, maxID) + pins, err := p.repo.GetSortedNewNPins(ctx, count, minID, maxID) if err != nil { p.log.Error(err.Error()) } diff --git a/internal/pkg/usecase/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go index fa58f07..a57cb33 100644 --- a/internal/pkg/usecase/pin/usecase_test.go +++ b/internal/pkg/usecase/pin/usecase_test.go @@ -41,7 +41,7 @@ func TestSelectNewPins(t *testing.T) { for _, tCase := range testCases { t.Run(tCase.name, func(t *testing.T) { - _, actualLastID := pinCase.SelectNewPins(context.Background(), tCase.count, tCase.lastID) + _, actualLastID, _ := pinCase.SelectNewPins(context.Background(), tCase.count, tCase.lastID, 0) require.Equal(t, tCase.expNewLastID, actualLastID) }) } From 4f2b0f95ae25231ff63c97701f93e79d1052fac4 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 2 Nov 2023 21:14:31 +0300 Subject: [PATCH 111/266] TP-e3a add: chek avalability pins --- internal/api/server/router/router.go | 7 ++- .../delivery/http/v1/{like.go => like_pin.go} | 0 internal/pkg/delivery/http/v1/pin.go | 35 +++++++++++++- internal/pkg/entity/pin/pin.go | 34 ++++++++++--- internal/pkg/entity/pin/tag.go | 4 +- internal/pkg/repository/pin/availability.go | 16 +++++++ internal/pkg/repository/pin/like.go | 10 ++++ internal/pkg/repository/pin/queries.go | 24 +++++++++- internal/pkg/repository/pin/repo.go | 16 +++++++ internal/pkg/repository/pin/tag.go | 19 ++++++++ internal/pkg/repository/ramrepo/pin.go | 16 +++++++ internal/pkg/usecase/pin/check.go | 48 +++++++++++++++++++ internal/pkg/usecase/pin/like.go | 8 +++- internal/pkg/usecase/pin/usecase.go | 23 +++++++++ 14 files changed, 243 insertions(+), 17 deletions(-) rename internal/pkg/delivery/http/v1/{like.go => like_pin.go} (100%) create mode 100644 internal/pkg/repository/pin/availability.go create mode 100644 internal/pkg/usecase/pin/check.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 8ab1980..e0f2b2d 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -44,9 +44,8 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Route("/auth", func(r chi.Router) { r.Post("/login", handler.Login) r.Post("/signup", handler.Signup) - r.Group(func(r chi.Router) { - r.Use(auth.RequireAuth) + r.With(auth.RequireAuth).Group(func(r chi.Router) { r.Get("/login", handler.CheckLogin) r.Delete("/logout", handler.Logout) }) @@ -60,9 +59,9 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Route("/pin", func(r chi.Router) { r.Get("/", handler.GetPins) - r.Group(func(r chi.Router) { - r.Use(auth.RequireAuth) + r.Get("/{pinID:\\d+}", handler.ViewPin) + r.With(auth.RequireAuth).Group(func(r chi.Router) { r.Post("/create", handler.CreateNewPin) r.Post("/like/{pinID:\\d+}", handler.SetLikePin) r.Put("/edit/{pinID:\\d+}", handler.EditPin) diff --git a/internal/pkg/delivery/http/v1/like.go b/internal/pkg/delivery/http/v1/like_pin.go similarity index 100% rename from internal/pkg/delivery/http/v1/like.go rename to internal/pkg/delivery/http/v1/like_pin.go diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 4e77b87..7e75fd9 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -85,9 +85,9 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { newPin.Tags = append(newPin.Tags, entity.Tag{Title: title}) } - newPin.Title = r.FormValue("title") + newPin.SetTitle(r.FormValue("title")) - newPin.Description = r.FormValue("description") + newPin.SetDescription(r.FormValue("description")) public := r.FormValue("public") @@ -188,3 +188,34 @@ func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { h.log.Error(err.Error()) } } + +func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on view pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + pinIdStr := chi.URLParam(r, "pinID") + pinID, err := strconv.ParseInt(pinIdStr, 10, 64) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "parse_url", "internal error") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + userID, ok := r.Context().Value(auth.KeyCurrentUserID).(int) + if !ok { + userID = usecase.UserUnknown + } + pin, err := h.pinCase.ViewAnPin(r.Context(), int(pinID), userID) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "edit_pin", "internal error") + } else { + err = responseOk(w, "pin was successfully received", pin) + } + if err != nil { + h.log.Error(err.Error()) + } +} diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 9d6e1f4..5d67970 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -1,11 +1,31 @@ package pin +import "github.com/jackc/pgx/v5/pgtype" + type Pin struct { - ID int `json:"id" example:"55"` - AuthorID int `json:"-" example:"23"` - Picture string `json:"picture" example:"pinspire/imgs/image.png"` - Title string `json:"-" example:"Nature's beauty"` - Description string `json:"-" example:"about face"` - Tags []Tag - Public bool + ID int `json:"id" example:"55"` + AuthorID int `json:"author" example:"23"` + Picture string `json:"picture" example:"pinspire/imgs/image.png"` + Title pgtype.Text `json:"-" example:"Nature's beauty"` + Description pgtype.Text `json:"-" example:"about face"` + Public bool `json:"public"` + + Tags []Tag `json:"tags"` + CountLike int `json:"count_likes"` + + DeletedAt pgtype.Timestamptz `json:"-"` } //@name Pin + +func (p *Pin) SetTitle(title string) { + p.Title = pgtype.Text{ + String: title, + Valid: true, + } +} + +func (p *Pin) SetDescription(description string) { + p.Description = pgtype.Text{ + String: description, + Valid: true, + } +} diff --git a/internal/pkg/entity/pin/tag.go b/internal/pkg/entity/pin/tag.go index e952f0c..475163c 100644 --- a/internal/pkg/entity/pin/tag.go +++ b/internal/pkg/entity/pin/tag.go @@ -1,6 +1,6 @@ package pin type Tag struct { - ID int - Title string + ID int `json:"-"` + Title string `json:"title"` } diff --git a/internal/pkg/repository/pin/availability.go b/internal/pkg/repository/pin/availability.go new file mode 100644 index 0000000..7dfc03d --- /dev/null +++ b/internal/pkg/repository/pin/availability.go @@ -0,0 +1,16 @@ +package pin + +import ( + "context" + "fmt" +) + +func (p *pinRepoPG) IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) { + row := p.db.QueryRow(ctx, SelectCheckAvailability, pinID, userID) + var check bool + err := row.Scan(&check) + if err != nil { + return false, fmt.Errorf("check available pin for user at the storage layer: %w", err) + } + return check, nil +} diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go index 208db44..f6a89e5 100644 --- a/internal/pkg/repository/pin/like.go +++ b/internal/pkg/repository/pin/like.go @@ -20,3 +20,13 @@ func (p *pinRepoPG) DelLike(ctx context.Context, pinID, userID int) error { } return nil } + +func (p *pinRepoPG) GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) { + row := p.db.QueryRow(ctx, SelectCountLikePin, pinID) + var count int + err := row.Scan(&count) + if err != nil { + return 0, fmt.Errorf("get count like by pin id: %w", err) + } + return count, nil +} diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index da7eb5b..efeb1da 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -2,8 +2,30 @@ package pin var ( SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" + SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" + SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" + SelectTagsByPinID = `SELECT tag.title FROM pin INNER JOIN pin_tag ON pin.id = pin_tag.pin_id + INNER JOIN tag ON pin_tag.tag_id = tag.id WHERE pin.id = $1;` + SelectCheckAvailability = `SELECT EXISTS (SELECT FROM pin INNER JOIN membership + ON pin.id = membership.pin_id INNER JOIN board + ON membership.board_id = board.id INNER JOIN contributor + ON board.id = contributor.board_id + WHERE pin.id = $1 AND (board.author = $2 OR contributor.user_id = $2));` - InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" + InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" + InsertLikePinFromUserAtomic = `INSERT INTO like_pin (pin_id, user_id) + SELECT $1, $2 WHERE ( + SELECT public OR author = $2 OR + EXISTS (SELECT FROM pin + INNER JOIN membership + ON pin.id = membership.pin_id + INNER JOIN board + ON membership.board_id = board.id + INNER JOIN contributor + ON board.id = contributor.board_id + WHERE contributor.user_id = $2 AND pin.id = $1) + FROM pin WHERE id = $1 + );` UpdatePinSetStatusDelete = "UPDATE pin SET deleted_at = now() WHERE id = $1 AND author = $2;" diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 74153d3..7deb1e6 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -17,11 +17,15 @@ type S map[string]any type Repository interface { GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) + GetPinByID(ctx context.Context, pinID int) (*entity.Pin, error) AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error SetLike(ctx context.Context, pinID, userID int) error DelLike(ctx context.Context, pinID, userID int) error EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error + GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) + GetTagsByPinID(ctx context.Context, pinID int) ([]entity.Tag, error) + IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) } type pinRepoPG struct { @@ -55,6 +59,18 @@ func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID i return pins, nil } +func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int) (*entity.Pin, error) { + row := p.db.QueryRow(ctx, SelectPinByID, pinID) + pin := &entity.Pin{} + err := row.Scan(&pin.AuthorID, &pin.Title, &pin.Description, + &pin.Picture, &pin.Public, &pin.DeletedAt) + if err != nil { + return nil, fmt.Errorf("get pin by id from storage: %w", err) + } + pin.ID = pinID + return pin, nil +} + func (p *pinRepoPG) AddNewPin(ctx context.Context, pin *entity.Pin) error { titles := fetchTitles(pin.Tags) diff --git a/internal/pkg/repository/pin/tag.go b/internal/pkg/repository/pin/tag.go index 507858e..e739fcf 100644 --- a/internal/pkg/repository/pin/tag.go +++ b/internal/pkg/repository/pin/tag.go @@ -14,6 +14,25 @@ import ( var ErrNumberAffectedRows = errors.New("different number of affected rows was expected") +func (p *pinRepoPG) GetTagsByPinID(ctx context.Context, pinID int) ([]pin.Tag, error) { + rows, err := p.db.Query(ctx, SelectTagsByPinID, pinID) + if err != nil { + return nil, fmt.Errorf("get pin tags by its id in storage: %w", err) + } + defer rows.Close() + + tags := []pin.Tag{} + tag := pin.Tag{} + for rows.Next() { + err = rows.Scan(&tag.Title) + if err != nil { + return tags, fmt.Errorf("scan title tag for get pin tags in storage: %w", err) + } + tags = append(tags, tag) + } + return tags, nil +} + func (p *pinRepoPG) addTags(ctx context.Context, tx pgx.Tx, titles []string) error { insertBuilder := p.sqlBuilder.Insert("tag").Columns("title") for _, title := range titles { diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index e0e6323..1e71b6b 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -64,3 +64,19 @@ func (r *ramPinRepo) EditPinTags(ctx context.Context, pinID, userID int, titlePi func (r *ramPinRepo) EditPin(ctx context.Context, pinID int, updateData repository.S, titleTags []string) error { return ErrMethodUnimplemented } + +func (r *ramPinRepo) GetPinByID(ctx context.Context, pinID int) (*pin.Pin, error) { + return nil, ErrMethodUnimplemented +} + +func (r *ramPinRepo) IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) { + return false, ErrMethodUnimplemented +} + +func (r *ramPinRepo) GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) { + return 0, ErrMethodUnimplemented +} + +func (r *ramPinRepo) GetTagsByPinID(ctx context.Context, pinID int) ([]pin.Tag, error) { + return nil, ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/pin/check.go b/internal/pkg/usecase/pin/check.go new file mode 100644 index 0000000..8ec917a --- /dev/null +++ b/internal/pkg/usecase/pin/check.go @@ -0,0 +1,48 @@ +package pin + +import ( + "context" + "errors" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" +) + +var ( + ErrPinNotAccess = errors.New("pin is not available") + ErrPinDeleted = errors.New("") +) + +const UserUnknown = -1 + +func (p *pinCase) isAvailablePinForViewingUser(ctx context.Context, pin *entity.Pin, userID int) error { + if pin.DeletedAt.Valid { + return ErrPinDeleted + } + + if pin.Public || pin.AuthorID == userID { + return nil + } + if userID == UserUnknown { + return ErrPinNotAccess + } + + ok, err := p.repo.IsAvailableToUserAsContributorBoard(ctx, pin.ID, userID) + if err != nil { + return fmt.Errorf("fail check available pin: %w", err) + } + + if !ok { + return ErrPinNotAccess + } + return nil +} + +func (p *pinCase) isAvailablePinForSetLike(ctx context.Context, pinID, userID int) error { + pin, err := p.repo.GetPinByID(ctx, pinID) + if err != nil { + return fmt.Errorf("get a pin to check for the availability of a like: %w", err) + } + + return p.isAvailablePinForViewingUser(ctx, pin, userID) +} diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go index c8391ae..072227f 100644 --- a/internal/pkg/usecase/pin/like.go +++ b/internal/pkg/usecase/pin/like.go @@ -1,8 +1,14 @@ package pin -import "context" +import ( + "context" + "fmt" +) func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) error { + if err := p.isAvailablePinForSetLike(ctx, pinID, userID); err != nil { + return fmt.Errorf("set like from user: %w", err) + } return p.repo.SetLike(ctx, pinID, userID) } diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 39ff023..d1778d8 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -24,6 +24,7 @@ type Usecase interface { SetLikeFromUser(ctx context.Context, pinID, userID int) error DeleteLikeFromUser(ctx context.Context, pinID, userID int) error EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error + ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) } type pinCase struct { @@ -84,3 +85,25 @@ func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io. func (p *pinCase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { return p.repo.DeletePin(ctx, pinID, userID) } + +func (p *pinCase) ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) { + pin, err := p.repo.GetPinByID(ctx, pinID) + if err != nil { + return nil, fmt.Errorf("get a pin to view: %w", err) + } + + if err := p.isAvailablePinForViewingUser(ctx, pin, userID); err != nil { + return nil, fmt.Errorf("view pin: %w", err) + } + + pin.CountLike, err = p.repo.GetCountLikeByPinID(ctx, pinID) + if err != nil { + p.log.Error(err.Error()) + } + pin.Tags, err = p.repo.GetTagsByPinID(ctx, pinID) + if err != nil { + p.log.Error(err.Error()) + } + + return pin, nil +} From 4fb258520898e3a64bc56f665315099473026126 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 2 Nov 2023 22:58:26 +0300 Subject: [PATCH 112/266] TP-e3a add: check action fix pin on board --- internal/pkg/repository/pin/queries.go | 2 +- internal/pkg/usecase/pin/check.go | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index efeb1da..88f4669 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -27,7 +27,7 @@ var ( FROM pin WHERE id = $1 );` - UpdatePinSetStatusDelete = "UPDATE pin SET deleted_at = now() WHERE id = $1 AND author = $2;" + UpdatePinSetStatusDelete = "UPDATE pin SET deleted_at = now() WHERE id = $1 AND author = $2 AND deleted_at IS NULL;" DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2;" DeleteAllTagsFromPin = "DELETE FROM pin_tag WHERE pin_id = $1;" diff --git a/internal/pkg/usecase/pin/check.go b/internal/pkg/usecase/pin/check.go index 8ec917a..0c018d1 100644 --- a/internal/pkg/usecase/pin/check.go +++ b/internal/pkg/usecase/pin/check.go @@ -9,12 +9,29 @@ import ( ) var ( - ErrPinNotAccess = errors.New("pin is not available") - ErrPinDeleted = errors.New("") + ErrPinNotAccess = errors.New("pin is not available") + ErrPinDeleted = errors.New("pin has been deleted") + ErrForbiddenAction = errors.New("this action is not available to the user") ) const UserUnknown = -1 +func (p *pinCase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { + pin, err := p.repo.GetPinByID(ctx, pinID) + if err != nil { + return err + } + + if pin.DeletedAt.Valid { + return ErrPinDeleted + } + if !pin.Public && pin.AuthorID != userID { + return ErrForbiddenAction + } + + return nil +} + func (p *pinCase) isAvailablePinForViewingUser(ctx context.Context, pin *entity.Pin, userID int) error { if pin.DeletedAt.Valid { return ErrPinDeleted From 833bc5594c08796d75b882b05d638502f3478d2b Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 3 Nov 2023 01:39:13 +0300 Subject: [PATCH 113/266] TP-e3a add: user info when get pin --- internal/pkg/delivery/http/v1/pin.go | 5 +++-- internal/pkg/entity/pin/pin.go | 10 ++++++--- internal/pkg/entity/user/user.go | 4 ++-- internal/pkg/repository/pin/queries.go | 12 ++++++----- internal/pkg/repository/pin/repo.go | 30 +++++++++++++++++++------- internal/pkg/repository/ramrepo/pin.go | 2 +- internal/pkg/usecase/pin/check.go | 8 +++---- internal/pkg/usecase/pin/usecase.go | 2 +- 8 files changed, 47 insertions(+), 26 deletions(-) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 7e75fd9..abf10a2 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -9,6 +9,7 @@ import ( chi "github.com/go-chi/chi/v5" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -75,8 +76,8 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { } defer r.Body.Close() - newPin := &entity.Pin{} - newPin.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) + newPin := &entity.Pin{Author: &user.User{}} + newPin.Author.ID = r.Context().Value(auth.KeyCurrentUserID).(int) tags := r.FormValue("tags") titles := strings.Split(tags, ",") diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index 5d67970..bef7532 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -1,16 +1,20 @@ package pin -import "github.com/jackc/pgx/v5/pgtype" +import ( + "github.com/jackc/pgx/v5/pgtype" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) type Pin struct { ID int `json:"id" example:"55"` - AuthorID int `json:"author" example:"23"` + Author *user.User `json:"author,omitempty" example:"23"` Picture string `json:"picture" example:"pinspire/imgs/image.png"` Title pgtype.Text `json:"-" example:"Nature's beauty"` Description pgtype.Text `json:"-" example:"about face"` Public bool `json:"public"` - Tags []Tag `json:"tags"` + Tags []Tag `json:"tags,omitempty"` CountLike int `json:"count_likes"` DeletedAt pgtype.Timestamptz `json:"-"` diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 97e300c..4b70349 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -3,11 +3,11 @@ package user import "github.com/jackc/pgx/v5/pgtype" type User struct { - ID int `json:"-" example:"123"` + ID int `json:"id" example:"123"` Username string `json:"username" example:"Green"` Name pgtype.Text `json:"name" example:"Peter"` Surname pgtype.Text `json:"surname" example:"Green"` - Email string `json:"email" example:"digital@gmail.com"` + Email string `json:"email,omitempty" example:"digital@gmail.com"` Avatar string `json:"avatar" example:"pinspire.online/avatars/avatar.jpg"` AboutMe pgtype.Text `json:"about_me"` Password string `json:"password,omitempty" example:"pass123"` diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 88f4669..b389bb4 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -1,11 +1,13 @@ package pin var ( - SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" - SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" - SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" - SelectTagsByPinID = `SELECT tag.title FROM pin INNER JOIN pin_tag ON pin.id = pin_tag.pin_id - INNER JOIN tag ON pin_tag.tag_id = tag.id WHERE pin.id = $1;` + SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" + SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" + SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" + SelectPinByIDWithAuthor = `SELECT author, title, description, picture, public, pin.deleted_at, username, avatar + FROM pin INNER JOIN profile ON author = profile.id WHERE pin.id = $1;` + SelectTagsByPinID = `SELECT tag.title FROM pin INNER JOIN pin_tag ON pin.id = pin_tag.pin_id + INNER JOIN tag ON pin_tag.tag_id = tag.id WHERE pin.id = $1;` SelectCheckAvailability = `SELECT EXISTS (SELECT FROM pin INNER JOIN membership ON pin.id = membership.pin_id INNER JOIN board ON membership.board_id = board.id INNER JOIN contributor diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 7deb1e6..8541cb2 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -17,7 +17,7 @@ type S map[string]any type Repository interface { GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) - GetPinByID(ctx context.Context, pinID int) (*entity.Pin, error) + GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error SetLike(ctx context.Context, pinID, userID int) error @@ -47,7 +47,7 @@ func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID i } pins := make([]entity.Pin, 0, count) - pin := entity.Pin{} + pin := entity.Pin{Public: true} for rows.Next() { err := rows.Scan(&pin.ID, &pin.Picture) if err != nil { @@ -59,14 +59,23 @@ func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID i return pins, nil } -func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int) (*entity.Pin, error) { - row := p.db.QueryRow(ctx, SelectPinByID, pinID) - pin := &entity.Pin{} - err := row.Scan(&pin.AuthorID, &pin.Title, &pin.Description, - &pin.Picture, &pin.Public, &pin.DeletedAt) +func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) { + pin := &entity.Pin{Author: &user.User{}} + var err error + if revealAuthor { + err = p.getPinByID(ctx, pinID, SelectPinByIDWithAuthor, + &pin.Author.ID, &pin.Title, &pin.Description, + &pin.Picture, &pin.Public, &pin.DeletedAt, + &pin.Author.Username, &pin.Author.Avatar) + } else { + err = p.getPinByID(ctx, pinID, SelectPinByID, + &pin.Author.ID, &pin.Title, &pin.Description, + &pin.Picture, &pin.Public, &pin.DeletedAt) + } if err != nil { return nil, fmt.Errorf("get pin by id from storage: %w", err) } + pin.ID = pinID return pin, nil } @@ -168,7 +177,7 @@ func (p *pinRepoPG) updateHeaderPin(ctx context.Context, tx pgx.Tx, pinID int, n func (p *pinRepoPG) addPin(ctx context.Context, tx pgx.Tx, pin *entity.Pin) (int, error) { sqlRow, args, err := p.sqlBuilder.Insert("pin"). Columns("author", "title", "description", "picture", "public"). - Values(pin.AuthorID, pin.Title, pin.Description, pin.Picture, pin.Public). + Values(pin.Author.ID, pin.Title, pin.Description, pin.Picture, pin.Public). Suffix("RETURNING id"). ToSql() if err != nil { @@ -183,3 +192,8 @@ func (p *pinRepoPG) addPin(ctx context.Context, tx pgx.Tx, pin *entity.Pin) (int } return pinID, nil } + +func (p *pinRepoPG) getPinByID(ctx context.Context, pinID int, query string, dest ...any) error { + row := p.db.QueryRow(ctx, query, pinID) + return row.Scan(dest...) +} diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 1e71b6b..58be15e 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -65,7 +65,7 @@ func (r *ramPinRepo) EditPin(ctx context.Context, pinID int, updateData reposito return ErrMethodUnimplemented } -func (r *ramPinRepo) GetPinByID(ctx context.Context, pinID int) (*pin.Pin, error) { +func (r *ramPinRepo) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*pin.Pin, error) { return nil, ErrMethodUnimplemented } diff --git a/internal/pkg/usecase/pin/check.go b/internal/pkg/usecase/pin/check.go index 0c018d1..d3e41cd 100644 --- a/internal/pkg/usecase/pin/check.go +++ b/internal/pkg/usecase/pin/check.go @@ -17,7 +17,7 @@ var ( const UserUnknown = -1 func (p *pinCase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { - pin, err := p.repo.GetPinByID(ctx, pinID) + pin, err := p.repo.GetPinByID(ctx, pinID, false) if err != nil { return err } @@ -25,7 +25,7 @@ func (p *pinCase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID if pin.DeletedAt.Valid { return ErrPinDeleted } - if !pin.Public && pin.AuthorID != userID { + if !pin.Public && pin.Author.ID != userID { return ErrForbiddenAction } @@ -37,7 +37,7 @@ func (p *pinCase) isAvailablePinForViewingUser(ctx context.Context, pin *entity. return ErrPinDeleted } - if pin.Public || pin.AuthorID == userID { + if pin.Public || pin.Author.ID == userID { return nil } if userID == UserUnknown { @@ -56,7 +56,7 @@ func (p *pinCase) isAvailablePinForViewingUser(ctx context.Context, pin *entity. } func (p *pinCase) isAvailablePinForSetLike(ctx context.Context, pinID, userID int) error { - pin, err := p.repo.GetPinByID(ctx, pinID) + pin, err := p.repo.GetPinByID(ctx, pinID, false) if err != nil { return fmt.Errorf("get a pin to check for the availability of a like: %w", err) } diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index d1778d8..a8cf4fa 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -87,7 +87,7 @@ func (p *pinCase) DeletePinFromUser(ctx context.Context, pinID, userID int) erro } func (p *pinCase) ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) { - pin, err := p.repo.GetPinByID(ctx, pinID) + pin, err := p.repo.GetPinByID(ctx, pinID, true) if err != nil { return nil, fmt.Errorf("get a pin to view: %w", err) } From 1b64bc34f48f9454fd5ed89d8c6ffb9453147986 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 3 Nov 2023 16:42:05 +0300 Subject: [PATCH 114/266] TP-ffc_handlers_board update: add response codes/messages --- internal/pkg/delivery/http/v1/response.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index 1f7056e..fe61c69 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -6,10 +6,17 @@ import ( "net/http" ) -// type JsonResponseNoBody struct { -// Status string `json:"status" example:"ok"` -// Message string `json:"message" example:"Response message"` -// } +var ( + InternalServerErrMessage = "internal server error occured" + BadBodyMessage = "can't parse body, JSON expected" + BadQueryParamMessage = "invalid query parameters have been provided" +) + +var ( + BadBodyCode = "bad_body" + BadQueryParamCode = "bad_queryParam" + InternalErrorCode = "internal_error" +) type JsonResponse struct { Status string `json:"status" example:"ok"` From 8141be2a15fbfe799623f4be334e5861622146d4 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 3 Nov 2023 16:42:57 +0300 Subject: [PATCH 115/266] TP-ffc_handlers_board update: add repo.go with general info about repository layer --- internal/pkg/repository/repo.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 internal/pkg/repository/repo.go diff --git a/internal/pkg/repository/repo.go b/internal/pkg/repository/repo.go new file mode 100644 index 0000000..b0142f0 --- /dev/null +++ b/internal/pkg/repository/repo.go @@ -0,0 +1,12 @@ +package repository + +import "errors" + +const ( + TimeFormat = "02.01.2006" +) + +var ( + ErrMethodUnimplemented = errors.New("unimplemented") + ErrNoData = errors.New("got no data from repository layer") +) From 4582b0f40ccb7ceaa5e5f9aec429dffe50fd760f Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 3 Nov 2023 16:43:51 +0300 Subject: [PATCH 116/266] TP-ffc_handlers_board update: add CreateBoard, GetUserBoards, GetCertainBoard handlers --- go.mod | 13 +- go.sum | 23 ++- internal/api/server/router/router.go | 11 +- internal/app/app.go | 11 +- internal/pkg/delivery/http/v1/board.go | 113 +++++++++- .../pkg/repository/board/postgres/queries.go | 7 + .../pkg/repository/board/postgres/repo.go | 195 ++++++++++++++++++ internal/pkg/repository/board/postgres/tag.go | 58 ++++++ internal/pkg/repository/board/queries.go | 5 - internal/pkg/repository/board/ramrepo/repo.go | 41 ++++ internal/pkg/repository/board/repo.go | 104 +--------- internal/pkg/repository/board/tag.go | 34 --- internal/pkg/repository/ramrepo/user.go | 8 + internal/pkg/repository/user/queries.go | 4 +- internal/pkg/repository/user/repo.go | 27 +++ internal/pkg/usecase/board/create.go | 25 +-- internal/pkg/usecase/board/dto/board.go | 19 ++ internal/pkg/usecase/board/errors.go | 11 + internal/pkg/usecase/board/get.go | 82 ++++++++ internal/pkg/usecase/board/usecase.go | 27 ++- internal/pkg/usecase/board/validation.go | 45 +++- 21 files changed, 670 insertions(+), 193 deletions(-) create mode 100644 internal/pkg/repository/board/postgres/queries.go create mode 100644 internal/pkg/repository/board/postgres/repo.go create mode 100644 internal/pkg/repository/board/postgres/tag.go delete mode 100644 internal/pkg/repository/board/queries.go create mode 100644 internal/pkg/repository/board/ramrepo/repo.go delete mode 100644 internal/pkg/repository/board/tag.go create mode 100644 internal/pkg/usecase/board/dto/board.go create mode 100644 internal/pkg/usecase/board/errors.go create mode 100644 internal/pkg/usecase/board/get.go diff --git a/go.mod b/go.mod index 3ef4676..c16fc15 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/go-park-mail-ru/2023_2_OND_team -go 1.19 +go 1.21 + +toolchain go1.21.0 require ( github.com/Masterminds/squirrel v1.5.4 @@ -8,6 +10,7 @@ require ( github.com/go-chi/chi/v5 v5.0.10 github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 + github.com/microcosm-cc/bluemonday v1.0.26 github.com/proullon/ramsql v0.0.1 github.com/redis/go-redis/v9 v9.2.1 github.com/rs/cors v1.10.1 @@ -16,11 +19,12 @@ require ( github.com/swaggo/swag v1.16.2 go.uber.org/config v1.4.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -28,6 +32,7 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect @@ -40,9 +45,9 @@ require ( github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index ae7a898..0f34694 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,12 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -37,6 +41,8 @@ github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -51,6 +57,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -65,6 +72,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -100,6 +109,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -108,18 +118,19 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -127,8 +138,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 8ab1980..e127b4e 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -36,7 +36,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess ExposedHeaders: []string{cfgCSRF.HeaderSet}, }) - r.Mux.Use(c.Handler, security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) + r.Mux.Use(c.Handler, auth.NewAuthMiddleware(sm).ContextWithUserID) //security.CSRF(cfgCSRF) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) @@ -70,5 +70,14 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Delete("/delete/{pinID:\\d+}", handler.DeletePin) }) }) + + r.Route("/board", func(r chi.Router) { + r.Get("/{username}", handler.GetUserBoards) + r.Get("/get/{boardID:\\d+}", handler.GetCertainBoard) + r.Group(func(r chi.Router) { + r.Use(auth.RequireAuth) + r.Post("/create", handler.CreateNewBoard) + }) + }) }) } diff --git a/internal/app/app.go b/internal/app/app.go index 9bc85be..7690aef 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,12 +5,13 @@ import ( "time" "github.com/jackc/pgx/v5/pgxpool" + "github.com/microcosm-cc/bluemonday" redis "github.com/redis/go-redis/v9" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" - boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" + boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" @@ -22,14 +23,10 @@ import ( ) func Run(ctx context.Context, log *log.Logger, configFile string) { -<<<<<<< HEAD - pool, err := pgxpool.New(ctx, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") -======= ctxApp, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - pool, err := pgxpool.New(ctxApp, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") ->>>>>>> dev2 + pool, err := pgxpool.New(ctxApp, "postgres://exy:password@localhost:5432/pinspire?search_path=pinspire") // change creds if err != nil { log.Error(err.Error()) return @@ -56,7 +53,7 @@ func Run(ctx context.Context, log *log.Logger, configFile string) { sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) userCase := user.New(log, userRepo.NewUserRepoPG(pool)) pinCase := pin.New(log, pinRepo.NewPinRepoPG(pool)) - boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool)) + boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()) handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase) cfgServ, err := server.NewConfig(configFile) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index b0f72d6..69f1a02 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -1,14 +1,115 @@ package v1 import ( + "encoding/json" + "errors" + "fmt" "net/http" + "strconv" + + "github.com/go-chi/chi/v5" + boardDTO "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + bCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { - /* - 1. достаем user_id из контекста запроса - 2. парсим тело в json, проверяем парсинг - 3. вызываем UseCase, проверяем ошибку - 4. формируем ответ: заголовок content-type, body ответа - */ + h.log.Info("request on create new board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) + SetContentTypeJSON(w) + + var newBoard boardDTO.CreateBoard + err := json.NewDecoder(r.Body).Decode(&newBoard) + defer r.Body.Close() + if err != nil { + h.log.Info("create board: ", logger.F{"message", err.Error()}) + responseError(w, BadBodyCode, BadBodyMessage) + return + } + fmt.Println(newBoard) + newBoard.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) + err = h.boardCase.CreateNewBoard(r.Context(), newBoard) + if err != nil { + h.log.Info("create board", logger.F{"message", err.Error()}) + switch err { + case bCase.ErrInvalidBoardTitle: + responseError(w, "bad_boardTitle", err.Error()) + default: + if errors.Is(err, bCase.ErrInvalidTagTitles) { + responseError(w, "bad_tagTitles", err.Error()) + return + } + responseError(w, InternalErrorCode, InternalServerErrMessage) + w.WriteHeader(http.StatusInternalServerError) + } + return + } + + err = responseOk(w, "new board was created successfully", nil) + if err != nil { + h.log.Error(err.Error()) + w.Write([]byte(InternalServerErrMessage)) + return + } + h.log.Info("create new board request: successfull respond") +} + +func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { + h.log.Info("request to get user boards:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) + SetContentTypeJSON(w) + + userBoards, err := h.boardCase.GetBoardsByUsername(r.Context(), chi.URLParam(r, "username")) + if err != nil { + h.log.Info("get user boards: ", logger.F{"message", err.Error()}) + switch err { + case bCase.ErrInvalidUsername: + responseError(w, "bad_username", err.Error()) + default: + responseError(w, InternalErrorCode, InternalServerErrMessage) + w.WriteHeader(http.StatusInternalServerError) + } + return + } + + err = responseOk(w, "got user boards successfully", userBoards) + if err != nil { + h.log.Error(err.Error()) + w.Write([]byte(InternalServerErrMessage)) + return + } + h.log.Info("get user boards request: successfull respond") +} + +func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { + h.log.Info("request to get certain board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) + SetContentTypeJSON(w) + + boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) + if err != nil { + h.log.Info("get certain board ", logger.F{"message", err.Error()}) + responseError(w, BadQueryParamCode, BadQueryParamMessage) + return + } + + board, err := h.boardCase.GetCertainBoardByID(r.Context(), int(boardID)) + if err != nil { + h.log.Info("get certain board: ", logger.F{"message", err.Error()}) + switch err { + case bCase.ErrNoSuchBoard: + responseError(w, "no_board", err.Error()) + default: + responseError(w, InternalErrorCode, InternalServerErrMessage) + } + return + } + + err = responseOk(w, "got certain board successfully", board) + if err != nil { + h.log.Error(err.Error()) + w.Write([]byte(InternalServerErrMessage)) + return + } + + h.log.Info("get certain board request: successfull respond") } diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go new file mode 100644 index 0000000..fd7b403 --- /dev/null +++ b/internal/pkg/repository/board/postgres/queries.go @@ -0,0 +1,7 @@ +package board + +const ( + InsertBoardQuery = "INSERT INTO board (author, title, description, public) VALUES ($1, $2, $3, $4) RETURNING id;" + SelectBoardAuthorByBoardIdQuery = "SELECT author FROM board WHERE id = $1;" + SelectBoardContributorsByBoardIdQuery = "SELECT user_id FROM contributor WHERE board_id = $1;" +) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go new file mode 100644 index 0000000..03f6589 --- /dev/null +++ b/internal/pkg/repository/board/postgres/repo.go @@ -0,0 +1,195 @@ +package board + +import ( + "context" + "fmt" + + "github.com/Masterminds/squirrel" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type BoardRepoPG struct { + db *pgxpool.Pool + sqlBuilder squirrel.StatementBuilderType +} + +func NewBoardRepoPG(db *pgxpool.Pool) *BoardRepoPG { + return &BoardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} +} + +func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error { + + tx, err := repo.db.Begin(ctx) + if err != nil { + return fmt.Errorf("starting transaction for creating new board: %w", err) + } + + newBoardId, err := repo.insertBoard(ctx, tx, board) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("inserting board within transaction: %w", err) + } + + err = repo.insertTags(ctx, tx, tagTitles) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("inserting new tags within transaction: %w", err) + } + + err = repo.addTagsToBoard(ctx, tx, tagTitles, newBoardId, true) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("adding new tags on board within transaction: %w", err) + } + + tx.Commit(ctx) + + return nil +} + +func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.GetUserBoard, error) { + getBoardsQuery := boardRepo.sqlBuilder. + Select( + "board.id", + "board.title", + "TO_CHAR(board.created_at, 'DD:MM:YYYY')", + "COUNT(pin.id) AS pins_number", + "ARRAY_REMOVE((ARRAY_AGG(pin.picture))[:3], NULL) AS pins"). + From("membership"). + JoinClause("FULL JOIN pin ON membership.pin_id = pin.id"). + JoinClause("FULL JOIN board ON membership.board_id = board.id"). + Where(squirrel.Eq{"board.author": userID}) + + if !isAuthor { + getBoardsQuery = getBoardsQuery.Where(squirrel.Eq{"board.public": true}) + } + getBoardsQuery = getBoardsQuery. + GroupBy( + "board.id", + "board.title", + "board.created_at", + ). + OrderBy("board.id ASC") + + sqlRow, args, err := getBoardsQuery.ToSql() + if err != nil { + return nil, fmt.Errorf("building query for get boards by user id: %w", err) + } + + rows, err := boardRepo.db.Query(ctx, sqlRow, args...) + if err != nil { + return nil, fmt.Errorf("making query for get boards by user id: %w", err) + } + defer rows.Close() + + boards := make([]dto.GetUserBoard, 0) + for rows.Next() { + board := dto.GetUserBoard{} + err = rows.Scan(&board.BoardID, &board.Title, &board.CreatedAt, &board.PinsNumber, &board.Pins) + if err != nil { + return nil, fmt.Errorf("scanning the result of get boards by user id query: %w", err) + } + boards = append(boards, board) + } + + return boards, nil +} + +func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.GetUserBoard, err error) { + getBoardByIdQuery := repo.sqlBuilder. + Select( + "board.id", + "board.title", + "COALESCE(board.description, '')", + "TO_CHAR(board.created_at, 'DD:MM:YYYY')", + "COUNT(DISTINCT pin.id) AS pins_number", + "ARRAY_REMOVE(ARRAY_AGG(DISTINCT pin.picture), NULL) AS pins", + "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). + From("membership"). + JoinClause("FULL JOIN pin ON membership.pin_id = pin.id"). + JoinClause("FULL JOIN board ON membership.board_id = board.id"). + JoinClause("FULL JOIN board_tag ON board_tag.board_id = board.id"). + JoinClause("FULL JOIN tag ON board_tag.tag_id = tag.id"). + Where(squirrel.Eq{"board.id": boardID}) + + if !hasAccess { + getBoardByIdQuery = getBoardByIdQuery.Where(squirrel.Eq{"board.public": true}) + + } + getBoardByIdQuery = getBoardByIdQuery.GroupBy( + "board.id", + "board.title", + "board.description", + "board.created_at"). + OrderBy("board.id ASC") + + sqlRow, args, err := getBoardByIdQuery.ToSql() + if err != nil { + return dto.GetUserBoard{}, fmt.Errorf("building get board by id query: %w", err) + } + + row := repo.db.QueryRow(ctx, sqlRow, args...) + board = dto.GetUserBoard{} + err = row.Scan(&board.BoardID, &board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) + if err != nil { + switch err { + case pgx.ErrNoRows: + return dto.GetUserBoard{}, repository.ErrNoData + default: + return dto.GetUserBoard{}, fmt.Errorf("scan result of get board by id query: %w", err) + } + } + + return board, nil +} + +func (repo *BoardRepoPG) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { + row := repo.db.QueryRow(ctx, SelectBoardAuthorByBoardIdQuery, boardID) + var authorID int + err := row.Scan(&authorID) + if err != nil { + switch err { + case pgx.ErrNoRows: + return 0, repository.ErrNoData + default: + return 0, fmt.Errorf("get board author by board id query: %w", err) + } + } + return authorID, nil +} + +func (repo *BoardRepoPG) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) { + rows, err := repo.db.Query(ctx, SelectBoardContributorsByBoardIdQuery, boardID) + if err != nil { + return nil, fmt.Errorf("select contributors by board id query: %w", err) + } + defer rows.Close() + + contributors := make([]uEntity.User, 0) + for rows.Next() { + var contributorID int + err = rows.Scan(&contributorID) + if err != nil { + return nil, fmt.Errorf("scan result of get contributors by board id query: %w", err) + } + contributors = append(contributors, uEntity.User{ID: contributorID}) + } + + return contributors, nil +} + +func (repo *BoardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { + row := tx.QueryRow(ctx, InsertBoardQuery, board.AuthorID, board.Title, board.Description, board.Public) + + var newBoardID int + err := row.Scan(&newBoardID) + if err != nil { + return 0, fmt.Errorf("scan result of insterting new board: %w", err) + } + return newBoardID, nil +} diff --git a/internal/pkg/repository/board/postgres/tag.go b/internal/pkg/repository/board/postgres/tag.go new file mode 100644 index 0000000..1588952 --- /dev/null +++ b/internal/pkg/repository/board/postgres/tag.go @@ -0,0 +1,58 @@ +package board + +import ( + "context" + "fmt" + "strconv" + + "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5" +) + +func (repo *BoardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []string) error { + insertTagsQuery := repo.sqlBuilder. + Insert("tag"). + Columns("title") + for _, title := range titles { + insertTagsQuery = insertTagsQuery.Values(title) + } + sqlRow, args, err := insertTagsQuery. + Suffix("ON CONFLICT (title) DO NOTHING"). + ToSql() + if err != nil { + return fmt.Errorf("build sql row query while inserting tags: %w", err) + } + + _, err = tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("making insertTags query: %w", err) + } + return nil +} + +func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitles []string, boardID int, isNewBoard bool) error { + addTagsToBoardQuery := repo.sqlBuilder. + Insert("board_tag"). + Columns("board_id", "tag_id"). + Select( + squirrel.Select(strconv.FormatInt(int64(boardID), 10), "id"). + From("tag"). + Where(squirrel.Eq{"title": tagTitles}), + ) + + if !isNewBoard { + addTagsToBoardQuery.Suffix("ON CONFLICT DO NOTHING") + } + + sqlRow, args, err := addTagsToBoardQuery.ToSql() + if err != nil { + return fmt.Errorf("building sql query row for adding tags to board: %w", err) + } + + _, err = tx.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("execute sql query to add tags to board: %w", err) + } + + return nil +} diff --git a/internal/pkg/repository/board/queries.go b/internal/pkg/repository/board/queries.go deleted file mode 100644 index 4dc71a7..0000000 --- a/internal/pkg/repository/board/queries.go +++ /dev/null @@ -1,5 +0,0 @@ -package board - -const ( - CreateBoardQuery = "INSERT INTO board (author, title, description, public) VALUES ($1 $2 $3 $4) RETURNING id;" -) diff --git a/internal/pkg/repository/board/ramrepo/repo.go b/internal/pkg/repository/board/ramrepo/repo.go new file mode 100644 index 0000000..6555e19 --- /dev/null +++ b/internal/pkg/repository/board/ramrepo/repo.go @@ -0,0 +1,41 @@ +package board + +import ( + "context" + "database/sql" + + "github.com/Masterminds/squirrel" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" +) + +type BoardRepoRam struct { + db *sql.DB + sqlBuilder squirrel.StatementBuilderType +} + +func NewBoardRepoRam(db *sql.DB) *BoardRepoRam { + return &BoardRepoRam{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} +} + +func (repo *BoardRepoRam) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error { + return repository.ErrMethodUnimplemented +} + +func (repo *BoardRepoRam) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.GetUserBoard, error) { + return nil, repository.ErrMethodUnimplemented +} + +func (repo *BoardRepoRam) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.GetUserBoard, err error) { + return dto.GetUserBoard{}, repository.ErrMethodUnimplemented +} + +func (repo *BoardRepoRam) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { + return 0, repository.ErrMethodUnimplemented +} + +func (repo *BoardRepoRam) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) { + return nil, repository.ErrMethodUnimplemented +} diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 47e1dd7..bde7f2d 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -2,102 +2,20 @@ package board import ( "context" - "fmt" - "strconv" - "github.com/Masterminds/squirrel" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" + uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) type Repository interface { - CreateBoard(ctx context.Context, board entity.Board, pinIDs []int, tagTitles []string) error - // CreateBoardMembershipByPinIDs(ctx context.Context, pinIDs []int) error - // SelectOwnBoardsByUserID(ctx context.Context, userID int) ([]entity.Board, error) - // SelectUserBoardsByUserID(ctx context.Context, userID int) ([]entity.Board, error) - // SelectBoardsByTitle(ctx context.Context, title string) ([]entity.Board, error) - // SelctBoardsByTag(ctx context.Context, tagTitle string) ([]entity.Board, error) - // SelectBoardTags(ctx context.Context, boardID int) (tagTitles []string, err error) -} - -type BoardRepoPG struct { - db *pgxpool.Pool - sqlBuilder squirrel.StatementBuilderType -} - -func NewBoardRepoPG(db *pgxpool.Pool) *BoardRepoPG { - return &BoardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} -} - -func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, pinIDs []int, tagTitles []string) error { - - tx, err := repo.db.Begin(ctx) - if err != nil { - return fmt.Errorf("starting transaction for creating new board: %w", err) - } - - newBoardId, err := repo.insertBoard(ctx, tx, board) - if err != nil { - tx.Rollback(ctx) - return fmt.Errorf("inserting board within transaction: %w", err) - } - - err = repo.insertTags(ctx, tx, tagTitles) - if err != nil { - tx.Rollback(ctx) - return fmt.Errorf("inserting new tags within transaction: %w", err) - } - - err = repo.addTagsToBoard(ctx, tx, tagTitles, newBoardId, true) - if err != nil { - tx.Rollback(ctx) - return fmt.Errorf("adding new tags on board within transaction: %w", err) - } - - tx.Commit(ctx) - - return nil -} - -func (repo *BoardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { - row := tx.QueryRow(ctx, CreateBoardQuery, board.AuthorID, board.Title, board.Description, board.Public) - - var newBoardID int - err := row.Scan(&newBoardID) - if err != nil { - return 0, fmt.Errorf("scan result of insterting new board: %w", err) - } - return newBoardID, nil -} - -func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitles []string, boardID int, isNewBoard bool) error { - addTagsToBoardQuery := repo.sqlBuilder. - Insert("board_tag"). - Columns("board_id", "tag_id"). - Select( - squirrel.Select(strconv.FormatInt(int64(boardID), 10), "id"). - From("tag"). - Where(squirrel.Eq{"title": tagTitles}), - ) - - if !isNewBoard { - addTagsToBoardQuery.Suffix("ON CONFLICT DO NOTHING") - } - - sqlRow, args, err := addTagsToBoardQuery.ToSql() - if err != nil { - return fmt.Errorf("building sql query row for adding tags to board: %w", err) - } - - cmdTag, err := tx.Exec(ctx, sqlRow, args...) - if err != nil { - return fmt.Errorf("execute sql query to add tags to board: %w", err) - } - - if cmdTag.RowsAffected() != int64(len(tagTitles)) { - return fmt.Errorf("not all tags were inserted correctly") - } - - return nil + CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error + GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.GetUserBoard, error) + GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.GetUserBoard, err error) + GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) + GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) + // AddContributor + // GetBoardsByTitle(ctx context.Context, title string) ([]entity.Board, error) + // GetBoardsByTag(ctx context.Context, tagTitle string) ([]entity.Board, error) + // GetBoardTags(ctx context.Context, boardID int) (tagTitles []string, err error) } diff --git a/internal/pkg/repository/board/tag.go b/internal/pkg/repository/board/tag.go deleted file mode 100644 index d56806d..0000000 --- a/internal/pkg/repository/board/tag.go +++ /dev/null @@ -1,34 +0,0 @@ -package board - -import ( - "context" - "fmt" - - "github.com/jackc/pgx/v5" -) - -func (repo *BoardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []string) error { - insertTagsQuery := repo.sqlBuilder. - Insert("tag"). - Columns("title") - for _, title := range titles { - insertTagsQuery = insertTagsQuery.Values(title) - } - sqlRow, args, err := insertTagsQuery. - Suffix("ON CONFLICT (title) DO NOTHING"). - Suffix("RETURNING id"). - ToSql() - if err != nil { - return fmt.Errorf("build sql row query while inserting tags: %w", err) - } - - cmdTag, err := tx.Exec(ctx, sqlRow, args...) - if err != nil { - return fmt.Errorf("making insertTags query: %w", err) - } - if cmdTag.RowsAffected() != int64(len(titles)) { - return fmt.Errorf("checking rows affected after insertTags: %w", err) - } - - return nil -} diff --git a/internal/pkg/repository/ramrepo/user.go b/internal/pkg/repository/ramrepo/user.go index 25326a3..fb0cca7 100644 --- a/internal/pkg/repository/ramrepo/user.go +++ b/internal/pkg/repository/ramrepo/user.go @@ -55,3 +55,11 @@ func (r *ramUserRepo) GetAllUserData(ctx context.Context, userID int) (*entity.U func (r *ramUserRepo) EditUserInfo(ctx context.Context, userID int, s rp.S) error { return ErrMethodUnimplemented } + +func (r *ramUserRepo) GetUserIdByUsername(ctx context.Context, username string) (int, error) { + return 0, ErrMethodUnimplemented +} + +func (r *ramUserRepo) GetLastUserID(ctx context.Context) (int, error) { + return 0, ErrMethodUnimplemented +} diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go index 88af57c..4d50b45 100644 --- a/internal/pkg/repository/user/queries.go +++ b/internal/pkg/repository/user/queries.go @@ -7,5 +7,7 @@ var ( SelectUsernameAndAvatar = "SELECT username, avatar FROM profile WHERE id = $1;" SelectUserDataExceptPassword = "SELECT username, email, avatar, name, surname FROM profile WHERE id = $1;" - UpdateAvatarProfile = "UPDATE profile SET avatar = $1 WHERE id = $2;" + UpdateAvatarProfile = "UPDATE profile SET avatar = $1 WHERE id = $2;" + SelectUserIdByUsername = "SELECT id FROM profile WHERE username = $1;" + SelectLastUserID = "SELECT id FROM profile ORDER BY id DESC LIMIT 1;" ) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 150e5a6..4188aaf 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -5,15 +5,19 @@ import ( "fmt" sq "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" ) type Repository interface { AddNewUser(ctx context.Context, user *user.User) error GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) + GetUserIdByUsername(ctx context.Context, username string) (int, error) + GetLastUserID(ctx context.Context) (int, error) EditUserAvatar(ctx context.Context, userID int, avatar string) error GetAllUserData(ctx context.Context, userID int) (*user.User, error) EditUserInfo(ctx context.Context, userID int, updateFields S) error @@ -91,3 +95,26 @@ func (u *userRepoPG) EditUserInfo(ctx context.Context, userID int, updateFields } return nil } + +func (u *userRepoPG) GetUserIdByUsername(ctx context.Context, username string) (int, error) { + var userID int + err := u.db.QueryRow(ctx, SelectUserIdByUsername, username).Scan(&userID) + if err != nil { + switch err { + case pgx.ErrNoRows: + return 0, repository.ErrNoData + default: + return 0, fmt.Errorf("scan result of get user id by username query: %w", err) + } + } + return userID, nil +} + +func (u *userRepoPG) GetLastUserID(ctx context.Context) (int, error) { + var lastUserID int + err := u.db.QueryRow(ctx, SelectLastUserID).Scan(&lastUserID) + if err != nil { + return 0, fmt.Errorf("get last user id: %w", err) + } + return lastUserID, nil +} diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go index c0ee409..8868cf2 100644 --- a/internal/pkg/usecase/board/create.go +++ b/internal/pkg/usecase/board/create.go @@ -4,23 +4,24 @@ import ( "context" "fmt" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, createBoardObj CreateBoard) error { - if !isValidBoardTitle(createBoardObj.Title) { - return fmt.Errorf("creating new board: invalid board title '%s'", createBoardObj.Title) +func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.CreateBoard) error { + if !bCase.isValidBoardTitle(newBoard.Title) { + return ErrInvalidBoardTitle } - if !isValidTagTitles(createBoardObj.TagTitles) { //return errFields, errF.Err() in Errorf - return fmt.Errorf("invalid board tag titles") + if err := bCase.checkIsValidTagTitles(newBoard.TagTitles); err != nil { + return fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) } - err := bCase.BoardRepo.CreateBoard(ctx, board.Board{ - AuthorID: createBoardObj.AuthorID, - Title: createBoardObj.Title, - Description: createBoardObj.Description, - Public: createBoardObj.Public, - }, createBoardObj.PinIDs, createBoardObj.TagTitles) + err := bCase.boardRepo.CreateBoard(ctx, entity.Board{ + AuthorID: newBoard.AuthorID, + Title: newBoard.Title, + Description: newBoard.Description, + Public: newBoard.Public, + }, newBoard.TagTitles) if err != nil { return fmt.Errorf("create new board usecase: %w", err) diff --git a/internal/pkg/usecase/board/dto/board.go b/internal/pkg/usecase/board/dto/board.go new file mode 100644 index 0000000..a12659a --- /dev/null +++ b/internal/pkg/usecase/board/dto/board.go @@ -0,0 +1,19 @@ +package board + +type CreateBoard struct { + Title string `json:"title" example:"Sunny places"` + Description string `json:"description" example:"long description"` + AuthorID int `json:"author_id" example:"45"` + Public bool `json:"public" example:"true"` + TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` +} //@name NewBoardData + +type GetUserBoard struct { + BoardID int `json:"board_id" example:"15"` + Title string `json:"title" example:"Sunny places"` + Description string `json:"description,omitempty" example:"Sunny places"` + CreatedAt string `json:"created_at" example:"08.10.2020"` + PinsNumber int `json:"pins_number" example:"10"` + Pins []string `json:"pins" example:"['/upload/pin/pic1', '/upload/pin/pic2']"` + TagTitles []string `json:"tags,omitempty" example:"['flowers', 'sunrise']"` +} //@name UserBoard diff --git a/internal/pkg/usecase/board/errors.go b/internal/pkg/usecase/board/errors.go new file mode 100644 index 0000000..754ca01 --- /dev/null +++ b/internal/pkg/usecase/board/errors.go @@ -0,0 +1,11 @@ +package board + +import "errors" + +var ( + ErrInvalidUsername = errors.New("invalid username has been provided or username doesn't exist") + ErrNoSuchBoard = errors.New("board is not accessable or doesn't exist") + ErrInvalidBoardTitle = errors.New("invalid or empty board title has been provided") + ErrInvalidTagTitles = errors.New("invalid tag titles have been provided") + ErrInvalidUserID = errors.New("invalid user id has been provided") +) diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go new file mode 100644 index 0000000..909012b --- /dev/null +++ b/internal/pkg/usecase/board/get.go @@ -0,0 +1,82 @@ +package board + +import ( + "context" + "fmt" + "slices" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" +) + +func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.GetUserBoard, error) { + if !bCase.isValidUsername(username) { + return nil, ErrInvalidUsername + } + + userID, err := bCase.userRepo.GetUserIdByUsername(ctx, username) + if err != nil { + switch err { + case repository.ErrNoData: + return nil, ErrInvalidUsername + default: + return nil, fmt.Errorf("get user id by username in get boards usecase: %w", err) + } + } + + var isAuthor bool + currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) + if loggedIn && currUserID == userID { + isAuthor = true + } + boards, err := bCase.boardRepo.GetBoardsByUserID(ctx, userID, isAuthor) + if err != nil { + return nil, fmt.Errorf("get boards by user id usecase: %w", err) + } + + return boards, nil +} + +func (bCase *BoardUsecase) GetCertainBoardByID(ctx context.Context, boardID int) (dto.GetUserBoard, error) { + boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) + if err != nil { + switch err { + case repository.ErrNoData: + return dto.GetUserBoard{}, ErrNoSuchBoard + default: + return dto.GetUserBoard{}, fmt.Errorf("get certain board by id: %w", err) + } + } + + boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) + if err != nil { + return dto.GetUserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) + } + + boardContributorsIDs := make([]int, 0, len(boardContributors)) + func() { + for _, contributor := range boardContributors { + boardContributorsIDs = append(boardContributorsIDs, contributor.ID) + } + }() + + var hasAccess bool + currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) + if loggedIn && (currUserID == boardAuthorID || slices.Contains(boardContributorsIDs, currUserID)) { + hasAccess = true + } + + fmt.Println(loggedIn, currUserID, boardAuthorID, hasAccess) + board, err := bCase.boardRepo.GetBoardByID(ctx, boardID, hasAccess) + if err != nil { + switch err { + case repository.ErrNoData: + return dto.GetUserBoard{}, ErrNoSuchBoard + default: + return dto.GetUserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) + } + } + + return board, nil +} diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index cece80a..98eab6f 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -4,29 +4,26 @@ import ( "context" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" + userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/microcosm-cc/bluemonday" ) -type CreateBoard struct { - Title string `json:"title" example:"Sunny places"` - Description string `json:"description" example:"long description"` - AuthorID int `json:"author_id" example:"45"` - Public bool `json:"public" example:"true"` - PinIDs []int `json:"pin_ids" example:"[1, 2, 3]"` - TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` -} //@name Board - type Usecase interface { - CreateNewBoard(ctx context.Context, createBoardObj CreateBoard) error - // GetOwnBoards() - // GetUserBoards() + CreateNewBoard(ctx context.Context, createBoardObj dto.CreateBoard) error + GetBoardsByUsername(ctx context.Context, username string) ([]dto.GetUserBoard, error) + GetCertainBoardByID(ctx context.Context, boardID int) (dto.GetUserBoard, error) + // CheckBoardContributor(ctx context.Context, userID int) (bool, error) } type BoardUsecase struct { log *logger.Logger - BoardRepo boardRepo.Repository + boardRepo boardRepo.Repository + userRepo userRepo.Repository + sanitizer *bluemonday.Policy } -func New(logger *logger.Logger, boardRepo boardRepo.Repository) *BoardUsecase { - return &BoardUsecase{log: logger, BoardRepo: boardRepo} +func New(logger *logger.Logger, boardRepo boardRepo.Repository, userRepo userRepo.Repository, sanitizer *bluemonday.Policy) *BoardUsecase { + return &BoardUsecase{log: logger, boardRepo: boardRepo, userRepo: userRepo, sanitizer: sanitizer} } diff --git a/internal/pkg/usecase/board/validation.go b/internal/pkg/usecase/board/validation.go index 9208a64..d6936c1 100644 --- a/internal/pkg/usecase/board/validation.go +++ b/internal/pkg/usecase/board/validation.go @@ -1,8 +1,15 @@ package board -import "unicode" +import ( + "fmt" + "unicode" +) + +func (bCase *BoardUsecase) isValidTagTitle(title string) bool { + if len(title) > 20 { + return false + } -func isValidTagTitle(title string) bool { for _, sym := range title { if !(unicode.IsNumber(sym) || unicode.IsLetter(sym)) { return false @@ -11,26 +18,46 @@ func isValidTagTitle(title string) bool { return true } -func isValidTagTitles(titles []string) bool { +func (bCase *BoardUsecase) checkIsValidTagTitles(titles []string) error { if len(titles) > 7 { - return false + return fmt.Errorf("too many titles") } + + invalidTitles := make([]string, 0) for _, title := range titles { - if !isValidTagTitle(title) { + if !bCase.isValidTagTitle(title) { + invalidTitles = append(invalidTitles, title) + } + } + if len(invalidTitles) > 0 { + return fmt.Errorf("%v", invalidTitles) + } + return nil +} + +func (bCase *BoardUsecase) isValidBoardTitle(title string) bool { + if len(title) == 0 || len(title) > 20 { + return false + } + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { return false } } + bCase.sanitizer.Sanitize(title) return true } -func isValidBoardTitle(title string) bool { - if len(title) < 4 || len(title) > 50 { +func (bCase *BoardUsecase) isValidUsername(username string) bool { + if len(username) < 4 || len(username) > 50 { return false } - for _, sym := range title { - if !(unicode.IsNumber(sym) || unicode.IsLetter(sym)) { + for _, r := range username { + if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { return false } } + bCase.sanitizer.Sanitize(username) + return true } From 1e88794f0701d0d0dcd389f38f631f351bb6a446 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 3 Nov 2023 20:37:54 +0300 Subject: [PATCH 117/266] TP-ffc_handlers_board update: add access for contributors in GetUserBoards handler --- internal/pkg/delivery/http/v1/board.go | 2 +- .../pkg/repository/board/postgres/errors.go | 7 ++ .../pkg/repository/board/postgres/queries.go | 2 + .../pkg/repository/board/postgres/repo.go | 74 ++++++++++++++++--- internal/pkg/repository/board/postgres/tag.go | 6 +- internal/pkg/repository/board/ramrepo/repo.go | 6 +- internal/pkg/repository/board/repo.go | 6 +- internal/pkg/usecase/board/create.go | 2 +- internal/pkg/usecase/board/dto/board.go | 6 +- internal/pkg/usecase/board/get.go | 33 +++++---- internal/pkg/usecase/board/update.go | 11 +++ internal/pkg/usecase/board/usecase.go | 7 +- 12 files changed, 125 insertions(+), 37 deletions(-) create mode 100644 internal/pkg/repository/board/postgres/errors.go create mode 100644 internal/pkg/usecase/board/update.go diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 69f1a02..165c0ec 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -19,7 +19,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { h.log.Info("request on create new board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) SetContentTypeJSON(w) - var newBoard boardDTO.CreateBoard + var newBoard boardDTO.BoardData err := json.NewDecoder(r.Body).Decode(&newBoard) defer r.Body.Close() if err != nil { diff --git a/internal/pkg/repository/board/postgres/errors.go b/internal/pkg/repository/board/postgres/errors.go new file mode 100644 index 0000000..03e047f --- /dev/null +++ b/internal/pkg/repository/board/postgres/errors.go @@ -0,0 +1,7 @@ +package board + +import "errors" + +var ( + ErrIncorrectNumberRowsAffcted = errors.New("incorrect number of rows affected") +) diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index fd7b403..8baffaf 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -4,4 +4,6 @@ const ( InsertBoardQuery = "INSERT INTO board (author, title, description, public) VALUES ($1, $2, $3, $4) RETURNING id;" SelectBoardAuthorByBoardIdQuery = "SELECT author FROM board WHERE id = $1;" SelectBoardContributorsByBoardIdQuery = "SELECT user_id FROM contributor WHERE board_id = $1;" + UpdateBoardByIdQuery = "UPDATE board SET title = $1, description = $2, public = $3 WHERE id = $4;" + GetContributorBoardsIDs = "SELECT board_id FROM contributor WHERE user_id = $1;" ) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 03f6589..dcfcae3 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -52,7 +52,7 @@ func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, ta return nil } -func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.GetUserBoard, error) { +func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { getBoardsQuery := boardRepo.sqlBuilder. Select( "board.id", @@ -63,10 +63,16 @@ func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, From("membership"). JoinClause("FULL JOIN pin ON membership.pin_id = pin.id"). JoinClause("FULL JOIN board ON membership.board_id = board.id"). + Where(squirrel.Eq{"board.deleted_at": nil}). Where(squirrel.Eq{"board.author": userID}) if !isAuthor { - getBoardsQuery = getBoardsQuery.Where(squirrel.Eq{"board.public": true}) + getBoardsQuery = getBoardsQuery.Where( + squirrel.Or{ + squirrel.Eq{"board.public": true}, + squirrel.Eq{"board.id": accessableBoardsIDs}, + }, + ) } getBoardsQuery = getBoardsQuery. GroupBy( @@ -87,9 +93,9 @@ func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, } defer rows.Close() - boards := make([]dto.GetUserBoard, 0) + boards := make([]dto.UserBoard, 0) for rows.Next() { - board := dto.GetUserBoard{} + board := dto.UserBoard{} err = rows.Scan(&board.BoardID, &board.Title, &board.CreatedAt, &board.PinsNumber, &board.Pins) if err != nil { return nil, fmt.Errorf("scanning the result of get boards by user id query: %w", err) @@ -100,7 +106,7 @@ func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, return boards, nil } -func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.GetUserBoard, err error) { +func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) { getBoardByIdQuery := repo.sqlBuilder. Select( "board.id", @@ -115,6 +121,7 @@ func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces JoinClause("FULL JOIN board ON membership.board_id = board.id"). JoinClause("FULL JOIN board_tag ON board_tag.board_id = board.id"). JoinClause("FULL JOIN tag ON board_tag.tag_id = tag.id"). + Where(squirrel.Eq{"board.deleted_at": nil}). Where(squirrel.Eq{"board.id": boardID}) if !hasAccess { @@ -130,18 +137,18 @@ func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces sqlRow, args, err := getBoardByIdQuery.ToSql() if err != nil { - return dto.GetUserBoard{}, fmt.Errorf("building get board by id query: %w", err) + return dto.UserBoard{}, fmt.Errorf("building get board by id query: %w", err) } row := repo.db.QueryRow(ctx, sqlRow, args...) - board = dto.GetUserBoard{} + board = dto.UserBoard{} err = row.Scan(&board.BoardID, &board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) if err != nil { switch err { case pgx.ErrNoRows: - return dto.GetUserBoard{}, repository.ErrNoData + return dto.UserBoard{}, repository.ErrNoData default: - return dto.GetUserBoard{}, fmt.Errorf("scan result of get board by id query: %w", err) + return dto.UserBoard{}, fmt.Errorf("scan result of get board by id query: %w", err) } } @@ -183,6 +190,55 @@ func (repo *BoardRepoPG) GetContributorsByBoardID(ctx context.Context, boardID i return contributors, nil } +func (repo *BoardRepoPG) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) { + rows, err := repo.db.Query(ctx, GetContributorBoardsIDs, contributorID) + if err != nil { + return nil, fmt.Errorf("get contributor boardsIDs query: %w", err) + } + defer rows.Close() + + boardsIDs := make([]int, 0) + for rows.Next() { + var boardID int + err = rows.Scan(&boardID) + if err != nil { + return nil, fmt.Errorf("get contributor boardsIDs query: %w", err) + } + boardsIDs = append(boardsIDs, boardID) + } + + return boardsIDs, nil +} + +func (repo *BoardRepoPG) UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error { + tx, err := repo.db.Begin(ctx) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("start update board transaction: %w", err) + } + + err = repo.insertTags(ctx, tx, tagTitles) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("update board: insert tags within transaction - %w", err) + } + + err = repo.addTagsToBoard(ctx, tx, tagTitles, newBoardData.ID, false) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("update board: add tags to board within transsaction - %w", err) + } + + _, err = repo.db.Exec(ctx, UpdateBoardByIdQuery, newBoardData.Title, newBoardData.Description, newBoardData.Public, newBoardData.ID) + if err != nil { + tx.Rollback(ctx) + return fmt.Errorf("update board: edit board data within transaction - %w", err) + } + + tx.Commit(ctx) + return nil +} + func (repo *BoardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { row := tx.QueryRow(ctx, InsertBoardQuery, board.AuthorID, board.Title, board.Description, board.Public) diff --git a/internal/pkg/repository/board/postgres/tag.go b/internal/pkg/repository/board/postgres/tag.go index 1588952..626c583 100644 --- a/internal/pkg/repository/board/postgres/tag.go +++ b/internal/pkg/repository/board/postgres/tag.go @@ -49,10 +49,14 @@ func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitle return fmt.Errorf("building sql query row for adding tags to board: %w", err) } - _, err = tx.Exec(ctx, sqlRow, args...) + cmdTag, err := tx.Exec(ctx, sqlRow, args...) if err != nil { return fmt.Errorf("execute sql query to add tags to board: %w", err) } + if isNewBoard && int(cmdTag.RowsAffected()) != len(tagTitles) { + return ErrIncorrectNumberRowsAffcted + } + return nil } diff --git a/internal/pkg/repository/board/ramrepo/repo.go b/internal/pkg/repository/board/ramrepo/repo.go index 6555e19..3fcef20 100644 --- a/internal/pkg/repository/board/ramrepo/repo.go +++ b/internal/pkg/repository/board/ramrepo/repo.go @@ -24,12 +24,12 @@ func (repo *BoardRepoRam) CreateBoard(ctx context.Context, board entity.Board, t return repository.ErrMethodUnimplemented } -func (repo *BoardRepoRam) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.GetUserBoard, error) { +func (repo *BoardRepoRam) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.UserBoard, error) { return nil, repository.ErrMethodUnimplemented } -func (repo *BoardRepoRam) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.GetUserBoard, err error) { - return dto.GetUserBoard{}, repository.ErrMethodUnimplemented +func (repo *BoardRepoRam) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) { + return dto.UserBoard{}, repository.ErrMethodUnimplemented } func (repo *BoardRepoRam) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index bde7f2d..bbacc5d 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -10,10 +10,12 @@ import ( type Repository interface { CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error - GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.GetUserBoard, error) - GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.GetUserBoard, err error) + GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) + GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) + GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) + UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error // AddContributor // GetBoardsByTitle(ctx context.Context, title string) ([]entity.Board, error) // GetBoardsByTag(ctx context.Context, tagTitle string) ([]entity.Board, error) diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go index 8868cf2..2871e16 100644 --- a/internal/pkg/usecase/board/create.go +++ b/internal/pkg/usecase/board/create.go @@ -8,7 +8,7 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.CreateBoard) error { +func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) error { if !bCase.isValidBoardTitle(newBoard.Title) { return ErrInvalidBoardTitle } diff --git a/internal/pkg/usecase/board/dto/board.go b/internal/pkg/usecase/board/dto/board.go index a12659a..82d9e8b 100644 --- a/internal/pkg/usecase/board/dto/board.go +++ b/internal/pkg/usecase/board/dto/board.go @@ -1,14 +1,14 @@ package board -type CreateBoard struct { +type BoardData struct { Title string `json:"title" example:"Sunny places"` Description string `json:"description" example:"long description"` - AuthorID int `json:"author_id" example:"45"` + AuthorID int `json:"author_id,omitempty" example:"45"` Public bool `json:"public" example:"true"` TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` } //@name NewBoardData -type GetUserBoard struct { +type UserBoard struct { BoardID int `json:"board_id" example:"15"` Title string `json:"title" example:"Sunny places"` Description string `json:"description,omitempty" example:"Sunny places"` diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index 909012b..6175c64 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -10,7 +10,7 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.GetUserBoard, error) { +func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) { if !bCase.isValidUsername(username) { return nil, ErrInvalidUsername } @@ -30,7 +30,14 @@ func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username str if loggedIn && currUserID == userID { isAuthor = true } - boards, err := bCase.boardRepo.GetBoardsByUserID(ctx, userID, isAuthor) + + contributorBoardsIDs, err := bCase.boardRepo.GetContributorBoardsIDs(ctx, currUserID) + if err != nil { + return nil, fmt.Errorf("get contributor boards in get boards by username usecase: %w", err) + } + + fmt.Println(currUserID, isAuthor, contributorBoardsIDs) + boards, err := bCase.boardRepo.GetBoardsByUserID(ctx, userID, isAuthor, contributorBoardsIDs) if err != nil { return nil, fmt.Errorf("get boards by user id usecase: %w", err) } @@ -38,28 +45,27 @@ func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username str return boards, nil } -func (bCase *BoardUsecase) GetCertainBoardByID(ctx context.Context, boardID int) (dto.GetUserBoard, error) { +func (bCase *BoardUsecase) GetCertainBoardByID(ctx context.Context, boardID int) (dto.UserBoard, error) { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) if err != nil { switch err { case repository.ErrNoData: - return dto.GetUserBoard{}, ErrNoSuchBoard + return dto.UserBoard{}, ErrNoSuchBoard default: - return dto.GetUserBoard{}, fmt.Errorf("get certain board by id: %w", err) + return dto.UserBoard{}, fmt.Errorf("get certain board by id: %w", err) } } boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) if err != nil { - return dto.GetUserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) + return dto.UserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) } boardContributorsIDs := make([]int, 0, len(boardContributors)) - func() { - for _, contributor := range boardContributors { - boardContributorsIDs = append(boardContributorsIDs, contributor.ID) - } - }() + + for _, contributor := range boardContributors { + boardContributorsIDs = append(boardContributorsIDs, contributor.ID) + } var hasAccess bool currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) @@ -67,14 +73,13 @@ func (bCase *BoardUsecase) GetCertainBoardByID(ctx context.Context, boardID int) hasAccess = true } - fmt.Println(loggedIn, currUserID, boardAuthorID, hasAccess) board, err := bCase.boardRepo.GetBoardByID(ctx, boardID, hasAccess) if err != nil { switch err { case repository.ErrNoData: - return dto.GetUserBoard{}, ErrNoSuchBoard + return dto.UserBoard{}, ErrNoSuchBoard default: - return dto.GetUserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) + return dto.UserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) } } diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go new file mode 100644 index 0000000..6e6f8d2 --- /dev/null +++ b/internal/pkg/usecase/board/update.go @@ -0,0 +1,11 @@ +package board + +import ( + "context" + + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" +) + +func (bCase *BoardUsecase) UpdateBoard(ctx context.Context, updatedData dto.BoardData) error { + return nil +} diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 98eab6f..0ad7919 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -11,9 +11,10 @@ import ( ) type Usecase interface { - CreateNewBoard(ctx context.Context, createBoardObj dto.CreateBoard) error - GetBoardsByUsername(ctx context.Context, username string) ([]dto.GetUserBoard, error) - GetCertainBoardByID(ctx context.Context, boardID int) (dto.GetUserBoard, error) + CreateNewBoard(ctx context.Context, createBoardObj dto.BoardData) error + GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) + GetCertainBoardByID(ctx context.Context, boardID int) (dto.UserBoard, error) + UpdateBoard(ctx context.Context, updatedData dto.BoardData) error // CheckBoardContributor(ctx context.Context, userID int) (bool, error) } From 47ff285f13c746074e8d7176bdaf425c78287e42 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 3 Nov 2023 22:29:50 +0300 Subject: [PATCH 118/266] TP-e3a add: getting user pins --- internal/api/server/router/router.go | 1 + internal/pkg/delivery/http/v1/pin.go | 28 +++++++++++++++++++++++++- internal/pkg/repository/pin/queries.go | 1 + internal/pkg/repository/pin/repo.go | 20 ++++++++++++++++++ internal/pkg/repository/ramrepo/pin.go | 4 ++++ internal/pkg/usecase/pin/usecase.go | 12 +++++++++++ 6 files changed, 65 insertions(+), 1 deletion(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e0f2b2d..14779e3 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -62,6 +62,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Get("/{pinID:\\d+}", handler.ViewPin) r.With(auth.RequireAuth).Group(func(r chi.Router) { + r.Get("/personal", handler.GetUserPins) r.Post("/create", handler.CreateNewPin) r.Post("/like/{pinID:\\d+}", handler.SetLikePin) r.Put("/edit/{pinID:\\d+}", handler.EditPin) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index abf10a2..267d15d 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -39,7 +39,7 @@ func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { if err != nil { h.log.Info("parse url query params", log.F{"error", err.Error()}) err = responseError(w, "bad_params", - "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)") + "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") } else { h.log.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectNewPins(r.Context(), count, minID, maxID) @@ -220,3 +220,29 @@ func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { h.log.Error(err.Error()) } } + +func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { + h.log.Info("request on view pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + SetContentTypeJSON(w) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) + if err != nil { + h.log.Info("parse url query params", log.F{"error", err.Error()}) + err = responseError(w, "bad_params", + "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") + } else { + h.log.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) + err = responseOk(w, "pins received are sorted by id", map[string]any{ + "pins": pins, + "minID": minID, + "maxID": maxID, + }) + } + if err != nil { + h.log.Error(err.Error()) + } + +} diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index b389bb4..e40e126 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -2,6 +2,7 @@ package pin var ( SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" + SelectUserPinsLimit = "SELECT id, picture, public FROM pin WHERE author = $1 AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" SelectPinByIDWithAuthor = `SELECT author, title, description, picture, public, pin.deleted_at, username, avatar diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 8541cb2..4b66c94 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -16,6 +16,7 @@ import ( type S map[string]any type Repository interface { GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) + GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) AddNewPin(ctx context.Context, pin *entity.Pin) error @@ -59,6 +60,25 @@ func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID i return pins, nil } +func (p *pinRepoPG) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) { + rows, err := p.db.Query(ctx, SelectUserPinsLimit, userID, minID, maxID, count) + if err != nil { + return nil, fmt.Errorf("select to receive %d pins: %w", count, err) + } + + pins := make([]entity.Pin, 0, count) + pin := entity.Pin{} + for rows.Next() { + err := rows.Scan(&pin.ID, &pin.Picture, &pin.Public) + if err != nil { + return pins, fmt.Errorf("scan to receive %d pins: %w", count, err) + } + pins = append(pins, pin) + } + + return pins, nil +} + func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) { pin := &entity.Pin{Author: &user.User{}} var err error diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 58be15e..13eacb0 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -80,3 +80,7 @@ func (r *ramPinRepo) GetCountLikeByPinID(ctx context.Context, pinID int) (int, e func (r *ramPinRepo) GetTagsByPinID(ctx context.Context, pinID int) ([]pin.Tag, error) { return nil, ErrMethodUnimplemented } + +func (r *ramPinRepo) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, error) { + return nil, ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index a8cf4fa..35d8954 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -19,6 +19,7 @@ var ErrBadMIMEType = errors.New("bad mime type") type Usecase interface { SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) + SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) error @@ -47,6 +48,17 @@ func (p *pinCase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([ return pins, pins[len(pins)-1].ID, pins[0].ID } +func (p *pinCase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) { + pins, err := p.repo.GetSortedUserPins(ctx, userID, count, minID, maxID) + if err != nil { + p.log.Error(err.Error()) + } + if len(pins) == 0 { + return []entity.Pin{}, minID, maxID + } + return pins, pins[len(pins)-1].ID, pins[0].ID +} + func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error { filename := uuid.New().String() dir := "upload/pins/" + time.Now().UTC().Format("2006/01/02/") From aa153ae4d12231a5bdf5f23ec6036590dbfcfe70 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sat, 4 Nov 2023 18:22:36 +0300 Subject: [PATCH 119/266] TP-ffc_handlers_board update: add UpdateBoard, DeleteBoard handlers, fixed previous changes --- internal/api/server/router/router.go | 8 +- internal/app/app.go | 2 +- internal/pkg/delivery/http/v1/board.go | 109 ++++++++++++++++-- .../pkg/repository/board/postgres/queries.go | 5 +- .../pkg/repository/board/postgres/repo.go | 35 ++++-- internal/pkg/repository/board/postgres/tag.go | 6 +- internal/pkg/repository/board/repo.go | 7 +- internal/pkg/repository/repo.go | 1 + internal/pkg/usecase/board/create.go | 13 ++- internal/pkg/usecase/board/delete.go | 33 ++++++ internal/pkg/usecase/board/dto/board.go | 1 + internal/pkg/usecase/board/errors.go | 1 + internal/pkg/usecase/board/get.go | 9 +- internal/pkg/usecase/board/update.go | 38 +++++- internal/pkg/usecase/board/usecase.go | 8 +- internal/pkg/usecase/board/validation.go | 4 +- 16 files changed, 233 insertions(+), 47 deletions(-) create mode 100644 internal/pkg/usecase/board/delete.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e127b4e..d373fbe 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -72,11 +72,15 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) r.Route("/board", func(r chi.Router) { - r.Get("/{username}", handler.GetUserBoards) - r.Get("/get/{boardID:\\d+}", handler.GetCertainBoard) + r.Route("/get", func(r chi.Router) { + r.Get("/user/{username}", handler.GetUserBoards) + r.Get("/{boardID:\\d+}", handler.GetCertainBoard) + }) r.Group(func(r chi.Router) { r.Use(auth.RequireAuth) r.Post("/create", handler.CreateNewBoard) + r.Put("/update/{boardID:\\d+}", handler.UpdateBoardInfo) + r.Delete("/delete/{boardID:\\d+}", handler.DeleteBoard) }) }) }) diff --git a/internal/app/app.go b/internal/app/app.go index 7690aef..723e54b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -26,7 +26,7 @@ func Run(ctx context.Context, log *log.Logger, configFile string) { ctxApp, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - pool, err := pgxpool.New(ctxApp, "postgres://exy:password@localhost:5432/pinspire?search_path=pinspire") // change creds + pool, err := pgxpool.New(ctxApp, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") if err != nil { log.Error(err.Error()) return diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 165c0ec..43e9662 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -3,7 +3,6 @@ package v1 import ( "encoding/json" "errors" - "fmt" "net/http" "strconv" @@ -27,9 +26,9 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { responseError(w, BadBodyCode, BadBodyMessage) return } - fmt.Println(newBoard) + newBoard.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) - err = h.boardCase.CreateNewBoard(r.Context(), newBoard) + newBoardID, err := h.boardCase.CreateNewBoard(r.Context(), newBoard) if err != nil { h.log.Info("create board", logger.F{"message", err.Error()}) switch err { @@ -46,13 +45,14 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(w, "new board was created successfully", nil) + err = responseOk(w, "new board was created successfully", map[string]int{"new_board_id": newBoardID}) if err != nil { h.log.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) return } - h.log.Info("create new board request: successfull respond") + h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { @@ -75,10 +75,11 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { err = responseOk(w, "got user boards successfully", userBoards) if err != nil { h.log.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) return } - h.log.Info("get user boards request: successfull respond") + h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { @@ -92,7 +93,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { return } - board, err := h.boardCase.GetCertainBoardByID(r.Context(), int(boardID)) + board, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) if err != nil { h.log.Info("get certain board: ", logger.F{"message", err.Error()}) switch err { @@ -100,6 +101,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { responseError(w, "no_board", err.Error()) default: responseError(w, InternalErrorCode, InternalServerErrMessage) + w.WriteHeader(http.StatusInternalServerError) } return } @@ -107,9 +109,100 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { err = responseOk(w, "got certain board successfully", board) if err != nil { h.log.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(InternalServerErrMessage)) + return + } + + h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) +} + +func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { + h.log.Info("request to update certain board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) + SetContentTypeJSON(w) + + boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) + if err != nil { + h.log.Info("update certain board ", logger.F{"message", err.Error()}) + responseError(w, BadQueryParamCode, BadQueryParamMessage) + return + } + + var updatedBoard boardDTO.BoardData + err = json.NewDecoder(r.Body).Decode(&updatedBoard) + defer r.Body.Close() + if err != nil { + h.log.Info("update certain board: ", logger.F{"message", err.Error()}) + responseError(w, BadBodyCode, BadBodyMessage) + return + } + updatedBoard.ID = int(boardID) + + err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard) + if err != nil { + h.log.Info("update certain board: ", logger.F{"message", err.Error()}) + switch err { + case bCase.ErrNoSuchBoard: + responseError(w, "no_board", err.Error()) + case bCase.ErrNoAccess: + responseError(w, "no_access", err.Error()) + case bCase.ErrInvalidBoardTitle: + responseError(w, "bad_boardTitle", err.Error()) + default: + if errors.Is(err, bCase.ErrInvalidTagTitles) { + responseError(w, "bad_tagTitles", err.Error()) + return + } + responseError(w, InternalErrorCode, InternalServerErrMessage) + w.WriteHeader(http.StatusInternalServerError) + } + return + } + + err = responseOk(w, "updated certain board successfully", nil) + if err != nil { + h.log.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(InternalServerErrMessage)) + return + } + + h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) +} + +func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { + h.log.Info("request to delete board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) + SetContentTypeJSON(w) + + boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) + if err != nil { + h.log.Info("delete board ", logger.F{"message", err.Error()}) + responseError(w, BadQueryParamCode, BadQueryParamMessage) + return + } + + err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) + if err != nil { + h.log.Info("delete board: ", logger.F{"message", err.Error()}) + switch err { + case bCase.ErrNoSuchBoard: + responseError(w, "no_board", err.Error()) + case bCase.ErrNoAccess: + responseError(w, "no_access", err.Error()) + default: + responseError(w, InternalErrorCode, InternalServerErrMessage) + w.WriteHeader(http.StatusInternalServerError) + } + return + } + + err = responseOk(w, "deleted board successfully", nil) + if err != nil { + h.log.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) return } - h.log.Info("get certain board request: successfull respond") + h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index 8baffaf..dec7a1a 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -2,8 +2,9 @@ package board const ( InsertBoardQuery = "INSERT INTO board (author, title, description, public) VALUES ($1, $2, $3, $4) RETURNING id;" - SelectBoardAuthorByBoardIdQuery = "SELECT author FROM board WHERE id = $1;" + SelectBoardAuthorByBoardIdQuery = "SELECT author FROM board WHERE id = $1 AND deleted_at IS NULL;" SelectBoardContributorsByBoardIdQuery = "SELECT user_id FROM contributor WHERE board_id = $1;" - UpdateBoardByIdQuery = "UPDATE board SET title = $1, description = $2, public = $3 WHERE id = $4;" + UpdateBoardByIdQuery = "UPDATE board SET title = $1, description = $2, public = $3 WHERE id = $4 AND deleted_at IS NULL;" GetContributorBoardsIDs = "SELECT board_id FROM contributor WHERE user_id = $1;" + DeleteBoardByIdQuery = "UPDATE board SET deleted_at = $1 WHERE id = $2;" ) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index dcfcae3..4b8e658 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -3,6 +3,7 @@ package board import ( "context" "fmt" + "time" "github.com/Masterminds/squirrel" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" @@ -22,34 +23,34 @@ func NewBoardRepoPG(db *pgxpool.Pool) *BoardRepoPG { return &BoardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} } -func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error { +func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) { tx, err := repo.db.Begin(ctx) if err != nil { - return fmt.Errorf("starting transaction for creating new board: %w", err) + return 0, fmt.Errorf("starting transaction for creating new board: %w", err) } newBoardId, err := repo.insertBoard(ctx, tx, board) if err != nil { tx.Rollback(ctx) - return fmt.Errorf("inserting board within transaction: %w", err) + return 0, fmt.Errorf("inserting board within transaction: %w", err) } err = repo.insertTags(ctx, tx, tagTitles) if err != nil { tx.Rollback(ctx) - return fmt.Errorf("inserting new tags within transaction: %w", err) + return 0, fmt.Errorf("inserting new tags within transaction: %w", err) } err = repo.addTagsToBoard(ctx, tx, tagTitles, newBoardId, true) if err != nil { tx.Rollback(ctx) - return fmt.Errorf("adding new tags on board within transaction: %w", err) + return 0, fmt.Errorf("adding new tags on board within transaction: %w", err) } tx.Commit(ctx) - return nil + return newBoardId, nil } func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { @@ -226,19 +227,37 @@ func (repo *BoardRepoPG) UpdateBoard(ctx context.Context, newBoardData entity.Bo err = repo.addTagsToBoard(ctx, tx, tagTitles, newBoardData.ID, false) if err != nil { tx.Rollback(ctx) - return fmt.Errorf("update board: add tags to board within transsaction - %w", err) + return fmt.Errorf("update board: add tags to board within transaction - %w", err) } - _, err = repo.db.Exec(ctx, UpdateBoardByIdQuery, newBoardData.Title, newBoardData.Description, newBoardData.Public, newBoardData.ID) + status, err := repo.db.Exec(ctx, UpdateBoardByIdQuery, newBoardData.Title, newBoardData.Description, newBoardData.Public, newBoardData.ID) if err != nil { tx.Rollback(ctx) return fmt.Errorf("update board: edit board data within transaction - %w", err) } + if status.RowsAffected() == 0 { + tx.Rollback(ctx) + return repository.ErrNoData + } + tx.Commit(ctx) return nil } +func (repo *BoardRepoPG) DeleteBoardByID(ctx context.Context, boardID int) error { + status, err := repo.db.Exec(ctx, DeleteBoardByIdQuery, time.Now(), boardID) + if err != nil { + return fmt.Errorf("delete board by id: %w", err) + } + + if status.RowsAffected() == 0 { + return repository.ErrNoDataAffected + } + + return nil +} + func (repo *BoardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { row := tx.QueryRow(ctx, InsertBoardQuery, board.AuthorID, board.Title, board.Description, board.Public) diff --git a/internal/pkg/repository/board/postgres/tag.go b/internal/pkg/repository/board/postgres/tag.go index 626c583..c6f3aee 100644 --- a/internal/pkg/repository/board/postgres/tag.go +++ b/internal/pkg/repository/board/postgres/tag.go @@ -41,7 +41,7 @@ func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitle ) if !isNewBoard { - addTagsToBoardQuery.Suffix("ON CONFLICT DO NOTHING") + addTagsToBoardQuery = addTagsToBoardQuery.Suffix("ON CONFLICT DO NOTHING") } sqlRow, args, err := addTagsToBoardQuery.ToSql() @@ -49,12 +49,12 @@ func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitle return fmt.Errorf("building sql query row for adding tags to board: %w", err) } - cmdTag, err := tx.Exec(ctx, sqlRow, args...) + status, err := tx.Exec(ctx, sqlRow, args...) if err != nil { return fmt.Errorf("execute sql query to add tags to board: %w", err) } - if isNewBoard && int(cmdTag.RowsAffected()) != len(tagTitles) { + if isNewBoard && int(status.RowsAffected()) != len(tagTitles) { return ErrIncorrectNumberRowsAffcted } diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index bbacc5d..88c6eae 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -9,15 +9,12 @@ import ( ) type Repository interface { - CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error + CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error - // AddContributor - // GetBoardsByTitle(ctx context.Context, title string) ([]entity.Board, error) - // GetBoardsByTag(ctx context.Context, tagTitle string) ([]entity.Board, error) - // GetBoardTags(ctx context.Context, boardID int) (tagTitles []string, err error) + DeleteBoardByID(ctx context.Context, boardID int) error } diff --git a/internal/pkg/repository/repo.go b/internal/pkg/repository/repo.go index b0142f0..eb11314 100644 --- a/internal/pkg/repository/repo.go +++ b/internal/pkg/repository/repo.go @@ -9,4 +9,5 @@ const ( var ( ErrMethodUnimplemented = errors.New("unimplemented") ErrNoData = errors.New("got no data from repository layer") + ErrNoDataAffected = errors.New("no repository data affected by affecting query") ) diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go index 2871e16..2425511 100644 --- a/internal/pkg/usecase/board/create.go +++ b/internal/pkg/usecase/board/create.go @@ -8,15 +8,16 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) error { +func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) { if !bCase.isValidBoardTitle(newBoard.Title) { - return ErrInvalidBoardTitle + return 0, ErrInvalidBoardTitle } if err := bCase.checkIsValidTagTitles(newBoard.TagTitles); err != nil { - return fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) + return 0, fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) } + bCase.sanitizer.Sanitize(newBoard.Description) - err := bCase.boardRepo.CreateBoard(ctx, entity.Board{ + newBoardID, err := bCase.boardRepo.CreateBoard(ctx, entity.Board{ AuthorID: newBoard.AuthorID, Title: newBoard.Title, Description: newBoard.Description, @@ -24,7 +25,7 @@ func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.Boar }, newBoard.TagTitles) if err != nil { - return fmt.Errorf("create new board usecase: %w", err) + return 0, fmt.Errorf("create new board: %w", err) } - return nil + return newBoardID, nil } diff --git a/internal/pkg/usecase/board/delete.go b/internal/pkg/usecase/board/delete.go new file mode 100644 index 0000000..2e74abb --- /dev/null +++ b/internal/pkg/usecase/board/delete.go @@ -0,0 +1,33 @@ +package board + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" +) + +func (bCase *BoardUsecase) DeleteCertainBoard(ctx context.Context, boardID int) error { + boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) + if err != nil { + switch err { + case repository.ErrNoData: + return ErrNoSuchBoard + default: + return fmt.Errorf("delete certain board: %w", err) + } + } + + currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) + if !(loggedIn && currUserID == boardAuthorID) { + return ErrNoAccess + } + + err = bCase.boardRepo.DeleteBoardByID(ctx, boardID) + if err != nil { + return fmt.Errorf("delete certain board: %w", err) + } + + return nil +} diff --git a/internal/pkg/usecase/board/dto/board.go b/internal/pkg/usecase/board/dto/board.go index 82d9e8b..6eb8821 100644 --- a/internal/pkg/usecase/board/dto/board.go +++ b/internal/pkg/usecase/board/dto/board.go @@ -1,6 +1,7 @@ package board type BoardData struct { + ID int `json:"board_id,omitempty" example:"33"` Title string `json:"title" example:"Sunny places"` Description string `json:"description" example:"long description"` AuthorID int `json:"author_id,omitempty" example:"45"` diff --git a/internal/pkg/usecase/board/errors.go b/internal/pkg/usecase/board/errors.go index 754ca01..5d4c86b 100644 --- a/internal/pkg/usecase/board/errors.go +++ b/internal/pkg/usecase/board/errors.go @@ -8,4 +8,5 @@ var ( ErrInvalidBoardTitle = errors.New("invalid or empty board title has been provided") ErrInvalidTagTitles = errors.New("invalid tag titles have been provided") ErrInvalidUserID = errors.New("invalid user id has been provided") + ErrNoAccess = errors.New("no access for this action") ) diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index 6175c64..4831710 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -36,7 +36,6 @@ func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username str return nil, fmt.Errorf("get contributor boards in get boards by username usecase: %w", err) } - fmt.Println(currUserID, isAuthor, contributorBoardsIDs) boards, err := bCase.boardRepo.GetBoardsByUserID(ctx, userID, isAuthor, contributorBoardsIDs) if err != nil { return nil, fmt.Errorf("get boards by user id usecase: %w", err) @@ -45,20 +44,20 @@ func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username str return boards, nil } -func (bCase *BoardUsecase) GetCertainBoardByID(ctx context.Context, boardID int) (dto.UserBoard, error) { +func (bCase *BoardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) if err != nil { switch err { case repository.ErrNoData: return dto.UserBoard{}, ErrNoSuchBoard default: - return dto.UserBoard{}, fmt.Errorf("get certain board by id: %w", err) + return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) } } boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) if err != nil { - return dto.UserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) + return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) } boardContributorsIDs := make([]int, 0, len(boardContributors)) @@ -79,7 +78,7 @@ func (bCase *BoardUsecase) GetCertainBoardByID(ctx context.Context, boardID int) case repository.ErrNoData: return dto.UserBoard{}, ErrNoSuchBoard default: - return dto.UserBoard{}, fmt.Errorf("get certain board by id usecase: %w", err) + return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) } } diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go index 6e6f8d2..c5c67d6 100644 --- a/internal/pkg/usecase/board/update.go +++ b/internal/pkg/usecase/board/update.go @@ -2,10 +2,46 @@ package board import ( "context" + "fmt" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) UpdateBoard(ctx context.Context, updatedData dto.BoardData) error { +func (bCase *BoardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error { + boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, updatedData.ID) + if err != nil { + switch err { + case repository.ErrNoData: + return ErrNoSuchBoard + default: + return fmt.Errorf("update certain board: %w", err) + } + } + + currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) + if !(loggedIn && currUserID == boardAuthorID) { + return ErrNoAccess + } + + if !bCase.isValidBoardTitle(updatedData.Title) { + return ErrInvalidBoardTitle + } + if err := bCase.checkIsValidTagTitles(updatedData.TagTitles); err != nil { + return fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) + } + bCase.sanitizer.Sanitize(updatedData.Description) + + err = bCase.boardRepo.UpdateBoard(ctx, board.Board{ + ID: updatedData.ID, + Title: updatedData.Title, + Description: updatedData.Description, + Public: updatedData.Public, + }, updatedData.TagTitles) + if err != nil { + return fmt.Errorf("update certain board: %w", err) + } return nil } diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 0ad7919..f9884df 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -11,11 +11,11 @@ import ( ) type Usecase interface { - CreateNewBoard(ctx context.Context, createBoardObj dto.BoardData) error + CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) - GetCertainBoardByID(ctx context.Context, boardID int) (dto.UserBoard, error) - UpdateBoard(ctx context.Context, updatedData dto.BoardData) error - // CheckBoardContributor(ctx context.Context, userID int) (bool, error) + GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) + UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error + DeleteCertainBoard(ctx context.Context, boardID int) error } type BoardUsecase struct { diff --git a/internal/pkg/usecase/board/validation.go b/internal/pkg/usecase/board/validation.go index d6936c1..a80e929 100644 --- a/internal/pkg/usecase/board/validation.go +++ b/internal/pkg/usecase/board/validation.go @@ -11,7 +11,7 @@ func (bCase *BoardUsecase) isValidTagTitle(title string) bool { } for _, sym := range title { - if !(unicode.IsNumber(sym) || unicode.IsLetter(sym)) { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { return false } } @@ -36,7 +36,7 @@ func (bCase *BoardUsecase) checkIsValidTagTitles(titles []string) error { } func (bCase *BoardUsecase) isValidBoardTitle(title string) bool { - if len(title) == 0 || len(title) > 20 { + if len(title) == 0 || len(title) > 40 { return false } for _, sym := range title { From 7f7ef4fefc59a916172436c5015278479a9c822e Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 4 Nov 2023 22:58:52 +0300 Subject: [PATCH 120/266] TP-a9b add: pkg/validator --- go.mod | 18 +++++- go.sum | 55 +++++++++++++++-- internal/app/app.go | 5 +- internal/pkg/delivery/http/v1/auth_test.go | 8 +-- internal/pkg/delivery/http/v1/handler.go | 5 +- internal/pkg/delivery/http/v1/pin.go | 16 ++++- internal/pkg/delivery/http/v1/pin_test.go | 2 +- internal/pkg/delivery/http/v1/profile.go | 12 +++- internal/pkg/repository/image/repo.go | 71 ++++++++++++++++++++++ internal/pkg/usecase/image/usecase.go | 50 +++++++++++++++ internal/pkg/usecase/pin/usecase.go | 37 ++--------- internal/pkg/usecase/user/profile.go | 37 +---------- internal/pkg/usecase/user/usecase.go | 3 +- pkg/validator/image/jpeg/valid_jpg.go | 17 ++++++ pkg/validator/image/png/valid_png.go | 18 ++++++ pkg/validator/image/svg/valid_svg.go | 20 ++++++ pkg/validator/image/valid_img.go | 34 +++++++++++ pkg/validator/image/webp/valid_webp.go | 18 ++++++ pkg/validator/validator.go | 51 ++++++++++++++++ 19 files changed, 389 insertions(+), 88 deletions(-) create mode 100644 internal/pkg/repository/image/repo.go create mode 100644 internal/pkg/usecase/image/usecase.go create mode 100644 pkg/validator/image/jpeg/valid_jpg.go create mode 100644 pkg/validator/image/png/valid_png.go create mode 100644 pkg/validator/image/svg/valid_svg.go create mode 100644 pkg/validator/image/valid_img.go create mode 100644 pkg/validator/image/webp/valid_webp.go create mode 100644 pkg/validator/validator.go diff --git a/go.mod b/go.mod index 3ef4676..698a802 100644 --- a/go.mod +++ b/go.mod @@ -14,20 +14,29 @@ require ( github.com/stretchr/testify v1.8.4 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.2 + github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf go.uber.org/config v1.4.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 + golang.org/x/image v0.13.0 ) require ( + github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/benoitkugler/textlayout v0.3.0 // indirect + github.com/benoitkugler/textprocessing v0.0.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/go-fonts/latin-modern v0.3.1 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect @@ -38,13 +47,16 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect + github.com/tdewolff/minify/v2 v2.20.5 // indirect + github.com/tdewolff/parse/v2 v2.7.3 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + star-tex.org/x/tex v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index ae7a898..dd5079d 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,23 @@ +git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= +github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= +github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= +github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= +github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= +github.com/benoitkugler/textprocessing v0.0.3 h1:Q2X+Z6vxuW5Bxn1R9RaNt0qcprBfpc2hEUDeTlz90Ng= +github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -15,10 +26,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM= +github.com/go-fonts/latin-modern v0.3.1/go.mod h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0= +github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= +github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -34,6 +52,12 @@ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= +github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d h1:HrdwTlHVMdi9nOW7ZnYiLmIT1hJHvipIwM0aX3rKn8I= +github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= +github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -48,6 +72,8 @@ github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFr github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -96,6 +122,15 @@ github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64 github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf h1:SiFIPDecTcEpw8GTDi2NsX0Cedp+GizeammteylMvmE= +github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf/go.mod h1:QT5dPLAqJLD02UXi9Dd2BkCBV4i3zh+dVxITYpd7q2Y= +github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI= +github.com/tdewolff/minify/v2 v2.20.5/go.mod h1:N78HtaitkDYAWXFbqhWX/LzgwylwudK0JvybGDVQ+Mw= +github.com/tdewolff/parse/v2 v2.7.3 h1:SHj/ry85FdqniccvzJTG+Gt/mi/HNa1cJcTzYZnvc5U= +github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= +github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= @@ -108,8 +143,11 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= +golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -117,9 +155,10 @@ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -127,12 +166,13 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -144,6 +184,7 @@ golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -159,3 +200,5 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= +star-tex.org/x/tex v0.4.0 h1:AXUwgpnHLCxZUWW3qrmjv6ezNhH3PjUVBuLLejz2cgU= +star-tex.org/x/tex v0.4.0/go.mod h1:w91ycsU/DkkCr7GWr60GPWqp3gn2U+6VX71T0o8k8qE= diff --git a/internal/app/app.go b/internal/app/app.go index 18d4312..afd2ffe 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,9 +10,11 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -50,8 +52,9 @@ func Run(ctx context.Context, log *log.Logger, configFile string) { sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) userCase := user.New(log, userRepo.NewUserRepoPG(pool)) pinCase := pin.New(log, pinRepo.NewPinRepoPG(pool)) + imgCase := image.New(log, imgRepo.NewImageRepoFS("upload/")) - handler := deliveryHTTP.New(log, sm, userCase, pinCase) + handler := deliveryHTTP.New(log, sm, userCase, pinCase, imgCase) cfgServ, err := server.NewConfig(configFile) if err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go index 6f35a78..9fbdae5 100644 --- a/internal/pkg/delivery/http/v1/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -38,7 +38,7 @@ func TestCheckLogin(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) url := "https://domain.test:8080/api/v1/login" goodCases := []struct { @@ -144,7 +144,7 @@ func TestLogin(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string @@ -280,7 +280,7 @@ func TestSignUp(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string @@ -396,7 +396,7 @@ func TestLogout(t *testing.T) { sm := session.New(log, ramrepo.NewRamSessionRepo(db)) userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index ab66db9..ecf8321 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -11,14 +12,16 @@ type HandlerHTTP struct { log *logger.Logger userCase user.Usecase pinCase pin.Usecase + imgCase image.Usecase sm session.SessionManager } -func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase) *HandlerHTTP { +func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, img image.Usecase) *HandlerHTTP { return &HandlerHTTP{ log: log, userCase: user, pinCase: pin, + imgCase: img, sm: sm, } } diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index abf10a2..6a1cffd 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -101,12 +101,24 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { picture, mime, err := r.FormFile("picture") if err != nil { - responseError(w, "bad_body", "unable to get an image from the request body") + err = responseError(w, "bad_body", "unable to get an image from the request body") + if err != nil { + h.log.Error(err.Error()) + } return } defer picture.Close() - err = h.pinCase.CreateNewPin(r.Context(), newPin, picture, mime.Header.Get("Content-Type")) + newPin.Picture, err = h.imgCase.UploadImage("pins/", mime.Header.Get("Content-Type"), mime.Size, picture) + if err != nil { + err = responseError(w, "bad_body", "failed to upload the file received in the body") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + err = h.pinCase.CreateNewPin(r.Context(), newPin) if err != nil { h.log.Error(err.Error()) err = responseError(w, "add_pin", "failed to create pin") diff --git a/internal/pkg/delivery/http/v1/pin_test.go b/internal/pkg/delivery/http/v1/pin_test.go index 058651a..dce223f 100644 --- a/internal/pkg/delivery/http/v1/pin_test.go +++ b/internal/pkg/delivery/http/v1/pin_test.go @@ -24,7 +24,7 @@ func TestGetPins(t *testing.T) { defer db.Close() pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) - service := New(log, nil, nil, pinCase) + service := New(log, nil, nil, pinCase, nil) rawUrl := "https://domain.test:8080/api/v1/pin" goodCases := []struct { diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 6aea528..9bd6546 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -78,7 +78,17 @@ func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) defer r.Body.Close() - err := h.userCase.UpdateUserAvatar(r.Context(), userID, r.Body, r.Header.Get("Content-Type")) + avatar, err := h.imgCase.UploadImage("avatars/", r.Header.Get("Content-Type"), r.ContentLength, r.Body) + if err != nil { + h.log.Error(err.Error()) + err = responseError(w, "edit_avatar", "file upload failed") + if err != nil { + h.log.Error(err.Error()) + } + return + } + + err = h.userCase.UpdateUserAvatar(r.Context(), userID, avatar) if err != nil { h.log.Error(err.Error()) err = responseError(w, "edit_avatar", "failed to change user's avatar") diff --git a/internal/pkg/repository/image/repo.go b/internal/pkg/repository/image/repo.go new file mode 100644 index 0000000..a4e3e8f --- /dev/null +++ b/internal/pkg/repository/image/repo.go @@ -0,0 +1,71 @@ +package image + +import ( + "fmt" + "io" + "os" + "sync" + "time" + + "github.com/google/uuid" +) + +type Repository interface { + SaveImage(prefixPath, extension string, image io.Reader) (filename string, written int64, err error) + SetBasePath(path string) + SetDirToSave(fn func() string) +} + +type imageRepoFS struct { + basePath string + m sync.Mutex + directoryToSave func() string +} + +func NewImageRepoFS(basePath string) *imageRepoFS { + return &imageRepoFS{ + basePath: basePath, + m: sync.Mutex{}, + directoryToSave: dirTosave, + } +} + +func (img *imageRepoFS) SaveImage(prefixPath, extension string, image io.Reader) (filename string, written int64, err error) { + id, err := uuid.NewUUID() + if err != nil { + return "", 0, fmt.Errorf("generate new filename by uuid for save file: %w", err) + } + filename = id.String() + + dir := img.basePath + prefixPath + img.directoryToSave() + err = os.MkdirAll(dir, 0750) + if err != nil { + return "", 0, fmt.Errorf("mkdir %s to save file: %w", dir, err) + } + + filePath := dir + filename + "." + extension + file, err := os.Create(filePath) + if err != nil { + return "", 0, fmt.Errorf("create %s to save file: %w", filePath, err) + } + defer file.Close() + + written, err = io.Copy(file, image) + return +} + +func (img *imageRepoFS) SetBasePath(path string) { + img.m.Lock() + img.basePath = path + img.m.Unlock() +} + +func (img *imageRepoFS) SetDirToSave(fn func() string) { + img.m.Lock() + img.directoryToSave = fn + img.m.Unlock() +} + +func dirTosave() string { + return time.Now().UTC().Format("2006/01/02/") +} diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go new file mode 100644 index 0000000..67e79b0 --- /dev/null +++ b/internal/pkg/usecase/image/usecase.go @@ -0,0 +1,50 @@ +package image + +import ( + "bytes" + "errors" + "fmt" + "io" + + repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + valid "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image" +) + +const PrefixURLImage = "httsp://pinspire.online:8081/" + +var ErrInvalidImage = errors.New("invalid images") +var ErrUploadFile = errors.New("file upload failed") + +type Usecase interface { + UploadImage(path string, mimeType string, size int64, image io.Reader) (string, error) +} + +type imageCase struct { + log *log.Logger + repo repo.Repository +} + +func New(log *log.Logger, repo repo.Repository) *imageCase { + return &imageCase{log, repo} +} + +func (img *imageCase) UploadImage(path string, mimeType string, size int64, image io.Reader) (string, error) { + buf := bytes.NewBuffer(nil) + + extension, ok := valid.IsValidImage(io.TeeReader(image, buf), mimeType) + if !ok { + return "", ErrInvalidImage + } + + io.Copy(buf, image) + + filename, written, err := img.repo.SaveImage(path, extension, buf) + if err != nil { + return "", fmt.Errorf("upload image: %w", err) + } + if written != size { + return "", ErrUploadFile + } + return filename, nil +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index a8cf4fa..151e775 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -4,22 +4,17 @@ import ( "context" "errors" "fmt" - "io" - "os" - "strings" - "time" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/google/uuid" ) var ErrBadMIMEType = errors.New("bad mime type") type Usecase interface { SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) - CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error + CreateNewPin(ctx context.Context, pin *entity.Pin) error DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) error DeleteLikeFromUser(ctx context.Context, pinID, userID int) error @@ -47,34 +42,10 @@ func (p *pinCase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([ return pins, pins[len(pins)-1].ID, pins[0].ID } -func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, picture io.Reader, mimeType string) error { - filename := uuid.New().String() - dir := "upload/pins/" + time.Now().UTC().Format("2006/01/02/") - err := os.MkdirAll(dir, 0750) - if err != nil { - return fmt.Errorf("create dir for upload file: %w", err) - } - piecesMimeType := strings.Split(mimeType, "/") - if len(piecesMimeType) != 2 || piecesMimeType[0] != "image" { - return ErrBadMIMEType - } - - filePath := dir + filename + "." + piecesMimeType[1] - file, err := os.Create(filePath) - if err != nil { - return fmt.Errorf("create %s to save avatar to it: %w", filePath, err) - } - defer file.Close() - - _, err = io.Copy(file, picture) - if err != nil { - return fmt.Errorf("copy avatar in file %s: %w", filePath, err) - } - p.log.Info("upload file", log.F{"file", filePath}) - - pin.Picture = "https://pinspire.online:8081/" + filePath +func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin) error { + pin.Picture = "https://pinspire.online:8081/" + pin.Picture - err = p.repo.AddNewPin(ctx, pin) + err := p.repo.AddNewPin(ctx, pin) if err != nil { return fmt.Errorf("add new pin: %w", err) } diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 5889224..7940284 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -4,47 +4,16 @@ import ( "context" "errors" "fmt" - "io" - "os" - "strings" - "time" - - "github.com/google/uuid" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -var ErrBadMIMEType = errors.New("bad mime type") - -func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error { - filename := uuid.New().String() - dir := "upload/avatars/" + time.Now().UTC().Format("2006/01/02/") - err := os.MkdirAll(dir, 0750) - if err != nil { - return fmt.Errorf("create dir for upload file: %w", err) - } - piecesMimeType := strings.Split(mimeType, "/") - if len(piecesMimeType) != 2 || piecesMimeType[0] != "image" { - return ErrBadMIMEType - } - - filePath := dir + filename + "." + piecesMimeType[1] - file, err := os.Create(filePath) - if err != nil { - return fmt.Errorf("create %s to save avatar to it: %w", filePath, err) - } - defer file.Close() - - _, err = io.Copy(file, avatar) - if err != nil { - return fmt.Errorf("copy avatar in file %s: %w", filePath, err) - } - u.log.Info("upload file", log.F{"file", filePath}) +var ErrBadBody = errors.New("bad body avatar") - err = u.repo.EditUserAvatar(ctx, userID, "https://pinspire.online:8081/"+filePath) +func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar string) error { + err := u.repo.EditUserAvatar(ctx, userID, "https://pinspire.online:8081/"+avatar) if err != nil { return fmt.Errorf("edit user avatar: %w", err) } diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index fa7b429..9d96941 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -3,7 +3,6 @@ package user import ( "context" "errors" - "io" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" @@ -21,7 +20,7 @@ type Usecase interface { Register(ctx context.Context, user *entity.User) error Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) - UpdateUserAvatar(ctx context.Context, userID int, avatar io.Reader, mimeType string) error + UpdateUserAvatar(ctx context.Context, userID int, avatar string) error GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error } diff --git a/pkg/validator/image/jpeg/valid_jpg.go b/pkg/validator/image/jpeg/valid_jpg.go new file mode 100644 index 0000000..d253990 --- /dev/null +++ b/pkg/validator/image/jpeg/valid_jpg.go @@ -0,0 +1,17 @@ +package jpeg + +import ( + "image/jpeg" + "io" +) + +func IsValidJPEG(r io.Reader) bool { + cfg, err := jpeg.DecodeConfig(r) + if err != nil { + return false + } + if cfg.Height < 200 || cfg.Width < 200 || cfg.Height > 600 || cfg.Width > 600 { + return false + } + return true +} diff --git a/pkg/validator/image/png/valid_png.go b/pkg/validator/image/png/valid_png.go new file mode 100644 index 0000000..f08d583 --- /dev/null +++ b/pkg/validator/image/png/valid_png.go @@ -0,0 +1,18 @@ +package png + +import ( + "image/png" + "io" +) + +func IsValidPNG(r io.Reader) bool { + cfg, err := png.DecodeConfig(r) + if err != nil { + return false + } + + if cfg.Height < 200 || cfg.Width < 200 || cfg.Height > 600 || cfg.Width > 600 { + return false + } + return true +} diff --git a/pkg/validator/image/svg/valid_svg.go b/pkg/validator/image/svg/valid_svg.go new file mode 100644 index 0000000..1ec9499 --- /dev/null +++ b/pkg/validator/image/svg/valid_svg.go @@ -0,0 +1,20 @@ +package svg + +import ( + "io" + + "github.com/tdewolff/canvas" +) + +func IsValidSVG(r io.Reader) bool { + can, err := canvas.ParseSVG(r) + if err != nil { + return false + } + + if can.H < 200 || can.W < 200 || can.H > 600 || can.W > 600 { + return false + } + + return true +} diff --git a/pkg/validator/image/valid_img.go b/pkg/validator/image/valid_img.go new file mode 100644 index 0000000..20880c0 --- /dev/null +++ b/pkg/validator/image/valid_img.go @@ -0,0 +1,34 @@ +package image + +import ( + "fmt" + "io" + "strings" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/jpeg" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/png" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/svg" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/webp" +) + +func IsValidImage(image io.Reader, mimeType string) (string, bool) { + if !strings.HasPrefix(mimeType, "image/") { + return "", false + } + mimeType = mimeType[6:] + + switch { + case strings.HasPrefix(mimeType, "jpeg"): + return "jpg", jpeg.IsValidJPEG(image) + case strings.HasPrefix(mimeType, "png"): + return "png", png.IsValidPNG(image) + case strings.HasPrefix(mimeType, "svg"): + fmt.Println("SVG") + return "svg", svg.IsValidSVG(image) + case strings.HasPrefix(mimeType, "webp"): + return "webp", webp.IsValidWEBP(image) + default: + return "", false + } + +} diff --git a/pkg/validator/image/webp/valid_webp.go b/pkg/validator/image/webp/valid_webp.go new file mode 100644 index 0000000..b2a6f9d --- /dev/null +++ b/pkg/validator/image/webp/valid_webp.go @@ -0,0 +1,18 @@ +package webp + +import ( + "io" + + "golang.org/x/image/webp" +) + +func IsValidWEBP(r io.Reader) bool { + cfg, err := webp.DecodeConfig(r) + if err != nil { + return false + } + if cfg.Height < 200 || cfg.Width < 200 || cfg.Height > 600 || cfg.Width > 600 { + return false + } + return true +} diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go new file mode 100644 index 0000000..e121725 --- /dev/null +++ b/pkg/validator/validator.go @@ -0,0 +1,51 @@ +package validator + +import "strings" + +type ( + CheckRune func(r rune) bool + CheckLen func(length int) bool +) + +func InRange(left, right int) CheckLen { + return func(length int) bool { + return left <= length && length <= right + } +} + +func Less(maxLength int) CheckLen { + return func(length int) bool { + return length <= maxLength + } +} + +func IsValidString(str string, validLen CheckLen, validRune ...CheckRune) bool { + if !validLen(len(str)) { + return false + } + + for _, r := range str { + for _, check := range validRune { + if !check(r) { + return false + } + } + } + return true +} + +func IsValidListFromString(str, sep string, validLenEveryone CheckLen, maxLenList int, + validRuneEveryone ...CheckRune) ([]string, bool) { + + list := strings.Split(str, sep) + if len(list) > maxLenList { + return nil, false + } + + for _, part := range list { + if !IsValidString(part, validLenEveryone, validRuneEveryone...) { + return nil, false + } + } + return list, true +} From cb54f6059dec316be9fcb705f379b5a4cc6ec662 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sat, 4 Nov 2023 23:35:27 +0300 Subject: [PATCH 121/266] TP-ffc_handlers_board update: fixed GetBoardsByUserID, GetBoardByID in board repository --- internal/pkg/repository/board/postgres/repo.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 4b8e658..d296510 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -61,9 +61,9 @@ func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, "TO_CHAR(board.created_at, 'DD:MM:YYYY')", "COUNT(pin.id) AS pins_number", "ARRAY_REMOVE((ARRAY_AGG(pin.picture))[:3], NULL) AS pins"). - From("membership"). - JoinClause("FULL JOIN pin ON membership.pin_id = pin.id"). - JoinClause("FULL JOIN board ON membership.board_id = board.id"). + From("board"). + LeftJoin("membership ON board.id = membership.board_id"). + LeftJoin("pin ON membership.pin_id = pin.id"). Where(squirrel.Eq{"board.deleted_at": nil}). Where(squirrel.Eq{"board.author": userID}) @@ -117,11 +117,11 @@ func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces "COUNT(DISTINCT pin.id) AS pins_number", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT pin.picture), NULL) AS pins", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). - From("membership"). - JoinClause("FULL JOIN pin ON membership.pin_id = pin.id"). - JoinClause("FULL JOIN board ON membership.board_id = board.id"). - JoinClause("FULL JOIN board_tag ON board_tag.board_id = board.id"). - JoinClause("FULL JOIN tag ON board_tag.tag_id = tag.id"). + From("board"). + LeftJoin("board_tag ON board.id = board_tag.board_id"). + LeftJoin("tag ON board_tag.tag_id = tag.id"). + LeftJoin("membership ON board.id = membership.board_id"). + LeftJoin("pin ON membership.pin_id = pin.id"). Where(squirrel.Eq{"board.deleted_at": nil}). Where(squirrel.Eq{"board.id": boardID}) From c87ca52c4354302686127990e213adf0007065f0 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 5 Nov 2023 00:08:52 +0300 Subject: [PATCH 122/266] dev2 add: returning count like when request on set like --- internal/pkg/delivery/http/v1/like_pin.go | 6 +++--- internal/pkg/repository/pin/like.go | 10 ++++++---- internal/pkg/repository/pin/queries.go | 2 +- internal/pkg/repository/pin/repo.go | 2 +- internal/pkg/repository/ramrepo/pin.go | 4 ++-- internal/pkg/usecase/pin/like.go | 4 ++-- internal/pkg/usecase/pin/usecase.go | 2 +- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index b5e657f..e24f755 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -26,12 +26,12 @@ func (h *HandlerHTTP) SetLikePin(w http.ResponseWriter, r *http.Request) { return } - err = h.pinCase.SetLikeFromUser(r.Context(), int(pinID), userID) + countLike, err := h.pinCase.SetLikeFromUser(r.Context(), int(pinID), userID) if err != nil { h.log.Error(err.Error()) - err = responseError(w, "like_pin_del", "internal error") + err = responseError(w, "like_pin_set", "internal error") } else { - err = responseOk(w, "ok", nil) + err = responseOk(w, "ok", map[string]int{"count_like": countLike}) } if err != nil { h.log.Error(err.Error()) diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go index f6a89e5..412856c 100644 --- a/internal/pkg/repository/pin/like.go +++ b/internal/pkg/repository/pin/like.go @@ -5,12 +5,14 @@ import ( "fmt" ) -func (p *pinRepoPG) SetLike(ctx context.Context, pinID, userID int) error { - _, err := p.db.Exec(ctx, InsertLikePinFromUser, pinID, userID) +func (p *pinRepoPG) SetLike(ctx context.Context, pinID, userID int) (int, error) { + row := p.db.QueryRow(ctx, InsertLikePinFromUser, pinID, userID) + var currCountLike int + err := row.Scan(&currCountLike) if err != nil { - return fmt.Errorf("insert like to pin from user in storage: %w", err) + return 0, fmt.Errorf("insert like to pin from user in storage: %w", err) } - return nil + return currCountLike + 1, nil } func (p *pinRepoPG) DelLike(ctx context.Context, pinID, userID int) error { diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index e40e126..5ecbe38 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -15,7 +15,7 @@ var ( ON board.id = contributor.board_id WHERE pin.id = $1 AND (board.author = $2 OR contributor.user_id = $2));` - InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2);" + InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2) RETURNING (SELECT COUNT(*) FROM like_pin WHERE pin_id = $1);" InsertLikePinFromUserAtomic = `INSERT INTO like_pin (pin_id, user_id) SELECT $1, $2 WHERE ( SELECT public OR author = $2 OR diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 4b66c94..b804681 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -21,7 +21,7 @@ type Repository interface { GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error - SetLike(ctx context.Context, pinID, userID int) error + SetLike(ctx context.Context, pinID, userID int) (int, error) DelLike(ctx context.Context, pinID, userID int) error EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 13eacb0..7fdd0e1 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -49,8 +49,8 @@ func (r *ramPinRepo) DeletePin(ctx context.Context, pinID, userID int) error { return ErrMethodUnimplemented } -func (r *ramPinRepo) SetLike(ctx context.Context, pinID, userID int) error { - return ErrMethodUnimplemented +func (r *ramPinRepo) SetLike(ctx context.Context, pinID, userID int) (int, error) { + return 0, ErrMethodUnimplemented } func (r *ramPinRepo) DelLike(ctx context.Context, pinID, userID int) error { diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go index 072227f..bab8186 100644 --- a/internal/pkg/usecase/pin/like.go +++ b/internal/pkg/usecase/pin/like.go @@ -5,9 +5,9 @@ import ( "fmt" ) -func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) error { +func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { if err := p.isAvailablePinForSetLike(ctx, pinID, userID); err != nil { - return fmt.Errorf("set like from user: %w", err) + return 0, fmt.Errorf("set like from user: %w", err) } return p.repo.SetLike(ctx, pinID, userID) } diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index ba681d0..c6e7dee 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -17,7 +17,7 @@ type Usecase interface { SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) CreateNewPin(ctx context.Context, pin *entity.Pin) error DeletePinFromUser(ctx context.Context, pinID, userID int) error - SetLikeFromUser(ctx context.Context, pinID, userID int) error + SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) From 907df11790253f12a6f6fd672cf02077a417c30f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 5 Nov 2023 02:54:25 +0300 Subject: [PATCH 123/266] TP-113 add: logger --- internal/api/server/router/router.go | 4 +- internal/pkg/middleware/middleware.go | 56 ++++++++++++++++++++++++++- internal/pkg/middleware/response.go | 30 ++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/middleware/response.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 14779e3..358cc99 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -9,6 +9,7 @@ import ( _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + mw "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/security" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" @@ -36,7 +37,8 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess ExposedHeaders: []string{cfgCSRF.HeaderSet}, }) - r.Mux.Use(c.Handler, security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) + r.Mux.Use(mw.RequestID, mw.Logger(log), c.Handler, + security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) diff --git a/internal/pkg/middleware/middleware.go b/internal/pkg/middleware/middleware.go index ff0f53a..bfab378 100644 --- a/internal/pkg/middleware/middleware.go +++ b/internal/pkg/middleware/middleware.go @@ -1,5 +1,59 @@ package middleware -import "net/http" +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/google/uuid" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) type Middleware func(next http.Handler) http.Handler + +type ctxKeyRequestID string + +const RequestIDKey ctxKeyRequestID = "RequestID" + +func RequestID(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id, err := uuid.NewRandom() + if err != nil { + return + } + + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), RequestIDKey, id.String()))) + }) +} + +func Logger(log *logger.Logger) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestID, ok := r.Context().Value(RequestIDKey).(string) + if !ok { + log.Warn("for middleware to work with logging, enable middleware with request id assignment") + next.ServeHTTP(w, r) + return + } + + wrapResponse := NewWrapResponseWriter(w) + + log.Info("request", logger.F{"request-id", requestID}, + logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}, + logger.F{"content-type", r.Header.Get("Content-Type")}, + logger.F{"content-length", r.Header.Get("Content-Length")}, + logger.F{"address", r.RemoteAddr}) + + defer func(t time.Time) { + log.Info("response", logger.F{"request-id", requestID}, + logger.F{"status", strconv.FormatInt(int64(wrapResponse.statusCode), 10)}, + logger.F{"processing_time", strconv.FormatInt(int64(time.Since(t).Milliseconds()), 10) + "ms"}, + logger.F{"content-type", w.Header().Get("Content-Type")}, + logger.F{"content-length", w.Header().Get("Content-Length")}) + }(time.Now()) + next.ServeHTTP(w, r) + }) + } +} diff --git a/internal/pkg/middleware/response.go b/internal/pkg/middleware/response.go new file mode 100644 index 0000000..14305a3 --- /dev/null +++ b/internal/pkg/middleware/response.go @@ -0,0 +1,30 @@ +package middleware + +import "net/http" + +type wrapResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func NewWrapResponseWriter(w http.ResponseWriter) *wrapResponseWriter { + return &wrapResponseWriter{ + ResponseWriter: w, + } +} + +func (w *wrapResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode + w.ResponseWriter.WriteHeader(statusCode) +} + +func SetResponseHeaders(headers map[string]string) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for key, value := range headers { + w.Header().Set(key, value) + } + next.ServeHTTP(w, r) + }) + } +} From b9d83935deeaaf4633b25031bedd6d0765f1742a Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 5 Nov 2023 17:53:31 +0300 Subject: [PATCH 124/266] TP-113 add: logger in context --- internal/api/server/router/router.go | 2 +- internal/pkg/middleware/middleware.go | 56 ++++++++++--------- internal/pkg/middleware/response.go | 16 ------ internal/pkg/middleware/wrap_response.go | 35 ++++++++++++ pkg/logger/logger.go | 68 ++++++++++++++++++++---- pkg/logger/types.go | 26 +++++++++ 6 files changed, 149 insertions(+), 54 deletions(-) create mode 100644 internal/pkg/middleware/wrap_response.go create mode 100644 pkg/logger/types.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 358cc99..e349309 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -37,7 +37,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess ExposedHeaders: []string{cfgCSRF.HeaderSet}, }) - r.Mux.Use(mw.RequestID, mw.Logger(log), c.Handler, + r.Mux.Use(mw.RequestID(log), mw.Logger(log), c.Handler, security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { diff --git a/internal/pkg/middleware/middleware.go b/internal/pkg/middleware/middleware.go index bfab378..383b061 100644 --- a/internal/pkg/middleware/middleware.go +++ b/internal/pkg/middleware/middleware.go @@ -3,7 +3,6 @@ package middleware import ( "context" "net/http" - "strconv" "time" "github.com/google/uuid" @@ -17,43 +16,48 @@ type ctxKeyRequestID string const RequestIDKey ctxKeyRequestID = "RequestID" -func RequestID(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - id, err := uuid.NewRandom() - if err != nil { - return - } +func RequestID(log *logger.Logger) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id, err := uuid.NewRandom() + if err != nil { + log.Sugar().Errorf("middleware requestID: %s", err.Error()) + } - next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), RequestIDKey, id.String()))) - }) + next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), RequestIDKey, id.String()))) + }) + } } -func Logger(log *logger.Logger) Middleware { +func Logger(logMW *logger.Logger) Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestID, ok := r.Context().Value(RequestIDKey).(string) - if !ok { + log := logMW + if requestID, ok := r.Context().Value(RequestIDKey).(string); ok { + log = log.WithField("request-id", requestID) + } else { log.Warn("for middleware to work with logging, enable middleware with request id assignment") - next.ServeHTTP(w, r) - return } wrapResponse := NewWrapResponseWriter(w) - log.Info("request", logger.F{"request-id", requestID}, - logger.F{"method", r.Method}, logger.F{"path", r.URL.Path}, - logger.F{"content-type", r.Header.Get("Content-Type")}, - logger.F{"content-length", r.Header.Get("Content-Length")}, - logger.F{"address", r.RemoteAddr}) - + log.InfoMap("request", logger.M{ + "method": r.Method, + "path": r.URL.Path, + "content_type": r.Header.Get("Content-Type"), + "content_length": r.ContentLength, + "address": r.RemoteAddr, + }) defer func(t time.Time) { - log.Info("response", logger.F{"request-id", requestID}, - logger.F{"status", strconv.FormatInt(int64(wrapResponse.statusCode), 10)}, - logger.F{"processing_time", strconv.FormatInt(int64(time.Since(t).Milliseconds()), 10) + "ms"}, - logger.F{"content-type", w.Header().Get("Content-Type")}, - logger.F{"content-length", w.Header().Get("Content-Length")}) + log.InfoMap("response", logger.M{ + "status": wrapResponse.statusCode, + "processing_time_ms": time.Since(t).Milliseconds(), + "content_type": w.Header().Get("Content-Type"), + "content_length": w.Header().Get("Content-Length"), + "written": wrapResponse.written, + }) }(time.Now()) - next.ServeHTTP(w, r) + next.ServeHTTP(wrapResponse, r.WithContext(context.WithValue(r.Context(), logger.KeyLogger, log))) }) } } diff --git a/internal/pkg/middleware/response.go b/internal/pkg/middleware/response.go index 14305a3..8d94e23 100644 --- a/internal/pkg/middleware/response.go +++ b/internal/pkg/middleware/response.go @@ -2,22 +2,6 @@ package middleware import "net/http" -type wrapResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func NewWrapResponseWriter(w http.ResponseWriter) *wrapResponseWriter { - return &wrapResponseWriter{ - ResponseWriter: w, - } -} - -func (w *wrapResponseWriter) WriteHeader(statusCode int) { - w.statusCode = statusCode - w.ResponseWriter.WriteHeader(statusCode) -} - func SetResponseHeaders(headers map[string]string) Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/pkg/middleware/wrap_response.go b/internal/pkg/middleware/wrap_response.go new file mode 100644 index 0000000..552218f --- /dev/null +++ b/internal/pkg/middleware/wrap_response.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "io" + "net/http" +) + +type wrapResponseWriter struct { + http.ResponseWriter + statusCode int + written int +} + +func NewWrapResponseWriter(w http.ResponseWriter) *wrapResponseWriter { + return &wrapResponseWriter{ + ResponseWriter: w, + } +} + +func (w *wrapResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *wrapResponseWriter) Write(data []byte) (written int, err error) { + written, err = w.ResponseWriter.Write(data) + w.written += written + return +} + +func (w *wrapResponseWriter) WriteString(data string) (written int, err error) { + written, err = io.WriteString(w.ResponseWriter, data) + w.written += written + return +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index bbe7194..3b85b7c 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -7,13 +7,9 @@ import ( "go.uber.org/zap/zapcore" ) -type F struct { - FieldName string - Value string -} - type Logger struct { *zap.Logger + fields []F } func New(options ...ConfigOption) (*Logger, error) { @@ -28,14 +24,64 @@ func New(options ...ConfigOption) (*Logger, error) { return &Logger{Logger: log}, nil } +func (log *Logger) WithField(key string, val any) *Logger { + newFields := make([]F, len(log.fields)+1) + copy(newFields, log.fields) + newFields[len(log.fields)] = F{key, val} + return &Logger{ + Logger: log.Logger, + fields: newFields, + } +} + func (log *Logger) Info(msg string, fields ...F) { + log.multiLevelLog(log.Logger.Info, msg, fields...) +} + +func (log *Logger) Warn(msg string, fields ...F) { + log.multiLevelLog(log.Logger.Warn, msg, fields...) +} + +func (log *Logger) Error(msg string, fields ...F) { + log.multiLevelLog(log.Logger.Error, msg, fields...) +} + +func (log *Logger) InfoMap(msg string, mapFields M) { + log.Info(msg, mapToSliceFields(mapFields)...) +} + +func (log *Logger) makeFields(fields []F) []zap.Field { listFields := make([]zap.Field, 0, len(fields)) for _, field := range fields { - listFields = append(listFields, zap.Field{ - Key: field.FieldName, - Type: zapcore.StringType, - String: field.Value, - }) + zf := zap.Field{ + Key: field.FieldName, + } + + switch value := field.Value.(type) { + case string: + zf.Type = zapcore.StringType + zf.String = value + case int64: + zf.Type = zapcore.Int64Type + zf.Integer = value + case int: + zf.Type = zapcore.Int64Type + zf.Integer = int64(value) + case bool: + zf.Type = zapcore.BoolType + zf.Interface = value + + default: + log.Warn("unknown type field for the logger") + zf.Type = zapcore.SkipType + } + + listFields = append(listFields, zf) } - log.Logger.Info(msg, listFields...) + return listFields +} + +func (log *Logger) multiLevelLog(logFn zapLogFn, msg string, fields ...F) { + fields = append(fields, log.fields...) + logFn(msg, log.makeFields(fields)...) } diff --git a/pkg/logger/types.go b/pkg/logger/types.go new file mode 100644 index 0000000..6d4b471 --- /dev/null +++ b/pkg/logger/types.go @@ -0,0 +1,26 @@ +package logger + +import "go.uber.org/zap/zapcore" + +type ( + F struct { + FieldName string + Value any + } + + M map[string]any +) + +type zapLogFn func(msg string, fields ...zapcore.Field) + +type ctxLoggerKey int8 + +var KeyLogger ctxLoggerKey = 0 + +func mapToSliceFields(m M) []F { + fields := make([]F, 0, len(m)) + for key, val := range m { + fields = append(fields, F{key, val}) + } + return fields +} From 9bd35aff793325d794ff3d0be8a3c9806171e695 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 5 Nov 2023 18:45:46 +0300 Subject: [PATCH 125/266] TP-113 add: get logger in handlers --- internal/api/server/router/router.go | 5 +- internal/pkg/delivery/http/v1/auth.go | 55 ++++++++--------- internal/pkg/delivery/http/v1/like_pin.go | 21 ++++--- internal/pkg/delivery/http/v1/logger.go | 14 +++++ internal/pkg/delivery/http/v1/pin.go | 72 +++++++++++------------ internal/pkg/delivery/http/v1/profile.go | 31 +++++----- 6 files changed, 101 insertions(+), 97 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/logger.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e349309..6e917df 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -38,7 +38,10 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) r.Mux.Use(mw.RequestID(log), mw.Logger(log), c.Handler, - security.CSRF(cfgCSRF), auth.NewAuthMiddleware(sm).ContextWithUserID) + security.CSRF(cfgCSRF), mw.SetResponseHeaders(map[string]string{ + "Content-Type": "application/json", + }), + auth.NewAuthMiddleware(sm).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index e981086..5662727 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -23,20 +23,18 @@ import ( // @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/login [get] func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) - + logger := h.getRequestLogger(r) userID, _ := r.Context().Value(auth.KeyCurrentUserID).(int) username, avatar, err := h.userCase.FindOutUsernameAndAvatar(r.Context(), userID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "no_auth", "no user was found for this session") } else { err = responseOk(w, "user found", map[string]string{"username": username, "avatar": avatar}) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } @@ -55,46 +53,45 @@ func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { // @Header 200 {string} session_key "Auth cookie with new valid session id" // @Router /api/v1/auth/login [post] func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) params := usecase.NewCredentials() err := json.NewDecoder(r.Body).Decode(&params) defer r.Body.Close() if err != nil { - h.log.Info("failed to parse parameters", log.F{"error", err.Error()}) + logger.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username and password are expected to be received in JSON format") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } if !isValidPassword(params.Password) || !isValidUsername(params.Username) { - h.log.Info("invalid credentials") + logger.Info("invalid credentials") err = responseError(w, "invalid_credentials", "invalid user credentials") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } user, err := h.userCase.Authentication(r.Context(), params) if err != nil { - h.log.Warn(err.Error()) + logger.Warn(err.Error()) err = responseError(w, "bad_credentials", "incorrect user credentials") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } session, err := h.sm.CreateNewSessionForUser(r.Context(), user.ID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "session", "failed to create a session for the user") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -112,7 +109,7 @@ func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { err = responseOk(w, "a new session has been created for the user", nil) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } @@ -131,39 +128,38 @@ func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} JsonErrResponse // @Router /api/v1/auth/signup [post] func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) user := &user.User{} err := json.NewDecoder(r.Body).Decode(user) defer r.Body.Close() if err != nil { - h.log.Info("failed to parse parameters", log.F{"error", err.Error()}) + logger.Info("failed to parse parameters", log.F{"error", err.Error()}) err = responseError(w, "parse_body", "the correct username, email and password are expected to be received in JSON format") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } if err := IsValidUserForRegistration(user); err != nil { - h.log.Info("invalid user registration data") + logger.Info("invalid user registration data") err = responseError(w, "invalid_params", err.Error()) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } err = h.userCase.Register(r.Context(), user) if err != nil { - h.log.Warn(err.Error()) + logger.Warn(err.Error()) err = responseError(w, "uniq_fields", "there is already an account with this username or email") } else { err = responseOk(w, "the user has been successfully registered", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } @@ -181,15 +177,14 @@ func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { // @Header 200 {string} Session-id "Auth cookie with expired session id" // @Router /api/v1/auth/logout [delete] func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) cookie, err := r.Cookie("session_key") if err != nil { - h.log.Info("no cookie", log.F{"error", err.Error()}) + logger.Info("no cookie", log.F{"error", err.Error()}) err = responseError(w, "no_auth", "to log out, you must first log in") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -200,12 +195,12 @@ func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { err = h.sm.DeleteUserSession(r.Context(), cookie.Value) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "session", "the user logged out, but his session did not end") } else { err = responseOk(w, "the user has successfully logged out", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index e24f755..909528d 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -7,61 +7,60 @@ import ( chi "github.com/go-chi/chi/v5" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerHTTP) SetLikePin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on set like for pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) pinIdStr := chi.URLParam(r, "pinID") pinID, err := strconv.ParseInt(pinIdStr, 10, 64) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "parse_url", "internal error") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } countLike, err := h.pinCase.SetLikeFromUser(r.Context(), int(pinID), userID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "like_pin_set", "internal error") } else { err = responseOk(w, "ok", map[string]int{"count_like": countLike}) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) DeleteLikePin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on set like for pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) pinIdStr := chi.URLParam(r, "pinID") pinID, err := strconv.ParseInt(pinIdStr, 10, 64) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "parse_url", "internal error") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } err = h.pinCase.DeleteLikeFromUser(r.Context(), int(pinID), userID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "like_pin_del", "internal error") } else { err = responseOk(w, "ok", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } diff --git a/internal/pkg/delivery/http/v1/logger.go b/internal/pkg/delivery/http/v1/logger.go new file mode 100644 index 0000000..c623577 --- /dev/null +++ b/internal/pkg/delivery/http/v1/logger.go @@ -0,0 +1,14 @@ +package v1 + +import ( + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func (h *HandlerHTTP) getRequestLogger(r *http.Request) *logger.Logger { + if log, ok := r.Context().Value(logger.KeyLogger).(*logger.Logger); ok { + return log + } + return h.log +} diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 091754e..069d9c2 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -15,7 +15,7 @@ import ( log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -const MaxMemoryParseFormData = 10 * 1 << 20 +const MaxMemoryParseFormData = 12 * 1 << 20 // GetPins godoc // @@ -32,16 +32,15 @@ const MaxMemoryParseFormData = 10 * 1 << 20 // @Failure 500 {object} JsonErrResponse // @Router /api/v1/pin [get] func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) if err != nil { - h.log.Info("parse url query params", log.F{"error", err.Error()}) + logger.Info("parse url query params", log.F{"error", err.Error()}) err = responseError(w, "bad_params", "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") } else { - h.log.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + logger.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectNewPins(r.Context(), count, minID, maxID) err = responseOk(w, "pins received are sorted by id", map[string]any{ "pins": pins, @@ -50,18 +49,17 @@ func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { }) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on create new pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) if !strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") { err := responseError(w, "bad_request", "the request body should be multipart/form-data") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -70,7 +68,7 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { if err != nil { err = responseError(w, "bad_body", "failed to read request body") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -103,7 +101,7 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { if err != nil { err = responseError(w, "bad_body", "unable to get an image from the request body") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -113,65 +111,63 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { if err != nil { err = responseError(w, "bad_body", "failed to upload the file received in the body") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } err = h.pinCase.CreateNewPin(r.Context(), newPin) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "add_pin", "failed to create pin") } else { err = responseOk(w, "pin successfully created", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) DeletePin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on delete new pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) pinIdStr := chi.URLParam(r, "pinID") pinID, err := strconv.ParseInt(pinIdStr, 10, 64) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "parse_url", "internal error") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } err = h.pinCase.DeletePinFromUser(r.Context(), int(pinID), userID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "pin_del", "internal error") } else { err = responseOk(w, "ok", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on edit pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) pinIdStr := chi.URLParam(r, "pinID") pinID, err := strconv.ParseInt(pinIdStr, 10, 64) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "parse_url", "internal error") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -183,36 +179,35 @@ func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(r.Body).Decode(pinUpdate) defer r.Body.Close() if err != nil { - h.log.Info(err.Error()) + logger.Info(err.Error()) err = responseError(w, "parse_body", "could not read the data to change") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } err = h.pinCase.EditPinByID(r.Context(), int(pinID), userID, pinUpdate) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "edit_pin", "internal error") } else { err = responseOk(w, "pin data has been successfully changed", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on view pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) pinIdStr := chi.URLParam(r, "pinID") pinID, err := strconv.ParseInt(pinIdStr, 10, 64) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "parse_url", "internal error") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -223,29 +218,28 @@ func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { } pin, err := h.pinCase.ViewAnPin(r.Context(), int(pinID), userID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "edit_pin", "internal error") } else { err = responseOk(w, "pin was successfully received", pin) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on view pin", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) if err != nil { - h.log.Info("parse url query params", log.F{"error", err.Error()}) + logger.Info("parse url query params", log.F{"error", err.Error()}) err = responseError(w, "bad_params", "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") } else { - h.log.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + logger.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) err = responseOk(w, "pins received are sorted by id", map[string]any{ "pins": pins, @@ -254,7 +248,7 @@ func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { }) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 9bd6546..e7401f9 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -11,8 +11,7 @@ import ( ) func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) @@ -20,11 +19,11 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(data) defer r.Body.Close() if err != nil { - h.log.Info("json decode: " + err.Error()) + logger.Info("json decode: " + err.Error()) err = responseError(w, "parse_body", "the request body must contain json with any of the fields: username, email, name, surname, password") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } @@ -51,69 +50,69 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { if invalidFields.Err() != nil { err = responseError(w, "invalid_params", invalidFields.Error()) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } err = h.userCase.EditProfileInfo(r.Context(), userID, data) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "uniq_fields", "there is already an account with this username or email") } else { err = responseOk(w, "user data has been successfully changed", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) { - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) - h.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}, + logger.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}, log.F{"userID", fmt.Sprint(userID)}, log.F{"content-type", r.Header.Get("Content-Type")}) defer r.Body.Close() avatar, err := h.imgCase.UploadImage("avatars/", r.Header.Get("Content-Type"), r.ContentLength, r.Body) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "edit_avatar", "file upload failed") if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } return } err = h.userCase.UpdateUserAvatar(r.Context(), userID, avatar) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "edit_avatar", "failed to change user's avatar") } else { err = responseOk(w, "the user's avatar has been successfully changed", nil) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } func (h *HandlerHTTP) GetProfileInfo(w http.ResponseWriter, r *http.Request) { - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userID := r.Context().Value(auth.KeyCurrentUserID).(int) user, err := h.userCase.GetAllProfileInfo(r.Context(), userID) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) err = responseError(w, "get_info", "failed to get user information") } else { err = responseOk(w, "user data has been successfully received", user) } if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) } } From 981c4bf97de5b7e671e6adcacc8ecfa81979865a Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 5 Nov 2023 19:10:23 +0300 Subject: [PATCH 126/266] dev2 add: check set like to pin from user --- internal/api/server/router/router.go | 3 ++- internal/pkg/delivery/http/v1/like_pin.go | 26 +++++++++++++++++++++++ internal/pkg/repository/pin/like.go | 15 +++++++++++++ internal/pkg/repository/pin/queries.go | 1 + internal/pkg/repository/pin/repo.go | 1 + internal/pkg/repository/ramrepo/pin.go | 4 ++++ internal/pkg/usecase/pin/like.go | 4 ++++ internal/pkg/usecase/pin/usecase.go | 1 + 8 files changed, 54 insertions(+), 1 deletion(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 6e917df..da1d0de 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -68,8 +68,9 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.With(auth.RequireAuth).Group(func(r chi.Router) { r.Get("/personal", handler.GetUserPins) + r.Get("/like/isSet/{pinID:\\d+}", handler.IsSetLikePin) r.Post("/create", handler.CreateNewPin) - r.Post("/like/{pinID:\\d+}", handler.SetLikePin) + r.Post("/like/set/{pinID:\\d+}", handler.SetLikePin) r.Put("/edit/{pinID:\\d+}", handler.EditPin) r.Delete("/like/{pinID:\\d+}", handler.DeleteLikePin) r.Delete("/delete/{pinID:\\d+}", handler.DeletePin) diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index 909528d..bc0d0d5 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -64,3 +64,29 @@ func (h *HandlerHTTP) DeleteLikePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) } } + +func (h *HandlerHTTP) IsSetLikePin(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + pinIdStr := chi.URLParam(r, "pinID") + pinID, err := strconv.ParseInt(pinIdStr, 10, 64) + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "parse_url", "internal error") + if err != nil { + logger.Error(err.Error()) + } + return + } + + isSet, err := h.pinCase.CheckUserHasSetLike(r.Context(), int(pinID), userID) + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "like_pin_set", "internal error") + } else { + err = responseOk(w, "ok", map[string]bool{"is_set": isSet}) + } + if err != nil { + logger.Error(err.Error()) + } +} diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go index 412856c..c6b79c3 100644 --- a/internal/pkg/repository/pin/like.go +++ b/internal/pkg/repository/pin/like.go @@ -3,6 +3,8 @@ package pin import ( "context" "fmt" + + "github.com/jackc/pgx/v5" ) func (p *pinRepoPG) SetLike(ctx context.Context, pinID, userID int) (int, error) { @@ -32,3 +34,16 @@ func (p *pinRepoPG) GetCountLikeByPinID(ctx context.Context, pinID int) (int, er } return count, nil } + +func (p *pinRepoPG) IsSetLike(ctx context.Context, pinID, userID int) (bool, error) { + row := p.db.QueryRow(ctx, SelectCheckSetLike, pinID, userID) + var check int + err := row.Scan(&check) + if err == pgx.ErrNoRows { + return false, nil + } + if err != nil { + return false, fmt.Errorf("is set like to pin in storage: %w", err) + } + return true, nil +} diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 5ecbe38..068c1b4 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -14,6 +14,7 @@ var ( ON membership.board_id = board.id INNER JOIN contributor ON board.id = contributor.board_id WHERE pin.id = $1 AND (board.author = $2 OR contributor.user_id = $2));` + SelectCheckSetLike = "SELECT pin_id FROM like_pin WHERE pin_id = $1 AND user_id = $2;" InsertLikePinFromUser = "INSERT INTO like_pin (pin_id, user_id) VALUES ($1, $2) RETURNING (SELECT COUNT(*) FROM like_pin WHERE pin_id = $1);" InsertLikePinFromUserAtomic = `INSERT INTO like_pin (pin_id, user_id) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index b804681..ab7ce0a 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -22,6 +22,7 @@ type Repository interface { AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error SetLike(ctx context.Context, pinID, userID int) (int, error) + IsSetLike(ctx context.Context, pinID, userID int) (bool, error) DelLike(ctx context.Context, pinID, userID int) error EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 7fdd0e1..520e009 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -84,3 +84,7 @@ func (r *ramPinRepo) GetTagsByPinID(ctx context.Context, pinID int) ([]pin.Tag, func (r *ramPinRepo) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, error) { return nil, ErrMethodUnimplemented } + +func (r *ramPinRepo) IsSetLike(ctx context.Context, pinID, userID int) (bool, error) { + return false, ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go index bab8186..6c44858 100644 --- a/internal/pkg/usecase/pin/like.go +++ b/internal/pkg/usecase/pin/like.go @@ -15,3 +15,7 @@ func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, func (p *pinCase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { return p.repo.DelLike(ctx, pinID, userID) } + +func (p *pinCase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { + return p.repo.IsSetLike(ctx, pinID, userID) +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index c6e7dee..4777acc 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -18,6 +18,7 @@ type Usecase interface { CreateNewPin(ctx context.Context, pin *entity.Pin) error DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) + CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) From 0afbd3deb0e46c59d617bd3c3029141b865fd973 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 5 Nov 2023 21:41:31 +0300 Subject: [PATCH 127/266] TP-113 add: logger with formater --- internal/pkg/delivery/http/v1/pin.go | 4 ++-- pkg/logger/logger.go | 20 ++++++++++++++++++++ pkg/logger/types.go | 10 ++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 069d9c2..33da5b9 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -40,7 +40,7 @@ func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { err = responseError(w, "bad_params", "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") } else { - logger.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectNewPins(r.Context(), count, minID, maxID) err = responseOk(w, "pins received are sorted by id", map[string]any{ "pins": pins, @@ -239,7 +239,7 @@ func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { err = responseError(w, "bad_params", "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") } else { - logger.Sugar().Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) err = responseOk(w, "pins received are sorted by id", map[string]any{ "pins": pins, diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 3b85b7c..1220cd9 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -46,6 +46,18 @@ func (log *Logger) Error(msg string, fields ...F) { log.multiLevelLog(log.Logger.Error, msg, fields...) } +func (log *Logger) Infof(template string, args ...any) { + log.multiLevelSugarLog((*zap.SugaredLogger).Infof, template, args...) +} + +func (log *Logger) Warnf(template string, args ...any) { + log.multiLevelSugarLog((*zap.SugaredLogger).Warnf, template, args...) +} + +func (log *Logger) Errorf(template string, args ...any) { + log.multiLevelSugarLog((*zap.SugaredLogger).Errorf, template, args...) +} + func (log *Logger) InfoMap(msg string, mapFields M) { log.Info(msg, mapToSliceFields(mapFields)...) } @@ -85,3 +97,11 @@ func (log *Logger) multiLevelLog(logFn zapLogFn, msg string, fields ...F) { fields = append(fields, log.fields...) logFn(msg, log.makeFields(fields)...) } + +func (log *Logger) multiLevelSugarLog(logFn zapSugarLogFn, template string, args ...any) { + fieldsSugarLogger := make([]any, 0, 2*len(log.fields)) + for _, field := range log.fields { + fieldsSugarLogger = append(fieldsSugarLogger, field.FieldName, field.Value) + } + logFn(log.Logger.Sugar().With(fieldsSugarLogger...), template, args...) +} diff --git a/pkg/logger/types.go b/pkg/logger/types.go index 6d4b471..101ad60 100644 --- a/pkg/logger/types.go +++ b/pkg/logger/types.go @@ -1,6 +1,9 @@ package logger -import "go.uber.org/zap/zapcore" +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) type ( F struct { @@ -11,7 +14,10 @@ type ( M map[string]any ) -type zapLogFn func(msg string, fields ...zapcore.Field) +type ( + zapLogFn func(msg string, fields ...zapcore.Field) + zapSugarLogFn func(sugarLogger *zap.SugaredLogger, template string, args ...any) +) type ctxLoggerKey int8 From e202855a074d3687231ee397ea3a82a8874d9ed6 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 02:13:20 +0300 Subject: [PATCH 128/266] TP-errors update: usecase image --- internal/app/app.go | 8 ++++---- internal/pkg/delivery/http/v1/auth_test.go | 16 +++++++-------- internal/pkg/delivery/http/v1/handler.go | 5 +---- internal/pkg/delivery/http/v1/pin.go | 11 +--------- internal/pkg/delivery/http/v1/pin_test.go | 4 ++-- internal/pkg/delivery/http/v1/profile.go | 12 +---------- internal/pkg/entity/pin/pin.go | 4 ++-- internal/pkg/repository/image/repo.go | 6 +++--- internal/pkg/usecase/image/usecase.go | 9 ++++---- internal/pkg/usecase/pin/usecase.go | 24 ++++++++++++++++------ internal/pkg/usecase/pin/usecase_test.go | 2 +- internal/pkg/usecase/user/profile.go | 10 +++++++-- internal/pkg/usecase/user/usecase.go | 13 +++++++++--- internal/pkg/usecase/user/usecase_test.go | 2 +- pkg/validator/image/check/check.go | 18 ++++++++++++++++ pkg/validator/image/jpeg/valid_jpg.go | 9 ++++---- pkg/validator/image/png/valid_png.go | 9 ++++---- pkg/validator/image/svg/valid_svg.go | 9 +++----- pkg/validator/image/valid_img.go | 11 +++++----- pkg/validator/image/webp/valid_webp.go | 8 +++----- 20 files changed, 103 insertions(+), 87 deletions(-) create mode 100644 pkg/validator/image/check/check.go diff --git a/internal/app/app.go b/internal/app/app.go index e415229..5adad11 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -53,12 +53,12 @@ func Run(ctx context.Context, log *log.Logger, configFile string) { } sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) - userCase := user.New(log, userRepo.NewUserRepoPG(pool)) - pinCase := pin.New(log, pinRepo.NewPinRepoPG(pool)) - boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()) imgCase := image.New(log, imgRepo.NewImageRepoFS("upload/")) + userCase := user.New(log, imgCase, userRepo.NewUserRepoPG(pool)) + pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) + boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()) - handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase, imgCase) + handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase) cfgServ, err := server.NewConfig(configFile) if err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go index f454cd9..cfadb01 100644 --- a/internal/pkg/delivery/http/v1/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -37,8 +37,8 @@ func TestCheckLogin(t *testing.T) { defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil, nil, nil) + userCase := user.New(log, nil, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil, nil) url := "https://domain.test:8080/api/v1/login" goodCases := []struct { @@ -143,8 +143,8 @@ func TestLogin(t *testing.T) { defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil, nil, nil) + userCase := user.New(log, nil, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string @@ -279,8 +279,8 @@ func TestSignUp(t *testing.T) { defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil, nil, nil) + userCase := user.New(log, nil, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string @@ -395,8 +395,8 @@ func TestLogout(t *testing.T) { defer db.Close() sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil, nil, nil) + userCase := user.New(log, nil, ramrepo.NewRamUserRepo(db)) + service := New(log, sm, userCase, nil, nil) goodCases := []struct { name string diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index d2c8df8..2500fb1 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -2,7 +2,6 @@ package v1 import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -14,17 +13,15 @@ type HandlerHTTP struct { userCase user.Usecase pinCase pin.Usecase boardCase board.Usecase - imgCase image.Usecase sm session.SessionManager } -func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, board board.Usecase, img image.Usecase) *HandlerHTTP { +func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, board board.Usecase) *HandlerHTTP { return &HandlerHTTP{ log: log, userCase: user, pinCase: pin, boardCase: board, - imgCase: img, sm: sm, } } diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 33da5b9..fc3663e 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -107,16 +107,7 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { } defer picture.Close() - newPin.Picture, err = h.imgCase.UploadImage("pins/", mime.Header.Get("Content-Type"), mime.Size, picture) - if err != nil { - err = responseError(w, "bad_body", "failed to upload the file received in the body") - if err != nil { - logger.Error(err.Error()) - } - return - } - - err = h.pinCase.CreateNewPin(r.Context(), newPin) + err = h.pinCase.CreateNewPin(r.Context(), newPin, mime.Header.Get("Content-Type"), mime.Size, picture) if err != nil { logger.Error(err.Error()) err = responseError(w, "add_pin", "failed to create pin") diff --git a/internal/pkg/delivery/http/v1/pin_test.go b/internal/pkg/delivery/http/v1/pin_test.go index 8f125c6..e663591 100644 --- a/internal/pkg/delivery/http/v1/pin_test.go +++ b/internal/pkg/delivery/http/v1/pin_test.go @@ -23,8 +23,8 @@ func TestGetPins(t *testing.T) { db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) defer db.Close() - pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) - service := New(log, nil, nil, pinCase, nil, nil) + pinCase := pinCase.New(log, nil, ramrepo.NewRamPinRepo(db)) + service := New(log, nil, nil, pinCase, nil) rawUrl := "https://domain.test:8080/api/v1/pin" goodCases := []struct { diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index b7d4977..a2e8861 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -76,17 +76,7 @@ func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) defer r.Body.Close() - avatar, err := h.imgCase.UploadImage("avatars/", r.Header.Get("Content-Type"), r.ContentLength, r.Body) - if err != nil { - logger.Error(err.Error()) - err = responseError(w, "edit_avatar", "file upload failed") - if err != nil { - logger.Error(err.Error()) - } - return - } - - err = h.userCase.UpdateUserAvatar(r.Context(), userID, avatar) + err := h.userCase.UpdateUserAvatar(r.Context(), userID, r.Header.Get("Content-Type"), r.ContentLength, r.Body) if err != nil { logger.Error(err.Error()) err = responseError(w, "edit_avatar", "failed to change user's avatar") diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index bef7532..f547059 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -10,8 +10,8 @@ type Pin struct { ID int `json:"id" example:"55"` Author *user.User `json:"author,omitempty" example:"23"` Picture string `json:"picture" example:"pinspire/imgs/image.png"` - Title pgtype.Text `json:"-" example:"Nature's beauty"` - Description pgtype.Text `json:"-" example:"about face"` + Title pgtype.Text `json:"title" example:"Nature's beauty"` + Description pgtype.Text `json:"description" example:"about face"` Public bool `json:"public"` Tags []Tag `json:"tags,omitempty"` diff --git a/internal/pkg/repository/image/repo.go b/internal/pkg/repository/image/repo.go index a4e3e8f..a75e33f 100644 --- a/internal/pkg/repository/image/repo.go +++ b/internal/pkg/repository/image/repo.go @@ -43,10 +43,10 @@ func (img *imageRepoFS) SaveImage(prefixPath, extension string, image io.Reader) return "", 0, fmt.Errorf("mkdir %s to save file: %w", dir, err) } - filePath := dir + filename + "." + extension - file, err := os.Create(filePath) + filename = dir + filename + "." + extension + file, err := os.Create(filename) if err != nil { - return "", 0, fmt.Errorf("create %s to save file: %w", filePath, err) + return "", 0, fmt.Errorf("create %s to save file: %w", filename, err) } defer file.Close() diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go index 67e79b0..76bc148 100644 --- a/internal/pkg/usecase/image/usecase.go +++ b/internal/pkg/usecase/image/usecase.go @@ -9,6 +9,7 @@ import ( repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" valid "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) const PrefixURLImage = "httsp://pinspire.online:8081/" @@ -17,7 +18,7 @@ var ErrInvalidImage = errors.New("invalid images") var ErrUploadFile = errors.New("file upload failed") type Usecase interface { - UploadImage(path string, mimeType string, size int64, image io.Reader) (string, error) + UploadImage(path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) } type imageCase struct { @@ -29,10 +30,10 @@ func New(log *log.Logger, repo repo.Repository) *imageCase { return &imageCase{log, repo} } -func (img *imageCase) UploadImage(path string, mimeType string, size int64, image io.Reader) (string, error) { +func (img *imageCase) UploadImage(path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) { buf := bytes.NewBuffer(nil) - extension, ok := valid.IsValidImage(io.TeeReader(image, buf), mimeType) + extension, ok := valid.IsValidImage(io.TeeReader(image, buf), mimeType, check) if !ok { return "", ErrInvalidImage } @@ -46,5 +47,5 @@ func (img *imageCase) UploadImage(path string, mimeType string, size int64, imag if written != size { return "", ErrUploadFile } - return filename, nil + return "https://pinspire.online:8081/" + filename, nil } diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 4777acc..07780c5 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -4,10 +4,13 @@ import ( "context" "errors" "fmt" + "io" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) var ErrBadMIMEType = errors.New("bad mime type") @@ -15,7 +18,7 @@ var ErrBadMIMEType = errors.New("bad mime type") type Usecase interface { SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) - CreateNewPin(ctx context.Context, pin *entity.Pin) error + CreateNewPin(ctx context.Context, pin *entity.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) @@ -25,12 +28,17 @@ type Usecase interface { } type pinCase struct { + image.Usecase log *log.Logger repo repo.Repository } -func New(log *log.Logger, repo repo.Repository) *pinCase { - return &pinCase{log, repo} +func New(log *log.Logger, imgCase image.Usecase, repo repo.Repository) *pinCase { + return &pinCase{ + Usecase: imgCase, + log: log, + repo: repo, + } } func (p *pinCase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) { @@ -55,10 +63,14 @@ func (p *pinCase) SelectUserPins(ctx context.Context, userID, count, minID, maxI return pins, pins[len(pins)-1].ID, pins[0].ID } -func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin) error { - pin.Picture = "https://pinspire.online:8081/" + pin.Picture +func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { + picturePin, err := p.UploadImage("pins/", mimeTypePicture, sizePicture, picture, check.BothSidesFallIntoRange(200, 1800)) + if err != nil { + return fmt.Errorf("uploading an avatar when creating pin: %w", err) + } + pin.Picture = picturePin - err := p.repo.AddNewPin(ctx, pin) + err = p.repo.AddNewPin(ctx, pin) if err != nil { return fmt.Errorf("add new pin: %w", err) } diff --git a/internal/pkg/usecase/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go index a57cb33..61003b3 100644 --- a/internal/pkg/usecase/pin/usecase_test.go +++ b/internal/pkg/usecase/pin/usecase_test.go @@ -18,7 +18,7 @@ func TestSelectNewPins(t *testing.T) { db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) defer db.Close() - pinCase := New(log, ramrepo.NewRamPinRepo(db)) + pinCase := New(log, nil, ramrepo.NewRamPinRepo(db)) testCases := []struct { name string diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 7940284..f87b9dc 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -4,16 +4,22 @@ import ( "context" "errors" "fmt" + "io" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) var ErrBadBody = errors.New("bad body avatar") -func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, avatar string) error { - err := u.repo.EditUserAvatar(ctx, userID, "https://pinspire.online:8081/"+avatar) +func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAvatar string, sizeAvatar int64, avatar io.Reader) error { + avatarProfile, err := u.UploadImage("avatars/", mimeTypeAvatar, sizeAvatar, avatar, check.BothSidesFallIntoRange(200, 1800)) + if err != nil { + return fmt.Errorf("uploading an avatar when updating avatar profile: %w", err) + } + err = u.repo.EditUserAvatar(ctx, userID, avatarProfile) if err != nil { return fmt.Errorf("edit user avatar: %w", err) } diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index 9d96941..e06a465 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -3,9 +3,11 @@ package user import ( "context" "errors" + "io" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -20,16 +22,21 @@ type Usecase interface { Register(ctx context.Context, user *entity.User) error Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) - UpdateUserAvatar(ctx context.Context, userID int, avatar string) error + UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAvatar string, sizeAvatar int64, avatar io.Reader) error GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error } type userCase struct { + image.Usecase log *logger.Logger repo repo.Repository } -func New(log *logger.Logger, repo repo.Repository) *userCase { - return &userCase{log, repo} +func New(log *logger.Logger, imgCase image.Usecase, repo repo.Repository) *userCase { + return &userCase{ + Usecase: imgCase, + log: log, + repo: repo, + } } diff --git a/internal/pkg/usecase/user/usecase_test.go b/internal/pkg/usecase/user/usecase_test.go index 9fabfad..35c795e 100644 --- a/internal/pkg/usecase/user/usecase_test.go +++ b/internal/pkg/usecase/user/usecase_test.go @@ -24,7 +24,7 @@ func TestRegister(t *testing.T) { db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) defer db.Close() - userCase := New(log, ramrepo.NewRamUserRepo(db)) + userCase := New(log, nil, ramrepo.NewRamUserRepo(db)) testCases := []struct { name string diff --git a/pkg/validator/image/check/check.go b/pkg/validator/image/check/check.go new file mode 100644 index 0000000..15b72cb --- /dev/null +++ b/pkg/validator/image/check/check.go @@ -0,0 +1,18 @@ +package check + +type CheckSize func(width, height float64) bool + +func BothSidesFallIntoRange(minSize, maxSize int) CheckSize { + return func(width, height float64) bool { + if width < float64(minSize) || width > float64(maxSize) || + height < float64(minSize) || height > float64(minSize) { + + return false + } + return true + } +} + +func AnySize(width, height float64) bool { + return true +} diff --git a/pkg/validator/image/jpeg/valid_jpg.go b/pkg/validator/image/jpeg/valid_jpg.go index d253990..02588e3 100644 --- a/pkg/validator/image/jpeg/valid_jpg.go +++ b/pkg/validator/image/jpeg/valid_jpg.go @@ -3,15 +3,14 @@ package jpeg import ( "image/jpeg" "io" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) -func IsValidJPEG(r io.Reader) bool { +func IsValidJPEG(r io.Reader, check check.CheckSize) bool { cfg, err := jpeg.DecodeConfig(r) if err != nil { return false } - if cfg.Height < 200 || cfg.Width < 200 || cfg.Height > 600 || cfg.Width > 600 { - return false - } - return true + return check(float64(cfg.Width), float64(cfg.Height)) } diff --git a/pkg/validator/image/png/valid_png.go b/pkg/validator/image/png/valid_png.go index f08d583..b873a0b 100644 --- a/pkg/validator/image/png/valid_png.go +++ b/pkg/validator/image/png/valid_png.go @@ -3,16 +3,15 @@ package png import ( "image/png" "io" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) -func IsValidPNG(r io.Reader) bool { +func IsValidPNG(r io.Reader, check check.CheckSize) bool { cfg, err := png.DecodeConfig(r) if err != nil { return false } - if cfg.Height < 200 || cfg.Width < 200 || cfg.Height > 600 || cfg.Width > 600 { - return false - } - return true + return check(float64(cfg.Width), float64(cfg.Height)) } diff --git a/pkg/validator/image/svg/valid_svg.go b/pkg/validator/image/svg/valid_svg.go index 1ec9499..2c073e1 100644 --- a/pkg/validator/image/svg/valid_svg.go +++ b/pkg/validator/image/svg/valid_svg.go @@ -3,18 +3,15 @@ package svg import ( "io" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" "github.com/tdewolff/canvas" ) -func IsValidSVG(r io.Reader) bool { +func IsValidSVG(r io.Reader, check check.CheckSize) bool { can, err := canvas.ParseSVG(r) if err != nil { return false } - if can.H < 200 || can.W < 200 || can.H > 600 || can.W > 600 { - return false - } - - return true + return check(can.W, can.H) } diff --git a/pkg/validator/image/valid_img.go b/pkg/validator/image/valid_img.go index 20880c0..35800b9 100644 --- a/pkg/validator/image/valid_img.go +++ b/pkg/validator/image/valid_img.go @@ -5,13 +5,14 @@ import ( "io" "strings" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/jpeg" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/png" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/svg" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/webp" ) -func IsValidImage(image io.Reader, mimeType string) (string, bool) { +func IsValidImage(image io.Reader, mimeType string, check check.CheckSize) (string, bool) { if !strings.HasPrefix(mimeType, "image/") { return "", false } @@ -19,14 +20,14 @@ func IsValidImage(image io.Reader, mimeType string) (string, bool) { switch { case strings.HasPrefix(mimeType, "jpeg"): - return "jpg", jpeg.IsValidJPEG(image) + return "jpg", jpeg.IsValidJPEG(image, check) case strings.HasPrefix(mimeType, "png"): - return "png", png.IsValidPNG(image) + return "png", png.IsValidPNG(image, check) case strings.HasPrefix(mimeType, "svg"): fmt.Println("SVG") - return "svg", svg.IsValidSVG(image) + return "svg", svg.IsValidSVG(image, check) case strings.HasPrefix(mimeType, "webp"): - return "webp", webp.IsValidWEBP(image) + return "webp", webp.IsValidWEBP(image, check) default: return "", false } diff --git a/pkg/validator/image/webp/valid_webp.go b/pkg/validator/image/webp/valid_webp.go index b2a6f9d..2bf335c 100644 --- a/pkg/validator/image/webp/valid_webp.go +++ b/pkg/validator/image/webp/valid_webp.go @@ -3,16 +3,14 @@ package webp import ( "io" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" "golang.org/x/image/webp" ) -func IsValidWEBP(r io.Reader) bool { +func IsValidWEBP(r io.Reader, check check.CheckSize) bool { cfg, err := webp.DecodeConfig(r) if err != nil { return false } - if cfg.Height < 200 || cfg.Width < 200 || cfg.Height > 600 || cfg.Width > 600 { - return false - } - return true + return check(float64(cfg.Width), float64(cfg.Height)) } From 76c0e217b11a6281a6bcc9498e658552a0f3aa0d Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 02:32:47 +0300 Subject: [PATCH 129/266] TP-erros update: both sides fall into range --- pkg/validator/image/check/check.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/validator/image/check/check.go b/pkg/validator/image/check/check.go index 15b72cb..61e7fcb 100644 --- a/pkg/validator/image/check/check.go +++ b/pkg/validator/image/check/check.go @@ -5,7 +5,7 @@ type CheckSize func(width, height float64) bool func BothSidesFallIntoRange(minSize, maxSize int) CheckSize { return func(width, height float64) bool { if width < float64(minSize) || width > float64(maxSize) || - height < float64(minSize) || height > float64(minSize) { + height < float64(minSize) || height > float64(maxSize) { return false } From d620112cd494b339bea176efd86c5ca76cf01e8f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 12:13:22 +0300 Subject: [PATCH 130/266] TP-f21 update: responseOk --- internal/api/server/router/router.go | 3 +- internal/pkg/delivery/http/v1/auth.go | 8 +-- internal/pkg/delivery/http/v1/board.go | 70 +++++++++-------------- internal/pkg/delivery/http/v1/like_pin.go | 6 +- internal/pkg/delivery/http/v1/pin.go | 13 ++--- internal/pkg/delivery/http/v1/profile.go | 6 +- internal/pkg/delivery/http/v1/response.go | 6 +- internal/pkg/middleware/security/csrf.go | 1 + 8 files changed, 48 insertions(+), 65 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 3696ff8..e447e42 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -82,8 +82,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Get("/user/{username}", handler.GetUserBoards) r.Get("/{boardID:\\d+}", handler.GetCertainBoard) }) - r.Group(func(r chi.Router) { - r.Use(auth.RequireAuth) + r.With(auth.RequireAuth).Group(func(r chi.Router) { r.Post("/create", handler.CreateNewBoard) r.Put("/update/{boardID:\\d+}", handler.UpdateBoardInfo) r.Delete("/delete/{boardID:\\d+}", handler.DeleteBoard) diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index 5662727..c4f5d97 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -31,7 +31,7 @@ func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "no_auth", "no user was found for this session") } else { - err = responseOk(w, "user found", map[string]string{"username": username, "avatar": avatar}) + err = responseOk(http.StatusOK, w, "user found", map[string]string{"username": username, "avatar": avatar}) } if err != nil { logger.Error(err.Error()) @@ -107,7 +107,7 @@ func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { } http.SetCookie(w, cookie) - err = responseOk(w, "a new session has been created for the user", nil) + err = responseOk(http.StatusCreated, w, "a new session has been created for the user", nil) if err != nil { logger.Error(err.Error()) } @@ -156,7 +156,7 @@ func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { logger.Warn(err.Error()) err = responseError(w, "uniq_fields", "there is already an account with this username or email") } else { - err = responseOk(w, "the user has been successfully registered", nil) + err = responseOk(http.StatusCreated, w, "the user has been successfully registered", nil) } if err != nil { logger.Error(err.Error()) @@ -198,7 +198,7 @@ func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "session", "the user logged out, but his session did not end") } else { - err = responseOk(w, "the user has successfully logged out", nil) + err = responseOk(http.StatusOK, w, "the user has successfully logged out", nil) } if err != nil { logger.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 43e9662..5d3ea58 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -11,18 +11,17 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" bCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { - h.log.Info("request on create new board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) var newBoard boardDTO.BoardData err := json.NewDecoder(r.Body).Decode(&newBoard) defer r.Body.Close() if err != nil { - h.log.Info("create board: ", logger.F{"message", err.Error()}) + logger.Info("create board: ", log.F{"message", err.Error()}) responseError(w, BadBodyCode, BadBodyMessage) return } @@ -30,7 +29,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { newBoard.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) newBoardID, err := h.boardCase.CreateNewBoard(r.Context(), newBoard) if err != nil { - h.log.Info("create board", logger.F{"message", err.Error()}) + logger.Info("create board", log.F{"message", err.Error()}) switch err { case bCase.ErrInvalidBoardTitle: responseError(w, "bad_boardTitle", err.Error()) @@ -45,23 +44,20 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(w, "new board was created successfully", map[string]int{"new_board_id": newBoardID}) + err = responseOk(http.StatusCreated, w, "new board was created successfully", map[string]int{"new_board_id": newBoardID}) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) - return } - h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { - h.log.Info("request to get user boards:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) userBoards, err := h.boardCase.GetBoardsByUsername(r.Context(), chi.URLParam(r, "username")) if err != nil { - h.log.Info("get user boards: ", logger.F{"message", err.Error()}) + logger.Info("get user boards: ", log.F{"message", err.Error()}) switch err { case bCase.ErrInvalidUsername: responseError(w, "bad_username", err.Error()) @@ -72,30 +68,27 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(w, "got user boards successfully", userBoards) + err = responseOk(http.StatusOK, w, "got user boards successfully", userBoards) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) - return } - h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { - h.log.Info("request to get certain board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - h.log.Info("get certain board ", logger.F{"message", err.Error()}) + logger.Info("get certain board ", log.F{"message", err.Error()}) responseError(w, BadQueryParamCode, BadQueryParamMessage) return } board, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) if err != nil { - h.log.Info("get certain board: ", logger.F{"message", err.Error()}) + logger.Info("get certain board: ", log.F{"message", err.Error()}) switch err { case bCase.ErrNoSuchBoard: responseError(w, "no_board", err.Error()) @@ -106,24 +99,20 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(w, "got certain board successfully", board) + err = responseOk(http.StatusOK, w, "got certain board successfully", board) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) - return } - - h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { - h.log.Info("request to update certain board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - h.log.Info("update certain board ", logger.F{"message", err.Error()}) + logger.Info("update certain board ", log.F{"message", err.Error()}) responseError(w, BadQueryParamCode, BadQueryParamMessage) return } @@ -132,7 +121,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(r.Body).Decode(&updatedBoard) defer r.Body.Close() if err != nil { - h.log.Info("update certain board: ", logger.F{"message", err.Error()}) + logger.Info("update certain board: ", log.F{"message", err.Error()}) responseError(w, BadBodyCode, BadBodyMessage) return } @@ -140,7 +129,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard) if err != nil { - h.log.Info("update certain board: ", logger.F{"message", err.Error()}) + logger.Info("update certain board: ", log.F{"message", err.Error()}) switch err { case bCase.ErrNoSuchBoard: responseError(w, "no_board", err.Error()) @@ -159,31 +148,27 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(w, "updated certain board successfully", nil) + err = responseOk(http.StatusOK, w, "updated certain board successfully", nil) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) - return } - - h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { - h.log.Info("request to delete board:", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) - SetContentTypeJSON(w) + logger := h.getRequestLogger(r) boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - h.log.Info("delete board ", logger.F{"message", err.Error()}) + logger.Info("delete board ", log.F{"message", err.Error()}) responseError(w, BadQueryParamCode, BadQueryParamMessage) return } err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) if err != nil { - h.log.Info("delete board: ", logger.F{"message", err.Error()}) + logger.Info("delete board: ", log.F{"message", err.Error()}) switch err { case bCase.ErrNoSuchBoard: responseError(w, "no_board", err.Error()) @@ -196,13 +181,10 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(w, "deleted board successfully", nil) + err = responseOk(http.StatusOK, w, "deleted board successfully", nil) if err != nil { - h.log.Error(err.Error()) + logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(InternalServerErrMessage)) - return } - - h.log.Info("successfull respond", logger.F{"method", r.Method}, logger.F{"URL", r.URL.Path}) } diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index bc0d0d5..ae03e30 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -30,7 +30,7 @@ func (h *HandlerHTTP) SetLikePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "like_pin_set", "internal error") } else { - err = responseOk(w, "ok", map[string]int{"count_like": countLike}) + err = responseOk(http.StatusCreated, w, "ok", map[string]int{"count_like": countLike}) } if err != nil { logger.Error(err.Error()) @@ -58,7 +58,7 @@ func (h *HandlerHTTP) DeleteLikePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "like_pin_del", "internal error") } else { - err = responseOk(w, "ok", nil) + err = responseOk(http.StatusOK, w, "ok", nil) } if err != nil { logger.Error(err.Error()) @@ -84,7 +84,7 @@ func (h *HandlerHTTP) IsSetLikePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "like_pin_set", "internal error") } else { - err = responseOk(w, "ok", map[string]bool{"is_set": isSet}) + err = responseOk(http.StatusOK, w, "ok", map[string]bool{"is_set": isSet}) } if err != nil { logger.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index fc3663e..b1b6116 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -42,7 +42,7 @@ func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { } else { logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectNewPins(r.Context(), count, minID, maxID) - err = responseOk(w, "pins received are sorted by id", map[string]any{ + err = responseOk(http.StatusOK, w, "pins received are sorted by id", map[string]any{ "pins": pins, "minID": minID, "maxID": maxID, @@ -112,7 +112,7 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "add_pin", "failed to create pin") } else { - err = responseOk(w, "pin successfully created", nil) + err = responseOk(http.StatusCreated, w, "pin successfully created", nil) } if err != nil { logger.Error(err.Error()) @@ -140,7 +140,7 @@ func (h *HandlerHTTP) DeletePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "pin_del", "internal error") } else { - err = responseOk(w, "ok", nil) + err = responseOk(http.StatusOK, w, "ok", nil) } if err != nil { logger.Error(err.Error()) @@ -181,7 +181,7 @@ func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "edit_pin", "internal error") } else { - err = responseOk(w, "pin data has been successfully changed", nil) + err = responseOk(http.StatusOK, w, "pin data has been successfully changed", nil) } if err != nil { @@ -212,7 +212,7 @@ func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "edit_pin", "internal error") } else { - err = responseOk(w, "pin was successfully received", pin) + err = responseOk(http.StatusOK, w, "pin was successfully received", pin) } if err != nil { logger.Error(err.Error()) @@ -232,7 +232,7 @@ func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { } else { logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) - err = responseOk(w, "pins received are sorted by id", map[string]any{ + err = responseOk(http.StatusOK, w, "pins received are sorted by id", map[string]any{ "pins": pins, "minID": minID, "maxID": maxID, @@ -241,5 +241,4 @@ func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) } - } diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index a2e8861..f973137 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -59,7 +59,7 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "uniq_fields", "there is already an account with this username or email") } else { - err = responseOk(w, "user data has been successfully changed", nil) + err = responseOk(http.StatusOK, w, "user data has been successfully changed", nil) } if err != nil { @@ -81,7 +81,7 @@ func (h *HandlerHTTP) ProfileEditAvatar(w http.ResponseWriter, r *http.Request) logger.Error(err.Error()) err = responseError(w, "edit_avatar", "failed to change user's avatar") } else { - err = responseOk(w, "the user's avatar has been successfully changed", nil) + err = responseOk(http.StatusOK, w, "the user's avatar has been successfully changed", nil) } if err != nil { @@ -98,7 +98,7 @@ func (h *HandlerHTTP) GetProfileInfo(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "get_info", "failed to get user information") } else { - err = responseOk(w, "user data has been successfully received", user) + err = responseOk(http.StatusOK, w, "user data has been successfully received", user) } if err != nil { diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index fe61c69..1b69af1 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -34,7 +34,7 @@ func SetContentTypeJSON(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") } -func responseOk(w http.ResponseWriter, message string, body any) error { +func responseOk(statusCode int, w http.ResponseWriter, message string, body any) error { res := JsonResponse{ Status: "ok", Message: message, @@ -42,9 +42,11 @@ func responseOk(w http.ResponseWriter, message string, body any) error { } resBytes, err := json.Marshal(res) if err != nil { + w.WriteHeader(http.StatusInternalServerError) return fmt.Errorf("responseOk: %w", err) } - w.WriteHeader(http.StatusOK) + + w.WriteHeader(statusCode) _, err = w.Write(resBytes) return err } diff --git a/internal/pkg/middleware/security/csrf.go b/internal/pkg/middleware/security/csrf.go index 6c2035d..893b5b3 100644 --- a/internal/pkg/middleware/security/csrf.go +++ b/internal/pkg/middleware/security/csrf.go @@ -13,6 +13,7 @@ func CSRF(cfg CSRFConfig) mw.Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == cfg.PathToGet && r.Method == http.MethodGet { + w.WriteHeader(http.StatusNoContent) setToken(w, &cfg) return } From 45c8707cd62d59cb92ed4479851ddfb78ec3914c Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 13:35:32 +0300 Subject: [PATCH 131/266] TP-f21 update: rename BoardUsecase to boardUsecase --- internal/pkg/repository/board/ramrepo/repo.go | 41 ------------------- internal/pkg/usecase/board/create.go | 2 +- internal/pkg/usecase/board/delete.go | 2 +- internal/pkg/usecase/board/get.go | 4 +- internal/pkg/usecase/board/update.go | 2 +- internal/pkg/usecase/board/usecase.go | 6 +-- internal/pkg/usecase/board/validation.go | 8 ++-- 7 files changed, 12 insertions(+), 53 deletions(-) delete mode 100644 internal/pkg/repository/board/ramrepo/repo.go diff --git a/internal/pkg/repository/board/ramrepo/repo.go b/internal/pkg/repository/board/ramrepo/repo.go deleted file mode 100644 index 3fcef20..0000000 --- a/internal/pkg/repository/board/ramrepo/repo.go +++ /dev/null @@ -1,41 +0,0 @@ -package board - -import ( - "context" - "database/sql" - - "github.com/Masterminds/squirrel" - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" - uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" -) - -type BoardRepoRam struct { - db *sql.DB - sqlBuilder squirrel.StatementBuilderType -} - -func NewBoardRepoRam(db *sql.DB) *BoardRepoRam { - return &BoardRepoRam{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} -} - -func (repo *BoardRepoRam) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) error { - return repository.ErrMethodUnimplemented -} - -func (repo *BoardRepoRam) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool) ([]dto.UserBoard, error) { - return nil, repository.ErrMethodUnimplemented -} - -func (repo *BoardRepoRam) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) { - return dto.UserBoard{}, repository.ErrMethodUnimplemented -} - -func (repo *BoardRepoRam) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { - return 0, repository.ErrMethodUnimplemented -} - -func (repo *BoardRepoRam) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) { - return nil, repository.ErrMethodUnimplemented -} diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go index 2425511..5536057 100644 --- a/internal/pkg/usecase/board/create.go +++ b/internal/pkg/usecase/board/create.go @@ -8,7 +8,7 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) { +func (bCase *boardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) { if !bCase.isValidBoardTitle(newBoard.Title) { return 0, ErrInvalidBoardTitle } diff --git a/internal/pkg/usecase/board/delete.go b/internal/pkg/usecase/board/delete.go index 2e74abb..da4fd8b 100644 --- a/internal/pkg/usecase/board/delete.go +++ b/internal/pkg/usecase/board/delete.go @@ -8,7 +8,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" ) -func (bCase *BoardUsecase) DeleteCertainBoard(ctx context.Context, boardID int) error { +func (bCase *boardUsecase) DeleteCertainBoard(ctx context.Context, boardID int) error { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) if err != nil { switch err { diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index c5bf246..b0656fe 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -9,7 +9,7 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) { +func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) { if !bCase.isValidUsername(username) { return nil, ErrInvalidUsername } @@ -43,7 +43,7 @@ func (bCase *BoardUsecase) GetBoardsByUsername(ctx context.Context, username str return boards, nil } -func (bCase *BoardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) { +func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) if err != nil { switch err { diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go index c5c67d6..92f5dfe 100644 --- a/internal/pkg/usecase/board/update.go +++ b/internal/pkg/usecase/board/update.go @@ -10,7 +10,7 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *BoardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error { +func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, updatedData.ID) if err != nil { switch err { diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index f9884df..7bf5a59 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -18,13 +18,13 @@ type Usecase interface { DeleteCertainBoard(ctx context.Context, boardID int) error } -type BoardUsecase struct { +type boardUsecase struct { log *logger.Logger boardRepo boardRepo.Repository userRepo userRepo.Repository sanitizer *bluemonday.Policy } -func New(logger *logger.Logger, boardRepo boardRepo.Repository, userRepo userRepo.Repository, sanitizer *bluemonday.Policy) *BoardUsecase { - return &BoardUsecase{log: logger, boardRepo: boardRepo, userRepo: userRepo, sanitizer: sanitizer} +func New(logger *logger.Logger, boardRepo boardRepo.Repository, userRepo userRepo.Repository, sanitizer *bluemonday.Policy) *boardUsecase { + return &boardUsecase{log: logger, boardRepo: boardRepo, userRepo: userRepo, sanitizer: sanitizer} } diff --git a/internal/pkg/usecase/board/validation.go b/internal/pkg/usecase/board/validation.go index a80e929..c3df21e 100644 --- a/internal/pkg/usecase/board/validation.go +++ b/internal/pkg/usecase/board/validation.go @@ -5,7 +5,7 @@ import ( "unicode" ) -func (bCase *BoardUsecase) isValidTagTitle(title string) bool { +func (bCase *boardUsecase) isValidTagTitle(title string) bool { if len(title) > 20 { return false } @@ -18,7 +18,7 @@ func (bCase *BoardUsecase) isValidTagTitle(title string) bool { return true } -func (bCase *BoardUsecase) checkIsValidTagTitles(titles []string) error { +func (bCase *boardUsecase) checkIsValidTagTitles(titles []string) error { if len(titles) > 7 { return fmt.Errorf("too many titles") } @@ -35,7 +35,7 @@ func (bCase *BoardUsecase) checkIsValidTagTitles(titles []string) error { return nil } -func (bCase *BoardUsecase) isValidBoardTitle(title string) bool { +func (bCase *boardUsecase) isValidBoardTitle(title string) bool { if len(title) == 0 || len(title) > 40 { return false } @@ -48,7 +48,7 @@ func (bCase *BoardUsecase) isValidBoardTitle(title string) bool { return true } -func (bCase *BoardUsecase) isValidUsername(username string) bool { +func (bCase *boardUsecase) isValidUsername(username string) bool { if len(username) < 4 || len(username) > 50 { return false } From f0e2cd93c485cc101dcfcbe6e03de6bd0453bfd9 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 13:38:00 +0300 Subject: [PATCH 132/266] TP-f21 update: repository board --- .../pkg/repository/board/postgres/repo.go | 33 ++++++++++--------- internal/pkg/repository/board/postgres/tag.go | 4 +-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index d296510..6d52f5c 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -14,17 +14,16 @@ import ( "github.com/jackc/pgx/v5/pgxpool" ) -type BoardRepoPG struct { +type boardRepoPG struct { db *pgxpool.Pool sqlBuilder squirrel.StatementBuilderType } -func NewBoardRepoPG(db *pgxpool.Pool) *BoardRepoPG { - return &BoardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} +func NewBoardRepoPG(db *pgxpool.Pool) *boardRepoPG { + return &boardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} } -func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) { - +func (repo *boardRepoPG) CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) { tx, err := repo.db.Begin(ctx) if err != nil { return 0, fmt.Errorf("starting transaction for creating new board: %w", err) @@ -48,12 +47,14 @@ func (repo *BoardRepoPG) CreateBoard(ctx context.Context, board entity.Board, ta return 0, fmt.Errorf("adding new tags on board within transaction: %w", err) } - tx.Commit(ctx) + if err = tx.Commit(ctx); err != nil { + return 0, fmt.Errorf("commit transaction for create new board: %w", err) + } return newBoardId, nil } -func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { +func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { getBoardsQuery := boardRepo.sqlBuilder. Select( "board.id", @@ -107,7 +108,7 @@ func (boardRepo *BoardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, return boards, nil } -func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) { +func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) { getBoardByIdQuery := repo.sqlBuilder. Select( "board.id", @@ -156,7 +157,7 @@ func (repo *BoardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces return board, nil } -func (repo *BoardRepoPG) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { +func (repo *boardRepoPG) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { row := repo.db.QueryRow(ctx, SelectBoardAuthorByBoardIdQuery, boardID) var authorID int err := row.Scan(&authorID) @@ -171,7 +172,7 @@ func (repo *BoardRepoPG) GetBoardAuthorByBoardID(ctx context.Context, boardID in return authorID, nil } -func (repo *BoardRepoPG) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) { +func (repo *boardRepoPG) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) { rows, err := repo.db.Query(ctx, SelectBoardContributorsByBoardIdQuery, boardID) if err != nil { return nil, fmt.Errorf("select contributors by board id query: %w", err) @@ -191,7 +192,7 @@ func (repo *BoardRepoPG) GetContributorsByBoardID(ctx context.Context, boardID i return contributors, nil } -func (repo *BoardRepoPG) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) { +func (repo *boardRepoPG) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) { rows, err := repo.db.Query(ctx, GetContributorBoardsIDs, contributorID) if err != nil { return nil, fmt.Errorf("get contributor boardsIDs query: %w", err) @@ -211,7 +212,7 @@ func (repo *BoardRepoPG) GetContributorBoardsIDs(ctx context.Context, contributo return boardsIDs, nil } -func (repo *BoardRepoPG) UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error { +func (repo *boardRepoPG) UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error { tx, err := repo.db.Begin(ctx) if err != nil { tx.Rollback(ctx) @@ -241,11 +242,13 @@ func (repo *BoardRepoPG) UpdateBoard(ctx context.Context, newBoardData entity.Bo return repository.ErrNoData } - tx.Commit(ctx) + if err = tx.Commit(ctx); err != nil { + return fmt.Errorf("commit transaction for update board: %w", err) + } return nil } -func (repo *BoardRepoPG) DeleteBoardByID(ctx context.Context, boardID int) error { +func (repo *boardRepoPG) DeleteBoardByID(ctx context.Context, boardID int) error { status, err := repo.db.Exec(ctx, DeleteBoardByIdQuery, time.Now(), boardID) if err != nil { return fmt.Errorf("delete board by id: %w", err) @@ -258,7 +261,7 @@ func (repo *BoardRepoPG) DeleteBoardByID(ctx context.Context, boardID int) error return nil } -func (repo *BoardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { +func (repo *boardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entity.Board) (int, error) { row := tx.QueryRow(ctx, InsertBoardQuery, board.AuthorID, board.Title, board.Description, board.Public) var newBoardID int diff --git a/internal/pkg/repository/board/postgres/tag.go b/internal/pkg/repository/board/postgres/tag.go index c6f3aee..35d6188 100644 --- a/internal/pkg/repository/board/postgres/tag.go +++ b/internal/pkg/repository/board/postgres/tag.go @@ -9,7 +9,7 @@ import ( "github.com/jackc/pgx/v5" ) -func (repo *BoardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []string) error { +func (repo *boardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []string) error { insertTagsQuery := repo.sqlBuilder. Insert("tag"). Columns("title") @@ -30,7 +30,7 @@ func (repo *BoardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []str return nil } -func (repo *BoardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitles []string, boardID int, isNewBoard bool) error { +func (repo *boardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitles []string, boardID int, isNewBoard bool) error { addTagsToBoardQuery := repo.sqlBuilder. Insert("board_tag"). Columns("board_id", "tag_id"). From a5b028b2dbddbbdbb0e20fda24593744a0defb03 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 13:38:59 +0300 Subject: [PATCH 133/266] TP-f21 update: logger in delivery board --- internal/pkg/delivery/http/v1/board.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 5d3ea58..c8952fd 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -21,7 +21,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&newBoard) defer r.Body.Close() if err != nil { - logger.Info("create board: ", log.F{"message", err.Error()}) + logger.Info("create board", log.F{"message", err.Error()}) responseError(w, BadBodyCode, BadBodyMessage) return } @@ -57,7 +57,7 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { userBoards, err := h.boardCase.GetBoardsByUsername(r.Context(), chi.URLParam(r, "username")) if err != nil { - logger.Info("get user boards: ", log.F{"message", err.Error()}) + logger.Info("get user boards", log.F{"message", err.Error()}) switch err { case bCase.ErrInvalidUsername: responseError(w, "bad_username", err.Error()) @@ -81,14 +81,14 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - logger.Info("get certain board ", log.F{"message", err.Error()}) + logger.Info("get certain board", log.F{"message", err.Error()}) responseError(w, BadQueryParamCode, BadQueryParamMessage) return } board, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) if err != nil { - logger.Info("get certain board: ", log.F{"message", err.Error()}) + logger.Info("get certain board", log.F{"message", err.Error()}) switch err { case bCase.ErrNoSuchBoard: responseError(w, "no_board", err.Error()) @@ -112,7 +112,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - logger.Info("update certain board ", log.F{"message", err.Error()}) + logger.Info("update certain board", log.F{"message", err.Error()}) responseError(w, BadQueryParamCode, BadQueryParamMessage) return } @@ -121,7 +121,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(r.Body).Decode(&updatedBoard) defer r.Body.Close() if err != nil { - logger.Info("update certain board: ", log.F{"message", err.Error()}) + logger.Info("update certain board", log.F{"message", err.Error()}) responseError(w, BadBodyCode, BadBodyMessage) return } @@ -129,7 +129,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard) if err != nil { - logger.Info("update certain board: ", log.F{"message", err.Error()}) + logger.Info("update certain board", log.F{"message", err.Error()}) switch err { case bCase.ErrNoSuchBoard: responseError(w, "no_board", err.Error()) @@ -161,14 +161,14 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - logger.Info("delete board ", log.F{"message", err.Error()}) + logger.Info("delete board", log.F{"message", err.Error()}) responseError(w, BadQueryParamCode, BadQueryParamMessage) return } err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) if err != nil { - logger.Info("delete board: ", log.F{"message", err.Error()}) + logger.Info("delete board", log.F{"message", err.Error()}) switch err { case bCase.ErrNoSuchBoard: responseError(w, "no_board", err.Error()) From 585c0b35c1b7606032ef899eda02e0cc11ec1b36 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 17:45:03 +0300 Subject: [PATCH 134/266] TP-f21 add: fix pins on board --- internal/api/server/router/router.go | 1 + internal/pkg/delivery/http/v1/board.go | 58 +++++++++++++++++++ internal/pkg/middleware/middleware.go | 2 +- .../pkg/repository/board/postgres/queries.go | 4 ++ .../pkg/repository/board/postgres/repo.go | 17 ++++++ .../pkg/repository/board/postgres/role.go | 41 +++++++++++++ internal/pkg/repository/board/repo.go | 12 ++++ internal/pkg/repository/pin/repo.go | 31 ++++++++++ internal/pkg/repository/pin/tag.go | 5 +- internal/pkg/repository/ramrepo/pin.go | 4 ++ internal/pkg/usecase/board/update.go | 17 ++++++ internal/pkg/usecase/board/usecase.go | 1 + internal/pkg/usecase/pin/check.go | 35 +++++++++-- internal/pkg/usecase/pin/usecase.go | 2 + 14 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 internal/pkg/repository/board/postgres/role.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e447e42..0fcef89 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -83,6 +83,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Get("/{boardID:\\d+}", handler.GetCertainBoard) }) r.With(auth.RequireAuth).Group(func(r chi.Router) { + r.Post("/add/pins/{boardID:\\d+}", handler.AddPinsToBoard) r.Post("/create", handler.CreateNewBoard) r.Put("/update/{boardID:\\d+}", handler.UpdateBoardInfo) r.Delete("/delete/{boardID:\\d+}", handler.DeleteBoard) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index c8952fd..cec93a6 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -188,3 +188,61 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { w.Write([]byte(InternalServerErrMessage)) } } + +func (h *HandlerHTTP) AddPinsToBoard(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + boardIDStr := chi.URLParam(r, "boardID") + boardID, err := strconv.ParseInt(boardIDStr, 10, 64) + if err != nil { + logger.Error("parse board id from query params") + err = responseError(w, "parse_url", "internal error") + if err != nil { + logger.Error(err.Error()) + } + return + } + + pins := make(map[string][]int) + err = json.NewDecoder(r.Body).Decode(&pins) + defer r.Body.Close() + if err != nil { + logger.Info("bad decode body") + err = responseError(w, "bad_body", "failed to parse the request body") + if err != nil { + logger.Error(err.Error()) + } + return + } + pinIds, ok := pins["pins"] + if !ok { + logger.Info("the request does not specify pins") + err = responseError(w, "bad_body", "the request does not specify pins") + if err != nil { + logger.Error(err.Error()) + } + return + } + + err = h.pinCase.IsAvailableBatchPinForFixOnBoard(r.Context(), pinIds, userID) + if err != nil { + logger.Warn(err.Error(), log.F{"action", "check availability pins for fixed on board"}) + err = responseError(w, "not_access", "there are pins in the batch that are not available for the user to add") + if err != nil { + logger.Error(err.Error()) + } + return + } + + err = h.boardCase.FixPinsOnBoard(r.Context(), int(boardID), pinIds, userID) + if err != nil { + logger.Warn(err.Error(), log.F{"action", "fix pins on board"}) + err = responseError(w, "not_access", "there are pins in the batch that are not available for the user to add") + } else { + err = responseOk(http.StatusCreated, w, "pins have been successfully added to the board", nil) + } + if err != nil { + logger.Error(err.Error()) + } +} diff --git a/internal/pkg/middleware/middleware.go b/internal/pkg/middleware/middleware.go index 383b061..886d1d7 100644 --- a/internal/pkg/middleware/middleware.go +++ b/internal/pkg/middleware/middleware.go @@ -51,7 +51,7 @@ func Logger(logMW *logger.Logger) Middleware { defer func(t time.Time) { log.InfoMap("response", logger.M{ "status": wrapResponse.statusCode, - "processing_time_ms": time.Since(t).Milliseconds(), + "processing_time_us": time.Since(t).Microseconds(), "content_type": w.Header().Get("Content-Type"), "content_length": w.Header().Get("Content-Length"), "written": wrapResponse.written, diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index dec7a1a..00b8592 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -7,4 +7,8 @@ const ( UpdateBoardByIdQuery = "UPDATE board SET title = $1, description = $2, public = $3 WHERE id = $4 AND deleted_at IS NULL;" GetContributorBoardsIDs = "SELECT board_id FROM contributor WHERE user_id = $1;" DeleteBoardByIdQuery = "UPDATE board SET deleted_at = $1 WHERE id = $2;" + SelectAuthorOrContributorRole = `SELECT board.author, role.name FROM board LEFT JOIN contributor + ON contributor.board_id = board.id AND contributor.user_id = $1 LEFT JOIN role + ON contributor.role_id = role.id + WHERE board.id = $2;` ) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 6d52f5c..6a768c2 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -271,3 +271,20 @@ func (repo *boardRepoPG) insertBoard(ctx context.Context, tx pgx.Tx, board entit } return newBoardID, nil } + +func (repo *boardRepoPG) AddPinsOnBoard(ctx context.Context, boardID int, pinIds []int) error { + insertBuilder := repo.sqlBuilder.Insert("membership").Columns("pin_id", "board_id") + for _, pinID := range pinIds { + insertBuilder = insertBuilder.Values(pinID, boardID) + } + sqlRow, args, err := insertBuilder.Suffix("ON CONFLICT (pin_id, board_id) DO NOTHING").ToSql() + if err != nil { + return fmt.Errorf("build sql query for add pins on board: %w", err) + } + + _, err = repo.db.Exec(ctx, sqlRow, args...) + if err != nil { + return fmt.Errorf("insert membership for add pins on board: %w", err) + } + return nil +} diff --git a/internal/pkg/repository/board/postgres/role.go b/internal/pkg/repository/board/postgres/role.go new file mode 100644 index 0000000..d743ca1 --- /dev/null +++ b/internal/pkg/repository/board/postgres/role.go @@ -0,0 +1,41 @@ +package board + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgtype" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" +) + +func (repo *boardRepoPG) RoleUserHaveOnThisBoard(ctx context.Context, boardID int, userID int) (board.UserRole, error) { + row := repo.db.QueryRow(ctx, SelectAuthorOrContributorRole, userID, boardID) + var ( + author int + role pgtype.Text + ) + err := row.Scan(&author, &role) + if err != nil { + return 0, fmt.Errorf("scan select row for getting user role: %w", err) + } + if userID == author { + return board.Author, nil + } + return getUserRole(role), nil +} + +func getUserRole(role pgtype.Text) board.UserRole { + if !role.Valid { + return board.RegularUser + } + + switch role.String { + case "read-write": + return board.ContributorForReading | board.ContributorForAdding + case "read-only": + return board.ContributorForReading + default: + return board.RegularUser + } +} diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 88c6eae..dce2107 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -17,4 +17,16 @@ type Repository interface { GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error DeleteBoardByID(ctx context.Context, boardID int) error + RoleUserHaveOnThisBoard(ctx context.Context, boardID int, userID int) (UserRole, error) + AddPinsOnBoard(ctx context.Context, boardID int, pinIds []int) error } + +type UserRole uint8 + +const ( + RegularUser UserRole = 1 << iota + Subscriber + ContributorForReading + ContributorForAdding + Author +) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index ab7ce0a..982a8e3 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -19,6 +19,7 @@ type Repository interface { GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) + GetBatchPinByID(ctx context.Context, pinID []int) ([]entity.Pin, error) AddNewPin(ctx context.Context, pin *entity.Pin) error DeletePin(ctx context.Context, pinID, userID int) error SetLike(ctx context.Context, pinID, userID int) (int, error) @@ -101,6 +102,36 @@ func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int, revealAuthor bool return pin, nil } +func (p *pinRepoPG) GetBatchPinByID(ctx context.Context, pinID []int) ([]entity.Pin, error) { + sqlRow, args, err := p.sqlBuilder.Select("id", "author", "public", "deleted_at"). + From("profile"). + Where(sq.Eq{"id": pinID}). + ToSql() + if err != nil { + return nil, fmt.Errorf("sql query build for get batch pins: %w", err) + } + + rows, err := p.db.Query(ctx, sqlRow, args...) + if err != nil { + return nil, fmt.Errorf("select batch pins: %w", err) + } + + pin := entity.Pin{} + pins := make([]entity.Pin, 0, len(pinID)) + for rows.Next() { + err = rows.Scan(&pin.ID, &pin.Author.ID, &pin.Public, &pin.DeletedAt) + if err != nil { + return nil, fmt.Errorf("scan result select batch pins: %w", err) + } + pins = append(pins, pin) + } + + if len(pins) != len(pinID) { + return nil, ErrNumberSelectRows + } + return pins, nil +} + func (p *pinRepoPG) AddNewPin(ctx context.Context, pin *entity.Pin) error { titles := fetchTitles(pin.Tags) diff --git a/internal/pkg/repository/pin/tag.go b/internal/pkg/repository/pin/tag.go index e739fcf..5a3999b 100644 --- a/internal/pkg/repository/pin/tag.go +++ b/internal/pkg/repository/pin/tag.go @@ -12,7 +12,10 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" ) -var ErrNumberAffectedRows = errors.New("different number of affected rows was expected") +var ( + ErrNumberAffectedRows = errors.New("different number of affected rows was expected") + ErrNumberSelectRows = errors.New("the expected number of records does not match the selected one") +) func (p *pinRepoPG) GetTagsByPinID(ctx context.Context, pinID int) ([]pin.Tag, error) { rows, err := p.db.Query(ctx, SelectTagsByPinID, pinID) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 520e009..a827661 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -88,3 +88,7 @@ func (r *ramPinRepo) GetSortedUserPins(ctx context.Context, userID, count, minID func (r *ramPinRepo) IsSetLike(ctx context.Context, pinID, userID int) (bool, error) { return false, ErrMethodUnimplemented } + +func (r *ramPinRepo) GetBatchPinByID(ctx context.Context, pinID []int) ([]pin.Pin, error) { + return nil, ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go index 92f5dfe..877cf37 100644 --- a/internal/pkg/usecase/board/update.go +++ b/internal/pkg/usecase/board/update.go @@ -7,6 +7,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + repoBoard "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) @@ -45,3 +46,19 @@ func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto. } return nil } + +func (b *boardUsecase) FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error { + role, err := b.boardRepo.RoleUserHaveOnThisBoard(ctx, boardID, userID) + if err != nil { + return fmt.Errorf("get role for fix pins: %w", err) + } + if role&(repoBoard.Author|repoBoard.ContributorForAdding) == 0 { + return ErrNoAccess + } + + err = b.boardRepo.AddPinsOnBoard(ctx, boardID, pinIds) + if err != nil { + return fmt.Errorf("fix pins on board: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 7bf5a59..223e840 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -16,6 +16,7 @@ type Usecase interface { GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error DeleteCertainBoard(ctx context.Context, boardID int) error + FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error } type boardUsecase struct { diff --git a/internal/pkg/usecase/pin/check.go b/internal/pkg/usecase/pin/check.go index d3e41cd..396a45d 100644 --- a/internal/pkg/usecase/pin/check.go +++ b/internal/pkg/usecase/pin/check.go @@ -12,8 +12,12 @@ var ( ErrPinNotAccess = errors.New("pin is not available") ErrPinDeleted = errors.New("pin has been deleted") ErrForbiddenAction = errors.New("this action is not available to the user") + ErrEmptyBatch = errors.New("an empty batch was received") + ErrSizeBatch = errors.New("the batch size exceeds the maximum possible") ) +const MaxSizeBatchPin = 100 + const UserUnknown = -1 func (p *pinCase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { @@ -22,13 +26,24 @@ func (p *pinCase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID return err } - if pin.DeletedAt.Valid { - return ErrPinDeleted + return isAvailableBatchPinForFixOnBoard(userID, *pin) +} + +func (p *pinCase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { + if len(pinID) == 0 { + return ErrEmptyBatch } - if !pin.Public && pin.Author.ID != userID { - return ErrForbiddenAction + if len(pinID) > MaxSizeBatchPin { + return ErrSizeBatch } + pins, err := p.repo.GetBatchPinByID(ctx, pinID) + if err != nil { + return fmt.Errorf("get batch pin for chekc available: %w", err) + } + if err = isAvailableBatchPinForFixOnBoard(userID, pins...); err != nil { + return fmt.Errorf("one of the pins turned out to be inaccessible for fixing on the board: %w", err) + } return nil } @@ -63,3 +78,15 @@ func (p *pinCase) isAvailablePinForSetLike(ctx context.Context, pinID, userID in return p.isAvailablePinForViewingUser(ctx, pin, userID) } + +func isAvailableBatchPinForFixOnBoard(userID int, pins ...entity.Pin) error { + for ind := range pins { + if pins[ind].DeletedAt.Valid { + return ErrPinDeleted + } + if !pins[ind].Public && pins[ind].Author.ID != userID { + return ErrForbiddenAction + } + } + return nil +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 07780c5..89da90d 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -25,6 +25,8 @@ type Usecase interface { DeleteLikeFromUser(ctx context.Context, pinID, userID int) error EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) + IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error + IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error } type pinCase struct { From dabb9d8f1dc9aed18578c2475d56eb7702eba534 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 18:53:17 +0300 Subject: [PATCH 135/266] TP-28e add: mock for usecases and repositories --- Makefile | 10 +- go.mod | 1 + go.sum | 14 + .../pkg/repository/board/mock/board_mock.go | 186 +++++++++++++ internal/pkg/repository/board/repo.go | 1 + .../pkg/repository/image/mock/image_mock.go | 75 ++++++ internal/pkg/repository/image/repo.go | 1 + internal/pkg/repository/pin/mock/pin_mock.go | 244 ++++++++++++++++++ internal/pkg/repository/pin/repo.go | 2 + .../repository/session/mock/session_mock.go | 93 +++++++ internal/pkg/repository/session/repo.go | 1 + .../pkg/repository/user/mock/user_mock.go | 155 +++++++++++ internal/pkg/repository/user/repo.go | 1 + internal/pkg/usecase/board/mock/board_mock.go | 123 +++++++++ internal/pkg/usecase/board/usecase.go | 1 + internal/pkg/usecase/image/mock/image_mock.go | 51 ++++ internal/pkg/usecase/image/usecase.go | 1 + internal/pkg/usecase/pin/mock/pin_mock.go | 198 ++++++++++++++ internal/pkg/usecase/pin/usecase.go | 1 + internal/pkg/usecase/session/manager.go | 1 + .../pkg/usecase/session/mock/session_mock.go | 80 ++++++ internal/pkg/usecase/user/mock/user_mock.go | 125 +++++++++ internal/pkg/usecase/user/usecase.go | 1 + 23 files changed, 1365 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/repository/board/mock/board_mock.go create mode 100644 internal/pkg/repository/image/mock/image_mock.go create mode 100644 internal/pkg/repository/pin/mock/pin_mock.go create mode 100644 internal/pkg/repository/session/mock/session_mock.go create mode 100644 internal/pkg/repository/user/mock/user_mock.go create mode 100644 internal/pkg/usecase/board/mock/board_mock.go create mode 100644 internal/pkg/usecase/image/mock/image_mock.go create mode 100644 internal/pkg/usecase/pin/mock/pin_mock.go create mode 100644 internal/pkg/usecase/session/mock/session_mock.go create mode 100644 internal/pkg/usecase/user/mock/user_mock.go diff --git a/Makefile b/Makefile index 212a845..a7e4aae 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run test test_with_coverage cleantest retest doc +.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out @@ -26,3 +26,11 @@ retest: doc: swag fmt swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) + +generate: + go generate ./... + +cover_all: + go test -coverpkg=./... -coverprofile=cover ./... + cat cover | grep -v "mock" | grep -v "easyjson" | grep -v "proto" | grep -v "ramrepo" > cover.out + go tool cover -func=cover.out diff --git a/go.mod b/go.mod index 0f728f1..451a071 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Masterminds/squirrel v1.5.4 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 + github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/microcosm-cc/bluemonday v1.0.26 diff --git a/go.sum b/go.sum index ba611c1..745d1b7 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDB github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= 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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -137,6 +139,7 @@ github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9 github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= @@ -149,6 +152,7 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -157,26 +161,32 @@ golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -187,9 +197,13 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/pkg/repository/board/mock/board_mock.go b/internal/pkg/repository/board/mock/board_mock.go new file mode 100644 index 0000000..6e77688 --- /dev/null +++ b/internal/pkg/repository/board/mock/board_mock.go @@ -0,0 +1,186 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + board0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" + board1 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddPinsOnBoard mocks base method. +func (m *MockRepository) AddPinsOnBoard(ctx context.Context, boardID int, pinIds []int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddPinsOnBoard", ctx, boardID, pinIds) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddPinsOnBoard indicates an expected call of AddPinsOnBoard. +func (mr *MockRepositoryMockRecorder) AddPinsOnBoard(ctx, boardID, pinIds interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPinsOnBoard", reflect.TypeOf((*MockRepository)(nil).AddPinsOnBoard), ctx, boardID, pinIds) +} + +// CreateBoard mocks base method. +func (m *MockRepository) CreateBoard(ctx context.Context, board board.Board, tagTitles []string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBoard", ctx, board, tagTitles) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBoard indicates an expected call of CreateBoard. +func (mr *MockRepositoryMockRecorder) CreateBoard(ctx, board, tagTitles interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBoard", reflect.TypeOf((*MockRepository)(nil).CreateBoard), ctx, board, tagTitles) +} + +// DeleteBoardByID mocks base method. +func (m *MockRepository) DeleteBoardByID(ctx context.Context, boardID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBoardByID", ctx, boardID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBoardByID indicates an expected call of DeleteBoardByID. +func (mr *MockRepositoryMockRecorder) DeleteBoardByID(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoardByID", reflect.TypeOf((*MockRepository)(nil).DeleteBoardByID), ctx, boardID) +} + +// GetBoardAuthorByBoardID mocks base method. +func (m *MockRepository) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardAuthorByBoardID", ctx, boardID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardAuthorByBoardID indicates an expected call of GetBoardAuthorByBoardID. +func (mr *MockRepositoryMockRecorder) GetBoardAuthorByBoardID(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardAuthorByBoardID", reflect.TypeOf((*MockRepository)(nil).GetBoardAuthorByBoardID), ctx, boardID) +} + +// GetBoardByID mocks base method. +func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board1.UserBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardByID", ctx, boardID, hasAccess) + ret0, _ := ret[0].(board1.UserBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardByID indicates an expected call of GetBoardByID. +func (mr *MockRepositoryMockRecorder) GetBoardByID(ctx, boardID, hasAccess interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardByID", reflect.TypeOf((*MockRepository)(nil).GetBoardByID), ctx, boardID, hasAccess) +} + +// GetBoardsByUserID mocks base method. +func (m *MockRepository) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]board1.UserBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardsByUserID", ctx, userID, isAuthor, accessableBoardsIDs) + ret0, _ := ret[0].([]board1.UserBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardsByUserID indicates an expected call of GetBoardsByUserID. +func (mr *MockRepositoryMockRecorder) GetBoardsByUserID(ctx, userID, isAuthor, accessableBoardsIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardsByUserID", reflect.TypeOf((*MockRepository)(nil).GetBoardsByUserID), ctx, userID, isAuthor, accessableBoardsIDs) +} + +// GetContributorBoardsIDs mocks base method. +func (m *MockRepository) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContributorBoardsIDs", ctx, contributorID) + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContributorBoardsIDs indicates an expected call of GetContributorBoardsIDs. +func (mr *MockRepositoryMockRecorder) GetContributorBoardsIDs(ctx, contributorID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorBoardsIDs", reflect.TypeOf((*MockRepository)(nil).GetContributorBoardsIDs), ctx, contributorID) +} + +// GetContributorsByBoardID mocks base method. +func (m *MockRepository) GetContributorsByBoardID(ctx context.Context, boardID int) ([]user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContributorsByBoardID", ctx, boardID) + ret0, _ := ret[0].([]user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContributorsByBoardID indicates an expected call of GetContributorsByBoardID. +func (mr *MockRepositoryMockRecorder) GetContributorsByBoardID(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorsByBoardID", reflect.TypeOf((*MockRepository)(nil).GetContributorsByBoardID), ctx, boardID) +} + +// RoleUserHaveOnThisBoard mocks base method. +func (m *MockRepository) RoleUserHaveOnThisBoard(ctx context.Context, boardID, userID int) (board0.UserRole, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RoleUserHaveOnThisBoard", ctx, boardID, userID) + ret0, _ := ret[0].(board0.UserRole) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RoleUserHaveOnThisBoard indicates an expected call of RoleUserHaveOnThisBoard. +func (mr *MockRepositoryMockRecorder) RoleUserHaveOnThisBoard(ctx, boardID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoleUserHaveOnThisBoard", reflect.TypeOf((*MockRepository)(nil).RoleUserHaveOnThisBoard), ctx, boardID, userID) +} + +// UpdateBoard mocks base method. +func (m *MockRepository) UpdateBoard(ctx context.Context, newBoardData board.Board, tagTitles []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBoard", ctx, newBoardData, tagTitles) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBoard indicates an expected call of UpdateBoard. +func (mr *MockRepositoryMockRecorder) UpdateBoard(ctx, newBoardData, tagTitles interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBoard", reflect.TypeOf((*MockRepository)(nil).UpdateBoard), ctx, newBoardData, tagTitles) +} diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index dce2107..6a49903 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -8,6 +8,7 @@ import ( dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) +//go:generate mockgen -destination=./mock/board_mock.go -package=mock -source=repo.go Repository type Repository interface { CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) diff --git a/internal/pkg/repository/image/mock/image_mock.go b/internal/pkg/repository/image/mock/image_mock.go new file mode 100644 index 0000000..090fae9 --- /dev/null +++ b/internal/pkg/repository/image/mock/image_mock.go @@ -0,0 +1,75 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + io "io" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// SaveImage mocks base method. +func (m *MockRepository) SaveImage(prefixPath, extension string, image io.Reader) (string, int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveImage", prefixPath, extension, image) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// SaveImage indicates an expected call of SaveImage. +func (mr *MockRepositoryMockRecorder) SaveImage(prefixPath, extension, image interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveImage", reflect.TypeOf((*MockRepository)(nil).SaveImage), prefixPath, extension, image) +} + +// SetBasePath mocks base method. +func (m *MockRepository) SetBasePath(path string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBasePath", path) +} + +// SetBasePath indicates an expected call of SetBasePath. +func (mr *MockRepositoryMockRecorder) SetBasePath(path interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBasePath", reflect.TypeOf((*MockRepository)(nil).SetBasePath), path) +} + +// SetDirToSave mocks base method. +func (m *MockRepository) SetDirToSave(fn func() string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetDirToSave", fn) +} + +// SetDirToSave indicates an expected call of SetDirToSave. +func (mr *MockRepositoryMockRecorder) SetDirToSave(fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDirToSave", reflect.TypeOf((*MockRepository)(nil).SetDirToSave), fn) +} diff --git a/internal/pkg/repository/image/repo.go b/internal/pkg/repository/image/repo.go index a75e33f..ca1a42e 100644 --- a/internal/pkg/repository/image/repo.go +++ b/internal/pkg/repository/image/repo.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" ) +//go:generate mockgen -destination=./mock/image_mock.go -package=mock -source=repo.go Repository type Repository interface { SaveImage(prefixPath, extension string, image io.Reader) (filename string, written int64, err error) SetBasePath(path string) diff --git a/internal/pkg/repository/pin/mock/pin_mock.go b/internal/pkg/repository/pin/mock/pin_mock.go new file mode 100644 index 0000000..a3048d0 --- /dev/null +++ b/internal/pkg/repository/pin/mock/pin_mock.go @@ -0,0 +1,244 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + pin0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddNewPin mocks base method. +func (m *MockRepository) AddNewPin(ctx context.Context, pin *pin.Pin) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddNewPin", ctx, pin) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddNewPin indicates an expected call of AddNewPin. +func (mr *MockRepositoryMockRecorder) AddNewPin(ctx, pin interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddNewPin", reflect.TypeOf((*MockRepository)(nil).AddNewPin), ctx, pin) +} + +// DelLike mocks base method. +func (m *MockRepository) DelLike(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DelLike", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DelLike indicates an expected call of DelLike. +func (mr *MockRepositoryMockRecorder) DelLike(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelLike", reflect.TypeOf((*MockRepository)(nil).DelLike), ctx, pinID, userID) +} + +// DeletePin mocks base method. +func (m *MockRepository) DeletePin(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePin", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePin indicates an expected call of DeletePin. +func (mr *MockRepositoryMockRecorder) DeletePin(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePin", reflect.TypeOf((*MockRepository)(nil).DeletePin), ctx, pinID, userID) +} + +// EditPin mocks base method. +func (m *MockRepository) EditPin(ctx context.Context, pinID int, updateData pin0.S, titleTags []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditPin", ctx, pinID, updateData, titleTags) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditPin indicates an expected call of EditPin. +func (mr *MockRepositoryMockRecorder) EditPin(ctx, pinID, updateData, titleTags interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPin", reflect.TypeOf((*MockRepository)(nil).EditPin), ctx, pinID, updateData, titleTags) +} + +// GetAuthorPin mocks base method. +func (m *MockRepository) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAuthorPin", ctx, pinID) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAuthorPin indicates an expected call of GetAuthorPin. +func (mr *MockRepositoryMockRecorder) GetAuthorPin(ctx, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorPin", reflect.TypeOf((*MockRepository)(nil).GetAuthorPin), ctx, pinID) +} + +// GetBatchPinByID mocks base method. +func (m *MockRepository) GetBatchPinByID(ctx context.Context, pinID []int) ([]pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBatchPinByID", ctx, pinID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBatchPinByID indicates an expected call of GetBatchPinByID. +func (mr *MockRepositoryMockRecorder) GetBatchPinByID(ctx, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBatchPinByID", reflect.TypeOf((*MockRepository)(nil).GetBatchPinByID), ctx, pinID) +} + +// GetCountLikeByPinID mocks base method. +func (m *MockRepository) GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCountLikeByPinID", ctx, pinID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCountLikeByPinID indicates an expected call of GetCountLikeByPinID. +func (mr *MockRepositoryMockRecorder) GetCountLikeByPinID(ctx, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCountLikeByPinID", reflect.TypeOf((*MockRepository)(nil).GetCountLikeByPinID), ctx, pinID) +} + +// GetPinByID mocks base method. +func (m *MockRepository) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPinByID", ctx, pinID, revealAuthor) + ret0, _ := ret[0].(*pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPinByID indicates an expected call of GetPinByID. +func (mr *MockRepositoryMockRecorder) GetPinByID(ctx, pinID, revealAuthor interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPinByID", reflect.TypeOf((*MockRepository)(nil).GetPinByID), ctx, pinID, revealAuthor) +} + +// GetSortedNewNPins mocks base method. +func (m *MockRepository) GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSortedNewNPins", ctx, count, midID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSortedNewNPins indicates an expected call of GetSortedNewNPins. +func (mr *MockRepositoryMockRecorder) GetSortedNewNPins(ctx, count, midID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSortedNewNPins", reflect.TypeOf((*MockRepository)(nil).GetSortedNewNPins), ctx, count, midID, maxID) +} + +// GetSortedUserPins mocks base method. +func (m *MockRepository) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSortedUserPins", ctx, userID, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSortedUserPins indicates an expected call of GetSortedUserPins. +func (mr *MockRepositoryMockRecorder) GetSortedUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSortedUserPins", reflect.TypeOf((*MockRepository)(nil).GetSortedUserPins), ctx, userID, count, minID, maxID) +} + +// GetTagsByPinID mocks base method. +func (m *MockRepository) GetTagsByPinID(ctx context.Context, pinID int) ([]pin.Tag, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagsByPinID", ctx, pinID) + ret0, _ := ret[0].([]pin.Tag) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTagsByPinID indicates an expected call of GetTagsByPinID. +func (mr *MockRepositoryMockRecorder) GetTagsByPinID(ctx, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagsByPinID", reflect.TypeOf((*MockRepository)(nil).GetTagsByPinID), ctx, pinID) +} + +// IsAvailableToUserAsContributorBoard mocks base method. +func (m *MockRepository) IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailableToUserAsContributorBoard", ctx, pinID, userID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsAvailableToUserAsContributorBoard indicates an expected call of IsAvailableToUserAsContributorBoard. +func (mr *MockRepositoryMockRecorder) IsAvailableToUserAsContributorBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableToUserAsContributorBoard", reflect.TypeOf((*MockRepository)(nil).IsAvailableToUserAsContributorBoard), ctx, pinID, userID) +} + +// IsSetLike mocks base method. +func (m *MockRepository) IsSetLike(ctx context.Context, pinID, userID int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsSetLike", ctx, pinID, userID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsSetLike indicates an expected call of IsSetLike. +func (mr *MockRepositoryMockRecorder) IsSetLike(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSetLike", reflect.TypeOf((*MockRepository)(nil).IsSetLike), ctx, pinID, userID) +} + +// SetLike mocks base method. +func (m *MockRepository) SetLike(ctx context.Context, pinID, userID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLike", ctx, pinID, userID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetLike indicates an expected call of SetLike. +func (mr *MockRepositoryMockRecorder) SetLike(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLike", reflect.TypeOf((*MockRepository)(nil).SetLike), ctx, pinID, userID) +} diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 982a8e3..48ebf0a 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -14,6 +14,8 @@ import ( ) type S map[string]any + +//go:generate mockgen -destination=./mock/pin_mock.go -package=mock -source=repo.go Repository type Repository interface { GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) diff --git a/internal/pkg/repository/session/mock/session_mock.go b/internal/pkg/repository/session/mock/session_mock.go new file mode 100644 index 0000000..c1b0b1e --- /dev/null +++ b/internal/pkg/repository/session/mock/session_mock.go @@ -0,0 +1,93 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + session "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddSession mocks base method. +func (m *MockRepository) AddSession(ctx context.Context, session *session.Session) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddSession", ctx, session) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddSession indicates an expected call of AddSession. +func (mr *MockRepositoryMockRecorder) AddSession(ctx, session interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSession", reflect.TypeOf((*MockRepository)(nil).AddSession), ctx, session) +} + +// DeleteAllSessionForUser mocks base method. +func (m *MockRepository) DeleteAllSessionForUser(ctx context.Context, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAllSessionForUser", ctx, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllSessionForUser indicates an expected call of DeleteAllSessionForUser. +func (mr *MockRepositoryMockRecorder) DeleteAllSessionForUser(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllSessionForUser", reflect.TypeOf((*MockRepository)(nil).DeleteAllSessionForUser), ctx, userID) +} + +// DeleteSessionByKey mocks base method. +func (m *MockRepository) DeleteSessionByKey(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSessionByKey", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSessionByKey indicates an expected call of DeleteSessionByKey. +func (mr *MockRepositoryMockRecorder) DeleteSessionByKey(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSessionByKey", reflect.TypeOf((*MockRepository)(nil).DeleteSessionByKey), ctx, key) +} + +// GetSessionByKey mocks base method. +func (m *MockRepository) GetSessionByKey(ctx context.Context, key string) (*session.Session, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSessionByKey", ctx, key) + ret0, _ := ret[0].(*session.Session) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSessionByKey indicates an expected call of GetSessionByKey. +func (mr *MockRepositoryMockRecorder) GetSessionByKey(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionByKey", reflect.TypeOf((*MockRepository)(nil).GetSessionByKey), ctx, key) +} diff --git a/internal/pkg/repository/session/repo.go b/internal/pkg/repository/session/repo.go index 36cf9ca..425b0e7 100644 --- a/internal/pkg/repository/session/repo.go +++ b/internal/pkg/repository/session/repo.go @@ -13,6 +13,7 @@ import ( var ErrMethodUnimplemented = errors.New("unimplemented") var ErrExistsSession = errors.New("the session already exists") +//go:generate mockgen -destination=./mock/session_mock.go -package=mock -source=repo.go Repository type Repository interface { AddSession(ctx context.Context, session *session.Session) error GetSessionByKey(ctx context.Context, key string) (*session.Session, error) diff --git a/internal/pkg/repository/user/mock/user_mock.go b/internal/pkg/repository/user/mock/user_mock.go new file mode 100644 index 0000000..99b06b9 --- /dev/null +++ b/internal/pkg/repository/user/mock/user_mock.go @@ -0,0 +1,155 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + user0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddNewUser mocks base method. +func (m *MockRepository) AddNewUser(ctx context.Context, user *user.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddNewUser", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddNewUser indicates an expected call of AddNewUser. +func (mr *MockRepositoryMockRecorder) AddNewUser(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddNewUser", reflect.TypeOf((*MockRepository)(nil).AddNewUser), ctx, user) +} + +// EditUserAvatar mocks base method. +func (m *MockRepository) EditUserAvatar(ctx context.Context, userID int, avatar string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditUserAvatar", ctx, userID, avatar) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditUserAvatar indicates an expected call of EditUserAvatar. +func (mr *MockRepositoryMockRecorder) EditUserAvatar(ctx, userID, avatar interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditUserAvatar", reflect.TypeOf((*MockRepository)(nil).EditUserAvatar), ctx, userID, avatar) +} + +// EditUserInfo mocks base method. +func (m *MockRepository) EditUserInfo(ctx context.Context, userID int, updateFields user0.S) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditUserInfo", ctx, userID, updateFields) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditUserInfo indicates an expected call of EditUserInfo. +func (mr *MockRepositoryMockRecorder) EditUserInfo(ctx, userID, updateFields interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditUserInfo", reflect.TypeOf((*MockRepository)(nil).EditUserInfo), ctx, userID, updateFields) +} + +// GetAllUserData mocks base method. +func (m *MockRepository) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllUserData", ctx, userID) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllUserData indicates an expected call of GetAllUserData. +func (mr *MockRepositoryMockRecorder) GetAllUserData(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllUserData", reflect.TypeOf((*MockRepository)(nil).GetAllUserData), ctx, userID) +} + +// GetLastUserID mocks base method. +func (m *MockRepository) GetLastUserID(ctx context.Context) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLastUserID", ctx) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLastUserID indicates an expected call of GetLastUserID. +func (mr *MockRepositoryMockRecorder) GetLastUserID(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastUserID", reflect.TypeOf((*MockRepository)(nil).GetLastUserID), ctx) +} + +// GetUserByUsername mocks base method. +func (m *MockRepository) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByUsername", ctx, username) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByUsername indicates an expected call of GetUserByUsername. +func (mr *MockRepositoryMockRecorder) GetUserByUsername(ctx, username interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockRepository)(nil).GetUserByUsername), ctx, username) +} + +// GetUserIdByUsername mocks base method. +func (m *MockRepository) GetUserIdByUsername(ctx context.Context, username string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserIdByUsername", ctx, username) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserIdByUsername indicates an expected call of GetUserIdByUsername. +func (mr *MockRepositoryMockRecorder) GetUserIdByUsername(ctx, username interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserIdByUsername", reflect.TypeOf((*MockRepository)(nil).GetUserIdByUsername), ctx, username) +} + +// GetUsernameAndAvatarByID mocks base method. +func (m *MockRepository) GetUsernameAndAvatarByID(ctx context.Context, userID int) (string, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsernameAndAvatarByID", ctx, userID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetUsernameAndAvatarByID indicates an expected call of GetUsernameAndAvatarByID. +func (mr *MockRepositoryMockRecorder) GetUsernameAndAvatarByID(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsernameAndAvatarByID", reflect.TypeOf((*MockRepository)(nil).GetUsernameAndAvatarByID), ctx, userID) +} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 8cc6b37..beff813 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -12,6 +12,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" ) +//go:generate mockgen -destination=./mock/user_mock.go -package=mock -source=repo.go Repository type Repository interface { AddNewUser(ctx context.Context, user *user.User) error GetUserByUsername(ctx context.Context, username string) (*user.User, error) diff --git a/internal/pkg/usecase/board/mock/board_mock.go b/internal/pkg/usecase/board/mock/board_mock.go new file mode 100644 index 0000000..ca9e41e --- /dev/null +++ b/internal/pkg/usecase/board/mock/board_mock.go @@ -0,0 +1,123 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// CreateNewBoard mocks base method. +func (m *MockUsecase) CreateNewBoard(ctx context.Context, newBoard board.BoardData) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNewBoard", ctx, newBoard) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateNewBoard indicates an expected call of CreateNewBoard. +func (mr *MockUsecaseMockRecorder) CreateNewBoard(ctx, newBoard interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewBoard", reflect.TypeOf((*MockUsecase)(nil).CreateNewBoard), ctx, newBoard) +} + +// DeleteCertainBoard mocks base method. +func (m *MockUsecase) DeleteCertainBoard(ctx context.Context, boardID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteCertainBoard", ctx, boardID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteCertainBoard indicates an expected call of DeleteCertainBoard. +func (mr *MockUsecaseMockRecorder) DeleteCertainBoard(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCertainBoard", reflect.TypeOf((*MockUsecase)(nil).DeleteCertainBoard), ctx, boardID) +} + +// FixPinsOnBoard mocks base method. +func (m *MockUsecase) FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FixPinsOnBoard", ctx, boardID, pinIds, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// FixPinsOnBoard indicates an expected call of FixPinsOnBoard. +func (mr *MockUsecaseMockRecorder) FixPinsOnBoard(ctx, boardID, pinIds, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FixPinsOnBoard", reflect.TypeOf((*MockUsecase)(nil).FixPinsOnBoard), ctx, boardID, pinIds, userID) +} + +// GetBoardsByUsername mocks base method. +func (m *MockUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]board.UserBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardsByUsername", ctx, username) + ret0, _ := ret[0].([]board.UserBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardsByUsername indicates an expected call of GetBoardsByUsername. +func (mr *MockUsecaseMockRecorder) GetBoardsByUsername(ctx, username interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardsByUsername", reflect.TypeOf((*MockUsecase)(nil).GetBoardsByUsername), ctx, username) +} + +// GetCertainBoard mocks base method. +func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board.UserBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCertainBoard", ctx, boardID) + ret0, _ := ret[0].(board.UserBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCertainBoard indicates an expected call of GetCertainBoard. +func (mr *MockUsecaseMockRecorder) GetCertainBoard(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertainBoard", reflect.TypeOf((*MockUsecase)(nil).GetCertainBoard), ctx, boardID) +} + +// UpdateBoardInfo mocks base method. +func (m *MockUsecase) UpdateBoardInfo(ctx context.Context, updatedData board.BoardData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBoardInfo", ctx, updatedData) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBoardInfo indicates an expected call of UpdateBoardInfo. +func (mr *MockUsecaseMockRecorder) UpdateBoardInfo(ctx, updatedData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBoardInfo", reflect.TypeOf((*MockUsecase)(nil).UpdateBoardInfo), ctx, updatedData) +} diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 223e840..f4ce46e 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -10,6 +10,7 @@ import ( "github.com/microcosm-cc/bluemonday" ) +//go:generate mockgen -destination=./mock/board_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) diff --git a/internal/pkg/usecase/image/mock/image_mock.go b/internal/pkg/usecase/image/mock/image_mock.go new file mode 100644 index 0000000..4832c0e --- /dev/null +++ b/internal/pkg/usecase/image/mock/image_mock.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + io "io" + reflect "reflect" + + check "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// UploadImage mocks base method. +func (m *MockUsecase) UploadImage(path, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UploadImage", path, mimeType, size, image, check) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UploadImage indicates an expected call of UploadImage. +func (mr *MockUsecaseMockRecorder) UploadImage(path, mimeType, size, image, check interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadImage", reflect.TypeOf((*MockUsecase)(nil).UploadImage), path, mimeType, size, image, check) +} diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go index 76bc148..5a3ac94 100644 --- a/internal/pkg/usecase/image/usecase.go +++ b/internal/pkg/usecase/image/usecase.go @@ -17,6 +17,7 @@ const PrefixURLImage = "httsp://pinspire.online:8081/" var ErrInvalidImage = errors.New("invalid images") var ErrUploadFile = errors.New("file upload failed") +//go:generate mockgen -destination=./mock/image_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { UploadImage(path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) } diff --git a/internal/pkg/usecase/pin/mock/pin_mock.go b/internal/pkg/usecase/pin/mock/pin_mock.go new file mode 100644 index 0000000..548cac5 --- /dev/null +++ b/internal/pkg/usecase/pin/mock/pin_mock.go @@ -0,0 +1,198 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + io "io" + reflect "reflect" + + pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// CheckUserHasSetLike mocks base method. +func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. +func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) +} + +// CreateNewPin mocks base method. +func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNewPin indicates an expected call of CreateNewPin. +func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) +} + +// DeleteLikeFromUser mocks base method. +func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. +func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) +} + +// DeletePinFromUser mocks base method. +func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePinFromUser indicates an expected call of DeletePinFromUser. +func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) +} + +// EditPinByID mocks base method. +func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditPinByID indicates an expected call of EditPinByID. +func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) +} + +// IsAvailableBatchPinForFixOnBoard mocks base method. +func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. +func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) +} + +// IsAvailablePinForFixOnBoard mocks base method. +func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. +func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) +} + +// SelectNewPins mocks base method. +func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(int) + return ret0, ret1, ret2 +} + +// SelectNewPins indicates an expected call of SelectNewPins. +func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) +} + +// SelectUserPins mocks base method. +func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(int) + return ret0, ret1, ret2 +} + +// SelectUserPins indicates an expected call of SelectUserPins. +func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) +} + +// SetLikeFromUser mocks base method. +func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetLikeFromUser indicates an expected call of SetLikeFromUser. +func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) +} + +// ViewAnPin mocks base method. +func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) + ret0, _ := ret[0].(*pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ViewAnPin indicates an expected call of ViewAnPin. +func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 89da90d..ab0774b 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -15,6 +15,7 @@ import ( var ErrBadMIMEType = errors.New("bad mime type") +//go:generate mockgen -destination=./mock/pin_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) diff --git a/internal/pkg/usecase/session/manager.go b/internal/pkg/usecase/session/manager.go index 482a123..a0803ca 100644 --- a/internal/pkg/usecase/session/manager.go +++ b/internal/pkg/usecase/session/manager.go @@ -18,6 +18,7 @@ const lenSessionKey = 16 var ErrExpiredSession = errors.New("session lifetime expired") +//go:generate mockgen -destination=./mock/session_mock.go -package=mock -source=manager.go SessionManager type SessionManager interface { CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) diff --git a/internal/pkg/usecase/session/mock/session_mock.go b/internal/pkg/usecase/session/mock/session_mock.go new file mode 100644 index 0000000..d1b74c5 --- /dev/null +++ b/internal/pkg/usecase/session/mock/session_mock.go @@ -0,0 +1,80 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: manager.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + session "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + gomock "github.com/golang/mock/gomock" +) + +// MockSessionManager is a mock of SessionManager interface. +type MockSessionManager struct { + ctrl *gomock.Controller + recorder *MockSessionManagerMockRecorder +} + +// MockSessionManagerMockRecorder is the mock recorder for MockSessionManager. +type MockSessionManagerMockRecorder struct { + mock *MockSessionManager +} + +// NewMockSessionManager creates a new mock instance. +func NewMockSessionManager(ctrl *gomock.Controller) *MockSessionManager { + mock := &MockSessionManager{ctrl: ctrl} + mock.recorder = &MockSessionManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSessionManager) EXPECT() *MockSessionManagerMockRecorder { + return m.recorder +} + +// CreateNewSessionForUser mocks base method. +func (m *MockSessionManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNewSessionForUser", ctx, userID) + ret0, _ := ret[0].(*session.Session) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateNewSessionForUser indicates an expected call of CreateNewSessionForUser. +func (mr *MockSessionManagerMockRecorder) CreateNewSessionForUser(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewSessionForUser", reflect.TypeOf((*MockSessionManager)(nil).CreateNewSessionForUser), ctx, userID) +} + +// DeleteUserSession mocks base method. +func (m *MockSessionManager) DeleteUserSession(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteUserSession", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteUserSession indicates an expected call of DeleteUserSession. +func (mr *MockSessionManagerMockRecorder) DeleteUserSession(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUserSession", reflect.TypeOf((*MockSessionManager)(nil).DeleteUserSession), ctx, key) +} + +// GetUserIDBySessionKey mocks base method. +func (m *MockSessionManager) GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserIDBySessionKey", ctx, sessionKey) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserIDBySessionKey indicates an expected call of GetUserIDBySessionKey. +func (mr *MockSessionManagerMockRecorder) GetUserIDBySessionKey(ctx, sessionKey interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserIDBySessionKey", reflect.TypeOf((*MockSessionManager)(nil).GetUserIDBySessionKey), ctx, sessionKey) +} diff --git a/internal/pkg/usecase/user/mock/user_mock.go b/internal/pkg/usecase/user/mock/user_mock.go new file mode 100644 index 0000000..f51b839 --- /dev/null +++ b/internal/pkg/usecase/user/mock/user_mock.go @@ -0,0 +1,125 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + io "io" + reflect "reflect" + + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// Authentication mocks base method. +func (m *MockUsecase) Authentication(ctx context.Context, credentials userCredentials) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Authentication", ctx, credentials) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Authentication indicates an expected call of Authentication. +func (mr *MockUsecaseMockRecorder) Authentication(ctx, credentials interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authentication", reflect.TypeOf((*MockUsecase)(nil).Authentication), ctx, credentials) +} + +// EditProfileInfo mocks base method. +func (m *MockUsecase) EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditProfileInfo", ctx, userID, updateData) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditProfileInfo indicates an expected call of EditProfileInfo. +func (mr *MockUsecaseMockRecorder) EditProfileInfo(ctx, userID, updateData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditProfileInfo", reflect.TypeOf((*MockUsecase)(nil).EditProfileInfo), ctx, userID, updateData) +} + +// FindOutUsernameAndAvatar mocks base method. +func (m *MockUsecase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (string, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindOutUsernameAndAvatar", ctx, userID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// FindOutUsernameAndAvatar indicates an expected call of FindOutUsernameAndAvatar. +func (mr *MockUsecaseMockRecorder) FindOutUsernameAndAvatar(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOutUsernameAndAvatar", reflect.TypeOf((*MockUsecase)(nil).FindOutUsernameAndAvatar), ctx, userID) +} + +// GetAllProfileInfo mocks base method. +func (m *MockUsecase) GetAllProfileInfo(ctx context.Context, userID int) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllProfileInfo", ctx, userID) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllProfileInfo indicates an expected call of GetAllProfileInfo. +func (mr *MockUsecaseMockRecorder) GetAllProfileInfo(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllProfileInfo", reflect.TypeOf((*MockUsecase)(nil).GetAllProfileInfo), ctx, userID) +} + +// Register mocks base method. +func (m *MockUsecase) Register(ctx context.Context, user *user.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Register", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// Register indicates an expected call of Register. +func (mr *MockUsecaseMockRecorder) Register(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockUsecase)(nil).Register), ctx, user) +} + +// UpdateUserAvatar mocks base method. +func (m *MockUsecase) UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAvatar string, sizeAvatar int64, avatar io.Reader) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUserAvatar", ctx, userID, mimeTypeAvatar, sizeAvatar, avatar) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUserAvatar indicates an expected call of UpdateUserAvatar. +func (mr *MockUsecaseMockRecorder) UpdateUserAvatar(ctx, userID, mimeTypeAvatar, sizeAvatar, avatar interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserAvatar", reflect.TypeOf((*MockUsecase)(nil).UpdateUserAvatar), ctx, userID, mimeTypeAvatar, sizeAvatar, avatar) +} diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index e06a465..48f722f 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -18,6 +18,7 @@ const ( lenPasswordHash = 64 ) +//go:generate mockgen -destination=./mock/user_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { Register(ctx context.Context, user *entity.User) error Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) From 122b891907b8401b8ceceb6c494f82d5efe1baf2 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Mon, 6 Nov 2023 19:16:44 +0300 Subject: [PATCH 136/266] TP-28e_mockTests update: add tests on CreateNewBoard, UpdateBoardInfo --- Makefile | 6 +- go.mod | 1 + go.sum | 16 + .../pkg/repository/board/mock/mock_repo.go | 156 ++++++++++ .../pkg/repository/user/mock/mock_repo.go | 155 ++++++++++ internal/pkg/usecase/board/usecase_test.go | 287 ++++++++++++++++++ 6 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/repository/board/mock/mock_repo.go create mode 100644 internal/pkg/repository/user/mock/mock_repo.go create mode 100644 internal/pkg/usecase/board/usecase_test.go diff --git a/Makefile b/Makefile index 212a845..8e4f39a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run test test_with_coverage cleantest retest doc +.PHONY: build run test test_with_coverage cleantest retest doc gen ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out @@ -26,3 +26,7 @@ retest: doc: swag fmt swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) + +gen: + mockgen -source=internal/pkg/repository/board/repo.go -source=internal/pkg/repository/user/repo.go \ + -destination=internal/pkg/repository/board/mock/mock_repo.go -destination=internal/pkg/repository/user/mock/mock_repo.go \ No newline at end of file diff --git a/go.mod b/go.mod index 0f728f1..d0fad7a 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/mock v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect diff --git a/go.sum b/go.sum index ba611c1..1efacff 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -60,6 +62,8 @@ github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDB github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= 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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -137,6 +141,7 @@ github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9 github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= @@ -149,6 +154,7 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -157,26 +163,32 @@ golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -187,9 +199,13 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/pkg/repository/board/mock/mock_repo.go b/internal/pkg/repository/board/mock/mock_repo.go new file mode 100644 index 0000000..66109dd --- /dev/null +++ b/internal/pkg/repository/board/mock/mock_repo.go @@ -0,0 +1,156 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/pkg/repository/board/repo.go + +// Package mock_board is a generated GoMock package. +package mock_board + +import ( + context "context" + reflect "reflect" + + board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// CreateBoard mocks base method. +func (m *MockRepository) CreateBoard(ctx context.Context, board board.Board, tagTitles []string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBoard", ctx, board, tagTitles) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBoard indicates an expected call of CreateBoard. +func (mr *MockRepositoryMockRecorder) CreateBoard(ctx, board, tagTitles interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBoard", reflect.TypeOf((*MockRepository)(nil).CreateBoard), ctx, board, tagTitles) +} + +// DeleteBoardByID mocks base method. +func (m *MockRepository) DeleteBoardByID(ctx context.Context, boardID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBoardByID", ctx, boardID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBoardByID indicates an expected call of DeleteBoardByID. +func (mr *MockRepositoryMockRecorder) DeleteBoardByID(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoardByID", reflect.TypeOf((*MockRepository)(nil).DeleteBoardByID), ctx, boardID) +} + +// GetBoardAuthorByBoardID mocks base method. +func (m *MockRepository) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardAuthorByBoardID", ctx, boardID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardAuthorByBoardID indicates an expected call of GetBoardAuthorByBoardID. +func (mr *MockRepositoryMockRecorder) GetBoardAuthorByBoardID(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardAuthorByBoardID", reflect.TypeOf((*MockRepository)(nil).GetBoardAuthorByBoardID), ctx, boardID) +} + +// GetBoardByID mocks base method. +func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (dto.UserBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardByID", ctx, boardID, hasAccess) + ret0, _ := ret[0].(dto.UserBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardByID indicates an expected call of GetBoardByID. +func (mr *MockRepositoryMockRecorder) GetBoardByID(ctx, boardID, hasAccess interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardByID", reflect.TypeOf((*MockRepository)(nil).GetBoardByID), ctx, boardID, hasAccess) +} + +// GetBoardsByUserID mocks base method. +func (m *MockRepository) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardsByUserID", ctx, userID, isAuthor, accessableBoardsIDs) + ret0, _ := ret[0].([]dto.UserBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoardsByUserID indicates an expected call of GetBoardsByUserID. +func (mr *MockRepositoryMockRecorder) GetBoardsByUserID(ctx, userID, isAuthor, accessableBoardsIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardsByUserID", reflect.TypeOf((*MockRepository)(nil).GetBoardsByUserID), ctx, userID, isAuthor, accessableBoardsIDs) +} + +// GetContributorBoardsIDs mocks base method. +func (m *MockRepository) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContributorBoardsIDs", ctx, contributorID) + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContributorBoardsIDs indicates an expected call of GetContributorBoardsIDs. +func (mr *MockRepositoryMockRecorder) GetContributorBoardsIDs(ctx, contributorID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorBoardsIDs", reflect.TypeOf((*MockRepository)(nil).GetContributorBoardsIDs), ctx, contributorID) +} + +// GetContributorsByBoardID mocks base method. +func (m *MockRepository) GetContributorsByBoardID(ctx context.Context, boardID int) ([]user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContributorsByBoardID", ctx, boardID) + ret0, _ := ret[0].([]user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContributorsByBoardID indicates an expected call of GetContributorsByBoardID. +func (mr *MockRepositoryMockRecorder) GetContributorsByBoardID(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorsByBoardID", reflect.TypeOf((*MockRepository)(nil).GetContributorsByBoardID), ctx, boardID) +} + +// UpdateBoard mocks base method. +func (m *MockRepository) UpdateBoard(ctx context.Context, newBoardData board.Board, tagTitles []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBoard", ctx, newBoardData, tagTitles) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateBoard indicates an expected call of UpdateBoard. +func (mr *MockRepositoryMockRecorder) UpdateBoard(ctx, newBoardData, tagTitles interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBoard", reflect.TypeOf((*MockRepository)(nil).UpdateBoard), ctx, newBoardData, tagTitles) +} diff --git a/internal/pkg/repository/user/mock/mock_repo.go b/internal/pkg/repository/user/mock/mock_repo.go new file mode 100644 index 0000000..7573c6c --- /dev/null +++ b/internal/pkg/repository/user/mock/mock_repo.go @@ -0,0 +1,155 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/pkg/repository/user/repo.go + +// Package mock_user is a generated GoMock package. +package mock_user + +import ( + context "context" + reflect "reflect" + + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + user0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddNewUser mocks base method. +func (m *MockRepository) AddNewUser(ctx context.Context, user *user.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddNewUser", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddNewUser indicates an expected call of AddNewUser. +func (mr *MockRepositoryMockRecorder) AddNewUser(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddNewUser", reflect.TypeOf((*MockRepository)(nil).AddNewUser), ctx, user) +} + +// EditUserAvatar mocks base method. +func (m *MockRepository) EditUserAvatar(ctx context.Context, userID int, avatar string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditUserAvatar", ctx, userID, avatar) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditUserAvatar indicates an expected call of EditUserAvatar. +func (mr *MockRepositoryMockRecorder) EditUserAvatar(ctx, userID, avatar interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditUserAvatar", reflect.TypeOf((*MockRepository)(nil).EditUserAvatar), ctx, userID, avatar) +} + +// EditUserInfo mocks base method. +func (m *MockRepository) EditUserInfo(ctx context.Context, userID int, updateFields user0.S) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditUserInfo", ctx, userID, updateFields) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditUserInfo indicates an expected call of EditUserInfo. +func (mr *MockRepositoryMockRecorder) EditUserInfo(ctx, userID, updateFields interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditUserInfo", reflect.TypeOf((*MockRepository)(nil).EditUserInfo), ctx, userID, updateFields) +} + +// GetAllUserData mocks base method. +func (m *MockRepository) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllUserData", ctx, userID) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllUserData indicates an expected call of GetAllUserData. +func (mr *MockRepositoryMockRecorder) GetAllUserData(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllUserData", reflect.TypeOf((*MockRepository)(nil).GetAllUserData), ctx, userID) +} + +// GetLastUserID mocks base method. +func (m *MockRepository) GetLastUserID(ctx context.Context) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLastUserID", ctx) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLastUserID indicates an expected call of GetLastUserID. +func (mr *MockRepositoryMockRecorder) GetLastUserID(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastUserID", reflect.TypeOf((*MockRepository)(nil).GetLastUserID), ctx) +} + +// GetUserByUsername mocks base method. +func (m *MockRepository) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByUsername", ctx, username) + ret0, _ := ret[0].(*user.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByUsername indicates an expected call of GetUserByUsername. +func (mr *MockRepositoryMockRecorder) GetUserByUsername(ctx, username interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockRepository)(nil).GetUserByUsername), ctx, username) +} + +// GetUserIdByUsername mocks base method. +func (m *MockRepository) GetUserIdByUsername(ctx context.Context, username string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserIdByUsername", ctx, username) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserIdByUsername indicates an expected call of GetUserIdByUsername. +func (mr *MockRepositoryMockRecorder) GetUserIdByUsername(ctx, username interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserIdByUsername", reflect.TypeOf((*MockRepository)(nil).GetUserIdByUsername), ctx, username) +} + +// GetUsernameAndAvatarByID mocks base method. +func (m *MockRepository) GetUsernameAndAvatarByID(ctx context.Context, userID int) (string, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsernameAndAvatarByID", ctx, userID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetUsernameAndAvatarByID indicates an expected call of GetUsernameAndAvatarByID. +func (mr *MockRepositoryMockRecorder) GetUsernameAndAvatarByID(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsernameAndAvatarByID", reflect.TypeOf((*MockRepository)(nil).GetUsernameAndAvatarByID), ctx, userID) +} diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go new file mode 100644 index 0000000..4bc4cec --- /dev/null +++ b/internal/pkg/usecase/board/usecase_test.go @@ -0,0 +1,287 @@ +package board + +import ( + "context" + "fmt" + stdLog "log" + "testing" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + mock_board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/mock" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/golang/mock/gomock" + "github.com/microcosm-cc/bluemonday" + "github.com/stretchr/testify/require" +) + +type ( + CreateBoard func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) + UpdateBoard CreateBoard + GetBoardAuthorByBoardID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) +) + +var ( + sanitizer = bluemonday.UGCPolicy() +) + +func TestBoardUsecase_CreateNewBoard(t *testing.T) { + + tests := []struct { + name string + inCtx context.Context + newBoardData dto.BoardData + CreateBoard CreateBoard + expNewID int + expErr error + }{ + { + name: "valid board data", + inCtx: context.Background(), + newBoardData: dto.BoardData{ + Title: "valid title", + Description: "some description", + AuthorID: 45, + Public: false, + TagTitles: []string{"nice", "green"}, + }, + CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + mockRepo.EXPECT().CreateBoard(ctx, newBoardData, tagTitles).Return(1, nil).Times(1) + }, + expNewID: 1, + expErr: nil, + }, + { + name: "invalid board title", + inCtx: context.Background(), + newBoardData: dto.BoardData{ + Title: "~nval$d title~~", + Description: "some description", + AuthorID: 45, + Public: false, + TagTitles: []string{"nice", "green"}, + }, + CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expNewID: 0, + expErr: ErrInvalidBoardTitle, + }, + { + name: "invalid tag titles: all tags", + inCtx: context.Background(), + newBoardData: dto.BoardData{ + Title: "valid title", + Description: "some description", + AuthorID: 45, + Public: false, + TagTitles: []string{"nic~e", "gr~$een"}, + }, + CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expNewID: 0, + expErr: fmt.Errorf("%v: %w", []string{"nic~e", "gr~$een"}, ErrInvalidTagTitles), + }, + { + name: "invalid tag titles: some tags", + inCtx: context.Background(), + newBoardData: dto.BoardData{ + Title: "valid title", + Description: "some description", + AuthorID: 45, + Public: false, + TagTitles: []string{"nic~e", "green"}, + }, + CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expNewID: 0, + expErr: fmt.Errorf("%v: %w", []string{"nic~e"}, ErrInvalidTagTitles), + }, + { + name: "invalid tag titles: too many tags", + inCtx: context.Background(), + newBoardData: dto.BoardData{ + Title: "valid title", + Description: "some description", + AuthorID: 45, + Public: false, + TagTitles: []string{"nice", "green", "a", "b", "c", "d", "e", "f"}, + }, + CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expNewID: 0, + expErr: fmt.Errorf("too many titles: %w", ErrInvalidTagTitles), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctl := gomock.NewController(t) + defer ctl.Finish() + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + mockBoardRepo := mock_board.NewMockRepository(ctl) + test.CreateBoard(mockBoardRepo, test.inCtx, entity.Board{ + AuthorID: test.newBoardData.AuthorID, + Title: test.newBoardData.Title, + Description: test.newBoardData.Description, + Public: test.newBoardData.Public, + }, test.newBoardData.TagTitles) + + boardUsecase := New(log, mockBoardRepo, nil, sanitizer) + newBoardID, err := boardUsecase.CreateNewBoard(test.inCtx, test.newBoardData) + + if err != nil { + require.EqualError(t, err, test.expErr.Error()) + } + require.Equal(t, test.expNewID, newBoardID) + }) + } +} + +func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { + + tests := []struct { + name string + inCtx context.Context + updatedBoardData dto.BoardData + GetBoardAuthorByBoardID GetBoardAuthorByBoardID + UpdateBoard UpdateBoard + expErr error + }{ + { + name: "valid data, authenticated, with access", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + updatedBoardData: dto.BoardData{ + ID: 25, + Title: "valid title", + Description: "some description", + Public: false, + TagTitles: []string{"nice", "green"}, + }, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, updatedBoardData entity.Board, tagTitles []string) { + mockRepo.EXPECT().UpdateBoard(ctx, updatedBoardData, tagTitles).Return(nil).Times(1) + }, + expErr: nil, + }, + { + name: "valid data, authenticated, no access", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 534), + updatedBoardData: dto.BoardData{ + ID: 25, + Title: "valid title", + Description: "some description", + Public: false, + TagTitles: []string{"nice", "green"}, + }, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expErr: ErrNoAccess, + }, + { + name: "valid data, no_auth", + inCtx: context.Background(), + updatedBoardData: dto.BoardData{ + ID: 25, + Title: "valid title", + Description: "some description", + Public: false, + TagTitles: []string{"nice", "green"}, + }, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expErr: ErrNoAccess, + }, + { + name: "invalid board id", + inCtx: context.Background(), + updatedBoardData: dto.BoardData{ + ID: 122125, + Title: "valid title", + Description: "some description", + Public: false, + TagTitles: []string{"nice", "green"}, + }, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(0, repository.ErrNoData).Times(1) + }, + UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expErr: ErrNoSuchBoard, + }, + { + name: "invalid board title", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + updatedBoardData: dto.BoardData{ + ID: 1, + Title: "va!@#*^*!&@$*lid title", + Description: "some description", + Public: false, + TagTitles: []string{"nice", "green"}, + }, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expErr: ErrInvalidBoardTitle, + }, + { + name: "invalid board tags", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + updatedBoardData: dto.BoardData{ + ID: 11, + Title: "valid title", + Description: "some description", + Public: false, + TagTitles: []string{"ni@#@#%!~~ce", "green"}, + }, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { + }, + expErr: fmt.Errorf("%v: %w", []string{"ni@#@#%!~~ce"}, ErrInvalidTagTitles), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctl := gomock.NewController(t) + defer ctl.Finish() + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + mockBoardRepo := mock_board.NewMockRepository(ctl) + test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.updatedBoardData.ID) + test.UpdateBoard(mockBoardRepo, test.inCtx, entity.Board{ + ID: test.updatedBoardData.ID, + Title: test.updatedBoardData.Title, + Description: test.updatedBoardData.Description, + Public: test.updatedBoardData.Public, + }, test.updatedBoardData.TagTitles) + + boardUsecase := New(log, mockBoardRepo, nil, sanitizer) + err = boardUsecase.UpdateBoardInfo(test.inCtx, test.updatedBoardData) + + if err != nil { + require.EqualError(t, err, test.expErr.Error()) + } + }) + } +} From 44dcb6d8a8eb12e217fd902f27317de8181de2c7 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 6 Nov 2023 20:28:50 +0300 Subject: [PATCH 137/266] TP-2e4 update: old test --- internal/pkg/delivery/http/v1/auth_test.go | 43 ++--------- internal/pkg/delivery/http/v1/pin_test.go | 71 ++----------------- .../pkg/delivery/http/v1/validation_test.go | 33 ++++----- internal/pkg/usecase/pin/usecase_test.go | 48 ------------- 4 files changed, 28 insertions(+), 167 deletions(-) delete mode 100644 internal/pkg/usecase/pin/usecase_test.go diff --git a/internal/pkg/delivery/http/v1/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go index cfadb01..5afa92a 100644 --- a/internal/pkg/delivery/http/v1/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -41,39 +41,6 @@ func TestCheckLogin(t *testing.T) { service := New(log, sm, userCase, nil, nil) url := "https://domain.test:8080/api/v1/login" - goodCases := []struct { - name string - cookie *http.Cookie - expResp JsonResponse - }{ - { - "sending valid session_key", - &http.Cookie{ - Name: "session_key", - Value: "461afabf38b3147c", - }, - JsonResponse{ - Status: "ok", - Message: "user found", - Body: map[string]interface{}{"username": "dogsLover", "avatar": "https://pinspire.online:8081/upload/avatars/default-avatar.png"}, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, url, nil) - req.AddCookie(tCase.cookie) - w := httptest.NewRecorder() - - service.CheckLogin(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - actualResp.Body = actualResp.Body.(map[string]interface{}) - require.Equal(t, tCase.expResp, actualResp) - }) - } badCases := []struct { name string @@ -88,7 +55,7 @@ func TestCheckLogin(t *testing.T) { }, JsonErrResponse{ Status: "error", - Message: "the user is not logged in", + Message: "no user was found for this session", Code: "no_auth", }, }, @@ -100,7 +67,7 @@ func TestCheckLogin(t *testing.T) { }, JsonErrResponse{ Status: "error", - Message: "no user session found", + Message: "no user was found for this session", Code: "no_auth", }, }, @@ -134,7 +101,7 @@ func TestCheckLogin(t *testing.T) { } -func TestLogin(t *testing.T) { +func testLogin(t *testing.T) { url := "https://domain.test:8080/api/v1/login" log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() @@ -270,7 +237,7 @@ func TestLogin(t *testing.T) { } } -func TestSignUp(t *testing.T) { +func testSignUp(t *testing.T) { url := "https://domain.test:8080/api/v1/signup" log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() @@ -386,7 +353,7 @@ func TestSignUp(t *testing.T) { } } -func TestLogout(t *testing.T) { +func testLogout(t *testing.T) { url := "https://domain.test:8080/api/v1/logout" log, _ := logger.New(logger.RFC3339FormatTime()) defer log.Sync() diff --git a/internal/pkg/delivery/http/v1/pin_test.go b/internal/pkg/delivery/http/v1/pin_test.go index e663591..9f5bf20 100644 --- a/internal/pkg/delivery/http/v1/pin_test.go +++ b/internal/pkg/delivery/http/v1/pin_test.go @@ -27,65 +27,6 @@ func TestGetPins(t *testing.T) { service := New(log, nil, nil, pinCase, nil) rawUrl := "https://domain.test:8080/api/v1/pin" - goodCases := []struct { - rawURL string - expResp JsonResponse - }{ - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 1, 2), - expResp: JsonResponse{ - Status: "ok", - Message: "pins received are sorted by id", - Body: map[string]interface{}{ - "lastID": 3, - "pins": []interface{}{ - map[string]interface{}{"id": 3}, - }, - }, - }, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, 3), - expResp: JsonResponse{ - Status: "ok", - Message: "pins received are sorted by id", - Body: map[string]interface{}{ - "lastID": 5, - "pins": []interface{}{ - map[string]interface{}{"id": 4}, - map[string]interface{}{"id": 5}, - }, - }, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(fmt.Sprintf("TestGetPins good: %s", tCase.rawURL), func(t *testing.T) { - req := httptest.NewRequest("GET", tCase.rawURL, nil) - w := httptest.NewRecorder() - service.GetPins(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - - require.Equal(t, tCase.expResp.Status, actualResp.Status) - require.Equal(t, tCase.expResp.Message, actualResp.Message) - expLastID := tCase.expResp.Body.(map[string]interface{})["lastID"].(int) - actualLastID := actualResp.Body.(map[string]interface{})["lastID"].(float64) - - expIDs, actualIDs := make([]int, 0), make([]int, 0) - for _, pin := range tCase.expResp.Body.(map[string]interface{})["pins"].([]interface{}) { - expIDs = append(expIDs, pin.(map[string]interface{})["id"].(int)) - } - for _, pin := range actualResp.Body.(map[string]interface{})["pins"].([]interface{}) { - actualIDs = append(actualIDs, int(pin.(map[string]interface{})["id"].(float64))) - } - - require.Equal(t, expLastID, int(actualLastID)) - require.Equal(t, expIDs, actualIDs) - }) - } badCases := []struct { rawURL string @@ -95,7 +36,7 @@ func TestGetPins(t *testing.T) { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 3), expResp: JsonErrResponse{ Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", }, }, @@ -103,7 +44,7 @@ func TestGetPins(t *testing.T) { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 3), expResp: JsonErrResponse{ Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", }, }, @@ -111,7 +52,7 @@ func TestGetPins(t *testing.T) { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 213123, 3), expResp: JsonErrResponse{ Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", }, }, @@ -119,7 +60,7 @@ func TestGetPins(t *testing.T) { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, -1), expResp: JsonErrResponse{ Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", }, }, @@ -127,7 +68,7 @@ func TestGetPins(t *testing.T) { rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 3), expResp: JsonErrResponse{ Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", }, }, @@ -135,7 +76,7 @@ func TestGetPins(t *testing.T) { rawURL: fmt.Sprintf("%s?lastID=%d", rawUrl, 3), expResp: JsonErrResponse{ Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", + Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", }, }, diff --git a/internal/pkg/delivery/http/v1/validation_test.go b/internal/pkg/delivery/http/v1/validation_test.go index 12e0263..34c128f 100644 --- a/internal/pkg/delivery/http/v1/validation_test.go +++ b/internal/pkg/delivery/http/v1/validation_test.go @@ -11,16 +11,16 @@ func TestFetchValidParams(t *testing.T) { rawUrl := "https://domain.test:8080/api/v1/pin" tests := []struct { - name string - queryRow string - wantCount, wantLastID int + name string + queryRow string + wantCount, wantMinID, wantMaxID int }{ - {"both parameters were passed correctly", "?count=6&lastID=12", 6, 12}, - {"both parameters were passed correctly in a different order", "?lastID=1&count=3", 3, 1}, - {"repeating parameters", "?count=14&lastID=1&count=3&lastID=55&lastID=65", 14, 1}, - {"repeating parameters", "?count=14&lastID=1&count=3&lastID=55&lastID=65", 14, 1}, - {"empty parameter lastID", "?count=7", 7, 0}, - {"the parameter lastID is registered but not specified", "?lastID=&count=17", 17, 0}, + {"both parameters were passed correctly", "?count=6&minID=12&maxID=9", 6, 12, 9}, + {"both parameters were passed correctly in a different order", "?maxID=88&count=3&minID=22", 3, 22, 88}, + {"repeating parameters", "?count=14&maxID=9&count=3&maxID=55&minID=1", 14, 1, 9}, + {"repeating parameters", "?count=14&minID=1&count=3&mmmmmID=55&ID=65&maxID=1", 14, 1, 1}, + {"empty parameters minID, maxID", "?count=7", 7, 0, 0}, + {"the parameter maxID is registered but not specified", "?lastID=&count=17", 17, 0, 0}, } for _, test := range tests { @@ -29,10 +29,11 @@ func TestFetchValidParams(t *testing.T) { if err != nil { t.Fatalf("error when parsing into the url.URL structure: %v", err) } - actualCount, actualLastID, _, err := FetchValidParamForLoadTape(URL) - require.NoError(t, err) - require.Equal(t, test.wantCount, actualCount) - require.Equal(t, test.wantLastID, actualLastID) + actualCount, actualMinID, actualMaxID, err := FetchValidParamForLoadTape(URL) + require.NoError(t, err, test.name) + require.Equal(t, test.wantCount, actualCount, test.name) + require.Equal(t, test.wantMinID, actualMinID, test.name) + require.Equal(t, test.wantMaxID, actualMaxID, test.name) }) } } @@ -47,10 +48,10 @@ func TestErrorFetchValidParams(t *testing.T) { }{ {"empty query row", "", ErrCountParameterMissing}, {"count equal zero", "?count=0", ErrBadParams}, - {"negative count", "?count=-5&lastID=12", ErrBadParams}, - {"negative lastID", "?count=5&lastID=-6", ErrBadParams}, + {"negative count", "?count=-5&minID=12", ErrBadParams}, + {"negative ID", "?count=5&maxID=-6", ErrBadParams}, {"requested count is more than a thousand", "?count=1001", ErrBadParams}, - {"count param empty", "?count=&lastID=6", ErrCountParameterMissing}, + {"count param empty", "?count=&minID=6&maxID=9", ErrCountParameterMissing}, } for _, test := range tests { diff --git a/internal/pkg/usecase/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go deleted file mode 100644 index 61003b3..0000000 --- a/internal/pkg/usecase/pin/usecase_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package pin - -import ( - "context" - "math/rand" - "strconv" - "testing" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func TestSelectNewPins(t *testing.T) { - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - pinCase := New(log, nil, ramrepo.NewRamPinRepo(db)) - - testCases := []struct { - name string - count, lastID int - expNewLastID int - }{ - { - name: "provide correct count and lastID", - count: 2, - lastID: 1, - expNewLastID: 3, - }, - { - name: "provide incorrect count", - count: -2, - lastID: 1, - expNewLastID: 1, - }, - } - - for _, tCase := range testCases { - t.Run(tCase.name, func(t *testing.T) { - _, actualLastID, _ := pinCase.SelectNewPins(context.Background(), tCase.count, tCase.lastID, 0) - require.Equal(t, tCase.expNewLastID, actualLastID) - }) - } -} From fc96333057eb235faa7a6c269e0f313e58296aa2 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Mon, 6 Nov 2023 20:34:20 +0300 Subject: [PATCH 138/266] TP-28e_mockTests update: add GetBoardsByUsername test --- internal/pkg/usecase/board/usecase_test.go | 121 ++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index 4bc4cec..302030d 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -10,6 +10,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" mock_board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/mock" + mock_user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user/mock" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/golang/mock/gomock" @@ -21,6 +22,9 @@ type ( CreateBoard func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) UpdateBoard CreateBoard GetBoardAuthorByBoardID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) + GetUserIdByUsername func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) + GetContributorBoardsIDs func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) + GetBoardsByUserID func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ) var ( @@ -28,7 +32,6 @@ var ( ) func TestBoardUsecase_CreateNewBoard(t *testing.T) { - tests := []struct { name string inCtx context.Context @@ -144,7 +147,6 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { } func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { - tests := []struct { name string inCtx context.Context @@ -285,3 +287,118 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }) } } + +func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { + tests := []struct { + name string + inCtx context.Context + username string + GetUserIdByUsername GetUserIdByUsername + GetContributorBoardsIDs GetContributorBoardsIDs + GetBoardsByUserID GetBoardsByUserID + expBoards []dto.UserBoard + expErr error + }{ + { + name: "exisitng user with valid username", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + username: "validGuy", + GetUserIdByUsername: func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) { + mockRepo.EXPECT().GetUserIdByUsername(ctx, username).Return(3, nil).Times(1) + }, + GetContributorBoardsIDs: func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) { + mockRepo.EXPECT().GetContributorBoardsIDs(ctx, contributorID).Return([]int{1, 2, 3}, nil).Times(1) + }, + GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { + mockRepo.EXPECT().GetBoardsByUserID(ctx, userID, isAuthor, accessableBoardsIDs).Return( + []dto.UserBoard{ + { + BoardID: 23, + Title: "title", + CreatedAt: "25:10:2022", + PinsNumber: 10, + Pins: []string{"/pic1", "/pic2"}, + }, + { + BoardID: 21, + Title: "title21", + CreatedAt: "25:10:2012", + PinsNumber: 2, + Pins: []string{}, + }, + }, nil).Times(1) + }, + expBoards: []dto.UserBoard{ + { + BoardID: 23, + Title: "title", + CreatedAt: "25:10:2022", + PinsNumber: 10, + Pins: []string{"/pic1", "/pic2"}, + }, + { + BoardID: 21, + Title: "title21", + CreatedAt: "25:10:2012", + PinsNumber: 2, + Pins: []string{}, + }, + }, + expErr: nil, + }, + { + name: "non-exisitng user with valid username", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + username: "validGuy", + GetUserIdByUsername: func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) { + mockRepo.EXPECT().GetUserIdByUsername(ctx, username).Return(0, repository.ErrNoData).Times(1) + }, + GetContributorBoardsIDs: func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) { + }, + GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { + }, + expBoards: nil, + expErr: ErrInvalidUsername, + }, + { + name: "invalid username", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + username: "A$va@$@!%@~~~~~~uy", + GetUserIdByUsername: func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) { + }, + GetContributorBoardsIDs: func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) { + }, + GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { + }, + expBoards: nil, + expErr: ErrInvalidUsername, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctl := gomock.NewController(t) + defer ctl.Finish() + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + mockBoardRepo := mock_board.NewMockRepository(ctl) + mockUserRepo := mock_user.NewMockRepository(ctl) + + test.GetUserIdByUsername(mockUserRepo, test.inCtx, test.username) + test.GetContributorBoardsIDs(mockBoardRepo, test.inCtx, 1) + test.GetBoardsByUserID(mockBoardRepo, test.inCtx, 3, *new(bool), []int{1, 2, 3}) + + boardUsecase := New(log, mockBoardRepo, mockUserRepo, sanitizer) + userBoards, err := boardUsecase.GetBoardsByUsername(test.inCtx, test.username) + + if err != nil { + require.EqualError(t, err, test.expErr.Error()) + } + + require.Equal(t, test.expBoards, userBoards) + }) + } +} From 350e3373f8e65216e6386cbf8d99640ad0917778 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Mon, 6 Nov 2023 23:50:34 +0300 Subject: [PATCH 139/266] TP-28e_mockTests update: modified test_with_coverage target --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8e4f39a..232a7a4 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,11 @@ run: build ./bin/app test: - go test ./... -race -covermode=atomic -coverpkg ./... -coverprofile=$(COV_OUT) + go test ./... -covermode=atomic -coverpkg ./... -coverprofile=$(COV_OUT) test_with_coverage: test go tool cover -html $(COV_OUT) -o $(COV_HTML) + go tool cover -func profile.cov cleantest: rm coverage* From 4ceb357a29eb198661a5cb8aef19f0cea83e66f2 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Mon, 6 Nov 2023 23:53:13 +0300 Subject: [PATCH 140/266] TP-28e_mockTests update: add GetCertainBoard, GetBoardByUsername tests --- internal/pkg/delivery/http/v1/auth_test.go | 2 +- internal/pkg/usecase/board/get.go | 2 +- internal/pkg/usecase/board/usecase_test.go | 210 ++++++++++++++++++++- internal/pkg/usecase/pin/usecase_test.go | 2 +- 4 files changed, 203 insertions(+), 13 deletions(-) diff --git a/internal/pkg/delivery/http/v1/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go index cfadb01..41665a9 100644 --- a/internal/pkg/delivery/http/v1/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -321,7 +321,7 @@ func TestSignUp(t *testing.T) { `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"}`, JsonErrResponse{ Status: "error", - Message: "there is already an account with this username or password", + Message: "there is already an account with this username or email", Code: "uniq_fields", }, }, diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index c5bf246..364b929 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -51,7 +51,7 @@ func (bCase *BoardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dt return dto.UserBoard{}, ErrNoSuchBoard default: return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) - } + } } boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index 302030d..31fb487 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -7,6 +7,7 @@ import ( "testing" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" mock_board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/mock" @@ -19,12 +20,14 @@ import ( ) type ( - CreateBoard func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) - UpdateBoard CreateBoard - GetBoardAuthorByBoardID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) - GetUserIdByUsername func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) - GetContributorBoardsIDs func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) - GetBoardsByUserID func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) + CreateBoard func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) + UpdateBoard CreateBoard + GetBoardAuthorByBoardID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) + GetContributorBoardsIDs func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) + GetBoardsByUserID func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) + GetContributorsByBoardID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) + GetBoardByID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) + GetUserIdByUsername func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) ) var ( @@ -316,14 +319,14 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { BoardID: 23, Title: "title", CreatedAt: "25:10:2022", - PinsNumber: 10, + PinsNumber: 2, Pins: []string{"/pic1", "/pic2"}, }, { BoardID: 21, Title: "title21", CreatedAt: "25:10:2012", - PinsNumber: 2, + PinsNumber: 0, Pins: []string{}, }, }, nil).Times(1) @@ -333,14 +336,14 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { BoardID: 23, Title: "title", CreatedAt: "25:10:2022", - PinsNumber: 10, + PinsNumber: 2, Pins: []string{"/pic1", "/pic2"}, }, { BoardID: 21, Title: "title21", CreatedAt: "25:10:2012", - PinsNumber: 2, + PinsNumber: 0, Pins: []string{}, }, }, @@ -402,3 +405,190 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { }) } } + +func TestBoardUsecase_GetCertainBoard(t *testing.T) { + tests := []struct { + name string + inCtx context.Context + boardID int + GetBoardAuthorByBoardID GetBoardAuthorByBoardID + GetContributorsByBoardID GetContributorsByBoardID + GetBoardByID GetBoardByID + hasAccess bool + expBoard dto.UserBoard + expErr error + }{ + { + name: "private board, valid board id, request from author", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + boardID: 22, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + GetContributorsByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{}, nil).Times(1) + }, + GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{ + BoardID: boardID, + Title: "title", + Description: "description", + CreatedAt: "10:10:2020", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, + }, nil).Times(1) + }, + hasAccess: true, + expBoard: dto.UserBoard{ + BoardID: 22, + Title: "title", + Description: "description", + CreatedAt: "10:10:2020", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, + }, + expErr: nil, + }, + { + name: "private board, valid board id, request from contributor", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + boardID: 22, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(2, nil).Times(1) + }, + GetContributorsByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 1}}, nil).Times(1) + }, + GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{ + BoardID: boardID, + Title: "title", + Description: "description", + CreatedAt: "10:10:2020", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, + }, nil).Times(1) + }, + hasAccess: true, + expBoard: dto.UserBoard{ + BoardID: 22, + Title: "title", + Description: "description", + CreatedAt: "10:10:2020", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, + }, + expErr: nil, + }, + { + name: "private board, valid board id, request from not author, not contributor", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + boardID: 22, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(2, nil).Times(1) + }, + GetContributorsByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) + }, + GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{}, repository.ErrNoData).Times(1) + }, + hasAccess: false, + expBoard: dto.UserBoard{}, + expErr: ErrNoSuchBoard, + }, + { + name: "private board, valid board id, request from unauthorized", + inCtx: context.Background(), + boardID: 22, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(2, nil).Times(1) + }, + GetContributorsByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) + }, + GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{}, repository.ErrNoData).Times(1) + }, + hasAccess: false, + expBoard: dto.UserBoard{}, + expErr: ErrNoSuchBoard, + }, + { + name: "public board, valid board id, request from unauthorized", + inCtx: context.Background(), + boardID: 22, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(2, nil).Times(1) + }, + GetContributorsByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) + }, + GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{ + BoardID: boardID, + Title: "title", + Description: "description", + CreatedAt: "10:10:2020", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, + }, nil).Times(1) + }, + hasAccess: false, + expBoard: dto.UserBoard{ + BoardID: 22, + Title: "title", + Description: "description", + CreatedAt: "10:10:2020", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, + }, + expErr: nil, + }, + { + name: "invalid board id", + inCtx: context.Background(), + boardID: 1222, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(0, repository.ErrNoData).Times(1) + }, + GetContributorsByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + }, + GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { + }, + expBoard: dto.UserBoard{}, + expErr: ErrNoSuchBoard, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctl := gomock.NewController(t) + defer ctl.Finish() + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + mockBoardRepo := mock_board.NewMockRepository(ctl) + test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.boardID) + test.GetContributorsByBoardID(mockBoardRepo, test.inCtx, test.boardID) + test.GetBoardByID(mockBoardRepo, test.inCtx, test.boardID, test.hasAccess) + + boardUsecase := New(log, mockBoardRepo, nil, sanitizer) + board, err := boardUsecase.GetCertainBoard(test.inCtx, test.boardID) + + if err != nil { + require.EqualError(t, err, test.expErr.Error()) + } + + require.Equal(t, test.expBoard, board) + }) + } +} diff --git a/internal/pkg/usecase/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go index 61003b3..7b41f06 100644 --- a/internal/pkg/usecase/pin/usecase_test.go +++ b/internal/pkg/usecase/pin/usecase_test.go @@ -29,7 +29,7 @@ func TestSelectNewPins(t *testing.T) { name: "provide correct count and lastID", count: 2, lastID: 1, - expNewLastID: 3, + expNewLastID: 2, }, { name: "provide incorrect count", From 7798f9104b2a465b45a381d71203eb00c113b850 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 00:45:15 +0300 Subject: [PATCH 141/266] TP-28e_mockTests update: add DeleteCertainBoard test --- internal/pkg/usecase/board/usecase_test.go | 135 ++++++++++++++++++--- 1 file changed, 120 insertions(+), 15 deletions(-) diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index 31fb487..b454788 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -28,6 +28,7 @@ type ( GetContributorsByBoardID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) GetBoardByID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) GetUserIdByUsername func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) + DeleteBoardByID func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) ) var ( @@ -41,6 +42,7 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { newBoardData dto.BoardData CreateBoard CreateBoard expNewID int + wantErr bool expErr error }{ { @@ -57,7 +59,6 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { mockRepo.EXPECT().CreateBoard(ctx, newBoardData, tagTitles).Return(1, nil).Times(1) }, expNewID: 1, - expErr: nil, }, { name: "invalid board title", @@ -72,6 +73,7 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, expNewID: 0, + wantErr: true, expErr: ErrInvalidBoardTitle, }, { @@ -87,6 +89,7 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, expNewID: 0, + wantErr: true, expErr: fmt.Errorf("%v: %w", []string{"nic~e", "gr~$een"}, ErrInvalidTagTitles), }, { @@ -102,6 +105,7 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, expNewID: 0, + wantErr: true, expErr: fmt.Errorf("%v: %w", []string{"nic~e"}, ErrInvalidTagTitles), }, { @@ -117,6 +121,7 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, expNewID: 0, + wantErr: true, expErr: fmt.Errorf("too many titles: %w", ErrInvalidTagTitles), }, } @@ -141,8 +146,10 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { boardUsecase := New(log, mockBoardRepo, nil, sanitizer) newBoardID, err := boardUsecase.CreateNewBoard(test.inCtx, test.newBoardData) - if err != nil { + if test.wantErr { require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) } require.Equal(t, test.expNewID, newBoardID) }) @@ -156,6 +163,7 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { updatedBoardData dto.BoardData GetBoardAuthorByBoardID GetBoardAuthorByBoardID UpdateBoard UpdateBoard + wantErr bool expErr error }{ { @@ -174,7 +182,6 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, updatedBoardData entity.Board, tagTitles []string) { mockRepo.EXPECT().UpdateBoard(ctx, updatedBoardData, tagTitles).Return(nil).Times(1) }, - expErr: nil, }, { name: "valid data, authenticated, no access", @@ -191,7 +198,8 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }, UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, - expErr: ErrNoAccess, + wantErr: true, + expErr: ErrNoAccess, }, { name: "valid data, no_auth", @@ -208,7 +216,8 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }, UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, - expErr: ErrNoAccess, + wantErr: true, + expErr: ErrNoAccess, }, { name: "invalid board id", @@ -225,7 +234,8 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }, UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, - expErr: ErrNoSuchBoard, + wantErr: true, + expErr: ErrNoSuchBoard, }, { name: "invalid board title", @@ -242,7 +252,8 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }, UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, - expErr: ErrInvalidBoardTitle, + wantErr: true, + expErr: ErrInvalidBoardTitle, }, { name: "invalid board tags", @@ -259,7 +270,8 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }, UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { }, - expErr: fmt.Errorf("%v: %w", []string{"ni@#@#%!~~ce"}, ErrInvalidTagTitles), + wantErr: true, + expErr: fmt.Errorf("%v: %w", []string{"ni@#@#%!~~ce"}, ErrInvalidTagTitles), }, } @@ -284,8 +296,10 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { boardUsecase := New(log, mockBoardRepo, nil, sanitizer) err = boardUsecase.UpdateBoardInfo(test.inCtx, test.updatedBoardData) - if err != nil { + if test.wantErr { require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) } }) } @@ -300,6 +314,7 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { GetContributorBoardsIDs GetContributorBoardsIDs GetBoardsByUserID GetBoardsByUserID expBoards []dto.UserBoard + wantErr bool expErr error }{ { @@ -347,7 +362,6 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { Pins: []string{}, }, }, - expErr: nil, }, { name: "non-exisitng user with valid username", @@ -361,6 +375,7 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { }, expBoards: nil, + wantErr: true, expErr: ErrInvalidUsername, }, { @@ -374,6 +389,7 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { }, expBoards: nil, + wantErr: true, expErr: ErrInvalidUsername, }, } @@ -397,8 +413,10 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { boardUsecase := New(log, mockBoardRepo, mockUserRepo, sanitizer) userBoards, err := boardUsecase.GetBoardsByUsername(test.inCtx, test.username) - if err != nil { + if test.wantErr { require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) } require.Equal(t, test.expBoards, userBoards) @@ -416,6 +434,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { GetBoardByID GetBoardByID hasAccess bool expBoard dto.UserBoard + wantErr bool expErr error }{ { @@ -449,7 +468,6 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, }, - expErr: nil, }, { name: "private board, valid board id, request from contributor", @@ -482,7 +500,6 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, }, - expErr: nil, }, { name: "private board, valid board id, request from not author, not contributor", @@ -499,6 +516,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { }, hasAccess: false, expBoard: dto.UserBoard{}, + wantErr: true, expErr: ErrNoSuchBoard, }, { @@ -516,6 +534,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { }, hasAccess: false, expBoard: dto.UserBoard{}, + wantErr: true, expErr: ErrNoSuchBoard, }, { @@ -549,7 +568,6 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, }, - expErr: nil, }, { name: "invalid board id", @@ -563,6 +581,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { }, expBoard: dto.UserBoard{}, + wantErr: true, expErr: ErrNoSuchBoard, }, } @@ -584,11 +603,97 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { boardUsecase := New(log, mockBoardRepo, nil, sanitizer) board, err := boardUsecase.GetCertainBoard(test.inCtx, test.boardID) - if err != nil { + if test.wantErr { require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) } require.Equal(t, test.expBoard, board) }) } } + +func TestBoardUsecase_DeleteCertainBoard(t *testing.T) { + tests := []struct { + name string + inCtx context.Context + boardID int + GetBoardAuthorByBoardID GetBoardAuthorByBoardID + DeleteBoardByID DeleteBoardByID + wantErr bool + expErr error + }{ + { + name: "valid board id, deletion by author", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + boardID: 23, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) + }, + DeleteBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().DeleteBoardByID(ctx, boardID).Return(nil).Times(1) + }, + }, + { + name: "valid board id, deletion by another user", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + boardID: 23, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(2, nil).Times(1) + }, + DeleteBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + }, + wantErr: true, + expErr: ErrNoAccess, + }, + { + name: "valid board id, deletion by unauthorized user", + inCtx: context.Background(), + boardID: 23, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(2, nil).Times(1) + }, + DeleteBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + }, + wantErr: true, + expErr: ErrNoAccess, + }, + { + name: "invalid board id", + inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), + boardID: 1221323, + GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(0, repository.ErrNoData).Times(1) + }, + DeleteBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { + }, + wantErr: true, + expErr: ErrNoSuchBoard, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctl := gomock.NewController(t) + defer ctl.Finish() + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + mockBoardRepo := mock_board.NewMockRepository(ctl) + test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.boardID) + test.DeleteBoardByID(mockBoardRepo, test.inCtx, test.boardID) + + boardUsecase := New(log, mockBoardRepo, nil, sanitizer) + err = boardUsecase.DeleteCertainBoard(test.inCtx, test.boardID) + + if test.wantErr { + require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) + } + }) + } +} From 91362303b21bde617fa5257438194290a74c63b2 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 13:41:35 +0300 Subject: [PATCH 142/266] TP-28e_mockTests update: moved return value checking into if clause --- internal/pkg/usecase/board/usecase_test.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index b454788..4754c31 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -150,8 +150,8 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { require.EqualError(t, err, test.expErr.Error()) } else { require.NoError(t, err) + require.Equal(t, test.expNewID, newBoardID) } - require.Equal(t, test.expNewID, newBoardID) }) } } @@ -417,9 +417,8 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { require.EqualError(t, err, test.expErr.Error()) } else { require.NoError(t, err) + require.Equal(t, test.expBoards, userBoards) } - - require.Equal(t, test.expBoards, userBoards) }) } } @@ -607,9 +606,8 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { require.EqualError(t, err, test.expErr.Error()) } else { require.NoError(t, err) + require.Equal(t, test.expBoard, board) } - - require.Equal(t, test.expBoard, board) }) } } From 8ab83e8d00d64219814004b8bec087de49361a49 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 14:08:38 +0300 Subject: [PATCH 143/266] TP-28e_mockTests update: moved logger initialization to the outer scope --- internal/pkg/usecase/board/usecase_test.go | 52 +++++++++++++--------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index 4754c31..db9bbd5 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -36,6 +36,12 @@ var ( ) func TestBoardUsecase_CreateNewBoard(t *testing.T) { + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + tests := []struct { name string inCtx context.Context @@ -131,10 +137,6 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - stdLog.Fatal(err) - } mockBoardRepo := mock_board.NewMockRepository(ctl) test.CreateBoard(mockBoardRepo, test.inCtx, entity.Board{ AuthorID: test.newBoardData.AuthorID, @@ -157,6 +159,12 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { } func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + tests := []struct { name string inCtx context.Context @@ -280,10 +288,6 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - stdLog.Fatal(err) - } mockBoardRepo := mock_board.NewMockRepository(ctl) test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.updatedBoardData.ID) test.UpdateBoard(mockBoardRepo, test.inCtx, entity.Board{ @@ -294,7 +298,7 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { }, test.updatedBoardData.TagTitles) boardUsecase := New(log, mockBoardRepo, nil, sanitizer) - err = boardUsecase.UpdateBoardInfo(test.inCtx, test.updatedBoardData) + err := boardUsecase.UpdateBoardInfo(test.inCtx, test.updatedBoardData) if test.wantErr { require.EqualError(t, err, test.expErr.Error()) @@ -306,6 +310,12 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { } func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + tests := []struct { name string inCtx context.Context @@ -399,10 +409,6 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - stdLog.Fatal(err) - } mockBoardRepo := mock_board.NewMockRepository(ctl) mockUserRepo := mock_user.NewMockRepository(ctl) @@ -424,6 +430,12 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { } func TestBoardUsecase_GetCertainBoard(t *testing.T) { + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + tests := []struct { name string inCtx context.Context @@ -590,10 +602,6 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - stdLog.Fatal(err) - } mockBoardRepo := mock_board.NewMockRepository(ctl) test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.boardID) test.GetContributorsByBoardID(mockBoardRepo, test.inCtx, test.boardID) @@ -613,6 +621,12 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { } func TestBoardUsecase_DeleteCertainBoard(t *testing.T) { + + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + stdLog.Fatal(err) + } + tests := []struct { name string inCtx context.Context @@ -676,10 +690,6 @@ func TestBoardUsecase_DeleteCertainBoard(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - stdLog.Fatal(err) - } mockBoardRepo := mock_board.NewMockRepository(ctl) test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.boardID) test.DeleteBoardByID(mockBoardRepo, test.inCtx, test.boardID) From 9b2590f45b25f7043af7bec9206400057516c71d Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 17:01:06 +0300 Subject: [PATCH 144/266] TP-28e_mockTests: replaced log.Fatal with t.Fatalf --- internal/pkg/usecase/board/usecase_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index db9bbd5..f635f5f 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -3,7 +3,6 @@ package board import ( "context" "fmt" - stdLog "log" "testing" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" @@ -39,7 +38,7 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { - stdLog.Fatal(err) + t.Fatalf("test: log init - %s", err.Error()) } tests := []struct { @@ -162,7 +161,7 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { - stdLog.Fatal(err) + t.Fatalf("test: log init - %s", err.Error()) } tests := []struct { @@ -313,7 +312,7 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { - stdLog.Fatal(err) + t.Fatalf("test: log init - %s", err.Error()) } tests := []struct { @@ -433,7 +432,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { - stdLog.Fatal(err) + t.Fatalf("test: log init - %s", err.Error()) } tests := []struct { @@ -624,7 +623,7 @@ func TestBoardUsecase_DeleteCertainBoard(t *testing.T) { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { - stdLog.Fatal(err) + t.Fatalf("test: log init - %s", err.Error()) } tests := []struct { From 9b366c5f37d3fd02f21ae6765832d680b6cefacd Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Tue, 7 Nov 2023 17:55:48 +0300 Subject: [PATCH 145/266] TP-2e4 add testing cover --- Makefile | 7 +- go.mod | 1 + go.sum | 2 + internal/pkg/delivery/http/v1/auth.go | 2 +- internal/pkg/delivery/http/v1/profile.go | 2 +- internal/pkg/delivery/http/v1/profile_test.go | 60 +++ .../pkg/repository/board/postgres/repo.go | 6 +- .../pkg/repository/internal/pgtype/pgtype.go | 24 ++ internal/pkg/repository/pin/repo.go | 6 +- internal/pkg/repository/user/repo.go | 16 +- internal/pkg/repository/user/repo_test.go | 205 ++++++++++ internal/pkg/usecase/pin/mock/pin_mock.go | 384 +++++++++--------- internal/pkg/usecase/user/auth.go | 2 +- internal/pkg/usecase/user/auth_test.go | 145 +++++++ internal/pkg/usecase/user/credentials.go | 20 +- internal/pkg/usecase/user/info.go | 6 +- internal/pkg/usecase/user/mock/user_mock.go | 5 +- internal/pkg/usecase/user/profile.go | 2 +- internal/pkg/usecase/user/profile_test.go | 141 +++++++ internal/pkg/usecase/user/usecase.go | 4 +- 20 files changed, 811 insertions(+), 229 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/profile_test.go create mode 100644 internal/pkg/repository/internal/pgtype/pgtype.go create mode 100644 internal/pkg/repository/user/repo_test.go create mode 100644 internal/pkg/usecase/user/auth_test.go create mode 100644 internal/pkg/usecase/user/profile_test.go diff --git a/Makefile b/Makefile index a7e4aae..5a285f9 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all +.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out COV_HTML=coverage.html +CURRCOVER=github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1 build: go build -o bin/app cmd/app/*.go @@ -34,3 +35,7 @@ cover_all: go test -coverpkg=./... -coverprofile=cover ./... cat cover | grep -v "mock" | grep -v "easyjson" | grep -v "proto" | grep -v "ramrepo" > cover.out go tool cover -func=cover.out + +currcover: + go test -cover -v -coverprofile=cover.out ${CURRCOVER} + go tool cover -html=cover.out -o cover.html diff --git a/go.mod b/go.mod index 451a071..bc70b6b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/microcosm-cc/bluemonday v1.0.26 + github.com/pashagolub/pgxmock/v2 v2.12.0 github.com/proullon/ramsql v0.0.1 github.com/redis/go-redis/v9 v9.2.1 github.com/rs/cors v1.10.1 diff --git a/go.sum b/go.sum index 745d1b7..af80b91 100644 --- a/go.sum +++ b/go.sum @@ -100,6 +100,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= +github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= 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/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index c4f5d97..22cd871 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -55,7 +55,7 @@ func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) - params := usecase.NewCredentials() + params := usecase.UserCredentials{} err := json.NewDecoder(r.Body).Decode(&params) defer r.Body.Close() if err != nil { diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index f973137..4a58a3d 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -15,7 +15,7 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { userID := r.Context().Value(auth.KeyCurrentUserID).(int) - data := user.NewProfileUpdateData() + data := &user.ProfileUpdateData{} err := json.NewDecoder(r.Body).Decode(data) defer r.Body.Close() if err != nil { diff --git a/internal/pkg/delivery/http/v1/profile_test.go b/internal/pkg/delivery/http/v1/profile_test.go new file mode 100644 index 0000000..d927e7f --- /dev/null +++ b/internal/pkg/delivery/http/v1/profile_test.go @@ -0,0 +1,60 @@ +package v1 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func TestGetProfileInfo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + _ = ctx + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + usecase := mock.NewMockUsecase(ctrl) + hander := New(log, nil, usecase, nil, nil) + rec := httptest.NewRecorder() + request := &http.Request{} + ctxExp := context.WithValue(ctx, auth.KeyCurrentUserID, 122) + wantUser := user.User{ + ID: 122, + } + + usecase.EXPECT().GetAllProfileInfo(ctxExp, 122). + Return(&user.User{ID: 122}, nil). + Times(1) + + hander.GetProfileInfo(rec, request.WithContext(ctxExp)) + res := rec.Result() + defer res.Body.Close() + + actualBody := JsonResponse{Body: &user.User{}} + err = json.NewDecoder(res.Body).Decode(&actualBody) + require.NoError(t, err) + fmt.Println(actualBody.Body) + wantBody := JsonResponse{ + Status: "ok", + Message: "user data has been successfully received", + Body: &wantUser, + } + require.Equal(t, wantBody, actualBody) +} diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 6a768c2..93542a1 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -9,17 +9,17 @@ import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" ) type boardRepoPG struct { - db *pgxpool.Pool + db pgtype.PgxPoolIface sqlBuilder squirrel.StatementBuilderType } -func NewBoardRepoPG(db *pgxpool.Pool) *boardRepoPG { +func NewBoardRepoPG(db pgtype.PgxPoolIface) *boardRepoPG { return &boardRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} } diff --git a/internal/pkg/repository/internal/pgtype/pgtype.go b/internal/pkg/repository/internal/pgtype/pgtype.go new file mode 100644 index 0000000..64c51c9 --- /dev/null +++ b/internal/pkg/repository/internal/pgtype/pgtype.go @@ -0,0 +1,24 @@ +package pgtype + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" +) + +type PgxPoolIface interface { + Begin(ctx context.Context) (pgx.Tx, error) + BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) + Close() + Config() *pgxpool.Config + Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error) + Query(ctx context.Context, sql string, optionsAndArgs ...interface{}) (pgx.Rows, error) + QueryRow(ctx context.Context, sql string, optionsAndArgs ...interface{}) pgx.Row + Ping(ctx context.Context) error + Reset() + Stat() *pgxpool.Stat +} + +var _ PgxPoolIface = (*pgxpool.Pool)(nil) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 48ebf0a..3de6ea4 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -7,10 +7,10 @@ import ( sq "github.com/Masterminds/squirrel" pgx "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" ) type S map[string]any @@ -34,11 +34,11 @@ type Repository interface { } type pinRepoPG struct { - db *pgxpool.Pool + db pgtype.PgxPoolIface sqlBuilder sq.StatementBuilderType } -func NewPinRepoPG(db *pgxpool.Pool) *pinRepoPG { +func NewPinRepoPG(db pgtype.PgxPoolIface) *pinRepoPG { return &pinRepoPG{ db: db, sqlBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index beff813..1fb9deb 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -6,10 +6,10 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" ) //go:generate mockgen -destination=./mock/user_mock.go -package=mock -source=repo.go Repository @@ -18,7 +18,6 @@ type Repository interface { GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) GetUserIdByUsername(ctx context.Context, username string) (int, error) - GetLastUserID(ctx context.Context) (int, error) EditUserAvatar(ctx context.Context, userID int, avatar string) error GetAllUserData(ctx context.Context, userID int) (*user.User, error) EditUserInfo(ctx context.Context, userID int, updateFields S) error @@ -27,10 +26,10 @@ type Repository interface { type S map[string]any type userRepoPG struct { - db *pgxpool.Pool + db pgtype.PgxPoolIface } -func NewUserRepoPG(db *pgxpool.Pool) *userRepoPG { +func NewUserRepoPG(db pgtype.PgxPoolIface) *userRepoPG { return &userRepoPG{db} } @@ -110,12 +109,3 @@ func (u *userRepoPG) GetUserIdByUsername(ctx context.Context, username string) ( } return userID, nil } - -func (u *userRepoPG) GetLastUserID(ctx context.Context) (int, error) { - var lastUserID int - err := u.db.QueryRow(ctx, SelectLastUserID).Scan(&lastUserID) - if err != nil { - return 0, fmt.Errorf("get last user id: %w", err) - } - return lastUserID, nil -} diff --git a/internal/pkg/repository/user/repo_test.go b/internal/pkg/repository/user/repo_test.go new file mode 100644 index 0000000..e01b22e --- /dev/null +++ b/internal/pkg/repository/user/repo_test.go @@ -0,0 +1,205 @@ +package user + +import ( + "context" + "errors" + "testing" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/jackc/pgx/v5/pgtype" + "github.com/pashagolub/pgxmock/v2" + "github.com/stretchr/testify/require" +) + +func TestAddNewUser(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectExec("INSERT INTO profile"). + WithArgs("my_username", "1234", "a@test.com"). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + + err = repoUser.AddNewUser(ctx, &user.User{ + Username: "my_username", + Password: "1234", + Email: "a@test.com", + }) + require.NoError(t, err) + + wantErr := errors.New("insert profile fail") + pool.ExpectExec("INSERT INTO profile"). + WithArgs("my_username", "1234", "a@test.com"). + WillReturnError(wantErr) + + err = repoUser.AddNewUser(ctx, &user.User{ + Username: "my_username", + Password: "1234", + Email: "a@test.com", + }) + require.ErrorIs(t, err, wantErr) + require.EqualError(t, err, "add a new profile in storage: insert profile fail") +} + +func TestGetUserByUsername(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectQuery("SELECT id, password, email FROM profile"). + WithArgs("alex"). + WillReturnRows( + pgxmock.NewRows([]string{"id", "password", "email"}). + AddRow(3, "salt+hashPASS", "ex@mail.ru"), + ) + wantUser := user.User{ + ID: 3, + Password: "salt+hashPASS", + Email: "ex@mail.ru", + Username: "alex", + } + actualUser, err := repoUser.GetUserByUsername(ctx, "alex") + require.NoError(t, err) + require.NotNil(t, actualUser) + require.Equal(t, wantUser, *actualUser) + + wantErr := errors.New("fail select from profile") + pool.ExpectQuery("SELECT id, password, email FROM profile"). + WithArgs("alex"). + WillReturnError(wantErr) + actualUser, err = repoUser.GetUserByUsername(ctx, "alex") + require.ErrorIs(t, err, wantErr) + require.EqualError(t, err, "getting a user from storage: fail select from profile") + require.Nil(t, actualUser) +} + +func TestGetUsernameAndAvatarByID(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectQuery("SELECT username, avatar FROM profile"). + WithArgs(14). + WillReturnRows( + pgxmock.NewRows([]string{"username", "avatar"}). + AddRow("noname", "//pinspire.online/avatar.png"), + ) + actualUsername, actualAvatar, err := repoUser.GetUsernameAndAvatarByID(ctx, 14) + require.NoError(t, err) + require.Equal(t, "noname", actualUsername) + require.Equal(t, "//pinspire.online/avatar.png", actualAvatar) +} + +func TestEditUserAvatar(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectExec("UPDATE profile"). + WithArgs("new_avatar.svg", 16). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + err = repoUser.EditUserAvatar(ctx, 16, "new_avatar.svg") + require.NoError(t, err) + + wantErr := errors.New("fail update") + pool.ExpectExec("UPDATE profile"). + WithArgs("new_avatar.svg", 16). + WillReturnError(wantErr) + err = repoUser.EditUserAvatar(ctx, 16, "new_avatar.svg") + require.ErrorIs(t, err, wantErr) +} + +func TestGetAllUserData(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectQuery("SELECT username, email, avatar, name, surname, about_me FROM profile"). + WithArgs(54). + WillReturnRows( + pgxmock.NewRows([]string{"username", "email", "avatar", "name", "surname", "about_me"}). + AddRow("name", "12", "pic.webp", "", nil, "friendly"), + ) + actualUser, err := repoUser.GetAllUserData(ctx, 54) + require.NoError(t, err) + require.NotNil(t, actualUser) + require.Equal(t, user.User{ + ID: 54, + Avatar: "pic.webp", + Surname: pgtype.Text{String: "", Valid: false}, + Name: pgtype.Text{String: "", Valid: true}, + Username: "name", + AboutMe: pgtype.Text{String: "friendly", Valid: true}, + Email: "12", + }, *actualUser) +} + +func TestEditUserInfo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectExec("UPDATE profile"). + WithArgs("new", 16). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + err = repoUser.EditUserInfo(ctx, 16, S{"name": "new"}) + require.NoError(t, err) +} + +func TestGetUserIdByUsername(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatal(err) + } + + repoUser := NewUserRepoPG(pool) + + pool.ExpectQuery("SELECT id FROM profile"). + WithArgs("uniqname"). + WillReturnRows( + pgxmock.NewRows([]string{"id"}). + AddRow(4), + ) + id, err := repoUser.GetUserIdByUsername(ctx, "uniqname") + require.NoError(t, err) + require.Equal(t, 4, id) +} diff --git a/internal/pkg/usecase/pin/mock/pin_mock.go b/internal/pkg/usecase/pin/mock/pin_mock.go index 548cac5..822020d 100644 --- a/internal/pkg/usecase/pin/mock/pin_mock.go +++ b/internal/pkg/usecase/pin/mock/pin_mock.go @@ -4,195 +4,195 @@ // Package mock is a generated GoMock package. package mock -import ( - context "context" - io "io" - reflect "reflect" - - pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" - gomock "github.com/golang/mock/gomock" -) - -// MockUsecase is a mock of Usecase interface. -type MockUsecase struct { - ctrl *gomock.Controller - recorder *MockUsecaseMockRecorder -} - -// MockUsecaseMockRecorder is the mock recorder for MockUsecase. -type MockUsecaseMockRecorder struct { - mock *MockUsecase -} - -// NewMockUsecase creates a new mock instance. -func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { - mock := &MockUsecase{ctrl: ctrl} - mock.recorder = &MockUsecaseMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { - return m.recorder -} - -// CheckUserHasSetLike mocks base method. -func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. -func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) -} - -// CreateNewPin mocks base method. -func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) - ret0, _ := ret[0].(error) - return ret0 -} - -// CreateNewPin indicates an expected call of CreateNewPin. -func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) -} - -// DeleteLikeFromUser mocks base method. -func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. -func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) -} - -// DeletePinFromUser mocks base method. -func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeletePinFromUser indicates an expected call of DeletePinFromUser. -func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) -} - -// EditPinByID mocks base method. -func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) - ret0, _ := ret[0].(error) - return ret0 -} - -// EditPinByID indicates an expected call of EditPinByID. -func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) -} - -// IsAvailableBatchPinForFixOnBoard mocks base method. -func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) - ret0, _ := ret[0].(error) - return ret0 -} - -// IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. -func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) -} - -// IsAvailablePinForFixOnBoard mocks base method. -func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) - ret0, _ := ret[0].(error) - return ret0 -} - -// IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. -func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) -} - -// SelectNewPins mocks base method. -func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) - ret0, _ := ret[0].([]pin.Pin) - ret1, _ := ret[1].(int) - ret2, _ := ret[2].(int) - return ret0, ret1, ret2 -} - -// SelectNewPins indicates an expected call of SelectNewPins. -func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) -} - -// SelectUserPins mocks base method. -func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) - ret0, _ := ret[0].([]pin.Pin) - ret1, _ := ret[1].(int) - ret2, _ := ret[2].(int) - return ret0, ret1, ret2 -} - -// SelectUserPins indicates an expected call of SelectUserPins. -func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) -} - -// SetLikeFromUser mocks base method. -func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SetLikeFromUser indicates an expected call of SetLikeFromUser. -func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) -} - -// ViewAnPin mocks base method. -func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) - ret0, _ := ret[0].(*pin.Pin) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ViewAnPin indicates an expected call of ViewAnPin. -func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) -} +// import ( +// context "context" +// io "io" +// reflect "reflect" + +// pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" +// gomock "github.com/golang/mock/gomock" +// ) + +// // MockUsecase is a mock of Usecase interface. +// type MockUsecase struct { +// ctrl *gomock.Controller +// recorder *MockUsecaseMockRecorder +// } + +// // MockUsecaseMockRecorder is the mock recorder for MockUsecase. +// type MockUsecaseMockRecorder struct { +// mock *MockUsecase +// } + +// // NewMockUsecase creates a new mock instance. +// func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { +// mock := &MockUsecase{ctrl: ctrl} +// mock.recorder = &MockUsecaseMockRecorder{mock} +// return mock +// } + +// // EXPECT returns an object that allows the caller to indicate expected use. +// func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { +// return m.recorder +// } + +// // CheckUserHasSetLike mocks base method. +// func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) +// ret0, _ := ret[0].(bool) +// ret1, _ := ret[1].(error) +// return ret0, ret1 +// } + +// // CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. +// func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) +// } + +// // CreateNewPin mocks base method. +// func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) +// ret0, _ := ret[0].(error) +// return ret0 +// } + +// // CreateNewPin indicates an expected call of CreateNewPin. +// func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) +// } + +// // DeleteLikeFromUser mocks base method. +// func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) +// ret0, _ := ret[0].(error) +// return ret0 +// } + +// // DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. +// func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) +// } + +// // DeletePinFromUser mocks base method. +// func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) +// ret0, _ := ret[0].(error) +// return ret0 +// } + +// // DeletePinFromUser indicates an expected call of DeletePinFromUser. +// func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) +// } + +// // EditPinByID mocks base method. +// func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) +// ret0, _ := ret[0].(error) +// return ret0 +// } + +// // EditPinByID indicates an expected call of EditPinByID. +// func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) +// } + +// // IsAvailableBatchPinForFixOnBoard mocks base method. +// func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) +// ret0, _ := ret[0].(error) +// return ret0 +// } + +// // IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. +// func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) +// } + +// // IsAvailablePinForFixOnBoard mocks base method. +// func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) +// ret0, _ := ret[0].(error) +// return ret0 +// } + +// // IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. +// func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) +// } + +// // SelectNewPins mocks base method. +// func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) +// ret0, _ := ret[0].([]pin.Pin) +// ret1, _ := ret[1].(int) +// ret2, _ := ret[2].(int) +// return ret0, ret1, ret2 +// } + +// // SelectNewPins indicates an expected call of SelectNewPins. +// func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) +// } + +// // SelectUserPins mocks base method. +// func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) +// ret0, _ := ret[0].([]pin.Pin) +// ret1, _ := ret[1].(int) +// ret2, _ := ret[2].(int) +// return ret0, ret1, ret2 +// } + +// // SelectUserPins indicates an expected call of SelectUserPins. +// func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) +// } + +// // SetLikeFromUser mocks base method. +// func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) +// ret0, _ := ret[0].(int) +// ret1, _ := ret[1].(error) +// return ret0, ret1 +// } + +// // SetLikeFromUser indicates an expected call of SetLikeFromUser. +// func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) +// } + +// // ViewAnPin mocks base method. +// func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { +// m.ctrl.T.Helper() +// ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) +// ret0, _ := ret[0].(*pin.Pin) +// ret1, _ := ret[1].(error) +// return ret0, ret1 +// } + +// // ViewAnPin indicates an expected call of ViewAnPin. +// func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { +// mr.mock.ctrl.T.Helper() +// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) +// } diff --git a/internal/pkg/usecase/user/auth.go b/internal/pkg/usecase/user/auth.go index ab4fa59..d5ca8f9 100644 --- a/internal/pkg/usecase/user/auth.go +++ b/internal/pkg/usecase/user/auth.go @@ -22,7 +22,7 @@ func (u *userCase) Register(ctx context.Context, user *entity.User) error { return nil } -func (u *userCase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { +func (u *userCase) Authentication(ctx context.Context, credentials UserCredentials) (*entity.User, error) { user, err := u.repo.GetUserByUsername(ctx, credentials.Username) if err != nil { return nil, fmt.Errorf("user authentication: %w", err) diff --git a/internal/pkg/usecase/user/auth_test.go b/internal/pkg/usecase/user/auth_test.go new file mode 100644 index 0000000..9e42b15 --- /dev/null +++ b/internal/pkg/usecase/user/auth_test.go @@ -0,0 +1,145 @@ +package user + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func TestRegisterMock(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + repoMock := mock.NewMockRepository(ctrl) + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, nil, repoMock) + userRegister := &user.User{} + + repoMock.EXPECT(). + AddNewUser(ctx, userRegister). + Return(nil). + Times(1) + + err = usecase.Register(ctx, userRegister) + assert.NoError(t, err) + + expErr := errors.New("repo error") + repoMock.EXPECT(). + AddNewUser(ctx, userRegister). + Return(expErr). + Times(1) + + err = usecase.Register(ctx, userRegister) + require.ErrorIs(t, err, expErr) + require.EqualError(t, err, "user registration: repo error") +} + +func TestAuthentication(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + repoMock := mock.NewMockRepository(ctrl) + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, nil, repoMock) + cred := UserCredentials{ + Password: "1234", + Username: "test", + } + userAuth := &user.User{ + ID: 4, + Username: "test username", + Password: "$$$$$$$$$$$$$$$$$$$$$$$$$$$$", + } + + repoMock.EXPECT(). + GetUserByUsername(ctx, cred.Username). + Return(userAuth, nil). + Times(1) + + actualUser, err := usecase.Authentication(ctx, cred) + require.Nil(t, actualUser) + require.Equal(t, err, ErrUserAuthentication) + + expErr := errors.New("get user error") + repoMock.EXPECT(). + GetUserByUsername(ctx, cred.Username). + Return(userAuth, expErr). + Times(1) + + actualUser, err = usecase.Authentication(ctx, cred) + require.Nil(t, actualUser) + require.ErrorIs(t, err, expErr) + require.EqualError(t, err, "user authentication: get user error") + + salt, err := crypto.NewRandomString(lenSalt) + if err != nil { + t.Fatal(err) + } + + userAuth.Password = salt + crypto.PasswordHash(cred.Password, salt, lenPasswordHash) + repoMock.EXPECT(). + GetUserByUsername(ctx, cred.Username). + Return(userAuth, nil). + Times(1) + + actualUser, err = usecase.Authentication(ctx, cred) + require.NoError(t, err) + require.Equal(t, actualUser.Password, "") + require.Equal(t, userAuth, actualUser) +} + +func TestFindOutUsernameAndAvatar(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + repoMock := mock.NewMockRepository(ctrl) + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, nil, repoMock) + userID := 3 + + repoMock.EXPECT(). + GetUsernameAndAvatarByID(ctx, userID). + Return("TestUsername", "avatar.png", nil). + Times(1) + username, avatar, err := usecase.FindOutUsernameAndAvatar(ctx, userID) + require.NoError(t, err) + require.Equal(t, "TestUsername", username) + require.Equal(t, "avatar.png", avatar) + + expErr := errors.New("error find out") + repoMock.EXPECT(). + GetUsernameAndAvatarByID(ctx, userID). + Return("", "", expErr). + Times(1) + username, avatar, err = usecase.FindOutUsernameAndAvatar(ctx, userID) + require.Equal(t, expErr, err) + require.Equal(t, "", username) + require.Equal(t, "", avatar) +} diff --git a/internal/pkg/usecase/user/credentials.go b/internal/pkg/usecase/user/credentials.go index 9b5646b..e9f8537 100644 --- a/internal/pkg/usecase/user/credentials.go +++ b/internal/pkg/usecase/user/credentials.go @@ -1,10 +1,22 @@ package user -type userCredentials struct { +type UserCredentials struct { Username string Password string } -func NewCredentials() userCredentials { - return userCredentials{} -} +// func (cred *userCredentials) Username() string { +// return cred.username +// } + +// func (cred *userCredentials) Password() string { +// return cred.password +// } + +// func (cred *userCredentials) SetUsername(username string) { +// cred.username = username +// } + +// func (cred *userCredentials) SetPassword(password string) { +// cred.password = password +// } diff --git a/internal/pkg/usecase/user/info.go b/internal/pkg/usecase/user/info.go index 4420dea..456df7a 100644 --- a/internal/pkg/usecase/user/info.go +++ b/internal/pkg/usecase/user/info.go @@ -1,6 +1,6 @@ package user -type profileUpdateData struct { +type ProfileUpdateData struct { Username *string Email *string Name *string @@ -8,7 +8,3 @@ type profileUpdateData struct { AboutMe *string `json:"about_me` Password *string } - -func NewProfileUpdateData() *profileUpdateData { - return &profileUpdateData{} -} diff --git a/internal/pkg/usecase/user/mock/user_mock.go b/internal/pkg/usecase/user/mock/user_mock.go index f51b839..167e063 100644 --- a/internal/pkg/usecase/user/mock/user_mock.go +++ b/internal/pkg/usecase/user/mock/user_mock.go @@ -10,6 +10,7 @@ import ( reflect "reflect" user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + user0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" gomock "github.com/golang/mock/gomock" ) @@ -37,7 +38,7 @@ func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { } // Authentication mocks base method. -func (m *MockUsecase) Authentication(ctx context.Context, credentials userCredentials) (*user.User, error) { +func (m *MockUsecase) Authentication(ctx context.Context, credentials user0.UserCredentials) (*user.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Authentication", ctx, credentials) ret0, _ := ret[0].(*user.User) @@ -52,7 +53,7 @@ func (mr *MockUsecaseMockRecorder) Authentication(ctx, credentials interface{}) } // EditProfileInfo mocks base method. -func (m *MockUsecase) EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error { +func (m *MockUsecase) EditProfileInfo(ctx context.Context, userID int, updateData *user0.ProfileUpdateData) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "EditProfileInfo", ctx, userID, updateData) ret0, _ := ret[0].(error) diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index f87b9dc..5cb4f34 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -31,7 +31,7 @@ func (u *userCase) GetAllProfileInfo(ctx context.Context, userID int) (*entity.U return u.repo.GetAllUserData(ctx, userID) } -func (u *userCase) EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error { +func (u *userCase) EditProfileInfo(ctx context.Context, userID int, updateData *ProfileUpdateData) error { updateFields := repository.S{} if updateData.Username != nil { updateFields["username"] = *updateData.Username diff --git a/internal/pkg/usecase/user/profile_test.go b/internal/pkg/usecase/user/profile_test.go new file mode 100644 index 0000000..54753b9 --- /dev/null +++ b/internal/pkg/usecase/user/profile_test.go @@ -0,0 +1,141 @@ +package user + +import ( + "bytes" + "context" + "errors" + "io" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + repoUser "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user/mock" + usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func TestUpdateUserAvatar(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + userRepo := repo.NewMockRepository(ctrl) + imageUsecase := usecase.NewMockUsecase(ctrl) + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, imageUsecase, userRepo) + userID := 12 + var image io.Reader = bytes.NewBuffer(make([]byte, 2)) + + imageUsecase.EXPECT(). + UploadImage("avatars/", "image/png", int64(128), image, gomock.Any()). + Return("https://pinspire.online/upload/avatars/2023/avatar.png", nil). + Times(1) + + userRepo.EXPECT(). + EditUserAvatar(ctx, userID, "https://pinspire.online/upload/avatars/2023/avatar.png"). + Return(nil). + Times(1) + + err = usecase.UpdateUserAvatar(ctx, userID, "image/png", 128, image) + require.NoError(t, err) + + expErr := errors.New("upload avatar error") + imageUsecase.EXPECT(). + UploadImage("avatars/", "image/jpeg", int64(1), image, gomock.Any()). + Return("", expErr). + Times(1) + + err = usecase.UpdateUserAvatar(ctx, userID, "image/jpeg", 1, image) + require.ErrorIs(t, err, expErr) + require.EqualError(t, err, "uploading an avatar when updating avatar profile: upload avatar error") + + imageUsecase.EXPECT(). + UploadImage("avatars/", "", int64(-1), image, gomock.Any()). + Return("", nil). + Times(1) + + userRepo.EXPECT(). + EditUserAvatar(ctx, 144, ""). + Return(expErr). + Times(1) + err = usecase.UpdateUserAvatar(ctx, 144, "", -1, image) + require.ErrorIs(t, err, expErr) + require.EqualError(t, err, "edit user avatar: upload avatar error") +} + +func TestGetAllProfileInfo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + repoMock := repo.NewMockRepository(ctrl) + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, nil, repoMock) + wantUser := &user.User{} + wantErr := errors.New("err info") + + repoMock.EXPECT(). + GetAllUserData(ctx, 11). + Return(wantUser, wantErr). + Times(1) + actualUser, err := usecase.GetAllProfileInfo(ctx, 11) + require.Equal(t, wantUser, actualUser) + require.Equal(t, wantErr, err) +} + +func TestEditProfileInfo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + repoMock := repo.NewMockRepository(ctrl) + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, nil, repoMock) + updateData := &ProfileUpdateData{ + Username: new(string), + Email: new(string), + Name: new(string), + Surname: new(string), + AboutMe: new(string), + } + + repoMock.EXPECT(). + EditUserInfo(ctx, 5, repoUser.S{ + "username": "", + "email": "", + "name": "", + "surname": "", + "about_me": "", + }). + Return(nil). + Times(1) + err = usecase.EditProfileInfo(ctx, 5, updateData) + require.NoError(t, err) + + wantErr := errors.New("edit profile error") + repoMock.EXPECT(). + EditUserInfo(ctx, 5, repoUser.S{}). + Return(wantErr). + Times(1) + err = usecase.EditProfileInfo(ctx, 5, &ProfileUpdateData{}) + require.ErrorIs(t, err, wantErr) + require.EqualError(t, err, "edit profile info: edit profile error") +} diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index 48f722f..167860c 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -21,11 +21,11 @@ const ( //go:generate mockgen -destination=./mock/user_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { Register(ctx context.Context, user *entity.User) error - Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) + Authentication(ctx context.Context, credentials UserCredentials) (*entity.User, error) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAvatar string, sizeAvatar int64, avatar io.Reader) error GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) - EditProfileInfo(ctx context.Context, userID int, updateData *profileUpdateData) error + EditProfileInfo(ctx context.Context, userID int, updateData *ProfileUpdateData) error } type userCase struct { From 0f2511c836b4649e6bce9e6a2bc91fbf6d0636b5 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 19:28:10 +0300 Subject: [PATCH 146/266] TP-28e_mockTests update: delete makefile target, delete old pin usecase_test --- Makefile | 4 -- internal/pkg/usecase/pin/usecase_test.go | 48 ------------------------ 2 files changed, 52 deletions(-) delete mode 100644 internal/pkg/usecase/pin/usecase_test.go diff --git a/Makefile b/Makefile index 232a7a4..bdc3772 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,3 @@ retest: doc: swag fmt swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) - -gen: - mockgen -source=internal/pkg/repository/board/repo.go -source=internal/pkg/repository/user/repo.go \ - -destination=internal/pkg/repository/board/mock/mock_repo.go -destination=internal/pkg/repository/user/mock/mock_repo.go \ No newline at end of file diff --git a/internal/pkg/usecase/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go deleted file mode 100644 index 7b41f06..0000000 --- a/internal/pkg/usecase/pin/usecase_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package pin - -import ( - "context" - "math/rand" - "strconv" - "testing" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func TestSelectNewPins(t *testing.T) { - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - pinCase := New(log, nil, ramrepo.NewRamPinRepo(db)) - - testCases := []struct { - name string - count, lastID int - expNewLastID int - }{ - { - name: "provide correct count and lastID", - count: 2, - lastID: 1, - expNewLastID: 2, - }, - { - name: "provide incorrect count", - count: -2, - lastID: 1, - expNewLastID: 1, - }, - } - - for _, tCase := range testCases { - t.Run(tCase.name, func(t *testing.T) { - _, actualLastID, _ := pinCase.SelectNewPins(context.Background(), tCase.count, tCase.lastID, 0) - require.Equal(t, tCase.expNewLastID, actualLastID) - }) - } -} From e35385e3b49435b65cbfcf41c56540fd349971be Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 19:29:54 +0300 Subject: [PATCH 147/266] TP-28e_mockTests update: update makefile --- Makefile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bdc3772..1396885 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -.PHONY: build run test test_with_coverage cleantest retest doc gen +.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out COV_HTML=coverage.html +CURRCOVER=github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1 build: go build -o bin/app cmd/app/*.go @@ -11,11 +12,10 @@ run: build ./bin/app test: - go test ./... -covermode=atomic -coverpkg ./... -coverprofile=$(COV_OUT) + go test ./... -race -covermode=atomic -coverpkg ./... -coverprofile=$(COV_OUT) test_with_coverage: test go tool cover -html $(COV_OUT) -o $(COV_HTML) - go tool cover -func profile.cov cleantest: rm coverage* @@ -27,3 +27,15 @@ retest: doc: swag fmt swag init -g $(ENTRYPOINT) --pd -o $(DOC_DIR) + +generate: + go generate ./... + +cover_all: + go test -coverpkg=./... -coverprofile=cover ./... + cat cover | grep -v "mock" | grep -v "easyjson" | grep -v "proto" | grep -v "ramrepo" > cover.out + go tool cover -func=cover.out + +currcover: + go test -cover -v -coverprofile=cover.out ${CURRCOVER} + go tool cover -html=cover.out -o cover.html \ No newline at end of file From 4c1c90f6ffebb9073474a9822d223d2222128260 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 7 Nov 2023 19:43:48 +0300 Subject: [PATCH 148/266] dev2 update: deleted generated mock duplicates --- .../pkg/repository/board/mock/mock_repo.go | 156 ------------------ .../pkg/repository/user/mock/mock_repo.go | 155 ----------------- 2 files changed, 311 deletions(-) delete mode 100644 internal/pkg/repository/board/mock/mock_repo.go delete mode 100644 internal/pkg/repository/user/mock/mock_repo.go diff --git a/internal/pkg/repository/board/mock/mock_repo.go b/internal/pkg/repository/board/mock/mock_repo.go deleted file mode 100644 index 66109dd..0000000 --- a/internal/pkg/repository/board/mock/mock_repo.go +++ /dev/null @@ -1,156 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: internal/pkg/repository/board/repo.go - -// Package mock_board is a generated GoMock package. -package mock_board - -import ( - context "context" - reflect "reflect" - - board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" - user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" - gomock "github.com/golang/mock/gomock" -) - -// MockRepository is a mock of Repository interface. -type MockRepository struct { - ctrl *gomock.Controller - recorder *MockRepositoryMockRecorder -} - -// MockRepositoryMockRecorder is the mock recorder for MockRepository. -type MockRepositoryMockRecorder struct { - mock *MockRepository -} - -// NewMockRepository creates a new mock instance. -func NewMockRepository(ctrl *gomock.Controller) *MockRepository { - mock := &MockRepository{ctrl: ctrl} - mock.recorder = &MockRepositoryMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { - return m.recorder -} - -// CreateBoard mocks base method. -func (m *MockRepository) CreateBoard(ctx context.Context, board board.Board, tagTitles []string) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateBoard", ctx, board, tagTitles) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateBoard indicates an expected call of CreateBoard. -func (mr *MockRepositoryMockRecorder) CreateBoard(ctx, board, tagTitles interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBoard", reflect.TypeOf((*MockRepository)(nil).CreateBoard), ctx, board, tagTitles) -} - -// DeleteBoardByID mocks base method. -func (m *MockRepository) DeleteBoardByID(ctx context.Context, boardID int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteBoardByID", ctx, boardID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteBoardByID indicates an expected call of DeleteBoardByID. -func (mr *MockRepositoryMockRecorder) DeleteBoardByID(ctx, boardID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoardByID", reflect.TypeOf((*MockRepository)(nil).DeleteBoardByID), ctx, boardID) -} - -// GetBoardAuthorByBoardID mocks base method. -func (m *MockRepository) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBoardAuthorByBoardID", ctx, boardID) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBoardAuthorByBoardID indicates an expected call of GetBoardAuthorByBoardID. -func (mr *MockRepositoryMockRecorder) GetBoardAuthorByBoardID(ctx, boardID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardAuthorByBoardID", reflect.TypeOf((*MockRepository)(nil).GetBoardAuthorByBoardID), ctx, boardID) -} - -// GetBoardByID mocks base method. -func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (dto.UserBoard, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBoardByID", ctx, boardID, hasAccess) - ret0, _ := ret[0].(dto.UserBoard) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBoardByID indicates an expected call of GetBoardByID. -func (mr *MockRepositoryMockRecorder) GetBoardByID(ctx, boardID, hasAccess interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardByID", reflect.TypeOf((*MockRepository)(nil).GetBoardByID), ctx, boardID, hasAccess) -} - -// GetBoardsByUserID mocks base method. -func (m *MockRepository) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBoardsByUserID", ctx, userID, isAuthor, accessableBoardsIDs) - ret0, _ := ret[0].([]dto.UserBoard) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBoardsByUserID indicates an expected call of GetBoardsByUserID. -func (mr *MockRepositoryMockRecorder) GetBoardsByUserID(ctx, userID, isAuthor, accessableBoardsIDs interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardsByUserID", reflect.TypeOf((*MockRepository)(nil).GetBoardsByUserID), ctx, userID, isAuthor, accessableBoardsIDs) -} - -// GetContributorBoardsIDs mocks base method. -func (m *MockRepository) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetContributorBoardsIDs", ctx, contributorID) - ret0, _ := ret[0].([]int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetContributorBoardsIDs indicates an expected call of GetContributorBoardsIDs. -func (mr *MockRepositoryMockRecorder) GetContributorBoardsIDs(ctx, contributorID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorBoardsIDs", reflect.TypeOf((*MockRepository)(nil).GetContributorBoardsIDs), ctx, contributorID) -} - -// GetContributorsByBoardID mocks base method. -func (m *MockRepository) GetContributorsByBoardID(ctx context.Context, boardID int) ([]user.User, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetContributorsByBoardID", ctx, boardID) - ret0, _ := ret[0].([]user.User) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetContributorsByBoardID indicates an expected call of GetContributorsByBoardID. -func (mr *MockRepositoryMockRecorder) GetContributorsByBoardID(ctx, boardID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorsByBoardID", reflect.TypeOf((*MockRepository)(nil).GetContributorsByBoardID), ctx, boardID) -} - -// UpdateBoard mocks base method. -func (m *MockRepository) UpdateBoard(ctx context.Context, newBoardData board.Board, tagTitles []string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateBoard", ctx, newBoardData, tagTitles) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateBoard indicates an expected call of UpdateBoard. -func (mr *MockRepositoryMockRecorder) UpdateBoard(ctx, newBoardData, tagTitles interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBoard", reflect.TypeOf((*MockRepository)(nil).UpdateBoard), ctx, newBoardData, tagTitles) -} diff --git a/internal/pkg/repository/user/mock/mock_repo.go b/internal/pkg/repository/user/mock/mock_repo.go deleted file mode 100644 index 7573c6c..0000000 --- a/internal/pkg/repository/user/mock/mock_repo.go +++ /dev/null @@ -1,155 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: internal/pkg/repository/user/repo.go - -// Package mock_user is a generated GoMock package. -package mock_user - -import ( - context "context" - reflect "reflect" - - user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - user0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" - gomock "github.com/golang/mock/gomock" -) - -// MockRepository is a mock of Repository interface. -type MockRepository struct { - ctrl *gomock.Controller - recorder *MockRepositoryMockRecorder -} - -// MockRepositoryMockRecorder is the mock recorder for MockRepository. -type MockRepositoryMockRecorder struct { - mock *MockRepository -} - -// NewMockRepository creates a new mock instance. -func NewMockRepository(ctrl *gomock.Controller) *MockRepository { - mock := &MockRepository{ctrl: ctrl} - mock.recorder = &MockRepositoryMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { - return m.recorder -} - -// AddNewUser mocks base method. -func (m *MockRepository) AddNewUser(ctx context.Context, user *user.User) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddNewUser", ctx, user) - ret0, _ := ret[0].(error) - return ret0 -} - -// AddNewUser indicates an expected call of AddNewUser. -func (mr *MockRepositoryMockRecorder) AddNewUser(ctx, user interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddNewUser", reflect.TypeOf((*MockRepository)(nil).AddNewUser), ctx, user) -} - -// EditUserAvatar mocks base method. -func (m *MockRepository) EditUserAvatar(ctx context.Context, userID int, avatar string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EditUserAvatar", ctx, userID, avatar) - ret0, _ := ret[0].(error) - return ret0 -} - -// EditUserAvatar indicates an expected call of EditUserAvatar. -func (mr *MockRepositoryMockRecorder) EditUserAvatar(ctx, userID, avatar interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditUserAvatar", reflect.TypeOf((*MockRepository)(nil).EditUserAvatar), ctx, userID, avatar) -} - -// EditUserInfo mocks base method. -func (m *MockRepository) EditUserInfo(ctx context.Context, userID int, updateFields user0.S) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EditUserInfo", ctx, userID, updateFields) - ret0, _ := ret[0].(error) - return ret0 -} - -// EditUserInfo indicates an expected call of EditUserInfo. -func (mr *MockRepositoryMockRecorder) EditUserInfo(ctx, userID, updateFields interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditUserInfo", reflect.TypeOf((*MockRepository)(nil).EditUserInfo), ctx, userID, updateFields) -} - -// GetAllUserData mocks base method. -func (m *MockRepository) GetAllUserData(ctx context.Context, userID int) (*user.User, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAllUserData", ctx, userID) - ret0, _ := ret[0].(*user.User) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAllUserData indicates an expected call of GetAllUserData. -func (mr *MockRepositoryMockRecorder) GetAllUserData(ctx, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllUserData", reflect.TypeOf((*MockRepository)(nil).GetAllUserData), ctx, userID) -} - -// GetLastUserID mocks base method. -func (m *MockRepository) GetLastUserID(ctx context.Context) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLastUserID", ctx) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLastUserID indicates an expected call of GetLastUserID. -func (mr *MockRepositoryMockRecorder) GetLastUserID(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastUserID", reflect.TypeOf((*MockRepository)(nil).GetLastUserID), ctx) -} - -// GetUserByUsername mocks base method. -func (m *MockRepository) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUserByUsername", ctx, username) - ret0, _ := ret[0].(*user.User) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUserByUsername indicates an expected call of GetUserByUsername. -func (mr *MockRepositoryMockRecorder) GetUserByUsername(ctx, username interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockRepository)(nil).GetUserByUsername), ctx, username) -} - -// GetUserIdByUsername mocks base method. -func (m *MockRepository) GetUserIdByUsername(ctx context.Context, username string) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUserIdByUsername", ctx, username) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUserIdByUsername indicates an expected call of GetUserIdByUsername. -func (mr *MockRepositoryMockRecorder) GetUserIdByUsername(ctx, username interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserIdByUsername", reflect.TypeOf((*MockRepository)(nil).GetUserIdByUsername), ctx, username) -} - -// GetUsernameAndAvatarByID mocks base method. -func (m *MockRepository) GetUsernameAndAvatarByID(ctx context.Context, userID int) (string, string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUsernameAndAvatarByID", ctx, userID) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(string) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetUsernameAndAvatarByID indicates an expected call of GetUsernameAndAvatarByID. -func (mr *MockRepositoryMockRecorder) GetUsernameAndAvatarByID(ctx, userID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsernameAndAvatarByID", reflect.TypeOf((*MockRepository)(nil).GetUsernameAndAvatarByID), ctx, userID) -} From 4995fc47e4800b2a5753e451353113a78c487902 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Tue, 7 Nov 2023 22:17:43 +0300 Subject: [PATCH 149/266] TP-2e4 add: usecase test session and image --- internal/pkg/usecase/image/usecase_test.go | 54 +++++++++++++++++ internal/pkg/usecase/session/manager_test.go | 64 ++++++++++++++++++++ internal/pkg/usecase/user/credentials.go | 16 ----- internal/pkg/usecase/user/profile_test.go | 42 ++++++++++++- 4 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 internal/pkg/usecase/image/usecase_test.go diff --git a/internal/pkg/usecase/image/usecase_test.go b/internal/pkg/usecase/image/usecase_test.go new file mode 100644 index 0000000..0394a25 --- /dev/null +++ b/internal/pkg/usecase/image/usecase_test.go @@ -0,0 +1,54 @@ +package image + +import ( + "bytes" + "image" + "image/png" + "testing" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func TestUploadInvalidImage(t *testing.T) { + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + usecase := New(log, nil) + + s, err := usecase.UploadImage("prefixPath", "image/png", 30, bytes.NewBuffer(nil), check.AnySize) + require.Equal(t, ErrInvalidImage, err) + require.Equal(t, "", s) +} + +func TestUploadValidImage(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + imgRepo := mock.NewMockRepository(ctrl) + usecase := New(log, imgRepo) + rect := image.Rect(0, 0, 500, 500) + img := image.NewRGBA(rect).SubImage(rect) + buf := bytes.NewBuffer(nil) + err = png.Encode(buf, img) + require.NoError(t, err, "encode valid png image") + + prefixPath := "prefix" + written := int64(buf.Len()) + expFilename := "image.png" + imgRepo.EXPECT(). + SaveImage(prefixPath, "png", gomock.Any()). + Return(expFilename, written, nil). + Times(1) + actualFile, err := usecase.UploadImage("prefix", "image/png", written, buf, check.AnySize) + require.NoError(t, err) + require.Equal(t, "https://pinspire.online:8081/"+expFilename, actualFile) +} diff --git a/internal/pkg/usecase/session/manager_test.go b/internal/pkg/usecase/session/manager_test.go index 89a3633..3a1f4cf 100644 --- a/internal/pkg/usecase/session/manager_test.go +++ b/internal/pkg/usecase/session/manager_test.go @@ -2,16 +2,80 @@ package session import ( "context" + "errors" "fmt" "math/rand" "strconv" "testing" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session/mock" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) +func TestCreateNewSessionForUser(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + sessRepo := mock.NewMockRepository(ctrl) + + sm := New(log, sessRepo) + sessRepo.EXPECT(). + AddSession(ctx, gomock.Any()). + Return(nil). + Times(1) + + expUserID := 32 + s, err := sm.CreateNewSessionForUser(ctx, expUserID) + require.NoError(t, err) + require.NotNil(t, s) + require.Equal(t, expUserID, s.UserID) + + expErr := errors.New("err") + sessRepo.EXPECT(). + AddSession(ctx, gomock.Any()). + Return(expErr). + Times(1) + + s, err = sm.CreateNewSessionForUser(ctx, 0) + require.ErrorIs(t, err, expErr) + require.Nil(t, s) +} + +func TestDeleteUserSession(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + sessRepo := mock.NewMockRepository(ctrl) + + sm := New(log, sessRepo) + expKey := "session-key" + expErr := errors.New("err") + + sessRepo.EXPECT(). + DeleteSessionByKey(ctx, expKey). + Return(expErr). + Times(1) + + err = sm.DeleteUserSession(ctx, expKey) + require.ErrorIs(t, err, expErr) +} func TestGetUserIDBySessionKey(t *testing.T) { log, err := logger.New(logger.RFC3339FormatTime()) if err != nil { diff --git a/internal/pkg/usecase/user/credentials.go b/internal/pkg/usecase/user/credentials.go index e9f8537..c8d95d6 100644 --- a/internal/pkg/usecase/user/credentials.go +++ b/internal/pkg/usecase/user/credentials.go @@ -4,19 +4,3 @@ type UserCredentials struct { Username string Password string } - -// func (cred *userCredentials) Username() string { -// return cred.username -// } - -// func (cred *userCredentials) Password() string { -// return cred.password -// } - -// func (cred *userCredentials) SetUsername(username string) { -// cred.username = username -// } - -// func (cred *userCredentials) SetPassword(password string) { -// cred.password = password -// } diff --git a/internal/pkg/usecase/user/profile_test.go b/internal/pkg/usecase/user/profile_test.go index 54753b9..90e9b88 100644 --- a/internal/pkg/usecase/user/profile_test.go +++ b/internal/pkg/usecase/user/profile_test.go @@ -4,7 +4,9 @@ import ( "bytes" "context" "errors" + "fmt" "io" + "reflect" "testing" "github.com/golang/mock/gomock" @@ -17,6 +19,37 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +type requireVal int8 + +const anyVal requireVal = 0 + +type equalMap map[string]any + +func (em equalMap) Matches(x any) bool { + v := reflect.ValueOf(x) + fmt.Println(v.Type(), v.Type().String()) + if v.Kind() != reflect.Map || v.Len() != len(em) { + return false + } + iter := v.MapRange() + for iter.Next() { + if iter.Key().Type().Name() != "string" { + return false + } + + if val, ok := em[iter.Key().String()]; !ok || iter.Value().Interface() != val { + if t, ok := val.(requireVal); ok && t == anyVal { + continue + } + return false + } + } + + return true +} + +func (em equalMap) String() string { return "equal map" } + func TestUpdateUserAvatar(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -115,16 +148,19 @@ func TestEditProfileInfo(t *testing.T) { Name: new(string), Surname: new(string), AboutMe: new(string), + Password: new(string), } + *updateData.AboutMe = "friendly" repoMock.EXPECT(). - EditUserInfo(ctx, 5, repoUser.S{ + EditUserInfo(ctx, 5, gomock.All(equalMap{ "username": "", "email": "", "name": "", "surname": "", - "about_me": "", - }). + "about_me": "friendly", + "password": anyVal, + })). Return(nil). Times(1) err = usecase.EditProfileInfo(ctx, 5, updateData) From aa248b93192e3c17223fc368a08f1b7714789081 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 02:47:08 +0300 Subject: [PATCH 150/266] dev2 update: not view deleted pin in tape --- internal/pkg/repository/pin/queries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 068c1b4..83811f0 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -1,7 +1,7 @@ package pin var ( - SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" + SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND deleted_at IS NULL AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" SelectUserPinsLimit = "SELECT id, picture, public FROM pin WHERE author = $1 AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" From b2b1695742655d72712c52a87a1fd7fc1ce1b58b Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 02:47:52 +0300 Subject: [PATCH 151/266] dev2 add: test for usecase pin --- internal/pkg/usecase/pin/like_test.go | 222 ++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 internal/pkg/usecase/pin/like_test.go diff --git a/internal/pkg/usecase/pin/like_test.go b/internal/pkg/usecase/pin/like_test.go new file mode 100644 index 0000000..2baabb3 --- /dev/null +++ b/internal/pkg/usecase/pin/like_test.go @@ -0,0 +1,222 @@ +package pin + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/require" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func TestIsAvailableBatchPinForFixOnBoard(t *testing.T) { + testCases := []struct { + Name string + Pins []entity.Pin + UserID int + WantErr error + }{ + { + Name: "one deleted pin", + Pins: []entity.Pin{{DeletedAt: pgtype.Timestamptz{Valid: true}}}, + UserID: 12, + WantErr: ErrPinDeleted, + }, + { + Name: "all available", + Pins: []entity.Pin{ + {Author: &user.User{ID: 34}, DeletedAt: pgtype.Timestamptz{Valid: false}, Public: true}, + {Author: &user.User{ID: 12}, Public: false}, + {Author: &user.User{ID: 34}, Public: true}, + }, + UserID: 12, + WantErr: nil, + }, + { + Name: "one not available", + Pins: []entity.Pin{ + {Author: &user.User{ID: 34}, DeletedAt: pgtype.Timestamptz{Valid: false}, Public: true}, + {Author: &user.User{ID: 12}, Public: false}, + {Author: &user.User{ID: 34}, Public: false}, + }, + UserID: 12, + WantErr: ErrForbiddenAction, + }, + } + for _, test := range testCases { + t.Run(test.Name, func(t *testing.T) { + test := test + t.Parallel() + err := isAvailableBatchPinForFixOnBoard(test.UserID, test.Pins...) + require.Equal(t, test.WantErr, err) + }) + } +} +func TestSetLikeOnAvailablePin(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + wantCountLike := 12 + pinID, userID := 123, 1 + pin := &entity.Pin{ + ID: pinID, + Author: &user.User{ID: 0}, + Public: false, + } + + repo.EXPECT(). + GetPinByID(ctx, pinID, false). + Return(pin, nil). + Times(1) + repo.EXPECT(). + IsAvailableToUserAsContributorBoard(ctx, pinID, userID). + Return(true, nil). + Times(1) + repo.EXPECT(). + SetLike(ctx, pinID, userID). + Return(wantCountLike, nil). + Times(1) + + actualCoutnLike, actualErr := pinCase.SetLikeFromUser(ctx, pinID, userID) + require.NoError(t, actualErr) + require.Equal(t, wantCountLike, actualCoutnLike) + + pin.Author.ID = userID + repo.EXPECT(). + GetPinByID(ctx, pinID, false). + Return(pin, nil). + Times(1) + repo.EXPECT(). + SetLike(ctx, pinID, userID). + Return(wantCountLike, nil). + Times(1) + + actualCoutnLike, actualErr = pinCase.SetLikeFromUser(ctx, pinID, userID) + require.NoError(t, actualErr) + require.Equal(t, wantCountLike, actualCoutnLike) +} + +func TestSetLikeOnNotAvailablePin(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + wantCountLike := 0 + pinID, userID := 123, 1 + pin := &entity.Pin{ + ID: pinID, + Author: &user.User{ID: 0}, + Public: false, + } + + repo.EXPECT(). + GetPinByID(ctx, pinID, false). + Return(pin, nil). + Times(1) + repo.EXPECT(). + IsAvailableToUserAsContributorBoard(ctx, pinID, userID). + Return(false, nil). + Times(1) + + actualCoutnLike, actualErr := pinCase.SetLikeFromUser(ctx, pinID, userID) + require.ErrorIs(t, actualErr, ErrPinNotAccess) + require.Equal(t, wantCountLike, actualCoutnLike) + + wantErr := errors.New("returned IsAvailableToUserAsContributorBoard") + repo.EXPECT(). + GetPinByID(ctx, pinID, false). + Return(pin, nil). + Times(1) + repo.EXPECT(). + IsAvailableToUserAsContributorBoard(ctx, pinID, userID). + Return(false, wantErr). + Times(1) + + actualCoutnLike, actualErr = pinCase.SetLikeFromUser(ctx, pinID, userID) + require.ErrorIs(t, actualErr, wantErr) + require.EqualError(t, actualErr, "set like from user: fail check available pin: returned IsAvailableToUserAsContributorBoard") + require.Equal(t, wantCountLike, actualCoutnLike) + + pin.DeletedAt.Valid = true + repo.EXPECT(). + GetPinByID(ctx, pinID, false). + Return(pin, nil). + Times(1) + + actualCoutnLike, actualErr = pinCase.SetLikeFromUser(ctx, pinID, userID) + require.ErrorIs(t, actualErr, ErrPinDeleted) + require.EqualError(t, actualErr, "set like from user: pin has been deleted") + require.Equal(t, wantCountLike, actualCoutnLike) +} + +func TestDeleteLikeFromUser(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + pinID, userID := 123, 1 + + repo.EXPECT(). + DelLike(ctx, pinID, userID). + Return(nil). + Times(1) + + err = pinCase.DeleteLikeFromUser(ctx, pinID, userID) + require.NoError(t, err) +} + +func TestCheckUserHasSetLike(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + pinID, userID := 123, 1 + + repo.EXPECT(). + IsSetLike(ctx, pinID, userID). + Return(true, nil). + Times(1) + + has, err := pinCase.CheckUserHasSetLike(ctx, pinID, userID) + require.NoError(t, err) + require.True(t, has) +} From 5440c542beaa4b4a07f258d214535b38d2ec2661 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 12:50:10 +0300 Subject: [PATCH 152/266] dev2 add: returning count like with delete --- internal/pkg/delivery/http/v1/like_pin.go | 4 +- internal/pkg/delivery/http/v1/pin.go | 2 +- internal/pkg/delivery/http/v1/validation.go | 2 +- internal/pkg/repository/pin/like.go | 10 +- internal/pkg/repository/pin/mock/pin_mock.go | 7 +- internal/pkg/repository/pin/queries.go | 2 +- internal/pkg/repository/pin/repo.go | 2 +- internal/pkg/repository/ramrepo/pin.go | 4 +- internal/pkg/usecase/pin/like.go | 2 +- internal/pkg/usecase/pin/like_test.go | 5 +- internal/pkg/usecase/pin/mock/pin_mock.go | 386 ++++++++++--------- internal/pkg/usecase/pin/update.go | 8 +- internal/pkg/usecase/pin/usecase.go | 4 +- internal/pkg/usecase/user/info.go | 2 +- 14 files changed, 221 insertions(+), 219 deletions(-) diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index ae03e30..cf50b60 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -53,12 +53,12 @@ func (h *HandlerHTTP) DeleteLikePin(w http.ResponseWriter, r *http.Request) { return } - err = h.pinCase.DeleteLikeFromUser(r.Context(), int(pinID), userID) + countLike, err := h.pinCase.DeleteLikeFromUser(r.Context(), int(pinID), userID) if err != nil { logger.Error(err.Error()) err = responseError(w, "like_pin_del", "internal error") } else { - err = responseOk(http.StatusOK, w, "ok", nil) + err = responseOk(http.StatusOK, w, "ok", map[string]int{"count_like": countLike}) } if err != nil { logger.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index b1b6116..e7ede87 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -165,7 +165,7 @@ func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { _, _ = userID, pinID - pinUpdate := usecase.NewPinUpdateData() + pinUpdate := &usecase.PinUpdateData{} err = json.NewDecoder(r.Body).Decode(pinUpdate) defer r.Body.Close() diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index 8763cd3..5d8bfb5 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -138,7 +138,7 @@ func isValidAboutMe(info string) bool { return false } for _, r := range info { - if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { + if !(unicode.IsNumber(r) || unicode.IsLetter(r) || unicode.IsSymbol(r) || unicode.IsSpace(r)) { return false } } diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go index c6b79c3..a21860e 100644 --- a/internal/pkg/repository/pin/like.go +++ b/internal/pkg/repository/pin/like.go @@ -17,12 +17,14 @@ func (p *pinRepoPG) SetLike(ctx context.Context, pinID, userID int) (int, error) return currCountLike + 1, nil } -func (p *pinRepoPG) DelLike(ctx context.Context, pinID, userID int) error { - _, err := p.db.Exec(ctx, DeleteLikePinFromUser, pinID, userID) +func (p *pinRepoPG) DelLike(ctx context.Context, pinID, userID int) (int, error) { + row := p.db.QueryRow(ctx, DeleteLikePinFromUser, pinID, userID) + var currCountLike int + err := row.Scan(&currCountLike) if err != nil { - return fmt.Errorf("delete like to pin from user in storage: %w", err) + return 0, fmt.Errorf("delete like to pin from user in storage: %w", err) } - return nil + return currCountLike - 1, nil } func (p *pinRepoPG) GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) { diff --git a/internal/pkg/repository/pin/mock/pin_mock.go b/internal/pkg/repository/pin/mock/pin_mock.go index a3048d0..bc8a5df 100644 --- a/internal/pkg/repository/pin/mock/pin_mock.go +++ b/internal/pkg/repository/pin/mock/pin_mock.go @@ -52,11 +52,12 @@ func (mr *MockRepositoryMockRecorder) AddNewPin(ctx, pin interface{}) *gomock.Ca } // DelLike mocks base method. -func (m *MockRepository) DelLike(ctx context.Context, pinID, userID int) error { +func (m *MockRepository) DelLike(ctx context.Context, pinID, userID int) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DelLike", ctx, pinID, userID) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 } // DelLike indicates an expected call of DelLike. diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 83811f0..c719475 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -33,6 +33,6 @@ var ( UpdatePinSetStatusDelete = "UPDATE pin SET deleted_at = now() WHERE id = $1 AND author = $2 AND deleted_at IS NULL;" - DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2;" + DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2 RETURNING (SELECT COUNT(*) FROM like_pin WHERE pin_id = $1);" DeleteAllTagsFromPin = "DELETE FROM pin_tag WHERE pin_id = $1;" ) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 3de6ea4..f8708f6 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -26,7 +26,7 @@ type Repository interface { DeletePin(ctx context.Context, pinID, userID int) error SetLike(ctx context.Context, pinID, userID int) (int, error) IsSetLike(ctx context.Context, pinID, userID int) (bool, error) - DelLike(ctx context.Context, pinID, userID int) error + DelLike(ctx context.Context, pinID, userID int) (int, error) EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) GetTagsByPinID(ctx context.Context, pinID int) ([]entity.Tag, error) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index a827661..807dde9 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -53,8 +53,8 @@ func (r *ramPinRepo) SetLike(ctx context.Context, pinID, userID int) (int, error return 0, ErrMethodUnimplemented } -func (r *ramPinRepo) DelLike(ctx context.Context, pinID, userID int) error { - return ErrMethodUnimplemented +func (r *ramPinRepo) DelLike(ctx context.Context, pinID, userID int) (int, error) { + return 0, ErrMethodUnimplemented } func (r *ramPinRepo) EditPinTags(ctx context.Context, pinID, userID int, titlePins []string) error { diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go index 6c44858..e571b05 100644 --- a/internal/pkg/usecase/pin/like.go +++ b/internal/pkg/usecase/pin/like.go @@ -12,7 +12,7 @@ func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, return p.repo.SetLike(ctx, pinID, userID) } -func (p *pinCase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { +func (p *pinCase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { return p.repo.DelLike(ctx, pinID, userID) } diff --git a/internal/pkg/usecase/pin/like_test.go b/internal/pkg/usecase/pin/like_test.go index 2baabb3..600659e 100644 --- a/internal/pkg/usecase/pin/like_test.go +++ b/internal/pkg/usecase/pin/like_test.go @@ -186,14 +186,15 @@ func TestDeleteLikeFromUser(t *testing.T) { repo := mock.NewMockRepository(ctrl) pinCase := New(log, nil, repo) pinID, userID := 123, 1 - + wantCountLike := 999 repo.EXPECT(). DelLike(ctx, pinID, userID). Return(nil). Times(1) - err = pinCase.DeleteLikeFromUser(ctx, pinID, userID) + actualCountLike, err := pinCase.DeleteLikeFromUser(ctx, pinID, userID) require.NoError(t, err) + require.Equal(t, wantCountLike, actualCountLike) } func TestCheckUserHasSetLike(t *testing.T) { diff --git a/internal/pkg/usecase/pin/mock/pin_mock.go b/internal/pkg/usecase/pin/mock/pin_mock.go index 822020d..79ff01c 100644 --- a/internal/pkg/usecase/pin/mock/pin_mock.go +++ b/internal/pkg/usecase/pin/mock/pin_mock.go @@ -4,195 +4,197 @@ // Package mock is a generated GoMock package. package mock -// import ( -// context "context" -// io "io" -// reflect "reflect" - -// pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" -// gomock "github.com/golang/mock/gomock" -// ) - -// // MockUsecase is a mock of Usecase interface. -// type MockUsecase struct { -// ctrl *gomock.Controller -// recorder *MockUsecaseMockRecorder -// } - -// // MockUsecaseMockRecorder is the mock recorder for MockUsecase. -// type MockUsecaseMockRecorder struct { -// mock *MockUsecase -// } - -// // NewMockUsecase creates a new mock instance. -// func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { -// mock := &MockUsecase{ctrl: ctrl} -// mock.recorder = &MockUsecaseMockRecorder{mock} -// return mock -// } - -// // EXPECT returns an object that allows the caller to indicate expected use. -// func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { -// return m.recorder -// } - -// // CheckUserHasSetLike mocks base method. -// func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) -// ret0, _ := ret[0].(bool) -// ret1, _ := ret[1].(error) -// return ret0, ret1 -// } - -// // CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. -// func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) -// } - -// // CreateNewPin mocks base method. -// func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // CreateNewPin indicates an expected call of CreateNewPin. -// func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) -// } - -// // DeleteLikeFromUser mocks base method. -// func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. -// func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) -// } - -// // DeletePinFromUser mocks base method. -// func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // DeletePinFromUser indicates an expected call of DeletePinFromUser. -// func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) -// } - -// // EditPinByID mocks base method. -// func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // EditPinByID indicates an expected call of EditPinByID. -// func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) -// } - -// // IsAvailableBatchPinForFixOnBoard mocks base method. -// func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. -// func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) -// } - -// // IsAvailablePinForFixOnBoard mocks base method. -// func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. -// func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) -// } - -// // SelectNewPins mocks base method. -// func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) -// ret0, _ := ret[0].([]pin.Pin) -// ret1, _ := ret[1].(int) -// ret2, _ := ret[2].(int) -// return ret0, ret1, ret2 -// } - -// // SelectNewPins indicates an expected call of SelectNewPins. -// func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) -// } - -// // SelectUserPins mocks base method. -// func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) -// ret0, _ := ret[0].([]pin.Pin) -// ret1, _ := ret[1].(int) -// ret2, _ := ret[2].(int) -// return ret0, ret1, ret2 -// } - -// // SelectUserPins indicates an expected call of SelectUserPins. -// func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) -// } - -// // SetLikeFromUser mocks base method. -// func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) -// ret0, _ := ret[0].(int) -// ret1, _ := ret[1].(error) -// return ret0, ret1 -// } - -// // SetLikeFromUser indicates an expected call of SetLikeFromUser. -// func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) -// } - -// // ViewAnPin mocks base method. -// func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) -// ret0, _ := ret[0].(*pin.Pin) -// ret1, _ := ret[1].(error) -// return ret0, ret1 -// } - -// // ViewAnPin indicates an expected call of ViewAnPin. -// func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) -// } +import ( + context "context" + io "io" + reflect "reflect" + + pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + pin0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// CheckUserHasSetLike mocks base method. +func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. +func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) +} + +// CreateNewPin mocks base method. +func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNewPin indicates an expected call of CreateNewPin. +func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) +} + +// DeleteLikeFromUser mocks base method. +func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. +func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) +} + +// DeletePinFromUser mocks base method. +func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePinFromUser indicates an expected call of DeletePinFromUser. +func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) +} + +// EditPinByID mocks base method. +func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pin0.PinUpdateData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditPinByID indicates an expected call of EditPinByID. +func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) +} + +// IsAvailableBatchPinForFixOnBoard mocks base method. +func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. +func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) +} + +// IsAvailablePinForFixOnBoard mocks base method. +func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. +func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) +} + +// SelectNewPins mocks base method. +func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(int) + return ret0, ret1, ret2 +} + +// SelectNewPins indicates an expected call of SelectNewPins. +func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) +} + +// SelectUserPins mocks base method. +func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(int) + return ret0, ret1, ret2 +} + +// SelectUserPins indicates an expected call of SelectUserPins. +func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) +} + +// SetLikeFromUser mocks base method. +func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetLikeFromUser indicates an expected call of SetLikeFromUser. +func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) +} + +// ViewAnPin mocks base method. +func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) + ret0, _ := ret[0].(*pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ViewAnPin indicates an expected call of ViewAnPin. +func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) +} diff --git a/internal/pkg/usecase/pin/update.go b/internal/pkg/usecase/pin/update.go index dc2c36e..6653c00 100644 --- a/internal/pkg/usecase/pin/update.go +++ b/internal/pkg/usecase/pin/update.go @@ -7,18 +7,14 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" ) -type pinUpdateData struct { +type PinUpdateData struct { Title *string Description *string Public *bool Tags []string } -func NewPinUpdateData() *pinUpdateData { - return &pinUpdateData{} -} - -func (p *pinCase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { +func (p *pinCase) EditPinByID(ctx context.Context, pinID, userID int, updateData *PinUpdateData) error { data := pin.S{} if updateData.Title != nil { data["title"] = *updateData.Title diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index ab0774b..bc34e31 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -23,8 +23,8 @@ type Usecase interface { DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) - DeleteLikeFromUser(ctx context.Context, pinID, userID int) error - EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error + DeleteLikeFromUser(ctx context.Context, pinID, userID int) (int, error) + EditPinByID(ctx context.Context, pinID, userID int, updateData *PinUpdateData) error ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error diff --git a/internal/pkg/usecase/user/info.go b/internal/pkg/usecase/user/info.go index 456df7a..1993261 100644 --- a/internal/pkg/usecase/user/info.go +++ b/internal/pkg/usecase/user/info.go @@ -5,6 +5,6 @@ type ProfileUpdateData struct { Email *string Name *string Surname *string - AboutMe *string `json:"about_me` + AboutMe *string `json:"about_me"` Password *string } From e51042a69909cb72fa4acf6b21be954eeeffe47a Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 12:52:12 +0300 Subject: [PATCH 153/266] dev2 del: status code 204 on path /api/v1/csrf --- internal/pkg/middleware/security/csrf.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/pkg/middleware/security/csrf.go b/internal/pkg/middleware/security/csrf.go index 893b5b3..6c2035d 100644 --- a/internal/pkg/middleware/security/csrf.go +++ b/internal/pkg/middleware/security/csrf.go @@ -13,7 +13,6 @@ func CSRF(cfg CSRFConfig) mw.Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == cfg.PathToGet && r.Method == http.MethodGet { - w.WriteHeader(http.StatusNoContent) setToken(w, &cfg) return } From b2325984b94314f98789b35e8a7af710a0713708 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 13:06:18 +0300 Subject: [PATCH 154/266] dev2 update: select pin users --- internal/pkg/repository/pin/queries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index c719475..81b82af 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -2,7 +2,7 @@ package pin var ( SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND deleted_at IS NULL AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" - SelectUserPinsLimit = "SELECT id, picture, public FROM pin WHERE author = $1 AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" + SelectUserPinsLimit = "SELECT id, picture, public FROM pin WHERE author = $1 AND deleted_at IS NULL AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" SelectPinByIDWithAuthor = `SELECT author, title, description, picture, public, pin.deleted_at, username, avatar From 803f9723a0b2b26f8c947b4956ca39b899d70a80 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 16:26:09 +0300 Subject: [PATCH 155/266] dev2 update: fail test --- internal/pkg/usecase/pin/like_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/usecase/pin/like_test.go b/internal/pkg/usecase/pin/like_test.go index 600659e..2eb0a8e 100644 --- a/internal/pkg/usecase/pin/like_test.go +++ b/internal/pkg/usecase/pin/like_test.go @@ -189,7 +189,7 @@ func TestDeleteLikeFromUser(t *testing.T) { wantCountLike := 999 repo.EXPECT(). DelLike(ctx, pinID, userID). - Return(nil). + Return(wantCountLike, nil). Times(1) actualCountLike, err := pinCase.DeleteLikeFromUser(ctx, pinID, userID) From 34f143d629f83c1cfa3914815454e814237b87c4 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 8 Nov 2023 19:54:21 +0300 Subject: [PATCH 156/266] TP-2e4 add: cover usecase/pin --- go.mod | 1 - go.sum | 2 - internal/pkg/usecase/pin/check_test.go | 83 ++++++++++ internal/pkg/usecase/pin/like_test.go | 44 ----- internal/pkg/usecase/pin/update_test.go | 48 ++++++ internal/pkg/usecase/pin/usecase_test.go | 199 +++++++++++++++++++++++ 6 files changed, 330 insertions(+), 47 deletions(-) create mode 100644 internal/pkg/usecase/pin/check_test.go create mode 100644 internal/pkg/usecase/pin/update_test.go create mode 100644 internal/pkg/usecase/pin/usecase_test.go diff --git a/go.mod b/go.mod index e3101cb..bc70b6b 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,6 @@ require ( github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/golang/mock v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect diff --git a/go.sum b/go.sum index f31e129..af80b91 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= diff --git a/internal/pkg/usecase/pin/check_test.go b/internal/pkg/usecase/pin/check_test.go new file mode 100644 index 0000000..460987b --- /dev/null +++ b/internal/pkg/usecase/pin/check_test.go @@ -0,0 +1,83 @@ +package pin + +import ( + "context" + "testing" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/golang/mock/gomock" + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/require" +) + +func TestIsAvailableBatchPinForFixOnBoard(t *testing.T) { + testCases := []struct { + Name string + Pins []entity.Pin + UserID int + WantErr error + }{ + { + Name: "one deleted pin", + Pins: []entity.Pin{{DeletedAt: pgtype.Timestamptz{Valid: true}}}, + UserID: 12, + WantErr: ErrPinDeleted, + }, + { + Name: "all available", + Pins: []entity.Pin{ + {Author: &user.User{ID: 34}, DeletedAt: pgtype.Timestamptz{Valid: false}, Public: true}, + {Author: &user.User{ID: 12}, Public: false}, + {Author: &user.User{ID: 34}, Public: true}, + }, + UserID: 12, + WantErr: nil, + }, + { + Name: "one not available", + Pins: []entity.Pin{ + {Author: &user.User{ID: 34}, DeletedAt: pgtype.Timestamptz{Valid: false}, Public: true}, + {Author: &user.User{ID: 12}, Public: false}, + {Author: &user.User{ID: 34}, Public: false}, + }, + UserID: 12, + WantErr: ErrForbiddenAction, + }, + } + for _, test := range testCases { + t.Run(test.Name, func(t *testing.T) { + test := test + t.Parallel() + err := isAvailableBatchPinForFixOnBoard(test.UserID, test.Pins...) + require.Equal(t, test.WantErr, err) + }) + } +} + +func TestIsAvailablePinForFixOnBoard(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + pinID, userID := 123, 1 + pin := &entity.Pin{Author: &user.User{ID: userID}, DeletedAt: pgtype.Timestamptz{Valid: true}} + + repo.EXPECT(). + GetPinByID(ctx, pinID, false). + Return(pin, nil). + Times(1) + + err = pinCase.IsAvailablePinForFixOnBoard(ctx, pinID, userID) + require.Equal(t, ErrPinDeleted, err) +} diff --git a/internal/pkg/usecase/pin/like_test.go b/internal/pkg/usecase/pin/like_test.go index 2eb0a8e..0718227 100644 --- a/internal/pkg/usecase/pin/like_test.go +++ b/internal/pkg/usecase/pin/like_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/golang/mock/gomock" - "github.com/jackc/pgx/v5/pgtype" "github.com/stretchr/testify/require" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" @@ -15,49 +14,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -func TestIsAvailableBatchPinForFixOnBoard(t *testing.T) { - testCases := []struct { - Name string - Pins []entity.Pin - UserID int - WantErr error - }{ - { - Name: "one deleted pin", - Pins: []entity.Pin{{DeletedAt: pgtype.Timestamptz{Valid: true}}}, - UserID: 12, - WantErr: ErrPinDeleted, - }, - { - Name: "all available", - Pins: []entity.Pin{ - {Author: &user.User{ID: 34}, DeletedAt: pgtype.Timestamptz{Valid: false}, Public: true}, - {Author: &user.User{ID: 12}, Public: false}, - {Author: &user.User{ID: 34}, Public: true}, - }, - UserID: 12, - WantErr: nil, - }, - { - Name: "one not available", - Pins: []entity.Pin{ - {Author: &user.User{ID: 34}, DeletedAt: pgtype.Timestamptz{Valid: false}, Public: true}, - {Author: &user.User{ID: 12}, Public: false}, - {Author: &user.User{ID: 34}, Public: false}, - }, - UserID: 12, - WantErr: ErrForbiddenAction, - }, - } - for _, test := range testCases { - t.Run(test.Name, func(t *testing.T) { - test := test - t.Parallel() - err := isAvailableBatchPinForFixOnBoard(test.UserID, test.Pins...) - require.Equal(t, test.WantErr, err) - }) - } -} func TestSetLikeOnAvailablePin(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/internal/pkg/usecase/pin/update_test.go b/internal/pkg/usecase/pin/update_test.go new file mode 100644 index 0000000..b674394 --- /dev/null +++ b/internal/pkg/usecase/pin/update_test.go @@ -0,0 +1,48 @@ +package pin + +import ( + "context" + "testing" + + repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func TestEditPinByID(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + pinID, userID := 123, 1 + tags := []string{"new", "tag"} + + updateData := &PinUpdateData{ + Title: new(string), + Description: new(string), + Tags: tags, + Public: new(bool), + } + + repo.EXPECT(). + EditPin(ctx, pinID, repository.S{ + "title": "", + "description": "", + "public": false, + }, tags). + Return(nil). + Times(1) + + err = pinCase.EditPinByID(ctx, pinID, userID, updateData) + require.NoError(t, err) +} diff --git a/internal/pkg/usecase/pin/usecase_test.go b/internal/pkg/usecase/pin/usecase_test.go new file mode 100644 index 0000000..8a07ef3 --- /dev/null +++ b/internal/pkg/usecase/pin/usecase_test.go @@ -0,0 +1,199 @@ +package pin + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/require" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin/mock" + mockImage "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image/mock" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func TestSelectNewPins(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + count, minID, maxID := 3, 2, 7 + wantPins := []entity.Pin{{ID: 9}, {ID: 8}, {ID: 1}} + wantMin, wantMax := 1, 9 + + repo.EXPECT(). + GetSortedNewNPins(ctx, count, minID, maxID). + Return(wantPins, nil). + Times(1) + + actualPins, actualMin, actualMax := pinCase.SelectNewPins(ctx, count, minID, maxID) + require.Equal(t, wantMax, actualMax) + require.Equal(t, wantMin, actualMin) + require.Equal(t, wantPins, actualPins) + + count, minID, maxID = 10, 55, 60 + wantPins = []entity.Pin{} + wantMin, wantMax = 55, 60 + + repo.EXPECT(). + GetSortedNewNPins(ctx, count, minID, maxID). + Return(wantPins, nil). + Times(1) + + actualPins, actualMin, actualMax = pinCase.SelectNewPins(ctx, count, minID, maxID) + require.Equal(t, wantMax, actualMax) + require.Equal(t, wantMin, actualMin) + require.Equal(t, wantPins, actualPins) +} + +func TestSelectUserPins(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + count, minID, maxID := 3, 2, 7 + wantPins := []entity.Pin{{ID: 9}, {ID: 8}, {ID: 1}} + wantMin, wantMax := 1, 9 + userID := 16 + + repo.EXPECT(). + GetSortedUserPins(ctx, userID, count, minID, maxID). + Return(wantPins, nil). + Times(1) + + actualPins, actualMin, actualMax := pinCase.SelectUserPins(ctx, userID, count, minID, maxID) + require.Equal(t, wantMax, actualMax) + require.Equal(t, wantMin, actualMin) + require.Equal(t, wantPins, actualPins) + + count, minID, maxID = 10, 55, 60 + wantPins = []entity.Pin{} + wantMin, wantMax = 55, 60 + userID = 16 + + repo.EXPECT(). + GetSortedUserPins(ctx, userID, count, minID, maxID). + Return(wantPins, nil). + Times(1) + + actualPins, actualMin, actualMax = pinCase.SelectUserPins(ctx, userID, count, minID, maxID) + require.Equal(t, wantMax, actualMax) + require.Equal(t, wantMin, actualMin) + require.Equal(t, wantPins, actualPins) +} + +func TestCreateNewPin(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + imgCase := mockImage.NewMockUsecase(ctrl) + pinCase := New(log, imgCase, repo) + mimeType, size := "image/webp", int64(45) + filename := "filename.webp" + pin := &entity.Pin{ + ID: 34, + } + + imgCase.EXPECT(). + UploadImage("pins/", mimeType, size, nil, gomock.Any()). + Return(filename, nil). + Times(1) + + pin.Picture = filename + repo.EXPECT(). + AddNewPin(ctx, pin). + Return(nil). + Times(1) + + err = pinCase.CreateNewPin(ctx, pin, "image/webp", size, nil) + require.NoError(t, err) +} + +func TestDeletePinFromUser(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + pinID, userID := 8, 16 + + wantErr := errors.New("returned err") + repo.EXPECT(). + DeletePin(ctx, pinID, userID). + Return(wantErr). + Times(1) + + actualErr := pinCase.DeletePinFromUser(ctx, pinID, userID) + require.Equal(t, wantErr, actualErr) +} + +func TestViewAnPin(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + log, err := logger.New() + if err != nil { + t.Fatal(err) + } + + repo := mock.NewMockRepository(ctrl) + pinCase := New(log, nil, repo) + pinID, userID := 44, 90 + countLike := 22 + tags := []entity.Tag{{Title: "good"}, {Title: "home"}} + wantPin := entity.Pin{ + Title: pgtype.Text{String: "someone else 's public pin", Valid: true}, + Author: &user.User{ID: 100}, + Public: true, + } + actualPin := new(entity.Pin) + *actualPin = wantPin + + repo.EXPECT().GetPinByID(ctx, pinID, true).Return(actualPin, nil).Times(1) + repo.EXPECT().GetCountLikeByPinID(ctx, pinID).Return(countLike, nil).Times(1) + repo.EXPECT().GetTagsByPinID(ctx, pinID).Return(tags, nil).Times(1) + + wantPin.CountLike = countLike + wantPin.Tags = tags + + actualPin, actualErr := pinCase.ViewAnPin(ctx, pinID, userID) + require.NoError(t, actualErr) + require.Equal(t, wantPin, *actualPin) +} From 1c06f9bab6157d3aedcf92622a8a92d939e3e921 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 10 Nov 2023 15:15:26 +0300 Subject: [PATCH 157/266] TP-ffc_handlers_board_fix update: add types.go with common content types, defined general errors, add error-code mapping --- internal/pkg/delivery/http/v1/response.go | 19 +++++++++++++------ internal/pkg/delivery/http/v1/types.go | 6 ++++++ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/types.go diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index 1b69af1..ba0b3bd 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -2,20 +2,27 @@ package v1 import ( "encoding/json" + "errors" "fmt" "net/http" ) var ( - InternalServerErrMessage = "internal server error occured" - BadBodyMessage = "can't parse body, JSON expected" - BadQueryParamMessage = "invalid query parameters have been provided" + ErrBadBody = errors.New("can't parse body, JSON with correct data types is expected") + ErrBadUrlParam = errors.New("bad URL param has been provided") + ErrBadQueryParam = errors.New("invalid query parameters have been provided") + ErrInternalError = errors.New("internal server error occured") + ErrBadContentType = errors.New("application/json is expected") ) var ( - BadBodyCode = "bad_body" - BadQueryParamCode = "bad_queryParam" - InternalErrorCode = "internal_error" + generalErrCodeCompability = map[error]string{ + ErrBadBody: "bad_body", + ErrBadQueryParam: "bad_queryParams", + ErrInternalError: "internal_error", + ErrBadContentType: "bad_contentType", + ErrBadUrlParam: "bad_urlParam", + } ) type JsonResponse struct { diff --git a/internal/pkg/delivery/http/v1/types.go b/internal/pkg/delivery/http/v1/types.go new file mode 100644 index 0000000..56125b9 --- /dev/null +++ b/internal/pkg/delivery/http/v1/types.go @@ -0,0 +1,6 @@ +package v1 + +var ( + UrlEncodedContentType = "application/x-www-form-urlencoded" + ApplicationJson = "application/json" +) From 7de8fb1b603af6136689023f6d6aa7522f353317 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 10 Nov 2023 15:18:43 +0300 Subject: [PATCH 158/266] TP-ffc_handlers_board_fix update: fixed tags assignment, add GetBoardInfoForUpdade method --- .../pkg/repository/board/mock/board_mock.go | 16 ++++++ .../pkg/repository/board/postgres/queries.go | 1 + .../pkg/repository/board/postgres/repo.go | 54 +++++++++++++++++-- internal/pkg/repository/board/postgres/tag.go | 20 +++++++ internal/pkg/repository/board/repo.go | 1 + 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/internal/pkg/repository/board/mock/board_mock.go b/internal/pkg/repository/board/mock/board_mock.go index 6e77688..61f2844 100644 --- a/internal/pkg/repository/board/mock/board_mock.go +++ b/internal/pkg/repository/board/mock/board_mock.go @@ -111,6 +111,22 @@ func (mr *MockRepositoryMockRecorder) GetBoardByID(ctx, boardID, hasAccess inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardByID", reflect.TypeOf((*MockRepository)(nil).GetBoardByID), ctx, boardID, hasAccess) } +// GetBoardInfoForUpdate mocks base method. +func (m *MockRepository) GetBoardInfoForUpdate(ctx context.Context, boardID int, hasAccess bool) (board.Board, []string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardInfoForUpdate", ctx, boardID, hasAccess) + ret0, _ := ret[0].(board.Board) + ret1, _ := ret[1].([]string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetBoardInfoForUpdate indicates an expected call of GetBoardInfoForUpdate. +func (mr *MockRepositoryMockRecorder) GetBoardInfoForUpdate(ctx, boardID, hasAccess interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardInfoForUpdate", reflect.TypeOf((*MockRepository)(nil).GetBoardInfoForUpdate), ctx, boardID, hasAccess) +} + // GetBoardsByUserID mocks base method. func (m *MockRepository) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]board1.UserBoard, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index 00b8592..ec2964c 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -7,6 +7,7 @@ const ( UpdateBoardByIdQuery = "UPDATE board SET title = $1, description = $2, public = $3 WHERE id = $4 AND deleted_at IS NULL;" GetContributorBoardsIDs = "SELECT board_id FROM contributor WHERE user_id = $1;" DeleteBoardByIdQuery = "UPDATE board SET deleted_at = $1 WHERE id = $2;" + DeleteCurrentBoardTags = "DELETE FROM board_tag WHERE board_id = $1;" SelectAuthorOrContributorRole = `SELECT board.author, role.name FROM board LEFT JOIN contributor ON contributor.board_id = board.id AND contributor.user_id = $1 LEFT JOIN role ON contributor.role_id = role.id diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 93542a1..dbf8a3b 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -59,12 +59,16 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, Select( "board.id", "board.title", + "COALESCE(board.description, '')", "TO_CHAR(board.created_at, 'DD:MM:YYYY')", "COUNT(pin.id) AS pins_number", - "ARRAY_REMOVE((ARRAY_AGG(pin.picture))[:3], NULL) AS pins"). + "ARRAY_REMOVE((ARRAY_AGG(DISTINCT pin.picture))[:3], NULL) AS pins", + "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). From("board"). LeftJoin("membership ON board.id = membership.board_id"). LeftJoin("pin ON membership.pin_id = pin.id"). + LeftJoin("board_tag ON board.id = board_tag.board_id"). + LeftJoin("tag ON board_tag.tag_id = tag.id"). Where(squirrel.Eq{"board.deleted_at": nil}). Where(squirrel.Eq{"board.author": userID}) @@ -80,6 +84,7 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, GroupBy( "board.id", "board.title", + "board.description", "board.created_at", ). OrderBy("board.id ASC") @@ -98,7 +103,7 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, boards := make([]dto.UserBoard, 0) for rows.Next() { board := dto.UserBoard{} - err = rows.Scan(&board.BoardID, &board.Title, &board.CreatedAt, &board.PinsNumber, &board.Pins) + err = rows.Scan(&board.BoardID, &board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) if err != nil { return nil, fmt.Errorf("scanning the result of get boards by user id query: %w", err) } @@ -128,7 +133,6 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces if !hasAccess { getBoardByIdQuery = getBoardByIdQuery.Where(squirrel.Eq{"board.public": true}) - } getBoardByIdQuery = getBoardByIdQuery.GroupBy( "board.id", @@ -157,6 +161,50 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces return board, nil } +func (repo *boardRepoPG) GetBoardInfoForUpdate(ctx context.Context, boardID int, hasAccess bool) (entity.Board, []string, error) { + getBoardByIdQuery := repo.sqlBuilder. + Select( + "board.title", + "COALESCE(board.description, '')", + "board.public", + "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). + From("board"). + LeftJoin("board_tag ON board.id = board_tag.board_id"). + LeftJoin("tag ON board_tag.tag_id = tag.id"). + Where(squirrel.Eq{"board.deleted_at": nil}). + Where(squirrel.Eq{"board.id": boardID}) + + if !hasAccess { + getBoardByIdQuery = getBoardByIdQuery.Where(squirrel.Eq{"board.public": true}) + } + getBoardByIdQuery = getBoardByIdQuery.GroupBy( + "board.id", + "board.title", + "board.description", + "board.created_at"). + OrderBy("board.id ASC") + + sqlRow, args, err := getBoardByIdQuery.ToSql() + if err != nil { + return entity.Board{}, nil, fmt.Errorf("building get board info for update query: %w", err) + } + + row := repo.db.QueryRow(ctx, sqlRow, args...) + board := entity.Board{} + tagTitles := make([]string, 0) + err = row.Scan(&board.Title, &board.Description, &board.Public, &tagTitles) + if err != nil { + switch err { + case pgx.ErrNoRows: + return entity.Board{}, nil, repository.ErrNoData + default: + return entity.Board{}, nil, fmt.Errorf("scan result of get board by id query: %w", err) + } + } + + return board, tagTitles, nil +} + func (repo *boardRepoPG) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { row := repo.db.QueryRow(ctx, SelectBoardAuthorByBoardIdQuery, boardID) var authorID int diff --git a/internal/pkg/repository/board/postgres/tag.go b/internal/pkg/repository/board/postgres/tag.go index 35d6188..8e208ee 100644 --- a/internal/pkg/repository/board/postgres/tag.go +++ b/internal/pkg/repository/board/postgres/tag.go @@ -10,6 +10,10 @@ import ( ) func (repo *boardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []string) error { + if len(titles) == 0 { + return nil + } + insertTagsQuery := repo.sqlBuilder. Insert("tag"). Columns("title") @@ -31,6 +35,13 @@ func (repo *boardRepoPG) insertTags(ctx context.Context, tx pgx.Tx, titles []str } func (repo *boardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitles []string, boardID int, isNewBoard bool) error { + if !isNewBoard { + err := repo.deleteCurrentBoardTags(ctx, tx, boardID) + if err != nil { + return fmt.Errorf("add tags to board within transaction: %w", err) + } + } + addTagsToBoardQuery := repo.sqlBuilder. Insert("board_tag"). Columns("board_id", "tag_id"). @@ -60,3 +71,12 @@ func (repo *boardRepoPG) addTagsToBoard(ctx context.Context, tx pgx.Tx, tagTitle return nil } + +func (repo *boardRepoPG) deleteCurrentBoardTags(ctx context.Context, tx pgx.Tx, boardID int) error { + _, err := tx.Exec(ctx, DeleteCurrentBoardTags, boardID) + if err != nil { + return fmt.Errorf("deleting current board tags: %w", err) + } + + return nil +} diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 6a49903..9169fd6 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -16,6 +16,7 @@ type Repository interface { GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) + GetBoardInfoForUpdate(ctx context.Context, boardID int, hasAccess bool) (entity.Board, []string, error) UpdateBoard(ctx context.Context, newBoardData entity.Board, tagTitles []string) error DeleteBoardByID(ctx context.Context, boardID int) error RoleUserHaveOnThisBoard(ctx context.Context, boardID int, userID int) (UserRole, error) From 68e1b123eb9aee5e5e2b7e05417b9069686371fc Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 10 Nov 2023 16:03:41 +0300 Subject: [PATCH 159/266] TP-ffc_handlers_board_fix update: moved validation to the delivery layer, add Sanitize() method for entity, usecase structures, add GetBoardInfoForUpdate() method, regenerate mocks --- internal/pkg/entity/board/board.go | 27 ++-- internal/pkg/usecase/board/create.go | 7 -- internal/pkg/usecase/board/dto/board.go | 30 +++-- internal/pkg/usecase/board/errors.go | 10 +- internal/pkg/usecase/board/get.go | 10 +- internal/pkg/usecase/board/mock/board_mock.go | 31 +++-- internal/pkg/usecase/board/update.go | 54 ++++++-- internal/pkg/usecase/board/usecase.go | 2 + internal/pkg/usecase/board/usecase_test.go | 115 ------------------ internal/pkg/usecase/board/validation.go | 63 ---------- 10 files changed, 119 insertions(+), 230 deletions(-) delete mode 100644 internal/pkg/usecase/board/validation.go diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go index b3bceaf..b511134 100644 --- a/internal/pkg/entity/board/board.go +++ b/internal/pkg/entity/board/board.go @@ -1,14 +1,23 @@ package board -import "time" +import ( + "time" + + "github.com/microcosm-cc/bluemonday" +) type Board struct { - ID int - AuthorID int - Title string - Description string - Public bool - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt time.Time + ID int `json:"id,omitempty" example:"15"` + AuthorID int `json:"-"` + Title string `json:"title" example:"Sunny places"` + Description string `json:"description" example:"Sunny places desc"` + Public bool `json:"public" example:"true"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt time.Time `json:"-"` +} + +func (board *Board) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(board.Title) + sanitizer.Sanitize(board.Description) } diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go index 5536057..8a12dc1 100644 --- a/internal/pkg/usecase/board/create.go +++ b/internal/pkg/usecase/board/create.go @@ -9,13 +9,6 @@ import ( ) func (bCase *boardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) { - if !bCase.isValidBoardTitle(newBoard.Title) { - return 0, ErrInvalidBoardTitle - } - if err := bCase.checkIsValidTagTitles(newBoard.TagTitles); err != nil { - return 0, fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) - } - bCase.sanitizer.Sanitize(newBoard.Description) newBoardID, err := bCase.boardRepo.CreateBoard(ctx, entity.Board{ AuthorID: newBoard.AuthorID, diff --git a/internal/pkg/usecase/board/dto/board.go b/internal/pkg/usecase/board/dto/board.go index 6eb8821..06e7b42 100644 --- a/internal/pkg/usecase/board/dto/board.go +++ b/internal/pkg/usecase/board/dto/board.go @@ -1,20 +1,30 @@ package board +import "github.com/microcosm-cc/bluemonday" + type BoardData struct { - ID int `json:"board_id,omitempty" example:"33"` - Title string `json:"title" example:"Sunny places"` - Description string `json:"description" example:"long description"` - AuthorID int `json:"author_id,omitempty" example:"45"` - Public bool `json:"public" example:"true"` - TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` -} //@name NewBoardData + ID int + Title string + Description string + AuthorID int + Public bool + TagTitles []string +} type UserBoard struct { BoardID int `json:"board_id" example:"15"` Title string `json:"title" example:"Sunny places"` - Description string `json:"description,omitempty" example:"Sunny places"` + Description string `json:"description" example:"Sunny places"` CreatedAt string `json:"created_at" example:"08.10.2020"` PinsNumber int `json:"pins_number" example:"10"` Pins []string `json:"pins" example:"['/upload/pin/pic1', '/upload/pin/pic2']"` - TagTitles []string `json:"tags,omitempty" example:"['flowers', 'sunrise']"` -} //@name UserBoard + TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` +} + +func (uBoard *UserBoard) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(uBoard.Title) + sanitizer.Sanitize(uBoard.Description) + for id, title := range uBoard.TagTitles { + uBoard.TagTitles[id] = sanitizer.Sanitize(title) + } +} diff --git a/internal/pkg/usecase/board/errors.go b/internal/pkg/usecase/board/errors.go index 5d4c86b..b241f3c 100644 --- a/internal/pkg/usecase/board/errors.go +++ b/internal/pkg/usecase/board/errors.go @@ -3,10 +3,8 @@ package board import "errors" var ( - ErrInvalidUsername = errors.New("invalid username has been provided or username doesn't exist") - ErrNoSuchBoard = errors.New("board is not accessable or doesn't exist") - ErrInvalidBoardTitle = errors.New("invalid or empty board title has been provided") - ErrInvalidTagTitles = errors.New("invalid tag titles have been provided") - ErrInvalidUserID = errors.New("invalid user id has been provided") - ErrNoAccess = errors.New("no access for this action") + ErrInvalidUsername = errors.New("username doesn't exist") + ErrNoSuchBoard = errors.New("board is not accessable or doesn't exist") + ErrInvalidUserID = errors.New("invalid user id has been provided") + ErrNoAccess = errors.New("no access for this action") ) diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index 27a2a06..dafd821 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -10,10 +10,6 @@ import ( ) func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) { - if !bCase.isValidUsername(username) { - return nil, ErrInvalidUsername - } - userID, err := bCase.userRepo.GetUserIdByUsername(ctx, username) if err != nil { switch err { @@ -40,6 +36,9 @@ func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username str return nil, fmt.Errorf("get boards by user id usecase: %w", err) } + for _, board := range boards { + board.Sanitize(bCase.sanitizer) + } return boards, nil } @@ -51,7 +50,7 @@ func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dt return dto.UserBoard{}, ErrNoSuchBoard default: return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) - } + } } boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) @@ -81,6 +80,7 @@ func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dt } } + board.Sanitize(bCase.sanitizer) return board, nil } diff --git a/internal/pkg/usecase/board/mock/board_mock.go b/internal/pkg/usecase/board/mock/board_mock.go index ca9e41e..16b224c 100644 --- a/internal/pkg/usecase/board/mock/board_mock.go +++ b/internal/pkg/usecase/board/mock/board_mock.go @@ -8,7 +8,8 @@ import ( context "context" reflect "reflect" - board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + board0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" gomock "github.com/golang/mock/gomock" ) @@ -36,7 +37,7 @@ func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { } // CreateNewBoard mocks base method. -func (m *MockUsecase) CreateNewBoard(ctx context.Context, newBoard board.BoardData) (int, error) { +func (m *MockUsecase) CreateNewBoard(ctx context.Context, newBoard board0.BoardData) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateNewBoard", ctx, newBoard) ret0, _ := ret[0].(int) @@ -78,11 +79,27 @@ func (mr *MockUsecaseMockRecorder) FixPinsOnBoard(ctx, boardID, pinIds, userID i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FixPinsOnBoard", reflect.TypeOf((*MockUsecase)(nil).FixPinsOnBoard), ctx, boardID, pinIds, userID) } +// GetBoardInfoForUpdate mocks base method. +func (m *MockUsecase) GetBoardInfoForUpdate(ctx context.Context, boardID int) (board.Board, []string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoardInfoForUpdate", ctx, boardID) + ret0, _ := ret[0].(board.Board) + ret1, _ := ret[1].([]string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetBoardInfoForUpdate indicates an expected call of GetBoardInfoForUpdate. +func (mr *MockUsecaseMockRecorder) GetBoardInfoForUpdate(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoardInfoForUpdate", reflect.TypeOf((*MockUsecase)(nil).GetBoardInfoForUpdate), ctx, boardID) +} + // GetBoardsByUsername mocks base method. -func (m *MockUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]board.UserBoard, error) { +func (m *MockUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]board0.UserBoard, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBoardsByUsername", ctx, username) - ret0, _ := ret[0].([]board.UserBoard) + ret0, _ := ret[0].([]board0.UserBoard) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -94,10 +111,10 @@ func (mr *MockUsecaseMockRecorder) GetBoardsByUsername(ctx, username interface{} } // GetCertainBoard mocks base method. -func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board.UserBoard, error) { +func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board0.UserBoard, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCertainBoard", ctx, boardID) - ret0, _ := ret[0].(board.UserBoard) + ret0, _ := ret[0].(board0.UserBoard) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -109,7 +126,7 @@ func (mr *MockUsecaseMockRecorder) GetCertainBoard(ctx, boardID interface{}) *go } // UpdateBoardInfo mocks base method. -func (m *MockUsecase) UpdateBoardInfo(ctx context.Context, updatedData board.BoardData) error { +func (m *MockUsecase) UpdateBoardInfo(ctx context.Context, updatedData board0.BoardData) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateBoardInfo", ctx, updatedData) ret0, _ := ret[0].(error) diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go index 877cf37..22e50e4 100644 --- a/internal/pkg/usecase/board/update.go +++ b/internal/pkg/usecase/board/update.go @@ -5,12 +5,58 @@ import ( "fmt" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" repoBoard "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) +func (bCase *boardUsecase) GetBoardInfoForUpdate(ctx context.Context, boardID int) (entity.Board, []string, error) { + boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) + if err != nil { + switch err { + case repository.ErrNoData: + return entity.Board{}, nil, ErrNoSuchBoard + default: + return entity.Board{}, nil, fmt.Errorf("get certain board info for update: %w", err) + } + } + + boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) + if err != nil { + return entity.Board{}, nil, fmt.Errorf("get certain board info for update: %w", err) + } + + boardContributorsIDs := make([]int, 0, len(boardContributors)) + + for _, contributor := range boardContributors { + boardContributorsIDs = append(boardContributorsIDs, contributor.ID) + } + + var hasAccess bool + currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) + if loggedIn && (currUserID == boardAuthorID || isContributor(boardContributorsIDs, currUserID)) { + hasAccess = true + } + + board, tagTitles, err := bCase.boardRepo.GetBoardInfoForUpdate(ctx, boardID, hasAccess) + if err != nil { + switch err { + case repository.ErrNoData: + return entity.Board{}, nil, ErrNoSuchBoard + default: + return entity.Board{}, nil, fmt.Errorf("get certain board: %w", err) + } + } + + board.Sanitize(bCase.sanitizer) + for id, title := range tagTitles { + tagTitles[id] = bCase.sanitizer.Sanitize(title) + } + return board, tagTitles, nil +} + func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, updatedData.ID) if err != nil { @@ -27,14 +73,6 @@ func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto. return ErrNoAccess } - if !bCase.isValidBoardTitle(updatedData.Title) { - return ErrInvalidBoardTitle - } - if err := bCase.checkIsValidTagTitles(updatedData.TagTitles); err != nil { - return fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) - } - bCase.sanitizer.Sanitize(updatedData.Description) - err = bCase.boardRepo.UpdateBoard(ctx, board.Board{ ID: updatedData.ID, Title: updatedData.Title, diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index f4ce46e..06d273d 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -4,6 +4,7 @@ import ( "context" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -15,6 +16,7 @@ type Usecase interface { CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) + GetBoardInfoForUpdate(ctx context.Context, boardID int) (entity.Board, []string, error) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error DeleteCertainBoard(ctx context.Context, boardID int) error FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index f635f5f..ab9dd89 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -2,7 +2,6 @@ package board import ( "context" - "fmt" "testing" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" @@ -65,70 +64,6 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { }, expNewID: 1, }, - { - name: "invalid board title", - inCtx: context.Background(), - newBoardData: dto.BoardData{ - Title: "~nval$d title~~", - Description: "some description", - AuthorID: 45, - Public: false, - TagTitles: []string{"nice", "green"}, - }, - CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { - }, - expNewID: 0, - wantErr: true, - expErr: ErrInvalidBoardTitle, - }, - { - name: "invalid tag titles: all tags", - inCtx: context.Background(), - newBoardData: dto.BoardData{ - Title: "valid title", - Description: "some description", - AuthorID: 45, - Public: false, - TagTitles: []string{"nic~e", "gr~$een"}, - }, - CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { - }, - expNewID: 0, - wantErr: true, - expErr: fmt.Errorf("%v: %w", []string{"nic~e", "gr~$een"}, ErrInvalidTagTitles), - }, - { - name: "invalid tag titles: some tags", - inCtx: context.Background(), - newBoardData: dto.BoardData{ - Title: "valid title", - Description: "some description", - AuthorID: 45, - Public: false, - TagTitles: []string{"nic~e", "green"}, - }, - CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { - }, - expNewID: 0, - wantErr: true, - expErr: fmt.Errorf("%v: %w", []string{"nic~e"}, ErrInvalidTagTitles), - }, - { - name: "invalid tag titles: too many tags", - inCtx: context.Background(), - newBoardData: dto.BoardData{ - Title: "valid title", - Description: "some description", - AuthorID: 45, - Public: false, - TagTitles: []string{"nice", "green", "a", "b", "c", "d", "e", "f"}, - }, - CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { - }, - expNewID: 0, - wantErr: true, - expErr: fmt.Errorf("too many titles: %w", ErrInvalidTagTitles), - }, } for _, test := range tests { @@ -244,42 +179,6 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { wantErr: true, expErr: ErrNoSuchBoard, }, - { - name: "invalid board title", - inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), - updatedBoardData: dto.BoardData{ - ID: 1, - Title: "va!@#*^*!&@$*lid title", - Description: "some description", - Public: false, - TagTitles: []string{"nice", "green"}, - }, - GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { - mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) - }, - UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { - }, - wantErr: true, - expErr: ErrInvalidBoardTitle, - }, - { - name: "invalid board tags", - inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), - updatedBoardData: dto.BoardData{ - ID: 11, - Title: "valid title", - Description: "some description", - Public: false, - TagTitles: []string{"ni@#@#%!~~ce", "green"}, - }, - GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { - mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) - }, - UpdateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { - }, - wantErr: true, - expErr: fmt.Errorf("%v: %w", []string{"ni@#@#%!~~ce"}, ErrInvalidTagTitles), - }, } for _, test := range tests { @@ -387,20 +286,6 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { wantErr: true, expErr: ErrInvalidUsername, }, - { - name: "invalid username", - inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), - username: "A$va@$@!%@~~~~~~uy", - GetUserIdByUsername: func(mockRepo *mock_user.MockRepository, ctx context.Context, username string) { - }, - GetContributorBoardsIDs: func(mockRepo *mock_board.MockRepository, ctx context.Context, contributorID int) { - }, - GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { - }, - expBoards: nil, - wantErr: true, - expErr: ErrInvalidUsername, - }, } for _, test := range tests { diff --git a/internal/pkg/usecase/board/validation.go b/internal/pkg/usecase/board/validation.go deleted file mode 100644 index c3df21e..0000000 --- a/internal/pkg/usecase/board/validation.go +++ /dev/null @@ -1,63 +0,0 @@ -package board - -import ( - "fmt" - "unicode" -) - -func (bCase *boardUsecase) isValidTagTitle(title string) bool { - if len(title) > 20 { - return false - } - - for _, sym := range title { - if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { - return false - } - } - return true -} - -func (bCase *boardUsecase) checkIsValidTagTitles(titles []string) error { - if len(titles) > 7 { - return fmt.Errorf("too many titles") - } - - invalidTitles := make([]string, 0) - for _, title := range titles { - if !bCase.isValidTagTitle(title) { - invalidTitles = append(invalidTitles, title) - } - } - if len(invalidTitles) > 0 { - return fmt.Errorf("%v", invalidTitles) - } - return nil -} - -func (bCase *boardUsecase) isValidBoardTitle(title string) bool { - if len(title) == 0 || len(title) > 40 { - return false - } - for _, sym := range title { - if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { - return false - } - } - bCase.sanitizer.Sanitize(title) - return true -} - -func (bCase *boardUsecase) isValidUsername(username string) bool { - if len(username) < 4 || len(username) > 50 { - return false - } - for _, r := range username { - if !(unicode.IsNumber(r) || unicode.IsLetter(r)) { - return false - } - } - bCase.sanitizer.Sanitize(username) - - return true -} From 7e5938bdd5c433be2dbec59f541904978c6a2c5c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 10 Nov 2023 16:04:34 +0300 Subject: [PATCH 160/266] TP-ffc_handlers_board_fix update: regenerate mocks --- .../pkg/repository/user/mock/user_mock.go | 15 - internal/pkg/usecase/pin/mock/pin_mock.go | 384 +++++++++--------- 2 files changed, 192 insertions(+), 207 deletions(-) diff --git a/internal/pkg/repository/user/mock/user_mock.go b/internal/pkg/repository/user/mock/user_mock.go index 99b06b9..3fae4f7 100644 --- a/internal/pkg/repository/user/mock/user_mock.go +++ b/internal/pkg/repository/user/mock/user_mock.go @@ -93,21 +93,6 @@ func (mr *MockRepositoryMockRecorder) GetAllUserData(ctx, userID interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllUserData", reflect.TypeOf((*MockRepository)(nil).GetAllUserData), ctx, userID) } -// GetLastUserID mocks base method. -func (m *MockRepository) GetLastUserID(ctx context.Context) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLastUserID", ctx) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLastUserID indicates an expected call of GetLastUserID. -func (mr *MockRepositoryMockRecorder) GetLastUserID(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastUserID", reflect.TypeOf((*MockRepository)(nil).GetLastUserID), ctx) -} - // GetUserByUsername mocks base method. func (m *MockRepository) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/usecase/pin/mock/pin_mock.go b/internal/pkg/usecase/pin/mock/pin_mock.go index 822020d..548cac5 100644 --- a/internal/pkg/usecase/pin/mock/pin_mock.go +++ b/internal/pkg/usecase/pin/mock/pin_mock.go @@ -4,195 +4,195 @@ // Package mock is a generated GoMock package. package mock -// import ( -// context "context" -// io "io" -// reflect "reflect" - -// pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" -// gomock "github.com/golang/mock/gomock" -// ) - -// // MockUsecase is a mock of Usecase interface. -// type MockUsecase struct { -// ctrl *gomock.Controller -// recorder *MockUsecaseMockRecorder -// } - -// // MockUsecaseMockRecorder is the mock recorder for MockUsecase. -// type MockUsecaseMockRecorder struct { -// mock *MockUsecase -// } - -// // NewMockUsecase creates a new mock instance. -// func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { -// mock := &MockUsecase{ctrl: ctrl} -// mock.recorder = &MockUsecaseMockRecorder{mock} -// return mock -// } - -// // EXPECT returns an object that allows the caller to indicate expected use. -// func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { -// return m.recorder -// } - -// // CheckUserHasSetLike mocks base method. -// func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) -// ret0, _ := ret[0].(bool) -// ret1, _ := ret[1].(error) -// return ret0, ret1 -// } - -// // CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. -// func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) -// } - -// // CreateNewPin mocks base method. -// func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // CreateNewPin indicates an expected call of CreateNewPin. -// func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) -// } - -// // DeleteLikeFromUser mocks base method. -// func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. -// func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) -// } - -// // DeletePinFromUser mocks base method. -// func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // DeletePinFromUser indicates an expected call of DeletePinFromUser. -// func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) -// } - -// // EditPinByID mocks base method. -// func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // EditPinByID indicates an expected call of EditPinByID. -// func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) -// } - -// // IsAvailableBatchPinForFixOnBoard mocks base method. -// func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. -// func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) -// } - -// // IsAvailablePinForFixOnBoard mocks base method. -// func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) -// ret0, _ := ret[0].(error) -// return ret0 -// } - -// // IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. -// func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) -// } - -// // SelectNewPins mocks base method. -// func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) -// ret0, _ := ret[0].([]pin.Pin) -// ret1, _ := ret[1].(int) -// ret2, _ := ret[2].(int) -// return ret0, ret1, ret2 -// } - -// // SelectNewPins indicates an expected call of SelectNewPins. -// func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) -// } - -// // SelectUserPins mocks base method. -// func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) -// ret0, _ := ret[0].([]pin.Pin) -// ret1, _ := ret[1].(int) -// ret2, _ := ret[2].(int) -// return ret0, ret1, ret2 -// } - -// // SelectUserPins indicates an expected call of SelectUserPins. -// func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) -// } - -// // SetLikeFromUser mocks base method. -// func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) -// ret0, _ := ret[0].(int) -// ret1, _ := ret[1].(error) -// return ret0, ret1 -// } - -// // SetLikeFromUser indicates an expected call of SetLikeFromUser. -// func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) -// } - -// // ViewAnPin mocks base method. -// func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { -// m.ctrl.T.Helper() -// ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) -// ret0, _ := ret[0].(*pin.Pin) -// ret1, _ := ret[1].(error) -// return ret0, ret1 -// } - -// // ViewAnPin indicates an expected call of ViewAnPin. -// func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { -// mr.mock.ctrl.T.Helper() -// return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) -// } +import ( + context "context" + io "io" + reflect "reflect" + + pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// CheckUserHasSetLike mocks base method. +func (m *MockUsecase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckUserHasSetLike", ctx, pinID, userID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckUserHasSetLike indicates an expected call of CheckUserHasSetLike. +func (mr *MockUsecaseMockRecorder) CheckUserHasSetLike(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckUserHasSetLike", reflect.TypeOf((*MockUsecase)(nil).CheckUserHasSetLike), ctx, pinID, userID) +} + +// CreateNewPin mocks base method. +func (m *MockUsecase) CreateNewPin(ctx context.Context, pin *pin.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNewPin", ctx, pin, mimeTypePicture, sizePicture, picture) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNewPin indicates an expected call of CreateNewPin. +func (mr *MockUsecaseMockRecorder) CreateNewPin(ctx, pin, mimeTypePicture, sizePicture, picture interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewPin", reflect.TypeOf((*MockUsecase)(nil).CreateNewPin), ctx, pin, mimeTypePicture, sizePicture, picture) +} + +// DeleteLikeFromUser mocks base method. +func (m *MockUsecase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLikeFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLikeFromUser indicates an expected call of DeleteLikeFromUser. +func (mr *MockUsecaseMockRecorder) DeleteLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).DeleteLikeFromUser), ctx, pinID, userID) +} + +// DeletePinFromUser mocks base method. +func (m *MockUsecase) DeletePinFromUser(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePinFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePinFromUser indicates an expected call of DeletePinFromUser. +func (mr *MockUsecaseMockRecorder) DeletePinFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromUser", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromUser), ctx, pinID, userID) +} + +// EditPinByID mocks base method. +func (m *MockUsecase) EditPinByID(ctx context.Context, pinID, userID int, updateData *pinUpdateData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditPinByID", ctx, pinID, userID, updateData) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditPinByID indicates an expected call of EditPinByID. +func (mr *MockUsecaseMockRecorder) EditPinByID(ctx, pinID, userID, updateData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditPinByID", reflect.TypeOf((*MockUsecase)(nil).EditPinByID), ctx, pinID, userID, updateData) +} + +// IsAvailableBatchPinForFixOnBoard mocks base method. +func (m *MockUsecase) IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailableBatchPinForFixOnBoard", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailableBatchPinForFixOnBoard indicates an expected call of IsAvailableBatchPinForFixOnBoard. +func (mr *MockUsecaseMockRecorder) IsAvailableBatchPinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailableBatchPinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailableBatchPinForFixOnBoard), ctx, pinID, userID) +} + +// IsAvailablePinForFixOnBoard mocks base method. +func (m *MockUsecase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailablePinForFixOnBoard", ctx, pinID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailablePinForFixOnBoard indicates an expected call of IsAvailablePinForFixOnBoard. +func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) +} + +// SelectNewPins mocks base method. +func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(int) + return ret0, ret1, ret2 +} + +// SelectNewPins indicates an expected call of SelectNewPins. +func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) +} + +// SelectUserPins mocks base method. +func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) + ret0, _ := ret[0].([]pin.Pin) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(int) + return ret0, ret1, ret2 +} + +// SelectUserPins indicates an expected call of SelectUserPins. +func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) +} + +// SetLikeFromUser mocks base method. +func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLikeFromUser", ctx, pinID, userID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetLikeFromUser indicates an expected call of SetLikeFromUser. +func (mr *MockUsecaseMockRecorder) SetLikeFromUser(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLikeFromUser", reflect.TypeOf((*MockUsecase)(nil).SetLikeFromUser), ctx, pinID, userID) +} + +// ViewAnPin mocks base method. +func (m *MockUsecase) ViewAnPin(ctx context.Context, pinID, userID int) (*pin.Pin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ViewAnPin", ctx, pinID, userID) + ret0, _ := ret[0].(*pin.Pin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ViewAnPin indicates an expected call of ViewAnPin. +func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) +} From 154279c11020455cb201f32840e7f9dc649fce7c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 10 Nov 2023 16:06:38 +0300 Subject: [PATCH 161/266] TP-ffc_handlers_board_fix update: add board validation, additional errors, err-code mapping,GetBoardInfoForUpdate() on the delivery layer --- internal/api/server/router/router.go | 1 + internal/pkg/delivery/http/v1/board.go | 263 +++++++++++++----- .../pkg/delivery/http/v1/board_validation.go | 48 ++++ 3 files changed, 244 insertions(+), 68 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/board_validation.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 0fcef89..c32fad4 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -81,6 +81,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Route("/get", func(r chi.Router) { r.Get("/user/{username}", handler.GetUserBoards) r.Get("/{boardID:\\d+}", handler.GetCertainBoard) + r.Get("/forUpdate/{boardID:\\d+}", handler.GetBoardInfoForUpdate) }) r.With(auth.RequireAuth).Group(func(r chi.Router) { r.Post("/add/pins/{boardID:\\d+}", handler.AddPinsToBoard) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index cec93a6..5d6eb83 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -3,6 +3,7 @@ package v1 import ( "encoding/json" "errors" + "fmt" "net/http" "strconv" @@ -14,33 +15,126 @@ import ( log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +var ( + ErrEmptyTitle = errors.New("empty or null board title has been provided") + ErrEmptyPubOpt = errors.New("null public option has been provided") + ErrInvalidBoardTitle = errors.New("invalid or empty board title has been provided") + ErrInvalidTagTitles = errors.New("invalid tag titles have been provided") + ErrInvalidUsername = errors.New("invalid username has been provided") +) + +var ( + wrappedErrors = map[error]string{ErrInvalidTagTitles: "bad_Tagtitles"} + errCodeCompability = map[error]string{ + ErrInvalidBoardTitle: "bad_boardTitle", + ErrEmptyTitle: "empty_boardTitle", + ErrEmptyPubOpt: "bad_pubOpt", + ErrInvalidUsername: "bad_username", + bCase.ErrInvalidUsername: "non_existingUser", + bCase.ErrNoSuchBoard: "no_board", + bCase.ErrNoAccess: "no_access", + } +) + +type BoardData struct { + Title *string `json:"title" example:"new board"` + Description *string `json:"description" example:"long desc"` + Public *bool `json:"public" example:"true"` + Tags []string `json:"tags" example:"['blue', 'car']"` +} + +func (data *BoardData) Validate() error { + if data.Title == nil || *data.Title == "" { + return ErrInvalidBoardTitle + } + if data.Description == nil { + data.Description = new(string) + *data.Description = "" + } + if data.Public == nil { + return ErrEmptyPubOpt + } + if !isValidBoardTitle(*data.Title) { + return ErrInvalidBoardTitle + } + if err := checkIsValidTagTitles(data.Tags); err != nil { + return fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) + } + return nil +} + +func getErrCodeMessage(err error) (string, string) { + var ( + code string + general, specific bool + ) + + code, general = generalErrCodeCompability[err] + if general { + return code, err.Error() + } + + code, specific = errCodeCompability[err] + if !specific { + for wrappedErr, code_ := range wrappedErrors { + if errors.Is(err, wrappedErr) { + specific = true + code = code_ + } + } + } + if specific { + return code, err.Error() + } + + return ErrInternalError.Error(), generalErrCodeCompability[ErrInternalError] +} + func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) + if contentType := w.Header().Get("Content-Type"); contentType != ApplicationJson { + code, message := getErrCodeMessage(ErrBadContentType) + responseError(w, code, message) + return + } - var newBoard boardDTO.BoardData - err := json.NewDecoder(r.Body).Decode(&newBoard) + var newBoardData BoardData + err := json.NewDecoder(r.Body).Decode(&newBoardData) defer r.Body.Close() if err != nil { logger.Info("create board", log.F{"message", err.Error()}) - responseError(w, BadBodyCode, BadBodyMessage) + code, message := getErrCodeMessage(ErrBadBody) + responseError(w, code, message) return } - newBoard.AuthorID = r.Context().Value(auth.KeyCurrentUserID).(int) + err = newBoardData.Validate() + if err != nil { + logger.Info("create board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(err) + responseError(w, code, message) + return + } + + tagTitles := make([]string, 0) + if newBoardData.Tags != nil { + tagTitles = append(tagTitles, newBoardData.Tags...) + + } + authorID := r.Context().Value(auth.KeyCurrentUserID).(int) + newBoard := boardDTO.BoardData{ + Title: *newBoardData.Title, + Description: *newBoardData.Description, + Public: *newBoardData.Public, + AuthorID: authorID, + TagTitles: tagTitles, + } + newBoardID, err := h.boardCase.CreateNewBoard(r.Context(), newBoard) if err != nil { logger.Info("create board", log.F{"message", err.Error()}) - switch err { - case bCase.ErrInvalidBoardTitle: - responseError(w, "bad_boardTitle", err.Error()) - default: - if errors.Is(err, bCase.ErrInvalidTagTitles) { - responseError(w, "bad_tagTitles", err.Error()) - return - } - responseError(w, InternalErrorCode, InternalServerErrMessage) - w.WriteHeader(http.StatusInternalServerError) - } + code, message := getErrCodeMessage(err) + responseError(w, code, message) return } @@ -48,23 +142,26 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(InternalServerErrMessage)) + w.Write([]byte(ErrInternalError.Error())) } } func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) - userBoards, err := h.boardCase.GetBoardsByUsername(r.Context(), chi.URLParam(r, "username")) + username := chi.URLParam(r, "username") + if !isValidUsername(username) { + logger.Info("update board", log.F{"message", ErrInvalidUsername.Error()}) + code, message := getErrCodeMessage(ErrInvalidUsername) + responseError(w, code, message) + return + } + + userBoards, err := h.boardCase.GetBoardsByUsername(r.Context(), username) if err != nil { logger.Info("get user boards", log.F{"message", err.Error()}) - switch err { - case bCase.ErrInvalidUsername: - responseError(w, "bad_username", err.Error()) - default: - responseError(w, InternalErrorCode, InternalServerErrMessage) - w.WriteHeader(http.StatusInternalServerError) - } + code, message := getErrCodeMessage(err) + responseError(w, code, message) return } @@ -72,7 +169,7 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(InternalServerErrMessage)) + w.Write([]byte(ErrInternalError.Error())) } } @@ -82,20 +179,16 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("get certain board", log.F{"message", err.Error()}) - responseError(w, BadQueryParamCode, BadQueryParamMessage) + code, message := getErrCodeMessage(ErrBadUrlParam) + responseError(w, code, message) return } board, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) if err != nil { logger.Info("get certain board", log.F{"message", err.Error()}) - switch err { - case bCase.ErrNoSuchBoard: - responseError(w, "no_board", err.Error()) - default: - responseError(w, InternalErrorCode, InternalServerErrMessage) - w.WriteHeader(http.StatusInternalServerError) - } + code, message := getErrCodeMessage(err) + responseError(w, code, message) return } @@ -103,48 +196,88 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(InternalServerErrMessage)) + w.Write([]byte(ErrInternalError.Error())) + } +} + +func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + + boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) + if err != nil { + logger.Info("get certain board info for update", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(ErrBadUrlParam) + responseError(w, code, message) + return + } + + board, tagTitles, err := h.boardCase.GetBoardInfoForUpdate(r.Context(), int(boardID)) + if err != nil { + logger.Info("get certain board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(err) + responseError(w, code, message) + return + } + + err = responseOk(http.StatusOK, w, "got certain board successfully", map[string]interface{}{"board": board, "tags": tagTitles}) + if err != nil { + logger.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(ErrInternalError.Error())) } } func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) + if contentType := w.Header().Get("Content-Type"); contentType != ApplicationJson { + code, message := getErrCodeMessage(ErrBadContentType) + responseError(w, code, message) + return + } boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - responseError(w, BadQueryParamCode, BadQueryParamMessage) + code, message := getErrCodeMessage(ErrBadUrlParam) + responseError(w, code, message) return } - var updatedBoard boardDTO.BoardData - err = json.NewDecoder(r.Body).Decode(&updatedBoard) + var updatedData BoardData + err = json.NewDecoder(r.Body).Decode(&updatedData) defer r.Body.Close() if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - responseError(w, BadBodyCode, BadBodyMessage) + code, message := getErrCodeMessage(ErrBadBody) + responseError(w, code, message) + return + } + + err = updatedData.Validate() + if err != nil { + logger.Info("update certain board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(err) + responseError(w, code, message) return } - updatedBoard.ID = int(boardID) + tagTitles := make([]string, 0) + if updatedData.Tags != nil { + tagTitles = append(tagTitles, updatedData.Tags...) + } + + updatedBoard := boardDTO.BoardData{ + ID: int(boardID), + Title: *updatedData.Title, + Description: *updatedData.Description, + Public: *updatedData.Public, + TagTitles: tagTitles, + } err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - switch err { - case bCase.ErrNoSuchBoard: - responseError(w, "no_board", err.Error()) - case bCase.ErrNoAccess: - responseError(w, "no_access", err.Error()) - case bCase.ErrInvalidBoardTitle: - responseError(w, "bad_boardTitle", err.Error()) - default: - if errors.Is(err, bCase.ErrInvalidTagTitles) { - responseError(w, "bad_tagTitles", err.Error()) - return - } - responseError(w, InternalErrorCode, InternalServerErrMessage) - w.WriteHeader(http.StatusInternalServerError) - } + code, message := getErrCodeMessage(err) + responseError(w, code, message) return } @@ -152,7 +285,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(InternalServerErrMessage)) + w.Write([]byte(ErrInternalError.Error())) } } @@ -161,23 +294,17 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { - logger.Info("delete board", log.F{"message", err.Error()}) - responseError(w, BadQueryParamCode, BadQueryParamMessage) + logger.Info("update certain board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(ErrBadUrlParam) + responseError(w, code, message) return } err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) if err != nil { - logger.Info("delete board", log.F{"message", err.Error()}) - switch err { - case bCase.ErrNoSuchBoard: - responseError(w, "no_board", err.Error()) - case bCase.ErrNoAccess: - responseError(w, "no_access", err.Error()) - default: - responseError(w, InternalErrorCode, InternalServerErrMessage) - w.WriteHeader(http.StatusInternalServerError) - } + logger.Info("update certain board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(err) + responseError(w, code, message) return } @@ -185,7 +312,7 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(InternalServerErrMessage)) + w.Write([]byte(ErrInternalError.Error())) } } diff --git a/internal/pkg/delivery/http/v1/board_validation.go b/internal/pkg/delivery/http/v1/board_validation.go new file mode 100644 index 0000000..0cabf18 --- /dev/null +++ b/internal/pkg/delivery/http/v1/board_validation.go @@ -0,0 +1,48 @@ +package v1 + +import ( + "fmt" + "unicode" +) + +func isValidTagTitle(title string) bool { + if len(title) > 20 { + return false + } + + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { + return false + } + } + return true +} + +func checkIsValidTagTitles(titles []string) error { + if len(titles) > 7 { + return fmt.Errorf("too many titles") + } + + invalidTitles := make([]string, 0) + for _, title := range titles { + if !isValidTagTitle(title) { + invalidTitles = append(invalidTitles, title) + } + } + if len(invalidTitles) > 0 { + return fmt.Errorf("%v", invalidTitles) + } + return nil +} + +func isValidBoardTitle(title string) bool { + if len(title) == 0 || len(title) > 40 { + return false + } + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { + return false + } + } + return true +} From a0de50fedc33d1129114a7b8746944f1c7504493 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 10 Nov 2023 18:55:27 +0300 Subject: [PATCH 162/266] dev 2 update: minor board repo fix --- internal/pkg/repository/board/postgres/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index dbf8a3b..f080f5c 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -61,7 +61,7 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, "board.title", "COALESCE(board.description, '')", "TO_CHAR(board.created_at, 'DD:MM:YYYY')", - "COUNT(pin.id) AS pins_number", + "COUNT(DISTINCT pin.id) AS pins_number", "ARRAY_REMOVE((ARRAY_AGG(DISTINCT pin.picture))[:3], NULL) AS pins", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). From("board"). From 5a12489e484573bf9dac2289c33a562ed19abdfd Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 11 Nov 2023 00:37:32 +0300 Subject: [PATCH 163/266] TP-db2 update: test version of working with the tape --- internal/api/server/router/router.go | 4 ++ internal/pkg/delivery/http/v1/feed.go | 72 ++++++++++++++++++++ internal/pkg/delivery/http/v1/like_pin.go | 26 ++++++++ internal/pkg/entity/pin/feed.go | 30 +++++++++ internal/pkg/repository/pin/like.go | 21 ++++++ internal/pkg/repository/pin/queries.go | 3 + internal/pkg/repository/pin/repo.go | 80 ++++++++++++++++++++++- internal/pkg/repository/ramrepo/pin.go | 4 ++ internal/pkg/usecase/pin/like.go | 14 ++++ internal/pkg/usecase/pin/usecase.go | 8 +++ 10 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/feed.go create mode 100644 internal/pkg/entity/pin/feed.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 0fcef89..d5cdb9c 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -89,5 +89,9 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Delete("/delete/{boardID:\\d+}", handler.DeleteBoard) }) }) + + r.Route("/feed", func(r chi.Router) { + r.Get("/pin", handler.FeedPins) + }) }) } diff --git a/internal/pkg/delivery/http/v1/feed.go b/internal/pkg/delivery/http/v1/feed.go new file mode 100644 index 0000000..3c67580 --- /dev/null +++ b/internal/pkg/delivery/http/v1/feed.go @@ -0,0 +1,72 @@ +package v1 + +import ( + "net/http" + "net/url" + "strconv" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +// count, minID, maxID, liked{true,false}, protection{private,public,all}, board_id, user_id +func (h *HandlerHTTP) FeedPins(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID, isAuth := r.Context().Value(auth.KeyCurrentUserID).(int) + if !isAuth { + userID = usecase.UserUnknown + } + + logger.Info("request on getting feed of pins", log.F{"rawQuery", r.URL.RawQuery}) + + cfg := parseFeedConfig(r.URL) + + feed, err := h.pinCase.ViewFeedPin(r.Context(), userID, cfg) + logger.Info("send feed pins", log.F{"count", len(feed.Pins)}) + + if err != nil { + err = responseError(w, "fsdf", "dsfsdf") + } else { + err = responseOk(http.StatusOK, w, "ok", feed) + } + if err != nil { + logger.Error(err.Error()) + } +} + +func parseFeedConfig(u *url.URL) pin.FeedPinConfig { + cfg := pin.FeedPinConfig{} + i, _ := strconv.ParseInt(u.Query().Get("minID"), 10, 64) + cfg.MinID = int(i) + + i, _ = strconv.ParseInt(u.Query().Get("maxID"), 10, 64) + cfg.MaxID = int(i) + + i, _ = strconv.ParseInt(u.Query().Get("userID"), 10, 64) + cfg.UserID = int(i) + + i, _ = strconv.ParseInt(u.Query().Get("boardID"), 10, 64) + cfg.BoardID = int(i) + + i, _ = strconv.ParseInt(u.Query().Get("count"), 10, 64) + cfg.Count = int(i) + + ok, _ := strconv.ParseBool(u.Query().Get("deleted")) + cfg.Deleted = ok + + ok, _ = strconv.ParseBool(u.Query().Get("liked")) + cfg.Liked = ok + + switch u.Query().Get("protection") { + case "public": + cfg.Protection = pin.FeedProtectionPublic + case "private": + cfg.Protection = pin.FeedProtectionPrivate + default: + cfg.Protection = pin.FeedAll + } + + return cfg +} diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index cf50b60..7e30848 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -7,6 +7,7 @@ import ( chi "github.com/go-chi/chi/v5" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerHTTP) SetLikePin(w http.ResponseWriter, r *http.Request) { @@ -90,3 +91,28 @@ func (h *HandlerHTTP) IsSetLikePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) } } + +// unused +func (h *HandlerHTTP) LikedPins(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) + if err != nil { + logger.Info("parse url query params", log.F{"error", err.Error()}) + err = responseError(w, "bad_params", + "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") + } else { + logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) + pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) + err = responseOk(http.StatusOK, w, "pins received are sorted by id", map[string]any{ + "pins": pins, + "minID": minID, + "maxID": maxID, + }) + } + if err != nil { + logger.Error(err.Error()) + } + +} diff --git a/internal/pkg/entity/pin/feed.go b/internal/pkg/entity/pin/feed.go new file mode 100644 index 0000000..b2665df --- /dev/null +++ b/internal/pkg/entity/pin/feed.go @@ -0,0 +1,30 @@ +package pin + +type FeedPin struct { + Condition + Pins []Pin `json:"pins"` +} + +type Condition struct { + MinID int `json:"minID"` + MaxID int `json:"maxID"` +} + +type FeedPinConfig struct { + Condition + Count int + UserID int + BoardID int + Protection protection + Liked bool + Deleted bool +} + +type protection int8 + +const ( + _ protection = iota + FeedProtectionPublic + FeedProtectionPrivate + FeedAll +) diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go index a21860e..0f4a3e9 100644 --- a/internal/pkg/repository/pin/like.go +++ b/internal/pkg/repository/pin/like.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/jackc/pgx/v5" ) @@ -49,3 +50,23 @@ func (p *pinRepoPG) IsSetLike(ctx context.Context, pinID, userID int) (bool, err } return true, nil } + +// unused +func (p *pinRepoPG) GetSortedUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) { + rows, err := p.db.Query(ctx, SelectUserLikedPinsLimit, userID, minID, maxID, count) + if err != nil { + return nil, fmt.Errorf("select to receive %d pins: %w", count, err) + } + + pins := make([]entity.Pin, 0, count) + pin := entity.Pin{} + for rows.Next() { + err := rows.Scan(&pin.ID, &pin.Picture, &pin.Public) + if err != nil { + return pins, fmt.Errorf("scan to receive %d pins: %w", count, err) + } + pins = append(pins, pin) + } + + return pins, nil +} diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index 81b82af..d1a4e91 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -35,4 +35,7 @@ var ( DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2 RETURNING (SELECT COUNT(*) FROM like_pin WHERE pin_id = $1);" DeleteAllTagsFromPin = "DELETE FROM pin_tag WHERE pin_id = $1;" + + // unused + SelectUserLikedPinsLimit = "SELECT id, picture, public FROM pin INNER JOIN like_pin ON id = pin_id WHERE user_id = $1 AND author = $1 AND deleted_at IS NULL AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" ) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index f8708f6..08ccbe9 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -31,6 +31,9 @@ type Repository interface { GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) GetTagsByPinID(ctx context.Context, pinID int) ([]entity.Tag, error) IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) + + GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) (entity.FeedPin, error) + GetSortedUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) } type pinRepoPG struct { @@ -45,6 +48,79 @@ func NewPinRepoPG(db pgtype.PgxPoolIface) *pinRepoPG { } } +func (p *pinRepoPG) GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) (entity.FeedPin, error) { + queryBuild := p.sqlBuilder.Select("pin.id", "pin.picture"). + From("pin").Limit(uint64(cfg.Count)) + + pin := entity.Pin{} + scanFields := []any{&pin.ID, &pin.Picture} + + switch cfg.Protection { + case entity.FeedAll: + queryBuild = queryBuild.Columns("pin.public") + scanFields = append(scanFields, &pin.Public) + case entity.FeedProtectionPublic: + queryBuild = queryBuild.Where(sq.Eq{"pin.public": true}) + case entity.FeedProtectionPrivate: + queryBuild = queryBuild.Where(sq.Eq{"pin.public": false}) + } + + if cfg.Deleted { + queryBuild = queryBuild.Where(sq.NotEq{"pin.deleted_at": nil}) + } else { + queryBuild = queryBuild.Where(sq.Eq{"pin.deleted_at": nil}) + } + + if cfg.UserID != 0 && !cfg.Liked { + queryBuild = queryBuild.Where(sq.Eq{"pin.author": cfg.UserID}) + } + + if cfg.Liked { + queryBuild = queryBuild.InnerJoin("like_pin ON like_pin.pin_id = pin.id"). + Where(sq.Eq{"like_pin.user_id": cfg.UserID}). + OrderBy("like_pin.created_at DESC") + } + + if cfg.BoardID != 0 { + queryBuild = queryBuild.InnerJoin("membership", "membership.pin_id = pin.id"). + InnerJoin("board", "membership.board_id = board.id"). + Where(sq.Eq{"board.id": cfg.BoardID}) + } + + sqlRow, args, err := queryBuild. + Where(sq.Or{sq.Lt{"pin.id": cfg.MinID}, sq.Gt{"pin.id": cfg.MaxID}}). + OrderBy("pin.id DESC"). + ToSql() + + if err != nil { + return entity.FeedPin{}, fmt.Errorf("query build error: %w", err) + } + fmt.Println(sqlRow, args) + + rows, err := p.db.Query(ctx, sqlRow, args...) + if err != nil { + return entity.FeedPin{Pins: []entity.Pin{}, Condition: cfg.Condition}, fmt.Errorf("getting pins for feed from storage: %w", err) + } + feed := entity.FeedPin{Condition: cfg.Condition} + for rows.Next() { + fmt.Println("SCAN") + err = rows.Scan(scanFields...) + fmt.Println(pin) + if err != nil { + return feed, fmt.Errorf("scan feed pins: %w", err) + } + feed.Pins = append(feed.Pins, pin) + } + if len(feed.Pins) != 0 && feed.Pins[0].ID > cfg.MaxID { + feed.MaxID = feed.Pins[0].ID + } + if len(feed.Pins) != 0 && feed.Pins[len(feed.Pins)-1].ID < cfg.MinID { + feed.MinID = feed.Pins[len(feed.Pins)-1].ID + } + fmt.Println(len(feed.Pins)) + return feed, nil +} + func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, error) { rows, err := p.db.Query(ctx, SelectWithExcludeLimit, minID, maxID, count) if err != nil { @@ -106,7 +182,7 @@ func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int, revealAuthor bool func (p *pinRepoPG) GetBatchPinByID(ctx context.Context, pinID []int) ([]entity.Pin, error) { sqlRow, args, err := p.sqlBuilder.Select("id", "author", "public", "deleted_at"). - From("profile"). + From("pin"). Where(sq.Eq{"id": pinID}). ToSql() if err != nil { @@ -118,7 +194,7 @@ func (p *pinRepoPG) GetBatchPinByID(ctx context.Context, pinID []int) ([]entity. return nil, fmt.Errorf("select batch pins: %w", err) } - pin := entity.Pin{} + pin := entity.Pin{Author: &user.User{}} pins := make([]entity.Pin, 0, len(pinID)) for rows.Next() { err = rows.Scan(&pin.ID, &pin.Author.ID, &pin.Public, &pin.DeletedAt) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 807dde9..6181206 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -92,3 +92,7 @@ func (r *ramPinRepo) IsSetLike(ctx context.Context, pinID, userID int) (bool, er func (r *ramPinRepo) GetBatchPinByID(ctx context.Context, pinID []int) ([]pin.Pin, error) { return nil, ErrMethodUnimplemented } + +func (r *ramPinRepo) GetSortedUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, error) { + return nil, ErrMethodUnimplemented +} diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go index e571b05..d13062b 100644 --- a/internal/pkg/usecase/pin/like.go +++ b/internal/pkg/usecase/pin/like.go @@ -3,6 +3,8 @@ package pin import ( "context" "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" ) func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { @@ -19,3 +21,15 @@ func (p *pinCase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) (in func (p *pinCase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { return p.repo.IsSetLike(ctx, pinID, userID) } + +// unused +func (p *pinCase) SelectUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) { + pins, err := p.repo.GetSortedUserPins(ctx, userID, count, minID, maxID) + if err != nil { + p.log.Error(err.Error()) + } + if len(pins) == 0 { + return []entity.Pin{}, minID, maxID + } + return pins, pins[len(pins)-1].ID, pins[0].ID +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index bc34e31..b57450c 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -6,6 +6,7 @@ import ( "fmt" "io" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" @@ -28,6 +29,9 @@ type Usecase interface { ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error + + SelectUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) + ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) } type pinCase struct { @@ -106,3 +110,7 @@ func (p *pinCase) ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin return pin, nil } + +func (p *pinCase) ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) { + return p.repo.GetFeedPins(ctx, cfg) +} From d2b251af56d8973748781f302e96580b43079ad0 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 11 Nov 2023 01:16:34 +0300 Subject: [PATCH 164/266] TP-db2 add: return user id on request login --- internal/pkg/delivery/http/v1/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index 22cd871..3fa680a 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -31,7 +31,7 @@ func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "no_auth", "no user was found for this session") } else { - err = responseOk(http.StatusOK, w, "user found", map[string]string{"username": username, "avatar": avatar}) + err = responseOk(http.StatusOK, w, "user found", map[string]any{"username": username, "avatar": avatar, "id": userID}) } if err != nil { logger.Error(err.Error()) From e023ebdbd4daabb9f06f7b9dab089790f7dcbacc Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 11 Nov 2023 01:41:28 +0300 Subject: [PATCH 165/266] TP-db2 update: feed condition change --- internal/pkg/repository/pin/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 08ccbe9..5e21791 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -114,7 +114,7 @@ func (p *pinRepoPG) GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) ( if len(feed.Pins) != 0 && feed.Pins[0].ID > cfg.MaxID { feed.MaxID = feed.Pins[0].ID } - if len(feed.Pins) != 0 && feed.Pins[len(feed.Pins)-1].ID < cfg.MinID { + if len(feed.Pins) != 0 && (feed.Pins[len(feed.Pins)-1].ID < cfg.MinID || cfg.MinID == 0) { feed.MinID = feed.Pins[len(feed.Pins)-1].ID } fmt.Println(len(feed.Pins)) From 2886ba1d93015328e45a84579ebd1a2e356d1cc2 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 12 Nov 2023 11:49:34 +0300 Subject: [PATCH 166/266] dev2 update: fixed error display --- internal/app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/app.go b/internal/app/app.go index 5adad11..ea4d85b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -48,7 +48,7 @@ func Run(ctx context.Context, log *log.Logger, configFile string) { status := redisCl.Ping(ctxApp) if status.Err() != nil { - log.Error(err.Error()) + log.Error(status.Err().Error()) return } From bd4f068dea49340aa8b71fd165d41d65e5cc1d6c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 12 Nov 2023 11:50:28 +0300 Subject: [PATCH 167/266] dev2 update: add field in GetCertainBoard response --- internal/pkg/repository/board/postgres/repo.go | 4 +++- internal/pkg/usecase/board/dto/board.go | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index f080f5c..f6d589d 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -117,6 +117,7 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces getBoardByIdQuery := repo.sqlBuilder. Select( "board.id", + "board.author", "board.title", "COALESCE(board.description, '')", "TO_CHAR(board.created_at, 'DD:MM:YYYY')", @@ -136,6 +137,7 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces } getBoardByIdQuery = getBoardByIdQuery.GroupBy( "board.id", + "board.author", "board.title", "board.description", "board.created_at"). @@ -148,7 +150,7 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces row := repo.db.QueryRow(ctx, sqlRow, args...) board = dto.UserBoard{} - err = row.Scan(&board.BoardID, &board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) + err = row.Scan(&board.BoardID, &board.AuthorID,&board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) if err != nil { switch err { case pgx.ErrNoRows: diff --git a/internal/pkg/usecase/board/dto/board.go b/internal/pkg/usecase/board/dto/board.go index 06e7b42..7308e69 100644 --- a/internal/pkg/usecase/board/dto/board.go +++ b/internal/pkg/usecase/board/dto/board.go @@ -13,6 +13,7 @@ type BoardData struct { type UserBoard struct { BoardID int `json:"board_id" example:"15"` + AuthorID int `json:"author_id,omitempty" example:"15"` Title string `json:"title" example:"Sunny places"` Description string `json:"description" example:"Sunny places"` CreatedAt string `json:"created_at" example:"08.10.2020"` From 0b66ce3a216090c3b7a4e932f47f0d8a3db0a625 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 12 Nov 2023 13:56:33 +0300 Subject: [PATCH 168/266] TP-db2 update: feed pins --- internal/pkg/delivery/http/v1/feed.go | 49 ++++++++++++++----- internal/pkg/entity/pin/feed.go | 24 ++++++++- .../pkg/repository/board/postgres/repo.go | 16 +++++- internal/pkg/repository/board/repo.go | 9 ++++ internal/pkg/repository/pin/repo.go | 12 ++--- internal/pkg/usecase/board/check.go | 46 +++++++++++++++++ internal/pkg/usecase/board/usecase.go | 2 + internal/pkg/usecase/pin/usecase.go | 7 +++ 8 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 internal/pkg/usecase/board/check.go diff --git a/internal/pkg/delivery/http/v1/feed.go b/internal/pkg/delivery/http/v1/feed.go index 3c67580..d2fb9a0 100644 --- a/internal/pkg/delivery/http/v1/feed.go +++ b/internal/pkg/delivery/http/v1/feed.go @@ -21,13 +21,30 @@ func (h *HandlerHTTP) FeedPins(w http.ResponseWriter, r *http.Request) { logger.Info("request on getting feed of pins", log.F{"rawQuery", r.URL.RawQuery}) - cfg := parseFeedConfig(r.URL) + cfg, err := parseFeedConfig(r.URL) + if err != nil { + logger.Info("error parse query params", log.F{"parse_error", err.Error()}) + err = responseError(w, "parse_params", "bad url params") + if err != nil { + logger.Error(err.Error()) + } + return + } + err = h.boardCase.CheckAvailabilityFeedPinCfgOnBoard(r.Context(), cfg, userID, isAuth) + if err != nil { + logger.Info(err.Error()) + err = responseError(w, "no_access", "there is no access to get board pins") + if err != nil { + logger.Error(err.Error()) + } + return + } feed, err := h.pinCase.ViewFeedPin(r.Context(), userID, cfg) logger.Info("send feed pins", log.F{"count", len(feed.Pins)}) if err != nil { - err = responseError(w, "fsdf", "dsfsdf") + err = responseError(w, "no_access", "there is no access to get board pins") } else { err = responseOk(http.StatusOK, w, "ok", feed) } @@ -36,7 +53,7 @@ func (h *HandlerHTTP) FeedPins(w http.ResponseWriter, r *http.Request) { } } -func parseFeedConfig(u *url.URL) pin.FeedPinConfig { +func parseFeedConfig(u *url.URL) (pin.FeedPinConfig, error) { cfg := pin.FeedPinConfig{} i, _ := strconv.ParseInt(u.Query().Get("minID"), 10, 64) cfg.MinID = int(i) @@ -44,11 +61,21 @@ func parseFeedConfig(u *url.URL) pin.FeedPinConfig { i, _ = strconv.ParseInt(u.Query().Get("maxID"), 10, 64) cfg.MaxID = int(i) - i, _ = strconv.ParseInt(u.Query().Get("userID"), 10, 64) - cfg.UserID = int(i) + if u.Query().Has("userID") { + i, err := strconv.ParseInt(u.Query().Get("userID"), 10, 64) + if err != nil { + return pin.FeedPinConfig{}, err + } + cfg.SetUser(int(i)) + } - i, _ = strconv.ParseInt(u.Query().Get("boardID"), 10, 64) - cfg.BoardID = int(i) + if u.Query().Has("boardID") { + i, err := strconv.ParseInt(u.Query().Get("boardID"), 10, 64) + if err != nil { + return pin.FeedPinConfig{}, err + } + cfg.SetBoard(int(i)) + } i, _ = strconv.ParseInt(u.Query().Get("count"), 10, 64) cfg.Count = int(i) @@ -60,13 +87,13 @@ func parseFeedConfig(u *url.URL) pin.FeedPinConfig { cfg.Liked = ok switch u.Query().Get("protection") { - case "public": - cfg.Protection = pin.FeedProtectionPublic + case "all": + cfg.Protection = pin.FeedAll case "private": cfg.Protection = pin.FeedProtectionPrivate default: - cfg.Protection = pin.FeedAll + cfg.Protection = pin.FeedProtectionPublic } - return cfg + return cfg, nil } diff --git a/internal/pkg/entity/pin/feed.go b/internal/pkg/entity/pin/feed.go index b2665df..568a281 100644 --- a/internal/pkg/entity/pin/feed.go +++ b/internal/pkg/entity/pin/feed.go @@ -13,11 +13,31 @@ type Condition struct { type FeedPinConfig struct { Condition Count int - UserID int - BoardID int + userID int + boardID int Protection protection Liked bool Deleted bool + hasUser bool + hasBoard bool +} + +func (cfg *FeedPinConfig) SetBoard(boardID int) { + cfg.boardID = boardID + cfg.hasBoard = true +} + +func (cfg *FeedPinConfig) SetUser(userID int) { + cfg.userID = userID + cfg.hasUser = true +} + +func (cfg *FeedPinConfig) Board() (int, bool) { + return cfg.boardID, cfg.hasBoard +} + +func (cfg *FeedPinConfig) User() (int, bool) { + return cfg.userID, cfg.hasUser } type protection int8 diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 93542a1..aa35448 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -6,12 +6,14 @@ import ( "time" "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" + repoBoard "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" - "github.com/jackc/pgx/v5" ) type boardRepoPG struct { @@ -288,3 +290,15 @@ func (repo *boardRepoPG) AddPinsOnBoard(ctx context.Context, boardID int, pinIds } return nil } + +func (b *boardRepoPG) GerProtectionStatusBoard(ctx context.Context, boardID int) (repoBoard.ProtectionBoard, error) { + var isPublic bool + err := b.db.QueryRow(ctx, "", boardID).Scan(&isPublic) + if err != nil { + return 0, fmt.Errorf("get status board in storage: %w", err) + } + if isPublic { + return repoBoard.ProtectionPublic, nil + } + return repoBoard.ProtectionPrivate, nil +} diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 6a49903..4397202 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -20,6 +20,7 @@ type Repository interface { DeleteBoardByID(ctx context.Context, boardID int) error RoleUserHaveOnThisBoard(ctx context.Context, boardID int, userID int) (UserRole, error) AddPinsOnBoard(ctx context.Context, boardID int, pinIds []int) error + GerProtectionStatusBoard(ctx context.Context, boardID int) (ProtectionBoard, error) } type UserRole uint8 @@ -31,3 +32,11 @@ const ( ContributorForAdding Author ) + +type ProtectionBoard uint8 + +const ( + _ ProtectionBoard = iota + ProtectionPublic + ProtectionPrivate +) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 5e21791..9489e57 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -71,20 +71,20 @@ func (p *pinRepoPG) GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) ( queryBuild = queryBuild.Where(sq.Eq{"pin.deleted_at": nil}) } - if cfg.UserID != 0 && !cfg.Liked { - queryBuild = queryBuild.Where(sq.Eq{"pin.author": cfg.UserID}) + if userID, ok := cfg.User(); ok && !cfg.Liked { + queryBuild = queryBuild.Where(sq.Eq{"pin.author": userID}) } - if cfg.Liked { + if userID, ok := cfg.User(); ok && cfg.Liked { queryBuild = queryBuild.InnerJoin("like_pin ON like_pin.pin_id = pin.id"). - Where(sq.Eq{"like_pin.user_id": cfg.UserID}). + Where(sq.Eq{"like_pin.user_id": userID}). OrderBy("like_pin.created_at DESC") } - if cfg.BoardID != 0 { + if boardID, ok := cfg.Board(); ok { queryBuild = queryBuild.InnerJoin("membership", "membership.pin_id = pin.id"). InnerJoin("board", "membership.board_id = board.id"). - Where(sq.Eq{"board.id": cfg.BoardID}) + Where(sq.Eq{"board.id": boardID}) } sqlRow, args, err := queryBuild. diff --git a/internal/pkg/usecase/board/check.go b/internal/pkg/usecase/board/check.go new file mode 100644 index 0000000..5b64727 --- /dev/null +++ b/internal/pkg/usecase/board/check.go @@ -0,0 +1,46 @@ +package board + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" +) + +func (b *boardUsecase) CheckAvailabilityFeedPinCfgOnBoard(ctx context.Context, cfg pin.FeedPinConfig, + userID int, isAuth bool) error { + + boardID, ok := cfg.Board() + if !ok { + return nil + } + + if !isAuth && cfg.Protection != pin.FeedProtectionPublic { + return ErrNoAccess + } + + protection, err := b.boardRepo.GerProtectionStatusBoard(ctx, boardID) + if err != nil { + return fmt.Errorf("get protection status board for check availability: %w", err) + } + + if !isAuth && protection != board.ProtectionPublic { + return ErrNoAccess + } + if !isAuth { + return nil + } + + role, err := b.boardRepo.RoleUserHaveOnThisBoard(ctx, boardID, userID) + if err != nil { + return fmt.Errorf("get user role of the board for check availability: %w", err) + } + + if role&(board.Author|board.ContributorForAdding|board.ContributorForReading) == 0 && + (cfg.Protection != pin.FeedProtectionPublic || protection != board.ProtectionPublic) { + + return ErrNoAccess + } + return nil +} diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index f4ce46e..248d461 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -3,6 +3,7 @@ package board import ( "context" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" @@ -18,6 +19,7 @@ type Usecase interface { UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error DeleteCertainBoard(ctx context.Context, boardID int) error FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error + CheckAvailabilityFeedPinCfgOnBoard(ctx context.Context, cfg pin.FeedPinConfig, userID int, isAuth bool) error } type boardUsecase struct { diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index b57450c..abc1da4 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -112,5 +112,12 @@ func (p *pinCase) ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin } func (p *pinCase) ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) { + _, hasBoard := cfg.Board() + user, hasUser := cfg.User() + + if !hasBoard && (userID == UserUnknown || !hasUser || userID != user) && cfg.Protection != pin.FeedProtectionPublic { + return pin.FeedPin{}, ErrForbiddenAction + } + return p.repo.GetFeedPins(ctx, cfg) } From 5a10596cc4960caabfe5d1337260cf064e97e8b4 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 12 Nov 2023 17:43:42 +0300 Subject: [PATCH 169/266] TP-db2: update repository add filters --- internal/api/server/router/router.go | 2 - internal/pkg/delivery/http/v1/feed.go | 69 +++++++++++---- internal/pkg/delivery/http/v1/like_pin.go | 26 ------ internal/pkg/delivery/http/v1/pin.go | 61 ------------- .../pkg/repository/board/mock/board_mock.go | 15 ++++ .../pkg/repository/board/postgres/queries.go | 1 + .../pkg/repository/board/postgres/repo.go | 4 +- internal/pkg/repository/board/repo.go | 2 +- .../pkg/repository/pin/build_query_feed.go | 71 +++++++++++++++ internal/pkg/repository/pin/like.go | 21 ----- internal/pkg/repository/pin/mock/pin_mock.go | 43 +++------- internal/pkg/repository/pin/queries.go | 5 -- internal/pkg/repository/pin/repo.go | 86 ++----------------- internal/pkg/repository/ramrepo/pin.go | 4 + .../pkg/repository/user/mock/user_mock.go | 15 ---- internal/pkg/usecase/board/check.go | 10 ++- internal/pkg/usecase/board/mock/board_mock.go | 15 ++++ internal/pkg/usecase/pin/like.go | 14 --- internal/pkg/usecase/pin/mock/pin_mock.go | 47 ++++------ internal/pkg/usecase/pin/usecase.go | 36 ++------ 20 files changed, 213 insertions(+), 334 deletions(-) create mode 100644 internal/pkg/repository/pin/build_query_feed.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index d5cdb9c..c8d7101 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -63,11 +63,9 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) r.Route("/pin", func(r chi.Router) { - r.Get("/", handler.GetPins) r.Get("/{pinID:\\d+}", handler.ViewPin) r.With(auth.RequireAuth).Group(func(r chi.Router) { - r.Get("/personal", handler.GetUserPins) r.Get("/like/isSet/{pinID:\\d+}", handler.IsSetLikePin) r.Post("/create", handler.CreateNewPin) r.Post("/like/set/{pinID:\\d+}", handler.SetLikePin) diff --git a/internal/pkg/delivery/http/v1/feed.go b/internal/pkg/delivery/http/v1/feed.go index d2fb9a0..4a32690 100644 --- a/internal/pkg/delivery/http/v1/feed.go +++ b/internal/pkg/delivery/http/v1/feed.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "fmt" "net/http" "net/url" "strconv" @@ -11,7 +13,6 @@ import ( log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -// count, minID, maxID, liked{true,false}, protection{private,public,all}, board_id, user_id func (h *HandlerHTTP) FeedPins(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) userID, isAuth := r.Context().Value(auth.KeyCurrentUserID).(int) @@ -55,36 +56,68 @@ func (h *HandlerHTTP) FeedPins(w http.ResponseWriter, r *http.Request) { func parseFeedConfig(u *url.URL) (pin.FeedPinConfig, error) { cfg := pin.FeedPinConfig{} - i, _ := strconv.ParseInt(u.Query().Get("minID"), 10, 64) - cfg.MinID = int(i) + var ( + err error + numInt64 int64 + ok bool + ) + + if !u.Query().Has("count") { + return cfg, errors.New("parse feed config: require count") + } + numInt64, err = strconv.ParseInt(u.Query().Get("count"), 10, 64) + if err != nil { + return cfg, fmt.Errorf("pars feed config: %w", err) + } + cfg.Count = int(numInt64) - i, _ = strconv.ParseInt(u.Query().Get("maxID"), 10, 64) - cfg.MaxID = int(i) + if u.Query().Has("minID") { + numInt64, err = strconv.ParseInt(u.Query().Get("minID"), 10, 64) + if err != nil { + return pin.FeedPinConfig{}, fmt.Errorf("pars feed config: %w", err) + } + cfg.MinID = int(numInt64) + } + + if u.Query().Has("maxID") { + numInt64, err = strconv.ParseInt(u.Query().Get("maxID"), 10, 64) + if err != nil { + return pin.FeedPinConfig{}, fmt.Errorf("pars feed config: %w", err) + } + cfg.MaxID = int(numInt64) + } if u.Query().Has("userID") { - i, err := strconv.ParseInt(u.Query().Get("userID"), 10, 64) + numInt64, err = strconv.ParseInt(u.Query().Get("userID"), 10, 64) if err != nil { - return pin.FeedPinConfig{}, err + return pin.FeedPinConfig{}, fmt.Errorf("pars feed config: %w", err) } - cfg.SetUser(int(i)) + cfg.SetUser(int(numInt64)) } if u.Query().Has("boardID") { - i, err := strconv.ParseInt(u.Query().Get("boardID"), 10, 64) + numInt64, err = strconv.ParseInt(u.Query().Get("boardID"), 10, 64) if err != nil { - return pin.FeedPinConfig{}, err + return pin.FeedPinConfig{}, fmt.Errorf("pars feed config: %w", err) } - cfg.SetBoard(int(i)) + cfg.SetBoard(int(numInt64)) } - i, _ = strconv.ParseInt(u.Query().Get("count"), 10, 64) - cfg.Count = int(i) - - ok, _ := strconv.ParseBool(u.Query().Get("deleted")) - cfg.Deleted = ok + if u.Query().Has("deleted") { + ok, err = strconv.ParseBool(u.Query().Get("deleted")) + if err != nil { + return cfg, fmt.Errorf("pars feed config: %w", err) + } + cfg.Deleted = ok + } - ok, _ = strconv.ParseBool(u.Query().Get("liked")) - cfg.Liked = ok + if u.Query().Has("liked") { + ok, err = strconv.ParseBool(u.Query().Get("liked")) + if err != nil { + return cfg, fmt.Errorf("pars feed config: %w", err) + } + cfg.Liked = ok + } switch u.Query().Get("protection") { case "all": diff --git a/internal/pkg/delivery/http/v1/like_pin.go b/internal/pkg/delivery/http/v1/like_pin.go index 7e30848..cf50b60 100644 --- a/internal/pkg/delivery/http/v1/like_pin.go +++ b/internal/pkg/delivery/http/v1/like_pin.go @@ -7,7 +7,6 @@ import ( chi "github.com/go-chi/chi/v5" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerHTTP) SetLikePin(w http.ResponseWriter, r *http.Request) { @@ -91,28 +90,3 @@ func (h *HandlerHTTP) IsSetLikePin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) } } - -// unused -func (h *HandlerHTTP) LikedPins(w http.ResponseWriter, r *http.Request) { - logger := h.getRequestLogger(r) - userID := r.Context().Value(auth.KeyCurrentUserID).(int) - - count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) - if err != nil { - logger.Info("parse url query params", log.F{"error", err.Error()}) - err = responseError(w, "bad_params", - "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") - } else { - logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) - pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) - err = responseOk(http.StatusOK, w, "pins received are sorted by id", map[string]any{ - "pins": pins, - "minID": minID, - "maxID": maxID, - }) - } - if err != nil { - logger.Error(err.Error()) - } - -} diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index e7ede87..be7c2c9 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -12,47 +12,10 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) const MaxMemoryParseFormData = 12 * 1 << 20 -// GetPins godoc -// -// @Description Get pin collection -// @Tags Pin -// @Accept json -// @Produce json -// @Param lastID path string false "ID of the pin that will be just before the first pin in the requested collection, 0 by default" example(2) -// -// @Param count path string true "Pins quantity after last pin specified in lastID" example(5) -// @Success 200 {object} JsonResponse{body=[]Pin} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/pin [get] -func (h *HandlerHTTP) GetPins(w http.ResponseWriter, r *http.Request) { - logger := h.getRequestLogger(r) - - count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) - if err != nil { - logger.Info("parse url query params", log.F{"error", err.Error()}) - err = responseError(w, "bad_params", - "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") - } else { - logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) - pins, minID, maxID := h.pinCase.SelectNewPins(r.Context(), count, minID, maxID) - err = responseOk(http.StatusOK, w, "pins received are sorted by id", map[string]any{ - "pins": pins, - "minID": minID, - "maxID": maxID, - }) - } - if err != nil { - logger.Error(err.Error()) - } -} - func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) @@ -218,27 +181,3 @@ func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) } } - -func (h *HandlerHTTP) GetUserPins(w http.ResponseWriter, r *http.Request) { - logger := h.getRequestLogger(r) - - userID := r.Context().Value(auth.KeyCurrentUserID).(int) - - count, minID, maxID, err := FetchValidParamForLoadTape(r.URL) - if err != nil { - logger.Info("parse url query params", log.F{"error", err.Error()}) - err = responseError(w, "bad_params", - "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)") - } else { - logger.Infof("param: count=%d, minID=%d, maxID=%d", count, minID, maxID) - pins, minID, maxID := h.pinCase.SelectUserPins(r.Context(), userID, count, minID, maxID) - err = responseOk(http.StatusOK, w, "pins received are sorted by id", map[string]any{ - "pins": pins, - "minID": minID, - "maxID": maxID, - }) - } - if err != nil { - logger.Error(err.Error()) - } -} diff --git a/internal/pkg/repository/board/mock/board_mock.go b/internal/pkg/repository/board/mock/board_mock.go index 6e77688..8ae47b7 100644 --- a/internal/pkg/repository/board/mock/board_mock.go +++ b/internal/pkg/repository/board/mock/board_mock.go @@ -156,6 +156,21 @@ func (mr *MockRepositoryMockRecorder) GetContributorsByBoardID(ctx, boardID inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContributorsByBoardID", reflect.TypeOf((*MockRepository)(nil).GetContributorsByBoardID), ctx, boardID) } +// GetProtectionStatusBoard mocks base method. +func (m *MockRepository) GetProtectionStatusBoard(ctx context.Context, boardID int) (board0.ProtectionBoard, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProtectionStatusBoard", ctx, boardID) + ret0, _ := ret[0].(board0.ProtectionBoard) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProtectionStatusBoard indicates an expected call of GetProtectionStatusBoard. +func (mr *MockRepositoryMockRecorder) GetProtectionStatusBoard(ctx, boardID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProtectionStatusBoard", reflect.TypeOf((*MockRepository)(nil).GetProtectionStatusBoard), ctx, boardID) +} + // RoleUserHaveOnThisBoard mocks base method. func (m *MockRepository) RoleUserHaveOnThisBoard(ctx context.Context, boardID, userID int) (board0.UserRole, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index 00b8592..c37d654 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -1,6 +1,7 @@ package board const ( + SelectProtectionStatusBoard = "SELECT public FROM board WHERE id = $1;" InsertBoardQuery = "INSERT INTO board (author, title, description, public) VALUES ($1, $2, $3, $4) RETURNING id;" SelectBoardAuthorByBoardIdQuery = "SELECT author FROM board WHERE id = $1 AND deleted_at IS NULL;" SelectBoardContributorsByBoardIdQuery = "SELECT user_id FROM contributor WHERE board_id = $1;" diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index aa35448..245f1da 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -291,9 +291,9 @@ func (repo *boardRepoPG) AddPinsOnBoard(ctx context.Context, boardID int, pinIds return nil } -func (b *boardRepoPG) GerProtectionStatusBoard(ctx context.Context, boardID int) (repoBoard.ProtectionBoard, error) { +func (b *boardRepoPG) GetProtectionStatusBoard(ctx context.Context, boardID int) (repoBoard.ProtectionBoard, error) { var isPublic bool - err := b.db.QueryRow(ctx, "", boardID).Scan(&isPublic) + err := b.db.QueryRow(ctx, SelectProtectionStatusBoard, boardID).Scan(&isPublic) if err != nil { return 0, fmt.Errorf("get status board in storage: %w", err) } diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 4397202..f05fa08 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -20,7 +20,7 @@ type Repository interface { DeleteBoardByID(ctx context.Context, boardID int) error RoleUserHaveOnThisBoard(ctx context.Context, boardID int, userID int) (UserRole, error) AddPinsOnBoard(ctx context.Context, boardID int, pinIds []int) error - GerProtectionStatusBoard(ctx context.Context, boardID int) (ProtectionBoard, error) + GetProtectionStatusBoard(ctx context.Context, boardID int) (ProtectionBoard, error) } type UserRole uint8 diff --git a/internal/pkg/repository/pin/build_query_feed.go b/internal/pkg/repository/pin/build_query_feed.go new file mode 100644 index 0000000..d77c070 --- /dev/null +++ b/internal/pkg/repository/pin/build_query_feed.go @@ -0,0 +1,71 @@ +package pin + +import ( + sq "github.com/Masterminds/squirrel" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" +) + +func addFilters(queryBuild sq.SelectBuilder, cfg entity.FeedPinConfig, pin *entity.Pin, + fields []any) (sq.SelectBuilder, []any) { + + queryBuild, fields = protection(queryBuild, cfg, pin, fields) + queryBuild = addFilterDeleted(queryBuild, cfg.Deleted) + queryBuild, ok := addFilterLiked(queryBuild, cfg) + if !ok { + queryBuild = addFilterUser(queryBuild, cfg) + } + queryBuild = addFilterBoard(queryBuild, cfg) + return queryBuild, fields +} + +func protection(queryBuild sq.SelectBuilder, cfg entity.FeedPinConfig, pin *entity.Pin, + fields []any) (sq.SelectBuilder, []any) { + + switch cfg.Protection { + case entity.FeedAll: + queryBuild = queryBuild.Columns("pin.public") + fields = append(fields, &pin.Public) + case entity.FeedProtectionPublic: + queryBuild = queryBuild.Where(sq.Eq{"pin.public": true}) + pin.Public = true + case entity.FeedProtectionPrivate: + queryBuild = queryBuild.Where(sq.Eq{"pin.public": false}) + } + return queryBuild, fields +} + +func addFilterDeleted(queryBuild sq.SelectBuilder, deleted bool) sq.SelectBuilder { + if deleted { + queryBuild = queryBuild.Where(sq.NotEq{"pin.deleted_at": nil}) + } else { + queryBuild = queryBuild.Where(sq.Eq{"pin.deleted_at": nil}) + } + return queryBuild +} + +func addFilterBoard(queryBuild sq.SelectBuilder, cfg entity.FeedPinConfig) sq.SelectBuilder { + if boardID, ok := cfg.Board(); ok { + queryBuild = queryBuild.InnerJoin("membership ON membership.pin_id = pin.id"). + InnerJoin("board ON membership.board_id = board.id"). + Where(sq.Eq{"board.id": boardID}) + } + return queryBuild +} + +func addFilterLiked(queryBuild sq.SelectBuilder, cfg entity.FeedPinConfig) (sq.SelectBuilder, bool) { + if userID, ok := cfg.User(); ok && cfg.Liked { + queryBuild = queryBuild.InnerJoin("like_pin ON like_pin.pin_id = pin.id"). + Where(sq.Eq{"like_pin.user_id": userID}). + OrderBy("like_pin.created_at DESC") + return queryBuild, true + } + return queryBuild, false +} + +func addFilterUser(queryBuild sq.SelectBuilder, cfg entity.FeedPinConfig) sq.SelectBuilder { + if userID, ok := cfg.User(); ok { + queryBuild = queryBuild.Where(sq.Eq{"pin.author": userID}) + } + return queryBuild +} diff --git a/internal/pkg/repository/pin/like.go b/internal/pkg/repository/pin/like.go index 0f4a3e9..a21860e 100644 --- a/internal/pkg/repository/pin/like.go +++ b/internal/pkg/repository/pin/like.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/jackc/pgx/v5" ) @@ -50,23 +49,3 @@ func (p *pinRepoPG) IsSetLike(ctx context.Context, pinID, userID int) (bool, err } return true, nil } - -// unused -func (p *pinRepoPG) GetSortedUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) { - rows, err := p.db.Query(ctx, SelectUserLikedPinsLimit, userID, minID, maxID, count) - if err != nil { - return nil, fmt.Errorf("select to receive %d pins: %w", count, err) - } - - pins := make([]entity.Pin, 0, count) - pin := entity.Pin{} - for rows.Next() { - err := rows.Scan(&pin.ID, &pin.Picture, &pin.Public) - if err != nil { - return pins, fmt.Errorf("scan to receive %d pins: %w", count, err) - } - pins = append(pins, pin) - } - - return pins, nil -} diff --git a/internal/pkg/repository/pin/mock/pin_mock.go b/internal/pkg/repository/pin/mock/pin_mock.go index bc8a5df..416cc16 100644 --- a/internal/pkg/repository/pin/mock/pin_mock.go +++ b/internal/pkg/repository/pin/mock/pin_mock.go @@ -139,49 +139,34 @@ func (mr *MockRepositoryMockRecorder) GetCountLikeByPinID(ctx, pinID interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCountLikeByPinID", reflect.TypeOf((*MockRepository)(nil).GetCountLikeByPinID), ctx, pinID) } -// GetPinByID mocks base method. -func (m *MockRepository) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*pin.Pin, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPinByID", ctx, pinID, revealAuthor) - ret0, _ := ret[0].(*pin.Pin) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPinByID indicates an expected call of GetPinByID. -func (mr *MockRepositoryMockRecorder) GetPinByID(ctx, pinID, revealAuthor interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPinByID", reflect.TypeOf((*MockRepository)(nil).GetPinByID), ctx, pinID, revealAuthor) -} - -// GetSortedNewNPins mocks base method. -func (m *MockRepository) GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]pin.Pin, error) { +// GetFeedPins mocks base method. +func (m *MockRepository) GetFeedPins(ctx context.Context, cfg pin.FeedPinConfig) (pin.FeedPin, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSortedNewNPins", ctx, count, midID, maxID) - ret0, _ := ret[0].([]pin.Pin) + ret := m.ctrl.Call(m, "GetFeedPins", ctx, cfg) + ret0, _ := ret[0].(pin.FeedPin) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSortedNewNPins indicates an expected call of GetSortedNewNPins. -func (mr *MockRepositoryMockRecorder) GetSortedNewNPins(ctx, count, midID, maxID interface{}) *gomock.Call { +// GetFeedPins indicates an expected call of GetFeedPins. +func (mr *MockRepositoryMockRecorder) GetFeedPins(ctx, cfg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSortedNewNPins", reflect.TypeOf((*MockRepository)(nil).GetSortedNewNPins), ctx, count, midID, maxID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFeedPins", reflect.TypeOf((*MockRepository)(nil).GetFeedPins), ctx, cfg) } -// GetSortedUserPins mocks base method. -func (m *MockRepository) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, error) { +// GetPinByID mocks base method. +func (m *MockRepository) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*pin.Pin, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSortedUserPins", ctx, userID, count, minID, maxID) - ret0, _ := ret[0].([]pin.Pin) + ret := m.ctrl.Call(m, "GetPinByID", ctx, pinID, revealAuthor) + ret0, _ := ret[0].(*pin.Pin) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSortedUserPins indicates an expected call of GetSortedUserPins. -func (mr *MockRepositoryMockRecorder) GetSortedUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { +// GetPinByID indicates an expected call of GetPinByID. +func (mr *MockRepositoryMockRecorder) GetPinByID(ctx, pinID, revealAuthor interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSortedUserPins", reflect.TypeOf((*MockRepository)(nil).GetSortedUserPins), ctx, userID, count, minID, maxID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPinByID", reflect.TypeOf((*MockRepository)(nil).GetPinByID), ctx, pinID, revealAuthor) } // GetTagsByPinID mocks base method. diff --git a/internal/pkg/repository/pin/queries.go b/internal/pkg/repository/pin/queries.go index d1a4e91..0ec4e30 100644 --- a/internal/pkg/repository/pin/queries.go +++ b/internal/pkg/repository/pin/queries.go @@ -1,8 +1,6 @@ package pin var ( - SelectWithExcludeLimit = "SELECT id, picture FROM pin WHERE public AND deleted_at IS NULL AND (id < $1 OR id > $2) ORDER BY id DESC LIMIT $3;" - SelectUserPinsLimit = "SELECT id, picture, public FROM pin WHERE author = $1 AND deleted_at IS NULL AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" SelectPinByID = "SELECT author, title, description, picture, public, deleted_at FROM pin WHERE id = $1;" SelectCountLikePin = "SELECT COUNT(*) FROM like_pin WHERE pin_id = $1;" SelectPinByIDWithAuthor = `SELECT author, title, description, picture, public, pin.deleted_at, username, avatar @@ -35,7 +33,4 @@ var ( DeleteLikePinFromUser = "DELETE FROM like_pin WHERE pin_id = $1 AND user_id = $2 RETURNING (SELECT COUNT(*) FROM like_pin WHERE pin_id = $1);" DeleteAllTagsFromPin = "DELETE FROM pin_tag WHERE pin_id = $1;" - - // unused - SelectUserLikedPinsLimit = "SELECT id, picture, public FROM pin INNER JOIN like_pin ON id = pin_id WHERE user_id = $1 AND author = $1 AND deleted_at IS NULL AND (id < $2 OR id > $3) ORDER BY id DESC LIMIT $4;" ) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 9489e57..8beccc3 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -17,8 +17,7 @@ type S map[string]any //go:generate mockgen -destination=./mock/pin_mock.go -package=mock -source=repo.go Repository type Repository interface { - GetSortedNewNPins(ctx context.Context, count, midID, maxID int) ([]entity.Pin, error) - GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) + GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) (entity.FeedPin, error) GetAuthorPin(ctx context.Context, pinID int) (*user.User, error) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) GetBatchPinByID(ctx context.Context, pinID []int) ([]entity.Pin, error) @@ -31,9 +30,6 @@ type Repository interface { GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) GetTagsByPinID(ctx context.Context, pinID int) ([]entity.Tag, error) IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) - - GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) (entity.FeedPin, error) - GetSortedUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) } type pinRepoPG struct { @@ -50,115 +46,45 @@ func NewPinRepoPG(db pgtype.PgxPoolIface) *pinRepoPG { func (p *pinRepoPG) GetFeedPins(ctx context.Context, cfg entity.FeedPinConfig) (entity.FeedPin, error) { queryBuild := p.sqlBuilder.Select("pin.id", "pin.picture"). - From("pin").Limit(uint64(cfg.Count)) + From("pin") pin := entity.Pin{} scanFields := []any{&pin.ID, &pin.Picture} - switch cfg.Protection { - case entity.FeedAll: - queryBuild = queryBuild.Columns("pin.public") - scanFields = append(scanFields, &pin.Public) - case entity.FeedProtectionPublic: - queryBuild = queryBuild.Where(sq.Eq{"pin.public": true}) - case entity.FeedProtectionPrivate: - queryBuild = queryBuild.Where(sq.Eq{"pin.public": false}) - } - - if cfg.Deleted { - queryBuild = queryBuild.Where(sq.NotEq{"pin.deleted_at": nil}) - } else { - queryBuild = queryBuild.Where(sq.Eq{"pin.deleted_at": nil}) - } - - if userID, ok := cfg.User(); ok && !cfg.Liked { - queryBuild = queryBuild.Where(sq.Eq{"pin.author": userID}) - } - - if userID, ok := cfg.User(); ok && cfg.Liked { - queryBuild = queryBuild.InnerJoin("like_pin ON like_pin.pin_id = pin.id"). - Where(sq.Eq{"like_pin.user_id": userID}). - OrderBy("like_pin.created_at DESC") - } - - if boardID, ok := cfg.Board(); ok { - queryBuild = queryBuild.InnerJoin("membership", "membership.pin_id = pin.id"). - InnerJoin("board", "membership.board_id = board.id"). - Where(sq.Eq{"board.id": boardID}) - } + queryBuild, scanFields = addFilters(queryBuild, cfg, &pin, scanFields) sqlRow, args, err := queryBuild. Where(sq.Or{sq.Lt{"pin.id": cfg.MinID}, sq.Gt{"pin.id": cfg.MaxID}}). OrderBy("pin.id DESC"). + Limit(uint64(cfg.Count)). ToSql() - if err != nil { return entity.FeedPin{}, fmt.Errorf("query build error: %w", err) } - fmt.Println(sqlRow, args) rows, err := p.db.Query(ctx, sqlRow, args...) if err != nil { return entity.FeedPin{Pins: []entity.Pin{}, Condition: cfg.Condition}, fmt.Errorf("getting pins for feed from storage: %w", err) } feed := entity.FeedPin{Condition: cfg.Condition} + for rows.Next() { - fmt.Println("SCAN") err = rows.Scan(scanFields...) - fmt.Println(pin) if err != nil { return feed, fmt.Errorf("scan feed pins: %w", err) } feed.Pins = append(feed.Pins, pin) } + if len(feed.Pins) != 0 && feed.Pins[0].ID > cfg.MaxID { feed.MaxID = feed.Pins[0].ID } if len(feed.Pins) != 0 && (feed.Pins[len(feed.Pins)-1].ID < cfg.MinID || cfg.MinID == 0) { feed.MinID = feed.Pins[len(feed.Pins)-1].ID } - fmt.Println(len(feed.Pins)) return feed, nil } -func (p *pinRepoPG) GetSortedNewNPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, error) { - rows, err := p.db.Query(ctx, SelectWithExcludeLimit, minID, maxID, count) - if err != nil { - return nil, fmt.Errorf("select to receive %d pins: %w", count, err) - } - - pins := make([]entity.Pin, 0, count) - pin := entity.Pin{Public: true} - for rows.Next() { - err := rows.Scan(&pin.ID, &pin.Picture) - if err != nil { - return pins, fmt.Errorf("scan to receive %d pins: %w", count, err) - } - pins = append(pins, pin) - } - - return pins, nil -} - -func (p *pinRepoPG) GetSortedUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, error) { - rows, err := p.db.Query(ctx, SelectUserPinsLimit, userID, minID, maxID, count) - if err != nil { - return nil, fmt.Errorf("select to receive %d pins: %w", count, err) - } - - pins := make([]entity.Pin, 0, count) - pin := entity.Pin{} - for rows.Next() { - err := rows.Scan(&pin.ID, &pin.Picture, &pin.Public) - if err != nil { - return pins, fmt.Errorf("scan to receive %d pins: %w", count, err) - } - pins = append(pins, pin) - } - - return pins, nil -} - func (p *pinRepoPG) GetPinByID(ctx context.Context, pinID int, revealAuthor bool) (*entity.Pin, error) { pin := &entity.Pin{Author: &user.User{}} var err error diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 6181206..0a070fe 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -96,3 +96,7 @@ func (r *ramPinRepo) GetBatchPinByID(ctx context.Context, pinID []int) ([]pin.Pi func (r *ramPinRepo) GetSortedUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, error) { return nil, ErrMethodUnimplemented } + +func (r *ramPinRepo) GetFeedPins(ctx context.Context, cfg pin.FeedPinConfig) (pin.FeedPin, error) { + return pin.FeedPin{}, ErrMethodUnimplemented +} diff --git a/internal/pkg/repository/user/mock/user_mock.go b/internal/pkg/repository/user/mock/user_mock.go index 99b06b9..3fae4f7 100644 --- a/internal/pkg/repository/user/mock/user_mock.go +++ b/internal/pkg/repository/user/mock/user_mock.go @@ -93,21 +93,6 @@ func (mr *MockRepositoryMockRecorder) GetAllUserData(ctx, userID interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllUserData", reflect.TypeOf((*MockRepository)(nil).GetAllUserData), ctx, userID) } -// GetLastUserID mocks base method. -func (m *MockRepository) GetLastUserID(ctx context.Context) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLastUserID", ctx) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLastUserID indicates an expected call of GetLastUserID. -func (mr *MockRepositoryMockRecorder) GetLastUserID(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastUserID", reflect.TypeOf((*MockRepository)(nil).GetLastUserID), ctx) -} - // GetUserByUsername mocks base method. func (m *MockRepository) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/usecase/board/check.go b/internal/pkg/usecase/board/check.go index 5b64727..c23ab4d 100644 --- a/internal/pkg/usecase/board/check.go +++ b/internal/pkg/usecase/board/check.go @@ -16,11 +16,19 @@ func (b *boardUsecase) CheckAvailabilityFeedPinCfgOnBoard(ctx context.Context, c return nil } + if cfg.Count > 1000 || cfg.Count <= 0 { + return ErrNoAccess + } + + if _, ok := cfg.User(); cfg.Liked && !ok { + return ErrNoAccess + } + if !isAuth && cfg.Protection != pin.FeedProtectionPublic { return ErrNoAccess } - protection, err := b.boardRepo.GerProtectionStatusBoard(ctx, boardID) + protection, err := b.boardRepo.GetProtectionStatusBoard(ctx, boardID) if err != nil { return fmt.Errorf("get protection status board for check availability: %w", err) } diff --git a/internal/pkg/usecase/board/mock/board_mock.go b/internal/pkg/usecase/board/mock/board_mock.go index ca9e41e..635fa8e 100644 --- a/internal/pkg/usecase/board/mock/board_mock.go +++ b/internal/pkg/usecase/board/mock/board_mock.go @@ -8,6 +8,7 @@ import ( context "context" reflect "reflect" + pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" gomock "github.com/golang/mock/gomock" ) @@ -35,6 +36,20 @@ func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { return m.recorder } +// CheckAvailabilityFeedPinCfgOnBoard mocks base method. +func (m *MockUsecase) CheckAvailabilityFeedPinCfgOnBoard(ctx context.Context, cfg pin.FeedPinConfig, userID int, isAuth bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckAvailabilityFeedPinCfgOnBoard", ctx, cfg, userID, isAuth) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckAvailabilityFeedPinCfgOnBoard indicates an expected call of CheckAvailabilityFeedPinCfgOnBoard. +func (mr *MockUsecaseMockRecorder) CheckAvailabilityFeedPinCfgOnBoard(ctx, cfg, userID, isAuth interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckAvailabilityFeedPinCfgOnBoard", reflect.TypeOf((*MockUsecase)(nil).CheckAvailabilityFeedPinCfgOnBoard), ctx, cfg, userID, isAuth) +} + // CreateNewBoard mocks base method. func (m *MockUsecase) CreateNewBoard(ctx context.Context, newBoard board.BoardData) (int, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/usecase/pin/like.go b/internal/pkg/usecase/pin/like.go index d13062b..e571b05 100644 --- a/internal/pkg/usecase/pin/like.go +++ b/internal/pkg/usecase/pin/like.go @@ -3,8 +3,6 @@ package pin import ( "context" "fmt" - - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" ) func (p *pinCase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { @@ -21,15 +19,3 @@ func (p *pinCase) DeleteLikeFromUser(ctx context.Context, pinID, userID int) (in func (p *pinCase) CheckUserHasSetLike(ctx context.Context, pinID, userID int) (bool, error) { return p.repo.IsSetLike(ctx, pinID, userID) } - -// unused -func (p *pinCase) SelectUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) { - pins, err := p.repo.GetSortedUserPins(ctx, userID, count, minID, maxID) - if err != nil { - p.log.Error(err.Error()) - } - if len(pins) == 0 { - return []entity.Pin{}, minID, maxID - } - return pins, pins[len(pins)-1].ID, pins[0].ID -} diff --git a/internal/pkg/usecase/pin/mock/pin_mock.go b/internal/pkg/usecase/pin/mock/pin_mock.go index 79ff01c..b747d00 100644 --- a/internal/pkg/usecase/pin/mock/pin_mock.go +++ b/internal/pkg/usecase/pin/mock/pin_mock.go @@ -137,38 +137,6 @@ func (mr *MockUsecaseMockRecorder) IsAvailablePinForFixOnBoard(ctx, pinID, userI return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForFixOnBoard", reflect.TypeOf((*MockUsecase)(nil).IsAvailablePinForFixOnBoard), ctx, pinID, userID) } -// SelectNewPins mocks base method. -func (m *MockUsecase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]pin.Pin, int, int) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectNewPins", ctx, count, minID, maxID) - ret0, _ := ret[0].([]pin.Pin) - ret1, _ := ret[1].(int) - ret2, _ := ret[2].(int) - return ret0, ret1, ret2 -} - -// SelectNewPins indicates an expected call of SelectNewPins. -func (mr *MockUsecaseMockRecorder) SelectNewPins(ctx, count, minID, maxID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectNewPins", reflect.TypeOf((*MockUsecase)(nil).SelectNewPins), ctx, count, minID, maxID) -} - -// SelectUserPins mocks base method. -func (m *MockUsecase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]pin.Pin, int, int) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SelectUserPins", ctx, userID, count, minID, maxID) - ret0, _ := ret[0].([]pin.Pin) - ret1, _ := ret[1].(int) - ret2, _ := ret[2].(int) - return ret0, ret1, ret2 -} - -// SelectUserPins indicates an expected call of SelectUserPins. -func (mr *MockUsecaseMockRecorder) SelectUserPins(ctx, userID, count, minID, maxID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectUserPins", reflect.TypeOf((*MockUsecase)(nil).SelectUserPins), ctx, userID, count, minID, maxID) -} - // SetLikeFromUser mocks base method. func (m *MockUsecase) SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) { m.ctrl.T.Helper() @@ -198,3 +166,18 @@ func (mr *MockUsecaseMockRecorder) ViewAnPin(ctx, pinID, userID interface{}) *go mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAnPin", reflect.TypeOf((*MockUsecase)(nil).ViewAnPin), ctx, pinID, userID) } + +// ViewFeedPin mocks base method. +func (m *MockUsecase) ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ViewFeedPin", ctx, userID, cfg) + ret0, _ := ret[0].(pin.FeedPin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ViewFeedPin indicates an expected call of ViewFeedPin. +func (mr *MockUsecaseMockRecorder) ViewFeedPin(ctx, userID, cfg interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewFeedPin", reflect.TypeOf((*MockUsecase)(nil).ViewFeedPin), ctx, userID, cfg) +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index abc1da4..6f0699b 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -18,8 +18,7 @@ var ErrBadMIMEType = errors.New("bad mime type") //go:generate mockgen -destination=./mock/pin_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { - SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) - SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) + ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) CreateNewPin(ctx context.Context, pin *entity.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error DeletePinFromUser(ctx context.Context, pinID, userID int) error SetLikeFromUser(ctx context.Context, pinID, userID int) (int, error) @@ -29,9 +28,6 @@ type Usecase interface { ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin, error) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error IsAvailableBatchPinForFixOnBoard(ctx context.Context, pinID []int, userID int) error - - SelectUserLikedPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) - ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) } type pinCase struct { @@ -48,28 +44,6 @@ func New(log *log.Logger, imgCase image.Usecase, repo repo.Repository) *pinCase } } -func (p *pinCase) SelectNewPins(ctx context.Context, count, minID, maxID int) ([]entity.Pin, int, int) { - pins, err := p.repo.GetSortedNewNPins(ctx, count, minID, maxID) - if err != nil { - p.log.Error(err.Error()) - } - if len(pins) == 0 { - return []entity.Pin{}, minID, maxID - } - return pins, pins[len(pins)-1].ID, pins[0].ID -} - -func (p *pinCase) SelectUserPins(ctx context.Context, userID, count, minID, maxID int) ([]entity.Pin, int, int) { - pins, err := p.repo.GetSortedUserPins(ctx, userID, count, minID, maxID) - if err != nil { - p.log.Error(err.Error()) - } - if len(pins) == 0 { - return []entity.Pin{}, minID, maxID - } - return pins, pins[len(pins)-1].ID, pins[0].ID -} - func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { picturePin, err := p.UploadImage("pins/", mimeTypePicture, sizePicture, picture, check.BothSidesFallIntoRange(200, 1800)) if err != nil { @@ -112,9 +86,17 @@ func (p *pinCase) ViewAnPin(ctx context.Context, pinID, userID int) (*entity.Pin } func (p *pinCase) ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinConfig) (pin.FeedPin, error) { + if cfg.Count > 1000 || cfg.Count <= 0 { + return pin.FeedPin{}, ErrForbiddenAction + } + _, hasBoard := cfg.Board() user, hasUser := cfg.User() + if cfg.Liked && !hasUser { + return pin.FeedPin{}, ErrForbiddenAction + } + if !hasBoard && (userID == UserUnknown || !hasUser || userID != user) && cfg.Protection != pin.FeedProtectionPublic { return pin.FeedPin{}, ErrForbiddenAction } From c7479a9a4c4da29fef4b102316003c1d33138e4b Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 12 Nov 2023 17:58:37 +0300 Subject: [PATCH 170/266] dev2 update: check valid photo for pin --- internal/pkg/usecase/board/mock/board_mock.go | 2 +- internal/pkg/usecase/pin/usecase.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/usecase/board/mock/board_mock.go b/internal/pkg/usecase/board/mock/board_mock.go index 42ff454..a64e46d 100644 --- a/internal/pkg/usecase/board/mock/board_mock.go +++ b/internal/pkg/usecase/board/mock/board_mock.go @@ -8,8 +8,8 @@ import ( context "context" reflect "reflect" - pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" board0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" gomock "github.com/golang/mock/gomock" ) diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 6f0699b..6385475 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -45,7 +45,7 @@ func New(log *log.Logger, imgCase image.Usecase, repo repo.Repository) *pinCase } func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { - picturePin, err := p.UploadImage("pins/", mimeTypePicture, sizePicture, picture, check.BothSidesFallIntoRange(200, 1800)) + picturePin, err := p.UploadImage("pins/", mimeTypePicture, sizePicture, picture, check.BothSidesFallIntoRange(100, 6000)) if err != nil { return fmt.Errorf("uploading an avatar when creating pin: %w", err) } From 22bb6d2f1c661fe5b7323c44f02caf5547195f6f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 12 Nov 2023 21:44:52 +0300 Subject: [PATCH 171/266] TP-619 add: config for redis and .env file for postgres --- .gitignore | 3 +++ deployments/docker-compose.yml | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 817513b..da7de2a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ go.work bin/ testdata/ cert/ + +.env +redis.conf diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 574ddef..ba817e0 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -4,10 +4,8 @@ services: postgres: image: postgres:latest container_name: pinspirePG - environment: - POSTGRES_USER: ond_team - POSTGRES_PASSWORD: love - POSTGRES_DB: pinspire + env_file: + - ../.env volumes: - ../db/migrations:/docker-entrypoint-initdb.d ports: @@ -16,6 +14,8 @@ services: redis: image: redis:latest container_name: pinspireRedis - command: /bin/sh -c "redis-server --requirepass love" + volumes: + - ../redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf ports: - 6379:6379 From d7f69b1352a9b5d196c0f036f9b08d8ab3c6fb0b Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 13 Nov 2023 01:23:28 +0300 Subject: [PATCH 172/266] TP-619 add: pkg config for parse *.conf files --- pkg/config/config.go | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 pkg/config/config.go diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..347d89f --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,56 @@ +package config + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" +) + +const SupportedExt = "conf" + +var ( + ErrUnsupportedExt = errors.New("unsupported extension") + ErrParseConfig = errors.New("invalid syntax") +) + +type Config struct { + m map[string]string +} + +func (c Config) Get(key string) string { + return c.m[key] +} + +func ParseConfig(filename string) (Config, error) { + cfg := Config{} + + ind := strings.LastIndex(filename, ".") + if ind == -1 || ind+1 == len(filename) || filename[ind+1:] != SupportedExt { + return cfg, ErrUnsupportedExt + } + + file, err := os.Open(filename) + if err != nil { + return cfg, fmt.Errorf("parse config %s: %w", filename, err) + } + defer file.Close() + + cfg.m = make(map[string]string) + + scan := bufio.NewScanner(file) + var pair []string + for scan.Scan() { + pair = strings.SplitN(scan.Text(), " ", 2) + if len(pair) != 2 { + return Config{}, ErrParseConfig + } + cfg.m[pair[0]] = pair[1] + } + + if scan.Err() != nil { + return Config{}, fmt.Errorf("parse config %s: %w", filename, scan.Err()) + } + return cfg, nil +} From 274847f14259e73be6a8eeafe65d205b7d2495fd Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 13 Nov 2023 01:25:06 +0300 Subject: [PATCH 173/266] TP-619 add: connect with config --- cmd/app/config.go | 8 +++++++ cmd/app/main.go | 4 +--- go.mod | 11 +++++----- go.sum | 23 +++++++++++++------- internal/app/app.go | 41 ++++++++++++++++++++--------------- internal/app/config.go | 29 +++++++++++++++++++++++++ internal/app/pg_conn.go | 44 ++++++++++++++++++++++++++++++++++++++ internal/app/redis_conn.go | 22 +++++++++++++++++++ 8 files changed, 149 insertions(+), 33 deletions(-) create mode 100644 cmd/app/config.go create mode 100644 internal/app/config.go create mode 100644 internal/app/pg_conn.go create mode 100644 internal/app/redis_conn.go diff --git a/cmd/app/config.go b/cmd/app/config.go new file mode 100644 index 0000000..a59b192 --- /dev/null +++ b/cmd/app/config.go @@ -0,0 +1,8 @@ +package main + +import "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" + +var configFiles = app.ConfigFiles{ + ServerConfigFile: "configs/config.yml", + RedisConfigFile: "redis.conf", +} diff --git a/cmd/app/main.go b/cmd/app/main.go index b28003d..3d4dbb6 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -8,8 +8,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -const configFile = "configs/config.yml" - // @title Pinspire API // @version 1.0 // @description API for Pinspire project @@ -33,5 +31,5 @@ func main() { } defer log.Sync() - app.Run(ctxBase, log, configFile) + app.Run(ctxBase, log, configFiles) } diff --git a/go.mod b/go.mod index bc70b6b..1919958 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 + github.com/joho/godotenv v1.5.1 github.com/microcosm-cc/bluemonday v1.0.26 github.com/pashagolub/pgxmock/v2 v2.12.0 github.com/proullon/ramsql v0.0.1 @@ -31,7 +32,7 @@ require ( github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/benoitkugler/textprocessing v0.0.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/go-fonts/latin-modern v0.3.1 // indirect @@ -49,18 +50,18 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tdewolff/minify/v2 v2.20.5 // indirect github.com/tdewolff/parse/v2 v2.7.3 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect + golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect star-tex.org/x/tex v0.4.0 // indirect diff --git a/go.sum b/go.sum index af80b91..9f5314b 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,9 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= @@ -75,6 +76,8 @@ github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -102,8 +105,9 @@ github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02C github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= -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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= @@ -160,11 +164,13 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -175,8 +181,8 @@ golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -200,9 +206,10 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/app/app.go b/internal/app/app.go index ea4d85b..e6031b5 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -4,9 +4,8 @@ import ( "context" "time" - "github.com/jackc/pgx/v5/pgxpool" + "github.com/joho/godotenv" "github.com/microcosm-cc/bluemonday" - redis "github.com/redis/go-redis/v9" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" @@ -24,42 +23,50 @@ import ( log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -func Run(ctx context.Context, log *log.Logger, configFile string) { - ctxApp, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() +var ( + timeoutForConnPG = 5 * time.Second + timeoutForConnRedis = 5 * time.Second +) + +const uploadFiles = "upload/" + +func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { + godotenv.Load() + + ctx, cancelCtxPG := context.WithTimeout(ctx, timeoutForConnPG) + defer cancelCtxPG() - pool, err := pgxpool.New(ctxApp, "postgres://ond_team:love@localhost:5432/pinspire?search_path=pinspire") + pool, err := NewPoolPG(ctx) if err != nil { log.Error(err.Error()) return } defer pool.Close() - err = pool.Ping(ctxApp) + ctx, cancelCtxRedis := context.WithTimeout(ctx, timeoutForConnRedis) + defer cancelCtxRedis() + + redisCfg, err := NewConfig(cfg.RedisConfigFile) if err != nil { log.Error(err.Error()) return } - redisCl := redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Password: "love", - }) - - status := redisCl.Ping(ctxApp) - if status.Err() != nil { - log.Error(status.Err().Error()) + redisCl, err := NewRedisClient(ctx, redisCfg) + if err != nil { + log.Error(err.Error()) return } + defer redisCl.Close() sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) - imgCase := image.New(log, imgRepo.NewImageRepoFS("upload/")) + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) userCase := user.New(log, imgCase, userRepo.NewUserRepoPG(pool)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()) handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase) - cfgServ, err := server.NewConfig(configFile) + cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { log.Error(err.Error()) return diff --git a/internal/app/config.go b/internal/app/config.go new file mode 100644 index 0000000..e667344 --- /dev/null +++ b/internal/app/config.go @@ -0,0 +1,29 @@ +package app + +import ( + "fmt" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/config" +) + +type ConfigFiles struct { + ServerConfigFile string + RedisConfigFile string +} + +type redisConfig struct { + Password string + Addr string +} + +func NewConfig(filename string) (redisConfig, error) { + cfg, err := config.ParseConfig(filename) + if err != nil { + return redisConfig{}, fmt.Errorf("new redis config: %w", err) + } + + return redisConfig{ + Password: cfg.Get("requirepass"), + Addr: cfg.Get("host") + ":" + cfg.Get("port"), + }, nil +} diff --git a/internal/app/pg_conn.go b/internal/app/pg_conn.go new file mode 100644 index 0000000..3c902af --- /dev/null +++ b/internal/app/pg_conn.go @@ -0,0 +1,44 @@ +package app + +import ( + "context" + "fmt" + "os" + + "github.com/jackc/pgx/v5/pgxpool" +) + +const ( + maxConnDB = 500 + schemaDB = "pinspire" +) + +func NewPoolPG(ctx context.Context) (*pgxpool.Pool, error) { + cfg, err := pgxpool.ParseConfig(dsnPG()) + + cfg.MaxConns = maxConnDB + cfg.ConnConfig.RuntimeParams["search_path"] = schemaDB + + if err != nil { + return nil, fmt.Errorf("parse pool config: %w", err) + } + + pool, err := pgxpool.NewWithConfig(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("new postgres pool: %w", err) + } + + err = pool.Ping(ctx) + if err != nil { + pool.Close() + return nil, fmt.Errorf("ping new pool: %w", err) + } + return pool, err +} + +func dsnPG() string { + return fmt.Sprintf("postgres://%s:%s@%s:%s/%s", + os.Getenv("POSTGRES_USER"), os.Getenv("POSTGRES_PASSWORD"), + os.Getenv("POSTGRES_HOST"), os.Getenv("POSTGRES_PORT"), + os.Getenv("POSTGRES_DB")) +} diff --git a/internal/app/redis_conn.go b/internal/app/redis_conn.go new file mode 100644 index 0000000..8a7527c --- /dev/null +++ b/internal/app/redis_conn.go @@ -0,0 +1,22 @@ +package app + +import ( + "context" + "fmt" + + redis "github.com/redis/go-redis/v9" +) + +func NewRedisClient(ctx context.Context, cfg redisConfig) (*redis.Client, error) { + redisCl := redis.NewClient(&redis.Options{ + Addr: cfg.Addr, + Password: cfg.Password, + }) + + status := redisCl.Ping(ctx) + if status.Err() != nil { + redisCl.Close() + return nil, fmt.Errorf("new redis client: %w", status.Err()) + } + return redisCl, nil +} From 000fca39eb8bebd5652bd9700a5ad67105b83ae2 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 13 Nov 2023 01:33:04 +0300 Subject: [PATCH 174/266] TP-619 update: check author for edit pin --- internal/pkg/repository/pin/repo.go | 9 +++++---- internal/pkg/repository/ramrepo/pin.go | 2 +- internal/pkg/usecase/pin/update.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/pkg/repository/pin/repo.go b/internal/pkg/repository/pin/repo.go index 8beccc3..1e58d71 100644 --- a/internal/pkg/repository/pin/repo.go +++ b/internal/pkg/repository/pin/repo.go @@ -26,7 +26,7 @@ type Repository interface { SetLike(ctx context.Context, pinID, userID int) (int, error) IsSetLike(ctx context.Context, pinID, userID int) (bool, error) DelLike(ctx context.Context, pinID, userID int) (int, error) - EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error + EditPin(ctx context.Context, pinID, userID int, updateData S, titleTags []string) error GetCountLikeByPinID(ctx context.Context, pinID int) (int, error) GetTagsByPinID(ctx context.Context, pinID int) ([]entity.Tag, error) IsAvailableToUserAsContributorBoard(ctx context.Context, pinID, userID int) (bool, error) @@ -181,7 +181,7 @@ func (p *pinRepoPG) DeletePin(ctx context.Context, pinID, userID int) error { return nil } -func (p *pinRepoPG) EditPin(ctx context.Context, pinID int, updateData S, titleTags []string) error { +func (p *pinRepoPG) EditPin(ctx context.Context, pinID, userID int, updateData S, titleTags []string) error { if len(updateData) == 0 && titleTags == nil { return nil } @@ -192,7 +192,7 @@ func (p *pinRepoPG) EditPin(ctx context.Context, pinID int, updateData S, titleT } if len(updateData) != 0 { - err = p.updateHeaderPin(ctx, tx, pinID, updateData) + err = p.updateHeaderPin(ctx, tx, pinID, userID, updateData) } if err != nil { tx.Rollback(ctx) @@ -214,10 +214,11 @@ func (p *pinRepoPG) EditPin(ctx context.Context, pinID int, updateData S, titleT return nil } -func (p *pinRepoPG) updateHeaderPin(ctx context.Context, tx pgx.Tx, pinID int, newHeader S) error { +func (p *pinRepoPG) updateHeaderPin(ctx context.Context, tx pgx.Tx, pinID, userID int, newHeader S) error { sqlRow, args, err := p.sqlBuilder.Update("pin"). SetMap(newHeader). Where(sq.Eq{"id": pinID}). + Where(sq.Eq{"author": userID}). ToSql() if err != nil { return fmt.Errorf("build sql row for update header pin: %w", err) diff --git a/internal/pkg/repository/ramrepo/pin.go b/internal/pkg/repository/ramrepo/pin.go index 0a070fe..61ec4b1 100644 --- a/internal/pkg/repository/ramrepo/pin.go +++ b/internal/pkg/repository/ramrepo/pin.go @@ -61,7 +61,7 @@ func (r *ramPinRepo) EditPinTags(ctx context.Context, pinID, userID int, titlePi return ErrMethodUnimplemented } -func (r *ramPinRepo) EditPin(ctx context.Context, pinID int, updateData repository.S, titleTags []string) error { +func (r *ramPinRepo) EditPin(ctx context.Context, pinID, userID int, updateData repository.S, titleTags []string) error { return ErrMethodUnimplemented } diff --git a/internal/pkg/usecase/pin/update.go b/internal/pkg/usecase/pin/update.go index 6653c00..fc9281c 100644 --- a/internal/pkg/usecase/pin/update.go +++ b/internal/pkg/usecase/pin/update.go @@ -25,7 +25,7 @@ func (p *pinCase) EditPinByID(ctx context.Context, pinID, userID int, updateData if updateData.Public != nil { data["public"] = *updateData.Public } - err := p.repo.EditPin(ctx, pinID, data, updateData.Tags) + err := p.repo.EditPin(ctx, pinID, userID, data, updateData.Tags) if err != nil { return fmt.Errorf("edit pin by id: %w", err) } From 05b72242e7e3cda13b080e31401cbdce7ad4b417 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 15 Nov 2023 13:30:59 +0300 Subject: [PATCH 175/266] dev2 update: add insertBoard, CreateBoard, GetBoardByUserID tests --- .../repository/board/postgres/repo_test.go | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 internal/pkg/repository/board/postgres/repo_test.go diff --git a/internal/pkg/repository/board/postgres/repo_test.go b/internal/pkg/repository/board/postgres/repo_test.go new file mode 100644 index 0000000..38944af --- /dev/null +++ b/internal/pkg/repository/board/postgres/repo_test.go @@ -0,0 +1,267 @@ +package board + +import ( + "context" + "errors" + "fmt" + "log" + "testing" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + "github.com/jackc/pgx/v5" + "github.com/pashagolub/pgxmock/v2" + "github.com/stretchr/testify/require" +) + +func getTx(mockDB pgxmock.PgxPoolIface) pgx.Tx { + mockDB.ExpectBegin() + tx, err := mockDB.Begin(context.Background()) + if err != nil { + log.Fatalf("can't get tx for test: %s", err.Error()) + } + return tx +} + +func TestBoardRepo_insertBoard(t *testing.T) { + mockDB, err := pgxmock.NewPool() + + if err != nil { + t.Fatalf("test: get new mock pool - %s", err.Error()) + } + boardRepo := NewBoardRepoPG(mockDB) + + cases := []struct { + name string + tx pgx.Tx + board entity.Board + setMock func() + wantErr bool + expErr error + expID int + }{ + { + name: "insert valid board", + tx: getTx(mockDB), + board: entity.Board{ + AuthorID: 1, + Title: "title", + Description: "desc", + Public: false, + }, + setMock: func() { + row := mockDB.NewRows([]string{"id"}).AddRow(55) + mockDB.ExpectQuery("INSERT INTO board").WithArgs(1, "title", "desc", false).WillReturnRows(row) + }, + expID: 55, + }, + { + name: "invalid authorID", + tx: getTx(mockDB), + board: entity.Board{ + AuthorID: -1, + Title: "title", + Description: "desc", + Public: false, + }, + setMock: func() { + mockDB.ExpectQuery("INSERT INTO board").WithArgs(-1, "title", "desc", false).WillReturnError(pgx.ErrNoRows) + }, + wantErr: true, + expErr: fmt.Errorf("scan result of insterting new board: %w", pgx.ErrNoRows), + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + test.setMock() + boardID, err := boardRepo.insertBoard(context.Background(), test.tx, test.board) + + if test.wantErr { + require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) + require.Equal(t, test.expID, boardID) + } + + require.NoError(t, mockDB.ExpectationsWereMet()) + }) + } +} + +func TestBoardRepo_CreateBoard(t *testing.T) { + mockDB, err := pgxmock.NewPool() + + if err != nil { + t.Fatalf("test: get new mock pool - %s", err.Error()) + } + + boardRepo := NewBoardRepoPG(mockDB) + + cases := []struct { + name string + board entity.Board + tagTitles []string + setMock func() + wantErr bool + expErr error + expID int + }{ + { + name: "valid board", + board: entity.Board{ + AuthorID: 1, + Title: "title", + Description: "desc", + Public: false, + }, + tagTitles: []string{"cool", "view"}, + setMock: func() { + mockDB.ExpectBegin() + + row := mockDB.NewRows([]string{"id"}).AddRow(25) + mockDB.ExpectQuery("INSERT INTO board").WithArgs(1, "title", "desc", false).WillReturnRows(row) + + mockDB.ExpectExec("INSERT INTO tag").WithArgs("cool", "view").WillReturnResult(pgxmock.NewResult("INSERT", 2)) + mockDB.ExpectExec("INSERT INTO board_tag").WithArgs("cool", "view").WillReturnResult(pgxmock.NewResult("INSERT", 2)) + + mockDB.ExpectCommit() + }, + expID: 25, + }, + { + name: "invalid author id", + board: entity.Board{ + AuthorID: -1231, + Title: "title", + Description: "desc", + Public: false, + }, + tagTitles: []string{"cool", "view"}, + setMock: func() { + mockDB.ExpectBegin() + mockDB.ExpectQuery("INSERT INTO board").WithArgs(-1231, "title", "desc", false).WillReturnError(fmt.Errorf("scan result of insterting new board: %w", pgx.ErrNoRows)) + mockDB.ExpectRollback() + }, + wantErr: true, + expErr: errors.New("inserting board within transaction: scan result of insterting new board: scan result of insterting new board: no rows in result set"), + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + test.setMock() + boardID, err := boardRepo.CreateBoard(context.Background(), test.board, test.tagTitles) + + if test.wantErr { + require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) + require.Equal(t, test.expID, boardID) + } + + require.NoError(t, mockDB.ExpectationsWereMet()) + }) + } +} + +func TestBoardRepo_GetBoardsByUserID(t *testing.T) { + mockDB, err := pgxmock.NewPool() + + if err != nil { + t.Fatalf("test: get new mock pool - %s", err.Error()) + } + + boardRepo := NewBoardRepoPG(mockDB) + + cases := []struct { + name string + authorID int + isAuthor bool + accessableBoardsIDs []int + setMock func() + wantErr bool + expErr error + expBoards []dto.UserBoard + }{ + { + name: "valid user ID, author", + authorID: 2, + isAuthor: true, + accessableBoardsIDs: []int{1, 2}, + setMock: func() { + + rows := mockDB.NewRows([]string{"board.id", "board.title", "board.description", "board.created_at", "pins_number", "pins", "tags"}). + AddRow(4, "title", "desc", "12:12:2022", 1, []string{"/pic1"}, []string{"blue"}). + AddRow(5, "title_", "desc", "12:11:2022", 0, []string{}, []string{}) + + mockDB.ExpectQuery( + `SELECT (.+) FROM board LEFT JOIN membership ON (.+) WHERE (.+) GROUP BY (.+) ORDER BY (.+)`, + ).WithArgs(2).WillReturnRows(rows) + + }, + expBoards: []dto.UserBoard{ + { + BoardID: 4, + Title: "title", + Description: "desc", + CreatedAt: "12:12:2022", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"blue"}, + }, + { + BoardID: 5, + Title: "title_", + Description: "desc", + CreatedAt: "12:11:2022", + PinsNumber: 0, + Pins: []string{}, + TagTitles: []string{}, + }, + }, + }, + { + name: "valid user ID, contributor", + authorID: 3, + isAuthor: false, + accessableBoardsIDs: []int{3, 4}, + setMock: func() { + + rows := mockDB.NewRows([]string{"board.id", "board.title", "board.description", "board.created_at", "pins_number", "pins", "tags"}). + AddRow(4, "title", "desc", "12:12:2022", 1, []string{"/pic1"}, []string{"sun"}) + + mockDB.ExpectQuery( + `SELECT (.+) FROM board LEFT JOIN membership ON (.+) WHERE (.+) GROUP BY (.+) ORDER BY (.+)`, + ).WithArgs(3, true, 3, 4).WillReturnRows(rows) + + }, + expBoards: []dto.UserBoard{ + { + BoardID: 4, + Title: "title", + Description: "desc", + CreatedAt: "12:12:2022", + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"sun"}, + }, + }, + }, + } + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + test.setMock() + boards, err := boardRepo.GetBoardsByUserID(context.Background(), test.authorID, test.isAuthor, test.accessableBoardsIDs) + + if test.wantErr { + require.EqualError(t, err, test.expErr.Error()) + } else { + require.NoError(t, err) + require.Equal(t, test.expBoards, boards) + } + + require.NoError(t, mockDB.ExpectationsWereMet()) + }) + } +} From bc4eb86311b6f1c25e55fad5a2c061bae21885de Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Thu, 16 Nov 2023 23:01:36 +0300 Subject: [PATCH 176/266] TP-fcc_board_dataflow_change: removed dto from usecase, changed entity structure, add structures on the delivery layer --- internal/pkg/delivery/http/v1/board.go | 64 +++-- internal/pkg/entity/board/board.go | 36 ++- .../pkg/repository/board/mock/board_mock.go | 9 +- .../pkg/repository/board/postgres/repo.go | 28 +-- .../repository/board/postgres/repo_test.go | 62 ++--- internal/pkg/repository/board/repo.go | 5 +- internal/pkg/usecase/board/create.go | 6 +- internal/pkg/usecase/board/dto/board.go | 31 --- internal/pkg/usecase/board/get.go | 16 +- internal/pkg/usecase/board/mock/board_mock.go | 25 +- internal/pkg/usecase/board/update.go | 15 +- internal/pkg/usecase/board/usecase.go | 11 +- internal/pkg/usecase/board/usecase_test.go | 224 ++++++++++-------- 13 files changed, 281 insertions(+), 251 deletions(-) delete mode 100644 internal/pkg/usecase/board/dto/board.go diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 5d6eb83..f4e5f4d 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -8,13 +8,15 @@ import ( "strconv" "github.com/go-chi/chi/v5" - boardDTO "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" bCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +var TimeFormat = "2006-01-02" + var ( ErrEmptyTitle = errors.New("empty or null board title has been provided") ErrEmptyPubOpt = errors.New("null public option has been provided") @@ -36,6 +38,7 @@ var ( } ) +// data for board creation/update type BoardData struct { Title *string `json:"title" example:"new board"` Description *string `json:"description" example:"long desc"` @@ -43,6 +46,29 @@ type BoardData struct { Tags []string `json:"tags" example:"['blue', 'car']"` } +// board view for delivery layer +type CertainBoard struct { + ID int `json:"board_id" example:"22"` + Title string `json:"title" example:"new board"` + Description string `json:"description" example:"long desc"` + CreatedAt string `json:"created_at" example:"07-11-2023"` + PinsNumber int `json:"pins_number" example:"12"` + Pins []string `json:"pins" example:"['/pic1', '/pic2']"` + Tags []string `json:"tags" example:"['love', 'green']"` +} + +func ToCertainBoardFromService(board entity.BoardWithContent) CertainBoard { + return CertainBoard{ + ID: board.BoardInfo.ID, + Title: board.BoardInfo.Title, + Description: board.BoardInfo.Description, + CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), + PinsNumber: board.PinsNumber, + Pins: board.Pins, + Tags: board.TagTitles, + } +} + func (data *BoardData) Validate() error { if data.Title == nil || *data.Title == "" { return ErrInvalidBoardTitle @@ -98,8 +124,8 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { return } - var newBoardData BoardData - err := json.NewDecoder(r.Body).Decode(&newBoardData) + var newBoard BoardData + err := json.NewDecoder(r.Body).Decode(&newBoard) defer r.Body.Close() if err != nil { logger.Info("create board", log.F{"message", err.Error()}) @@ -108,7 +134,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { return } - err = newBoardData.Validate() + err = newBoard.Validate() if err != nil { logger.Info("create board", log.F{"message", err.Error()}) code, message := getErrCodeMessage(err) @@ -117,20 +143,19 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { } tagTitles := make([]string, 0) - if newBoardData.Tags != nil { - tagTitles = append(tagTitles, newBoardData.Tags...) + if newBoard.Tags != nil { + tagTitles = append(tagTitles, newBoard.Tags...) } authorID := r.Context().Value(auth.KeyCurrentUserID).(int) - newBoard := boardDTO.BoardData{ - Title: *newBoardData.Title, - Description: *newBoardData.Description, - Public: *newBoardData.Public, + + newBoardID, err := h.boardCase.CreateNewBoard(r.Context(), entity.Board{ + Title: *newBoard.Title, + Description: *newBoard.Description, + Public: *newBoard.Public, AuthorID: authorID, - TagTitles: tagTitles, - } + }, tagTitles) - newBoardID, err := h.boardCase.CreateNewBoard(r.Context(), newBoard) if err != nil { logger.Info("create board", log.F{"message", err.Error()}) code, message := getErrCodeMessage(err) @@ -157,7 +182,7 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { return } - userBoards, err := h.boardCase.GetBoardsByUsername(r.Context(), username) + boards, err := h.boardCase.GetBoardsByUsername(r.Context(), username) if err != nil { logger.Info("get user boards", log.F{"message", err.Error()}) code, message := getErrCodeMessage(err) @@ -165,6 +190,10 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { return } + userBoards := make([]CertainBoard, 0, len(boards)) + for _, board := range boards { + userBoards = append(userBoards, ToCertainBoardFromService(board)) + } err = responseOk(http.StatusOK, w, "got user boards successfully", userBoards) if err != nil { logger.Error(err.Error()) @@ -192,7 +221,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(http.StatusOK, w, "got certain board successfully", board) + err = responseOk(http.StatusOK, w, "got certain board successfully", ToCertainBoardFromService(board)) if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) @@ -266,14 +295,13 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { tagTitles = append(tagTitles, updatedData.Tags...) } - updatedBoard := boardDTO.BoardData{ + updatedBoard := entity.Board{ ID: int(boardID), Title: *updatedData.Title, Description: *updatedData.Description, Public: *updatedData.Public, - TagTitles: tagTitles, } - err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard) + err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard, tagTitles) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) code, message := getErrCodeMessage(err) diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go index b511134..92c37b6 100644 --- a/internal/pkg/entity/board/board.go +++ b/internal/pkg/entity/board/board.go @@ -7,17 +7,31 @@ import ( ) type Board struct { - ID int `json:"id,omitempty" example:"15"` - AuthorID int `json:"-"` - Title string `json:"title" example:"Sunny places"` - Description string `json:"description" example:"Sunny places desc"` - Public bool `json:"public" example:"true"` - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` - DeletedAt time.Time `json:"-"` + ID int `json:"id,omitempty" example:"15"` + AuthorID int `json:"-"` + Title string `json:"title" example:"Sunny places"` + Description string `json:"description" example:"Sunny places desc"` + Public bool `json:"public" example:"true"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"-"` + DeletedAt *time.Time `json:"-"` } -func (board *Board) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(board.Title) - sanitizer.Sanitize(board.Description) +type BoardWithContent struct { + BoardInfo Board + PinsNumber int + Pins []string + TagTitles []string +} + +func (b *Board) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(b.Title) + sanitizer.Sanitize(b.Description) +} + +func (b *BoardWithContent) Sanitize(sanitizer *bluemonday.Policy) { + b.BoardInfo.Sanitize(sanitizer) + for id, title := range b.TagTitles { + b.TagTitles[id] = sanitizer.Sanitize(title) + } } diff --git a/internal/pkg/repository/board/mock/board_mock.go b/internal/pkg/repository/board/mock/board_mock.go index 3da0192..d2af370 100644 --- a/internal/pkg/repository/board/mock/board_mock.go +++ b/internal/pkg/repository/board/mock/board_mock.go @@ -11,7 +11,6 @@ import ( board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" board0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" - board1 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" gomock "github.com/golang/mock/gomock" ) @@ -97,10 +96,10 @@ func (mr *MockRepositoryMockRecorder) GetBoardAuthorByBoardID(ctx, boardID inter } // GetBoardByID mocks base method. -func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board1.UserBoard, error) { +func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board.BoardWithContent, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBoardByID", ctx, boardID, hasAccess) - ret0, _ := ret[0].(board1.UserBoard) + ret0, _ := ret[0].(board.BoardWithContent) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -128,10 +127,10 @@ func (mr *MockRepositoryMockRecorder) GetBoardInfoForUpdate(ctx, boardID, hasAcc } // GetBoardsByUserID mocks base method. -func (m *MockRepository) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]board1.UserBoard, error) { +func (m *MockRepository) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]board.BoardWithContent, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBoardsByUserID", ctx, userID, isAuthor, accessableBoardsIDs) - ret0, _ := ret[0].([]board1.UserBoard) + ret0, _ := ret[0].([]board.BoardWithContent) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index b8916db..10cdefc 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -13,7 +13,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" repoBoard "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) type boardRepoPG struct { @@ -56,13 +55,13 @@ func (repo *boardRepoPG) CreateBoard(ctx context.Context, board entity.Board, ta return newBoardId, nil } -func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) { +func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]entity.BoardWithContent, error) { getBoardsQuery := boardRepo.sqlBuilder. Select( "board.id", "board.title", "COALESCE(board.description, '')", - "TO_CHAR(board.created_at, 'DD:MM:YYYY')", + "board.created_at", "COUNT(DISTINCT pin.id) AS pins_number", "ARRAY_REMOVE((ARRAY_AGG(DISTINCT pin.picture))[:3], NULL) AS pins", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). @@ -102,10 +101,10 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, } defer rows.Close() - boards := make([]dto.UserBoard, 0) + boards := make([]entity.BoardWithContent, 0) for rows.Next() { - board := dto.UserBoard{} - err = rows.Scan(&board.BoardID, &board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) + board := entity.BoardWithContent{} + err = rows.Scan(&board.BoardInfo.ID, &board.BoardInfo.Title, &board.BoardInfo.Description, &board.BoardInfo.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) if err != nil { return nil, fmt.Errorf("scanning the result of get boards by user id query: %w", err) } @@ -115,14 +114,14 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, return boards, nil } -func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) { +func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board entity.BoardWithContent, err error) { getBoardByIdQuery := repo.sqlBuilder. Select( "board.id", "board.author", "board.title", "COALESCE(board.description, '')", - "TO_CHAR(board.created_at, 'DD:MM:YYYY')", + "board.created_at", "COUNT(DISTINCT pin.id) AS pins_number", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT pin.picture), NULL) AS pins", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). @@ -147,18 +146,18 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces sqlRow, args, err := getBoardByIdQuery.ToSql() if err != nil { - return dto.UserBoard{}, fmt.Errorf("building get board by id query: %w", err) + return entity.BoardWithContent{}, fmt.Errorf("building get board by id query: %w", err) } row := repo.db.QueryRow(ctx, sqlRow, args...) - board = dto.UserBoard{} - err = row.Scan(&board.BoardID, &board.AuthorID,&board.Title, &board.Description, &board.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) + board = entity.BoardWithContent{} + err = row.Scan(&board.BoardInfo.ID, &board.BoardInfo.AuthorID, &board.BoardInfo.Title, &board.BoardInfo.Description, &board.BoardInfo.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) if err != nil { switch err { case pgx.ErrNoRows: - return dto.UserBoard{}, repository.ErrNoData + return entity.BoardWithContent{}, repository.ErrNoData default: - return dto.UserBoard{}, fmt.Errorf("scan result of get board by id query: %w", err) + return entity.BoardWithContent{}, fmt.Errorf("scan result of get board by id query: %w", err) } } @@ -184,8 +183,7 @@ func (repo *boardRepoPG) GetBoardInfoForUpdate(ctx context.Context, boardID int, getBoardByIdQuery = getBoardByIdQuery.GroupBy( "board.id", "board.title", - "board.description", - "board.created_at"). + "board.description"). OrderBy("board.id ASC") sqlRow, args, err := getBoardByIdQuery.ToSql() diff --git a/internal/pkg/repository/board/postgres/repo_test.go b/internal/pkg/repository/board/postgres/repo_test.go index 38944af..96a4c87 100644 --- a/internal/pkg/repository/board/postgres/repo_test.go +++ b/internal/pkg/repository/board/postgres/repo_test.go @@ -6,9 +6,9 @@ import ( "fmt" "log" "testing" + "time" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/jackc/pgx/v5" "github.com/pashagolub/pgxmock/v2" "github.com/stretchr/testify/require" @@ -182,7 +182,7 @@ func TestBoardRepo_GetBoardsByUserID(t *testing.T) { setMock func() wantErr bool expErr error - expBoards []dto.UserBoard + expBoards []entity.BoardWithContent }{ { name: "valid user ID, author", @@ -192,32 +192,36 @@ func TestBoardRepo_GetBoardsByUserID(t *testing.T) { setMock: func() { rows := mockDB.NewRows([]string{"board.id", "board.title", "board.description", "board.created_at", "pins_number", "pins", "tags"}). - AddRow(4, "title", "desc", "12:12:2022", 1, []string{"/pic1"}, []string{"blue"}). - AddRow(5, "title_", "desc", "12:11:2022", 0, []string{}, []string{}) + AddRow(4, "title", "desc", &time.Time{}, 1, []string{"/pic1"}, []string{"blue"}). + AddRow(5, "title_", "desc", &time.Time{}, 0, []string{}, []string{}) mockDB.ExpectQuery( `SELECT (.+) FROM board LEFT JOIN membership ON (.+) WHERE (.+) GROUP BY (.+) ORDER BY (.+)`, ).WithArgs(2).WillReturnRows(rows) }, - expBoards: []dto.UserBoard{ + expBoards: []entity.BoardWithContent{ { - BoardID: 4, - Title: "title", - Description: "desc", - CreatedAt: "12:12:2022", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"blue"}, + BoardInfo: entity.Board{ + ID: 4, + Title: "title", + Description: "desc", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"blue"}, }, { - BoardID: 5, - Title: "title_", - Description: "desc", - CreatedAt: "12:11:2022", - PinsNumber: 0, - Pins: []string{}, - TagTitles: []string{}, + BoardInfo: entity.Board{ + ID: 5, + Title: "title_", + Description: "desc", + CreatedAt: &time.Time{}, + }, + PinsNumber: 0, + Pins: []string{}, + TagTitles: []string{}, }, }, }, @@ -229,22 +233,24 @@ func TestBoardRepo_GetBoardsByUserID(t *testing.T) { setMock: func() { rows := mockDB.NewRows([]string{"board.id", "board.title", "board.description", "board.created_at", "pins_number", "pins", "tags"}). - AddRow(4, "title", "desc", "12:12:2022", 1, []string{"/pic1"}, []string{"sun"}) + AddRow(4, "title", "desc", &time.Time{}, 1, []string{"/pic1"}, []string{"sun"}) mockDB.ExpectQuery( `SELECT (.+) FROM board LEFT JOIN membership ON (.+) WHERE (.+) GROUP BY (.+) ORDER BY (.+)`, ).WithArgs(3, true, 3, 4).WillReturnRows(rows) }, - expBoards: []dto.UserBoard{ + expBoards: []entity.BoardWithContent{ { - BoardID: 4, - Title: "title", - Description: "desc", - CreatedAt: "12:12:2022", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"sun"}, + BoardInfo: entity.Board{ + ID: 4, + Title: "title", + Description: "desc", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"sun"}, }, }, }, diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 153c2a7..5d1fb4c 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -5,14 +5,13 @@ import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) //go:generate mockgen -destination=./mock/board_mock.go -package=mock -source=repo.go Repository type Repository interface { CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) - GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]dto.UserBoard, error) - GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board dto.UserBoard, err error) + GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]entity.BoardWithContent, error) + GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board entity.BoardWithContent, err error) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) diff --git a/internal/pkg/usecase/board/create.go b/internal/pkg/usecase/board/create.go index 8a12dc1..dfab022 100644 --- a/internal/pkg/usecase/board/create.go +++ b/internal/pkg/usecase/board/create.go @@ -5,17 +5,15 @@ import ( "fmt" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *boardUsecase) CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) { - +func (bCase *boardUsecase) CreateNewBoard(ctx context.Context, newBoard entity.Board, tagTitles []string) (int, error) { newBoardID, err := bCase.boardRepo.CreateBoard(ctx, entity.Board{ AuthorID: newBoard.AuthorID, Title: newBoard.Title, Description: newBoard.Description, Public: newBoard.Public, - }, newBoard.TagTitles) + }, tagTitles) if err != nil { return 0, fmt.Errorf("create new board: %w", err) diff --git a/internal/pkg/usecase/board/dto/board.go b/internal/pkg/usecase/board/dto/board.go deleted file mode 100644 index 7308e69..0000000 --- a/internal/pkg/usecase/board/dto/board.go +++ /dev/null @@ -1,31 +0,0 @@ -package board - -import "github.com/microcosm-cc/bluemonday" - -type BoardData struct { - ID int - Title string - Description string - AuthorID int - Public bool - TagTitles []string -} - -type UserBoard struct { - BoardID int `json:"board_id" example:"15"` - AuthorID int `json:"author_id,omitempty" example:"15"` - Title string `json:"title" example:"Sunny places"` - Description string `json:"description" example:"Sunny places"` - CreatedAt string `json:"created_at" example:"08.10.2020"` - PinsNumber int `json:"pins_number" example:"10"` - Pins []string `json:"pins" example:"['/upload/pin/pic1', '/upload/pin/pic2']"` - TagTitles []string `json:"tags" example:"['flowers', 'sunrise']"` -} - -func (uBoard *UserBoard) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(uBoard.Title) - sanitizer.Sanitize(uBoard.Description) - for id, title := range uBoard.TagTitles { - uBoard.TagTitles[id] = sanitizer.Sanitize(title) - } -} diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index dafd821..9e02946 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -4,12 +4,12 @@ import ( "context" "fmt" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) -func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) { +func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]entity.BoardWithContent, error) { userID, err := bCase.userRepo.GetUserIdByUsername(ctx, username) if err != nil { switch err { @@ -42,20 +42,20 @@ func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username str return boards, nil } -func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) { +func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (entity.BoardWithContent, error) { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) if err != nil { switch err { case repository.ErrNoData: - return dto.UserBoard{}, ErrNoSuchBoard + return entity.BoardWithContent{}, ErrNoSuchBoard default: - return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) + return entity.BoardWithContent{}, fmt.Errorf("get certain board: %w", err) } } boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) if err != nil { - return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) + return entity.BoardWithContent{}, fmt.Errorf("get certain board: %w", err) } boardContributorsIDs := make([]int, 0, len(boardContributors)) @@ -74,9 +74,9 @@ func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (dt if err != nil { switch err { case repository.ErrNoData: - return dto.UserBoard{}, ErrNoSuchBoard + return entity.BoardWithContent{}, ErrNoSuchBoard default: - return dto.UserBoard{}, fmt.Errorf("get certain board: %w", err) + return entity.BoardWithContent{}, fmt.Errorf("get certain board: %w", err) } } diff --git a/internal/pkg/usecase/board/mock/board_mock.go b/internal/pkg/usecase/board/mock/board_mock.go index a64e46d..39845c3 100644 --- a/internal/pkg/usecase/board/mock/board_mock.go +++ b/internal/pkg/usecase/board/mock/board_mock.go @@ -10,7 +10,6 @@ import ( board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" pin "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" - board0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" gomock "github.com/golang/mock/gomock" ) @@ -52,18 +51,18 @@ func (mr *MockUsecaseMockRecorder) CheckAvailabilityFeedPinCfgOnBoard(ctx, cfg, } // CreateNewBoard mocks base method. -func (m *MockUsecase) CreateNewBoard(ctx context.Context, newBoard board0.BoardData) (int, error) { +func (m *MockUsecase) CreateNewBoard(ctx context.Context, newBoard board.Board, tagTitles []string) (int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateNewBoard", ctx, newBoard) + ret := m.ctrl.Call(m, "CreateNewBoard", ctx, newBoard, tagTitles) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateNewBoard indicates an expected call of CreateNewBoard. -func (mr *MockUsecaseMockRecorder) CreateNewBoard(ctx, newBoard interface{}) *gomock.Call { +func (mr *MockUsecaseMockRecorder) CreateNewBoard(ctx, newBoard, tagTitles interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewBoard", reflect.TypeOf((*MockUsecase)(nil).CreateNewBoard), ctx, newBoard) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewBoard", reflect.TypeOf((*MockUsecase)(nil).CreateNewBoard), ctx, newBoard, tagTitles) } // DeleteCertainBoard mocks base method. @@ -111,10 +110,10 @@ func (mr *MockUsecaseMockRecorder) GetBoardInfoForUpdate(ctx, boardID interface{ } // GetBoardsByUsername mocks base method. -func (m *MockUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]board0.UserBoard, error) { +func (m *MockUsecase) GetBoardsByUsername(ctx context.Context, username string) ([]board.BoardWithContent, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBoardsByUsername", ctx, username) - ret0, _ := ret[0].([]board0.UserBoard) + ret0, _ := ret[0].([]board.BoardWithContent) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -126,10 +125,10 @@ func (mr *MockUsecaseMockRecorder) GetBoardsByUsername(ctx, username interface{} } // GetCertainBoard mocks base method. -func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board0.UserBoard, error) { +func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board.BoardWithContent, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCertainBoard", ctx, boardID) - ret0, _ := ret[0].(board0.UserBoard) + ret0, _ := ret[0].(board.BoardWithContent) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -141,15 +140,15 @@ func (mr *MockUsecaseMockRecorder) GetCertainBoard(ctx, boardID interface{}) *go } // UpdateBoardInfo mocks base method. -func (m *MockUsecase) UpdateBoardInfo(ctx context.Context, updatedData board0.BoardData) error { +func (m *MockUsecase) UpdateBoardInfo(ctx context.Context, updatedBoard board.Board, tagTitles []string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateBoardInfo", ctx, updatedData) + ret := m.ctrl.Call(m, "UpdateBoardInfo", ctx, updatedBoard, tagTitles) ret0, _ := ret[0].(error) return ret0 } // UpdateBoardInfo indicates an expected call of UpdateBoardInfo. -func (mr *MockUsecaseMockRecorder) UpdateBoardInfo(ctx, updatedData interface{}) *gomock.Call { +func (mr *MockUsecaseMockRecorder) UpdateBoardInfo(ctx, updatedBoard, tagTitles interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBoardInfo", reflect.TypeOf((*MockUsecase)(nil).UpdateBoardInfo), ctx, updatedData) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBoardInfo", reflect.TypeOf((*MockUsecase)(nil).UpdateBoardInfo), ctx, updatedBoard, tagTitles) } diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go index 22e50e4..c30180c 100644 --- a/internal/pkg/usecase/board/update.go +++ b/internal/pkg/usecase/board/update.go @@ -9,7 +9,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" repoBoard "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" ) func (bCase *boardUsecase) GetBoardInfoForUpdate(ctx context.Context, boardID int) (entity.Board, []string, error) { @@ -57,8 +56,8 @@ func (bCase *boardUsecase) GetBoardInfoForUpdate(ctx context.Context, boardID in return board, tagTitles, nil } -func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error { - boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, updatedData.ID) +func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedBoard entity.Board, tagTitles []string) error { + boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, updatedBoard.ID) if err != nil { switch err { case repository.ErrNoData: @@ -74,11 +73,11 @@ func (bCase *boardUsecase) UpdateBoardInfo(ctx context.Context, updatedData dto. } err = bCase.boardRepo.UpdateBoard(ctx, board.Board{ - ID: updatedData.ID, - Title: updatedData.Title, - Description: updatedData.Description, - Public: updatedData.Public, - }, updatedData.TagTitles) + ID: updatedBoard.ID, + Title: updatedBoard.Title, + Description: updatedBoard.Description, + Public: updatedBoard.Public, + }, tagTitles) if err != nil { return fmt.Errorf("update certain board: %w", err) } diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 3b5c1df..641945c 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -3,22 +3,21 @@ package board import ( "context" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/microcosm-cc/bluemonday" ) //go:generate mockgen -destination=./mock/board_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { - CreateNewBoard(ctx context.Context, newBoard dto.BoardData) (int, error) - GetBoardsByUsername(ctx context.Context, username string) ([]dto.UserBoard, error) - GetCertainBoard(ctx context.Context, boardID int) (dto.UserBoard, error) + CreateNewBoard(ctx context.Context, newBoard entity.Board, tagTitles []string) (int, error) + GetBoardsByUsername(ctx context.Context, username string) ([]entity.BoardWithContent, error) + GetCertainBoard(ctx context.Context, boardID int) (entity.BoardWithContent, error) GetBoardInfoForUpdate(ctx context.Context, boardID int) (entity.Board, []string, error) - UpdateBoardInfo(ctx context.Context, updatedData dto.BoardData) error + UpdateBoardInfo(ctx context.Context, updatedBoard entity.Board, tagTitles []string) error DeleteCertainBoard(ctx context.Context, boardID int) error FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error CheckAvailabilityFeedPinCfgOnBoard(ctx context.Context, cfg pin.FeedPinConfig, userID int, isAuth bool) error diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index ab9dd89..f3c20dd 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -3,6 +3,7 @@ package board import ( "context" "testing" + "time" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" uEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -10,7 +11,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" mock_board "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/mock" mock_user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user/mock" - dto "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board/dto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/golang/mock/gomock" "github.com/microcosm-cc/bluemonday" @@ -41,24 +41,25 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { } tests := []struct { - name string - inCtx context.Context - newBoardData dto.BoardData - CreateBoard CreateBoard - expNewID int - wantErr bool - expErr error + name string + inCtx context.Context + newBoard entity.Board + tagTitles []string + CreateBoard CreateBoard + expNewID int + wantErr bool + expErr error }{ { name: "valid board data", inCtx: context.Background(), - newBoardData: dto.BoardData{ + newBoard: entity.Board{ Title: "valid title", Description: "some description", AuthorID: 45, Public: false, - TagTitles: []string{"nice", "green"}, }, + tagTitles: []string{"nice", "green"}, CreateBoard: func(mockRepo *mock_board.MockRepository, ctx context.Context, newBoardData entity.Board, tagTitles []string) { mockRepo.EXPECT().CreateBoard(ctx, newBoardData, tagTitles).Return(1, nil).Times(1) }, @@ -73,14 +74,14 @@ func TestBoardUsecase_CreateNewBoard(t *testing.T) { mockBoardRepo := mock_board.NewMockRepository(ctl) test.CreateBoard(mockBoardRepo, test.inCtx, entity.Board{ - AuthorID: test.newBoardData.AuthorID, - Title: test.newBoardData.Title, - Description: test.newBoardData.Description, - Public: test.newBoardData.Public, - }, test.newBoardData.TagTitles) + AuthorID: test.newBoard.AuthorID, + Title: test.newBoard.Title, + Description: test.newBoard.Description, + Public: test.newBoard.Public, + }, test.tagTitles) boardUsecase := New(log, mockBoardRepo, nil, sanitizer) - newBoardID, err := boardUsecase.CreateNewBoard(test.inCtx, test.newBoardData) + newBoardID, err := boardUsecase.CreateNewBoard(test.inCtx, test.newBoard, test.tagTitles) if test.wantErr { require.EqualError(t, err, test.expErr.Error()) @@ -102,7 +103,8 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { tests := []struct { name string inCtx context.Context - updatedBoardData dto.BoardData + updatedBoard entity.Board + tagTitles []string GetBoardAuthorByBoardID GetBoardAuthorByBoardID UpdateBoard UpdateBoard wantErr bool @@ -111,13 +113,13 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { { name: "valid data, authenticated, with access", inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 1), - updatedBoardData: dto.BoardData{ + updatedBoard: entity.Board{ ID: 25, Title: "valid title", Description: "some description", Public: false, - TagTitles: []string{"nice", "green"}, }, + tagTitles: []string{"nice", "green"}, GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) }, @@ -128,13 +130,13 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { { name: "valid data, authenticated, no access", inCtx: context.WithValue(context.Background(), auth.KeyCurrentUserID, 534), - updatedBoardData: dto.BoardData{ + updatedBoard: entity.Board{ ID: 25, Title: "valid title", Description: "some description", Public: false, - TagTitles: []string{"nice", "green"}, }, + tagTitles: []string{"nice", "green"}, GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) }, @@ -146,13 +148,13 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { { name: "valid data, no_auth", inCtx: context.Background(), - updatedBoardData: dto.BoardData{ + updatedBoard: entity.Board{ ID: 25, Title: "valid title", Description: "some description", Public: false, - TagTitles: []string{"nice", "green"}, }, + tagTitles: []string{"nice", "green"}, GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(1, nil).Times(1) }, @@ -164,13 +166,13 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { { name: "invalid board id", inCtx: context.Background(), - updatedBoardData: dto.BoardData{ + updatedBoard: entity.Board{ ID: 122125, Title: "valid title", Description: "some description", Public: false, - TagTitles: []string{"nice", "green"}, }, + tagTitles: []string{"nice", "green"}, GetBoardAuthorByBoardID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int) { mockRepo.EXPECT().GetBoardAuthorByBoardID(ctx, boardID).Return(0, repository.ErrNoData).Times(1) }, @@ -187,16 +189,16 @@ func TestBoardUsecase_UpdateBoardInfo(t *testing.T) { defer ctl.Finish() mockBoardRepo := mock_board.NewMockRepository(ctl) - test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.updatedBoardData.ID) + test.GetBoardAuthorByBoardID(mockBoardRepo, test.inCtx, test.updatedBoard.ID) test.UpdateBoard(mockBoardRepo, test.inCtx, entity.Board{ - ID: test.updatedBoardData.ID, - Title: test.updatedBoardData.Title, - Description: test.updatedBoardData.Description, - Public: test.updatedBoardData.Public, - }, test.updatedBoardData.TagTitles) + ID: test.updatedBoard.ID, + Title: test.updatedBoard.Title, + Description: test.updatedBoard.Description, + Public: test.updatedBoard.Public, + }, test.tagTitles) boardUsecase := New(log, mockBoardRepo, nil, sanitizer) - err := boardUsecase.UpdateBoardInfo(test.inCtx, test.updatedBoardData) + err := boardUsecase.UpdateBoardInfo(test.inCtx, test.updatedBoard, test.tagTitles) if test.wantErr { require.EqualError(t, err, test.expErr.Error()) @@ -221,7 +223,7 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { GetUserIdByUsername GetUserIdByUsername GetContributorBoardsIDs GetContributorBoardsIDs GetBoardsByUserID GetBoardsByUserID - expBoards []dto.UserBoard + expBoards []entity.BoardWithContent wantErr bool expErr error }{ @@ -237,35 +239,43 @@ func TestBoardUsecase_GetBoardsByUsername(t *testing.T) { }, GetBoardsByUserID: func(mockRepo *mock_board.MockRepository, ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) { mockRepo.EXPECT().GetBoardsByUserID(ctx, userID, isAuthor, accessableBoardsIDs).Return( - []dto.UserBoard{ + []entity.BoardWithContent{ { - BoardID: 23, - Title: "title", - CreatedAt: "25:10:2022", + BoardInfo: entity.Board{ + ID: 23, + Title: "title", + CreatedAt: &time.Time{}, + }, PinsNumber: 2, Pins: []string{"/pic1", "/pic2"}, }, { - BoardID: 21, - Title: "title21", - CreatedAt: "25:10:2012", + BoardInfo: entity.Board{ + ID: 21, + Title: "title21", + CreatedAt: &time.Time{}, + }, PinsNumber: 0, Pins: []string{}, }, }, nil).Times(1) }, - expBoards: []dto.UserBoard{ + expBoards: []entity.BoardWithContent{ { - BoardID: 23, - Title: "title", - CreatedAt: "25:10:2022", + BoardInfo: entity.Board{ + ID: 23, + Title: "title", + CreatedAt: &time.Time{}, + }, PinsNumber: 2, Pins: []string{"/pic1", "/pic2"}, }, { - BoardID: 21, - Title: "title21", - CreatedAt: "25:10:2012", + BoardInfo: entity.Board{ + ID: 21, + Title: "title21", + CreatedAt: &time.Time{}, + }, PinsNumber: 0, Pins: []string{}, }, @@ -328,7 +338,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { GetContributorsByBoardID GetContributorsByBoardID GetBoardByID GetBoardByID hasAccess bool - expBoard dto.UserBoard + expBoard entity.BoardWithContent wantErr bool expErr error }{ @@ -343,25 +353,29 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{ - BoardID: boardID, - Title: "title", - Description: "description", - CreatedAt: "10:10:2020", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"good", "bad"}, + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{ + BoardInfo: entity.Board{ + ID: boardID, + Title: "title", + Description: "description", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, }, nil).Times(1) }, hasAccess: true, - expBoard: dto.UserBoard{ - BoardID: 22, - Title: "title", - Description: "description", - CreatedAt: "10:10:2020", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"good", "bad"}, + expBoard: entity.BoardWithContent{ + BoardInfo: entity.Board{ + ID: 22, + Title: "title", + Description: "description", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, }, }, { @@ -375,25 +389,29 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 1}}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{ - BoardID: boardID, - Title: "title", - Description: "description", - CreatedAt: "10:10:2020", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"good", "bad"}, + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{ + BoardInfo: entity.Board{ + ID: 22, + Title: "title", + Description: "description", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, }, nil).Times(1) }, hasAccess: true, - expBoard: dto.UserBoard{ - BoardID: 22, - Title: "title", - Description: "description", - CreatedAt: "10:10:2020", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"good", "bad"}, + expBoard: entity.BoardWithContent{ + BoardInfo: entity.Board{ + ID: 22, + Title: "title", + Description: "description", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, }, }, { @@ -407,10 +425,10 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{}, repository.ErrNoData).Times(1) + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{}, repository.ErrNoData).Times(1) }, hasAccess: false, - expBoard: dto.UserBoard{}, + expBoard: entity.BoardWithContent{}, wantErr: true, expErr: ErrNoSuchBoard, }, @@ -425,10 +443,10 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{}, repository.ErrNoData).Times(1) + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{}, repository.ErrNoData).Times(1) }, hasAccess: false, - expBoard: dto.UserBoard{}, + expBoard: entity.BoardWithContent{}, wantErr: true, expErr: ErrNoSuchBoard, }, @@ -443,25 +461,29 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(dto.UserBoard{ - BoardID: boardID, - Title: "title", - Description: "description", - CreatedAt: "10:10:2020", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"good", "bad"}, + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{ + BoardInfo: entity.Board{ + ID: boardID, + Title: "title", + Description: "description", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, }, nil).Times(1) }, hasAccess: false, - expBoard: dto.UserBoard{ - BoardID: 22, - Title: "title", - Description: "description", - CreatedAt: "10:10:2020", - PinsNumber: 1, - Pins: []string{"/pic1"}, - TagTitles: []string{"good", "bad"}, + expBoard: entity.BoardWithContent{ + BoardInfo: entity.Board{ + ID: 22, + Title: "title", + Description: "description", + CreatedAt: &time.Time{}, + }, + PinsNumber: 1, + Pins: []string{"/pic1"}, + TagTitles: []string{"good", "bad"}, }, }, { @@ -475,7 +497,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { }, - expBoard: dto.UserBoard{}, + expBoard: entity.BoardWithContent{}, wantErr: true, expErr: ErrNoSuchBoard, }, From 305555fef43e8a454f4e3bce9f6609862327ca89 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 17 Nov 2023 12:14:26 +0300 Subject: [PATCH 177/266] dev2 update: fixed pins view on board(s) --- internal/pkg/repository/board/postgres/repo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 10cdefc..9cbe5d7 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -62,8 +62,8 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, "board.title", "COALESCE(board.description, '')", "board.created_at", - "COUNT(DISTINCT pin.id) AS pins_number", - "ARRAY_REMOVE((ARRAY_AGG(DISTINCT pin.picture))[:3], NULL) AS pins", + "COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number", + "COALESCE(ARRAY_REMOVE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL))[:3], NULL), array[]::text[]) AS pins", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). From("board"). LeftJoin("membership ON board.id = membership.board_id"). @@ -122,8 +122,8 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces "board.title", "COALESCE(board.description, '')", "board.created_at", - "COUNT(DISTINCT pin.id) AS pins_number", - "ARRAY_REMOVE(ARRAY_AGG(DISTINCT pin.picture), NULL) AS pins", + "COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number", + "COALESCE(ARRAY_REMOVE(ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL), NULL), array[]::text[]) AS pins", "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). From("board"). LeftJoin("board_tag ON board.id = board_tag.board_id"). From 8c2dc94c58735a84eb25f108b31c5880d7136f3c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 17 Nov 2023 17:13:37 +0300 Subject: [PATCH 178/266] dev2 update: changed null value processing in board repo methods --- internal/pkg/repository/board/postgres/repo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 9cbe5d7..681dc57 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -63,8 +63,8 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, "COALESCE(board.description, '')", "board.created_at", "COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number", - "COALESCE(ARRAY_REMOVE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL))[:3], NULL), array[]::text[]) AS pins", - "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). + "COALESCE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL AND pin.picture IS NOT NULL))[:3], ARRAY[]::TEXT[]) AS pins", + "COALESCE(ARRAY_AGG(DISTINCT tag.title) FILTER (WHERE tag.title IS NOT NULL), ARRAY[]::TEXT[]) AS tag_titles"). From("board"). LeftJoin("membership ON board.id = membership.board_id"). LeftJoin("pin ON membership.pin_id = pin.id"). @@ -123,8 +123,8 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces "COALESCE(board.description, '')", "board.created_at", "COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number", - "COALESCE(ARRAY_REMOVE(ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL), NULL), array[]::text[]) AS pins", - "ARRAY_REMOVE(ARRAY_AGG(DISTINCT tag.title), NULL) AS tag_titles"). + "COALESCE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL AND pin.picture IS NOT NULL)), ARRAY[]::TEXT[]) AS pins", + "COALESCE(ARRAY_AGG(DISTINCT tag.title) FILTER (WHERE tag.title IS NOT NULL), ARRAY[]::TEXT[]) AS tag_titles"). From("board"). LeftJoin("board_tag ON board.id = board_tag.board_id"). LeftJoin("tag ON board_tag.tag_id = tag.id"). From 158ada4407c3e0ca63f3dcf73b6ae760b35fb74a Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 17 Nov 2023 17:22:37 +0300 Subject: [PATCH 179/266] dev 2 update: fixed double delete on board --- internal/pkg/repository/board/postgres/queries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index 25990b1..d86f049 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -7,7 +7,7 @@ const ( SelectBoardContributorsByBoardIdQuery = "SELECT user_id FROM contributor WHERE board_id = $1;" UpdateBoardByIdQuery = "UPDATE board SET title = $1, description = $2, public = $3 WHERE id = $4 AND deleted_at IS NULL;" GetContributorBoardsIDs = "SELECT board_id FROM contributor WHERE user_id = $1;" - DeleteBoardByIdQuery = "UPDATE board SET deleted_at = $1 WHERE id = $2;" + DeleteBoardByIdQuery = "UPDATE board SET deleted_at = $1 WHERE id = $2 AND deleted_at IS NULL;" DeleteCurrentBoardTags = "DELETE FROM board_tag WHERE board_id = $1;" SelectAuthorOrContributorRole = `SELECT board.author, role.name FROM board LEFT JOIN contributor ON contributor.board_id = board.id AND contributor.user_id = $1 LEFT JOIN role From acabd7493b21f42340a62a201c673dbfeb1cfd95 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 17 Nov 2023 20:19:25 +0300 Subject: [PATCH 180/266] TP-d07 update: rename func to dirToSave --- internal/pkg/repository/image/repo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/repository/image/repo.go b/internal/pkg/repository/image/repo.go index ca1a42e..4518f0c 100644 --- a/internal/pkg/repository/image/repo.go +++ b/internal/pkg/repository/image/repo.go @@ -27,7 +27,7 @@ func NewImageRepoFS(basePath string) *imageRepoFS { return &imageRepoFS{ basePath: basePath, m: sync.Mutex{}, - directoryToSave: dirTosave, + directoryToSave: dirToSave, } } @@ -67,6 +67,6 @@ func (img *imageRepoFS) SetDirToSave(fn func() string) { img.m.Unlock() } -func dirTosave() string { +func dirToSave() string { return time.Now().UTC().Format("2006/01/02/") } From 1304a0ee264baad6dc29ef9431466c565a388775 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 18 Nov 2023 15:04:46 +0300 Subject: [PATCH 181/266] update: use PrefixURLImage --- internal/pkg/usecase/image/usecase.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go index 5a3ac94..799ed62 100644 --- a/internal/pkg/usecase/image/usecase.go +++ b/internal/pkg/usecase/image/usecase.go @@ -12,7 +12,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) -const PrefixURLImage = "httsp://pinspire.online:8081/" +const PrefixURLImage = "https://pinspire.online:8081/" var ErrInvalidImage = errors.New("invalid images") var ErrUploadFile = errors.New("file upload failed") @@ -48,5 +48,5 @@ func (img *imageCase) UploadImage(path string, mimeType string, size int64, imag if written != size { return "", ErrUploadFile } - return "https://pinspire.online:8081/" + filename, nil + return PrefixURLImage + filename, nil } From 1459ce0a2adb3684d783b2887b97f8444b66239e Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sat, 18 Nov 2023 15:14:36 +0300 Subject: [PATCH 182/266] dev2 update: changed permissions on uploaded images, added :8081 in AllowedOrigins --- internal/api/server/router/router.go | 2 +- internal/pkg/repository/image/repo.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 368fb08..739da97 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -30,7 +30,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess c := cors.New(cors.Options{ AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443", - "https://pinspire.online:1444", "https://pinspire.online:1445", "https://pinspire.online:1446"}, + "https://pinspire.online:1444", "https://pinspire.online:1445", "https://pinspire.online:1446", "https://pinspire.online:8081"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut}, AllowCredentials: true, AllowedHeaders: []string{"content-type", cfgCSRF.Header}, diff --git a/internal/pkg/repository/image/repo.go b/internal/pkg/repository/image/repo.go index ca1a42e..c71286b 100644 --- a/internal/pkg/repository/image/repo.go +++ b/internal/pkg/repository/image/repo.go @@ -39,7 +39,7 @@ func (img *imageRepoFS) SaveImage(prefixPath, extension string, image io.Reader) filename = id.String() dir := img.basePath + prefixPath + img.directoryToSave() - err = os.MkdirAll(dir, 0750) + err = os.MkdirAll(dir, 0777) if err != nil { return "", 0, fmt.Errorf("mkdir %s to save file: %w", dir, err) } From c82bdb301ed01a1e244eadd0ea921ac0c165ffa4 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 19 Nov 2023 00:20:25 +0300 Subject: [PATCH 183/266] TP-f07 add: entity Message --- internal/api/server/router/router.go | 19 +- internal/pkg/delivery/http/v1/message.go | 49 +++ internal/pkg/delivery/http/v1/parse.go | 25 ++ internal/pkg/entity/message/message.go | 16 + internal/pkg/repository/message/repo.go | 14 + internal/pkg/service/auth.go | 226 ----------- internal/pkg/service/auth_test.go | 467 ---------------------- internal/pkg/service/pin.go | 49 --- internal/pkg/service/pin_test.go | 158 -------- internal/pkg/service/response.go | 57 --- internal/pkg/service/service.go | 24 -- internal/pkg/service/validation.go | 96 ----- internal/pkg/service/validation_test.go | 68 ---- internal/pkg/usecase/message/usecase.go | 15 + internal/usecases/pin/usecase.go | 29 -- internal/usecases/pin/usecase_test.go | 48 --- internal/usecases/session/manager.go | 61 --- internal/usecases/session/manager_test.go | 53 --- internal/usecases/user/credentials.go | 10 - internal/usecases/user/usecase.go | 59 --- internal/usecases/user/usecase_test.go | 51 --- 21 files changed, 134 insertions(+), 1460 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/message.go create mode 100644 internal/pkg/delivery/http/v1/parse.go create mode 100644 internal/pkg/entity/message/message.go create mode 100644 internal/pkg/repository/message/repo.go delete mode 100644 internal/pkg/service/auth.go delete mode 100644 internal/pkg/service/auth_test.go delete mode 100644 internal/pkg/service/pin.go delete mode 100644 internal/pkg/service/pin_test.go delete mode 100644 internal/pkg/service/response.go delete mode 100644 internal/pkg/service/service.go delete mode 100644 internal/pkg/service/validation.go delete mode 100644 internal/pkg/service/validation_test.go create mode 100644 internal/pkg/usecase/message/usecase.go delete mode 100644 internal/usecases/pin/usecase.go delete mode 100644 internal/usecases/pin/usecase_test.go delete mode 100644 internal/usecases/session/manager.go delete mode 100644 internal/usecases/session/manager_test.go delete mode 100644 internal/usecases/user/credentials.go delete mode 100644 internal/usecases/user/usecase.go delete mode 100644 internal/usecases/user/usecase_test.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 368fb08..e62dce6 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -56,10 +56,14 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) }) - r.With(auth.RequireAuth).Route("/profile", func(r chi.Router) { - r.Get("/info", handler.GetProfileInfo) - r.Put("/edit", handler.ProfileEditInfo) - r.Put("/avatar", handler.ProfileEditAvatar) + r.Route("/profile", func(r chi.Router) { + r.Get("/user/{userID:\\d+}", handler.AddPinsToBoard) + + r.With(auth.RequireAuth).Group(func(r chi.Router) { + r.Get("/info", handler.GetProfileInfo) + r.Put("/edit", handler.ProfileEditInfo) + r.Put("/avatar", handler.ProfileEditAvatar) + }) }) r.Route("/pin", func(r chi.Router) { @@ -92,5 +96,12 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Route("/feed", func(r chi.Router) { r.Get("/pin", handler.FeedPins) }) + + r.With(auth.RequireAuth).Route("/message", func(r chi.Router) { + r.Get("/get/{userID:\\d+}", handler.GetMessagesFromChat) + r.Post("/send/{userID:\\d+}", handler.SendMessageToUser) + r.Put("/update/{messageID:\\d+}", handler.UpdateMessage) + r.Delete("/delete/{messageID:\\d+}", handler.DeleteMessage) + }) }) } diff --git a/internal/pkg/delivery/http/v1/message.go b/internal/pkg/delivery/http/v1/message.go new file mode 100644 index 0000000..d319a3f --- /dev/null +++ b/internal/pkg/delivery/http/v1/message.go @@ -0,0 +1,49 @@ +package v1 + +import ( + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" +) + +func (h *HandlerHTTP) SendMessageToUser(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + + fromUserID := r.Context().Value(auth.KeyCurrentUserID).(int) + toUserID, err := fetchURLParamInt(r, "userID") + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "parse_url", "could not extract to whom the message is being sent") + if err != nil { + logger.Error(err.Error()) + } + return + } + + mes := &message.Message{} + err = decodeBody(r, mes) + defer r.Body.Close() + if err != nil { + logger.Info(err.Error()) + err = responseError(w, "parse_body", "invalid request body") + if err != nil { + logger.Error(err.Error()) + } + return + } + mes.From = fromUserID + mes.To = toUserID +} + +func (h *HandlerHTTP) DeleteMessage(w http.ResponseWriter, r *http.Request) { + +} + +func (h *HandlerHTTP) UpdateMessage(w http.ResponseWriter, r *http.Request) { + +} + +func (h *HandlerHTTP) GetMessagesFromChat(w http.ResponseWriter, r *http.Request) { + +} diff --git a/internal/pkg/delivery/http/v1/parse.go b/internal/pkg/delivery/http/v1/parse.go new file mode 100644 index 0000000..d200898 --- /dev/null +++ b/internal/pkg/delivery/http/v1/parse.go @@ -0,0 +1,25 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" +) + +func fetchURLParamInt(r *http.Request, param string) (int, error) { + paramInt64, err := strconv.ParseInt(chi.URLParam(r, param), 10, 64) + if err != nil { + return 0, fmt.Errorf("fetch integer param from query request %s: %w", r.URL.RawQuery, err) + } + return int(paramInt64), nil +} + +func decodeBody(r *http.Request, v any) error { + if err := json.NewDecoder(r.Body).Decode(v); err != nil { + return fmt.Errorf("decode body: %w", err) + } + return nil +} diff --git a/internal/pkg/entity/message/message.go b/internal/pkg/entity/message/message.go new file mode 100644 index 0000000..f80d1f6 --- /dev/null +++ b/internal/pkg/entity/message/message.go @@ -0,0 +1,16 @@ +package message + +import "github.com/jackc/pgx/v5/pgtype" + +type Chat [2]int + +type Message struct { + ID int + From int `json:"from"` + To int `json:"to"` + Content pgtype.Text `json:"context"` +} + +func (m Message) WhatChat() Chat { + return Chat{m.From, m.To} +} diff --git a/internal/pkg/repository/message/repo.go b/internal/pkg/repository/message/repo.go new file mode 100644 index 0000000..680f49c --- /dev/null +++ b/internal/pkg/repository/message/repo.go @@ -0,0 +1,14 @@ +package message + +import ( + "context" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" +) + +type Repository interface { + AddNewMessage(ctx context.Context, mes *entity.Message) error + GetMessages(ctx context.Context, chat entity.Chat) ([]entity.Message, error) + UpdateContentMessage(ctx context.Context, messageID int, newContent string) error + DelMessage(ctx context.Context, messageID int) error +} diff --git a/internal/pkg/service/auth.go b/internal/pkg/service/auth.go deleted file mode 100644 index e146083..0000000 --- a/internal/pkg/service/auth.go +++ /dev/null @@ -1,226 +0,0 @@ -package service - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -// Login godoc -// -// @Description User login, check authentication, get user info -// @Tags Auth -// @Produce json -// @Param session_key header string false "Auth session id" example(senjs7rvdnrgkjdr) -// @Success 200 {object} JsonResponse{body=user.User} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/auth/login [get] -func (s *Service) CheckLogin(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on check login", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) - - cookie, err := r.Cookie("session_key") - if err != nil { - s.log.Info("no cookie", log.F{"error", err.Error()}) - err = responseError(w, "no_auth", "the user is not logged in") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - userID, err := s.sm.GetUserIDBySessionKey(r.Context(), cookie.Value) - if err != nil { - err = responseError(w, "no_auth", "no user session found") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - username, avatar, err := s.userCase.FindOutUsernameAndAvatar(r.Context(), userID) - if err != nil { - s.log.Error(err.Error()) - err = responseError(w, "no_auth", "no user was found for this session") - } else { - err = responseOk(w, "user found", map[string]string{"username": username, "avatar": avatar}) - } - if err != nil { - s.log.Error(err.Error()) - } -} - -// Login godoc -// -// @Description User login, creating new session -// @Tags Auth -// @Accept json -// @Produce json -// @Param username body string true "Username" example(clicker123) -// @Param password body string true "Password" example(safe_pass) -// @Success 200 {object} JsonResponse -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Header 200 {string} session_key "Auth cookie with new valid session id" -// @Router /api/v1/auth/login [post] -func (s *Service) Login(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) - - defer r.Body.Close() - params := usecase.NewCredentials() - err := json.NewDecoder(r.Body).Decode(&params) - if err != nil { - s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) - err = responseError(w, "parse_body", "the correct username and password are expected to be received in JSON format") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - if !isValidPassword(params.Password) || !isValidUsername(params.Username) { - s.log.Info("invalid credentials") - err = responseError(w, "invalid_credentials", "invalid user credentials") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - user, err := s.userCase.Authentication(r.Context(), params) - if err != nil { - s.log.Warn(err.Error()) - err = responseError(w, "bad_credentials", "incorrect user credentials") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - session, err := s.sm.CreateNewSessionForUser(r.Context(), user.ID) - if err != nil { - s.log.Error(err.Error()) - err = responseError(w, "session", "failed to create a session for the user") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - cookie := &http.Cookie{ - Name: "session_key", - Value: session.Key, - HttpOnly: true, - Secure: true, - Path: "/", - Expires: session.Expire, - SameSite: http.SameSiteStrictMode, - } - http.SetCookie(w, cookie) - - err = responseOk(w, "a new session has been created for the user", nil) - if err != nil { - s.log.Error(err.Error()) - } -} - -// SignUp godoc -// -// @Description User registration -// @Tags Auth -// @Accept json -// @Produce json -// @Param username body string true "Username" example(clicker123) -// @Param email body string true "Email" example(clickkk@gmail.com) -// @Param password body string true "Password" example(safe_pass) -// @Success 200 {object} JsonResponse -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/auth/signup [post] -func (s *Service) Signup(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on signup", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) - - defer r.Body.Close() - user := &user.User{} - err := json.NewDecoder(r.Body).Decode(user) - if err != nil { - s.log.Info("failed to parse parameters", log.F{"error", err.Error()}) - err = responseError(w, "parse_body", "the correct username, email and password are expected to be received in JSON format") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - if err := IsValidUserForRegistration(user); err != nil { - s.log.Info("invalid user registration data") - err = responseError(w, "invalid_params", err.Error()) - if err != nil { - s.log.Error(err.Error()) - } - return - } - - err = s.userCase.Register(r.Context(), user) - if err != nil { - s.log.Warn(err.Error()) - err = responseError(w, "uniq_fields", "there is already an account with this username or password") - } else { - err = responseOk(w, "the user has been successfully registered", nil) - } - if err != nil { - s.log.Error(err.Error()) - } -} - -// Logout godoc -// -// @Description User logout, session deletion -// @Tags Auth -// @Produce json -// @Param session_key header string false "Auth session id" example(senjs7rvdnrgkjdr) -// -// @Success 200 {object} JsonResponse -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Header 200 {string} Session-id "Auth cookie with expired session id" -// @Router /api/v1/auth/logout [delete] -func (s *Service) Logout(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on logout", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) - - cookie, err := r.Cookie("session_key") - if err != nil { - s.log.Info("no cookie", log.F{"error", err.Error()}) - err = responseError(w, "no_auth", "to log out, you must first log in") - if err != nil { - s.log.Error(err.Error()) - } - return - } - - cookie.Expires = time.Now().UTC().AddDate(0, -1, 0) - http.SetCookie(w, cookie) - - err = s.sm.DeleteUserSession(r.Context(), cookie.Value) - if err != nil { - s.log.Error(err.Error()) - err = responseError(w, "session", "the user logged out, but his session did not end") - } else { - err = responseOk(w, "the user has successfully logged out", nil) - } - if err != nil { - s.log.Error(err.Error()) - } -} diff --git a/internal/pkg/service/auth_test.go b/internal/pkg/service/auth_test.go deleted file mode 100644 index 384296e..0000000 --- a/internal/pkg/service/auth_test.go +++ /dev/null @@ -1,467 +0,0 @@ -package service - -import ( - "encoding/json" - "io" - "math/rand" - "net/http" - "net/http/httptest" - "strconv" - "strings" - "testing" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func checkAuthCookie(cookies []*http.Cookie) bool { - if cookies == nil { - return false - } - for _, cookie := range cookies { - if cookie.Name == "session_key" { - return true - } - } - return false -} - -func TestCheckLogin(t *testing.T) { - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) - - url := "https://domain.test:8080/api/v1/login" - goodCases := []struct { - name string - cookie *http.Cookie - expResp JsonResponse - }{ - { - "sending valid session_key", - &http.Cookie{ - Name: "session_key", - Value: "461afabf38b3147c", - }, - JsonResponse{ - Status: "ok", - Message: "user found", - Body: map[string]interface{}{"username": "dogsLover", "avatar": "https://pinspire.online:8081/upload/avatars/default-avatar.png"}, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, url, nil) - req.AddCookie(tCase.cookie) - w := httptest.NewRecorder() - - service.CheckLogin(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - actualResp.Body = actualResp.Body.(map[string]interface{}) - require.Equal(t, tCase.expResp, actualResp) - }) - } - - badCases := []struct { - name string - cookie *http.Cookie - expResp JsonErrResponse - }{ - { - "sending empty cookie", - &http.Cookie{ - Name: "", - Value: "", - }, - JsonErrResponse{ - Status: "error", - Message: "the user is not logged in", - Code: "no_auth", - }, - }, - { - "sending invalid cookie", - &http.Cookie{ - Name: "session_key", - Value: "doesn't exist", - }, - JsonErrResponse{ - Status: "error", - Message: "no user session found", - Code: "no_auth", - }, - }, - { - "sending cookie with invald user", - &http.Cookie{ - Name: "session_key", - Value: "f4280a941b664d02", - }, - JsonErrResponse{ - Status: "error", - Message: "no user was found for this session", - Code: "no_auth", - }, - }, - } - - for _, tCase := range badCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, url, nil) - req.AddCookie(tCase.cookie) - w := httptest.NewRecorder() - - service.CheckLogin(w, req) - - var actualResp JsonErrResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - }) - } - -} - -func TestLogin(t *testing.T) { - url := "https://domain.test:8080/api/v1/login" - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) - - goodCases := []struct { - name string - rawBody string - expResp JsonResponse - }{ - { - "providing correct and valid user credentials", - `{"username":"dogsLover", "password":"big_string"}`, - JsonResponse{ - Status: "ok", - Message: "a new session has been created for the user", - Body: nil, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) - w := httptest.NewRecorder() - - service.Login(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - require.True(t, checkAuthCookie(w.Result().Cookies())) - }) - } - - badCases := []struct { - name string - rawBody string - expResp JsonErrResponse - }{ - { - "providing invalid credentials - broken body", - "{'username': 'dogsLover', 'password': 'big_string'", - JsonErrResponse{ - Status: "error", - Message: "the correct username and password are expected to be received in JSON format", - Code: "parse_body", - }, - }, - { - "providing invalid credentials - no username", - `{"password":"big_string"}`, - JsonErrResponse{ - Status: "error", - Message: "invalid user credentials", - Code: "invalid_credentials", - }, - }, - { - "providing invalid credentials - no password", - `{"username":"dogsLover"}`, - JsonErrResponse{ - Status: "error", - Message: "invalid user credentials", - Code: "invalid_credentials", - }, - }, - { - "providing invalid credentials - short username", - `{"username":"do", "password":"big_string"}`, - JsonErrResponse{ - Status: "error", - Message: "invalid user credentials", - Code: "invalid_credentials", - }, - }, - { - "providing invalid credentials - long username", - `{"username":"dojsbrjfbdrjhbhjldrbgbdrhjgbdjrbgjdhbgjhdbrghbdhj,gbdhjrbgjhdbvkvghkevfghjdvrfhvdhrvbjdfgdrgdr","password":"big_string"}`, - JsonErrResponse{ - Status: "error", - Message: "invalid user credentials", - Code: "invalid_credentials", - }, - }, - { - "providing invalid credentials - short password", - `{"username":"dogsLover","password":"bi"}`, - JsonErrResponse{ - Status: "error", - Message: "invalid user credentials", - Code: "invalid_credentials", - }, - }, - { - "providing invalid credentials - long password", - `{"username":"dogsLover","password":"biyugsgrusgubskhvfhkdgvfgvdvrjgbsjhgjkshzkljfskfwjkhkfjisuidgoquakflsjuzeofiow3i"}`, - JsonErrResponse{ - Status: "error", - Message: "invalid user credentials", - Code: "invalid_credentials", - }, - }, - { - "providing incorrect credentials - no user with such credentials", - `{"username":"dogsLover", "password":"doesn't_exist"}`, - JsonErrResponse{ - Status: "error", - Message: "incorrect user credentials", - Code: "bad_credentials", - }, - }, - } - - for _, tCase := range badCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) - w := httptest.NewRecorder() - - service.Login(w, req) - - var actualResp JsonErrResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - require.False(t, checkAuthCookie(w.Result().Cookies())) - }) - } -} - -func TestSignUp(t *testing.T) { - url := "https://domain.test:8080/api/v1/signup" - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) - - goodCases := []struct { - name string - rawBody string - expResp JsonResponse - }{ - { - "providing correct and valid data for signup", - `{"username":"newbie", "password":"getHigh123", "email":"world@uandex.ru"}`, - JsonResponse{ - Status: "ok", - Message: "the user has been successfully registered", - Body: nil, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) - w := httptest.NewRecorder() - - service.Signup(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - }) - } - - badCases := []struct { - name string - rawBody string - expResp JsonErrResponse - }{ - { - "user with such data already exists", - `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"}`, - JsonErrResponse{ - Status: "error", - Message: "there is already an account with this username or password", - Code: "uniq_fields", - }, - }, - { - "invalid data - broken body", - `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"`, - JsonErrResponse{ - Status: "error", - Message: "the correct username, email and password are expected to be received in JSON format", - Code: "parse_body", - }, - }, - { - "invalid data - no username", - `{"password":"big_string", "email":"dogslove@gmail.com"}`, - JsonErrResponse{ - Status: "error", - Message: "username", - Code: "invalid_params", - }, - }, - { - "invalid data - no username, password", - `{"email":"dogslove@gmail.com"}`, - JsonErrResponse{ - Status: "error", - Message: "password,username", - Code: "invalid_params", - }, - }, - { - "invalid data - short username", - `{"username":"sh", "password":"big_string", "email":"dogslove@gmail.com"}`, - JsonErrResponse{ - Status: "error", - Message: "username", - Code: "invalid_params", - }, - }, - { - "invalid data - incorrect email", - `{"username":"sh", "password":"big_string", "email":"dog"}`, - JsonErrResponse{ - Status: "error", - Message: "email,username", - Code: "invalid_params", - }, - }, - } - - for _, tCase := range badCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, url, io.NopCloser(strings.NewReader(tCase.rawBody))) - w := httptest.NewRecorder() - - service.Signup(w, req) - - var actualResp JsonErrResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - }) - } -} - -func TestLogout(t *testing.T) { - url := "https://domain.test:8080/api/v1/logout" - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - sm := session.New(log, ramrepo.NewRamSessionRepo(db)) - userCase := user.New(log, ramrepo.NewRamUserRepo(db)) - service := New(log, sm, userCase, nil) - - goodCases := []struct { - name string - cookie *http.Cookie - expResp JsonResponse - }{ - { - "user is logged in - providing valid cookie", - &http.Cookie{ - Name: "session_key", - Value: "461afabf38b3147c", - }, - JsonResponse{ - Status: "ok", - Message: "the user has successfully logged out", - Body: nil, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, url, nil) - req.AddCookie(tCase.cookie) - w := httptest.NewRecorder() - - service.Logout(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - }) - } - - badCases := []struct { - name string - cookie *http.Cookie - expResp JsonErrResponse - }{ - { - "user isn't logged in - providing invalid cookie", - &http.Cookie{ - Name: "not_auth_cookie", - Value: "blablalba", - }, - JsonErrResponse{ - Status: "error", - Message: "to log out, you must first log in", - Code: "no_auth", - }, - }, - } - - for _, tCase := range badCases { - t.Run(tCase.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, url, nil) - req.AddCookie(tCase.cookie) - w := httptest.NewRecorder() - - service.Logout(w, req) - - var actualResp JsonErrResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - require.Equal(t, tCase.expResp, actualResp) - }) - } - -} diff --git a/internal/pkg/service/pin.go b/internal/pkg/service/pin.go deleted file mode 100644 index 41332f2..0000000 --- a/internal/pkg/service/pin.go +++ /dev/null @@ -1,49 +0,0 @@ -package service - -import ( - "errors" - "net/http" - - _ "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" - - log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -var ErrCountParameterMissing = errors.New("the count parameter is missing") -var ErrBadParams = errors.New("bad params") - -// GetPins godoc -// -// @Description Get pin collection -// @Tags Pin -// @Accept json -// @Produce json -// @Param lastID path string false "ID of the pin that will be just before the first pin in the requested collection, 0 by default" example(2) -// -// @Param count path string true "Pins quantity after last pin specified in lastID" example(5) -// @Success 200 {object} JsonResponse{body=[]Pin} -// @Failure 400 {object} JsonErrResponse -// @Failure 404 {object} JsonErrResponse -// @Failure 500 {object} JsonErrResponse -// @Router /api/v1/pin [get] -func (s *Service) GetPins(w http.ResponseWriter, r *http.Request) { - s.log.Info("request on get pins", log.F{"method", r.Method}, log.F{"path", r.URL.Path}) - SetContentTypeJSON(w) - - count, lastID, err := FetchValidParamForLoadTape(r.URL) - if err != nil { - s.log.Info("parse url query params", log.F{"error", err.Error()}) - err = responseError(w, "bad_params", - "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)") - } else { - s.log.Sugar().Infof("param: count=%d, lastID=%d", count, lastID) - pins, last := s.pinCase.SelectNewPins(r.Context(), count, lastID) - err = responseOk(w, "pins received are sorted by id", map[string]any{ - "pins": pins, - "lastID": last, - }) - } - if err != nil { - s.log.Error(err.Error()) - } -} diff --git a/internal/pkg/service/pin_test.go b/internal/pkg/service/pin_test.go deleted file mode 100644 index 2dd7998..0000000 --- a/internal/pkg/service/pin_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "io" - "math/rand" - "net/http/httptest" - "strconv" - "testing" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - pinCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func TestGetPins(t *testing.T) { - - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - pinCase := pinCase.New(log, ramrepo.NewRamPinRepo(db)) - service := New(log, nil, nil, pinCase) - - rawUrl := "https://domain.test:8080/api/v1/pin" - goodCases := []struct { - rawURL string - expResp JsonResponse - }{ - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 1, 2), - expResp: JsonResponse{ - Status: "ok", - Message: "pins received are sorted by id", - Body: map[string]interface{}{ - "lastID": 3, - "pins": []interface{}{ - map[string]interface{}{"id": 3}, - }, - }, - }, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 2, 3), - expResp: JsonResponse{ - Status: "ok", - Message: "pins received are sorted by id", - Body: map[string]interface{}{ - "lastID": 5, - "pins": []interface{}{ - map[string]interface{}{"id": 4}, - map[string]interface{}{"id": 5}, - }, - }, - }, - }, - } - - for _, tCase := range goodCases { - t.Run(fmt.Sprintf("TestGetPins good: %s", tCase.rawURL), func(t *testing.T) { - req := httptest.NewRequest("GET", tCase.rawURL, nil) - w := httptest.NewRecorder() - service.GetPins(w, req) - - var actualResp JsonResponse - json.NewDecoder(w.Result().Body).Decode(&actualResp) - - require.Equal(t, tCase.expResp.Status, actualResp.Status) - require.Equal(t, tCase.expResp.Message, actualResp.Message) - expLastID := tCase.expResp.Body.(map[string]interface{})["lastID"].(int) - actualLastID := actualResp.Body.(map[string]interface{})["lastID"].(float64) - - expIDs, actualIDs := make([]int, 0), make([]int, 0) - for _, pin := range tCase.expResp.Body.(map[string]interface{})["pins"].([]interface{}) { - expIDs = append(expIDs, pin.(map[string]interface{})["id"].(int)) - } - for _, pin := range actualResp.Body.(map[string]interface{})["pins"].([]interface{}) { - actualIDs = append(actualIDs, int(pin.(map[string]interface{})["id"].(float64))) - } - - require.Equal(t, expLastID, int(actualLastID)) - require.Equal(t, expIDs, actualIDs) - }) - } - - badCases := []struct { - rawURL string - expResp JsonErrResponse - }{ - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 3), - expResp: JsonErrResponse{ - Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - Code: "bad_params", - }, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 3), - expResp: JsonErrResponse{ - Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - Code: "bad_params", - }, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 213123, 3), - expResp: JsonErrResponse{ - Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - Code: "bad_params", - }, - }, - { - rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, -1), - expResp: JsonErrResponse{ - Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - Code: "bad_params", - }, - }, - { - rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 3), - expResp: JsonErrResponse{ - Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - Code: "bad_params", - }, - }, - { - rawURL: fmt.Sprintf("%s?lastID=%d", rawUrl, 3), - expResp: JsonErrResponse{ - Status: "error", - Message: "expected parameters: count(positive integer: [1; 1000]), lastID(positive integer, the absence of this parameter is equal to the value 0)", - Code: "bad_params", - }, - }, - } - - for _, tCase := range badCases { - t.Run(fmt.Sprintf("TestGetPins bad: %s", tCase.rawURL), func(t *testing.T) { - req := httptest.NewRequest("GET", tCase.rawURL, nil) - w := httptest.NewRecorder() - service.GetPins(w, req) - - resp := w.Result() - body, _ := io.ReadAll(resp.Body) - var actualResp JsonErrResponse - - json.Unmarshal(body, &actualResp) - require.Equal(t, tCase.expResp, actualResp) - }) - } -} diff --git a/internal/pkg/service/response.go b/internal/pkg/service/response.go deleted file mode 100644 index df3121d..0000000 --- a/internal/pkg/service/response.go +++ /dev/null @@ -1,57 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "net/http" -) - -// type JsonResponseNoBody struct { -// Status string `json:"status" example:"ok"` -// Message string `json:"message" example:"Response message"` -// } - -type JsonResponse struct { - Status string `json:"status" example:"ok"` - Message string `json:"message" example:"Response message"` - Body interface{} `json:"body" extensions:"x-omitempty"` -} // @name JsonResponse - -type JsonErrResponse struct { - Status string `json:"status" example:"error"` - Message string `json:"message" example:"Error description"` - Code string `json:"code"` -} // @name JsonErrResponse - -func SetContentTypeJSON(w http.ResponseWriter) { - w.Header().Set("Content-Type", "application/json") -} - -func responseOk(w http.ResponseWriter, message string, body any) error { - res := JsonResponse{ - Status: "ok", - Message: message, - Body: body, - } - resBytes, err := json.Marshal(res) - if err != nil { - return fmt.Errorf("responseOk: %w", err) - } - w.WriteHeader(http.StatusOK) - _, err = w.Write(resBytes) - return err -} - -func responseError(w http.ResponseWriter, code, message string) error { - res := JsonErrResponse{ - Status: "error", - Message: message, - Code: code, - } - resBytes, err := json.Marshal(res) - if err != nil { - return fmt.Errorf("responseError: %w", err) - } - _, err = w.Write(resBytes) - return err -} diff --git a/internal/pkg/service/service.go b/internal/pkg/service/service.go deleted file mode 100644 index 05bb916..0000000 --- a/internal/pkg/service/service.go +++ /dev/null @@ -1,24 +0,0 @@ -package service - -import ( - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/session" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/usecases/user" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -type Service struct { - log *logger.Logger - userCase *user.Usecase - pinCase *pin.Usecase - sm *session.SessionManager -} - -func New(log *logger.Logger, sm *session.SessionManager, user *user.Usecase, pin *pin.Usecase) *Service { - return &Service{ - log: log, - userCase: user, - pinCase: pin, - sm: sm, - } -} diff --git a/internal/pkg/service/validation.go b/internal/pkg/service/validation.go deleted file mode 100644 index 9356faa..0000000 --- a/internal/pkg/service/validation.go +++ /dev/null @@ -1,96 +0,0 @@ -package service - -import ( - "fmt" - "net/url" - "strconv" - "strings" - "unicode" - - valid "github.com/asaskevich/govalidator" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" -) - -type errorFields []string - -func (b *errorFields) Error() string { - return strings.Join(*b, ",") -} - -func (b *errorFields) addInvalidField(fieldName string) { - *b = append(*b, fieldName) -} - -func (b *errorFields) Err() error { - if len(*b) == 0 { - return nil - } - return b -} - -func FetchValidParamForLoadTape(u *url.URL) (count int, lastID int, err error) { - if param := u.Query().Get("count"); len(param) > 0 { - c, err := strconv.ParseInt(param, 10, 64) - if err != nil { - return 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) - } - count = int(c) - } else { - return 0, 0, ErrCountParameterMissing - } - if param := u.Query().Get("lastID"); len(param) > 0 { - last, err := strconv.ParseInt(param, 10, 64) - if err != nil { - return 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) - } - lastID = int(last) - } - if count <= 0 || count > 1000 || lastID < 0 { - return 0, 0, ErrBadParams - } - return -} - -func IsValidUserForRegistration(user *user.User) error { - invalidFields := new(errorFields) - - if !isValidPassword(user.Password) { - invalidFields.addInvalidField("password") - } - if !isValidEmail(user.Email) { - invalidFields.addInvalidField("email") - } - if !isValidUsername(user.Username) { - invalidFields.addInvalidField("username") - } - - return invalidFields.Err() -} - -func isValidUsername(username string) bool { - if len(username) < 4 || len(username) > 50 { - return false - } - for _, r := range username { - if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { - return false - } - } - return true -} - -func isValidEmail(email string) bool { - return valid.IsEmail(email) && len(email) <= 50 -} - -func isValidPassword(password string) bool { - if len(password) < 8 || len(password) > 50 { - return false - } - for _, r := range password { - if !(unicode.IsNumber(r) || unicode.IsSymbol(r) || unicode.IsPunct(r) || unicode.IsLetter(r)) { - return false - } - } - return true -} diff --git a/internal/pkg/service/validation_test.go b/internal/pkg/service/validation_test.go deleted file mode 100644 index f2b094c..0000000 --- a/internal/pkg/service/validation_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package service - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFetchValidParams(t *testing.T) { - rawUrl := "https://domain.test:8080/api/v1/pin" - - tests := []struct { - name string - queryRow string - wantCount, wantLastID int - }{ - {"both parameters were passed correctly", "?count=6&lastID=12", 6, 12}, - {"both parameters were passed correctly in a different order", "?lastID=1&count=3", 3, 1}, - {"repeating parameters", "?count=14&lastID=1&count=3&lastID=55&lastID=65", 14, 1}, - {"repeating parameters", "?count=14&lastID=1&count=3&lastID=55&lastID=65", 14, 1}, - {"empty parameter lastID", "?count=7", 7, 0}, - {"the parameter lastID is registered but not specified", "?lastID=&count=17", 17, 0}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - URL, err := url.Parse(rawUrl + test.queryRow) - if err != nil { - t.Fatalf("error when parsing into the url.URL structure: %v", err) - } - actualCount, actualLastID, err := FetchValidParamForLoadTape(URL) - require.NoError(t, err) - require.Equal(t, test.wantCount, actualCount) - require.Equal(t, test.wantLastID, actualLastID) - }) - } -} - -func TestErrorFetchValidParams(t *testing.T) { - rawUrl := "https://domain.test:8080/api/v1/pin" - - tests := []struct { - name string - queryRow string - wantErr error - }{ - {"empty query row", "", ErrCountParameterMissing}, - {"count equal zero", "?count=0", ErrBadParams}, - {"negative count", "?count=-5&lastID=12", ErrBadParams}, - {"negative lastID", "?count=5&lastID=-6", ErrBadParams}, - {"requested count is more than a thousand", "?count=1001", ErrBadParams}, - {"count param empty", "?count=&lastID=6", ErrCountParameterMissing}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - URL, err := url.Parse(rawUrl + test.queryRow) - if err != nil { - t.Fatalf("error when parsing into the url.URL structure: %v", err) - } - actualCount, actualLastID, err := FetchValidParamForLoadTape(URL) - require.ErrorIs(t, err, test.wantErr) - require.Equal(t, 0, actualCount) - require.Equal(t, 0, actualLastID) - }) - } -} diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go new file mode 100644 index 0000000..6d4fea4 --- /dev/null +++ b/internal/pkg/usecase/message/usecase.go @@ -0,0 +1,15 @@ +package message + +import ( + "context" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" +) + +//go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=usecase.go Usecase +type Usecase interface { + SendMessage(ctx context.Context, mes *entity.Message) error + GetMessagesFromChat(ctx context.Context, chat entity.Chat, lastID int) ([]entity.Message, error) + UpdateContentMessage(ctx context.Context, mes *entity.Message) error + DeleteMessage(ctx context.Context, mesID int) error +} diff --git a/internal/usecases/pin/usecase.go b/internal/usecases/pin/usecase.go deleted file mode 100644 index 2dbec72..0000000 --- a/internal/usecases/pin/usecase.go +++ /dev/null @@ -1,29 +0,0 @@ -package pin - -import ( - "context" - - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" - repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -type Usecase struct { - log *logger.Logger - repo repo.Repository -} - -func New(log *logger.Logger, repo repo.Repository) *Usecase { - return &Usecase{log, repo} -} - -func (u *Usecase) SelectNewPins(ctx context.Context, count, lastID int) ([]entity.Pin, int) { - pins, err := u.repo.GetSortedNPinsAfterID(ctx, count, lastID) - if err != nil { - u.log.Error(err.Error()) - } - if len(pins) == 0 { - return []entity.Pin{}, lastID - } - return pins, pins[len(pins)-1].ID -} diff --git a/internal/usecases/pin/usecase_test.go b/internal/usecases/pin/usecase_test.go deleted file mode 100644 index fa58f07..0000000 --- a/internal/usecases/pin/usecase_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package pin - -import ( - "context" - "math/rand" - "strconv" - "testing" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func TestSelectNewPins(t *testing.T) { - log, _ := logger.New(logger.RFC3339FormatTime()) - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - pinCase := New(log, ramrepo.NewRamPinRepo(db)) - - testCases := []struct { - name string - count, lastID int - expNewLastID int - }{ - { - name: "provide correct count and lastID", - count: 2, - lastID: 1, - expNewLastID: 3, - }, - { - name: "provide incorrect count", - count: -2, - lastID: 1, - expNewLastID: 1, - }, - } - - for _, tCase := range testCases { - t.Run(tCase.name, func(t *testing.T) { - _, actualLastID := pinCase.SelectNewPins(context.Background(), tCase.count, tCase.lastID) - require.Equal(t, tCase.expNewLastID, actualLastID) - }) - } -} diff --git a/internal/usecases/session/manager.go b/internal/usecases/session/manager.go deleted file mode 100644 index a7a3b8e..0000000 --- a/internal/usecases/session/manager.go +++ /dev/null @@ -1,61 +0,0 @@ -package session - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" - repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -const SessionLifeTime = 30 * 24 * time.Hour - -const lenSessionKey = 16 - -var ErrExpiredSession = errors.New("session lifetime expired") - -type SessionManager struct { - log *logger.Logger - repo repo.Repository -} - -func New(log *logger.Logger, repo repo.Repository) *SessionManager { - return &SessionManager{log, repo} -} - -func (sm *SessionManager) CreateNewSessionForUser(ctx context.Context, userID int) (*session.Session, error) { - sessionKey, err := crypto.NewRandomString(lenSessionKey) - if err != nil { - return nil, fmt.Errorf("session key generation for new session: %w", err) - } - - session := &session.Session{ - Key: sessionKey, - UserID: userID, - Expire: time.Now().UTC().Add(SessionLifeTime), - } - err = sm.repo.AddSession(ctx, session) - if err != nil { - return nil, fmt.Errorf("creating a new session by the session manager: %w", err) - } - return session, nil -} - -func (sm *SessionManager) GetUserIDBySessionKey(ctx context.Context, sessionKey string) (int, error) { - session, err := sm.repo.GetSessionByKey(ctx, sessionKey) - if err != nil { - return 0, fmt.Errorf("getting a session by a manager: %w", err) - } - if time.Now().UTC().After(session.Expire) { - return 0, ErrExpiredSession - } - return session.UserID, nil -} - -func (sm *SessionManager) DeleteUserSession(ctx context.Context, key string) error { - return sm.repo.DeleteSessionByKey(ctx, key) -} diff --git a/internal/usecases/session/manager_test.go b/internal/usecases/session/manager_test.go deleted file mode 100644 index 89a3633..0000000 --- a/internal/usecases/session/manager_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package session - -import ( - "context" - "fmt" - "math/rand" - "strconv" - "testing" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func TestGetUserIDBySessionKey(t *testing.T) { - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - fmt.Println(err) - return - } - defer log.Sync() - - db, err := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - if err != nil { - log.Error(err.Error()) - return - } - defer db.Close() - - sm := New(log, ramrepo.NewRamSessionRepo(db)) - - testCases := []struct { - name string - session_key string - expUserId int - expErr error - }{ - { - "providing valid session key", - "461afabf38b3147c", - 1, - nil, - }, - } - - for _, tCase := range testCases { - t.Run(tCase.name, func(t *testing.T) { - id, err := sm.GetUserIDBySessionKey(context.Background(), tCase.session_key) - require.Equal(t, tCase.expErr, err) - require.Equal(t, tCase.expUserId, id) - }) - } -} diff --git a/internal/usecases/user/credentials.go b/internal/usecases/user/credentials.go deleted file mode 100644 index 9b5646b..0000000 --- a/internal/usecases/user/credentials.go +++ /dev/null @@ -1,10 +0,0 @@ -package user - -type userCredentials struct { - Username string - Password string -} - -func NewCredentials() userCredentials { - return userCredentials{} -} diff --git a/internal/usecases/user/usecase.go b/internal/usecases/user/usecase.go deleted file mode 100644 index b9cd907..0000000 --- a/internal/usecases/user/usecase.go +++ /dev/null @@ -1,59 +0,0 @@ -package user - -import ( - "context" - "errors" - "fmt" - - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" -) - -var ErrUserAuthentication = errors.New("user authentication") - -const ( - lenSalt = 16 - lenPasswordHash = 64 -) - -type Usecase struct { - log *logger.Logger - repo repo.Repository -} - -func New(log *logger.Logger, repo repo.Repository) *Usecase { - return &Usecase{log, repo} -} - -func (u *Usecase) Register(ctx context.Context, user *entity.User) error { - salt, err := crypto.NewRandomString(lenSalt) - if err != nil { - return fmt.Errorf("generating salt for registration: %w", err) - } - - user.Password = salt + crypto.PasswordHash(user.Password, salt, lenPasswordHash) - err = u.repo.AddNewUser(ctx, user) - if err != nil { - return fmt.Errorf("user registration: %w", err) - } - return nil -} - -func (u *Usecase) Authentication(ctx context.Context, credentials userCredentials) (*entity.User, error) { - user, err := u.repo.GetUserByUsername(ctx, credentials.Username) - if err != nil { - return nil, fmt.Errorf("user authentication: %w", err) - } - salt := user.Password[:lenSalt] - if crypto.PasswordHash(credentials.Password, salt, lenPasswordHash) != user.Password[lenSalt:] { - return nil, ErrUserAuthentication - } - user.Password = "" - return user, nil -} - -func (u *Usecase) FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) { - return u.repo.GetUsernameAndAvatarByID(ctx, userID) -} diff --git a/internal/usecases/user/usecase_test.go b/internal/usecases/user/usecase_test.go deleted file mode 100644 index 9fabfad..0000000 --- a/internal/usecases/user/usecase_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package user - -import ( - "context" - "fmt" - "math/rand" - "strconv" - "testing" - - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/stretchr/testify/require" -) - -func TestRegister(t *testing.T) { - log, err := logger.New(logger.RFC3339FormatTime()) - if err != nil { - fmt.Println(err) - return - } - defer log.Sync() - - db, _ := ramrepo.OpenDB(strconv.FormatInt(int64(rand.Int()), 10)) - defer db.Close() - - userCase := New(log, ramrepo.NewRamUserRepo(db)) - - testCases := []struct { - name string - user *entity.User - expErr error - }{ - { - "providing valid user", - &entity.User{ - Username: "valid_user", - Password: "helloworld", - Email: "gggg@yandex.ru", - }, - nil, - }, - } - - for _, tCase := range testCases { - t.Run(tCase.name, func(t *testing.T) { - err := userCase.Register(context.Background(), tCase.user) - require.Equal(t, tCase.expErr, err) - }) - } -} From 658591ff8e35b67ae9a1107cf7240d20818ca916 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 19 Nov 2023 13:36:51 +0300 Subject: [PATCH 184/266] TP-f07 add write layers: delivery, usecase, repository for message --- internal/api/server/router/router.go | 12 +-- internal/app/app.go | 14 ++- internal/pkg/delivery/http/v1/handler.go | 33 ++++--- internal/pkg/delivery/http/v1/message.go | 102 ++++++++++++++++++++ internal/pkg/delivery/http/v1/validation.go | 23 ++--- internal/pkg/repository/message/queries.go | 16 +++ internal/pkg/repository/message/repo.go | 67 ++++++++++++- internal/pkg/usecase/message/check.go | 17 ++++ internal/pkg/usecase/message/usecase.go | 54 ++++++++++- 9 files changed, 294 insertions(+), 44 deletions(-) create mode 100644 internal/pkg/repository/message/queries.go create mode 100644 internal/pkg/usecase/message/check.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e62dce6..c4e8019 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -56,14 +56,10 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess }) }) - r.Route("/profile", func(r chi.Router) { - r.Get("/user/{userID:\\d+}", handler.AddPinsToBoard) - - r.With(auth.RequireAuth).Group(func(r chi.Router) { - r.Get("/info", handler.GetProfileInfo) - r.Put("/edit", handler.ProfileEditInfo) - r.Put("/avatar", handler.ProfileEditAvatar) - }) + r.With(auth.RequireAuth).Route("/profile", func(r chi.Router) { + r.Get("/info", handler.GetProfileInfo) + r.Put("/edit", handler.ProfileEditInfo) + r.Put("/avatar", handler.ProfileEditAvatar) }) r.Route("/pin", func(r chi.Router) { diff --git a/internal/app/app.go b/internal/app/app.go index e6031b5..5c10b11 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -12,11 +12,13 @@ import ( deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" + mesCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -61,11 +63,15 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) - userCase := user.New(log, imgCase, userRepo.NewUserRepoPG(pool)) - pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) - boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()) - handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase) + handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ + UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), + PinCase: pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)), + BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), + MessageCase: message.New(mesCase.NewMessageRepo(pool)), + SM: sm, + }) + cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 2500fb1..9cc1191 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -2,6 +2,7 @@ package v1 import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -9,19 +10,29 @@ import ( ) type HandlerHTTP struct { - log *logger.Logger - userCase user.Usecase - pinCase pin.Usecase - boardCase board.Usecase - sm session.SessionManager + log *logger.Logger + userCase user.Usecase + pinCase pin.Usecase + boardCase board.Usecase + messageCase message.Usecase + sm session.SessionManager } -func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, board board.Usecase) *HandlerHTTP { +func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { return &HandlerHTTP{ - log: log, - userCase: user, - pinCase: pin, - boardCase: board, - sm: sm, + log: log, + userCase: hub.UserCase, + pinCase: hub.PinCase, + boardCase: hub.BoardCase, + messageCase: hub.MessageCase, + sm: hub.SM, } } + +type UsecaseHub struct { + UserCase user.Usecase + PinCase pin.Usecase + BoardCase board.Usecase + MessageCase message.Usecase + SM session.SessionManager +} diff --git a/internal/pkg/delivery/http/v1/message.go b/internal/pkg/delivery/http/v1/message.go index d319a3f..57e2dde 100644 --- a/internal/pkg/delivery/http/v1/message.go +++ b/internal/pkg/delivery/http/v1/message.go @@ -34,16 +34,118 @@ func (h *HandlerHTTP) SendMessageToUser(w http.ResponseWriter, r *http.Request) } mes.From = fromUserID mes.To = toUserID + + idNewMessage, err := h.messageCase.SendMessage(r.Context(), mes) + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "send_message", "failed to send message") + } else { + err = responseOk(http.StatusCreated, w, "the message was sent successfully", + map[string]int{"id": idNewMessage}) + } + if err != nil { + logger.Error(err.Error()) + } } func (h *HandlerHTTP) DeleteMessage(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + messageID, err := fetchURLParamInt(r, "messageID") + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "parse_url", "could not extract to whom the message is being sent") + if err != nil { + logger.Error(err.Error()) + } + return + } + err = h.messageCase.DeleteMessage(r.Context(), userID, messageID) + if err != nil { + logger.Warn(err.Error()) + err = responseError(w, "delete_message", "fail deleting a message") + } else { + err = responseOk(http.StatusOK, w, "the message was successfully deleted", nil) + } + if err != nil { + logger.Error(err.Error()) + } } func (h *HandlerHTTP) UpdateMessage(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + messageID, err := fetchURLParamInt(r, "messageID") + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "parse_url", "could not extract to whom the message is being sent") + if err != nil { + logger.Error(err.Error()) + } + return + } + + mes := &message.Message{} + err = decodeBody(r, mes) + defer r.Body.Close() + if err != nil { + logger.Info(err.Error()) + err = responseError(w, "parse_body", "invalid request body") + if err != nil { + logger.Error(err.Error()) + } + return + } + mes.ID = messageID + err = h.messageCase.UpdateContentMessage(r.Context(), userID, mes) + if err != nil { + logger.Warn(err.Error()) + err = responseError(w, "update_message", "fail updating a message") + } else { + err = responseOk(http.StatusOK, w, "the message was successfully updated", nil) + } + if err != nil { + logger.Error(err.Error()) + } } func (h *HandlerHTTP) GetMessagesFromChat(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + userID2, err := fetchURLParamInt(r, "userID") + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "parse_url", "could not extract to whom the message is being sent") + if err != nil { + logger.Error(err.Error()) + } + return + } + + count, lastID, err := FetchValidParamForLoadFeed(r.URL) + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "bad_request", "failed to get parameters for receiving a message from a chat") + if err != nil { + logger.Error(err.Error()) + } + return + } + feed, newLastID, err := h.messageCase.GetMessagesFromChat(r.Context(), message.Chat{userID, userID2}, count, lastID) + if err != nil { + logger.Warn(err.Error()) + } + err = responseOk(http.StatusOK, w, "messages received successfully", map[string]any{ + "messages": feed, + "lastID": newLastID, + }) + if err != nil { + logger.Error(err.Error()) + } } diff --git a/internal/pkg/delivery/http/v1/validation.go b/internal/pkg/delivery/http/v1/validation.go index 5d8bfb5..9d73fce 100644 --- a/internal/pkg/delivery/http/v1/validation.go +++ b/internal/pkg/delivery/http/v1/validation.go @@ -35,32 +35,25 @@ func (b *errorFields) Err() error { return b } -func FetchValidParamForLoadTape(u *url.URL) (count int, minID int, maxID int, err error) { +func FetchValidParamForLoadFeed(u *url.URL) (count, lastID int, err error) { if param := u.Query().Get("count"); len(param) > 0 { c, err := strconv.ParseInt(param, 10, 64) if err != nil { - return 0, 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) + return 0, 0, fmt.Errorf("fetch count param for load tape: %w", err) } count = int(c) } else { - return 0, 0, 0, ErrCountParameterMissing + return 0, 0, ErrCountParameterMissing } - if param := u.Query().Get("minID"); len(param) > 0 { + if param := u.Query().Get("lastID"); len(param) > 0 { id, err := strconv.ParseInt(param, 10, 64) if err != nil { - return 0, 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) + return 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) } - minID = int(id) + lastID = int(id) } - if param := u.Query().Get("maxID"); len(param) > 0 { - id, err := strconv.ParseInt(param, 10, 64) - if err != nil { - return 0, 0, 0, fmt.Errorf("fetch lastID param for load tape: %w", err) - } - maxID = int(id) - } - if count <= 0 || count > 1000 || minID < 0 || maxID < 0 { - return 0, 0, 0, ErrBadParams + if count <= 0 || count > 1000 || lastID < 0 { + return 0, 0, ErrBadParams } return } diff --git a/internal/pkg/repository/message/queries.go b/internal/pkg/repository/message/queries.go new file mode 100644 index 0000000..7856ac4 --- /dev/null +++ b/internal/pkg/repository/message/queries.go @@ -0,0 +1,16 @@ +package message + +const ( + SelectMessageByID = "SELECT user_from, user_to, content WHERE id = $1 AND deleted_at IS NULL;" + SelectMessageFromChat = `SELECT id, user_from, user_to, content + FROM message + WHERE deleted_at IS NULL AND id < $1 AND + (user_from = $2 AND user_to = $3 OR user_from = $3 AND user_to = $2) + ORDER BY id DESC + LIMIT $4;` + + InsertMessage = "INSERT INTO message (user_from, user_to, content) VALUES ($1, $2, $3) RETURNING id;" + + UpdateMessageContent = "UPDATE message SET content = $1 WHERE id = $2;" + UpdateMessageStatusToDeleted = "UPDATE message SET deleted_at = now() WHERE id = $1 AND deleted_at IS NULL;" +) diff --git a/internal/pkg/repository/message/repo.go b/internal/pkg/repository/message/repo.go index 680f49c..670e718 100644 --- a/internal/pkg/repository/message/repo.go +++ b/internal/pkg/repository/message/repo.go @@ -2,13 +2,76 @@ package message import ( "context" + "fmt" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" ) +//go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=repo.go Repository type Repository interface { - AddNewMessage(ctx context.Context, mes *entity.Message) error - GetMessages(ctx context.Context, chat entity.Chat) ([]entity.Message, error) + GetMessageByID(ctx context.Context, mesID int) (*entity.Message, error) + AddNewMessage(ctx context.Context, mes *entity.Message) (int, error) + GetMessages(ctx context.Context, chat entity.Chat, count, lastID int) ([]entity.Message, error) UpdateContentMessage(ctx context.Context, messageID int, newContent string) error DelMessage(ctx context.Context, messageID int) error } + +type messageRepo struct { + db pgtype.PgxPoolIface +} + +func NewMessageRepo(db pgtype.PgxPoolIface) *messageRepo { + return &messageRepo{db} +} + +func (m *messageRepo) GetMessageByID(ctx context.Context, mesID int) (*entity.Message, error) { + message := &entity.Message{ID: mesID} + err := m.db.QueryRow(ctx, SelectMessageByID, mesID).Scan(&message.From, &message.To, &message.Content) + if err != nil { + return nil, fmt.Errorf("get message by id from storage: %w", err) + } + return message, nil +} + +func (m *messageRepo) AddNewMessage(ctx context.Context, mes *entity.Message) (int, error) { + err := m.db.QueryRow(ctx, InsertMessage, mes.From, mes.To, mes.Content).Scan(&mes.ID) + if err != nil { + return 0, fmt.Errorf("add new message in storage: %w", err) + } + return mes.ID, nil +} + +func (m *messageRepo) GetMessages(ctx context.Context, chat entity.Chat, count, lastID int) ([]entity.Message, error) { + rows, err := m.db.Query(ctx, SelectMessageFromChat, lastID, chat[0], chat[1], count) + if err != nil { + return nil, fmt.Errorf("get message for chat from storage: %w", err) + } + + message := entity.Message{} + messages := make([]entity.Message, 0, count) + for rows.Next() { + err = rows.Scan(&message.ID, &message.From, &message.To, &message.Content) + if err != nil { + return messages, fmt.Errorf("scan selected message: %w", err) + } + messages = append(messages, message) + } + return messages, nil +} + +func (m *messageRepo) UpdateContentMessage(ctx context.Context, messageID int, newContent string) error { + _, err := m.db.Exec(ctx, UpdateMessageContent, newContent, messageID) + if err != nil { + return fmt.Errorf("update content message in storage: %w", err) + } + return nil +} + +func (m *messageRepo) DelMessage(ctx context.Context, messageID int) error { + _, err := m.db.Exec(ctx, UpdateMessageStatusToDeleted, messageID) + if err != nil { + return fmt.Errorf("delete message from storage: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/message/check.go b/internal/pkg/usecase/message/check.go new file mode 100644 index 0000000..136082f --- /dev/null +++ b/internal/pkg/usecase/message/check.go @@ -0,0 +1,17 @@ +package message + +import ( + "context" + "fmt" +) + +func (m *messageCase) isAvailableForChanges(ctx context.Context, userID, mesID int) (bool, error) { + mes, err := m.repo.GetMessageByID(ctx, mesID) + if err != nil { + return false, fmt.Errorf("get message for check available: %w", err) + } + if mes.From == userID { + return true, nil + } + return false, nil +} diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 6d4fea4..357544e 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -2,14 +2,60 @@ package message import ( "context" + "errors" + "fmt" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" ) +var ErrNoAccess = errors.New("there is no access to perform this action") + //go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { - SendMessage(ctx context.Context, mes *entity.Message) error - GetMessagesFromChat(ctx context.Context, chat entity.Chat, lastID int) ([]entity.Message, error) - UpdateContentMessage(ctx context.Context, mes *entity.Message) error - DeleteMessage(ctx context.Context, mesID int) error + SendMessage(ctx context.Context, mes *entity.Message) (int, error) + GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) + UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error + DeleteMessage(ctx context.Context, userID, mesID int) error +} + +type messageCase struct { + repo mesRepo.Repository +} + +func New(repo mesRepo.Repository) *messageCase { + return &messageCase{repo} +} + +func (m *messageCase) SendMessage(ctx context.Context, mes *entity.Message) (int, error) { + return m.repo.AddNewMessage(ctx, mes) +} + +func (m *messageCase) GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) { + feed, err = m.repo.GetMessages(ctx, chat, count, lastID) + if err != nil { + err = fmt.Errorf("get message: %w", err) + } + if len(feed) != 0 { + newLastID = feed[len(feed)-1].ID + } + return +} + +func (m *messageCase) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error { + if ok, err := m.isAvailableForChanges(ctx, userID, mes.ID); err != nil { + return fmt.Errorf("update message: %w", err) + } else if !ok { + return ErrNoAccess + } + return m.repo.UpdateContentMessage(ctx, mes.ID, mes.Content.String) +} + +func (m *messageCase) DeleteMessage(ctx context.Context, userID, mesID int) error { + if ok, err := m.isAvailableForChanges(ctx, userID, mesID); err != nil { + return fmt.Errorf("delete message: %w", err) + } else if !ok { + return ErrNoAccess + } + return m.repo.DelMessage(ctx, mesID) } From 7155b873349c6bb479206368773e3ebac0acfe1d Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 19 Nov 2023 18:58:37 +0300 Subject: [PATCH 185/266] TP-f07 update sql queries --- internal/pkg/repository/message/queries.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/repository/message/queries.go b/internal/pkg/repository/message/queries.go index 7856ac4..9e2291d 100644 --- a/internal/pkg/repository/message/queries.go +++ b/internal/pkg/repository/message/queries.go @@ -1,10 +1,10 @@ package message const ( - SelectMessageByID = "SELECT user_from, user_to, content WHERE id = $1 AND deleted_at IS NULL;" + SelectMessageByID = "SELECT user_from, user_to, content FROM message WHERE id = $1 AND deleted_at IS NULL;" SelectMessageFromChat = `SELECT id, user_from, user_to, content FROM message - WHERE deleted_at IS NULL AND id < $1 AND + WHERE deleted_at IS NULL AND (id < $1 OR $1 = 0) AND (user_from = $2 AND user_to = $3 OR user_from = $3 AND user_to = $2) ORDER BY id DESC LIMIT $4;` From 26ee2357dd0589c5183bd8d517cddd32164f253f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 19 Nov 2023 22:20:38 +0300 Subject: [PATCH 186/266] TP-f07 add: fake communication via websocket --- go.mod | 1 + go.sum | 2 + internal/api/server/router/router.go | 7 ++- internal/app/app.go | 5 +- internal/pkg/delivery/websocket/websocket.go | 62 ++++++++++++++++++++ internal/pkg/middleware/wrap_response.go | 12 ++++ 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/delivery/websocket/websocket.go diff --git a/go.mod b/go.mod index 1919958..db4e3ed 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/image v0.13.0 + nhooyr.io/websocket v1.8.10 ) require ( diff --git a/go.sum b/go.sum index 9f5314b..59d9fda 100644 --- a/go.sum +++ b/go.sum @@ -229,5 +229,7 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= +nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= star-tex.org/x/tex v0.4.0 h1:AXUwgpnHLCxZUWW3qrmjv6ezNhH3PjUVBuLLejz2cgU= star-tex.org/x/tex v0.4.0/go.mod h1:w91ycsU/DkkCr7GWr60GPWqp3gn2U+6VX71T0o8k8qE= diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index c4e8019..3b7377f 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -9,6 +9,7 @@ import ( _ "github.com/go-park-mail-ru/2023_2_OND_team/docs" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" mw "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/security" @@ -24,7 +25,7 @@ func New() Router { return Router{chi.NewMux()} } -func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.SessionManager, log *logger.Logger) { +func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deliveryWS.HandlerWebSocket, sm session.SessionManager, log *logger.Logger) { cfgCSRF := security.DefaultCSRFConfig() cfgCSRF.PathToGet = "/api/v1/csrf" @@ -100,4 +101,8 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Delete("/delete/{messageID:\\d+}", handler.DeleteMessage) }) }) + + r.Mux.With(auth.RequireAuth).Route("/websocket/connect", func(r chi.Router) { + r.Get("/chat/{userID:\\d+}", wsHandler.WebSocketConnect) + }) } diff --git a/internal/app/app.go b/internal/app/app.go index 5c10b11..778e330 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,6 +10,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" + deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" mesCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" @@ -72,6 +73,8 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { SM: sm, }) + wsHandler := deliveryWS.New(log) + cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { log.Error(err.Error()) @@ -79,7 +82,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } server := server.New(log, cfgServ) router := router.New() - router.RegisterRoute(handler, sm, log) + router.RegisterRoute(handler, wsHandler, sm, log) if err := server.Run(router.Mux); err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go new file mode 100644 index 0000000..4efcc03 --- /dev/null +++ b/internal/pkg/delivery/websocket/websocket.go @@ -0,0 +1,62 @@ +package websocket + +import ( + "fmt" + "math/rand" + "net/http" + "time" + + ws "nhooyr.io/websocket" + "nhooyr.io/websocket/wsjson" + + log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type HandlerWebSocket struct { + log *log.Logger +} + +func New(log *log.Logger) *HandlerWebSocket { + return &HandlerWebSocket{log} +} + +type Event struct { + Event string `json:"event"` + ObjectID int `json:"objID"` + Content string `json:"content"` + PublisherID int `json:"publisherID"` +} + +var mes [3]Event = [3]Event{ + {Event: "del", ObjectID: 12, PublisherID: 2332}, + {Event: "new", ObjectID: 12, Content: "some text", PublisherID: 2332}, + {Event: "edit", ObjectID: 12, Content: "new some text", PublisherID: 2332}, +} + +func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + conn, err := ws.Accept(w, r, &ws.AcceptOptions{}) + if err != nil { + h.log.Error(err.Error()) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"status":"error","code":"websocket_connect","message":"fail connect"}`)) + return + } + defer conn.CloseNow() + + for { + time.Sleep(time.Second * time.Duration(rand.Intn(2))) + err = wsjson.Write(ctx, conn, mes[rand.Int31n(3)]) + if err != nil { + closeStatus := ws.CloseStatus(err) + if closeStatus != ws.StatusNormalClosure { + fmt.Println(closeStatus) + h.log.Error(err.Error()) + } + if closeStatus == -1 { + conn.Close(ws.StatusAbnormalClosure, "error write") + } + return + } + } +} diff --git a/internal/pkg/middleware/wrap_response.go b/internal/pkg/middleware/wrap_response.go index 552218f..aafadaa 100644 --- a/internal/pkg/middleware/wrap_response.go +++ b/internal/pkg/middleware/wrap_response.go @@ -1,10 +1,15 @@ package middleware import ( + "bufio" + "errors" "io" + "net" "net/http" ) +var ErrUnimplementedMethod = errors.New("unimplemented method") + type wrapResponseWriter struct { http.ResponseWriter statusCode int @@ -33,3 +38,10 @@ func (w *wrapResponseWriter) WriteString(data string) (written int, err error) { w.written += written return } + +func (w *wrapResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hijacker, ok := w.ResponseWriter.(http.Hijacker); ok { + return hijacker.Hijack() + } + return nil, nil, ErrUnimplementedMethod +} From 5077b356c46f1a2dc0a35659e68cff8e9aa48313 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 20 Nov 2023 01:05:41 +0300 Subject: [PATCH 187/266] TP-f07 add: origin patterns --- internal/app/app.go | 3 ++- internal/pkg/delivery/websocket/websocket.go | 24 ++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 778e330..3934fc0 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -73,7 +73,8 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { SM: sm, }) - wsHandler := deliveryWS.New(log) + wsHandler := deliveryWS.New(log, + deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 4efcc03..e164a15 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -13,11 +13,25 @@ import ( ) type HandlerWebSocket struct { - log *log.Logger + originPatterns []string + log *log.Logger } -func New(log *log.Logger) *HandlerWebSocket { - return &HandlerWebSocket{log} +type Option func(h *HandlerWebSocket) + +func SetOriginPatterns(patterns []string) Option { + return func(h *HandlerWebSocket) { + h.originPatterns = patterns + } +} + +func New(log *log.Logger, opts ...Option) *HandlerWebSocket { + handlerWS := &HandlerWebSocket{log: log} + for _, opt := range opts { + opt(handlerWS) + } + + return handlerWS } type Event struct { @@ -35,7 +49,7 @@ var mes [3]Event = [3]Event{ func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - conn, err := ws.Accept(w, r, &ws.AcceptOptions{}) + conn, err := ws.Accept(w, r, &ws.AcceptOptions{OriginPatterns: h.originPatterns}) if err != nil { h.log.Error(err.Error()) w.WriteHeader(http.StatusBadRequest) @@ -51,7 +65,7 @@ func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Reque closeStatus := ws.CloseStatus(err) if closeStatus != ws.StatusNormalClosure { fmt.Println(closeStatus) - h.log.Error(err.Error()) + h.log.Warn(err.Error()) } if closeStatus == -1 { conn.Close(ws.StatusAbnormalClosure, "error write") From 7db4eec45990a2bb17729b0c1a1305ae8faac17f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 20 Nov 2023 21:11:16 +0300 Subject: [PATCH 188/266] TP-f07 add: implementation http.Flusher --- internal/pkg/middleware/wrap_response.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/pkg/middleware/wrap_response.go b/internal/pkg/middleware/wrap_response.go index aafadaa..3b44b1e 100644 --- a/internal/pkg/middleware/wrap_response.go +++ b/internal/pkg/middleware/wrap_response.go @@ -45,3 +45,9 @@ func (w *wrapResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { } return nil, nil, ErrUnimplementedMethod } + +func (w *wrapResponseWriter) Flush() { + if flusher, ok := w.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } +} From 5f210acdde0a60d5859a8883ed2a4ad2cee99b8c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 21 Nov 2023 03:12:23 +0300 Subject: [PATCH 189/266] TP-500 update: add global errors package with general application error types, errors --- internal/pkg/errors/types.go | 58 +++++++++++++++++++ .../pkg/repository/{repo.go => errors.go} | 7 +-- 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 internal/pkg/errors/types.go rename internal/pkg/repository/{repo.go => errors.go} (82%) diff --git a/internal/pkg/errors/types.go b/internal/pkg/errors/types.go new file mode 100644 index 0000000..9205f6d --- /dev/null +++ b/internal/pkg/errors/types.go @@ -0,0 +1,58 @@ +package errors + +type Type uint8 + +const ( + _ Type = iota + ErrNotFound + ErrAlreadyExists + ErrInvalidInput + ErrNoAccess + ErrNoAuth + ErrNotImplemented + ErrTimeout +) + +type DeclaredError interface { + Type() Type +} + +// general application errors +type ErrNotAuthenticated struct{} + +func (e *ErrNotAuthenticated) Error() string { + return "Auth required" +} + +func (e *ErrNotAuthenticated) Type() Type { + return ErrNoAuth +} + +type InternalError struct { +} + +func (e *InternalError) Error() string { + return "Internal error occured" +} + +type ErrorNotImplemented struct { +} + +func (e *ErrorNotImplemented) Error() string { + return "Functionality not implemented" +} + +func (e *ErrorNotImplemented) Type() Type { + return ErrNotImplemented +} + +type ErrTimeoutExceeded struct { +} + +func (e *ErrTimeoutExceeded) Error() string { + return "timeout exceeded" +} + +func (e *ErrTimeoutExceeded) Type() Type { + return ErrTimeout +} diff --git a/internal/pkg/repository/repo.go b/internal/pkg/repository/errors.go similarity index 82% rename from internal/pkg/repository/repo.go rename to internal/pkg/repository/errors.go index eb11314..8b2a226 100644 --- a/internal/pkg/repository/repo.go +++ b/internal/pkg/repository/errors.go @@ -1,11 +1,10 @@ package repository -import "errors" - -const ( - TimeFormat = "02.01.2006" +import ( + "errors" ) +// for backward compatibility var ( ErrMethodUnimplemented = errors.New("unimplemented") ErrNoData = errors.New("got no data from repository layer") From 2be95f2e3649b3d878c2a77069751548cd213bd5 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 21 Nov 2023 03:13:38 +0300 Subject: [PATCH 190/266] TP-500 update: add function to get logger from ctx --- pkg/logger/logger.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 1220cd9..a6d4f1a 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,6 +1,7 @@ package logger import ( + "context" "fmt" "go.uber.org/zap" @@ -105,3 +106,10 @@ func (log *Logger) multiLevelSugarLog(logFn zapSugarLogFn, template string, args } logFn(log.Logger.Sugar().With(fieldsSugarLogger...), template, args...) } + +func GetLoggerFromCtx(ctx context.Context) *Logger { + if log, ok := ctx.Value(KeyLogger).(*Logger); ok { + return log + } + return &Logger{} +} From 9527e92dbfc54a9c867954922aa01cefd0cd3d58 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 21 Nov 2023 03:14:49 +0300 Subject: [PATCH 191/266] TP-500 update: add middleware with request timeout assignment --- internal/pkg/middleware/timeout.go | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 internal/pkg/middleware/timeout.go diff --git a/internal/pkg/middleware/timeout.go b/internal/pkg/middleware/timeout.go new file mode 100644 index 0000000..02f5d0f --- /dev/null +++ b/internal/pkg/middleware/timeout.go @@ -0,0 +1,38 @@ +package middleware + +import ( + "context" + "net/http" + "strconv" + "strings" + "time" +) + +func SetRequestTimeout(timeout time.Duration) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if clientTimeout := extractTimeout(r); clientTimeout != 0 { + timeout = time.Duration(clientTimeout) + } + ctx, cancel := context.WithTimeout(r.Context(), timeout) + defer cancel() + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + +func extractTimeout(r *http.Request) int { + var keepAlive string + if keepAlive = r.Header.Get("Keep-Alive"); keepAlive == "" { + return *new(int) + } + + if options := strings.Split(keepAlive, " "); len(options) > 0 && len(options) <= 2 { + if timeoutOpt := options[0]; strings.Contains(timeoutOpt, "timeout") { + if timeout, err := strconv.ParseInt(strings.Split(timeoutOpt, "=")[1], 10, 64); err == nil { + return int(timeout) + } + } + } + return *new(int) +} From 0e5fc51b48a7c08c7f9b476929d0d6407491f023 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 21 Nov 2023 03:19:05 +0300 Subject: [PATCH 192/266] TP-500 update: add general rest api errors on the delivery layer, add getCodeStatusHttp for errors, responseErr for err convertion and response assignment --- internal/pkg/delivery/http/v1/response.go | 110 ++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index ba0b3bd..6b17d74 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -5,8 +5,12 @@ import ( "errors" "fmt" "net/http" + + errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +// for backward compatibility - begin var ( ErrBadBody = errors.New("can't parse body, JSON with correct data types is expected") ErrBadUrlParam = errors.New("bad URL param has been provided") @@ -25,6 +29,85 @@ var ( } ) +// for backward compatibility - end + +type ErrInvalidBody struct{} + +func (e *ErrInvalidBody) Error() string { + return "invalid body" +} + +func (e *ErrInvalidBody) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrInvalidQueryParam struct { + params map[string]string +} + +func (e *ErrInvalidQueryParam) Error() string { + return fmt.Sprintf("invalid query params: %v", e.params) +} + +func (e *ErrInvalidQueryParam) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrInvalidContentType struct{} + +func (e *ErrInvalidContentType) Error() string { + return "invalid content type" +} + +func (e *ErrInvalidContentType) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrInvalidUrlParam struct{} + +func (e *ErrInvalidUrlParam) Error() string { + return "invalid URL param" +} + +func (e *ErrInvalidUrlParam) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrMissingBodyParams struct { + params []string +} + +func (e *ErrMissingBodyParams) Error() string { + return fmt.Sprintf("missing body params: %v", e.params) +} + +func (e *ErrMissingBodyParams) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +func getCodeStatusHttp(err error) (ErrCode string, httpStatus int) { + + var declaredErr errPkg.DeclaredError + if errors.As(err, &declaredErr) { + switch declaredErr.Type() { + case errPkg.ErrInvalidInput: + return "bad_input", http.StatusBadRequest + case errPkg.ErrNotFound: + return "not_found", http.StatusNotFound + case errPkg.ErrAlreadyExists: + return "already_exists", http.StatusConflict + case errPkg.ErrNoAuth: + return "no_auth", http.StatusUnauthorized + case errPkg.ErrNoAccess: + return "no_access", http.StatusForbidden + case errPkg.ErrTimeout: + return "timeout", http.StatusRequestTimeout + } + } + + return "internal_error", http.StatusInternalServerError +} + type JsonResponse struct { Status string `json:"status" example:"ok"` Message string `json:"message" example:"Response message"` @@ -71,3 +154,30 @@ func responseError(w http.ResponseWriter, code, message string) error { _, err = w.Write(resBytes) return err } + +func (h *HandlerHTTP) responseErr(w http.ResponseWriter, r *http.Request, err error) error { + log := logger.GetLoggerFromCtx(r.Context()) + + code, status := getCodeStatusHttp(err) + var message string + if status == http.StatusInternalServerError { + log.Warnf("unexpected error on the delivery http: %s\n", err.Error()) + err := &errPkg.InternalError{} + message = err.Error() + } else { + message = err.Error() + } + + res := JsonErrResponse{ + Status: "error", + Message: message, + Code: code, + } + resBytes, err := json.Marshal(res) + if err != nil { + return fmt.Errorf("responseError: %w", err) + } + w.WriteHeader(status) + _, err = w.Write(resBytes) + return err +} From cc67ccfbd8bc1c37aa209657babd4eb83463d9a2 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 21 Nov 2023 03:23:31 +0300 Subject: [PATCH 193/266] TP-500 update: add user subscription handlers, converter from postgresql error to application error in user and subscription repos --- go.mod | 10 + go.sum | 27 +++ internal/api/server/router/router.go | 22 ++- internal/app/app.go | 5 +- internal/pkg/delivery/http/v1/board.go | 54 +----- internal/pkg/delivery/http/v1/board_errors.go | 55 ++++++ internal/pkg/delivery/http/v1/handler.go | 5 +- internal/pkg/delivery/http/v1/subscription.go | 157 ++++++++++++++++ internal/pkg/entity/user/user.go | 15 ++ .../pkg/repository/subscription/errors.go | 23 +++ .../subscription/postgres/queries.go | 6 + .../repository/subscription/postgres/repo.go | 172 ++++++++++++++++++ internal/pkg/repository/subscription/repo.go | 14 ++ internal/pkg/repository/user/errors.go | 13 ++ internal/pkg/repository/user/queries.go | 1 + internal/pkg/repository/user/repo.go | 40 ++++ internal/pkg/usecase/subscription/create.go | 19 ++ internal/pkg/usecase/subscription/delete.go | 19 ++ internal/pkg/usecase/subscription/errors.go | 23 +++ internal/pkg/usecase/subscription/get.go | 32 ++++ internal/pkg/usecase/subscription/usecase.go | 26 +++ 21 files changed, 682 insertions(+), 56 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/board_errors.go create mode 100644 internal/pkg/delivery/http/v1/subscription.go create mode 100644 internal/pkg/repository/subscription/errors.go create mode 100644 internal/pkg/repository/subscription/postgres/queries.go create mode 100644 internal/pkg/repository/subscription/postgres/repo.go create mode 100644 internal/pkg/repository/subscription/repo.go create mode 100644 internal/pkg/repository/user/errors.go create mode 100644 internal/pkg/usecase/subscription/create.go create mode 100644 internal/pkg/usecase/subscription/delete.go create mode 100644 internal/pkg/usecase/subscription/errors.go create mode 100644 internal/pkg/usecase/subscription/get.go create mode 100644 internal/pkg/usecase/subscription/usecase.go diff --git a/go.mod b/go.mod index 1919958..093f577 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,9 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 github.com/golang/mock v1.6.0 + github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 + github.com/jackc/pgx v3.6.2+incompatible github.com/jackc/pgx/v5 v5.4.3 github.com/joho/godotenv v1.5.1 github.com/microcosm-cc/bluemonday v1.0.26 @@ -23,6 +25,8 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/image v0.13.0 + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.31.0 ) require ( @@ -32,6 +36,7 @@ require ( github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/benoitkugler/textprocessing v0.0.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/apd v1.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dsnet/compress v0.0.1 // indirect @@ -41,8 +46,10 @@ require ( github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect @@ -50,8 +57,10 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tdewolff/minify/v2 v2.20.5 // indirect github.com/tdewolff/parse/v2 v2.7.3 // indirect @@ -62,6 +71,7 @@ require ( golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect star-tex.org/x/tex v0.4.0 // indirect diff --git a/go.sum b/go.sum index 9f5314b..57e189e 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -59,19 +61,30 @@ github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d h1:HrdwTlHVMdi9nOW7ZnYiLmIT1hJHvipIwM0aX3rKn8I= github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= @@ -94,6 +107,7 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -105,6 +119,8 @@ github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02C github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -117,6 +133,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -212,8 +230,17 @@ golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 739da97..e2f33a3 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -2,6 +2,7 @@ package router import ( "net/http" + "time" "github.com/go-chi/chi/v5" "github.com/rs/cors" @@ -16,6 +17,8 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +const requestTimeout = 10 * time.Second + type Router struct { Mux *chi.Mux } @@ -37,8 +40,9 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess ExposedHeaders: []string{cfgCSRF.HeaderSet}, }) - r.Mux.Use(mw.RequestID(log), mw.Logger(log), c.Handler, - security.CSRF(cfgCSRF), mw.SetResponseHeaders(map[string]string{ + r.Mux.Use(mw.SetRequestTimeout(requestTimeout), mw.RequestID(log), mw.Logger(log), c.Handler, + security.CSRF(cfgCSRF), + mw.SetResponseHeaders(map[string]string{ "Content-Type": "application/json", }), auth.NewAuthMiddleware(sm).ContextWithUserID) @@ -62,6 +66,20 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Put("/avatar", handler.ProfileEditAvatar) }) + r.Route("/subscription", func(r chi.Router) { + r.Route("/user", func(r chi.Router) { + r.With(auth.RequireAuth).Group(func(r chi.Router) { + r.Post("/create", handler.Subscribe) + r.Delete("/delete", handler.Unsubscribe) + }) + r.Get("/get", handler.GetSubscriptionInfoForUser) + }) + + r.Get("/", handler.GetProfileInfo) + r.Put("/edit", handler.ProfileEditInfo) + r.Put("/avatar", handler.ProfileEditAvatar) + }) + r.Route("/pin", func(r chi.Router) { r.Get("/{pinID:\\d+}", handler.ViewPin) diff --git a/internal/app/app.go b/internal/app/app.go index e6031b5..5bf01d6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -14,11 +14,13 @@ import ( imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" + subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription/postgres" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -64,8 +66,9 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { userCase := user.New(log, imgCase, userRepo.NewUserRepoPG(pool)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) boardCase := board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()) + subCase := subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool)) - handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase) + handler := deliveryHTTP.New(log, sm, userCase, pinCase, boardCase, subCase) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index f4e5f4d..f492ac4 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -2,7 +2,6 @@ package v1 import ( "encoding/json" - "errors" "fmt" "net/http" "strconv" @@ -11,33 +10,11 @@ import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" - bCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) var TimeFormat = "2006-01-02" -var ( - ErrEmptyTitle = errors.New("empty or null board title has been provided") - ErrEmptyPubOpt = errors.New("null public option has been provided") - ErrInvalidBoardTitle = errors.New("invalid or empty board title has been provided") - ErrInvalidTagTitles = errors.New("invalid tag titles have been provided") - ErrInvalidUsername = errors.New("invalid username has been provided") -) - -var ( - wrappedErrors = map[error]string{ErrInvalidTagTitles: "bad_Tagtitles"} - errCodeCompability = map[error]string{ - ErrInvalidBoardTitle: "bad_boardTitle", - ErrEmptyTitle: "empty_boardTitle", - ErrEmptyPubOpt: "bad_pubOpt", - ErrInvalidUsername: "bad_username", - bCase.ErrInvalidUsername: "non_existingUser", - bCase.ErrNoSuchBoard: "no_board", - bCase.ErrNoAccess: "no_access", - } -) - // data for board creation/update type BoardData struct { Title *string `json:"title" example:"new board"` @@ -89,36 +66,9 @@ func (data *BoardData) Validate() error { return nil } -func getErrCodeMessage(err error) (string, string) { - var ( - code string - general, specific bool - ) - - code, general = generalErrCodeCompability[err] - if general { - return code, err.Error() - } - - code, specific = errCodeCompability[err] - if !specific { - for wrappedErr, code_ := range wrappedErrors { - if errors.Is(err, wrappedErr) { - specific = true - code = code_ - } - } - } - if specific { - return code, err.Error() - } - - return ErrInternalError.Error(), generalErrCodeCompability[ErrInternalError] -} - func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) - if contentType := w.Header().Get("Content-Type"); contentType != ApplicationJson { + if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { code, message := getErrCodeMessage(ErrBadContentType) responseError(w, code, message) return @@ -258,7 +208,7 @@ func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Reque func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) - if contentType := w.Header().Get("Content-Type"); contentType != ApplicationJson { + if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { code, message := getErrCodeMessage(ErrBadContentType) responseError(w, code, message) return diff --git a/internal/pkg/delivery/http/v1/board_errors.go b/internal/pkg/delivery/http/v1/board_errors.go new file mode 100644 index 0000000..8993775 --- /dev/null +++ b/internal/pkg/delivery/http/v1/board_errors.go @@ -0,0 +1,55 @@ +package v1 + +import ( + "errors" + + bCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" +) + +var ( + ErrEmptyTitle = errors.New("empty or null board title has been provided") + ErrEmptyPubOpt = errors.New("null public option has been provided") + ErrInvalidBoardTitle = errors.New("invalid or empty board title has been provided") + ErrInvalidTagTitles = errors.New("invalid tag titles have been provided") + ErrInvalidUsername = errors.New("invalid username has been provided") +) + +var ( + wrappedErrors = map[error]string{ErrInvalidTagTitles: "bad_Tagtitles"} + errCodeCompability = map[error]string{ + ErrInvalidBoardTitle: "bad_boardTitle", + ErrEmptyTitle: "empty_boardTitle", + ErrEmptyPubOpt: "bad_pubOpt", + ErrInvalidUsername: "bad_username", + bCase.ErrInvalidUsername: "non_existingUser", + bCase.ErrNoSuchBoard: "no_board", + bCase.ErrNoAccess: "no_access", + } +) + +func getErrCodeMessage(err error) (string, string) { + var ( + code string + general, specific bool + ) + + code, general = generalErrCodeCompability[err] + if general { + return code, err.Error() + } + + code, specific = errCodeCompability[err] + if !specific { + for wrappedErr, code_ := range wrappedErrors { + if errors.Is(err, wrappedErr) { + specific = true + code = code_ + } + } + } + if specific { + return code, err.Error() + } + + return ErrInternalError.Error(), generalErrCodeCompability[ErrInternalError] +} diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 2500fb1..187b854 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -4,6 +4,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -13,15 +14,17 @@ type HandlerHTTP struct { userCase user.Usecase pinCase pin.Usecase boardCase board.Usecase + subCase subscription.Usecase sm session.SessionManager } -func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, board board.Usecase) *HandlerHTTP { +func New(log *logger.Logger, sm session.SessionManager, user user.Usecase, pin pin.Usecase, board board.Usecase, sub subscription.Usecase) *HandlerHTTP { return &HandlerHTTP{ log: log, userCase: user, pinCase: pin, boardCase: board, + subCase: sub, sm: sm, } } diff --git a/internal/pkg/delivery/http/v1/subscription.go b/internal/pkg/delivery/http/v1/subscription.go new file mode 100644 index 0000000..0735f86 --- /dev/null +++ b/internal/pkg/delivery/http/v1/subscription.go @@ -0,0 +1,157 @@ +package v1 + +import ( + "encoding/json" + "net/http" + "strconv" + + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" +) + +var ( + defaultSubCount = 20 + defaultSubLastID = 0 + subscriptionsView = "subscriptions" + subscribersView = "subscribers" +) + +type SubscriptionAction struct { + To *int `json:"to" example:"2"` +} + +func (s *SubscriptionAction) Validate() error { + if s.To == nil { + return &ErrMissingBodyParams{[]string{"to"}} + } + return nil +} + +func (h *HandlerHTTP) Subscribe(w http.ResponseWriter, r *http.Request) { + if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { + h.responseErr(w, r, &ErrInvalidContentType{}) + return + } + + sub := SubscriptionAction{} + if err := json.NewDecoder(r.Body).Decode(&sub); err != nil { + h.responseErr(w, r, &ErrInvalidBody{}) + return + } + defer r.Body.Close() + if err := sub.Validate(); err != nil { + h.responseErr(w, r, err) + return + } + + from := r.Context().Value(auth.KeyCurrentUserID).(int) + if err := h.subCase.SubscribeToUser(r.Context(), from, *sub.To); err != nil { + h.responseErr(w, r, err) + return + } + + if err := responseOk(http.StatusOK, w, "subscribed successfully", nil); err != nil { + h.responseErr(w, r, err) + } +} + +func (h *HandlerHTTP) Unsubscribe(w http.ResponseWriter, r *http.Request) { + if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { + h.responseErr(w, r, &ErrInvalidContentType{}) + return + } + + sub := SubscriptionAction{} + if err := json.NewDecoder(r.Body).Decode(&sub); err != nil { + h.responseErr(w, r, &ErrInvalidBody{}) + return + } + defer r.Body.Close() + if err := sub.Validate(); err != nil { + h.responseErr(w, r, err) + return + } + + from := r.Context().Value(auth.KeyCurrentUserID).(int) + if err := h.subCase.UnsubscribeFromUser(r.Context(), from, *sub.To); err != nil { + h.responseErr(w, r, err) + return + } + + if err := responseOk(http.StatusOK, w, "unsubscribed successfully", nil); err != nil { + h.responseErr(w, r, err) + } +} + +func (h *HandlerHTTP) GetSubscriptionInfoForUser(w http.ResponseWriter, r *http.Request) { + opts, err := GetOpts(r) + if err != nil { + h.responseErr(w, r, err) + return + } + + users, err := h.subCase.GetSubscriptionInfoForUser(r.Context(), opts) + if err != nil { + h.responseErr(w, r, err) + return + } + + if err := responseOk(http.StatusOK, w, "got subscription info successfully", users); err != nil { + h.responseErr(w, r, err) + } +} + +func GetOpts(r *http.Request) (*userEntity.SubscriptionOpts, error) { + opts := &userEntity.SubscriptionOpts{} + invalidParams := map[string]string{} + + var ( + userID, count, lastID int64 + filter string + err error + ) + if userIdParam := r.URL.Query().Get("userID"); userIdParam != "" { + if userID, err = strconv.ParseInt(userIdParam, 10, 64); err != nil || userID < 0 { + invalidParams["userID"] = userIdParam + } else { + opts.UserID = int(userID) + } + } else { + opts.UserID, _ = r.Context().Value(auth.KeyCurrentUserID).(int) + } + + if countParam := r.URL.Query().Get("count"); countParam != "" { + if count, err = strconv.ParseInt(countParam, 10, 64); err != nil || count < 0 { + invalidParams["count"] = countParam + } else { + opts.Count = int(count) + } + } else { + opts.Count = defaultSubCount + } + + if lastIdParam := r.URL.Query().Get("lastID"); lastIdParam != "" { + if lastID, err = strconv.ParseInt(lastIdParam, 10, 64); err != nil || lastID < 0 { + invalidParams["lastID"] = lastIdParam + } else { + opts.LastID = int(lastID) + } + } else { + opts.LastID = 0 + } + + if filter = r.URL.Query().Get("view"); filter != "" { + if filter != subscriptionsView && filter != subscribersView { + invalidParams["view"] = filter + } else { + opts.Filter = filter + } + } else { + invalidParams["view"] = filter + } + + if len(invalidParams) > 0 { + return nil, &ErrInvalidQueryParam{invalidParams} + } + return opts, nil +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 4b70349..35b4345 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -12,3 +12,18 @@ type User struct { AboutMe pgtype.Text `json:"about_me"` Password string `json:"password,omitempty" example:"pass123"` } // @name User + +// UserInfo + +type SubscriptionUser struct { + Username string `json:"username"` + Avatar string `json:"avatar"` + HasSubscribeFromCurUser bool `json:"is_subscribed"` +} + +type SubscriptionOpts struct { + UserID int + Count int + LastID int + Filter string +} diff --git a/internal/pkg/repository/subscription/errors.go b/internal/pkg/repository/subscription/errors.go new file mode 100644 index 0000000..79d236d --- /dev/null +++ b/internal/pkg/repository/subscription/errors.go @@ -0,0 +1,23 @@ +package subscription + +import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + +type ErrSubscriptionAlreadyExist struct{} + +func (e *ErrSubscriptionAlreadyExist) Error() string { + return "subscription on that user already exists" +} + +func (e *ErrSubscriptionAlreadyExist) Type() errPkg.Type { + return errPkg.ErrAlreadyExists +} + +type ErrNonExistingSubscription struct{} + +func (e *ErrNonExistingSubscription) Error() string { + return "such subscription doesn't exist" +} + +func (e *ErrNonExistingSubscription) Type() errPkg.Type { + return errPkg.ErrNotFound +} diff --git a/internal/pkg/repository/subscription/postgres/queries.go b/internal/pkg/repository/subscription/postgres/queries.go new file mode 100644 index 0000000..f85b966 --- /dev/null +++ b/internal/pkg/repository/subscription/postgres/queries.go @@ -0,0 +1,6 @@ +package subscription + +var ( + CreateSubscriptionUser = "INSERT INTO subscription_user (who, whom) values ($1, $2);" + DeleteSubscriptionUser = "DELETE FROM subscription_user WHERE who = $1 AND whom = $2;" +) diff --git a/internal/pkg/repository/subscription/postgres/repo.go b/internal/pkg/repository/subscription/postgres/repo.go new file mode 100644 index 0000000..998107d --- /dev/null +++ b/internal/pkg/repository/subscription/postgres/repo.go @@ -0,0 +1,172 @@ +package subscription + +import ( + "context" + "errors" + "strconv" + + "github.com/Masterminds/squirrel" + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" + subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/jackc/pgx/v5/pgconn" +) + +type subscriptionRepoPG struct { + db pgtype.PgxPoolIface + sqlBuilder squirrel.StatementBuilderType +} + +func NewSubscriptionRepoPG(db pgtype.PgxPoolIface) subRepo.Repository { + return &subscriptionRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} +} + +func convertErrorPostgres(ctx context.Context, err error) error { + logger := logger.GetLoggerFromCtx(ctx) + + if errors.Is(err, context.DeadlineExceeded) { + return &errPkg.ErrTimeoutExceeded{} + } + + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + switch pgErr.SQLState() { + case strconv.Itoa(23505): + return &subRepo.ErrSubscriptionAlreadyExist{} + default: + logger.Warnf("Unexpected error from subscription repo - postgres: %s\n", err.Error()) + return &errPkg.InternalError{} + } + } + logger.Warnf("Unexpected error from subscription repo: %s\n", err.Error()) + return &errPkg.InternalError{} +} + +func (r *subscriptionRepoPG) CreateSubscriptionUser(ctx context.Context, from, to int) error { + tx, err := r.db.Begin(ctx) + if err != nil { + return convertErrorPostgres(ctx, err) + } + + if _, err = tx.Exec(ctx, CreateSubscriptionUser, from, to); err != nil { + if err := tx.Rollback(ctx); err != nil { + return convertErrorPostgres(ctx, err) + } + return convertErrorPostgres(ctx, err) + } + + if err = tx.Commit(ctx); err != nil { + return convertErrorPostgres(ctx, err) + } + return nil +} + +func (r *subscriptionRepoPG) DeleteSubscriptionUser(ctx context.Context, from, to int) error { + tx, err := r.db.Begin(ctx) + if err != nil { + return convertErrorPostgres(ctx, err) + } + + status, err := tx.Exec(ctx, DeleteSubscriptionUser, from, to) + if err != nil { + if err := tx.Rollback(ctx); err != nil { + return convertErrorPostgres(ctx, err) + } + return convertErrorPostgres(ctx, err) + } + + if err = tx.Commit(ctx); err != nil { + return convertErrorPostgres(ctx, err) + } + if status.RowsAffected() == 0 { + return &subRepo.ErrNonExistingSubscription{} + } + return nil +} + +func (r *subscriptionRepoPG) GetUserSubscriptions(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) { + + var getUserSubscriptions squirrel.SelectBuilder + if currUserID != 0 { + getUserSubscriptions = r.sqlBuilder.Select( + "p.username, p.avatar, s.who IS NOT NULL AS is_subscribed", + ) + } else { + getUserSubscriptions = r.sqlBuilder.Select( + "p.username, p.avatar, false AS is_subscribed", + ) + } + getUserSubscriptions = getUserSubscriptions. + From("subscription_user f"). + LeftJoin("profile p ON f.whom = p.id"). + LeftJoin("subscription_user s ON f.whom = s.whom AND s.who = $1", currUserID). + Where("f.who = $2", userID). + Where("p.deleted_at IS NULL"). + OrderBy("f.whom ASC"). + Limit(uint64(count)). + Offset(uint64(offset)) + + sqlRow, args, err := getUserSubscriptions.ToSql() + if err != nil { + return nil, convertErrorPostgres(ctx, err) + } + + rows, err := r.db.Query(ctx, sqlRow, args...) + defer rows.Close() + + subscriptions := make([]userEntity.SubscriptionUser, 0) + for rows.Next() { + var subscription userEntity.SubscriptionUser + if err = rows.Scan(&subscription.Username, &subscription.Avatar, &subscription.HasSubscribeFromCurUser); err != nil { + return nil, convertErrorPostgres(ctx, err) + } + subscriptions = append(subscriptions, subscription) + } + return subscriptions, nil +} + +func (r *subscriptionRepoPG) GetUserSubscribers(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) { + + var getUserSubscribers squirrel.SelectBuilder + if currUserID != 0 { + getUserSubscribers = r.sqlBuilder.Select( + "p.username, p.avatar, s.who IS NOT NULL AS is_subscribed", + ) + } else { + getUserSubscribers = r.sqlBuilder.Select( + "p.username, p.avatar, false AS is_subscribed", + ) + } + getUserSubscribers = getUserSubscribers. + From("subscription_user f"). + LeftJoin("profile p ON f.who = p.id"). + LeftJoin("subscription_user s ON f.who = s.whom AND s.who = $1", currUserID). + Where("f.whom = $2", userID). + Where("p.deleted_at IS NULL"). + OrderBy("f.who ASC"). + Limit(uint64(count)). + Offset(uint64(offset)) + + sqlRow, args, err := getUserSubscribers.ToSql() + if err != nil { + return nil, convertErrorPostgres(ctx, err) + } + + rows, err := r.db.Query(ctx, sqlRow, args...) + if err != nil { + return nil, convertErrorPostgres(ctx, err) + } + defer rows.Close() + + subscribers := make([]userEntity.SubscriptionUser, 0) + for rows.Next() { + var subscriber userEntity.SubscriptionUser + if err = rows.Scan(&subscriber.Username, &subscriber.Avatar, &subscriber.HasSubscribeFromCurUser); err != nil { + return nil, convertErrorPostgres(ctx, err) + } + subscribers = append(subscribers, subscriber) + } + return subscribers, nil +} diff --git a/internal/pkg/repository/subscription/repo.go b/internal/pkg/repository/subscription/repo.go new file mode 100644 index 0000000..c39cf32 --- /dev/null +++ b/internal/pkg/repository/subscription/repo.go @@ -0,0 +1,14 @@ +package subscription + +import ( + "context" + + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +type Repository interface { + CreateSubscriptionUser(ctx context.Context, from, to int) error + DeleteSubscriptionUser(ctx context.Context, from, to int) error + GetUserSubscriptions(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) + GetUserSubscribers(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) +} diff --git a/internal/pkg/repository/user/errors.go b/internal/pkg/repository/user/errors.go new file mode 100644 index 0000000..e998ec3 --- /dev/null +++ b/internal/pkg/repository/user/errors.go @@ -0,0 +1,13 @@ +package user + +import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + +type ErrNonExistingUser struct{} + +func (e *ErrNonExistingUser) Error() string { + return "user doesn't exist" +} + +func (e *ErrNonExistingUser) Type() errPkg.Type { + return errPkg.ErrNotFound +} diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go index f94d2b9..c8b910c 100644 --- a/internal/pkg/repository/user/queries.go +++ b/internal/pkg/repository/user/queries.go @@ -10,4 +10,5 @@ var ( UpdateAvatarProfile = "UPDATE profile SET avatar = $1 WHERE id = $2;" SelectUserIdByUsername = "SELECT id FROM profile WHERE username = $1;" SelectLastUserID = "SELECT id FROM profile ORDER BY id DESC LIMIT 1;" + CheckUserExistence = "SELECT username FROM profile WHERE id = $1 AND deleted_at IS NULL;" ) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 1fb9deb..935b537 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -2,14 +2,18 @@ package user import ( "context" + "errors" "fmt" sq "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) //go:generate mockgen -destination=./mock/user_mock.go -package=mock -source=repo.go Repository @@ -18,6 +22,7 @@ type Repository interface { GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) GetUserIdByUsername(ctx context.Context, username string) (int, error) + CheckUserExistence(ctx context.Context, userID int) error EditUserAvatar(ctx context.Context, userID int, avatar string) error GetAllUserData(ctx context.Context, userID int) (*user.User, error) EditUserInfo(ctx context.Context, userID int, updateFields S) error @@ -33,6 +38,41 @@ func NewUserRepoPG(db pgtype.PgxPoolIface) *userRepoPG { return &userRepoPG{db} } +func convertErrorPostgres(ctx context.Context, err error) error { + logger := logger.GetLoggerFromCtx(ctx) + + if errors.Is(err, context.DeadlineExceeded) { + return &errPkg.ErrTimeoutExceeded{} + } + + switch err { + case pgx.ErrNoRows: + return &ErrNonExistingUser{} + } + + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + switch pgErr.Code { + // TODO: add err codes + default: + logger.Warnf("Unexpected error from user repo - postgres: %s\n", err.Error()) + return &errPkg.InternalError{} + } + } + logger.Warnf("Unexpected error from user repo: %s\n", err.Error()) + return &errPkg.InternalError{} +} + +func (u *userRepoPG) CheckUserExistence(ctx context.Context, userID int) error { + row := u.db.QueryRow(ctx, CheckUserExistence, userID) + var dummy string + if err := row.Scan(&dummy); err != nil { + return convertErrorPostgres(ctx, err) + } + + return nil +} + func (u *userRepoPG) AddNewUser(ctx context.Context, user *user.User) error { _, err := u.db.Exec(ctx, InsertNewUser, user.Username, user.Password, user.Email) if err != nil { diff --git a/internal/pkg/usecase/subscription/create.go b/internal/pkg/usecase/subscription/create.go new file mode 100644 index 0000000..049015b --- /dev/null +++ b/internal/pkg/usecase/subscription/create.go @@ -0,0 +1,19 @@ +package subscription + +import "context" + +func (u *subscriptionUsecase) SubscribeToUser(ctx context.Context, from, to int) error { + if from == to { + return &ErrSelfSubscription{} + } + + if err := u.userRepo.CheckUserExistence(ctx, to); err != nil { + return err + } + + if err := u.subRepo.CreateSubscriptionUser(ctx, from, to); err != nil { + return err + } + + return nil +} diff --git a/internal/pkg/usecase/subscription/delete.go b/internal/pkg/usecase/subscription/delete.go new file mode 100644 index 0000000..32793df --- /dev/null +++ b/internal/pkg/usecase/subscription/delete.go @@ -0,0 +1,19 @@ +package subscription + +import "context" + +func (u *subscriptionUsecase) UnsubscribeFromUser(ctx context.Context, from, to int) error { + if from == to { + return &ErrSelfUnsubscription{} + } + + if err := u.userRepo.CheckUserExistence(ctx, to); err != nil { + return err + } + + if err := u.subRepo.DeleteSubscriptionUser(ctx, from, to); err != nil { + return err + } + + return nil +} diff --git a/internal/pkg/usecase/subscription/errors.go b/internal/pkg/usecase/subscription/errors.go new file mode 100644 index 0000000..7e5b2f0 --- /dev/null +++ b/internal/pkg/usecase/subscription/errors.go @@ -0,0 +1,23 @@ +package subscription + +import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + +type ErrSelfSubscription struct{} + +func (e *ErrSelfSubscription) Error() string { + return "can't subscribe on yourself" +} + +func (e *ErrSelfSubscription) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrSelfUnsubscription struct{} + +func (e *ErrSelfUnsubscription) Error() string { + return "can't unsubscribe from yourself" +} + +func (e *ErrSelfUnsubscription) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} diff --git a/internal/pkg/usecase/subscription/get.go b/internal/pkg/usecase/subscription/get.go new file mode 100644 index 0000000..6f9e389 --- /dev/null +++ b/internal/pkg/usecase/subscription/get.go @@ -0,0 +1,32 @@ +package subscription + +import ( + "context" + + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" +) + +func (u *subscriptionUsecase) GetSubscriptionInfoForUser(ctx context.Context, subOpts *userEntity.SubscriptionOpts) ([]userEntity.SubscriptionUser, error) { + if err := u.userRepo.CheckUserExistence(ctx, subOpts.UserID); err != nil { + return nil, err + } + + var ( + currUserID, _ = ctx.Value(auth.KeyCurrentUserID).(int) + users = make([]userEntity.SubscriptionUser, 0) + err error + ) + + switch subOpts.Filter { + case "subscriptions": + users, err = u.subRepo.GetUserSubscriptions(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + case "subscribers": + users, err = u.subRepo.GetUserSubscribers(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + } + if err != nil { + return nil, err + } + + return users, nil +} diff --git a/internal/pkg/usecase/subscription/usecase.go b/internal/pkg/usecase/subscription/usecase.go new file mode 100644 index 0000000..f308b49 --- /dev/null +++ b/internal/pkg/usecase/subscription/usecase.go @@ -0,0 +1,26 @@ +package subscription + +import ( + "context" + + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription" + uRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type Usecase interface { + SubscribeToUser(ctx context.Context, from, to int) error + UnsubscribeFromUser(ctx context.Context, from, to int) error + GetSubscriptionInfoForUser(ctx context.Context, subOpts *userEntity.SubscriptionOpts) ([]userEntity.SubscriptionUser, error) +} + +type subscriptionUsecase struct { + subRepo subRepo.Repository + userRepo uRepo.Repository + log *logger.Logger +} + +func New(log *logger.Logger, subRepo subRepo.Repository, uRepo uRepo.Repository) Usecase { + return &subscriptionUsecase{subRepo: subRepo, userRepo: uRepo, log: log} +} From 64a61395d396f848039769067a191ce625a664a0 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Tue, 21 Nov 2023 15:39:14 +0300 Subject: [PATCH 194/266] TP-409 add: description real time server as .proto --- api/proto/realtime.proto | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 api/proto/realtime.proto diff --git a/api/proto/realtime.proto b/api/proto/realtime.proto new file mode 100644 index 0000000..2977b05 --- /dev/null +++ b/api/proto/realtime.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime"; + +package realtime; + +service RealTime { + rpc Publish(PublishMessage) returns (google.protobuf.Empty) {} + rpc Subscribe(Channel) returns (stream Message) {} +} + +message Channel { + string topic = 1; + string name = 2; +} + +enum EventType { + EV_CREATE = 0; + EV_DELETE = 1; + EV_UPDATE = 2; +} + +message EventObject { + int64 id = 1; + EventType type = 2; +} + +message Message { + oneof body { + EventObject object = 1; + string content = 2; + } +} + +message PublishMessage { + Channel channel = 1; + Message message = 2; +} From 6ab1d3071755e46ed3ab7e306e5a3943b40d7c9e Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 21 Nov 2023 19:18:43 +0300 Subject: [PATCH 195/266] TP-500_subscriptions update: add handlers for getting user/profile info with subscriptions, made some optimizations --- internal/api/server/router/router.go | 5 ++ internal/pkg/delivery/http/v1/profile.go | 64 +++++++++++++++++++ internal/pkg/delivery/http/v1/response.go | 10 +-- internal/pkg/delivery/http/v1/subscription.go | 25 +++----- internal/pkg/entity/user/user.go | 15 +++-- .../subscription/postgres/queries.go | 34 ++++++++++ .../repository/subscription/postgres/repo.go | 55 ++-------------- internal/pkg/repository/user/queries.go | 30 +++++++++ internal/pkg/repository/user/repo.go | 25 +++++++- internal/pkg/usecase/subscription/create.go | 6 +- internal/pkg/usecase/subscription/delete.go | 6 +- internal/pkg/usecase/subscription/errors.go | 18 +++++- internal/pkg/usecase/subscription/get.go | 17 ++--- internal/pkg/usecase/user/profile.go | 10 +++ internal/pkg/usecase/user/usecase.go | 2 + 15 files changed, 223 insertions(+), 99 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index e2f33a3..55967dd 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -64,6 +64,11 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, sm session.Sess r.Get("/info", handler.GetProfileInfo) r.Put("/edit", handler.ProfileEditInfo) r.Put("/avatar", handler.ProfileEditAvatar) + r.Get("/header", handler.GetProfileHeaderInfo) + }) + + r.Route("/user", func(r chi.Router) { + r.Get("/info/{userID:\\d+}", handler.GetUserInfo) }) r.Route("/subscription", func(r chi.Router) { diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 4a58a3d..4f5b4d3 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -4,12 +4,76 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" + "github.com/go-chi/chi/v5" + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +type UserInfo struct { + ID int `json:"id" example:"123"` + Username string `json:"username" example:"Snapshot"` + Avatar string `json:"avatar" example:"/pic1"` + Name string `json:"name" example:"Bob"` + Surname string `json:"surname" example:"Dylan"` + About string `json:"about" example:"Cool guy"` + IsSubscribed bool `json:"is_subscribed" example:"true"` + SubsCount int `json:"subscribers" example:"23"` +} + +type ProfileInfo struct { + Username string `json:"username" example:"baobab"` + Avatar string `json:"avatar" example:"/pic1"` + SubsCount int `json:"subscribers" example:"12"` +} + +func ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount int) UserInfo { + return UserInfo{ + ID: user.ID, + Username: user.Username, + Avatar: user.Avatar, + Name: user.Name.String, + Surname: user.Surname.String, + About: user.AboutMe.String, + IsSubscribed: isSubscribed, + SubsCount: subsCount, + } +} + +func ToProfileInfoFromService(user *userEntity.User, subsCount int) ProfileInfo { + return ProfileInfo{ + Username: user.Username, + Avatar: user.Avatar, + SubsCount: subsCount, + } +} + +func (h *HandlerHTTP) GetUserInfo(w http.ResponseWriter, r *http.Request) { + userIdParam := chi.URLParam(r, "userID") + userID, err := strconv.ParseInt(userIdParam, 10, 64) + if err != nil { + h.responseErr(w, r, &ErrInvalidUrlParams{map[string]string{"userID": userIdParam}}) + return + } + + if user, isSubscribed, subsCount, err := h.userCase.GetUserInfo(r.Context(), int(userID)); err != nil { + h.responseErr(w, r, err) + } else if err := responseOk(http.StatusOK, w, "got user info successfully", ToUserInfoFromService(user, isSubscribed, subsCount)); err != nil { + h.responseErr(w, r, err) + } +} + +func (h *HandlerHTTP) GetProfileHeaderInfo(w http.ResponseWriter, r *http.Request) { + if user, subsCount, err := h.userCase.GetProfileInfo(r.Context()); err != nil { + h.responseErr(w, r, err) + } else if err := responseOk(http.StatusOK, w, "got profile info successfully", ToProfileInfoFromService(user, subsCount)); err != nil { + h.responseErr(w, r, err) + } +} + func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index 6b17d74..37c319f 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -63,13 +63,15 @@ func (e *ErrInvalidContentType) Type() errPkg.Type { return errPkg.ErrInvalidInput } -type ErrInvalidUrlParam struct{} +type ErrInvalidUrlParams struct { + params map[string]string +} -func (e *ErrInvalidUrlParam) Error() string { - return "invalid URL param" +func (e *ErrInvalidUrlParams) Error() string { + return fmt.Sprintf("invalid URL params: %v", e.params) } -func (e *ErrInvalidUrlParam) Type() errPkg.Type { +func (e *ErrInvalidUrlParams) Type() errPkg.Type { return errPkg.ErrInvalidInput } diff --git a/internal/pkg/delivery/http/v1/subscription.go b/internal/pkg/delivery/http/v1/subscription.go index 0735f86..c3e9a23 100644 --- a/internal/pkg/delivery/http/v1/subscription.go +++ b/internal/pkg/delivery/http/v1/subscription.go @@ -14,6 +14,7 @@ var ( defaultSubLastID = 0 subscriptionsView = "subscriptions" subscribersView = "subscribers" + maxCount = 50 ) type SubscriptionAction struct { @@ -47,12 +48,10 @@ func (h *HandlerHTTP) Subscribe(w http.ResponseWriter, r *http.Request) { from := r.Context().Value(auth.KeyCurrentUserID).(int) if err := h.subCase.SubscribeToUser(r.Context(), from, *sub.To); err != nil { h.responseErr(w, r, err) - return - } - - if err := responseOk(http.StatusOK, w, "subscribed successfully", nil); err != nil { + } else if err := responseOk(http.StatusOK, w, "subscribed successfully", nil); err != nil { h.responseErr(w, r, err) } + } func (h *HandlerHTTP) Unsubscribe(w http.ResponseWriter, r *http.Request) { @@ -75,10 +74,7 @@ func (h *HandlerHTTP) Unsubscribe(w http.ResponseWriter, r *http.Request) { from := r.Context().Value(auth.KeyCurrentUserID).(int) if err := h.subCase.UnsubscribeFromUser(r.Context(), from, *sub.To); err != nil { h.responseErr(w, r, err) - return - } - - if err := responseOk(http.StatusOK, w, "unsubscribed successfully", nil); err != nil { + } else if err := responseOk(http.StatusOK, w, "unsubscribed successfully", nil); err != nil { h.responseErr(w, r, err) } } @@ -90,13 +86,9 @@ func (h *HandlerHTTP) GetSubscriptionInfoForUser(w http.ResponseWriter, r *http. return } - users, err := h.subCase.GetSubscriptionInfoForUser(r.Context(), opts) - if err != nil { + if users, err := h.subCase.GetSubscriptionInfoForUser(r.Context(), opts); err != nil { h.responseErr(w, r, err) - return - } - - if err := responseOk(http.StatusOK, w, "got subscription info successfully", users); err != nil { + } else if err := responseOk(http.StatusOK, w, "got subscription info successfully", users); err != nil { h.responseErr(w, r, err) } } @@ -137,7 +129,7 @@ func GetOpts(r *http.Request) (*userEntity.SubscriptionOpts, error) { opts.LastID = int(lastID) } } else { - opts.LastID = 0 + opts.LastID = defaultSubLastID } if filter = r.URL.Query().Get("view"); filter != "" { @@ -150,6 +142,9 @@ func GetOpts(r *http.Request) (*userEntity.SubscriptionOpts, error) { invalidParams["view"] = filter } + if opts.Count > maxCount { + opts.Count = maxCount + } if len(invalidParams) > 0 { return nil, &ErrInvalidQueryParam{invalidParams} } diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 35b4345..4a28e48 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -3,19 +3,24 @@ package user import "github.com/jackc/pgx/v5/pgtype" type User struct { - ID int `json:"id" example:"123"` + ID int `json:"id,omitempty" example:"123"` Username string `json:"username" example:"Green"` - Name pgtype.Text `json:"name" example:"Peter"` - Surname pgtype.Text `json:"surname" example:"Green"` + Name pgtype.Text `json:"name,omitempty" example:"Peter"` + Surname pgtype.Text `json:"surname,omitempty" example:"Green"` Email string `json:"email,omitempty" example:"digital@gmail.com"` Avatar string `json:"avatar" example:"pinspire.online/avatars/avatar.jpg"` - AboutMe pgtype.Text `json:"about_me"` + AboutMe pgtype.Text `json:"about_me,omitempty"` Password string `json:"password,omitempty" example:"pass123"` } // @name User -// UserInfo +type UserInfo struct { // maybe just user, subCount and is sub, in profiel case -> user, subCount + User User + SubscribersNum int `json:"subscribers" example:"22"` + HasSubscribeFromCurUser bool `json:"is_subscribed" example:"false"` +} type SubscriptionUser struct { + ID int `json:"id"` Username string `json:"username"` Avatar string `json:"avatar"` HasSubscribeFromCurUser bool `json:"is_subscribed"` diff --git a/internal/pkg/repository/subscription/postgres/queries.go b/internal/pkg/repository/subscription/postgres/queries.go index f85b966..4aa1d4f 100644 --- a/internal/pkg/repository/subscription/postgres/queries.go +++ b/internal/pkg/repository/subscription/postgres/queries.go @@ -3,4 +3,38 @@ package subscription var ( CreateSubscriptionUser = "INSERT INTO subscription_user (who, whom) values ($1, $2);" DeleteSubscriptionUser = "DELETE FROM subscription_user WHERE who = $1 AND whom = $2;" + GetUserSubscriptions = ` + SELECT + p.id, p.username, p.avatar, s.who IS NOT NULL AS is_subscribed + FROM + subscription_user f + LEFT JOIN + profile p ON f.whom = p.id + LEFT JOIN + subscription_user s ON f.whom = s.whom AND s.who = $1 + WHERE + f.who = $2 AND p.deleted_at IS NULL + ORDER BY + f.whom ASC + LIMIT + $3 + OFFSET + $4;` + GetUserSubscribers = ` + SELECT + p.id, p.username, p.avatar, s.who IS NOT NULL AS is_subscribed + FROM + subscription_user f + LEFT JOIN + profile p ON f.who = p.id + LEFT JOIN + subscription_user s ON f.who = s.whom AND s.who = $1 + WHERE + f.whom = $2 AND p.deleted_at IS NULL + ORDER BY + f.who ASC + LIMIT + $3 + OFFSET + $4;` ) diff --git a/internal/pkg/repository/subscription/postgres/repo.go b/internal/pkg/repository/subscription/postgres/repo.go index 998107d..7f6aed4 100644 --- a/internal/pkg/repository/subscription/postgres/repo.go +++ b/internal/pkg/repository/subscription/postgres/repo.go @@ -88,38 +88,16 @@ func (r *subscriptionRepoPG) DeleteSubscriptionUser(ctx context.Context, from, t func (r *subscriptionRepoPG) GetUserSubscriptions(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) { - var getUserSubscriptions squirrel.SelectBuilder - if currUserID != 0 { - getUserSubscriptions = r.sqlBuilder.Select( - "p.username, p.avatar, s.who IS NOT NULL AS is_subscribed", - ) - } else { - getUserSubscriptions = r.sqlBuilder.Select( - "p.username, p.avatar, false AS is_subscribed", - ) - } - getUserSubscriptions = getUserSubscriptions. - From("subscription_user f"). - LeftJoin("profile p ON f.whom = p.id"). - LeftJoin("subscription_user s ON f.whom = s.whom AND s.who = $1", currUserID). - Where("f.who = $2", userID). - Where("p.deleted_at IS NULL"). - OrderBy("f.whom ASC"). - Limit(uint64(count)). - Offset(uint64(offset)) - - sqlRow, args, err := getUserSubscriptions.ToSql() + rows, err := r.db.Query(ctx, GetUserSubscriptions, currUserID, userID, count, offset) if err != nil { return nil, convertErrorPostgres(ctx, err) } - - rows, err := r.db.Query(ctx, sqlRow, args...) defer rows.Close() subscriptions := make([]userEntity.SubscriptionUser, 0) for rows.Next() { var subscription userEntity.SubscriptionUser - if err = rows.Scan(&subscription.Username, &subscription.Avatar, &subscription.HasSubscribeFromCurUser); err != nil { + if err = rows.Scan(&subscription.ID, &subscription.Username, &subscription.Avatar, &subscription.HasSubscribeFromCurUser); err != nil { return nil, convertErrorPostgres(ctx, err) } subscriptions = append(subscriptions, subscription) @@ -129,32 +107,7 @@ func (r *subscriptionRepoPG) GetUserSubscriptions(ctx context.Context, userID, c func (r *subscriptionRepoPG) GetUserSubscribers(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) { - var getUserSubscribers squirrel.SelectBuilder - if currUserID != 0 { - getUserSubscribers = r.sqlBuilder.Select( - "p.username, p.avatar, s.who IS NOT NULL AS is_subscribed", - ) - } else { - getUserSubscribers = r.sqlBuilder.Select( - "p.username, p.avatar, false AS is_subscribed", - ) - } - getUserSubscribers = getUserSubscribers. - From("subscription_user f"). - LeftJoin("profile p ON f.who = p.id"). - LeftJoin("subscription_user s ON f.who = s.whom AND s.who = $1", currUserID). - Where("f.whom = $2", userID). - Where("p.deleted_at IS NULL"). - OrderBy("f.who ASC"). - Limit(uint64(count)). - Offset(uint64(offset)) - - sqlRow, args, err := getUserSubscribers.ToSql() - if err != nil { - return nil, convertErrorPostgres(ctx, err) - } - - rows, err := r.db.Query(ctx, sqlRow, args...) + rows, err := r.db.Query(ctx, GetUserSubscribers, currUserID, userID, count, offset) if err != nil { return nil, convertErrorPostgres(ctx, err) } @@ -163,7 +116,7 @@ func (r *subscriptionRepoPG) GetUserSubscribers(ctx context.Context, userID, cou subscribers := make([]userEntity.SubscriptionUser, 0) for rows.Next() { var subscriber userEntity.SubscriptionUser - if err = rows.Scan(&subscriber.Username, &subscriber.Avatar, &subscriber.HasSubscribeFromCurUser); err != nil { + if err = rows.Scan(&subscriber.ID, &subscriber.Username, &subscriber.Avatar, &subscriber.HasSubscribeFromCurUser); err != nil { return nil, convertErrorPostgres(ctx, err) } subscribers = append(subscribers, subscriber) diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go index c8b910c..ae27304 100644 --- a/internal/pkg/repository/user/queries.go +++ b/internal/pkg/repository/user/queries.go @@ -11,4 +11,34 @@ var ( SelectUserIdByUsername = "SELECT id FROM profile WHERE username = $1;" SelectLastUserID = "SELECT id FROM profile ORDER BY id DESC LIMIT 1;" CheckUserExistence = "SELECT username FROM profile WHERE id = $1 AND deleted_at IS NULL;" + GetUserInfo = ` + SELECT + p1.id, p1.username, p1.avatar, COALESCE(p1.name, '') name, COALESCE(p1.surname, '') surname, COALESCE(p1.about_me, '') about_me, s2.who IS NOT NULL as is_subscribed, COUNT(s1.who) subscribers + FROM + profile p1 + LEFT JOIN + subscription_user s1 ON p1.id = s1.whom + LEFT JOIN + profile p2 ON s1.who = p2.id + LEFT JOIN + subscription_user s2 ON s1.whom = s2.whom AND s2.who = $1 + WHERE + p1.id = $2 AND p1.deleted_at IS NULL AND p2.deleted_at IS NULL + GROUP BY + p1.id, p1.username,p1.avatar, p1.name, p1.surname, p1.about_me, s2.who IS NOT NULL; + ` + GetProfileInfo = ` + SELECT + p1.username, p1.avatar, COUNT(s.who) subscribers + FROM + profile p1 + LEFT JOIN + subscription_user s ON p1.id = s.whom + LEFT JOIN + profile p2 ON s.who = p2.id + WHERE + p1.id = $1 AND p1.deleted_at IS NULL AND p2.deleted_at IS NULL + GROUP BY + p1.username, p1.avatar; + ` ) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 935b537..fb69777 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -22,6 +22,8 @@ type Repository interface { GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUsernameAndAvatarByID(ctx context.Context, userID int) (username string, avatar string, err error) GetUserIdByUsername(ctx context.Context, username string) (int, error) + GetUserData(ctx context.Context, userID, currUserID int) (user_ *user.User, isSubscribed bool, subsCount int, err error) + GetProfileData(ctx context.Context, userID int) (user_ *user.User, subsCount int, err error) CheckUserExistence(ctx context.Context, userID int) error EditUserAvatar(ctx context.Context, userID int, avatar string) error GetAllUserData(ctx context.Context, userID int) (*user.User, error) @@ -53,7 +55,7 @@ func convertErrorPostgres(ctx context.Context, err error) error { var pgErr *pgconn.PgError if errors.As(err, &pgErr) { switch pgErr.Code { - // TODO: add err codes + // add SQL states if necessary default: logger.Warnf("Unexpected error from user repo - postgres: %s\n", err.Error()) return &errPkg.InternalError{} @@ -100,6 +102,27 @@ func (u *userRepoPG) GetUsernameAndAvatarByID(ctx context.Context, userID int) ( return } +func (u *userRepoPG) GetUserData(ctx context.Context, userID, currUserID int) (user_ *user.User, isSubscribed bool, subsCount int, err error) { + user_ = &user.User{} + if err := u.db.QueryRow(ctx, GetUserInfo, currUserID, userID).Scan( + &user_.ID, &user_.Username, &user_.Avatar, &user_.Name, &user_.Surname, + &user_.AboutMe, &isSubscribed, &subsCount, + ); err != nil { + return nil, false, 0, convertErrorPostgres(ctx, err) + } + return user_, isSubscribed, subsCount, nil +} + +func (u *userRepoPG) GetProfileData(ctx context.Context, userID int) (user_ *user.User, subsCount int, err error) { + user_ = &user.User{} + if err := u.db.QueryRow(ctx, GetProfileInfo, userID).Scan( + &user_.Username, &user_.Avatar, &subsCount, + ); err != nil { + return nil, 0, convertErrorPostgres(ctx, err) + } + return user_, subsCount, nil +} + func (u *userRepoPG) EditUserAvatar(ctx context.Context, userID int, avatar string) error { _, err := u.db.Exec(ctx, UpdateAvatarProfile, avatar, userID) if err != nil { diff --git a/internal/pkg/usecase/subscription/create.go b/internal/pkg/usecase/subscription/create.go index 049015b..dba99a0 100644 --- a/internal/pkg/usecase/subscription/create.go +++ b/internal/pkg/usecase/subscription/create.go @@ -11,9 +11,5 @@ func (u *subscriptionUsecase) SubscribeToUser(ctx context.Context, from, to int) return err } - if err := u.subRepo.CreateSubscriptionUser(ctx, from, to); err != nil { - return err - } - - return nil + return u.subRepo.CreateSubscriptionUser(ctx, from, to) } diff --git a/internal/pkg/usecase/subscription/delete.go b/internal/pkg/usecase/subscription/delete.go index 32793df..c8c95f2 100644 --- a/internal/pkg/usecase/subscription/delete.go +++ b/internal/pkg/usecase/subscription/delete.go @@ -11,9 +11,5 @@ func (u *subscriptionUsecase) UnsubscribeFromUser(ctx context.Context, from, to return err } - if err := u.subRepo.DeleteSubscriptionUser(ctx, from, to); err != nil { - return err - } - - return nil + return u.subRepo.DeleteSubscriptionUser(ctx, from, to) } diff --git a/internal/pkg/usecase/subscription/errors.go b/internal/pkg/usecase/subscription/errors.go index 7e5b2f0..4b2e376 100644 --- a/internal/pkg/usecase/subscription/errors.go +++ b/internal/pkg/usecase/subscription/errors.go @@ -1,6 +1,10 @@ package subscription -import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" +import ( + "fmt" + + errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" +) type ErrSelfSubscription struct{} @@ -21,3 +25,15 @@ func (e *ErrSelfUnsubscription) Error() string { func (e *ErrSelfUnsubscription) Type() errPkg.Type { return errPkg.ErrInvalidInput } + +type ErrInvalidFilter struct { + filter string +} + +func (e *ErrInvalidFilter) Error() string { + return fmt.Sprintf("invalid filter: %s", e.filter) +} + +func (e *ErrInvalidFilter) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} diff --git a/internal/pkg/usecase/subscription/get.go b/internal/pkg/usecase/subscription/get.go index 6f9e389..1e86ad1 100644 --- a/internal/pkg/usecase/subscription/get.go +++ b/internal/pkg/usecase/subscription/get.go @@ -12,21 +12,14 @@ func (u *subscriptionUsecase) GetSubscriptionInfoForUser(ctx context.Context, su return nil, err } - var ( - currUserID, _ = ctx.Value(auth.KeyCurrentUserID).(int) - users = make([]userEntity.SubscriptionUser, 0) - err error - ) + currUserID, _ := ctx.Value(auth.KeyCurrentUserID).(int) switch subOpts.Filter { case "subscriptions": - users, err = u.subRepo.GetUserSubscriptions(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + return u.subRepo.GetUserSubscriptions(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) case "subscribers": - users, err = u.subRepo.GetUserSubscribers(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + return u.subRepo.GetUserSubscribers(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + default: + return nil, &ErrInvalidFilter{subOpts.Filter} } - if err != nil { - return nil, err - } - - return users, nil } diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 5cb4f34..1a5d1e4 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -7,6 +7,7 @@ import ( "io" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" repository "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/crypto" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" @@ -27,6 +28,15 @@ func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAva return nil } +func (u *userCase) GetUserInfo(ctx context.Context, userID int) (user *entity.User, isSubscribed bool, subsCount int, err error) { + currUserID, _ := ctx.Value(auth.KeyCurrentUserID).(int) + return u.repo.GetUserData(ctx, userID, currUserID) +} + +func (u *userCase) GetProfileInfo(ctx context.Context) (user *entity.User, subsCount int, err error) { + return u.repo.GetProfileData(ctx, ctx.Value(auth.KeyCurrentUserID).(int)) +} + func (u *userCase) GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) { return u.repo.GetAllUserData(ctx, userID) } diff --git a/internal/pkg/usecase/user/usecase.go b/internal/pkg/usecase/user/usecase.go index 167860c..c9a3eb9 100644 --- a/internal/pkg/usecase/user/usecase.go +++ b/internal/pkg/usecase/user/usecase.go @@ -25,6 +25,8 @@ type Usecase interface { FindOutUsernameAndAvatar(ctx context.Context, userID int) (username string, avatar string, err error) UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAvatar string, sizeAvatar int64, avatar io.Reader) error GetAllProfileInfo(ctx context.Context, userID int) (*entity.User, error) + GetUserInfo(ctx context.Context, userID int) (user *entity.User, isSubscribed bool, subsCount int, err error) + GetProfileInfo(ctx context.Context) (user *entity.User, subsCount int, err error) EditProfileInfo(ctx context.Context, userID int, updateData *ProfileUpdateData) error } From 63711332103f6034e6416e2c1b4aa369fe7b1e06 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 22 Nov 2023 13:16:28 +0300 Subject: [PATCH 196/266] TP-500_subscriptions: add id field in profile header --- internal/pkg/delivery/http/v1/profile.go | 2 ++ internal/pkg/repository/user/queries.go | 4 ++-- internal/pkg/repository/user/repo.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 4f5b4d3..b03ed6f 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -25,6 +25,7 @@ type UserInfo struct { } type ProfileInfo struct { + ID int `json:"id" example:"1"` Username string `json:"username" example:"baobab"` Avatar string `json:"avatar" example:"/pic1"` SubsCount int `json:"subscribers" example:"12"` @@ -45,6 +46,7 @@ func ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount i func ToProfileInfoFromService(user *userEntity.User, subsCount int) ProfileInfo { return ProfileInfo{ + ID: user.ID, Username: user.Username, Avatar: user.Avatar, SubsCount: subsCount, diff --git a/internal/pkg/repository/user/queries.go b/internal/pkg/repository/user/queries.go index ae27304..677b99c 100644 --- a/internal/pkg/repository/user/queries.go +++ b/internal/pkg/repository/user/queries.go @@ -29,7 +29,7 @@ var ( ` GetProfileInfo = ` SELECT - p1.username, p1.avatar, COUNT(s.who) subscribers + p1.id, p1.username, p1.avatar, COUNT(s.who) subscribers FROM profile p1 LEFT JOIN @@ -39,6 +39,6 @@ var ( WHERE p1.id = $1 AND p1.deleted_at IS NULL AND p2.deleted_at IS NULL GROUP BY - p1.username, p1.avatar; + p1.id, p1.username, p1.avatar; ` ) diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index fb69777..985aced 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -116,7 +116,7 @@ func (u *userRepoPG) GetUserData(ctx context.Context, userID, currUserID int) (u func (u *userRepoPG) GetProfileData(ctx context.Context, userID int) (user_ *user.User, subsCount int, err error) { user_ = &user.User{} if err := u.db.QueryRow(ctx, GetProfileInfo, userID).Scan( - &user_.Username, &user_.Avatar, &subsCount, + &user_.ID, &user_.Username, &user_.Avatar, &subsCount, ); err != nil { return nil, 0, convertErrorPostgres(ctx, err) } From 0b539fa4c345b6730172d20ddf493ff225afdbba Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 22 Nov 2023 23:13:13 +0300 Subject: [PATCH 197/266] TP-409 add: skeleton real time server --- api/realtime/realtime.pb.go | 481 ++++++++++++++++++ api/realtime/realtime_grpc.pb.go | 170 +++++++ cmd/realtime/main.go | 43 ++ deployments/docker-compose.yml | 31 ++ go.mod | 20 + go.sum | 70 +++ .../microservices/realtime/kafka_broker.go | 163 ++++++ internal/microservices/realtime/server.go | 142 ++++++ 8 files changed, 1120 insertions(+) create mode 100644 api/realtime/realtime.pb.go create mode 100644 api/realtime/realtime_grpc.pb.go create mode 100644 cmd/realtime/main.go create mode 100644 internal/microservices/realtime/kafka_broker.go create mode 100644 internal/microservices/realtime/server.go diff --git a/api/realtime/realtime.pb.go b/api/realtime/realtime.pb.go new file mode 100644 index 0000000..8e17fe5 --- /dev/null +++ b/api/realtime/realtime.pb.go @@ -0,0 +1,481 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.6.1 +// source: api/proto/realtime.proto + +package realtime + +import ( + empty "github.com/golang/protobuf/ptypes/empty" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type EventType int32 + +const ( + EventType_EV_CREATE EventType = 0 + EventType_EV_DELETE EventType = 1 + EventType_EV_UPDATE EventType = 2 +) + +// Enum value maps for EventType. +var ( + EventType_name = map[int32]string{ + 0: "EV_CREATE", + 1: "EV_DELETE", + 2: "EV_UPDATE", + } + EventType_value = map[string]int32{ + "EV_CREATE": 0, + "EV_DELETE": 1, + "EV_UPDATE": 2, + } +) + +func (x EventType) Enum() *EventType { + p := new(EventType) + *p = x + return p +} + +func (x EventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EventType) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_realtime_proto_enumTypes[0].Descriptor() +} + +func (EventType) Type() protoreflect.EnumType { + return &file_api_proto_realtime_proto_enumTypes[0] +} + +func (x EventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EventType.Descriptor instead. +func (EventType) EnumDescriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{0} +} + +type Channel struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Channel) Reset() { + *x = Channel{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_realtime_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Channel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Channel) ProtoMessage() {} + +func (x *Channel) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_realtime_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Channel.ProtoReflect.Descriptor instead. +func (*Channel) Descriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{0} +} + +func (x *Channel) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *Channel) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type EventObject struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Type EventType `protobuf:"varint,2,opt,name=type,proto3,enum=realtime.EventType" json:"type,omitempty"` +} + +func (x *EventObject) Reset() { + *x = EventObject{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_realtime_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EventObject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventObject) ProtoMessage() {} + +func (x *EventObject) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_realtime_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventObject.ProtoReflect.Descriptor instead. +func (*EventObject) Descriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{1} +} + +func (x *EventObject) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *EventObject) GetType() EventType { + if x != nil { + return x.Type + } + return EventType_EV_CREATE +} + +type Message struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Body: + // + // *Message_Object + // *Message_Content + Body isMessage_Body `protobuf_oneof:"body"` +} + +func (x *Message) Reset() { + *x = Message{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_realtime_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Message) ProtoMessage() {} + +func (x *Message) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_realtime_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Message.ProtoReflect.Descriptor instead. +func (*Message) Descriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{2} +} + +func (m *Message) GetBody() isMessage_Body { + if m != nil { + return m.Body + } + return nil +} + +func (x *Message) GetObject() *EventObject { + if x, ok := x.GetBody().(*Message_Object); ok { + return x.Object + } + return nil +} + +func (x *Message) GetContent() string { + if x, ok := x.GetBody().(*Message_Content); ok { + return x.Content + } + return "" +} + +type isMessage_Body interface { + isMessage_Body() +} + +type Message_Object struct { + Object *EventObject `protobuf:"bytes,1,opt,name=object,proto3,oneof"` +} + +type Message_Content struct { + Content string `protobuf:"bytes,2,opt,name=content,proto3,oneof"` +} + +func (*Message_Object) isMessage_Body() {} + +func (*Message_Content) isMessage_Body() {} + +type PublishMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Channel *Channel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` + Message *Message `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *PublishMessage) Reset() { + *x = PublishMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_realtime_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PublishMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishMessage) ProtoMessage() {} + +func (x *PublishMessage) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_realtime_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishMessage.ProtoReflect.Descriptor instead. +func (*PublishMessage) Descriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{3} +} + +func (x *PublishMessage) GetChannel() *Channel { + if x != nil { + return x.Channel + } + return nil +} + +func (x *PublishMessage) GetMessage() *Message { + if x != nil { + return x.Message + } + return nil +} + +var File_api_proto_realtime_proto protoreflect.FileDescriptor + +var file_api_proto_realtime_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x33, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, + 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x0b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x5e, + 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x48, 0x00, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x6a, + 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x2b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x2b, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, + 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x38, 0x0a, 0x09, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x43, 0x52, + 0x45, 0x41, 0x54, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x44, 0x45, 0x4c, + 0x45, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x55, 0x50, 0x44, 0x41, + 0x54, 0x45, 0x10, 0x02, 0x32, 0x80, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x18, 0x2e, 0x72, + 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x12, 0x35, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x11, 0x2e, + 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x1a, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x70, 0x61, 0x72, 0x6b, 0x2d, 0x6d, 0x61, + 0x69, 0x6c, 0x2d, 0x72, 0x75, 0x2f, 0x32, 0x30, 0x32, 0x33, 0x5f, 0x32, 0x5f, 0x4f, 0x4e, 0x44, + 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, + 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_realtime_proto_rawDescOnce sync.Once + file_api_proto_realtime_proto_rawDescData = file_api_proto_realtime_proto_rawDesc +) + +func file_api_proto_realtime_proto_rawDescGZIP() []byte { + file_api_proto_realtime_proto_rawDescOnce.Do(func() { + file_api_proto_realtime_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_realtime_proto_rawDescData) + }) + return file_api_proto_realtime_proto_rawDescData +} + +var file_api_proto_realtime_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_api_proto_realtime_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_api_proto_realtime_proto_goTypes = []interface{}{ + (EventType)(0), // 0: realtime.EventType + (*Channel)(nil), // 1: realtime.Channel + (*EventObject)(nil), // 2: realtime.EventObject + (*Message)(nil), // 3: realtime.Message + (*PublishMessage)(nil), // 4: realtime.PublishMessage + (*empty.Empty)(nil), // 5: google.protobuf.Empty +} +var file_api_proto_realtime_proto_depIdxs = []int32{ + 0, // 0: realtime.EventObject.type:type_name -> realtime.EventType + 2, // 1: realtime.Message.object:type_name -> realtime.EventObject + 1, // 2: realtime.PublishMessage.channel:type_name -> realtime.Channel + 3, // 3: realtime.PublishMessage.message:type_name -> realtime.Message + 4, // 4: realtime.RealTime.Publish:input_type -> realtime.PublishMessage + 1, // 5: realtime.RealTime.Subscribe:input_type -> realtime.Channel + 5, // 6: realtime.RealTime.Publish:output_type -> google.protobuf.Empty + 3, // 7: realtime.RealTime.Subscribe:output_type -> realtime.Message + 6, // [6:8] is the sub-list for method output_type + 4, // [4:6] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_api_proto_realtime_proto_init() } +func file_api_proto_realtime_proto_init() { + if File_api_proto_realtime_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_realtime_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Channel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_realtime_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EventObject); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_realtime_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Message); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_realtime_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PublishMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_api_proto_realtime_proto_msgTypes[2].OneofWrappers = []interface{}{ + (*Message_Object)(nil), + (*Message_Content)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_realtime_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_realtime_proto_goTypes, + DependencyIndexes: file_api_proto_realtime_proto_depIdxs, + EnumInfos: file_api_proto_realtime_proto_enumTypes, + MessageInfos: file_api_proto_realtime_proto_msgTypes, + }.Build() + File_api_proto_realtime_proto = out.File + file_api_proto_realtime_proto_rawDesc = nil + file_api_proto_realtime_proto_goTypes = nil + file_api_proto_realtime_proto_depIdxs = nil +} diff --git a/api/realtime/realtime_grpc.pb.go b/api/realtime/realtime_grpc.pb.go new file mode 100644 index 0000000..759e3ff --- /dev/null +++ b/api/realtime/realtime_grpc.pb.go @@ -0,0 +1,170 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.6.1 +// source: api/proto/realtime.proto + +package realtime + +import ( + context "context" + empty "github.com/golang/protobuf/ptypes/empty" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// RealTimeClient is the client API for RealTime service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RealTimeClient interface { + Publish(ctx context.Context, in *PublishMessage, opts ...grpc.CallOption) (*empty.Empty, error) + Subscribe(ctx context.Context, in *Channel, opts ...grpc.CallOption) (RealTime_SubscribeClient, error) +} + +type realTimeClient struct { + cc grpc.ClientConnInterface +} + +func NewRealTimeClient(cc grpc.ClientConnInterface) RealTimeClient { + return &realTimeClient{cc} +} + +func (c *realTimeClient) Publish(ctx context.Context, in *PublishMessage, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/realtime.RealTime/Publish", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *realTimeClient) Subscribe(ctx context.Context, in *Channel, opts ...grpc.CallOption) (RealTime_SubscribeClient, error) { + stream, err := c.cc.NewStream(ctx, &RealTime_ServiceDesc.Streams[0], "/realtime.RealTime/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &realTimeSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type RealTime_SubscribeClient interface { + Recv() (*Message, error) + grpc.ClientStream +} + +type realTimeSubscribeClient struct { + grpc.ClientStream +} + +func (x *realTimeSubscribeClient) Recv() (*Message, error) { + m := new(Message) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// RealTimeServer is the server API for RealTime service. +// All implementations must embed UnimplementedRealTimeServer +// for forward compatibility +type RealTimeServer interface { + Publish(context.Context, *PublishMessage) (*empty.Empty, error) + Subscribe(*Channel, RealTime_SubscribeServer) error + mustEmbedUnimplementedRealTimeServer() +} + +// UnimplementedRealTimeServer must be embedded to have forward compatible implementations. +type UnimplementedRealTimeServer struct { +} + +func (UnimplementedRealTimeServer) Publish(context.Context, *PublishMessage) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Publish not implemented") +} +func (UnimplementedRealTimeServer) Subscribe(*Channel, RealTime_SubscribeServer) error { + return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") +} +func (UnimplementedRealTimeServer) mustEmbedUnimplementedRealTimeServer() {} + +// UnsafeRealTimeServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RealTimeServer will +// result in compilation errors. +type UnsafeRealTimeServer interface { + mustEmbedUnimplementedRealTimeServer() +} + +func RegisterRealTimeServer(s grpc.ServiceRegistrar, srv RealTimeServer) { + s.RegisterService(&RealTime_ServiceDesc, srv) +} + +func _RealTime_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PublishMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RealTimeServer).Publish(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/realtime.RealTime/Publish", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RealTimeServer).Publish(ctx, req.(*PublishMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _RealTime_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Channel) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RealTimeServer).Subscribe(m, &realTimeSubscribeServer{stream}) +} + +type RealTime_SubscribeServer interface { + Send(*Message) error + grpc.ServerStream +} + +type realTimeSubscribeServer struct { + grpc.ServerStream +} + +func (x *realTimeSubscribeServer) Send(m *Message) error { + return x.ServerStream.SendMsg(m) +} + +// RealTime_ServiceDesc is the grpc.ServiceDesc for RealTime service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RealTime_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "realtime.RealTime", + HandlerType: (*RealTimeServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Publish", + Handler: _RealTime_Publish_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Subscribe", + Handler: _RealTime_Subscribe_Handler, + ServerStreams: true, + }, + }, + Metadata: "api/proto/realtime.proto", +} diff --git a/cmd/realtime/main.go b/cmd/realtime/main.go new file mode 100644 index 0000000..8d8b3d8 --- /dev/null +++ b/cmd/realtime/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "net" + + "google.golang.org/grpc" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +const _address = "localhost:8090" + +func main() { + log, err := logger.New() + if err != nil { + fmt.Println(err) + return + } + if err := RealTimeRun(log, _address); err != nil { + log.Error(err.Error()) + } +} + +func RealTimeRun(log *logger.Logger, addr string) error { + l, err := net.Listen("tcp", addr) + if err != nil { + return fmt.Errorf("listen tcp %s: %w", addr, err) + } + + node, err := realtime.NewNode() + if err != nil { + return fmt.Errorf("new server node: %w", err) + } + + serv := grpc.NewServer() + rt.RegisterRealTimeServer(serv, realtime.NewServer(node)) + + log.Info("start realtime server", logger.F{"network", "tcp"}, logger.F{"addr", addr}) + return serv.Serve(l) +} diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index ba817e0..618cf6f 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -19,3 +19,34 @@ services: command: redis-server /usr/local/etc/redis/redis.conf ports: - 6379:6379 + + zookeeper: + image: bitnami/zookeeper:latest + container_name: pinspireZookeeper + ports: + - "2181:2181" + volumes: + - "zookeeper_data:/bitnami" + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + + kafka: + image: bitnami/kafka:latest + container_name: pinspireKafka + ports: + - "9092:9092" + volumes: + - "kafka_data:/bitnami" + environment: + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 + depends_on: + - zookeeper + +volumes: + zookeeper_data: + driver: local + kafka_data: + driver: local diff --git a/go.mod b/go.mod index db4e3ed..a7103f7 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,12 @@ module github.com/go-park-mail-ru/2023_2_OND_team go 1.19 require ( + github.com/IBM/sarama v1.42.1 github.com/Masterminds/squirrel v1.5.4 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 github.com/golang/mock v1.6.0 + github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/joho/godotenv v1.5.1 @@ -23,6 +25,8 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/image v0.13.0 + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.31.0 nhooyr.io/websocket v1.8.10 ) @@ -36,6 +40,9 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dsnet/compress v0.0.1 // indirect + github.com/eapache/go-resiliency v1.4.0 // indirect + github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect + github.com/eapache/queue v1.1.0 // indirect github.com/go-fonts/latin-modern v0.3.1 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -43,15 +50,27 @@ require ( github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tdewolff/minify/v2 v2.20.5 // indirect @@ -63,6 +82,7 @@ require ( golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect star-tex.org/x/tex v0.4.0 // indirect diff --git a/go.sum b/go.sum index 59d9fda..ee9e3fb 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= +github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= +github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= @@ -32,6 +34,13 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0= +github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= +github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM= @@ -63,11 +72,27 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -76,12 +101,26 @@ github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -105,11 +144,15 @@ github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02C github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -146,6 +189,7 @@ github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqY github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= @@ -159,6 +203,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -170,17 +216,24 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -189,14 +242,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -208,12 +268,22 @@ golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/microservices/realtime/kafka_broker.go b/internal/microservices/realtime/kafka_broker.go new file mode 100644 index 0000000..d9922b0 --- /dev/null +++ b/internal/microservices/realtime/kafka_broker.go @@ -0,0 +1,163 @@ +package realtime + +import ( + "errors" + "fmt" + "hash" + "hash/fnv" + "sync" + "time" + + "github.com/IBM/sarama" +) + +var ErrAlreadyMaxNumberTopic = errors.New("the maximum number has already been created topics") + +const _timeoutCreateTopic = time.Second + +type KafkaConfig struct { + Addres []string + PartitionsOnTopic int + MaxNumTopic int +} + +type kafkaBroker struct { + node *Node + mainClient sarama.Client + existingTopics map[string]struct{} + cfg KafkaConfig + producer sarama.SyncProducer + + m sync.RWMutex +} + +var _ Broker = (*kafkaBroker)(nil) + +func NewKafkaBroker(node *Node, cfg KafkaConfig) (*kafkaBroker, error) { + clientCfg := sarama.NewConfig() + clientCfg.Producer.Return.Successes = true + clientCfg.Producer.Partitioner = sarama.NewCustomHashPartitioner(func() hash.Hash32 { return fnv.New32() }) + + client, err := sarama.NewClient(cfg.Addres, clientCfg) + if err != nil { + return nil, fmt.Errorf("new client for kafka broker: %w", err) + } + producer, err := sarama.NewSyncProducerFromClient(client) + if err != nil { + return nil, fmt.Errorf("new sync producer for kafka broker: %w", err) + } + + k := &kafkaBroker{ + node: node, + cfg: cfg, + mainClient: client, + producer: producer, + m: sync.RWMutex{}, + existingTopics: make(map[string]struct{}), + } + return k, nil +} + +func (k *kafkaBroker) Publish(topic string, channel string, message []byte) error { + created, err := k.checkOrCreateTopic(topic) + if err != nil { + return fmt.Errorf("publish to topic %s: %w", topic, err) + } + + if created { + err = k.serveTopic(topic) + if err != nil { + return fmt.Errorf("serve new topic %s: %w", topic, err) + } + } + + _, _, err = k.producer.SendMessage(&sarama.ProducerMessage{ + Topic: topic, + Key: sarama.ByteEncoder(channel), + Value: sarama.ByteEncoder(message), + }) + if err != nil { + return fmt.Errorf("send message with topic %s and channel %s to kafka server: %w", topic, channel, err) + } + return nil +} + +func (k *kafkaBroker) Close() { + k.producer.Close() + k.mainClient.Close() +} + +func (k *kafkaBroker) checkOrCreateTopic(topic string) (bool, error) { + isExists := false + var countTopics int + k.m.RLock() + if _, ok := k.existingTopics[topic]; ok { + isExists = true + } else { + countTopics = len(k.existingTopics) + } + k.m.RUnlock() + + if isExists { + return false, nil + } + if countTopics == k.cfg.MaxNumTopic { + return false, ErrAlreadyMaxNumberTopic + } + + detail := &sarama.TopicDetail{ + NumPartitions: int32(k.cfg.PartitionsOnTopic), + ReplicationFactor: -1, + } + _, err := k.mainClient.LeastLoadedBroker().CreateTopics(&sarama.CreateTopicsRequest{ + ValidateOnly: true, + Timeout: _timeoutCreateTopic, + TopicDetails: map[string]*sarama.TopicDetail{topic: detail}, + }) + if err != nil { + return false, fmt.Errorf("create topic: %w", err) + } + + k.m.Lock() + if _, ok := k.existingTopics[topic]; !ok && len(k.existingTopics) == k.cfg.MaxNumTopic { + k.m.Unlock() + go delTopic(topic, k.mainClient) + return false, ErrAlreadyMaxNumberTopic + } + k.existingTopics[topic] = struct{}{} + k.m.Unlock() + + k.mainClient.RefreshController() + return true, nil +} + +func delTopic(topic string, client sarama.Client) { + client.LeastLoadedBroker().DeleteTopics(&sarama.DeleteTopicsRequest{ + Topics: []string{topic}, + Timeout: _timeoutCreateTopic, + }) +} + +func (k *kafkaBroker) serveTopic(topic string) error { + cons, err := sarama.NewConsumer(k.cfg.Addres, sarama.NewConfig()) + if err != nil { + return fmt.Errorf("serve topic %s: %w", topic, err) + } + + for i := int32(0); int(i) < k.node.numWorkers; i++ { + go func(partition int32) { + offset, err := k.mainClient.GetOffset(topic, partition, -1) + if err != nil { + return + } + partConsumer, err := cons.ConsumePartition(topic, int32(partition), offset) + if err != nil { + return + } + for message := range partConsumer.Messages() { + k.node.SendOut(Channel{Name: string(message.Key), Topic: topic}, message.Value) + } + }(i) + } + return nil +} diff --git a/internal/microservices/realtime/server.go b/internal/microservices/realtime/server.go new file mode 100644 index 0000000..87b2c6c --- /dev/null +++ b/internal/microservices/realtime/server.go @@ -0,0 +1,142 @@ +package realtime + +import ( + "context" + "fmt" + "hash/fnv" + "sync" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + "github.com/golang/protobuf/ptypes/empty" + "github.com/google/uuid" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +const _numWorkers = 64 + +type Channel struct { + Name string + Topic string +} + +type SubscriberHub map[Channel]map[string]*Client + +type Node struct { + subscriptions [_numWorkers]SubscriberHub + broker Broker + numWorkers int + mu sync.RWMutex +} + +func NewNode() (*Node, error) { + node := &Node{} + broker, err := NewKafkaBroker(node, KafkaConfig{ + Addres: []string{"localhost:9092"}, + PartitionsOnTopic: _numWorkers, + MaxNumTopic: 10, + }) + if err != nil { + return nil, fmt.Errorf("new kafka broker: %w", err) + } + node.broker = broker + for ind := range node.subscriptions { + node.subscriptions[ind] = make(SubscriberHub) + } + node.numWorkers = _numWorkers + node.mu = sync.RWMutex{} + return node, nil +} + +func (n *Node) SendOut(channel Channel, message []byte) { + clients := []*Client{} + ind := index(channel.Name) + n.mu.RLock() + + if m, ok := n.subscriptions[ind][channel]; ok { + for _, client := range m { + clients = append(clients, client) + } + } + n.mu.RUnlock() + for _, client := range clients { + mes := &rt.Message{} + err := proto.Unmarshal(message, mes) + if err != nil { + fmt.Println(err) + } + client.Transport.Send(mes) + } + mes := &rt.Message{} + err := proto.Unmarshal(message, mes) + if err != nil { + fmt.Println(err) + } +} + +type Client struct { + ID uuid.UUID + Transport rt.RealTime_SubscribeServer +} + +type Server struct { + rt.UnimplementedRealTimeServer + + node *Node +} + +func NewServer(node *Node) Server { + return Server{ + UnimplementedRealTimeServer: rt.UnimplementedRealTimeServer{}, + node: node, + } +} + +type Broker interface { + Publish(topic string, channel string, message []byte) error + Close() +} + +func (s Server) Publish(ctx context.Context, pm *rt.PublishMessage) (*empty.Empty, error) { + message, err := proto.Marshal(pm.Message) + if err != nil { + return nil, status.Error(codes.Internal, "marshaling message") + } + if err := s.node.broker.Publish(pm.Channel.Topic, pm.Channel.Name, message); err != nil { + return nil, status.Error(codes.Internal, "publish message") + } + return &empty.Empty{}, nil +} + +func (s Server) Subscribe(c *rt.Channel, ss rt.RealTime_SubscribeServer) error { + id, err := uuid.NewRandom() + if err != nil { + return status.Error(codes.Internal, "generate uuid v4") + } + client := &Client{ + ID: id, + Transport: ss, + } + channel := Channel{ + Name: c.GetName(), + Topic: c.GetTopic(), + } + ind := index(channel.Name) + + s.node.mu.Lock() + subscribeChannel, ok := s.node.subscriptions[ind][channel] + if !ok { + subscribeChannel = make(map[string]*Client) + s.node.subscriptions[ind][channel] = subscribeChannel + } + subscribeChannel[id.String()] = client + s.node.mu.Unlock() + select {} +} + +func index(nameChannel string) uint32 { + h := fnv.New32() + h.Write([]byte(nameChannel)) + return h.Sum32() % _numWorkers +} From 7149f24f6353ec065b4e48ae20780e6d73c4cf6f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 23 Nov 2023 00:06:07 +0300 Subject: [PATCH 198/266] TP-409 add: partitions package internal/microservices/realtime --- internal/microservices/realtime/node.go | 80 +++++++++++++++++++ internal/microservices/realtime/server.go | 95 ++--------------------- internal/microservices/realtime/types.go | 27 +++++++ 3 files changed, 113 insertions(+), 89 deletions(-) create mode 100644 internal/microservices/realtime/node.go create mode 100644 internal/microservices/realtime/types.go diff --git a/internal/microservices/realtime/node.go b/internal/microservices/realtime/node.go new file mode 100644 index 0000000..4e476a5 --- /dev/null +++ b/internal/microservices/realtime/node.go @@ -0,0 +1,80 @@ +package realtime + +import ( + "fmt" + "sync" + + "google.golang.org/protobuf/proto" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" +) + +type Node struct { + subscriptions [_numWorkers]SubscriberHub + broker Broker + numWorkers int + mu sync.RWMutex +} + +func NewNode() (*Node, error) { + node := &Node{} + + broker, err := NewKafkaBroker(node, KafkaConfig{ + Addres: []string{"localhost:9092"}, + PartitionsOnTopic: _numWorkers, + MaxNumTopic: 10, + }) + if err != nil { + return nil, fmt.Errorf("new kafka broker: %w", err) + } + + node.broker = broker + + for ind := range node.subscriptions { + node.subscriptions[ind] = make(SubscriberHub) + } + + node.numWorkers = _numWorkers + node.mu = sync.RWMutex{} + return node, nil +} + +func (n *Node) SendOut(channel Channel, message []byte) { + clients := []*Client{} + ind := index(channel.Name) + + n.mu.RLock() + if m, ok := n.subscriptions[ind][channel]; ok { + for _, client := range m { + clients = append(clients, client) + } + } + n.mu.RUnlock() + + for _, client := range clients { + mes := &rt.Message{} + err := proto.Unmarshal(message, mes) + if err != nil { + fmt.Println(err) + } + client.transport.Send(mes) + } +} + +func (n *Node) AddSubscriber(c *rt.Channel, client *Client) { + channel := Channel{ + Name: c.GetName(), + Topic: c.GetTopic(), + } + + ind := index(channel.Name) + + n.mu.Lock() + defer n.mu.Unlock() + subscribeChannel, ok := n.subscriptions[ind][channel] + if !ok { + subscribeChannel = make(map[string]*Client) + n.subscriptions[ind][channel] = subscribeChannel + } + subscribeChannel[client.id.String()] = client +} diff --git a/internal/microservices/realtime/server.go b/internal/microservices/realtime/server.go index 87b2c6c..8feabfe 100644 --- a/internal/microservices/realtime/server.go +++ b/internal/microservices/realtime/server.go @@ -2,9 +2,6 @@ package realtime import ( "context" - "fmt" - "hash/fnv" - "sync" rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" "github.com/golang/protobuf/ptypes/empty" @@ -16,70 +13,6 @@ import ( const _numWorkers = 64 -type Channel struct { - Name string - Topic string -} - -type SubscriberHub map[Channel]map[string]*Client - -type Node struct { - subscriptions [_numWorkers]SubscriberHub - broker Broker - numWorkers int - mu sync.RWMutex -} - -func NewNode() (*Node, error) { - node := &Node{} - broker, err := NewKafkaBroker(node, KafkaConfig{ - Addres: []string{"localhost:9092"}, - PartitionsOnTopic: _numWorkers, - MaxNumTopic: 10, - }) - if err != nil { - return nil, fmt.Errorf("new kafka broker: %w", err) - } - node.broker = broker - for ind := range node.subscriptions { - node.subscriptions[ind] = make(SubscriberHub) - } - node.numWorkers = _numWorkers - node.mu = sync.RWMutex{} - return node, nil -} - -func (n *Node) SendOut(channel Channel, message []byte) { - clients := []*Client{} - ind := index(channel.Name) - n.mu.RLock() - - if m, ok := n.subscriptions[ind][channel]; ok { - for _, client := range m { - clients = append(clients, client) - } - } - n.mu.RUnlock() - for _, client := range clients { - mes := &rt.Message{} - err := proto.Unmarshal(message, mes) - if err != nil { - fmt.Println(err) - } - client.Transport.Send(mes) - } - mes := &rt.Message{} - err := proto.Unmarshal(message, mes) - if err != nil { - fmt.Println(err) - } -} - -type Client struct { - ID uuid.UUID - Transport rt.RealTime_SubscribeServer -} - type Server struct { rt.UnimplementedRealTimeServer @@ -103,7 +36,7 @@ func (s Server) Publish(ctx context.Context, pm *rt.PublishMessage) (*empty.Empt if err != nil { return nil, status.Error(codes.Internal, "marshaling message") } - if err := s.node.broker.Publish(pm.Channel.Topic, pm.Channel.Name, message); err != nil { + if err := s.node.broker.Publish(pm.Channel.GetTopic(), pm.Channel.GetName(), message); err != nil { return nil, status.Error(codes.Internal, "publish message") } return &empty.Empty{}, nil @@ -115,28 +48,12 @@ func (s Server) Subscribe(c *rt.Channel, ss rt.RealTime_SubscribeServer) error { return status.Error(codes.Internal, "generate uuid v4") } client := &Client{ - ID: id, - Transport: ss, - } - channel := Channel{ - Name: c.GetName(), - Topic: c.GetTopic(), + id: id, + transport: ss, } - ind := index(channel.Name) - s.node.mu.Lock() - subscribeChannel, ok := s.node.subscriptions[ind][channel] - if !ok { - subscribeChannel = make(map[string]*Client) - s.node.subscriptions[ind][channel] = subscribeChannel - } - subscribeChannel[id.String()] = client - s.node.mu.Unlock() - select {} -} + s.node.AddSubscriber(c, client) -func index(nameChannel string) uint32 { - h := fnv.New32() - h.Write([]byte(nameChannel)) - return h.Sum32() % _numWorkers + <-ss.Context().Done() + return nil } diff --git a/internal/microservices/realtime/types.go b/internal/microservices/realtime/types.go new file mode 100644 index 0000000..483c261 --- /dev/null +++ b/internal/microservices/realtime/types.go @@ -0,0 +1,27 @@ +package realtime + +import ( + "hash/fnv" + + "github.com/google/uuid" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" +) + +type Channel struct { + Name string + Topic string +} + +type SubscriberHub map[Channel]map[string]*Client + +type Client struct { + id uuid.UUID + transport rt.RealTime_SubscribeServer +} + +func index(nameChannel string) uint32 { + h := fnv.New32() + h.Write([]byte(nameChannel)) + return h.Sum32() % _numWorkers +} From 423ec07ca037f8186c3611b76633502c4db9d302 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 23 Nov 2023 13:36:29 +0300 Subject: [PATCH 199/266] TP-409 update: websocket handler --- internal/api/server/router/router.go | 2 +- internal/pkg/delivery/websocket/types.go | 72 +++++++ internal/pkg/delivery/websocket/websocket.go | 186 ++++++++++++++++--- 3 files changed, 234 insertions(+), 26 deletions(-) create mode 100644 internal/pkg/delivery/websocket/types.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 3b7377f..1f39e3c 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -103,6 +103,6 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli }) r.Mux.With(auth.RequireAuth).Route("/websocket/connect", func(r chi.Router) { - r.Get("/chat/{userID:\\d+}", wsHandler.WebSocketConnect) + r.Get("/chat", wsHandler.WebSocketConnect) }) } diff --git a/internal/pkg/delivery/websocket/types.go b/internal/pkg/delivery/websocket/types.go new file mode 100644 index 0000000..b0ff2c9 --- /dev/null +++ b/internal/pkg/delivery/websocket/types.go @@ -0,0 +1,72 @@ +package websocket + +import "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + +type Channel struct { + Name string `json:"name"` + Topic string `json:"topic"` +} + +type Object struct { + Type string `json:"eventType,omitempty"` + Message message.Message `json:"message"` +} + +type Request struct { + ID int `json:"requestID"` + Action string + Channel Channel + Message Object +} + +type MessageFromChannel struct { + Type string `json:"type"` + Channel Channel `json:"channel"` + Message ResponseMessage `json:"message"` +} + +type ResponseMessage struct { + Object + Status string `json:"status"` + Code string `json:"code,omitempty"` + MessageText string `json:"messageText,omitempty"` +} + +type ResponseOnRequest struct { + ID int `json:"requestID"` + Type string `json:"type"` + Status string `json:"status"` + Code string `json:"code,omitempty"` + Message string `json:"message"` + Body any `json:"body,omitempty"` +} + +func newResponseOnRequest(id int, status, code, message string, body any) *ResponseOnRequest { + return &ResponseOnRequest{ + ID: id, + Type: "response", + Status: status, + Code: code, + Message: message, + Body: body, + } +} + +func newMessageFromChannel(channel Channel, status, code string, v any) *MessageFromChannel { + mes := &MessageFromChannel{ + Type: "event", + Channel: channel, + Message: ResponseMessage{ + Status: status, + Code: code, + }, + } + if v, ok := v.(Object); ok { + mes.Message.Object = v + return mes + } + if v, ok := v.(string); ok { + mes.Message.MessageText = v + } + return mes +} diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index e164a15..0231451 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -1,20 +1,26 @@ package websocket import ( + "context" "fmt" - "math/rand" "net/http" - "time" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ws "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" + rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) type HandlerWebSocket struct { originPatterns []string log *log.Logger + messageCase usecase.Usecase } type Option func(h *HandlerWebSocket) @@ -34,21 +40,7 @@ func New(log *log.Logger, opts ...Option) *HandlerWebSocket { return handlerWS } -type Event struct { - Event string `json:"event"` - ObjectID int `json:"objID"` - Content string `json:"content"` - PublisherID int `json:"publisherID"` -} - -var mes [3]Event = [3]Event{ - {Event: "del", ObjectID: 12, PublisherID: 2332}, - {Event: "new", ObjectID: 12, Content: "some text", PublisherID: 2332}, - {Event: "edit", ObjectID: 12, Content: "new some text", PublisherID: 2332}, -} - func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() conn, err := ws.Accept(w, r, &ws.AcceptOptions{OriginPatterns: h.originPatterns}) if err != nil { h.log.Error(err.Error()) @@ -58,19 +50,163 @@ func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Reque } defer conn.CloseNow() + err = h.serveWebSocketConn(r.Context(), conn) + if err != nil { + h.log.Error(err.Error()) + } +} + +func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn) error { + userID, ok := ctx.Value(auth.KeyCurrentUserID).(int) + if !ok { + userID = 0 + } + gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return fmt.Errorf("grpc dial: %w", err) + } + defer gRPCConn.Close() + + client := rt.NewRealTimeClient(gRPCConn) + request := &Request{} + for { - time.Sleep(time.Second * time.Duration(rand.Intn(2))) - err = wsjson.Write(ctx, conn, mes[rand.Int31n(3)]) + err = wsjson.Read(ctx, conn, request) if err != nil { - closeStatus := ws.CloseStatus(err) - if closeStatus != ws.StatusNormalClosure { - fmt.Println(closeStatus) - h.log.Warn(err.Error()) + h.log.Error(err.Error()) + return fmt.Errorf("read message: %w", err) + } + switch request.Action { + case "Publish": + switch request.Message.Type { + case "create": + mesCopy := &message.Message{} + *mesCopy = request.Message.Message + id, err := h.messageCase.SendMessage(ctx, mesCopy) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]int{"id": id})) + _, err = client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: request.Channel.Topic, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_CREATE, + Id: int64(id), + }, + }, + }, + }) + h.log.Error(err.Error()) + case "update": + mesCopy := &message.Message{} + *mesCopy = request.Message.Message + err = h.messageCase.UpdateContentMessage(ctx, userID, mesCopy) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", nil)) + _, err = client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: request.Channel.Topic, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_UPDATE, + Id: int64(request.Message.Message.ID), + }, + }, + }, + }) + h.log.Error(err.Error()) + + case "delete": + err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", nil)) + _, err = client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: request.Channel.Topic, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_DELETE, + Id: int64(request.Message.Message.ID), + }, + }, + }, + }) + h.log.Error(err.Error()) + default: + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) } - if closeStatus == -1 { - conn.Close(ws.StatusAbnormalClosure, "error write") + case "Subscribe": + err = h.subscribe(ctx, client, request, conn) + if err != nil { + h.log.Warn(err.Error()) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "subscribe_fail", "failed to subscribe to the channel", nil)) + continue } - return + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "you have successfully subscribed to the channel", nil)) + default: + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported action", nil)) } } } + +func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClient, req *Request, conn *ws.Conn) error { + sc, err := client.Subscribe(ctx, &rt.Channel{ + Name: req.Channel.Name, + Topic: req.Channel.Topic, + }) + if err != nil { + return fmt.Errorf("subscribe: %w", err) + } + go func() { + for { + obj, err := sc.Recv() + if err != nil { + return + } + mes, ok := obj.Body.(*rt.Message_Object) + if ok { + message, err := h.messageCase.GetMessage(ctx, int(mes.Object.Id)) + if err != nil { + h.log.Error(err.Error()) + return + } + objType := "" + switch mes.Object.Type { + case rt.EventType_EV_CREATE: + objType = "create" + case rt.EventType_EV_UPDATE: + objType = "update" + case rt.EventType_EV_DELETE: + objType = "delete" + } + err = wsjson.Write(ctx, conn, newMessageFromChannel(req.Channel, "ok", "", Object{ + Type: objType, + Message: *message, + })) + if err != nil { + h.log.Error(err.Error()) + return + } + } + } + }() + return nil +} From 831b6fdd720cc576b3951a208d178e3521f8e40a Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 23 Nov 2023 22:24:58 +0300 Subject: [PATCH 200/266] TP-409 update func new websocket --- configs/config.yml | 2 +- internal/app/app.go | 7 ++++--- internal/pkg/delivery/websocket/websocket.go | 4 ++-- internal/pkg/usecase/message/usecase.go | 5 +++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/configs/config.yml b/configs/config.yml index 4d1fda6..88ea0c1 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -1,6 +1,6 @@ app: server: - host: 0.0.0.0 + host: 127.0.0.1 port: 8080 https: false certFile: /home/ond_team/cert/fullchain.pem diff --git a/internal/app/app.go b/internal/app/app.go index 3934fc0..7dadce6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -13,7 +13,7 @@ import ( deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" - mesCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" + mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" @@ -64,16 +64,17 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) + messageCase := message.New(mesRepo.NewMessageRepo(pool)) handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), PinCase: pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)), BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), - MessageCase: message.New(mesCase.NewMessageRepo(pool)), + MessageCase: messageCase, SM: sm, }) - wsHandler := deliveryWS.New(log, + wsHandler := deliveryWS.New(log, messageCase, deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 0231451..922ec25 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -31,8 +31,8 @@ func SetOriginPatterns(patterns []string) Option { } } -func New(log *log.Logger, opts ...Option) *HandlerWebSocket { - handlerWS := &HandlerWebSocket{log: log} +func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { + handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase} for _, opt := range opts { opt(handlerWS) } diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 357544e..c457be1 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -17,6 +17,7 @@ type Usecase interface { GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error DeleteMessage(ctx context.Context, userID, mesID int) error + GetMessage(ctx context.Context, messageID int) (*entity.Message, error) } type messageCase struct { @@ -59,3 +60,7 @@ func (m *messageCase) DeleteMessage(ctx context.Context, userID, mesID int) erro } return m.repo.DelMessage(ctx, mesID) } + +func (m *messageCase) GetMessage(ctx context.Context, messageID int) (*entity.Message, error) { + return m.repo.GetMessageByID(ctx, messageID) +} From 685015d8df7fd4dd3fc77780ffd4ef9ea7442b74 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 24 Nov 2023 00:11:49 +0300 Subject: [PATCH 201/266] TP-409 update: entity message json tag --- internal/pkg/delivery/websocket/websocket.go | 28 ++++++++++++++------ internal/pkg/entity/message/message.go | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 922ec25..d995663 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -82,6 +82,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn case "create": mesCopy := &message.Message{} *mesCopy = request.Message.Message + mesCopy.From = userID id, err := h.messageCase.SendMessage(ctx, mesCopy) if err != nil { h.log.Warn(err.Error()) @@ -102,7 +103,9 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn }, }, }) - h.log.Error(err.Error()) + if err != nil { + h.log.Error(err.Error()) + } case "update": mesCopy := &message.Message{} *mesCopy = request.Message.Message @@ -126,7 +129,9 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn }, }, }) - h.log.Error(err.Error()) + if err != nil { + h.log.Error(err.Error()) + } case "delete": err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) @@ -149,7 +154,9 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn }, }, }) - h.log.Error(err.Error()) + if err != nil { + h.log.Error(err.Error()) + } default: wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) } @@ -183,10 +190,15 @@ func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClie } mes, ok := obj.Body.(*rt.Message_Object) if ok { - message, err := h.messageCase.GetMessage(ctx, int(mes.Object.Id)) - if err != nil { - h.log.Error(err.Error()) - return + var msg *message.Message + if mes.Object.Type == rt.EventType_EV_DELETE { + msg = &message.Message{ID: int(mes.Object.Id)} + } else { + msg, err = h.messageCase.GetMessage(ctx, int(mes.Object.Id)) + if err != nil { + h.log.Error(err.Error()) + return + } } objType := "" switch mes.Object.Type { @@ -199,7 +211,7 @@ func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClie } err = wsjson.Write(ctx, conn, newMessageFromChannel(req.Channel, "ok", "", Object{ Type: objType, - Message: *message, + Message: *msg, })) if err != nil { h.log.Error(err.Error()) diff --git a/internal/pkg/entity/message/message.go b/internal/pkg/entity/message/message.go index f80d1f6..a3cd65d 100644 --- a/internal/pkg/entity/message/message.go +++ b/internal/pkg/entity/message/message.go @@ -8,7 +8,7 @@ type Message struct { ID int From int `json:"from"` To int `json:"to"` - Content pgtype.Text `json:"context"` + Content pgtype.Text `json:"content"` } func (m Message) WhatChat() Chat { From 7299621086a92baa6a0cf1426b3fc98e4e2d7ed1 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 24 Nov 2023 16:17:32 +0300 Subject: [PATCH 202/266] dev3: changed pagination for subscriptions, minor errors update --- internal/pkg/delivery/http/v1/board.go | 1 - internal/pkg/delivery/http/v1/board_errors.go | 1 + internal/pkg/delivery/http/v1/response.go | 11 ++--- internal/pkg/delivery/http/v1/subscription.go | 6 +-- internal/pkg/errors/types.go | 14 +++++- .../subscription/postgres/queries.go | 12 ++--- .../repository/subscription/postgres/repo.go | 45 +++++++++---------- internal/pkg/repository/subscription/repo.go | 4 +- internal/pkg/repository/user/repo.go | 22 ++++----- 9 files changed, 58 insertions(+), 58 deletions(-) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index b114f3a..f492ac4 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -66,7 +66,6 @@ func (data *BoardData) Validate() error { return nil } - func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { diff --git a/internal/pkg/delivery/http/v1/board_errors.go b/internal/pkg/delivery/http/v1/board_errors.go index 8993775..ef0eb23 100644 --- a/internal/pkg/delivery/http/v1/board_errors.go +++ b/internal/pkg/delivery/http/v1/board_errors.go @@ -6,6 +6,7 @@ import ( bCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" ) +// for backward compatibility var ( ErrEmptyTitle = errors.New("empty or null board title has been provided") ErrEmptyPubOpt = errors.New("null public option has been provided") diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index 6687d70..98ab544 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -53,10 +53,12 @@ func (e *ErrInvalidQueryParam) Type() errPkg.Type { return errPkg.ErrInvalidInput } -type ErrInvalidContentType struct{} +type ErrInvalidContentType struct { + preferredType string +} func (e *ErrInvalidContentType) Error() string { - return "invalid content type" + return fmt.Sprintf("invalid content type, should be %s", e.preferredType) } func (e *ErrInvalidContentType) Type() errPkg.Type { @@ -163,9 +165,8 @@ func (h *HandlerHTTP) responseErr(w http.ResponseWriter, r *http.Request, err er code, status := getCodeStatusHttp(err) var msg string if status == http.StatusInternalServerError { - log.Warnf("unexpected error on the delivery http: %s\n", err.Error()) - err := &errPkg.InternalError{} - msg = err.Error() + log.Warnf("unexpected application error: %s", err.Error()) + msg = "internal error occured" } else { msg = err.Error() } diff --git a/internal/pkg/delivery/http/v1/subscription.go b/internal/pkg/delivery/http/v1/subscription.go index c3e9a23..4614476 100644 --- a/internal/pkg/delivery/http/v1/subscription.go +++ b/internal/pkg/delivery/http/v1/subscription.go @@ -11,7 +11,7 @@ import ( var ( defaultSubCount = 20 - defaultSubLastID = 0 + defaultSubLastID = 1 << 30 subscriptionsView = "subscriptions" subscribersView = "subscribers" maxCount = 50 @@ -30,7 +30,7 @@ func (s *SubscriptionAction) Validate() error { func (h *HandlerHTTP) Subscribe(w http.ResponseWriter, r *http.Request) { if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - h.responseErr(w, r, &ErrInvalidContentType{}) + h.responseErr(w, r, &ErrInvalidContentType{preferredType: ApplicationJson}) return } @@ -56,7 +56,7 @@ func (h *HandlerHTTP) Subscribe(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) Unsubscribe(w http.ResponseWriter, r *http.Request) { if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - h.responseErr(w, r, &ErrInvalidContentType{}) + h.responseErr(w, r, &ErrInvalidContentType{preferredType: ApplicationJson}) return } diff --git a/internal/pkg/errors/types.go b/internal/pkg/errors/types.go index 9205f6d..815bd5b 100644 --- a/internal/pkg/errors/types.go +++ b/internal/pkg/errors/types.go @@ -1,7 +1,17 @@ package errors +import "fmt" + type Type uint8 +type Layer string + +const ( + Repo Layer = "Repository" + Usecase Layer = "Usecase" + Delivery Layer = "Delivery" +) + const ( _ Type = iota ErrNotFound @@ -29,10 +39,12 @@ func (e *ErrNotAuthenticated) Type() Type { } type InternalError struct { + Message string + Layer string } func (e *InternalError) Error() string { - return "Internal error occured" + return fmt.Sprintf("Internal error occured. Message: '%s'. Layer: %s", e.Message, e.Layer) } type ErrorNotImplemented struct { diff --git a/internal/pkg/repository/subscription/postgres/queries.go b/internal/pkg/repository/subscription/postgres/queries.go index 4aa1d4f..4a90c47 100644 --- a/internal/pkg/repository/subscription/postgres/queries.go +++ b/internal/pkg/repository/subscription/postgres/queries.go @@ -13,12 +13,10 @@ var ( LEFT JOIN subscription_user s ON f.whom = s.whom AND s.who = $1 WHERE - f.who = $2 AND p.deleted_at IS NULL + f.who = $2 AND p.deleted_at IS NULL AND f.whom < $3 ORDER BY - f.whom ASC + f.whom DESC LIMIT - $3 - OFFSET $4;` GetUserSubscribers = ` SELECT @@ -30,11 +28,9 @@ var ( LEFT JOIN subscription_user s ON f.who = s.whom AND s.who = $1 WHERE - f.whom = $2 AND p.deleted_at IS NULL + f.whom = $2 AND p.deleted_at IS NULL AND f.who < $3 ORDER BY - f.who ASC + f.who DESC LIMIT - $3 - OFFSET $4;` ) diff --git a/internal/pkg/repository/subscription/postgres/repo.go b/internal/pkg/repository/subscription/postgres/repo.go index 7f6aed4..8966d1d 100644 --- a/internal/pkg/repository/subscription/postgres/repo.go +++ b/internal/pkg/repository/subscription/postgres/repo.go @@ -10,7 +10,6 @@ import ( errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/jackc/pgx/v5/pgconn" ) @@ -23,10 +22,10 @@ func NewSubscriptionRepoPG(db pgtype.PgxPoolIface) subRepo.Repository { return &subscriptionRepoPG{db: db, sqlBuilder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} } -func convertErrorPostgres(ctx context.Context, err error) error { - logger := logger.GetLoggerFromCtx(ctx) +func convertErrorPostgres(err error) error { - if errors.Is(err, context.DeadlineExceeded) { + switch err { + case context.DeadlineExceeded: return &errPkg.ErrTimeoutExceeded{} } @@ -36,29 +35,27 @@ func convertErrorPostgres(ctx context.Context, err error) error { case strconv.Itoa(23505): return &subRepo.ErrSubscriptionAlreadyExist{} default: - logger.Warnf("Unexpected error from subscription repo - postgres: %s\n", err.Error()) - return &errPkg.InternalError{} + return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} } } - logger.Warnf("Unexpected error from subscription repo: %s\n", err.Error()) - return &errPkg.InternalError{} + return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} } func (r *subscriptionRepoPG) CreateSubscriptionUser(ctx context.Context, from, to int) error { tx, err := r.db.Begin(ctx) if err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } if _, err = tx.Exec(ctx, CreateSubscriptionUser, from, to); err != nil { if err := tx.Rollback(ctx); err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } if err = tx.Commit(ctx); err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } return nil } @@ -66,19 +63,19 @@ func (r *subscriptionRepoPG) CreateSubscriptionUser(ctx context.Context, from, t func (r *subscriptionRepoPG) DeleteSubscriptionUser(ctx context.Context, from, to int) error { tx, err := r.db.Begin(ctx) if err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } status, err := tx.Exec(ctx, DeleteSubscriptionUser, from, to) if err != nil { if err := tx.Rollback(ctx); err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } if err = tx.Commit(ctx); err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } if status.RowsAffected() == 0 { return &subRepo.ErrNonExistingSubscription{} @@ -86,11 +83,11 @@ func (r *subscriptionRepoPG) DeleteSubscriptionUser(ctx context.Context, from, t return nil } -func (r *subscriptionRepoPG) GetUserSubscriptions(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) { +func (r *subscriptionRepoPG) GetUserSubscriptions(ctx context.Context, userID, count, lastID int, currUserID int) ([]userEntity.SubscriptionUser, error) { - rows, err := r.db.Query(ctx, GetUserSubscriptions, currUserID, userID, count, offset) + rows, err := r.db.Query(ctx, GetUserSubscriptions, currUserID, userID, lastID, count) if err != nil { - return nil, convertErrorPostgres(ctx, err) + return nil, convertErrorPostgres(err) } defer rows.Close() @@ -98,18 +95,18 @@ func (r *subscriptionRepoPG) GetUserSubscriptions(ctx context.Context, userID, c for rows.Next() { var subscription userEntity.SubscriptionUser if err = rows.Scan(&subscription.ID, &subscription.Username, &subscription.Avatar, &subscription.HasSubscribeFromCurUser); err != nil { - return nil, convertErrorPostgres(ctx, err) + return nil, convertErrorPostgres(err) } subscriptions = append(subscriptions, subscription) } return subscriptions, nil } -func (r *subscriptionRepoPG) GetUserSubscribers(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) { +func (r *subscriptionRepoPG) GetUserSubscribers(ctx context.Context, userID, count, lastID int, currUserID int) ([]userEntity.SubscriptionUser, error) { - rows, err := r.db.Query(ctx, GetUserSubscribers, currUserID, userID, count, offset) + rows, err := r.db.Query(ctx, GetUserSubscribers, currUserID, userID, lastID, count) if err != nil { - return nil, convertErrorPostgres(ctx, err) + return nil, convertErrorPostgres(err) } defer rows.Close() @@ -117,7 +114,7 @@ func (r *subscriptionRepoPG) GetUserSubscribers(ctx context.Context, userID, cou for rows.Next() { var subscriber userEntity.SubscriptionUser if err = rows.Scan(&subscriber.ID, &subscriber.Username, &subscriber.Avatar, &subscriber.HasSubscribeFromCurUser); err != nil { - return nil, convertErrorPostgres(ctx, err) + return nil, convertErrorPostgres(err) } subscribers = append(subscribers, subscriber) } diff --git a/internal/pkg/repository/subscription/repo.go b/internal/pkg/repository/subscription/repo.go index c39cf32..7d23950 100644 --- a/internal/pkg/repository/subscription/repo.go +++ b/internal/pkg/repository/subscription/repo.go @@ -9,6 +9,6 @@ import ( type Repository interface { CreateSubscriptionUser(ctx context.Context, from, to int) error DeleteSubscriptionUser(ctx context.Context, from, to int) error - GetUserSubscriptions(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) - GetUserSubscribers(ctx context.Context, userID, count, offset int, currUserID int) ([]userEntity.SubscriptionUser, error) + GetUserSubscriptions(ctx context.Context, userID, count, lastID int, currUserID int) ([]userEntity.SubscriptionUser, error) + GetUserSubscribers(ctx context.Context, userID, count, lastID int, currUserID int) ([]userEntity.SubscriptionUser, error) } diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index 985aced..fca5e22 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -13,7 +13,6 @@ import ( errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) //go:generate mockgen -destination=./mock/user_mock.go -package=mock -source=repo.go Repository @@ -40,16 +39,13 @@ func NewUserRepoPG(db pgtype.PgxPoolIface) *userRepoPG { return &userRepoPG{db} } -func convertErrorPostgres(ctx context.Context, err error) error { - logger := logger.GetLoggerFromCtx(ctx) - - if errors.Is(err, context.DeadlineExceeded) { - return &errPkg.ErrTimeoutExceeded{} - } +func convertErrorPostgres(err error) error { switch err { case pgx.ErrNoRows: return &ErrNonExistingUser{} + case context.DeadlineExceeded: + return &errPkg.ErrTimeoutExceeded{} } var pgErr *pgconn.PgError @@ -57,19 +53,17 @@ func convertErrorPostgres(ctx context.Context, err error) error { switch pgErr.Code { // add SQL states if necessary default: - logger.Warnf("Unexpected error from user repo - postgres: %s\n", err.Error()) - return &errPkg.InternalError{} + return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} } } - logger.Warnf("Unexpected error from user repo: %s\n", err.Error()) - return &errPkg.InternalError{} + return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} } func (u *userRepoPG) CheckUserExistence(ctx context.Context, userID int) error { row := u.db.QueryRow(ctx, CheckUserExistence, userID) var dummy string if err := row.Scan(&dummy); err != nil { - return convertErrorPostgres(ctx, err) + return convertErrorPostgres(err) } return nil @@ -108,7 +102,7 @@ func (u *userRepoPG) GetUserData(ctx context.Context, userID, currUserID int) (u &user_.ID, &user_.Username, &user_.Avatar, &user_.Name, &user_.Surname, &user_.AboutMe, &isSubscribed, &subsCount, ); err != nil { - return nil, false, 0, convertErrorPostgres(ctx, err) + return nil, false, 0, convertErrorPostgres(err) } return user_, isSubscribed, subsCount, nil } @@ -118,7 +112,7 @@ func (u *userRepoPG) GetProfileData(ctx context.Context, userID int) (user_ *use if err := u.db.QueryRow(ctx, GetProfileInfo, userID).Scan( &user_.ID, &user_.Username, &user_.Avatar, &subsCount, ); err != nil { - return nil, 0, convertErrorPostgres(ctx, err) + return nil, 0, convertErrorPostgres(err) } return user_, subsCount, nil } From f9375325aceda179f85cdaf22e8f00995201249d Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 24 Nov 2023 17:46:37 +0300 Subject: [PATCH 203/266] dev3 minor: removed default case --- internal/pkg/repository/subscription/postgres/repo.go | 2 -- internal/pkg/repository/user/repo.go | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/pkg/repository/subscription/postgres/repo.go b/internal/pkg/repository/subscription/postgres/repo.go index 8966d1d..49737de 100644 --- a/internal/pkg/repository/subscription/postgres/repo.go +++ b/internal/pkg/repository/subscription/postgres/repo.go @@ -34,8 +34,6 @@ func convertErrorPostgres(err error) error { switch pgErr.SQLState() { case strconv.Itoa(23505): return &subRepo.ErrSubscriptionAlreadyExist{} - default: - return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} } } return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} diff --git a/internal/pkg/repository/user/repo.go b/internal/pkg/repository/user/repo.go index fca5e22..ea4d0be 100644 --- a/internal/pkg/repository/user/repo.go +++ b/internal/pkg/repository/user/repo.go @@ -50,10 +50,8 @@ func convertErrorPostgres(err error) error { var pgErr *pgconn.PgError if errors.As(err, &pgErr) { - switch pgErr.Code { + switch pgErr.SQLState() { // add SQL states if necessary - default: - return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} } } return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} From 810c02afbc393098f146fa4fefc9621d046b0792 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 25 Nov 2023 00:20:35 +0300 Subject: [PATCH 204/266] TP-409 add: get feed user chats with other users --- internal/api/server/router/router.go | 3 ++- .../delivery/http/v1/{message.go => chat.go} | 26 +++++++++++++++++++ internal/pkg/entity/message/message.go | 13 +++++++++- internal/pkg/repository/message/queries.go | 8 +++++- internal/pkg/repository/message/repo.go | 21 +++++++++++++++ internal/pkg/usecase/message/usecase.go | 14 ++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) rename internal/pkg/delivery/http/v1/{message.go => chat.go} (85%) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 1f39e3c..59ba052 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -94,7 +94,8 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli r.Get("/pin", handler.FeedPins) }) - r.With(auth.RequireAuth).Route("/message", func(r chi.Router) { + r.With(auth.RequireAuth).Route("/chat", func(r chi.Router) { + r.Get("/personal", handler.FeedChats) r.Get("/get/{userID:\\d+}", handler.GetMessagesFromChat) r.Post("/send/{userID:\\d+}", handler.SendMessageToUser) r.Put("/update/{messageID:\\d+}", handler.UpdateMessage) diff --git a/internal/pkg/delivery/http/v1/message.go b/internal/pkg/delivery/http/v1/chat.go similarity index 85% rename from internal/pkg/delivery/http/v1/message.go rename to internal/pkg/delivery/http/v1/chat.go index 57e2dde..2d550bb 100644 --- a/internal/pkg/delivery/http/v1/message.go +++ b/internal/pkg/delivery/http/v1/chat.go @@ -7,6 +7,32 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" ) +func (h *HandlerHTTP) FeedChats(w http.ResponseWriter, r *http.Request) { + log := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + count, lastID, err := FetchValidParamForLoadFeed(r.URL) + if err != nil { + log.Info(err.Error()) + err = responseError(w, "parse_url", "bad request url for getting feed chat") + if err != nil { + log.Error(err.Error()) + } + } + + chats, newLastID, err := h.messageCase.GetUserChatsWithOtherUsers(r.Context(), userID, count, lastID) + if err != nil { + log.Errorf(err.Error()) + } + err = responseOk(http.StatusOK, w, "success get feed user chats", map[string]any{ + "chats": chats, + "lastID": newLastID, + }) + if err != nil { + log.Fatal(err.Error()) + } +} + func (h *HandlerHTTP) SendMessageToUser(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) diff --git a/internal/pkg/entity/message/message.go b/internal/pkg/entity/message/message.go index a3cd65d..f6f893d 100644 --- a/internal/pkg/entity/message/message.go +++ b/internal/pkg/entity/message/message.go @@ -1,6 +1,10 @@ package message -import "github.com/jackc/pgx/v5/pgtype" +import ( + "github.com/jackc/pgx/v5/pgtype" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) type Chat [2]int @@ -14,3 +18,10 @@ type Message struct { func (m Message) WhatChat() Chat { return Chat{m.From, m.To} } + +type FeedUserChats []ChatWithUser + +type ChatWithUser struct { + MessageLastID int `json:"-"` + WichWhomChat user.User `json:"user"` +} diff --git a/internal/pkg/repository/message/queries.go b/internal/pkg/repository/message/queries.go index 9e2291d..6de9176 100644 --- a/internal/pkg/repository/message/queries.go +++ b/internal/pkg/repository/message/queries.go @@ -1,7 +1,13 @@ package message const ( - SelectMessageByID = "SELECT user_from, user_to, content FROM message WHERE id = $1 AND deleted_at IS NULL;" + SelectMessageByID = "SELECT user_from, user_to, content FROM message WHERE id = $1 AND deleted_at IS NULL;" + SelectUserChats = `SELECT max(message.id) AS mmid, profile.id, username, avatar + FROM message INNER JOIN profile ON user_to = profile.id + WHERE user_from = $1 AND (message.id < $2 OR $2 = 0) + GROUP BY profile.id + ORDER BY mmid DESC + LIMIT $3;` SelectMessageFromChat = `SELECT id, user_from, user_to, content FROM message WHERE deleted_at IS NULL AND (id < $1 OR $1 = 0) AND diff --git a/internal/pkg/repository/message/repo.go b/internal/pkg/repository/message/repo.go index 670e718..f556efb 100644 --- a/internal/pkg/repository/message/repo.go +++ b/internal/pkg/repository/message/repo.go @@ -15,6 +15,7 @@ type Repository interface { GetMessages(ctx context.Context, chat entity.Chat, count, lastID int) ([]entity.Message, error) UpdateContentMessage(ctx context.Context, messageID int, newContent string) error DelMessage(ctx context.Context, messageID int) error + GetUserChats(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, error) } type messageRepo struct { @@ -75,3 +76,23 @@ func (m *messageRepo) DelMessage(ctx context.Context, messageID int) error { } return nil } + +func (m *messageRepo) GetUserChats(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, error) { + rows, err := m.db.Query(ctx, SelectUserChats, userID, lastID, count) + if err != nil { + return nil, fmt.Errorf("get user chats in storage: %w", err) + } + defer rows.Close() + + feed := make(entity.FeedUserChats, 0, count) + chat := entity.ChatWithUser{} + for rows.Next() { + if err = rows.Scan(&chat.MessageLastID, &chat.WichWhomChat.ID, + &chat.WichWhomChat.Username, &chat.WichWhomChat.Avatar); err != nil { + + return feed, fmt.Errorf("scan chat with user for feed: %w", err) + } + feed = append(feed, chat) + } + return feed, nil +} diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index c457be1..d68c3b1 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -13,6 +13,7 @@ var ErrNoAccess = errors.New("there is no access to perform this action") //go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { + GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, int, error) SendMessage(ctx context.Context, mes *entity.Message) (int, error) GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error @@ -64,3 +65,16 @@ func (m *messageCase) DeleteMessage(ctx context.Context, userID, mesID int) erro func (m *messageCase) GetMessage(ctx context.Context, messageID int) (*entity.Message, error) { return m.repo.GetMessageByID(ctx, messageID) } + +func (m *messageCase) GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, int, error) { + var newLastID int + feed, err := m.repo.GetUserChats(ctx, userID, count, lastID) + if len(feed) != 0 { + newLastID = feed[len(feed)-1].MessageLastID + } + + if err != nil { + return feed, newLastID, fmt.Errorf("get user chats with other users: %w", err) + } + return feed, newLastID, nil +} From 63bdccf737611cc26e47e35ad0adf8e1a1228424 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sat, 25 Nov 2023 17:35:01 +0300 Subject: [PATCH 205/266] microservice auth --- api/auth/auth.pb.go | 483 ++++++++++++++++++ api/auth/auth_grpc.pb.go | 214 ++++++++ api/proto/auth.proto | 41 ++ cmd/auth/main.go | 72 +++ internal/api/server/router/router.go | 6 +- internal/app/app.go | 15 +- .../auth/delivery/grpc/server.go | 88 ++++ .../microservices/auth/usecase/usecase.go | 1 + internal/pkg/delivery/http/v1/auth.go | 22 +- internal/pkg/delivery/http/v1/handler.go | 4 + internal/pkg/middleware/auth/auth.go | 11 +- internal/pkg/repository/auth/repo.go | 73 +++ internal/pkg/usecase/auth/usecase.go | 40 ++ 13 files changed, 1048 insertions(+), 22 deletions(-) create mode 100644 api/auth/auth.pb.go create mode 100644 api/auth/auth_grpc.pb.go create mode 100644 api/proto/auth.proto create mode 100644 cmd/auth/main.go create mode 100644 internal/microservices/auth/delivery/grpc/server.go create mode 100644 internal/microservices/auth/usecase/usecase.go create mode 100644 internal/pkg/repository/auth/repo.go create mode 100644 internal/pkg/usecase/auth/usecase.go diff --git a/api/auth/auth.pb.go b/api/auth/auth.pb.go new file mode 100644 index 0000000..24c7be4 --- /dev/null +++ b/api/auth/auth.pb.go @@ -0,0 +1,483 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.6.1 +// source: api/proto/auth.proto + +package auth + +import ( + empty "github.com/golang/protobuf/ptypes/empty" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Credentials struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` +} + +func (x *Credentials) Reset() { + *x = Credentials{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Credentials) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Credentials) ProtoMessage() {} + +func (x *Credentials) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_auth_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Credentials.ProtoReflect.Descriptor instead. +func (*Credentials) Descriptor() ([]byte, []int) { + return file_api_proto_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *Credentials) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *Credentials) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +type RegisterData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Cred *Credentials `protobuf:"bytes,1,opt,name=cred,proto3" json:"cred,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` +} + +func (x *RegisterData) Reset() { + *x = RegisterData{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterData) ProtoMessage() {} + +func (x *RegisterData) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_auth_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterData.ProtoReflect.Descriptor instead. +func (*RegisterData) Descriptor() ([]byte, []int) { + return file_api_proto_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *RegisterData) GetCred() *Credentials { + if x != nil { + return x.Cred + } + return nil +} + +func (x *RegisterData) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` +} + +func (x *User) Reset() { + *x = User{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_auth_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_api_proto_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *User) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *User) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *User) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +type Session struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + UserID int64 `protobuf:"varint,2,opt,name=userID,proto3" json:"userID,omitempty"` + Expire *timestamp.Timestamp `protobuf:"bytes,3,opt,name=expire,proto3" json:"expire,omitempty"` +} + +func (x *Session) Reset() { + *x = Session{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Session) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Session) ProtoMessage() {} + +func (x *Session) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_auth_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Session.ProtoReflect.Descriptor instead. +func (*Session) Descriptor() ([]byte, []int) { + return file_api_proto_auth_proto_rawDescGZIP(), []int{3} +} + +func (x *Session) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Session) GetUserID() int64 { + if x != nil { + return x.UserID + } + return 0 +} + +func (x *Session) GetExpire() *timestamp.Timestamp { + if x != nil { + return x.Expire + } + return nil +} + +type UserID struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *UserID) Reset() { + *x = UserID{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserID) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserID) ProtoMessage() {} + +func (x *UserID) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_auth_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserID.ProtoReflect.Descriptor instead. +func (*UserID) Descriptor() ([]byte, []int) { + return file_api_proto_auth_proto_rawDescGZIP(), []int{4} +} + +func (x *UserID) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +var File_api_proto_auth_proto protoreflect.FileDescriptor + +var file_api_proto_auth_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x61, 0x75, 0x74, 0x68, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x45, 0x0a, 0x0b, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x4b, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x25, 0x0a, 0x04, 0x63, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x73, 0x52, 0x04, 0x63, 0x72, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x4a, + 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x67, 0x0a, 0x07, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, + 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x22, 0x18, 0x0a, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x32, 0xcc, 0x01, + 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x38, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x12, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x12, 0x2b, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x11, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x1a, 0x0d, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x31, 0x0a, + 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x0d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x12, 0x2a, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x0d, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x0c, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x00, 0x42, 0x35, 0x5a, 0x33, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x70, 0x61, + 0x72, 0x6b, 0x2d, 0x6d, 0x61, 0x69, 0x6c, 0x2d, 0x72, 0x75, 0x2f, 0x32, 0x30, 0x32, 0x33, 0x5f, + 0x32, 0x5f, 0x4f, 0x4e, 0x44, 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, + 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_auth_proto_rawDescOnce sync.Once + file_api_proto_auth_proto_rawDescData = file_api_proto_auth_proto_rawDesc +) + +func file_api_proto_auth_proto_rawDescGZIP() []byte { + file_api_proto_auth_proto_rawDescOnce.Do(func() { + file_api_proto_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_auth_proto_rawDescData) + }) + return file_api_proto_auth_proto_rawDescData +} + +var file_api_proto_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_api_proto_auth_proto_goTypes = []interface{}{ + (*Credentials)(nil), // 0: auth.Credentials + (*RegisterData)(nil), // 1: auth.RegisterData + (*User)(nil), // 2: auth.User + (*Session)(nil), // 3: auth.Session + (*UserID)(nil), // 4: auth.UserID + (*timestamp.Timestamp)(nil), // 5: google.protobuf.Timestamp + (*empty.Empty)(nil), // 6: google.protobuf.Empty +} +var file_api_proto_auth_proto_depIdxs = []int32{ + 0, // 0: auth.RegisterData.cred:type_name -> auth.Credentials + 5, // 1: auth.Session.expire:type_name -> google.protobuf.Timestamp + 1, // 2: auth.Auth.Register:input_type -> auth.RegisterData + 0, // 3: auth.Auth.Login:input_type -> auth.Credentials + 3, // 4: auth.Auth.Logout:input_type -> auth.Session + 3, // 5: auth.Auth.GetUserID:input_type -> auth.Session + 6, // 6: auth.Auth.Register:output_type -> google.protobuf.Empty + 3, // 7: auth.Auth.Login:output_type -> auth.Session + 6, // 8: auth.Auth.Logout:output_type -> google.protobuf.Empty + 4, // 9: auth.Auth.GetUserID:output_type -> auth.UserID + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_api_proto_auth_proto_init() } +func file_api_proto_auth_proto_init() { + if File_api_proto_auth_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Credentials); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_auth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_auth_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Session); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_auth_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserID); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_auth_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_auth_proto_goTypes, + DependencyIndexes: file_api_proto_auth_proto_depIdxs, + MessageInfos: file_api_proto_auth_proto_msgTypes, + }.Build() + File_api_proto_auth_proto = out.File + file_api_proto_auth_proto_rawDesc = nil + file_api_proto_auth_proto_goTypes = nil + file_api_proto_auth_proto_depIdxs = nil +} diff --git a/api/auth/auth_grpc.pb.go b/api/auth/auth_grpc.pb.go new file mode 100644 index 0000000..bc4a5e7 --- /dev/null +++ b/api/auth/auth_grpc.pb.go @@ -0,0 +1,214 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.6.1 +// source: api/proto/auth.proto + +package auth + +import ( + context "context" + empty "github.com/golang/protobuf/ptypes/empty" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AuthClient is the client API for Auth service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthClient interface { + Register(ctx context.Context, in *RegisterData, opts ...grpc.CallOption) (*empty.Empty, error) + Login(ctx context.Context, in *Credentials, opts ...grpc.CallOption) (*Session, error) + Logout(ctx context.Context, in *Session, opts ...grpc.CallOption) (*empty.Empty, error) + GetUserID(ctx context.Context, in *Session, opts ...grpc.CallOption) (*UserID, error) +} + +type authClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthClient(cc grpc.ClientConnInterface) AuthClient { + return &authClient{cc} +} + +func (c *authClient) Register(ctx context.Context, in *RegisterData, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/auth.Auth/Register", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authClient) Login(ctx context.Context, in *Credentials, opts ...grpc.CallOption) (*Session, error) { + out := new(Session) + err := c.cc.Invoke(ctx, "/auth.Auth/Login", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authClient) Logout(ctx context.Context, in *Session, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/auth.Auth/Logout", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authClient) GetUserID(ctx context.Context, in *Session, opts ...grpc.CallOption) (*UserID, error) { + out := new(UserID) + err := c.cc.Invoke(ctx, "/auth.Auth/GetUserID", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthServer is the server API for Auth service. +// All implementations must embed UnimplementedAuthServer +// for forward compatibility +type AuthServer interface { + Register(context.Context, *RegisterData) (*empty.Empty, error) + Login(context.Context, *Credentials) (*Session, error) + Logout(context.Context, *Session) (*empty.Empty, error) + GetUserID(context.Context, *Session) (*UserID, error) + mustEmbedUnimplementedAuthServer() +} + +// UnimplementedAuthServer must be embedded to have forward compatible implementations. +type UnimplementedAuthServer struct { +} + +func (UnimplementedAuthServer) Register(context.Context, *RegisterData) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedAuthServer) Login(context.Context, *Credentials) (*Session, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedAuthServer) Logout(context.Context, *Session) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") +} +func (UnimplementedAuthServer) GetUserID(context.Context, *Session) (*UserID, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetUserID not implemented") +} +func (UnimplementedAuthServer) mustEmbedUnimplementedAuthServer() {} + +// UnsafeAuthServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthServer will +// result in compilation errors. +type UnsafeAuthServer interface { + mustEmbedUnimplementedAuthServer() +} + +func RegisterAuthServer(s grpc.ServiceRegistrar, srv AuthServer) { + s.RegisterService(&Auth_ServiceDesc, srv) +} + +func _Auth_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterData) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/auth.Auth/Register", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).Register(ctx, req.(*RegisterData)) + } + return interceptor(ctx, in, info, handler) +} + +func _Auth_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Credentials) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/auth.Auth/Login", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).Login(ctx, req.(*Credentials)) + } + return interceptor(ctx, in, info, handler) +} + +func _Auth_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Session) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).Logout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/auth.Auth/Logout", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).Logout(ctx, req.(*Session)) + } + return interceptor(ctx, in, info, handler) +} + +func _Auth_GetUserID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Session) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).GetUserID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/auth.Auth/GetUserID", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).GetUserID(ctx, req.(*Session)) + } + return interceptor(ctx, in, info, handler) +} + +// Auth_ServiceDesc is the grpc.ServiceDesc for Auth service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Auth_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "auth.Auth", + HandlerType: (*AuthServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Register", + Handler: _Auth_Register_Handler, + }, + { + MethodName: "Login", + Handler: _Auth_Login_Handler, + }, + { + MethodName: "Logout", + Handler: _Auth_Logout_Handler, + }, + { + MethodName: "GetUserID", + Handler: _Auth_GetUserID_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/proto/auth.proto", +} diff --git a/api/proto/auth.proto b/api/proto/auth.proto new file mode 100644 index 0000000..12a5507 --- /dev/null +++ b/api/proto/auth.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/api/auth"; + +package auth; + +service Auth { + rpc Register(RegisterData) returns (google.protobuf.Empty) {} + rpc Login(Credentials) returns (Session) {} + rpc Logout(Session) returns (google.protobuf.Empty) {} + rpc GetUserID(Session) returns (UserID) {} +} + +message Credentials { + string password = 1; + string username = 2; +} + +message RegisterData { + Credentials cred = 1; + string email = 2; +} + +message User { + int64 id = 1; + string username = 2; + string avatar = 3; +} + +message Session { + string key = 1; + int64 userID = 2; + google.protobuf.Timestamp expire = 3; +} + +message UserID { + int64 id = 1; +} \ No newline at end of file diff --git a/cmd/auth/main.go b/cmd/auth/main.go new file mode 100644 index 0000000..799ae03 --- /dev/null +++ b/cmd/auth/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "fmt" + "net" + "time" + + "google.golang.org/grpc" + + "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" + authMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/auth/delivery/grpc" + imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" + sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" + userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/joho/godotenv" +) + +func main() { + log, err := logger.New(logger.RFC3339FormatTime()) + if err != nil { + fmt.Println(err) + return + } + l, err := net.Listen("tcp", "localhost:8085") + if err != nil { + log.Error(err.Error()) + return + } + + s := grpc.NewServer() + godotenv.Load() + + ctx := context.Background() + + pool, err := app.NewPoolPG(ctx) + if err != nil { + log.Error(err.Error()) + return + } + defer pool.Close() + + ctx, cancelCtxRedis := context.WithTimeout(ctx, time.Second) + defer cancelCtxRedis() + + redisCfg, err := app.NewConfig("redis.conf") + if err != nil { + log.Error(err.Error()) + return + } + + redisCl, err := app.NewRedisClient(ctx, redisCfg) + if err != nil { + log.Error(err.Error()) + return + } + defer redisCl.Close() + + sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) + imgCase := image.New(log, imgRepo.NewImageRepoFS("upload/")) + u := user.New(log, imgCase, userRepo.NewUserRepoPG(pool)) + auth.RegisterAuthServer(s, authMS.New(log, sm, u)) + if err = s.Serve(l); err != nil { + log.Error(err.Error()) + return + } +} diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index ee32448..08950ac 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -14,7 +14,7 @@ import ( mw "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/security" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + authCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -28,7 +28,7 @@ func New() Router { return Router{chi.NewMux()} } -func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deliveryWS.HandlerWebSocket, sm session.SessionManager, log *logger.Logger) { +func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deliveryWS.HandlerWebSocket, ac authCase.Usecase, log *logger.Logger) { cfgCSRF := security.DefaultCSRFConfig() cfgCSRF.PathToGet = "/api/v1/csrf" @@ -46,7 +46,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli mw.SetResponseHeaders(map[string]string{ "Content-Type": "application/json", }), - auth.NewAuthMiddleware(sm).ContextWithUserID) + auth.NewAuthMiddleware(ac).ContextWithUserID) r.Mux.Route("/api/v1", func(r chi.Router) { r.Get("/docs/*", httpSwagger.WrapHandler) diff --git a/internal/app/app.go b/internal/app/app.go index 8cd42b8..a7d8a82 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,11 +6,15 @@ import ( "github.com/joho/godotenv" "github.com/microcosm-cc/bluemonday" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" + authRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/auth" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" @@ -18,6 +22,7 @@ import ( sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription/postgres" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" @@ -68,7 +73,15 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) messageCase := message.New(mesRepo.NewMessageRepo(pool)) + conn, err := grpc.Dial("localhost:8085", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Error(err.Error()) + return + } + ac := auth.New(authRepo.NewAuthRepo(authProto.NewAuthClient(conn))) + handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ + AuhtCase: ac, UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), PinCase: pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)), BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), @@ -87,7 +100,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } server := server.New(log, cfgServ) router := router.New() - router.RegisterRoute(handler, wsHandler, sm, log) + router.RegisterRoute(handler, wsHandler, ac, log) if err := server.Run(router.Mux); err != nil { log.Error(err.Error()) diff --git a/internal/microservices/auth/delivery/grpc/server.go b/internal/microservices/auth/delivery/grpc/server.go new file mode 100644 index 0000000..1251882 --- /dev/null +++ b/internal/microservices/auth/delivery/grpc/server.go @@ -0,0 +1,88 @@ +package auth + +import ( + "context" + + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + userUsecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type AuthServer struct { + authProto.UnimplementedAuthServer + + log *logger.Logger + sm session.SessionManager + userCase userUsecase.Usecase +} + +func New(log *logger.Logger, sm session.SessionManager, userCase userUsecase.Usecase) AuthServer { + return AuthServer{ + UnimplementedAuthServer: authProto.UnimplementedAuthServer{}, + log: log, + sm: sm, + userCase: userCase, + } +} + +func (as AuthServer) Register(ctx context.Context, rd *authProto.RegisterData) (*empty.Empty, error) { + user := &user.User{ + Email: rd.Email, + Username: rd.Cred.Username, + Password: rd.Cred.Password, + } + + err := as.userCase.Register(ctx, user) + if err != nil { + as.log.Error(err.Error()) + return nil, status.Error(codes.Internal, "") + } + return &empty.Empty{}, nil +} + +func (as AuthServer) Login(ctx context.Context, cred *authProto.Credentials) (*authProto.Session, error) { + user, err := as.userCase.Authentication(ctx, userUsecase.UserCredentials{ + Username: cred.Username, + Password: cred.Password, + }) + if err != nil { + as.log.Error(err.Error()) + return nil, status.Error(codes.PermissionDenied, "") + } + + session, err := as.sm.CreateNewSessionForUser(ctx, user.ID) + if err != nil { + as.log.Error(err.Error()) + return nil, status.Error(codes.Internal, "") + } + + return &authProto.Session{ + Key: session.Key, + UserID: int64(session.UserID), + Expire: timestamppb.New(session.Expire), + }, nil +} + +func (as AuthServer) Logout(ctx context.Context, sess *authProto.Session) (*empty.Empty, error) { + err := as.sm.DeleteUserSession(ctx, sess.Key) + if err != nil { + as.log.Error(err.Error()) + return nil, status.Error(codes.Internal, "delete user session") + } + return &empty.Empty{}, nil +} + +func (as AuthServer) GetUserID(ctx context.Context, sess *authProto.Session) (*authProto.UserID, error) { + if userID, err := as.sm.GetUserIDBySessionKey(ctx, sess.Key); err != nil { + return nil, status.Error(codes.Internal, "") + } else { + return &authProto.UserID{Id: int64(userID)}, nil + } +} diff --git a/internal/microservices/auth/usecase/usecase.go b/internal/microservices/auth/usecase/usecase.go new file mode 100644 index 0000000..aed2454 --- /dev/null +++ b/internal/microservices/auth/usecase/usecase.go @@ -0,0 +1 @@ +package usecase diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index 3fa680a..6f536a7 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -76,17 +77,7 @@ func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { return } - user, err := h.userCase.Authentication(r.Context(), params) - if err != nil { - logger.Warn(err.Error()) - err = responseError(w, "bad_credentials", "incorrect user credentials") - if err != nil { - logger.Error(err.Error()) - } - return - } - - session, err := h.sm.CreateNewSessionForUser(r.Context(), user.ID) + session, err := h.authCase.Login(r.Context(), params.Username, params.Password) if err != nil { logger.Error(err.Error()) err = responseError(w, "session", "failed to create a session for the user") @@ -151,7 +142,7 @@ func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { return } - err = h.userCase.Register(r.Context(), user) + err = h.authCase.Register(r.Context(), user) if err != nil { logger.Warn(err.Error()) err = responseError(w, "uniq_fields", "there is already an account with this username or email") @@ -178,6 +169,7 @@ func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { // @Router /api/v1/auth/logout [delete] func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) cookie, err := r.Cookie("session_key") if err != nil { @@ -193,7 +185,11 @@ func (h *HandlerHTTP) Logout(w http.ResponseWriter, r *http.Request) { cookie.Path = "/" http.SetCookie(w, cookie) - err = h.sm.DeleteUserSession(r.Context(), cookie.Value) + err = h.authCase.Logout(r.Context(), &session.Session{ + Key: cookie.Value, + UserID: userID, + Expire: cookie.Expires, + }) if err != nil { logger.Error(err.Error()) err = responseError(w, "session", "the user logged out, but his session did not end") diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 22bfb80..2a54b1a 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" @@ -12,6 +13,7 @@ import ( type HandlerHTTP struct { log *logger.Logger + authCase auth.Usecase userCase user.Usecase pinCase pin.Usecase boardCase board.Usecase @@ -23,6 +25,7 @@ type HandlerHTTP struct { func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { return &HandlerHTTP{ log: log, + authCase: hub.AuhtCase, userCase: hub.UserCase, pinCase: hub.PinCase, boardCase: hub.BoardCase, @@ -33,6 +36,7 @@ func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { } type UsecaseHub struct { + AuhtCase auth.Usecase UserCase user.Usecase PinCase pin.Usecase BoardCase board.Usecase diff --git a/internal/pkg/middleware/auth/auth.go b/internal/pkg/middleware/auth/auth.go index 09b50ec..30d26dd 100644 --- a/internal/pkg/middleware/auth/auth.go +++ b/internal/pkg/middleware/auth/auth.go @@ -4,7 +4,8 @@ import ( "context" "net/http" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + authCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" ) type authContextValueKey string @@ -16,17 +17,17 @@ const ( ) type authMiddleware struct { - sm session.SessionManager + authCase authCase.Usecase } -func NewAuthMiddleware(sm session.SessionManager) authMiddleware { - return authMiddleware{sm} +func NewAuthMiddleware(auth authCase.Usecase) authMiddleware { + return authMiddleware{auth} } func (am authMiddleware) ContextWithUserID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if cookie, err := r.Cookie(SessionCookieName); err == nil { - if userID, err := am.sm.GetUserIDBySessionKey(r.Context(), cookie.Value); err == nil { + if userID, err := am.authCase.GetUserIDBySession(r.Context(), &session.Session{Key: cookie.Value, Expire: cookie.Expires}); err == nil { r = r.WithContext(context.WithValue(r.Context(), KeyCurrentUserID, userID)) } } diff --git a/internal/pkg/repository/auth/repo.go b/internal/pkg/repository/auth/repo.go new file mode 100644 index 0000000..98581bd --- /dev/null +++ b/internal/pkg/repository/auth/repo.go @@ -0,0 +1,73 @@ +package auth + +import ( + "context" + "fmt" + + authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Repository interface { + Register(ctx context.Context, user *entity.User) error + Logout(ctx context.Context, sess *session.Session) error + Login(ctx context.Context, username, password string) (*session.Session, error) + GetUserID(ctx context.Context, sess *session.Session) (int, error) +} + +type authRepo struct { + client authProto.AuthClient +} + +func NewAuthRepo(c authProto.AuthClient) *authRepo { + return &authRepo{c} +} + +func (r *authRepo) Register(ctx context.Context, user *entity.User) error { + _, err := r.client.Register(ctx, &authProto.RegisterData{ + Cred: &authProto.Credentials{ + Username: user.Username, + Password: user.Password, + }, + Email: user.Email, + }) + return err +} + +func (r *authRepo) Logout(ctx context.Context, sess *session.Session) error { + _, err := r.client.Logout(ctx, &authProto.Session{ + Key: sess.Key, + UserID: int64(sess.UserID), + Expire: timestamppb.New(sess.Expire), + }) + return err +} + +func (r *authRepo) Login(ctx context.Context, username, password string) (*session.Session, error) { + sess, err := r.client.Login(ctx, &authProto.Credentials{ + Username: username, + Password: password, + }) + if err != nil { + return nil, fmt.Errorf("login: %w", err) + } + return &session.Session{ + Key: sess.Key, + UserID: int(sess.UserID), + Expire: sess.Expire.AsTime(), + }, nil +} + +func (r *authRepo) GetUserID(ctx context.Context, sess *session.Session) (int, error) { + userID, err := r.client.GetUserID(ctx, &authProto.Session{ + Key: sess.Key, + UserID: int64(sess.UserID), + Expire: timestamppb.New(sess.Expire), + }) + if err != nil { + return 0, fmt.Errorf("get user id: %w", err) + } + return int(userID.Id), nil +} diff --git a/internal/pkg/usecase/auth/usecase.go b/internal/pkg/usecase/auth/usecase.go new file mode 100644 index 0000000..85543f4 --- /dev/null +++ b/internal/pkg/usecase/auth/usecase.go @@ -0,0 +1,40 @@ +package auth + +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + authRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/auth" +) + +type Usecase interface { + Register(ctx context.Context, user *entity.User) error + Login(ctx context.Context, username, password string) (*session.Session, error) + GetUserIDBySession(ctx context.Context, sess *session.Session) (int, error) + Logout(ctx context.Context, sess *session.Session) error +} + +type authCase struct { + repo authRepo.Repository +} + +func New(repo authRepo.Repository) *authCase { + return &authCase{repo} +} + +func (ac *authCase) Register(ctx context.Context, user *entity.User) error { + return ac.repo.Register(ctx, user) +} + +func (ac *authCase) Logout(ctx context.Context, sess *session.Session) error { + return ac.repo.Logout(ctx, sess) +} + +func (ac *authCase) Login(ctx context.Context, username, password string) (*session.Session, error) { + return ac.repo.Login(ctx, username, password) +} + +func (ac *authCase) GetUserIDBySession(ctx context.Context, sess *session.Session) (int, error) { + return ac.repo.GetUserID(ctx, sess) +} From c3111f8cbae32536bfd30c79bd692d313e25e466 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 26 Nov 2023 12:03:02 +0300 Subject: [PATCH 206/266] TP-34d_search: add user, board, pin search with pagination and sort options --- internal/api/server/router/router.go | 6 + internal/app/app.go | 16 +- internal/pkg/delivery/http/v1/handler.go | 4 + internal/pkg/delivery/http/v1/search.go | 161 ++++++++++++++++++ .../pkg/delivery/http/v1/search_errors.go | 23 +++ internal/pkg/entity/search/search.go | 74 ++++++++ internal/pkg/entity/user/user.go | 9 +- internal/pkg/repository/search/errors.go | 36 ++++ .../pkg/repository/search/postgres/builder.go | 131 ++++++++++++++ .../pkg/repository/search/postgres/queries.go | 51 ++++++ .../pkg/repository/search/postgres/repo.go | 129 ++++++++++++++ internal/pkg/repository/search/repo.go | 13 ++ internal/pkg/usecase/search/usecase.go | 67 ++++++++ internal/pkg/usecase/subscription/get.go | 18 +- internal/pkg/usecase/subscription/usecase.go | 12 +- 15 files changed, 734 insertions(+), 16 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/search.go create mode 100644 internal/pkg/delivery/http/v1/search_errors.go create mode 100644 internal/pkg/entity/search/search.go create mode 100644 internal/pkg/repository/search/errors.go create mode 100644 internal/pkg/repository/search/postgres/builder.go create mode 100644 internal/pkg/repository/search/postgres/queries.go create mode 100644 internal/pkg/repository/search/postgres/repo.go create mode 100644 internal/pkg/repository/search/repo.go create mode 100644 internal/pkg/usecase/search/usecase.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index b9c36f3..95bb286 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -82,6 +82,12 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli }) }) + r.Route("/search", func(r chi.Router) { + r.Get("/users", handler.SearchUsers) + r.Get("/boards", handler.SearchBoards) + r.Get("/pins", handler.SearchPins) + }) + r.Route("/pin", func(r chi.Router) { r.Get("/{pinID:\\d+}", handler.ViewPin) diff --git a/internal/app/app.go b/internal/app/app.go index 747afd9..4355548 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -15,6 +15,7 @@ import ( imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" mesCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" + searchRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/search/postgres" sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription/postgres" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" @@ -22,6 +23,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/search" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -68,18 +70,18 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ - UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), - PinCase: pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)), - BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), - SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool)), - MessageCase: message.New(mesCase.NewMessageRepo(pool)), - SM: sm, + UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), + PinCase: pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)), + BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), + SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), + SearchCase: search.New(log, searchRepo.NewSearchRepoPG(pool), bluemonday.UGCPolicy()), + MessageCase: message.New(mesCase.NewMessageRepo(pool)), + SM: sm, }) wsHandler := deliveryWS.New(log, deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) - cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { log.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 22bfb80..e45989a 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -4,6 +4,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/search" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -16,6 +17,7 @@ type HandlerHTTP struct { pinCase pin.Usecase boardCase board.Usecase subCase subscription.Usecase + searchCase search.Usecase messageCase message.Usecase sm session.SessionManager } @@ -27,6 +29,7 @@ func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { pinCase: hub.PinCase, boardCase: hub.BoardCase, subCase: hub.SubscriptionCase, + searchCase: hub.SearchCase, messageCase: hub.MessageCase, sm: hub.SM, } @@ -37,6 +40,7 @@ type UsecaseHub struct { PinCase pin.Usecase BoardCase board.Usecase SubscriptionCase subscription.Usecase + SearchCase search.Usecase MessageCase message.Usecase SM session.SessionManager } diff --git a/internal/pkg/delivery/http/v1/search.go b/internal/pkg/delivery/http/v1/search.go new file mode 100644 index 0000000..f798d36 --- /dev/null +++ b/internal/pkg/delivery/http/v1/search.go @@ -0,0 +1,161 @@ +package v1 + +import ( + "net/http" + "strconv" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" +) + +var ( + defaultSearchCount = 20 + maxSearchCount = 50 + defaultSearchOffset = 0 + maxSearchOffset = 50 +) + +var ( + userSortOpts = []string{"id", "subscribers"} + boardSortOpts = []string{"id", "pins"} + pinSortOpts = []string{"id", "likes"} +) + +var ( + defaultSearchSort = "id" + defaultSortOder = "desc" +) + +func (h *HandlerHTTP) SearchUsers(w http.ResponseWriter, r *http.Request) { + opts, err := GetSearchOpts(r, userSortOpts, defaultSearchSort) + if err != nil { + h.responseErr(w, r, err) + return + } + + if users, err := h.searchCase.GetUsers(r.Context(), opts); err != nil { + h.responseErr(w, r, err) + } else if err := responseOk(http.StatusOK, w, "got users sucessfully", users); err != nil { + h.responseErr(w, r, err) + } +} + +func (h *HandlerHTTP) SearchBoards(w http.ResponseWriter, r *http.Request) { + opts, err := GetSearchOpts(r, boardSortOpts, defaultSearchSort) + if err != nil { + h.responseErr(w, r, err) + return + } + + if boards, err := h.searchCase.GetBoards(r.Context(), opts); err != nil { + h.responseErr(w, r, err) + } else if err := responseOk(http.StatusOK, w, "got boards sucessfully", boards); err != nil { + h.responseErr(w, r, err) + } +} + +func (h *HandlerHTTP) SearchPins(w http.ResponseWriter, r *http.Request) { + opts, err := GetSearchOpts(r, pinSortOpts, defaultSearchSort) + if err != nil { + h.responseErr(w, r, err) + return + } + + if pins, err := h.searchCase.GetPins(r.Context(), opts); err != nil { + h.responseErr(w, r, err) + } else if err := responseOk(http.StatusOK, w, "got pins sucessfully", pins); err != nil { + h.responseErr(w, r, err) + } +} + +func GetSearchOpts(r *http.Request, sortOpts []string, defaultSortOpt string) (*search.SearchOpts, error) { + opts := &search.SearchOpts{} + invalidParams := map[string]string{} + + generalOpts, err := GetGeneralOpts(r, invalidParams) + if err != nil { + return nil, err + } + opts.General = *generalOpts + + if sortBy := r.URL.Query().Get("sortBy"); sortBy != "" { + if !isCorrentSortOpt(sortOpts, sortBy) { + invalidParams["sortBy"] = sortBy + } else { + opts.SortBy = sortBy + } + } else { + opts.SortBy = defaultSortOpt + } + + if len(invalidParams) > 0 { + return nil, &ErrInvalidQueryParam{params: invalidParams} + } + + return opts, nil +} + +func isCorrentSortOpt(correctOpts []string, opt string) bool { + for _, correctOpt := range correctOpts { + if opt == correctOpt { + return true + } + } + return false +} + +func GetGeneralOpts(r *http.Request, invalidParams map[string]string) (*search.GeneralOpts, error) { + opts := &search.GeneralOpts{} + + if templateParam := r.URL.Query().Get("template"); templateParam != "" { + if template := search.Template(templateParam); !template.Validate() { + invalidParams["template"] = string(template) + } else { + opts.Template = template + } + } else { + return nil, &ErrNoData{} + } + + if sortOrder := r.URL.Query().Get("order"); sortOrder != "" { + if sortOrder != "asc" && sortOrder != "desc" { + invalidParams["order"] = sortOrder + } else { + opts.SortOrder = sortOrder + } + } else { + opts.SortOrder = defaultSortOder + } + + if countParam := r.URL.Query().Get("count"); countParam != "" { + if count, err := strconv.ParseInt(countParam, 10, 64); err != nil || count < 0 { + invalidParams["count"] = countParam + } else { + opts.Count = int(count) + } + } else { + opts.Count = defaultSearchCount + } + + if offsetParam := r.URL.Query().Get("offset"); offsetParam != "" { + if offset, err := strconv.ParseInt(offsetParam, 10, 64); err != nil || offset < 0 { + invalidParams["offset"] = offsetParam + } else { + opts.Offset = int(offset) + } + } else { + opts.Offset = defaultSearchOffset + } + + if opts.Count > maxSearchCount { + opts.Count = maxSearchCount + } + if opts.Offset > maxSearchOffset { + opts.Offset = maxSearchOffset + } + + userID, _ := r.Context().Value(auth.KeyCurrentUserID).(int) + opts.CurrUserID = userID + + return opts, nil +} diff --git a/internal/pkg/delivery/http/v1/search_errors.go b/internal/pkg/delivery/http/v1/search_errors.go new file mode 100644 index 0000000..1fb9f00 --- /dev/null +++ b/internal/pkg/delivery/http/v1/search_errors.go @@ -0,0 +1,23 @@ +package v1 + +import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + +type ErrNoData struct{} + +func (e *ErrNoData) Error() string { + return "Can't find any user/board/pin" +} + +func (e *ErrNoData) Type() errPkg.Type { + return errPkg.ErrNotFound +} + +type ErrInvalidTemplate struct{} + +func (e *ErrInvalidTemplate) Error() string { + return "Invalid template has been provided" +} + +func (e *ErrInvalidTemplate) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} diff --git a/internal/pkg/entity/search/search.go b/internal/pkg/entity/search/search.go new file mode 100644 index 0000000..76b0c6a --- /dev/null +++ b/internal/pkg/entity/search/search.go @@ -0,0 +1,74 @@ +package search + +import ( + "strings" + "unicode" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + "github.com/microcosm-cc/bluemonday" +) + +type Template string + +func (t *Template) Validate() bool { + if len(*t) == 0 || len(*t) > 40 { + return false + } + for _, sym := range *t { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { + return false + } + } + return true +} + +func (t *Template) GetSubStrings(sep string) []string { + return strings.Split(string(*t), sep) +} + +type BoardForSearch struct { + BoardHeader board.Board + PinsNumber int `json:"pins_number"` + PreviewPins []string `json:"pins"` +} + +type PinForSearch struct { + ID int `json:"id"` + Title string `json:"title"` + Picture string `json:"picture"` + Likes int `json:"likes"` +} + +type UserForSearch struct { + ID int `json:"id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + SubsCount int `json:"subscribers"` + HasSubscribeFromCurUser bool `json:"is_subscribed"` +} + +func (u *UserForSearch) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(u.Username) +} + +func (b *BoardForSearch) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(b.BoardHeader.Title) + sanitizer.Sanitize(b.BoardHeader.Description) +} + +func (p *PinForSearch) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(p.Title) +} + +type SearchOpts struct { + General GeneralOpts + SortBy string +} + +type GeneralOpts struct { + Template Template + SortOrder string + CurrUserID int + Count int + Offset int +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 7465e13..1b270be 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -1,6 +1,9 @@ package user -import "github.com/jackc/pgx/v5/pgtype" +import ( + "github.com/jackc/pgx/v5/pgtype" + "github.com/microcosm-cc/bluemonday" +) type User struct { ID int `json:"id,omitempty" example:"123"` @@ -20,6 +23,10 @@ type SubscriptionUser struct { HasSubscribeFromCurUser bool `json:"is_subscribed"` } +func (u *SubscriptionUser) Sanitize(sanitizer *bluemonday.Policy) { + sanitizer.Sanitize(u.Username) +} + type SubscriptionOpts struct { UserID int Count int diff --git a/internal/pkg/repository/search/errors.go b/internal/pkg/repository/search/errors.go new file mode 100644 index 0000000..141884c --- /dev/null +++ b/internal/pkg/repository/search/errors.go @@ -0,0 +1,36 @@ +package search + +import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + +type ErrNoUsers struct { +} + +func (e *ErrNoUsers) Error() string { + return "Can't find any user" +} + +func (e *ErrNoUsers) Type() errPkg.Type { + return errPkg.ErrNotFound +} + +type ErrNoBoards struct { +} + +func (e *ErrNoBoards) Error() string { + return "Can't find any board" +} + +func (e *ErrNoBoards) Type() errPkg.Type { + return errPkg.ErrNotFound +} + +type ErrNoPins struct { +} + +func (e *ErrNoPins) Error() string { + return "Can't find any pin" +} + +func (e *ErrNoPins) Type() errPkg.Type { + return errPkg.ErrNotFound +} diff --git a/internal/pkg/repository/search/postgres/builder.go b/internal/pkg/repository/search/postgres/builder.go new file mode 100644 index 0000000..a025850 --- /dev/null +++ b/internal/pkg/repository/search/postgres/builder.go @@ -0,0 +1,131 @@ +package search + +import ( + "fmt" + + "github.com/Masterminds/squirrel" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" +) + +func SetUserSortType(sq squirrel.SelectBuilder, opts *search.SearchOpts) squirrel.SelectBuilder { + switch opts.SortBy { + case "subscribers": + return sq.OrderBy(fmt.Sprintf("subscribers %s", opts.General.SortOrder)) + default: + return sq.OrderBy(fmt.Sprintf("p1.id %s", opts.General.SortOrder)) + } +} + +func SetBoardSortType(sq squirrel.SelectBuilder, opts *search.SearchOpts) squirrel.SelectBuilder { + switch opts.SortBy { + case "pins": + return sq.OrderBy(fmt.Sprintf("pins_number %s", opts.General.SortOrder)) + default: + return sq.OrderBy(fmt.Sprintf("board.id %s", opts.General.SortOrder)) + } +} + +func SetPinSortType(sq squirrel.SelectBuilder, opts *search.SearchOpts) squirrel.SelectBuilder { + switch opts.SortBy { + case "likes": + return sq.OrderBy(fmt.Sprintf("likes %s", opts.General.SortOrder)) + default: + return sq.OrderBy(fmt.Sprintf("p.id %s", opts.General.SortOrder)) + } +} + +func (r *searchRepoPG) SelectBoardsForSearch(opts *search.SearchOpts) (string, []interface{}, error) { + SelectBoardsForSearch := r.sqlBuilder.Select( + "board.id", + "board.title", + "board.created_at", + "COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number", + "COALESCE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL AND pin.picture IS NOT NULL))[:3], ARRAY[]::TEXT[]) AS pins", + ).From( + "board", + ).LeftJoin( + "membership ON board.id = membership.board_id", + ).LeftJoin( + "pin ON membership.pin_id = pin.id", + ).Where( + squirrel.Eq{"board.deleted_at": nil}, + ).Where( + squirrel.ILike{"board.title": defaultSearchTemplate(opts.General.Template).GetTempl()}, + ).Where( + fmt.Sprintf("(board.public OR board.author = %d OR %d IN (SELECT user_id FROM contributor WHERE board_id = board.id))", opts.General.CurrUserID, opts.General.CurrUserID), + ).GroupBy( + "board.id", + "board.title", + "board.created_at", + ) + + SelectBoardsForSearch = SetBoardSortType(SelectBoardsForSearch, opts) + SelectBoardsForSearch = SelectBoardsForSearch.Limit(uint64(opts.General.Count)).Offset(uint64(opts.General.Offset)) + + return SelectBoardsForSearch.ToSql() +} + +func (r *searchRepoPG) SelectUsersForSearch(opts *search.SearchOpts) (string, []interface{}, error) { + SelectUsersForSearch := r.sqlBuilder.Select( + "p1.id", + "p1.username", + "p1.avatar", + "COUNT(s1.who) AS subscribers", + "s2.who IS NOT NULL AS is_subscribed", + ).From( + "profile p1", + ).LeftJoin( + "subscription_user s1 ON p1.id = s1.whom", + ).LeftJoin( + "profile p2 ON s1.who = p2.id", + ).LeftJoin( + fmt.Sprintf("subscription_user s2 ON p1.id = s2.whom AND s2.who = %d", opts.General.CurrUserID), + ).Where( + squirrel.And{ + squirrel.Eq{"p1.deleted_at": nil}, + squirrel.Eq{"p2.deleted_at": nil}, + squirrel.ILike{"p1.username": defaultSearchTemplate(opts.General.Template).GetTempl()}, + }, + ).GroupBy( + "p1.id", + "p1.username", + "p1.avatar", + "s2.who IS NOT NULL", + ) + + SelectUsersForSearch = SetUserSortType(SelectUsersForSearch, opts) + SelectUsersForSearch = SelectUsersForSearch.Limit(uint64(opts.General.Count)).Offset(uint64(opts.General.Offset)) + + return SelectUsersForSearch.ToSql() +} + +func (r *searchRepoPG) SelectPinsForSearch(opts *search.SearchOpts) (string, []interface{}, error) { + SelectPinsForSearch := r.sqlBuilder.Select( + "p.id", + "p.title", + "p.picture", + "COUNT(*) AS likes", + ).From( + "pin p", + ).LeftJoin( + "like_pin lp ON p.id = lp.pin_id", + ).Where( + squirrel.And{ + squirrel.Eq{"p.deleted_at": nil}, + squirrel.Or{ + squirrel.Eq{"p.public": true}, + squirrel.Eq{"p.author": opts.General.CurrUserID}, + }, + squirrel.ILike{"p.title": defaultSearchTemplate(opts.General.Template).GetTempl()}, + }, + ).GroupBy( + "p.id", + "p.title", + "p.picture", + ) + + SelectPinsForSearch = SetPinSortType(SelectPinsForSearch, opts) + SelectPinsForSearch = SelectPinsForSearch.Limit(uint64(opts.General.Count)).Offset(uint64(opts.General.Offset)) + + return SelectPinsForSearch.ToSql() +} diff --git a/internal/pkg/repository/search/postgres/queries.go b/internal/pkg/repository/search/postgres/queries.go new file mode 100644 index 0000000..2085e8e --- /dev/null +++ b/internal/pkg/repository/search/postgres/queries.go @@ -0,0 +1,51 @@ +package search + +// id, username, avatar, is_subcribed, subsCount + +const ( + SelectUsersByUsername = ` + SELECT + p1.id, p1.username, p1.avatar, COUNT(s1.who) AS subscribers, s2.who IS NOT NULL AS is_subscribed + FROM + profile p1 + LEFT JOIN + subscription_user s1 ON p1.id = s1.whom + LEFT JOIN + profile p2 ON s1.who = p2.id + LEFT JOIN + subscription_user s2 ON p1.id = s2.whom AND s2.who = $1 -- curr user + WHERE + p1.deleted_at IS NULL AND p2.deleted_at IS NULL AND p1.username ILIKE $2 -- AND p1.id < $3 --lastID and template + GROUP BY + p1.id, p1.username, p1.avatar, s2.who IS NOT NULL + ORDER BY + p1.id DESC + LIMIT + $3 --count + OFFSET + $4;` + SelectBoardsByTitle = ` + SELECT + board.id, + board.title, + board.created_at, + board.public, + COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number, + COALESCE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL AND pin.picture is not null))[:3], ARRAY[]::TEXT[]) AS pins + FROM + board + LEFT JOIN + membership ON board.id = membership.board_id + LEFT JOIN + pin ON membership.pin_id = pin.id + WHERE + board.title ILIKE $1 AND (board.public OR board.author = $2 OR $2 IN (SELECT user_id FROM contributor WHERE board_id = board.id)) + GROUP BY + board.id, board.title, board.created_at + ORDER BY + board.id DESC + LIMIT + $3 + OFFSET + $4;` +) diff --git a/internal/pkg/repository/search/postgres/repo.go b/internal/pkg/repository/search/postgres/repo.go new file mode 100644 index 0000000..bc4a7e5 --- /dev/null +++ b/internal/pkg/repository/search/postgres/repo.go @@ -0,0 +1,129 @@ +package search + +import ( + "context" + "errors" + "fmt" + + "github.com/Masterminds/squirrel" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" + errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" + searchRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/search" + "github.com/jackc/pgx/v5/pgconn" +) + +type defaultSearchTemplate string + +func (t defaultSearchTemplate) GetTempl() string { + return fmt.Sprintf("%%%s%%", t) +} + +type searchRepoPG struct { + db pgtype.PgxPoolIface + sqlBuilder squirrel.StatementBuilderType +} + +func NewSearchRepoPG(db pgtype.PgxPoolIface) *searchRepoPG { + return &searchRepoPG{db, squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} +} + +func convertErrorPostgres(err error) error { + + switch err { + case context.DeadlineExceeded: + return &errPkg.ErrTimeoutExceeded{} + } + + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + switch pgErr.SQLState() { + } + } + + return &errPkg.InternalError{Message: err.Error(), Layer: string(errPkg.Repo)} +} + +func (r *searchRepoPG) GetFilteredUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) { + sqlRow, args, err := r.SelectUsersForSearch(opts) + if err != nil { + return nil, convertErrorPostgres(err) + } + rows, err := r.db.Query(ctx, sqlRow, args...) + if err != nil { + return nil, convertErrorPostgres(err) + } + defer rows.Close() + + users := make([]search.UserForSearch, 0) + for rows.Next() { + user := search.UserForSearch{} + if err := rows.Scan(&user.ID, &user.Username, &user.Avatar, &user.SubsCount, &user.HasSubscribeFromCurUser); err != nil { + return nil, convertErrorPostgres(err) + } + users = append(users, user) + } + + if len(users) == 0 && opts.General.Offset == 0 { + return nil, &searchRepo.ErrNoUsers{} + } + + return users, nil +} + +func (r *searchRepoPG) GetFilteredBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) { + + sqlRow, args, err := r.SelectBoardsForSearch(opts) + if err != nil { + return nil, convertErrorPostgres(err) + } + rows, err := r.db.Query(ctx, sqlRow, args...) + if err != nil { + return nil, convertErrorPostgres(err) + } + defer rows.Close() + + boards := make([]search.BoardForSearch, 0) + for rows.Next() { + board := search.BoardForSearch{} + if err := rows.Scan(&board.BoardHeader.ID, &board.BoardHeader.Title, &board.BoardHeader.CreatedAt, &board.PinsNumber, &board.PreviewPins); err != nil { + return nil, convertErrorPostgres(err) + } + boards = append(boards, board) + } + + if len(boards) == 0 && opts.General.Offset == 0 { + return nil, &searchRepo.ErrNoBoards{} + } + + return boards, nil +} + +func (r *searchRepoPG) GetFilteredPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) { + + sqlRow, args, err := r.SelectPinsForSearch(opts) + if err != nil { + return nil, convertErrorPostgres(err) + } + rows, err := r.db.Query(ctx, sqlRow, args...) + if err != nil { + return nil, convertErrorPostgres(err) + } + defer rows.Close() + + pins := make([]search.PinForSearch, 0) + for rows.Next() { + pin := search.PinForSearch{} + if err := rows.Scan(&pin.ID, &pin.Title, &pin.Picture, &pin.Likes); err != nil { + return nil, convertErrorPostgres(err) + } + pins = append(pins, pin) + } + + if len(pins) == 0 && opts.General.Offset == 0 { + return nil, &searchRepo.ErrNoPins{} + } + + return pins, nil + +} diff --git a/internal/pkg/repository/search/repo.go b/internal/pkg/repository/search/repo.go new file mode 100644 index 0000000..98ca7bf --- /dev/null +++ b/internal/pkg/repository/search/repo.go @@ -0,0 +1,13 @@ +package search + +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" +) + +type Repository interface { + GetFilteredUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) + GetFilteredPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) + GetFilteredBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) +} diff --git a/internal/pkg/usecase/search/usecase.go b/internal/pkg/usecase/search/usecase.go new file mode 100644 index 0000000..6c11b1e --- /dev/null +++ b/internal/pkg/usecase/search/usecase.go @@ -0,0 +1,67 @@ +package search + +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" + sRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/search" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/microcosm-cc/bluemonday" +) + +//ctx context.Context, template string, currUserID, lastID, count int + +type Usecase interface { + GetUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) + GetBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) + GetPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) +} + +type searchUsecase struct { + log *logger.Logger + searchRepo sRepo.Repository + sanitizer *bluemonday.Policy +} + +func New(log *logger.Logger, searchRepo sRepo.Repository, sanitizer *bluemonday.Policy) Usecase { + return &searchUsecase{log: log, searchRepo: searchRepo, sanitizer: sanitizer} +} + +func (u *searchUsecase) GetUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) { + users, err := u.searchRepo.GetFilteredUsers(ctx, opts) + if err != nil { + return nil, err + } + + for id := range users { + users[id].Sanitize(u.sanitizer) + } + + return users, nil +} + +func (u *searchUsecase) GetBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) { + boards, err := u.searchRepo.GetFilteredBoards(ctx, opts) + if err != nil { + return nil, err + } + + for id := range boards { + boards[id].Sanitize(u.sanitizer) + } + + return boards, nil +} + +func (u *searchUsecase) GetPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) { + pins, err := u.searchRepo.GetFilteredPins(ctx, opts) + if err != nil { + return nil, err + } + + for id := range pins { + pins[id].Sanitize(u.sanitizer) + } + + return pins, nil +} diff --git a/internal/pkg/usecase/subscription/get.go b/internal/pkg/usecase/subscription/get.go index 1e86ad1..d421c6f 100644 --- a/internal/pkg/usecase/subscription/get.go +++ b/internal/pkg/usecase/subscription/get.go @@ -13,13 +13,25 @@ func (u *subscriptionUsecase) GetSubscriptionInfoForUser(ctx context.Context, su } currUserID, _ := ctx.Value(auth.KeyCurrentUserID).(int) - + var ( + users []userEntity.SubscriptionUser + err error + ) switch subOpts.Filter { case "subscriptions": - return u.subRepo.GetUserSubscriptions(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + users, err = u.subRepo.GetUserSubscriptions(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) case "subscribers": - return u.subRepo.GetUserSubscribers(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) + users, err = u.subRepo.GetUserSubscribers(ctx, subOpts.UserID, subOpts.Count, subOpts.LastID, currUserID) default: return nil, &ErrInvalidFilter{subOpts.Filter} } + if err != nil { + return nil, err + } + + for id := range users { + users[id].Sanitize(u.sanitizer) + } + + return users, nil } diff --git a/internal/pkg/usecase/subscription/usecase.go b/internal/pkg/usecase/subscription/usecase.go index f308b49..4a88d83 100644 --- a/internal/pkg/usecase/subscription/usecase.go +++ b/internal/pkg/usecase/subscription/usecase.go @@ -7,6 +7,7 @@ import ( subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription" uRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/microcosm-cc/bluemonday" ) type Usecase interface { @@ -16,11 +17,12 @@ type Usecase interface { } type subscriptionUsecase struct { - subRepo subRepo.Repository - userRepo uRepo.Repository - log *logger.Logger + subRepo subRepo.Repository + userRepo uRepo.Repository + log *logger.Logger + sanitizer *bluemonday.Policy } -func New(log *logger.Logger, subRepo subRepo.Repository, uRepo uRepo.Repository) Usecase { - return &subscriptionUsecase{subRepo: subRepo, userRepo: uRepo, log: log} +func New(log *logger.Logger, subRepo subRepo.Repository, uRepo uRepo.Repository, sanitizer *bluemonday.Policy) Usecase { + return &subscriptionUsecase{subRepo: subRepo, userRepo: uRepo, log: log, sanitizer: sanitizer} } From 6d9f6a8e093a896a3b9be9bdd853f76e43b39ae8 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 26 Nov 2023 12:11:23 +0300 Subject: [PATCH 207/266] TP-34d_search: changed pin likes aggregation --- internal/pkg/repository/search/postgres/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/repository/search/postgres/builder.go b/internal/pkg/repository/search/postgres/builder.go index a025850..989c22c 100644 --- a/internal/pkg/repository/search/postgres/builder.go +++ b/internal/pkg/repository/search/postgres/builder.go @@ -104,7 +104,7 @@ func (r *searchRepoPG) SelectPinsForSearch(opts *search.SearchOpts) (string, []i "p.id", "p.title", "p.picture", - "COUNT(*) AS likes", + "COUNT(pin_id) AS likes", ).From( "pin p", ).LeftJoin( From b7492daa90468efe066505728ee3af268c95ba03 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 26 Nov 2023 18:41:38 +0300 Subject: [PATCH 208/266] TP-4b9 update: auth --- Makefile | 5 +- cmd/app/config.go | 2 +- cmd/auth/config.go | 8 ++ cmd/auth/main.go | 58 +-------------- internal/app/app.go | 33 ++------- internal/app/auth/auth.go | 73 +++++++++++++++++++ internal/app/auth/config.go | 6 ++ internal/app/config.go | 2 +- .../auth/delivery/grpc/server.go | 22 ++++-- .../microservices/auth/usecase/usecase.go | 1 - internal/pkg/delivery/http/v1/handler.go | 4 - internal/pkg/repository/auth/repo.go | 73 ------------------- internal/pkg/usecase/auth/usecase.go | 55 ++++++++++++-- 13 files changed, 162 insertions(+), 180 deletions(-) create mode 100644 cmd/auth/config.go create mode 100644 internal/app/auth/auth.go create mode 100644 internal/app/auth/config.go delete mode 100644 internal/microservices/auth/usecase/usecase.go delete mode 100644 internal/pkg/repository/auth/repo.go diff --git a/Makefile b/Makefile index 8435cbf..12a2f08 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover +.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover build_auth ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out @@ -8,6 +8,9 @@ CURRCOVER=github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/ build: go build -o bin/app cmd/app/*.go +build_auth: + go build -o bin/auth cmd/auth/*.go + run: build ./bin/app diff --git a/cmd/app/config.go b/cmd/app/config.go index a59b192..0cb9a84 100644 --- a/cmd/app/config.go +++ b/cmd/app/config.go @@ -4,5 +4,5 @@ import "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" var configFiles = app.ConfigFiles{ ServerConfigFile: "configs/config.yml", - RedisConfigFile: "redis.conf", + AddrAuthServer: "localhost:8085", } diff --git a/cmd/auth/config.go b/cmd/auth/config.go new file mode 100644 index 0000000..bf1142d --- /dev/null +++ b/cmd/auth/config.go @@ -0,0 +1,8 @@ +package main + +import "github.com/go-park-mail-ru/2023_2_OND_team/internal/app/auth" + +var configAuth = auth.Config{ + Addr: "localhost:8085", + RedisFileConfig: "redis.conf", +} diff --git a/cmd/auth/main.go b/cmd/auth/main.go index 799ae03..91b055b 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -3,22 +3,9 @@ package main import ( "context" "fmt" - "net" - "time" - "google.golang.org/grpc" - - "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" - authMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/auth/delivery/grpc" - imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" - sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" - userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app/auth" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/joho/godotenv" ) func main() { @@ -27,46 +14,7 @@ func main() { fmt.Println(err) return } - l, err := net.Listen("tcp", "localhost:8085") - if err != nil { - log.Error(err.Error()) - return - } - - s := grpc.NewServer() - godotenv.Load() - - ctx := context.Background() - - pool, err := app.NewPoolPG(ctx) - if err != nil { - log.Error(err.Error()) - return - } - defer pool.Close() - - ctx, cancelCtxRedis := context.WithTimeout(ctx, time.Second) - defer cancelCtxRedis() + defer log.Sync() - redisCfg, err := app.NewConfig("redis.conf") - if err != nil { - log.Error(err.Error()) - return - } - - redisCl, err := app.NewRedisClient(ctx, redisCfg) - if err != nil { - log.Error(err.Error()) - return - } - defer redisCl.Close() - - sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) - imgCase := image.New(log, imgRepo.NewImageRepoFS("upload/")) - u := user.New(log, imgCase, userRepo.NewUserRepoPG(pool)) - auth.RegisterAuthServer(s, authMS.New(log, sm, u)) - if err = s.Serve(l); err != nil { - log.Error(err.Error()) - return - } + auth.Run(context.Background(), log, configAuth) } diff --git a/internal/app/app.go b/internal/app/app.go index a7d8a82..d66685d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -14,12 +14,10 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" - authRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/auth" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" - sessionRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription/postgres" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" @@ -27,23 +25,19 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -var ( - timeoutForConnPG = 5 * time.Second - timeoutForConnRedis = 5 * time.Second -) +var _timeoutForConnPG = 5 * time.Second const uploadFiles = "upload/" func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { godotenv.Load() - ctx, cancelCtxPG := context.WithTimeout(ctx, timeoutForConnPG) + ctx, cancelCtxPG := context.WithTimeout(ctx, _timeoutForConnPG) defer cancelCtxPG() pool, err := NewPoolPG(ctx) @@ -53,32 +47,16 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer pool.Close() - ctx, cancelCtxRedis := context.WithTimeout(ctx, timeoutForConnRedis) - defer cancelCtxRedis() - - redisCfg, err := NewConfig(cfg.RedisConfigFile) - if err != nil { - log.Error(err.Error()) - return - } - - redisCl, err := NewRedisClient(ctx, redisCfg) - if err != nil { - log.Error(err.Error()) - return - } - defer redisCl.Close() - - sm := session.New(log, sessionRepo.NewSessionRepo(redisCl)) imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) messageCase := message.New(mesRepo.NewMessageRepo(pool)) - conn, err := grpc.Dial("localhost:8085", grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.Dial(cfg.AddrAuthServer, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(err.Error()) return } - ac := auth.New(authRepo.NewAuthRepo(authProto.NewAuthClient(conn))) + defer conn.Close() + ac := auth.New(authProto.NewAuthClient(conn)) handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ AuhtCase: ac, @@ -87,7 +65,6 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool)), MessageCase: messageCase, - SM: sm, }) wsHandler := deliveryWS.New(log, messageCase, diff --git a/internal/app/auth/auth.go b/internal/app/auth/auth.go new file mode 100644 index 0000000..dab46f0 --- /dev/null +++ b/internal/app/auth/auth.go @@ -0,0 +1,73 @@ +package auth + +import ( + "context" + "net" + "time" + + "github.com/joho/godotenv" + "google.golang.org/grpc" + + "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" + authMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/auth/delivery/grpc" + sessRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" + userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +var ( + _timeoutForConnPG = 5 * time.Second + _timeoutForConnRedis = 5 * time.Second +) + +func Run(ctx context.Context, log *logger.Logger, cfg Config) { + godotenv.Load() + + l, err := net.Listen("tcp", cfg.Addr) + if err != nil { + log.Error(err.Error()) + return + } + defer l.Close() + + ctxPG, cancelCtxPG := context.WithTimeout(ctx, _timeoutForConnPG) + defer cancelCtxPG() + + pool, err := app.NewPoolPG(ctxPG) + if err != nil { + log.Error(err.Error()) + return + } + defer pool.Close() + + ctxRedis, cancelCtxRedis := context.WithTimeout(ctx, _timeoutForConnRedis) + defer cancelCtxRedis() + + redisCfg, err := app.NewConfig(cfg.RedisFileConfig) + if err != nil { + log.Error(err.Error()) + return + } + + redisCl, err := app.NewRedisClient(ctxRedis, redisCfg) + if err != nil { + log.Error(err.Error()) + return + } + defer redisCl.Close() + + sm := session.New(log, sessRepo.NewSessionRepo(redisCl)) + u := user.New(log, nil, userRepo.NewUserRepoPG(pool)) + + s := grpc.NewServer() + auth.RegisterAuthServer(s, authMS.New(log, sm, u)) + + log.Info("service auht start", logger.F{"addr", cfg.Addr}) + if err = s.Serve(l); err != nil { + log.Error(err.Error()) + return + } +} diff --git a/internal/app/auth/config.go b/internal/app/auth/config.go new file mode 100644 index 0000000..e31181b --- /dev/null +++ b/internal/app/auth/config.go @@ -0,0 +1,6 @@ +package auth + +type Config struct { + Addr string + RedisFileConfig string +} diff --git a/internal/app/config.go b/internal/app/config.go index e667344..7fa6c87 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -8,7 +8,7 @@ import ( type ConfigFiles struct { ServerConfigFile string - RedisConfigFile string + AddrAuthServer string } type redisConfig struct { diff --git a/internal/microservices/auth/delivery/grpc/server.go b/internal/microservices/auth/delivery/grpc/server.go index 1251882..55bcb6d 100644 --- a/internal/microservices/auth/delivery/grpc/server.go +++ b/internal/microservices/auth/delivery/grpc/server.go @@ -15,15 +15,20 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +type Usecase interface { + Register(ctx context.Context, user *user.User) error + Authentication(ctx context.Context, credentials userUsecase.UserCredentials) (*user.User, error) +} + type AuthServer struct { authProto.UnimplementedAuthServer log *logger.Logger sm session.SessionManager - userCase userUsecase.Usecase + userCase Usecase } -func New(log *logger.Logger, sm session.SessionManager, userCase userUsecase.Usecase) AuthServer { +func New(log *logger.Logger, sm session.SessionManager, userCase Usecase) AuthServer { return AuthServer{ UnimplementedAuthServer: authProto.UnimplementedAuthServer{}, log: log, @@ -54,13 +59,13 @@ func (as AuthServer) Login(ctx context.Context, cred *authProto.Credentials) (*a }) if err != nil { as.log.Error(err.Error()) - return nil, status.Error(codes.PermissionDenied, "") + return nil, status.Error(codes.Unauthenticated, "failed authentication") } session, err := as.sm.CreateNewSessionForUser(ctx, user.ID) if err != nil { as.log.Error(err.Error()) - return nil, status.Error(codes.Internal, "") + return nil, status.Error(codes.Internal, "failed to create a session for the user") } return &authProto.Session{ @@ -80,9 +85,10 @@ func (as AuthServer) Logout(ctx context.Context, sess *authProto.Session) (*empt } func (as AuthServer) GetUserID(ctx context.Context, sess *authProto.Session) (*authProto.UserID, error) { - if userID, err := as.sm.GetUserIDBySessionKey(ctx, sess.Key); err != nil { - return nil, status.Error(codes.Internal, "") - } else { - return &authProto.UserID{Id: int64(userID)}, nil + userID, err := as.sm.GetUserIDBySessionKey(ctx, sess.Key) + if err != nil { + as.log.Error(err.Error()) + return nil, status.Error(codes.NotFound, "session not found") } + return &authProto.UserID{Id: int64(userID)}, nil } diff --git a/internal/microservices/auth/usecase/usecase.go b/internal/microservices/auth/usecase/usecase.go deleted file mode 100644 index aed2454..0000000 --- a/internal/microservices/auth/usecase/usecase.go +++ /dev/null @@ -1 +0,0 @@ -package usecase diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 2a54b1a..08a7c0a 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -5,7 +5,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -19,7 +18,6 @@ type HandlerHTTP struct { boardCase board.Usecase subCase subscription.Usecase messageCase message.Usecase - sm session.SessionManager } func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { @@ -31,7 +29,6 @@ func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { boardCase: hub.BoardCase, subCase: hub.SubscriptionCase, messageCase: hub.MessageCase, - sm: hub.SM, } } @@ -42,5 +39,4 @@ type UsecaseHub struct { BoardCase board.Usecase SubscriptionCase subscription.Usecase MessageCase message.Usecase - SM session.SessionManager } diff --git a/internal/pkg/repository/auth/repo.go b/internal/pkg/repository/auth/repo.go deleted file mode 100644 index 98581bd..0000000 --- a/internal/pkg/repository/auth/repo.go +++ /dev/null @@ -1,73 +0,0 @@ -package auth - -import ( - "context" - "fmt" - - authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" - entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type Repository interface { - Register(ctx context.Context, user *entity.User) error - Logout(ctx context.Context, sess *session.Session) error - Login(ctx context.Context, username, password string) (*session.Session, error) - GetUserID(ctx context.Context, sess *session.Session) (int, error) -} - -type authRepo struct { - client authProto.AuthClient -} - -func NewAuthRepo(c authProto.AuthClient) *authRepo { - return &authRepo{c} -} - -func (r *authRepo) Register(ctx context.Context, user *entity.User) error { - _, err := r.client.Register(ctx, &authProto.RegisterData{ - Cred: &authProto.Credentials{ - Username: user.Username, - Password: user.Password, - }, - Email: user.Email, - }) - return err -} - -func (r *authRepo) Logout(ctx context.Context, sess *session.Session) error { - _, err := r.client.Logout(ctx, &authProto.Session{ - Key: sess.Key, - UserID: int64(sess.UserID), - Expire: timestamppb.New(sess.Expire), - }) - return err -} - -func (r *authRepo) Login(ctx context.Context, username, password string) (*session.Session, error) { - sess, err := r.client.Login(ctx, &authProto.Credentials{ - Username: username, - Password: password, - }) - if err != nil { - return nil, fmt.Errorf("login: %w", err) - } - return &session.Session{ - Key: sess.Key, - UserID: int(sess.UserID), - Expire: sess.Expire.AsTime(), - }, nil -} - -func (r *authRepo) GetUserID(ctx context.Context, sess *session.Session) (int, error) { - userID, err := r.client.GetUserID(ctx, &authProto.Session{ - Key: sess.Key, - UserID: int64(sess.UserID), - Expire: timestamppb.New(sess.Expire), - }) - if err != nil { - return 0, fmt.Errorf("get user id: %w", err) - } - return int(userID.Id), nil -} diff --git a/internal/pkg/usecase/auth/usecase.go b/internal/pkg/usecase/auth/usecase.go index 85543f4..79baa72 100644 --- a/internal/pkg/usecase/auth/usecase.go +++ b/internal/pkg/usecase/auth/usecase.go @@ -2,10 +2,12 @@ package auth import ( "context" + "fmt" + authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" - authRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/auth" + "google.golang.org/protobuf/types/known/timestamppb" ) type Usecase interface { @@ -16,25 +18,62 @@ type Usecase interface { } type authCase struct { - repo authRepo.Repository + client authProto.AuthClient } -func New(repo authRepo.Repository) *authCase { - return &authCase{repo} +func New(client authProto.AuthClient) *authCase { + return &authCase{client} } func (ac *authCase) Register(ctx context.Context, user *entity.User) error { - return ac.repo.Register(ctx, user) + _, err := ac.client.Register(ctx, &authProto.RegisterData{ + Cred: &authProto.Credentials{ + Username: user.Username, + Password: user.Password, + }, + Email: user.Email, + }) + if err != nil { + return fmt.Errorf("register: %w", err) + } + return nil } func (ac *authCase) Logout(ctx context.Context, sess *session.Session) error { - return ac.repo.Logout(ctx, sess) + _, err := ac.client.Logout(ctx, &authProto.Session{ + Key: sess.Key, + UserID: int64(sess.UserID), + Expire: timestamppb.New(sess.Expire), + }) + if err != nil { + return fmt.Errorf("logout: %w", err) + } + return nil } func (ac *authCase) Login(ctx context.Context, username, password string) (*session.Session, error) { - return ac.repo.Login(ctx, username, password) + sess, err := ac.client.Login(ctx, &authProto.Credentials{ + Username: username, + Password: password, + }) + if err != nil { + return nil, fmt.Errorf("login: %w", err) + } + return &session.Session{ + Key: sess.Key, + UserID: int(sess.UserID), + Expire: sess.Expire.AsTime(), + }, nil } func (ac *authCase) GetUserIDBySession(ctx context.Context, sess *session.Session) (int, error) { - return ac.repo.GetUserID(ctx, sess) + userID, err := ac.client.GetUserID(ctx, &authProto.Session{ + Key: sess.Key, + UserID: int64(sess.UserID), + Expire: timestamppb.New(sess.Expire), + }) + if err != nil { + return 0, fmt.Errorf("get user id by session: %w", err) + } + return int(userID.Id), nil } From 3220a4c56ae8e2d3a54ff774f74b4aa9904f9e52 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Mon, 27 Nov 2023 20:12:57 +0300 Subject: [PATCH 209/266] TP-0a2 add: metrics --- configs/prometheus.yml | 12 +++ deployments/docker-compose.yml | 29 +++++++ go.mod | 14 ++++ go.sum | 38 +++++++++ internal/api/server/router/router.go | 6 +- internal/app/app.go | 10 ++- internal/pkg/metrics/metrics.go | 81 +++++++++++++++++++ internal/pkg/middleware/monitoring/metrics.go | 43 ++++++++++ internal/pkg/middleware/wrap_response.go | 8 ++ 9 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 configs/prometheus.yml create mode 100644 internal/pkg/metrics/metrics.go create mode 100644 internal/pkg/middleware/monitoring/metrics.go diff --git a/configs/prometheus.yml b/configs/prometheus.yml new file mode 100644 index 0000000..c12dc59 --- /dev/null +++ b/configs/prometheus.yml @@ -0,0 +1,12 @@ +global: + scrape_interval: 10s + evaluation_interval: 10s + +scrape_configs: + - job_name: 'api' + static_configs: + - targets: ['host.docker.internal:8080'] + + - job_name: 'auth' + static_configs: + - targets: ['host.docker.internal:8085'] diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 618cf6f..a0946c7 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -44,9 +44,38 @@ services: - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 depends_on: - zookeeper + + prometheus: + image: prom/prometheus:latest + container_name: pinspirePrometheus + ports: + - "9090:9090" + volumes: + - "../configs/prometheus.yml:/etc/prometheus/prometheus.yml" + + grafana: + image: grafana/grafana:latest + container_name: pinspireGrafana + ports: + - 3000:3000 + volumes: + - 'grafana_storage:/var/lib/grafana' + + node_exporter: + image: quay.io/prometheus/node-exporter:latest + container_name: pinspireNodeExporter + command: + - '--path.rootfs=/host' + network_mode: host + pid: host + restart: unless-stopped + volumes: + - '/:/host:ro,rslave' + volumes: zookeeper_data: driver: local kafka_data: driver: local + grafana_storage: {} diff --git a/go.mod b/go.mod index a7103f7..f8b2c99 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,10 @@ require ( github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/joho/godotenv v1.5.1 + github.com/labstack/echo/v4 v4.11.3 github.com/microcosm-cc/bluemonday v1.0.26 github.com/pashagolub/pgxmock/v2 v2.12.0 + github.com/prometheus/client_golang v1.17.0 github.com/proullon/ramsql v0.0.1 github.com/redis/go-redis/v9 v9.2.1 github.com/rs/cors v1.10.1 @@ -36,6 +38,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/benoitkugler/textprocessing v0.0.3 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -49,6 +52,7 @@ require ( github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/css v1.0.0 // indirect @@ -65,22 +69,32 @@ require ( github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/labstack/gommon v0.4.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tdewolff/minify/v2 v2.20.5 // indirect github.com/tdewolff/parse/v2 v2.7.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index ee9e3fb..155b082 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7 github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/benoitkugler/textprocessing v0.0.3 h1:Q2X+Z6vxuW5Bxn1R9RaNt0qcprBfpc2hEUDeTlz90Ng= github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= @@ -68,6 +70,8 @@ github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d h1:HrdwTlHVMdi9nOW7ZnYiLmIT1hJHvipIwM0aX3rKn8I= github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -129,6 +133,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= +github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -139,6 +147,15 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -149,6 +166,14 @@ github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/proullon/ramsql v0.0.1 h1:tI7qN48Oj1LTmgdo4aWlvI9z45a4QlWaXlmdJ+IIfbU= github.com/proullon/ramsql v0.0.1/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= @@ -188,6 +213,11 @@ github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9 github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -243,9 +273,14 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -259,6 +294,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -296,6 +333,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 08950ac..40e8261 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -13,6 +13,7 @@ import ( deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" mw "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/monitoring" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/security" authCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -28,7 +29,7 @@ func New() Router { return Router{chi.NewMux()} } -func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deliveryWS.HandlerWebSocket, ac authCase.Usecase, log *logger.Logger) { +func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deliveryWS.HandlerWebSocket, ac authCase.Usecase, metrics monitoring.Metrics, log *logger.Logger) { cfgCSRF := security.DefaultCSRFConfig() cfgCSRF.PathToGet = "/api/v1/csrf" @@ -41,7 +42,8 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli ExposedHeaders: []string{cfgCSRF.HeaderSet}, }) - r.Mux.Use(mw.SetRequestTimeout(requestTimeout), mw.RequestID(log), mw.Logger(log), c.Handler, + r.Mux.Use(mw.SetRequestTimeout(requestTimeout), mw.RequestID(log), mw.Logger(log), + monitoring.Monitoring("/metrics", metrics), c.Handler, security.CSRF(cfgCSRF), mw.SetResponseHeaders(map[string]string{ "Content-Type": "application/json", diff --git a/internal/app/app.go b/internal/app/app.go index d66685d..ce8632a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -14,6 +14,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" @@ -37,6 +38,13 @@ const uploadFiles = "upload/" func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { godotenv.Load() + metrics := metrics.New("pinspire") + err := metrics.Registry() + if err != nil { + log.Error(err.Error()) + return + } + ctx, cancelCtxPG := context.WithTimeout(ctx, _timeoutForConnPG) defer cancelCtxPG() @@ -77,7 +85,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } server := server.New(log, cfgServ) router := router.New() - router.RegisterRoute(handler, wsHandler, ac, log) + router.RegisterRoute(handler, wsHandler, ac, metrics, log) if err := server.Run(router.Mux); err != nil { log.Error(err.Error()) diff --git a/internal/pkg/metrics/metrics.go b/internal/pkg/metrics/metrics.go new file mode 100644 index 0000000..5165af7 --- /dev/null +++ b/internal/pkg/metrics/metrics.go @@ -0,0 +1,81 @@ +package metrics + +import ( + "fmt" + "time" + + "github.com/prometheus/client_golang/prometheus" +) + +type metrics struct { + prefix string + totalHits prometheus.Counter + hitsDetail *prometheus.CounterVec + timeMeasurement *prometheus.HistogramVec +} + +func (m metrics) AddRequest(method, path string, statusResponse int, executed time.Duration) { + var labelStatus string + switch { + case statusResponse < 200: + labelStatus = "100" + case statusResponse < 300: + labelStatus = "200" + case statusResponse < 400: + labelStatus = "300" + case statusResponse < 500: + labelStatus = "400" + default: + labelStatus = "500" + } + + m.totalHits.Inc() + m.hitsDetail.WithLabelValues(method, path, labelStatus).Inc() + m.timeMeasurement.WithLabelValues(method, path, labelStatus).Observe(float64(executed.Milliseconds())) +} + +func New(prefix string) metrics { + return metrics{ + prefix: prefix, + totalHits: prometheus.NewCounter(prometheus.CounterOpts{ + Name: prefix + "_total_hits", + Help: "number of all requests", + }), + hitsDetail: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: prefix + "_hits_detail", + Help: "the number of requests indicating its method, path, and response status", + }, []string{"method", "path", "status"}), + timeMeasurement: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: prefix + "_request_time_execution", + Help: "time of request execution with indication of its method, path and response status", + Buckets: []float64{10, 100, 500, 1000, 5000}, + }, []string{"method", "path", "status"}), + } +} + +func (m metrics) Registry() (err error) { + defer func() { + if err != nil { + prometheus.Unregister(m.totalHits) + prometheus.Unregister(m.hitsDetail) + prometheus.Unregister(m.timeMeasurement) + } + }() + + err = prometheus.Register(m.totalHits) + if err != nil { + return fmt.Errorf("registry metric total hits: %w", err) + } + + err = prometheus.Register(m.hitsDetail) + if err != nil { + return fmt.Errorf("registry metric hits detail: %w", err) + } + + err = prometheus.Register(m.timeMeasurement) + if err != nil { + return fmt.Errorf("registry metric time measurement: %w", err) + } + + return nil +} diff --git a/internal/pkg/middleware/monitoring/metrics.go b/internal/pkg/middleware/monitoring/metrics.go new file mode 100644 index 0000000..b8645f7 --- /dev/null +++ b/internal/pkg/middleware/monitoring/metrics.go @@ -0,0 +1,43 @@ +package monitoring + +import ( + "net/http" + "time" + + mw "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type Metrics interface { + AddRequest(method, path string, statusResponse int, executed time.Duration) +} + +type StatusReceiver interface { + StatusCode() int +} + +func Monitoring(pathExporter string, metrics Metrics) func(http.Handler) http.Handler { + instrumentMetricHandler := promhttp.Handler() + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == pathExporter { + instrumentMetricHandler.ServeHTTP(w, r) + return + } + + stat, ok := w.(StatusReceiver) + if !ok { + wrapResponse := mw.NewWrapResponseWriter(w) + w = wrapResponse + stat = wrapResponse + } + + defer func(method, path string, t time.Time) { + metrics.AddRequest(method, path, stat.StatusCode(), time.Since(t)) + }(r.Method, r.URL.Path, time.Now()) + + next.ServeHTTP(w, r) + }) + } +} diff --git a/internal/pkg/middleware/wrap_response.go b/internal/pkg/middleware/wrap_response.go index 3b44b1e..cea28c8 100644 --- a/internal/pkg/middleware/wrap_response.go +++ b/internal/pkg/middleware/wrap_response.go @@ -51,3 +51,11 @@ func (w *wrapResponseWriter) Flush() { flusher.Flush() } } + +func (w *wrapResponseWriter) StatusCode() int { + return w.statusCode +} + +func (w *wrapResponseWriter) Written() int { + return w.written +} From 8ceb550477f6701ef78b2c0e2576ae860c810e98 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 28 Nov 2023 12:40:15 +0300 Subject: [PATCH 210/266] TP-34d_search: add DeletePinFromBoard handler --- internal/api/server/router/router.go | 1 + internal/pkg/delivery/http/v1/board.go | 73 +++++++++++++++++++ internal/pkg/delivery/http/v1/board_errors.go | 1 + .../pkg/repository/board/postgres/queries.go | 1 + .../pkg/repository/board/postgres/repo.go | 25 +++++++ internal/pkg/repository/board/repo.go | 1 + internal/pkg/usecase/board/delete.go | 28 +++++++ internal/pkg/usecase/board/errors.go | 1 + internal/pkg/usecase/board/usecase.go | 1 + 9 files changed, 132 insertions(+) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 95bb286..39835a8 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -109,6 +109,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli }) r.With(auth.RequireAuth).Group(func(r chi.Router) { r.Post("/add/pins/{boardID:\\d+}", handler.AddPinsToBoard) + r.Delete("/delete/pin/{boardID:\\d+}", handler.DeletePinFromBoard) r.Post("/create", handler.CreateNewBoard) r.Put("/update/{boardID:\\d+}", handler.UpdateBoardInfo) r.Delete("/delete/{boardID:\\d+}", handler.DeleteBoard) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index f492ac4..317e1c4 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -34,6 +34,10 @@ type CertainBoard struct { Tags []string `json:"tags" example:"['love', 'green']"` } +type DeletePinFromBoard struct { + PinID int `json:"pin_id" example:"22"` +} + func ToCertainBoardFromService(board entity.BoardWithContent) CertainBoard { return CertainBoard{ ID: board.BoardInfo.ID, @@ -351,3 +355,72 @@ func (h *HandlerHTTP) AddPinsToBoard(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) } } + +func (h *HandlerHTTP) DeletePinFromBoard(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + + boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) + if err != nil { + logger.Info("delete pin from board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(ErrBadUrlParam) + responseError(w, code, message) + return + } + + if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { + code, message := getErrCodeMessage(ErrBadContentType) + responseError(w, code, message) + return + } + + delPinFromBoard := DeletePinFromBoard{} + err = json.NewDecoder(r.Body).Decode(&delPinFromBoard) + if err != nil { + code, message := getErrCodeMessage(ErrBadBody) + responseError(w, code, message) + return + } + defer r.Body.Close() + + err = h.boardCase.DeletePinFromBoard(r.Context(), int(boardID), delPinFromBoard.PinID) + if err != nil { + logger.Info("delete pin from board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(err) + responseError(w, code, message) + return + } + + err = responseOk(http.StatusOK, w, "deleted pin from board successfully", nil) + if err != nil { + logger.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(ErrInternalError.Error())) + } +} + +/* +logger := h.getRequestLogger(r) + + boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) + if err != nil { + logger.Info("update certain board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(ErrBadUrlParam) + responseError(w, code, message) + return + } + + err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) + if err != nil { + logger.Info("update certain board", log.F{"message", err.Error()}) + code, message := getErrCodeMessage(err) + responseError(w, code, message) + return + } + + err = responseOk(http.StatusOK, w, "deleted board successfully", nil) + if err != nil { + logger.Error(err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(ErrInternalError.Error())) + } +*/ diff --git a/internal/pkg/delivery/http/v1/board_errors.go b/internal/pkg/delivery/http/v1/board_errors.go index ef0eb23..ab31833 100644 --- a/internal/pkg/delivery/http/v1/board_errors.go +++ b/internal/pkg/delivery/http/v1/board_errors.go @@ -25,6 +25,7 @@ var ( bCase.ErrInvalidUsername: "non_existingUser", bCase.ErrNoSuchBoard: "no_board", bCase.ErrNoAccess: "no_access", + bCase.ErrNoPinOnBoard: "no_pin", } ) diff --git a/internal/pkg/repository/board/postgres/queries.go b/internal/pkg/repository/board/postgres/queries.go index d86f049..0dfc71b 100644 --- a/internal/pkg/repository/board/postgres/queries.go +++ b/internal/pkg/repository/board/postgres/queries.go @@ -9,6 +9,7 @@ const ( GetContributorBoardsIDs = "SELECT board_id FROM contributor WHERE user_id = $1;" DeleteBoardByIdQuery = "UPDATE board SET deleted_at = $1 WHERE id = $2 AND deleted_at IS NULL;" DeleteCurrentBoardTags = "DELETE FROM board_tag WHERE board_id = $1;" + DeletePinFromBoard = "DELETE FROM membership m WHERE m.board_id = $1 AND m.pin_id = $2 AND (SELECT deleted_at IS NULL FROM pin p WHERE p.id = $2);" SelectAuthorOrContributorRole = `SELECT board.author, role.name FROM board LEFT JOIN contributor ON contributor.board_id = board.id AND contributor.user_id = $1 LEFT JOIN role ON contributor.role_id = role.id diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 681dc57..1c92ad1 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -339,6 +339,31 @@ func (repo *boardRepoPG) AddPinsOnBoard(ctx context.Context, boardID int, pinIds return nil } +func (repo *boardRepoPG) DeletePinFromBoard(ctx context.Context, boardID, pinID int) error { + tx, err := repo.db.Begin(ctx) + if err != nil { + return fmt.Errorf("delete pin from board - start tx: %w", err) + } + + status, err := tx.Exec(ctx, DeletePinFromBoard, boardID, pinID) + if err != nil { + if err := tx.Rollback(ctx); err != nil { + return fmt.Errorf("delete pin from board - rollback tx: %w", err) + } + return fmt.Errorf("delete pin from board - exec: %w", err) + } + + if err := tx.Commit(ctx); err != nil { + return fmt.Errorf("delete pin from board - commit tx: %w", err) + } + + if status.RowsAffected() == 0 { + return repository.ErrNoDataAffected + } + + return nil +} + func (b *boardRepoPG) GetProtectionStatusBoard(ctx context.Context, boardID int) (repoBoard.ProtectionBoard, error) { var isPublic bool err := b.db.QueryRow(ctx, SelectProtectionStatusBoard, boardID).Scan(&isPublic) diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 5d1fb4c..7c6634f 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -20,6 +20,7 @@ type Repository interface { DeleteBoardByID(ctx context.Context, boardID int) error RoleUserHaveOnThisBoard(ctx context.Context, boardID int, userID int) (UserRole, error) AddPinsOnBoard(ctx context.Context, boardID int, pinIds []int) error + DeletePinFromBoard(ctx context.Context, boardID, pinID int) error GetProtectionStatusBoard(ctx context.Context, boardID int) (ProtectionBoard, error) } diff --git a/internal/pkg/usecase/board/delete.go b/internal/pkg/usecase/board/delete.go index da4fd8b..cab1a87 100644 --- a/internal/pkg/usecase/board/delete.go +++ b/internal/pkg/usecase/board/delete.go @@ -31,3 +31,31 @@ func (bCase *boardUsecase) DeleteCertainBoard(ctx context.Context, boardID int) return nil } + +func (bCase *boardUsecase) DeletePinFromBoard(ctx context.Context, boardID, pinID int) error { + boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) + if err != nil { + switch err { + case repository.ErrNoData: + return ErrNoSuchBoard + default: + return fmt.Errorf("delete certain board: %w", err) + } + } + + currUserID, loggedIn := ctx.Value(auth.KeyCurrentUserID).(int) + if !(loggedIn && currUserID == boardAuthorID) { + return ErrNoAccess + } + + err = bCase.boardRepo.DeletePinFromBoard(ctx, boardID, pinID) + if err != nil { + switch err { + case repository.ErrNoDataAffected: + return ErrNoPinOnBoard + } + return fmt.Errorf("delete certain board: %w", err) + } + + return nil +} diff --git a/internal/pkg/usecase/board/errors.go b/internal/pkg/usecase/board/errors.go index b241f3c..da4ef61 100644 --- a/internal/pkg/usecase/board/errors.go +++ b/internal/pkg/usecase/board/errors.go @@ -5,6 +5,7 @@ import "errors" var ( ErrInvalidUsername = errors.New("username doesn't exist") ErrNoSuchBoard = errors.New("board is not accessable or doesn't exist") + ErrNoPinOnBoard = errors.New("no such pin on board") ErrInvalidUserID = errors.New("invalid user id has been provided") ErrNoAccess = errors.New("no access for this action") ) diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 641945c..2fefc44 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -20,6 +20,7 @@ type Usecase interface { UpdateBoardInfo(ctx context.Context, updatedBoard entity.Board, tagTitles []string) error DeleteCertainBoard(ctx context.Context, boardID int) error FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error + DeletePinFromBoard(ctx context.Context, boardID, pinID int) error CheckAvailabilityFeedPinCfgOnBoard(ctx context.Context, cfg pin.FeedPinConfig, userID int, isAuth bool) error } From 3c6b04bec61d2e8dd5cbbb5754b50b26cc26366c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 28 Nov 2023 23:56:59 +0300 Subject: [PATCH 211/266] dev3 minor: deleted redundant query.go from search repo --- .../pkg/repository/search/postgres/queries.go | 51 ------------------- internal/pkg/usecase/search/usecase.go | 2 - 2 files changed, 53 deletions(-) delete mode 100644 internal/pkg/repository/search/postgres/queries.go diff --git a/internal/pkg/repository/search/postgres/queries.go b/internal/pkg/repository/search/postgres/queries.go deleted file mode 100644 index 2085e8e..0000000 --- a/internal/pkg/repository/search/postgres/queries.go +++ /dev/null @@ -1,51 +0,0 @@ -package search - -// id, username, avatar, is_subcribed, subsCount - -const ( - SelectUsersByUsername = ` - SELECT - p1.id, p1.username, p1.avatar, COUNT(s1.who) AS subscribers, s2.who IS NOT NULL AS is_subscribed - FROM - profile p1 - LEFT JOIN - subscription_user s1 ON p1.id = s1.whom - LEFT JOIN - profile p2 ON s1.who = p2.id - LEFT JOIN - subscription_user s2 ON p1.id = s2.whom AND s2.who = $1 -- curr user - WHERE - p1.deleted_at IS NULL AND p2.deleted_at IS NULL AND p1.username ILIKE $2 -- AND p1.id < $3 --lastID and template - GROUP BY - p1.id, p1.username, p1.avatar, s2.who IS NOT NULL - ORDER BY - p1.id DESC - LIMIT - $3 --count - OFFSET - $4;` - SelectBoardsByTitle = ` - SELECT - board.id, - board.title, - board.created_at, - board.public, - COUNT(DISTINCT pin.id) FILTER (WHERE pin.deleted_at IS NULL) AS pins_number, - COALESCE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL AND pin.picture is not null))[:3], ARRAY[]::TEXT[]) AS pins - FROM - board - LEFT JOIN - membership ON board.id = membership.board_id - LEFT JOIN - pin ON membership.pin_id = pin.id - WHERE - board.title ILIKE $1 AND (board.public OR board.author = $2 OR $2 IN (SELECT user_id FROM contributor WHERE board_id = board.id)) - GROUP BY - board.id, board.title, board.created_at - ORDER BY - board.id DESC - LIMIT - $3 - OFFSET - $4;` -) diff --git a/internal/pkg/usecase/search/usecase.go b/internal/pkg/usecase/search/usecase.go index 6c11b1e..6b06e24 100644 --- a/internal/pkg/usecase/search/usecase.go +++ b/internal/pkg/usecase/search/usecase.go @@ -9,8 +9,6 @@ import ( "github.com/microcosm-cc/bluemonday" ) -//ctx context.Context, template string, currUserID, lastID, count int - type Usecase interface { GetUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) GetBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) From 5046160bef2ae1b23447eb09dc216816b9c65d39 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 01:20:38 +0300 Subject: [PATCH 212/266] TP-0a2 update: move api/auth, api/realtime to internal/api/auth, internal/api/realtime --- Makefile | 7 ++- cmd/realtime/main.go | 2 +- configs/prometheus.yml | 9 ++++ deployments/docker-compose.yml | 14 +++--- {api => internal/api}/auth/auth.pb.go | 0 {api => internal/api}/auth/auth_grpc.pb.go | 0 {api => internal/api}/realtime/realtime.pb.go | 0 .../api}/realtime/realtime_grpc.pb.go | 0 internal/app/app.go | 2 +- internal/app/auth/auth.go | 4 +- .../auth/delivery/grpc/server.go | 2 +- internal/microservices/realtime/node.go | 2 +- internal/microservices/realtime/server.go | 2 +- internal/microservices/realtime/types.go | 2 +- internal/pkg/delivery/websocket/websocket.go | 44 ++++++++++--------- internal/pkg/metrics/metrics.go | 8 ++-- internal/pkg/repository/message/queries.go | 2 +- internal/pkg/usecase/auth/usecase.go | 2 +- 18 files changed, 62 insertions(+), 40 deletions(-) rename {api => internal/api}/auth/auth.pb.go (100%) rename {api => internal/api}/auth/auth_grpc.pb.go (100%) rename {api => internal/api}/realtime/realtime.pb.go (100%) rename {api => internal/api}/realtime/realtime_grpc.pb.go (100%) diff --git a/Makefile b/Makefile index 12a2f08..2ba4739 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover build_auth +.PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover +.PHONY: build_auth build_realtime + ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs COV_OUT=coverage.out @@ -11,6 +13,9 @@ build: build_auth: go build -o bin/auth cmd/auth/*.go +build_realtime: + go build -o bin/realtime cmd/realtime/*.go + run: build ./bin/app diff --git a/cmd/realtime/main.go b/cmd/realtime/main.go index 8d8b3d8..ace3d44 100644 --- a/cmd/realtime/main.go +++ b/cmd/realtime/main.go @@ -6,7 +6,7 @@ import ( "google.golang.org/grpc" - rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/realtime" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) diff --git a/configs/prometheus.yml b/configs/prometheus.yml index c12dc59..1fd8c97 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -10,3 +10,12 @@ scrape_configs: - job_name: 'auth' static_configs: - targets: ['host.docker.internal:8085'] + + - job_name: 'node_exporter' + static_configs: + - targets: ['host.docker.internal:9100'] + + - job_name: 'pinspire' + scheme: https + static_configs: + - targets: ['pinspire.online:8080'] diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index a0946c7..8ee69d6 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -64,13 +64,17 @@ services: node_exporter: image: quay.io/prometheus/node-exporter:latest container_name: pinspireNodeExporter + user: root + privileged: true command: - - '--path.rootfs=/host' - network_mode: host - pid: host - restart: unless-stopped + - '--path.procfs=/host/proc' + - '--path.sysfs=/host/sys' volumes: - - '/:/host:ro,rslave' + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro + ports: + - "9100:9100" volumes: diff --git a/api/auth/auth.pb.go b/internal/api/auth/auth.pb.go similarity index 100% rename from api/auth/auth.pb.go rename to internal/api/auth/auth.pb.go diff --git a/api/auth/auth_grpc.pb.go b/internal/api/auth/auth_grpc.pb.go similarity index 100% rename from api/auth/auth_grpc.pb.go rename to internal/api/auth/auth_grpc.pb.go diff --git a/api/realtime/realtime.pb.go b/internal/api/realtime/realtime.pb.go similarity index 100% rename from api/realtime/realtime.pb.go rename to internal/api/realtime/realtime.pb.go diff --git a/api/realtime/realtime_grpc.pb.go b/internal/api/realtime/realtime_grpc.pb.go similarity index 100% rename from api/realtime/realtime_grpc.pb.go rename to internal/api/realtime/realtime_grpc.pb.go diff --git a/internal/app/app.go b/internal/app/app.go index ce8632a..bcde344 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -9,7 +9,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" diff --git a/internal/app/auth/auth.go b/internal/app/auth/auth.go index dab46f0..d1c1fc9 100644 --- a/internal/app/auth/auth.go +++ b/internal/app/auth/auth.go @@ -8,7 +8,7 @@ import ( "github.com/joho/godotenv" "google.golang.org/grpc" - "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" authMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/auth/delivery/grpc" sessRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" @@ -63,7 +63,7 @@ func Run(ctx context.Context, log *logger.Logger, cfg Config) { u := user.New(log, nil, userRepo.NewUserRepoPG(pool)) s := grpc.NewServer() - auth.RegisterAuthServer(s, authMS.New(log, sm, u)) + authProto.RegisterAuthServer(s, authMS.New(log, sm, u)) log.Info("service auht start", logger.F{"addr", cfg.Addr}) if err = s.Serve(l); err != nil { diff --git a/internal/microservices/auth/delivery/grpc/server.go b/internal/microservices/auth/delivery/grpc/server.go index 55bcb6d..1118b07 100644 --- a/internal/microservices/auth/delivery/grpc/server.go +++ b/internal/microservices/auth/delivery/grpc/server.go @@ -8,7 +8,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" - authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" userUsecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" diff --git a/internal/microservices/realtime/node.go b/internal/microservices/realtime/node.go index 4e476a5..db17bf6 100644 --- a/internal/microservices/realtime/node.go +++ b/internal/microservices/realtime/node.go @@ -6,7 +6,7 @@ import ( "google.golang.org/protobuf/proto" - rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" ) type Node struct { diff --git a/internal/microservices/realtime/server.go b/internal/microservices/realtime/server.go index 8feabfe..c83d166 100644 --- a/internal/microservices/realtime/server.go +++ b/internal/microservices/realtime/server.go @@ -3,7 +3,7 @@ package realtime import ( "context" - rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" "github.com/golang/protobuf/ptypes/empty" "github.com/google/uuid" "google.golang.org/grpc/codes" diff --git a/internal/microservices/realtime/types.go b/internal/microservices/realtime/types.go index 483c261..d0239e3 100644 --- a/internal/microservices/realtime/types.go +++ b/internal/microservices/realtime/types.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" - rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" ) type Channel struct { diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index d995663..c75c5c4 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "net/http" + "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ws "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" - rt "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" @@ -21,10 +22,13 @@ type HandlerWebSocket struct { originPatterns []string log *log.Logger messageCase usecase.Usecase + client rt.RealTimeClient } type Option func(h *HandlerWebSocket) +const _ctxOnServeConnect = 24 * time.Hour + func SetOriginPatterns(patterns []string) Option { return func(h *HandlerWebSocket) { h.originPatterns = patterns @@ -32,7 +36,14 @@ func SetOriginPatterns(patterns []string) Option { } func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { - handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase} + gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Error(fmt.Errorf("grpc dial: %w", err).Error()) + } + defer gRPCConn.Close() + + client := rt.NewRealTimeClient(gRPCConn) + handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase, client: client} for _, opt := range opts { opt(handlerWS) } @@ -50,26 +61,19 @@ func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Reque } defer conn.CloseNow() - err = h.serveWebSocketConn(r.Context(), conn) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) + defer cancel() + + err = h.serveWebSocketConn(ctx, conn, userID) if err != nil { h.log.Error(err.Error()) } } -func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn) error { - userID, ok := ctx.Value(auth.KeyCurrentUserID).(int) - if !ok { - userID = 0 - } - gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - return fmt.Errorf("grpc dial: %w", err) - } - defer gRPCConn.Close() - - client := rt.NewRealTimeClient(gRPCConn) +func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn, userID int) error { request := &Request{} - + var err error for { err = wsjson.Read(ctx, conn, request) if err != nil { @@ -89,7 +93,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn continue } wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]int{"id": id})) - _, err = client.Publish(ctx, &rt.PublishMessage{ + _, err = h.client.Publish(ctx, &rt.PublishMessage{ Channel: &rt.Channel{ Name: request.Channel.Name, Topic: request.Channel.Topic, @@ -115,7 +119,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn continue } wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", nil)) - _, err = client.Publish(ctx, &rt.PublishMessage{ + _, err = h.client.Publish(ctx, &rt.PublishMessage{ Channel: &rt.Channel{ Name: request.Channel.Name, Topic: request.Channel.Topic, @@ -140,7 +144,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn continue } wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", nil)) - _, err = client.Publish(ctx, &rt.PublishMessage{ + _, err = h.client.Publish(ctx, &rt.PublishMessage{ Channel: &rt.Channel{ Name: request.Channel.Name, Topic: request.Channel.Topic, @@ -161,7 +165,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) } case "Subscribe": - err = h.subscribe(ctx, client, request, conn) + err = h.subscribe(ctx, h.client, request, conn) if err != nil { h.log.Warn(err.Error()) wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "subscribe_fail", "failed to subscribe to the channel", nil)) diff --git a/internal/pkg/metrics/metrics.go b/internal/pkg/metrics/metrics.go index 5165af7..9a7afe8 100644 --- a/internal/pkg/metrics/metrics.go +++ b/internal/pkg/metrics/metrics.go @@ -30,8 +30,8 @@ func (m metrics) AddRequest(method, path string, statusResponse int, executed ti } m.totalHits.Inc() - m.hitsDetail.WithLabelValues(method, path, labelStatus).Inc() - m.timeMeasurement.WithLabelValues(method, path, labelStatus).Observe(float64(executed.Milliseconds())) + m.hitsDetail.WithLabelValues(method+" "+path, labelStatus).Inc() + m.timeMeasurement.WithLabelValues(method+" "+path, labelStatus).Observe(float64(executed.Milliseconds())) } func New(prefix string) metrics { @@ -44,12 +44,12 @@ func New(prefix string) metrics { hitsDetail: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: prefix + "_hits_detail", Help: "the number of requests indicating its method, path, and response status", - }, []string{"method", "path", "status"}), + }, []string{"handler", "status"}), timeMeasurement: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: prefix + "_request_time_execution", Help: "time of request execution with indication of its method, path and response status", Buckets: []float64{10, 100, 500, 1000, 5000}, - }, []string{"method", "path", "status"}), + }, []string{"handler", "status"}), } } diff --git a/internal/pkg/repository/message/queries.go b/internal/pkg/repository/message/queries.go index 6de9176..acc21b9 100644 --- a/internal/pkg/repository/message/queries.go +++ b/internal/pkg/repository/message/queries.go @@ -4,7 +4,7 @@ const ( SelectMessageByID = "SELECT user_from, user_to, content FROM message WHERE id = $1 AND deleted_at IS NULL;" SelectUserChats = `SELECT max(message.id) AS mmid, profile.id, username, avatar FROM message INNER JOIN profile ON user_to = profile.id - WHERE user_from = $1 AND (message.id < $2 OR $2 = 0) + WHERE (user_from = $1 OR user_to = $1) AND (message.id < $2 OR $2 = 0) GROUP BY profile.id ORDER BY mmid DESC LIMIT $3;` diff --git a/internal/pkg/usecase/auth/usecase.go b/internal/pkg/usecase/auth/usecase.go index 79baa72..2c23f5a 100644 --- a/internal/pkg/usecase/auth/usecase.go +++ b/internal/pkg/usecase/auth/usecase.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - authProto "github.com/go-park-mail-ru/2023_2_OND_team/api/auth" + authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "google.golang.org/protobuf/types/known/timestamppb" From 4f8e548ec2c1ad25a025b01fe157176840e4829a Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 03:38:24 +0300 Subject: [PATCH 213/266] dev3 del defer close grpc connect --- configs/prometheus.yml | 3 ++- internal/pkg/delivery/websocket/websocket.go | 1 - internal/pkg/repository/message/queries.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configs/prometheus.yml b/configs/prometheus.yml index 1fd8c97..4e71cdf 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -12,8 +12,9 @@ scrape_configs: - targets: ['host.docker.internal:8085'] - job_name: 'node_exporter' + scheme: https static_configs: - - targets: ['host.docker.internal:9100'] + - targets: ['pinspire.online:9100'] - job_name: 'pinspire' scheme: https diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index c75c5c4..581ec1b 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -40,7 +40,6 @@ func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSo if err != nil { log.Error(fmt.Errorf("grpc dial: %w", err).Error()) } - defer gRPCConn.Close() client := rt.NewRealTimeClient(gRPCConn) handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase, client: client} diff --git a/internal/pkg/repository/message/queries.go b/internal/pkg/repository/message/queries.go index acc21b9..6e3e1f8 100644 --- a/internal/pkg/repository/message/queries.go +++ b/internal/pkg/repository/message/queries.go @@ -3,8 +3,8 @@ package message const ( SelectMessageByID = "SELECT user_from, user_to, content FROM message WHERE id = $1 AND deleted_at IS NULL;" SelectUserChats = `SELECT max(message.id) AS mmid, profile.id, username, avatar - FROM message INNER JOIN profile ON user_to = profile.id - WHERE (user_from = $1 OR user_to = $1) AND (message.id < $2 OR $2 = 0) + FROM message INNER JOIN profile ON (user_to = $1 AND user_from = profile.id) OR (user_to = profile.id AND user_from = $1) + WHERE (message.id < $2 OR $2 = 0) GROUP BY profile.id ORDER BY mmid DESC LIMIT $3;` From 73ca3b1ba0a376c832a66f0ed4a9884f0673d55f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 03:53:18 +0300 Subject: [PATCH 214/266] dev3 del: scheme https for prometheus target pinspire.online:9100 --- configs/prometheus.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/configs/prometheus.yml b/configs/prometheus.yml index 4e71cdf..be00934 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -12,7 +12,6 @@ scrape_configs: - targets: ['host.docker.internal:8085'] - job_name: 'node_exporter' - scheme: https static_configs: - targets: ['pinspire.online:9100'] From 3bca698d143fe8d7a334064f35dbb00edff4f0b6 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 10:11:18 +0300 Subject: [PATCH 215/266] dev3 update: change option for proto files --- api/proto/auth.proto | 2 +- api/proto/realtime.proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/proto/auth.proto b/api/proto/auth.proto index 12a5507..b819a5c 100644 --- a/api/proto/auth.proto +++ b/api/proto/auth.proto @@ -3,7 +3,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; -option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/api/auth"; +option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth"; package auth; diff --git a/api/proto/realtime.proto b/api/proto/realtime.proto index 2977b05..d04bc35 100644 --- a/api/proto/realtime.proto +++ b/api/proto/realtime.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; -option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/api/realtime"; +option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime"; package realtime; From 7b3f54ba1121f5b02e818b259902a9d66571ca19 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 10:57:11 +0300 Subject: [PATCH 216/266] TP-cc2 add: proto description service messenger --- api/proto/messenger.proto | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 api/proto/messenger.proto diff --git a/api/proto/messenger.proto b/api/proto/messenger.proto new file mode 100644 index 0000000..c441c5c --- /dev/null +++ b/api/proto/messenger.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger"; + +package messenger; + +service Messenger { + rpc UserChatsWithOtherUsers(FeedChatRequest) returns (FeedChat) {} + rpc SendMessage(Message) returns (MsgID) {} + rpc MessageFromChat(FeedMessageRequest) returns (FeedMessage) {} + rpc UpdateMessage(Message) returns (google.protobuf.Empty) {} + rpc DeleteMessage(MsgID) returns (google.protobuf.Empty) {} + rpc GetMessage(MsgID) returns (Message) {} +} + +message MsgID { + int64 id = 1; +} + +message Message { + MsgID id = 1; + int64 user_from = 2; + int64 user_to = 3; + string content = 4; +} + +message Chat { + int64 userID1 = 1; + int64 userID2 = 2; +} + +message FeedChatRequest { + int64 count = 1; + int64 lastID = 2; +} + +message FeedMessageRequest { + Chat chat = 1; + int64 count = 2; + int64 lastID = 3; +} + +message WichWhomChat { + int64 userID = 1; + string username = 2; + string avatar = 3; +} + +message ChatWithUser { + int64 lastMessageID = 1; + ChatWithUser chat = 2; +} + +message FeedMessage { + repeated Message messages = 1; + int64 lastID = 2; +} + +message FeedChat { + repeated ChatWithUser chats = 1; +} From 0bb3a3a19b596ac27e9ce36ed8dbb72f9ade200f Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 13:16:11 +0300 Subject: [PATCH 217/266] TP-cc2 add: microservice messenger --- Makefile | 5 +- api/proto/messenger.proto | 3 +- cmd/messenger/main.go | 20 + internal/api/messenger/messenger.pb.go | 812 ++++++++++++++++++ internal/api/messenger/messenger_grpc.pb.go | 286 ++++++ internal/app/messenger/messenger.go | 67 ++ .../messenger/delivery/grpc/convert.go | 36 + .../messenger/delivery/grpc/server.go | 111 +++ 8 files changed, 1338 insertions(+), 2 deletions(-) create mode 100644 cmd/messenger/main.go create mode 100644 internal/api/messenger/messenger.pb.go create mode 100644 internal/api/messenger/messenger_grpc.pb.go create mode 100644 internal/app/messenger/messenger.go create mode 100644 internal/microservices/messenger/delivery/grpc/convert.go create mode 100644 internal/microservices/messenger/delivery/grpc/server.go diff --git a/Makefile b/Makefile index 2ba4739..7b0652c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover -.PHONY: build_auth build_realtime +.PHONY: build_auth build_realtime build_messenger ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs @@ -16,6 +16,9 @@ build_auth: build_realtime: go build -o bin/realtime cmd/realtime/*.go +build_messenger: + go build -o bin/messenger cmd/messenger/*.go + run: build ./bin/app diff --git a/api/proto/messenger.proto b/api/proto/messenger.proto index c441c5c..07a0dec 100644 --- a/api/proto/messenger.proto +++ b/api/proto/messenger.proto @@ -50,7 +50,7 @@ message WichWhomChat { message ChatWithUser { int64 lastMessageID = 1; - ChatWithUser chat = 2; + WichWhomChat chat = 2; } message FeedMessage { @@ -60,4 +60,5 @@ message FeedMessage { message FeedChat { repeated ChatWithUser chats = 1; + int64 lastID = 2; } diff --git a/cmd/messenger/main.go b/cmd/messenger/main.go new file mode 100644 index 0000000..a9dd91a --- /dev/null +++ b/cmd/messenger/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app/messenger" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func main() { + log, err := logger.New() + if err != nil { + fmt.Println(err) + return + } + defer log.Sync() + + messenger.Run(context.Background(), log) +} diff --git a/internal/api/messenger/messenger.pb.go b/internal/api/messenger/messenger.pb.go new file mode 100644 index 0000000..a9b7c6a --- /dev/null +++ b/internal/api/messenger/messenger.pb.go @@ -0,0 +1,812 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.6.1 +// source: api/proto/messenger.proto + +package messenger + +import ( + empty "github.com/golang/protobuf/ptypes/empty" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type MsgID struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *MsgID) Reset() { + *x = MsgID{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MsgID) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MsgID) ProtoMessage() {} + +func (x *MsgID) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MsgID.ProtoReflect.Descriptor instead. +func (*MsgID) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{0} +} + +func (x *MsgID) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +type Message struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id *MsgID `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + UserFrom int64 `protobuf:"varint,2,opt,name=user_from,json=userFrom,proto3" json:"user_from,omitempty"` + UserTo int64 `protobuf:"varint,3,opt,name=user_to,json=userTo,proto3" json:"user_to,omitempty"` + Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` +} + +func (x *Message) Reset() { + *x = Message{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Message) ProtoMessage() {} + +func (x *Message) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Message.ProtoReflect.Descriptor instead. +func (*Message) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{1} +} + +func (x *Message) GetId() *MsgID { + if x != nil { + return x.Id + } + return nil +} + +func (x *Message) GetUserFrom() int64 { + if x != nil { + return x.UserFrom + } + return 0 +} + +func (x *Message) GetUserTo() int64 { + if x != nil { + return x.UserTo + } + return 0 +} + +func (x *Message) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +type Chat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserID1 int64 `protobuf:"varint,1,opt,name=userID1,proto3" json:"userID1,omitempty"` + UserID2 int64 `protobuf:"varint,2,opt,name=userID2,proto3" json:"userID2,omitempty"` +} + +func (x *Chat) Reset() { + *x = Chat{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Chat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Chat) ProtoMessage() {} + +func (x *Chat) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Chat.ProtoReflect.Descriptor instead. +func (*Chat) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{2} +} + +func (x *Chat) GetUserID1() int64 { + if x != nil { + return x.UserID1 + } + return 0 +} + +func (x *Chat) GetUserID2() int64 { + if x != nil { + return x.UserID2 + } + return 0 +} + +type FeedChatRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` + LastID int64 `protobuf:"varint,2,opt,name=lastID,proto3" json:"lastID,omitempty"` +} + +func (x *FeedChatRequest) Reset() { + *x = FeedChatRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeedChatRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedChatRequest) ProtoMessage() {} + +func (x *FeedChatRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedChatRequest.ProtoReflect.Descriptor instead. +func (*FeedChatRequest) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{3} +} + +func (x *FeedChatRequest) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *FeedChatRequest) GetLastID() int64 { + if x != nil { + return x.LastID + } + return 0 +} + +type FeedMessageRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Chat *Chat `protobuf:"bytes,1,opt,name=chat,proto3" json:"chat,omitempty"` + Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + LastID int64 `protobuf:"varint,3,opt,name=lastID,proto3" json:"lastID,omitempty"` +} + +func (x *FeedMessageRequest) Reset() { + *x = FeedMessageRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeedMessageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedMessageRequest) ProtoMessage() {} + +func (x *FeedMessageRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedMessageRequest.ProtoReflect.Descriptor instead. +func (*FeedMessageRequest) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{4} +} + +func (x *FeedMessageRequest) GetChat() *Chat { + if x != nil { + return x.Chat + } + return nil +} + +func (x *FeedMessageRequest) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *FeedMessageRequest) GetLastID() int64 { + if x != nil { + return x.LastID + } + return 0 +} + +type WichWhomChat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserID int64 `protobuf:"varint,1,opt,name=userID,proto3" json:"userID,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` +} + +func (x *WichWhomChat) Reset() { + *x = WichWhomChat{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WichWhomChat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WichWhomChat) ProtoMessage() {} + +func (x *WichWhomChat) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WichWhomChat.ProtoReflect.Descriptor instead. +func (*WichWhomChat) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{5} +} + +func (x *WichWhomChat) GetUserID() int64 { + if x != nil { + return x.UserID + } + return 0 +} + +func (x *WichWhomChat) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *WichWhomChat) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +type ChatWithUser struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LastMessageID int64 `protobuf:"varint,1,opt,name=lastMessageID,proto3" json:"lastMessageID,omitempty"` + Chat *WichWhomChat `protobuf:"bytes,2,opt,name=chat,proto3" json:"chat,omitempty"` +} + +func (x *ChatWithUser) Reset() { + *x = ChatWithUser{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChatWithUser) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChatWithUser) ProtoMessage() {} + +func (x *ChatWithUser) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChatWithUser.ProtoReflect.Descriptor instead. +func (*ChatWithUser) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{6} +} + +func (x *ChatWithUser) GetLastMessageID() int64 { + if x != nil { + return x.LastMessageID + } + return 0 +} + +func (x *ChatWithUser) GetChat() *WichWhomChat { + if x != nil { + return x.Chat + } + return nil +} + +type FeedMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Messages []*Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` + LastID int64 `protobuf:"varint,2,opt,name=lastID,proto3" json:"lastID,omitempty"` +} + +func (x *FeedMessage) Reset() { + *x = FeedMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeedMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedMessage) ProtoMessage() {} + +func (x *FeedMessage) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedMessage.ProtoReflect.Descriptor instead. +func (*FeedMessage) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{7} +} + +func (x *FeedMessage) GetMessages() []*Message { + if x != nil { + return x.Messages + } + return nil +} + +func (x *FeedMessage) GetLastID() int64 { + if x != nil { + return x.LastID + } + return 0 +} + +type FeedChat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Chats []*ChatWithUser `protobuf:"bytes,1,rep,name=chats,proto3" json:"chats,omitempty"` + LastID int64 `protobuf:"varint,2,opt,name=lastID,proto3" json:"lastID,omitempty"` +} + +func (x *FeedChat) Reset() { + *x = FeedChat{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_messenger_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeedChat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedChat) ProtoMessage() {} + +func (x *FeedChat) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_messenger_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedChat.ProtoReflect.Descriptor instead. +func (*FeedChat) Descriptor() ([]byte, []int) { + return file_api_proto_messenger_proto_rawDescGZIP(), []int{8} +} + +func (x *FeedChat) GetChats() []*ChatWithUser { + if x != nil { + return x.Chats + } + return nil +} + +func (x *FeedChat) GetLastID() int64 { + if x != nil { + return x.LastID + } + return 0 +} + +var File_api_proto_messenger_proto protoreflect.FileDescriptor + +var file_api_proto_messenger_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x73, 0x73, + 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x65, 0x73, + 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x17, 0x0a, 0x05, 0x4d, 0x73, 0x67, 0x49, 0x44, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x7b, 0x0a, 0x07, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, + 0x4d, 0x73, 0x67, 0x49, 0x44, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, + 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x54, 0x6f, 0x12, + 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x0a, 0x04, 0x43, 0x68, 0x61, + 0x74, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x31, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x31, 0x12, 0x18, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x32, 0x22, 0x3f, 0x0a, 0x0f, 0x46, 0x65, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x6c, 0x61, 0x73, 0x74, 0x49, 0x44, 0x22, 0x67, 0x0a, 0x12, 0x46, 0x65, 0x65, 0x64, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x04, + 0x63, 0x68, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x52, 0x04, 0x63, 0x68, 0x61, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, + 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x44, 0x22, + 0x5a, 0x0a, 0x0c, 0x57, 0x69, 0x63, 0x68, 0x57, 0x68, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x61, 0x0a, 0x0c, 0x43, + 0x68, 0x61, 0x74, 0x57, 0x69, 0x74, 0x68, 0x55, 0x73, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x6c, + 0x61, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, + 0x44, 0x12, 0x2b, 0x0a, 0x04, 0x63, 0x68, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x57, 0x69, 0x63, 0x68, + 0x57, 0x68, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x52, 0x04, 0x63, 0x68, 0x61, 0x74, 0x22, 0x55, + 0x0a, 0x0b, 0x46, 0x65, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, + 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6c, + 0x61, 0x73, 0x74, 0x49, 0x44, 0x22, 0x51, 0x0a, 0x08, 0x46, 0x65, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x68, 0x61, + 0x74, 0x57, 0x69, 0x74, 0x68, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x63, 0x68, 0x61, 0x74, 0x73, + 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x44, 0x32, 0x8e, 0x03, 0x0a, 0x09, 0x4d, 0x65, 0x73, + 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x17, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, + 0x61, 0x74, 0x73, 0x57, 0x69, 0x74, 0x68, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, + 0x73, 0x12, 0x1a, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x46, 0x65, + 0x65, 0x64, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x74, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x12, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x10, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, + 0x67, 0x65, 0x72, 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x44, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0f, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x12, 0x1d, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, + 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, + 0x67, 0x65, 0x72, 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x44, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x10, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x4d, 0x73, + 0x67, 0x49, 0x44, 0x1a, 0x12, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x2e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x70, 0x61, 0x72, 0x6b, 0x2d, + 0x6d, 0x61, 0x69, 0x6c, 0x2d, 0x72, 0x75, 0x2f, 0x32, 0x30, 0x32, 0x33, 0x5f, 0x32, 0x5f, 0x4f, + 0x4e, 0x44, 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_messenger_proto_rawDescOnce sync.Once + file_api_proto_messenger_proto_rawDescData = file_api_proto_messenger_proto_rawDesc +) + +func file_api_proto_messenger_proto_rawDescGZIP() []byte { + file_api_proto_messenger_proto_rawDescOnce.Do(func() { + file_api_proto_messenger_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_messenger_proto_rawDescData) + }) + return file_api_proto_messenger_proto_rawDescData +} + +var file_api_proto_messenger_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_api_proto_messenger_proto_goTypes = []interface{}{ + (*MsgID)(nil), // 0: messenger.MsgID + (*Message)(nil), // 1: messenger.Message + (*Chat)(nil), // 2: messenger.Chat + (*FeedChatRequest)(nil), // 3: messenger.FeedChatRequest + (*FeedMessageRequest)(nil), // 4: messenger.FeedMessageRequest + (*WichWhomChat)(nil), // 5: messenger.WichWhomChat + (*ChatWithUser)(nil), // 6: messenger.ChatWithUser + (*FeedMessage)(nil), // 7: messenger.FeedMessage + (*FeedChat)(nil), // 8: messenger.FeedChat + (*empty.Empty)(nil), // 9: google.protobuf.Empty +} +var file_api_proto_messenger_proto_depIdxs = []int32{ + 0, // 0: messenger.Message.id:type_name -> messenger.MsgID + 2, // 1: messenger.FeedMessageRequest.chat:type_name -> messenger.Chat + 5, // 2: messenger.ChatWithUser.chat:type_name -> messenger.WichWhomChat + 1, // 3: messenger.FeedMessage.messages:type_name -> messenger.Message + 6, // 4: messenger.FeedChat.chats:type_name -> messenger.ChatWithUser + 3, // 5: messenger.Messenger.UserChatsWithOtherUsers:input_type -> messenger.FeedChatRequest + 1, // 6: messenger.Messenger.SendMessage:input_type -> messenger.Message + 4, // 7: messenger.Messenger.MessageFromChat:input_type -> messenger.FeedMessageRequest + 1, // 8: messenger.Messenger.UpdateMessage:input_type -> messenger.Message + 0, // 9: messenger.Messenger.DeleteMessage:input_type -> messenger.MsgID + 0, // 10: messenger.Messenger.GetMessage:input_type -> messenger.MsgID + 8, // 11: messenger.Messenger.UserChatsWithOtherUsers:output_type -> messenger.FeedChat + 0, // 12: messenger.Messenger.SendMessage:output_type -> messenger.MsgID + 7, // 13: messenger.Messenger.MessageFromChat:output_type -> messenger.FeedMessage + 9, // 14: messenger.Messenger.UpdateMessage:output_type -> google.protobuf.Empty + 9, // 15: messenger.Messenger.DeleteMessage:output_type -> google.protobuf.Empty + 1, // 16: messenger.Messenger.GetMessage:output_type -> messenger.Message + 11, // [11:17] is the sub-list for method output_type + 5, // [5:11] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_api_proto_messenger_proto_init() } +func file_api_proto_messenger_proto_init() { + if File_api_proto_messenger_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_messenger_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MsgID); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Message); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Chat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeedChatRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeedMessageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WichWhomChat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChatWithUser); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeedMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_messenger_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeedChat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_messenger_proto_rawDesc, + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_messenger_proto_goTypes, + DependencyIndexes: file_api_proto_messenger_proto_depIdxs, + MessageInfos: file_api_proto_messenger_proto_msgTypes, + }.Build() + File_api_proto_messenger_proto = out.File + file_api_proto_messenger_proto_rawDesc = nil + file_api_proto_messenger_proto_goTypes = nil + file_api_proto_messenger_proto_depIdxs = nil +} diff --git a/internal/api/messenger/messenger_grpc.pb.go b/internal/api/messenger/messenger_grpc.pb.go new file mode 100644 index 0000000..b2fb9eb --- /dev/null +++ b/internal/api/messenger/messenger_grpc.pb.go @@ -0,0 +1,286 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.6.1 +// source: api/proto/messenger.proto + +package messenger + +import ( + context "context" + empty "github.com/golang/protobuf/ptypes/empty" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// MessengerClient is the client API for Messenger service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type MessengerClient interface { + UserChatsWithOtherUsers(ctx context.Context, in *FeedChatRequest, opts ...grpc.CallOption) (*FeedChat, error) + SendMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*MsgID, error) + MessageFromChat(ctx context.Context, in *FeedMessageRequest, opts ...grpc.CallOption) (*FeedMessage, error) + UpdateMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*empty.Empty, error) + DeleteMessage(ctx context.Context, in *MsgID, opts ...grpc.CallOption) (*empty.Empty, error) + GetMessage(ctx context.Context, in *MsgID, opts ...grpc.CallOption) (*Message, error) +} + +type messengerClient struct { + cc grpc.ClientConnInterface +} + +func NewMessengerClient(cc grpc.ClientConnInterface) MessengerClient { + return &messengerClient{cc} +} + +func (c *messengerClient) UserChatsWithOtherUsers(ctx context.Context, in *FeedChatRequest, opts ...grpc.CallOption) (*FeedChat, error) { + out := new(FeedChat) + err := c.cc.Invoke(ctx, "/messenger.Messenger/UserChatsWithOtherUsers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *messengerClient) SendMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*MsgID, error) { + out := new(MsgID) + err := c.cc.Invoke(ctx, "/messenger.Messenger/SendMessage", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *messengerClient) MessageFromChat(ctx context.Context, in *FeedMessageRequest, opts ...grpc.CallOption) (*FeedMessage, error) { + out := new(FeedMessage) + err := c.cc.Invoke(ctx, "/messenger.Messenger/MessageFromChat", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *messengerClient) UpdateMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/messenger.Messenger/UpdateMessage", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *messengerClient) DeleteMessage(ctx context.Context, in *MsgID, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/messenger.Messenger/DeleteMessage", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *messengerClient) GetMessage(ctx context.Context, in *MsgID, opts ...grpc.CallOption) (*Message, error) { + out := new(Message) + err := c.cc.Invoke(ctx, "/messenger.Messenger/GetMessage", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MessengerServer is the server API for Messenger service. +// All implementations must embed UnimplementedMessengerServer +// for forward compatibility +type MessengerServer interface { + UserChatsWithOtherUsers(context.Context, *FeedChatRequest) (*FeedChat, error) + SendMessage(context.Context, *Message) (*MsgID, error) + MessageFromChat(context.Context, *FeedMessageRequest) (*FeedMessage, error) + UpdateMessage(context.Context, *Message) (*empty.Empty, error) + DeleteMessage(context.Context, *MsgID) (*empty.Empty, error) + GetMessage(context.Context, *MsgID) (*Message, error) + mustEmbedUnimplementedMessengerServer() +} + +// UnimplementedMessengerServer must be embedded to have forward compatible implementations. +type UnimplementedMessengerServer struct { +} + +func (UnimplementedMessengerServer) UserChatsWithOtherUsers(context.Context, *FeedChatRequest) (*FeedChat, error) { + return nil, status.Errorf(codes.Unimplemented, "method UserChatsWithOtherUsers not implemented") +} +func (UnimplementedMessengerServer) SendMessage(context.Context, *Message) (*MsgID, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendMessage not implemented") +} +func (UnimplementedMessengerServer) MessageFromChat(context.Context, *FeedMessageRequest) (*FeedMessage, error) { + return nil, status.Errorf(codes.Unimplemented, "method MessageFromChat not implemented") +} +func (UnimplementedMessengerServer) UpdateMessage(context.Context, *Message) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateMessage not implemented") +} +func (UnimplementedMessengerServer) DeleteMessage(context.Context, *MsgID) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteMessage not implemented") +} +func (UnimplementedMessengerServer) GetMessage(context.Context, *MsgID) (*Message, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMessage not implemented") +} +func (UnimplementedMessengerServer) mustEmbedUnimplementedMessengerServer() {} + +// UnsafeMessengerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MessengerServer will +// result in compilation errors. +type UnsafeMessengerServer interface { + mustEmbedUnimplementedMessengerServer() +} + +func RegisterMessengerServer(s grpc.ServiceRegistrar, srv MessengerServer) { + s.RegisterService(&Messenger_ServiceDesc, srv) +} + +func _Messenger_UserChatsWithOtherUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FeedChatRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessengerServer).UserChatsWithOtherUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/messenger.Messenger/UserChatsWithOtherUsers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessengerServer).UserChatsWithOtherUsers(ctx, req.(*FeedChatRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Messenger_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Message) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessengerServer).SendMessage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/messenger.Messenger/SendMessage", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessengerServer).SendMessage(ctx, req.(*Message)) + } + return interceptor(ctx, in, info, handler) +} + +func _Messenger_MessageFromChat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FeedMessageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessengerServer).MessageFromChat(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/messenger.Messenger/MessageFromChat", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessengerServer).MessageFromChat(ctx, req.(*FeedMessageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Messenger_UpdateMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Message) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessengerServer).UpdateMessage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/messenger.Messenger/UpdateMessage", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessengerServer).UpdateMessage(ctx, req.(*Message)) + } + return interceptor(ctx, in, info, handler) +} + +func _Messenger_DeleteMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessengerServer).DeleteMessage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/messenger.Messenger/DeleteMessage", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessengerServer).DeleteMessage(ctx, req.(*MsgID)) + } + return interceptor(ctx, in, info, handler) +} + +func _Messenger_GetMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MessengerServer).GetMessage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/messenger.Messenger/GetMessage", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MessengerServer).GetMessage(ctx, req.(*MsgID)) + } + return interceptor(ctx, in, info, handler) +} + +// Messenger_ServiceDesc is the grpc.ServiceDesc for Messenger service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Messenger_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "messenger.Messenger", + HandlerType: (*MessengerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UserChatsWithOtherUsers", + Handler: _Messenger_UserChatsWithOtherUsers_Handler, + }, + { + MethodName: "SendMessage", + Handler: _Messenger_SendMessage_Handler, + }, + { + MethodName: "MessageFromChat", + Handler: _Messenger_MessageFromChat_Handler, + }, + { + MethodName: "UpdateMessage", + Handler: _Messenger_UpdateMessage_Handler, + }, + { + MethodName: "DeleteMessage", + Handler: _Messenger_DeleteMessage_Handler, + }, + { + MethodName: "GetMessage", + Handler: _Messenger_GetMessage_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/proto/messenger.proto", +} diff --git a/internal/app/messenger/messenger.go b/internal/app/messenger/messenger.go new file mode 100644 index 0000000..bbcd8d0 --- /dev/null +++ b/internal/app/messenger/messenger.go @@ -0,0 +1,67 @@ +package messenger + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/joho/godotenv" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" + messMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/delivery/grpc" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +const _timeoutForConnPG = 5 * time.Second + +func Run(ctx context.Context, log *logger.Logger) { + godotenv.Load() + + ctx, cancelCtxPG := context.WithTimeout(ctx, _timeoutForConnPG) + defer cancelCtxPG() + + pool, err := app.NewPoolPG(ctx) + if err != nil { + log.Error(err.Error()) + return + } + defer pool.Close() + + messageCase := message.New(mesRepo.NewMessageRepo(pool)) + + server := grpc.NewServer(grpc.UnaryInterceptor( + func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + f := metadata.ValueFromIncomingContext(ctx, string(auth.KeyCurrentUserID)) + if len(f) != 1 { + return nil, status.Error(codes.Unauthenticated, "unauthenticated") + } + userID, err := strconv.ParseInt(f[0], 10, 64) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "unauthenticated") + } + return handler(context.WithValue(ctx, auth.KeyCurrentUserID, int(userID)), req) + }, + )) + messenger.RegisterMessengerServer(server, messMS.New(log, messageCase)) + + l, err := net.Listen("tcp", "localhost:8095") + if err != nil { + log.Error(err.Error()) + return + } + + log.Info("server messenger start", logger.F{"addr", "localhost:8095"}) + if err := server.Serve(l); err != nil { + log.Error(err.Error()) + return + } +} diff --git a/internal/microservices/messenger/delivery/grpc/convert.go b/internal/microservices/messenger/delivery/grpc/convert.go new file mode 100644 index 0000000..fed3fd8 --- /dev/null +++ b/internal/microservices/messenger/delivery/grpc/convert.go @@ -0,0 +1,36 @@ +package grpc + +import ( + mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" +) + +func convertFeedChat(feed message.FeedUserChats) []*mess.ChatWithUser { + res := make([]*mess.ChatWithUser, len(feed)) + + for ind := range feed { + res[ind] = &mess.ChatWithUser{ + LastMessageID: int64(feed[ind].MessageLastID), + Chat: &mess.WichWhomChat{ + UserID: int64(feed[ind].WichWhomChat.ID), + Username: feed[ind].WichWhomChat.Username, + Avatar: feed[ind].WichWhomChat.Avatar, + }, + } + } + return res +} + +func convertFeedMessage(feed []message.Message) []*mess.Message { + res := make([]*mess.Message, len(feed)) + + for ind := range res { + res[ind] = &mess.Message{ + Id: &mess.MsgID{Id: int64(feed[ind].ID)}, + UserFrom: int64(feed[ind].From), + UserTo: int64(feed[ind].To), + Content: feed[ind].Content.String, + } + } + return res +} diff --git a/internal/microservices/messenger/delivery/grpc/server.go b/internal/microservices/messenger/delivery/grpc/server.go new file mode 100644 index 0000000..6764f5d --- /dev/null +++ b/internal/microservices/messenger/delivery/grpc/server.go @@ -0,0 +1,111 @@ +package grpc + +import ( + "context" + + mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/golang/protobuf/ptypes/empty" + "github.com/jackc/pgx/v5/pgtype" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type MessengerServer struct { + mess.UnimplementedMessengerServer + + log *logger.Logger + messageCase message.Usecase +} + +func New(log *logger.Logger, msgCase message.Usecase) MessengerServer { + return MessengerServer{ + log: log, + messageCase: msgCase, + } +} + +func (m MessengerServer) UserChatsWithOtherUsers(ctx context.Context, r *mess.FeedChatRequest) (*mess.FeedChat, error) { + userID := ctx.Value(auth.KeyCurrentUserID).(int) + + feed, lastID, err := m.messageCase.GetUserChatsWithOtherUsers(ctx, userID, int(r.GetCount()), int(r.GetLastID())) + if err != nil { + m.log.Error(err.Error()) + } + + return &mess.FeedChat{Chats: convertFeedChat(feed), LastID: int64(lastID)}, nil +} + +func (m MessengerServer) SendMessage(ctx context.Context, msg *mess.Message) (*mess.MsgID, error) { + userID := ctx.Value(auth.KeyCurrentUserID).(int) + + msgID, err := m.messageCase.SendMessage(ctx, &entity.Message{ + From: userID, + To: int(msg.UserTo), + Content: pgtype.Text{String: msg.GetContent(), Valid: true}, + }) + + if err != nil { + m.log.Error(err.Error()) + return nil, status.Error(codes.Internal, "send message error") + } + + return &mess.MsgID{Id: int64(msgID)}, nil +} + +func (m MessengerServer) MessageFromChat(ctx context.Context, r *mess.FeedMessageRequest) (*mess.FeedMessage, error) { + feed, lastID, err := m.messageCase.GetMessagesFromChat(ctx, entity.Chat{int(r.Chat.GetUserID1()), int(r.Chat.GetUserID2())}, + int(r.GetCount()), int(r.GetLastID())) + if err != nil { + m.log.Error(err.Error()) + } + + return &mess.FeedMessage{ + Messages: convertFeedMessage(feed), + LastID: int64(lastID), + }, nil +} + +func (m MessengerServer) UpdateMessage(ctx context.Context, msg *mess.Message) (*empty.Empty, error) { + userID := ctx.Value(auth.KeyCurrentUserID).(int) + + err := m.messageCase.UpdateContentMessage(ctx, userID, &entity.Message{ + ID: int(msg.Id.GetId()), + Content: pgtype.Text{String: msg.Content, Valid: true}, + }) + if err != nil { + return nil, status.Error(codes.Internal, "update message error") + } + + return &empty.Empty{}, nil +} + +func (m MessengerServer) DeleteMessage(ctx context.Context, msgID *mess.MsgID) (*empty.Empty, error) { + userID := ctx.Value(auth.KeyCurrentUserID).(int) + + err := m.messageCase.DeleteMessage(ctx, userID, int(msgID.GetId())) + if err != nil { + m.log.Error(err.Error()) + return nil, status.Error(codes.Internal, "delete message") + } + + return &empty.Empty{}, nil +} + +func (m MessengerServer) GetMessage(ctx context.Context, msgID *mess.MsgID) (*mess.Message, error) { + msg, err := m.messageCase.GetMessage(ctx, int(msgID.GetId())) + if err != nil { + m.log.Error(err.Error()) + return nil, status.Error(codes.Internal, "get message") + } + + return &mess.Message{ + Id: msgID, + UserFrom: int64(msg.From), + UserTo: int64(msg.To), + Content: msg.Content.String, + }, nil +} From c6d82742dc95b5b21f312095cf8a05786e77b0b3 Mon Sep 17 00:00:00 2001 From: Gvidow <96253031+Gvidow@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:17:18 +0300 Subject: [PATCH 218/266] Tp cc2 ms message (#19) * TP-cc2 update: ms messenger * TP-cc2 update: rename key metadata for auth * TP-cc2 add: metrics --- cmd/realtime/main.go | 13 ++- configs/prometheus.yml | 10 +- internal/app/app.go | 11 +- internal/app/auth/auth.go | 13 ++- internal/app/messenger/messenger.go | 31 +++-- .../messenger/delivery/grpc/server.go | 4 +- .../messenger}/usecase/message/check.go | 0 .../usecase/message/mock/message_mock.go | 95 +++++++++++++++ .../messenger/usecase/message/usecase.go | 80 +++++++++++++ internal/pkg/delivery/http/v1/chat.go | 6 +- internal/pkg/delivery/websocket/websocket.go | 8 +- internal/pkg/metrics/grpc/metrics.go | 82 +++++++++++++ .../pkg/middleware/grpc/interceptor/auth.go | 27 +++++ .../pkg/middleware/grpc/interceptor/logger.go | 19 +++ .../middleware/grpc/interceptor/metrics.go | 33 ++++++ internal/pkg/usecase/message/convert.go | 41 +++++++ internal/pkg/usecase/message/usecase.go | 108 ++++++++++++------ 17 files changed, 517 insertions(+), 64 deletions(-) rename internal/{pkg => microservices/messenger}/usecase/message/check.go (100%) create mode 100644 internal/microservices/messenger/usecase/message/mock/message_mock.go create mode 100644 internal/microservices/messenger/usecase/message/usecase.go create mode 100644 internal/pkg/metrics/grpc/metrics.go create mode 100644 internal/pkg/middleware/grpc/interceptor/auth.go create mode 100644 internal/pkg/middleware/grpc/interceptor/logger.go create mode 100644 internal/pkg/middleware/grpc/interceptor/metrics.go create mode 100644 internal/pkg/usecase/message/convert.go diff --git a/cmd/realtime/main.go b/cmd/realtime/main.go index ace3d44..84c0ecb 100644 --- a/cmd/realtime/main.go +++ b/cmd/realtime/main.go @@ -8,6 +8,8 @@ import ( rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/realtime" + grpcMetrics "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics/grpc" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/grpc/interceptor" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -30,12 +32,21 @@ func RealTimeRun(log *logger.Logger, addr string) error { return fmt.Errorf("listen tcp %s: %w", addr, err) } + metrics := grpcMetrics.New("realtime") + if err := metrics.Registry(); err != nil { + log.Error(err.Error()) + return err + } + node, err := realtime.NewNode() if err != nil { return fmt.Errorf("new server node: %w", err) } - serv := grpc.NewServer() + serv := grpc.NewServer(grpc.ChainUnaryInterceptor( + interceptor.Monitoring(metrics, "localhost:8091"), + interceptor.Logger(log), + )) rt.RegisterRealTimeServer(serv, realtime.NewServer(node)) log.Info("start realtime server", logger.F{"network", "tcp"}, logger.F{"addr", addr}) diff --git a/configs/prometheus.yml b/configs/prometheus.yml index be00934..2fcfe33 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -9,7 +9,15 @@ scrape_configs: - job_name: 'auth' static_configs: - - targets: ['host.docker.internal:8085'] + - targets: ['host.docker.internal:8086'] + + - job_name: 'messenger' + static_configs: + - targets: ['host.docker.internal:8096'] + + - job_name: 'realtime' + static_configs: + - targets: ['host.docker.internal:8091'] - job_name: 'node_exporter' static_configs: diff --git a/internal/app/app.go b/internal/app/app.go index bb3b167..0437156 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc/credentials/insecure" authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" @@ -17,7 +18,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" - mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" searchRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/search/postgres" subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription/postgres" @@ -57,8 +57,15 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer pool.Close() + connMessMS, err := grpc.Dial("localhost:8095", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Error(err.Error()) + return + } + defer connMessMS.Close() + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) - messageCase := message.New(mesRepo.NewMessageRepo(pool)) + messageCase := message.New(messenger.NewMessengerClient(connMessMS)) conn, err := grpc.Dial(cfg.AddrAuthServer, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { diff --git a/internal/app/auth/auth.go b/internal/app/auth/auth.go index d1c1fc9..e5a9aa7 100644 --- a/internal/app/auth/auth.go +++ b/internal/app/auth/auth.go @@ -11,6 +11,8 @@ import ( authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" authMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/auth/delivery/grpc" + grpcMetrics "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics/grpc" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/grpc/interceptor" sessRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/session" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" @@ -26,6 +28,12 @@ var ( func Run(ctx context.Context, log *logger.Logger, cfg Config) { godotenv.Load() + metrics := grpcMetrics.New("auth") + if err := metrics.Registry(); err != nil { + log.Error(err.Error()) + return + } + l, err := net.Listen("tcp", cfg.Addr) if err != nil { log.Error(err.Error()) @@ -62,7 +70,10 @@ func Run(ctx context.Context, log *logger.Logger, cfg Config) { sm := session.New(log, sessRepo.NewSessionRepo(redisCl)) u := user.New(log, nil, userRepo.NewUserRepoPG(pool)) - s := grpc.NewServer() + s := grpc.NewServer(grpc.ChainUnaryInterceptor( + interceptor.Monitoring(metrics, "localhost:8086"), + interceptor.Logger(log), + )) authProto.RegisterAuthServer(s, authMS.New(log, sm, u)) log.Info("service auht start", logger.F{"addr", cfg.Addr}) diff --git a/internal/app/messenger/messenger.go b/internal/app/messenger/messenger.go index bbcd8d0..a363334 100644 --- a/internal/app/messenger/messenger.go +++ b/internal/app/messenger/messenger.go @@ -3,21 +3,18 @@ package messenger import ( "context" "net" - "strconv" "time" "github.com/joho/godotenv" "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" messMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/delivery/grpc" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/usecase/message" + grpcMetrics "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics/grpc" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/grpc/interceptor" mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -26,6 +23,12 @@ const _timeoutForConnPG = 5 * time.Second func Run(ctx context.Context, log *logger.Logger) { godotenv.Load() + metrics := grpcMetrics.New("messenger") + if err := metrics.Registry(); err != nil { + log.Error(err.Error()) + return + } + ctx, cancelCtxPG := context.WithTimeout(ctx, _timeoutForConnPG) defer cancelCtxPG() @@ -38,18 +41,10 @@ func Run(ctx context.Context, log *logger.Logger) { messageCase := message.New(mesRepo.NewMessageRepo(pool)) - server := grpc.NewServer(grpc.UnaryInterceptor( - func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { - f := metadata.ValueFromIncomingContext(ctx, string(auth.KeyCurrentUserID)) - if len(f) != 1 { - return nil, status.Error(codes.Unauthenticated, "unauthenticated") - } - userID, err := strconv.ParseInt(f[0], 10, 64) - if err != nil { - return nil, status.Error(codes.Unauthenticated, "unauthenticated") - } - return handler(context.WithValue(ctx, auth.KeyCurrentUserID, int(userID)), req) - }, + server := grpc.NewServer(grpc.ChainUnaryInterceptor( + interceptor.Monitoring(metrics, "localhost:8096"), + interceptor.Logger(log), + interceptor.Auth(), )) messenger.RegisterMessengerServer(server, messMS.New(log, messageCase)) diff --git a/internal/microservices/messenger/delivery/grpc/server.go b/internal/microservices/messenger/delivery/grpc/server.go index 6764f5d..eeb23ce 100644 --- a/internal/microservices/messenger/delivery/grpc/server.go +++ b/internal/microservices/messenger/delivery/grpc/server.go @@ -4,9 +4,9 @@ import ( "context" mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/usecase/message" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/golang/protobuf/ptypes/empty" "github.com/jackc/pgx/v5/pgtype" @@ -14,6 +14,8 @@ import ( "google.golang.org/grpc/status" ) +const AuthenticatedMetadataKey = "user_id" + type MessengerServer struct { mess.UnimplementedMessengerServer diff --git a/internal/pkg/usecase/message/check.go b/internal/microservices/messenger/usecase/message/check.go similarity index 100% rename from internal/pkg/usecase/message/check.go rename to internal/microservices/messenger/usecase/message/check.go diff --git a/internal/microservices/messenger/usecase/message/mock/message_mock.go b/internal/microservices/messenger/usecase/message/mock/message_mock.go new file mode 100644 index 0000000..449be90 --- /dev/null +++ b/internal/microservices/messenger/usecase/message/mock/message_mock.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + message "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// DeleteMessage mocks base method. +func (m *MockUsecase) DeleteMessage(ctx context.Context, userID, mesID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteMessage", ctx, userID, mesID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteMessage indicates an expected call of DeleteMessage. +func (mr *MockUsecaseMockRecorder) DeleteMessage(ctx, userID, mesID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockUsecase)(nil).DeleteMessage), ctx, userID, mesID) +} + +// GetMessagesFromChat mocks base method. +func (m *MockUsecase) GetMessagesFromChat(ctx context.Context, chat message.Chat, count, lastID int) ([]message.Message, int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessagesFromChat", ctx, chat, count, lastID) + ret0, _ := ret[0].([]message.Message) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetMessagesFromChat indicates an expected call of GetMessagesFromChat. +func (mr *MockUsecaseMockRecorder) GetMessagesFromChat(ctx, chat, count, lastID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessagesFromChat", reflect.TypeOf((*MockUsecase)(nil).GetMessagesFromChat), ctx, chat, count, lastID) +} + +// SendMessage mocks base method. +func (m *MockUsecase) SendMessage(ctx context.Context, mes *message.Message) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMessage", ctx, mes) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendMessage indicates an expected call of SendMessage. +func (mr *MockUsecaseMockRecorder) SendMessage(ctx, mes interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockUsecase)(nil).SendMessage), ctx, mes) +} + +// UpdateContentMessage mocks base method. +func (m *MockUsecase) UpdateContentMessage(ctx context.Context, userID int, mes *message.Message) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateContentMessage", ctx, userID, mes) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateContentMessage indicates an expected call of UpdateContentMessage. +func (mr *MockUsecaseMockRecorder) UpdateContentMessage(ctx, userID, mes interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateContentMessage", reflect.TypeOf((*MockUsecase)(nil).UpdateContentMessage), ctx, userID, mes) +} diff --git a/internal/microservices/messenger/usecase/message/usecase.go b/internal/microservices/messenger/usecase/message/usecase.go new file mode 100644 index 0000000..d68c3b1 --- /dev/null +++ b/internal/microservices/messenger/usecase/message/usecase.go @@ -0,0 +1,80 @@ +package message + +import ( + "context" + "errors" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" +) + +var ErrNoAccess = errors.New("there is no access to perform this action") + +//go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=usecase.go Usecase +type Usecase interface { + GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, int, error) + SendMessage(ctx context.Context, mes *entity.Message) (int, error) + GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) + UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error + DeleteMessage(ctx context.Context, userID, mesID int) error + GetMessage(ctx context.Context, messageID int) (*entity.Message, error) +} + +type messageCase struct { + repo mesRepo.Repository +} + +func New(repo mesRepo.Repository) *messageCase { + return &messageCase{repo} +} + +func (m *messageCase) SendMessage(ctx context.Context, mes *entity.Message) (int, error) { + return m.repo.AddNewMessage(ctx, mes) +} + +func (m *messageCase) GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) { + feed, err = m.repo.GetMessages(ctx, chat, count, lastID) + if err != nil { + err = fmt.Errorf("get message: %w", err) + } + if len(feed) != 0 { + newLastID = feed[len(feed)-1].ID + } + return +} + +func (m *messageCase) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error { + if ok, err := m.isAvailableForChanges(ctx, userID, mes.ID); err != nil { + return fmt.Errorf("update message: %w", err) + } else if !ok { + return ErrNoAccess + } + return m.repo.UpdateContentMessage(ctx, mes.ID, mes.Content.String) +} + +func (m *messageCase) DeleteMessage(ctx context.Context, userID, mesID int) error { + if ok, err := m.isAvailableForChanges(ctx, userID, mesID); err != nil { + return fmt.Errorf("delete message: %w", err) + } else if !ok { + return ErrNoAccess + } + return m.repo.DelMessage(ctx, mesID) +} + +func (m *messageCase) GetMessage(ctx context.Context, messageID int) (*entity.Message, error) { + return m.repo.GetMessageByID(ctx, messageID) +} + +func (m *messageCase) GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, int, error) { + var newLastID int + feed, err := m.repo.GetUserChats(ctx, userID, count, lastID) + if len(feed) != 0 { + newLastID = feed[len(feed)-1].MessageLastID + } + + if err != nil { + return feed, newLastID, fmt.Errorf("get user chats with other users: %w", err) + } + return feed, newLastID, nil +} diff --git a/internal/pkg/delivery/http/v1/chat.go b/internal/pkg/delivery/http/v1/chat.go index 2d550bb..ac6adf5 100644 --- a/internal/pkg/delivery/http/v1/chat.go +++ b/internal/pkg/delivery/http/v1/chat.go @@ -18,6 +18,7 @@ func (h *HandlerHTTP) FeedChats(w http.ResponseWriter, r *http.Request) { if err != nil { log.Error(err.Error()) } + return } chats, newLastID, err := h.messageCase.GetUserChatsWithOtherUsers(r.Context(), userID, count, lastID) @@ -35,6 +36,7 @@ func (h *HandlerHTTP) FeedChats(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) SendMessageToUser(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) fromUserID := r.Context().Value(auth.KeyCurrentUserID).(int) toUserID, err := fetchURLParamInt(r, "userID") @@ -61,7 +63,7 @@ func (h *HandlerHTTP) SendMessageToUser(w http.ResponseWriter, r *http.Request) mes.From = fromUserID mes.To = toUserID - idNewMessage, err := h.messageCase.SendMessage(r.Context(), mes) + idNewMessage, err := h.messageCase.SendMessage(r.Context(), userID, mes) if err != nil { logger.Error(err.Error()) err = responseError(w, "send_message", "failed to send message") @@ -163,7 +165,7 @@ func (h *HandlerHTTP) GetMessagesFromChat(w http.ResponseWriter, r *http.Request return } - feed, newLastID, err := h.messageCase.GetMessagesFromChat(r.Context(), message.Chat{userID, userID2}, count, lastID) + feed, newLastID, err := h.messageCase.GetMessagesFromChat(r.Context(), userID, message.Chat{userID, userID2}, count, lastID) if err != nil { logger.Warn(err.Error()) } diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 581ec1b..6074730 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -86,7 +86,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn mesCopy := &message.Message{} *mesCopy = request.Message.Message mesCopy.From = userID - id, err := h.messageCase.SendMessage(ctx, mesCopy) + id, err := h.messageCase.SendMessage(ctx, userID, mesCopy) if err != nil { h.log.Warn(err.Error()) continue @@ -164,7 +164,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) } case "Subscribe": - err = h.subscribe(ctx, h.client, request, conn) + err = h.subscribe(ctx, h.client, request, conn, userID) if err != nil { h.log.Warn(err.Error()) wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "subscribe_fail", "failed to subscribe to the channel", nil)) @@ -177,7 +177,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn } } -func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClient, req *Request, conn *ws.Conn) error { +func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClient, req *Request, conn *ws.Conn, userID int) error { sc, err := client.Subscribe(ctx, &rt.Channel{ Name: req.Channel.Name, Topic: req.Channel.Topic, @@ -197,7 +197,7 @@ func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClie if mes.Object.Type == rt.EventType_EV_DELETE { msg = &message.Message{ID: int(mes.Object.Id)} } else { - msg, err = h.messageCase.GetMessage(ctx, int(mes.Object.Id)) + msg, err = h.messageCase.GetMessage(ctx, userID, int(mes.Object.Id)) if err != nil { h.log.Error(err.Error()) return diff --git a/internal/pkg/metrics/grpc/metrics.go b/internal/pkg/metrics/grpc/metrics.go new file mode 100644 index 0000000..45cf2c8 --- /dev/null +++ b/internal/pkg/metrics/grpc/metrics.go @@ -0,0 +1,82 @@ +package messenger + +import ( + "fmt" + "time" + + "github.com/prometheus/client_golang/prometheus" +) + +type metrics struct { + prefix string + totalHits prometheus.Counter + hitsDetailOk *prometheus.CounterVec + hitsDetailErr *prometheus.CounterVec + timeMeasurement *prometheus.HistogramVec +} + +func (m metrics) AddRequest(handler string, ok bool, executed time.Duration) { + m.totalHits.Inc() + if ok { + m.hitsDetailOk.WithLabelValues(handler).Inc() + } else { + m.hitsDetailErr.WithLabelValues(handler).Inc() + } + m.timeMeasurement.WithLabelValues(handler).Observe(float64(executed.Milliseconds())) +} + +func New(prefix string) metrics { + return metrics{ + prefix: prefix, + totalHits: prometheus.NewCounter(prometheus.CounterOpts{ + Name: prefix + "_total_hits", + Help: "number of all requests", + }), + hitsDetailOk: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: prefix + "_hits_detail_ok", + Help: "the number of requests indicating handler with normal status", + }, []string{"handler"}), + hitsDetailErr: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: prefix + "_hits_detail_err", + Help: "the number of requests indicating its handler with error status", + }, []string{"handler"}), + timeMeasurement: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: prefix + "_request_time_execution", + Help: "time of request execution with indication of its handler", + Buckets: []float64{10, 100, 500, 1000, 5000}, + }, []string{"handler"}), + } +} + +func (m metrics) Registry() (err error) { + defer func() { + if err != nil { + prometheus.Unregister(m.totalHits) + prometheus.Unregister(m.hitsDetailOk) + prometheus.Unregister(m.timeMeasurement) + prometheus.Unregister(m.hitsDetailErr) + } + }() + + err = prometheus.Register(m.totalHits) + if err != nil { + return fmt.Errorf("registry metric total hits: %w", err) + } + + err = prometheus.Register(m.hitsDetailOk) + if err != nil { + return fmt.Errorf("registry metric hits detail ok: %w", err) + } + + err = prometheus.Register(m.hitsDetailErr) + if err != nil { + return fmt.Errorf("registry metric hits detail error: %w", err) + } + + err = prometheus.Register(m.timeMeasurement) + if err != nil { + return fmt.Errorf("registry metric time measurement: %w", err) + } + + return nil +} diff --git a/internal/pkg/middleware/grpc/interceptor/auth.go b/internal/pkg/middleware/grpc/interceptor/auth.go new file mode 100644 index 0000000..8fd862f --- /dev/null +++ b/internal/pkg/middleware/grpc/interceptor/auth.go @@ -0,0 +1,27 @@ +package interceptor + +import ( + "context" + "strconv" + + messMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/delivery/grpc" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +func Auth() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + md := metadata.ValueFromIncomingContext(ctx, messMS.AuthenticatedMetadataKey) + if len(md) != 1 { + return nil, status.Error(codes.Unauthenticated, "unauthenticated") + } + userID, err := strconv.ParseInt(md[0], 10, 64) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "unauthenticated") + } + return handler(context.WithValue(ctx, auth.KeyCurrentUserID, int(userID)), req) + } +} diff --git a/internal/pkg/middleware/grpc/interceptor/logger.go b/internal/pkg/middleware/grpc/interceptor/logger.go new file mode 100644 index 0000000..089dfe0 --- /dev/null +++ b/internal/pkg/middleware/grpc/interceptor/logger.go @@ -0,0 +1,19 @@ +package interceptor + +import ( + "context" + "time" + + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "google.golang.org/grpc" +) + +func Logger(log *logger.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + defer func(t time.Time) { + log.Info("call rpc", logger.F{"handler", info.FullMethod}, logger.F{"time_execution", time.Since(t).Milliseconds()}) + }(time.Now()) + res, err := handler(ctx, req) + return res, err + } +} diff --git a/internal/pkg/middleware/grpc/interceptor/metrics.go b/internal/pkg/middleware/grpc/interceptor/metrics.go new file mode 100644 index 0000000..2e24abc --- /dev/null +++ b/internal/pkg/middleware/grpc/interceptor/metrics.go @@ -0,0 +1,33 @@ +package interceptor + +import ( + "context" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "google.golang.org/grpc" +) + +type Metrics interface { + AddRequest(handler string, ok bool, executed time.Duration) +} + +func Monitoring(m Metrics, addres string) grpc.UnaryServerInterceptor { + serv := http.Server{ + Addr: addres, + Handler: promhttp.Handler(), + } + go func() { serv.ListenAndServe() }() + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + var ok = true + defer func(t time.Time) { + m.AddRequest(info.FullMethod, ok, time.Since(t)) + }(time.Now()) + res, err := handler(ctx, req) + if err != nil { + ok = false + } + return res, err + } +} diff --git a/internal/pkg/usecase/message/convert.go b/internal/pkg/usecase/message/convert.go new file mode 100644 index 0000000..847065e --- /dev/null +++ b/internal/pkg/usecase/message/convert.go @@ -0,0 +1,41 @@ +package message + +import ( + "github.com/jackc/pgx/v5/pgtype" + + mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +func convertFeedMessage(feed *mess.FeedMessage) []entity.Message { + res := make([]entity.Message, len(feed.Messages)) + for ind := range feed.Messages { + res[ind] = entity.Message{ + ID: int(feed.Messages[ind].GetId().Id), + From: int(feed.Messages[ind].UserFrom), + To: int(feed.Messages[ind].UserTo), + Content: pgtype.Text{ + String: feed.Messages[ind].GetContent(), + Valid: true, + }, + } + } + return res +} + +func convertFeedChat(feed *mess.FeedChat) entity.FeedUserChats { + res := make(entity.FeedUserChats, len(feed.Chats)) + + for ind := range feed.Chats { + res[ind] = entity.ChatWithUser{ + MessageLastID: int(feed.Chats[ind].GetLastMessageID()), + WichWhomChat: user.User{ + ID: int(feed.Chats[ind].GetChat().GetUserID()), + Username: feed.Chats[ind].GetChat().GetUsername(), + Avatar: feed.Chats[ind].GetChat().GetAvatar(), + }, + } + } + return res +} diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index d68c3b1..4cf8794 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -4,9 +4,14 @@ import ( "context" "errors" "fmt" + "strconv" + "github.com/jackc/pgx/v5/pgtype" + "google.golang.org/grpc/metadata" + + mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + messMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/delivery/grpc" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" - mesRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/message" ) var ErrNoAccess = errors.New("there is no access to perform this action") @@ -14,67 +19,102 @@ var ErrNoAccess = errors.New("there is no access to perform this action") //go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, int, error) - SendMessage(ctx context.Context, mes *entity.Message) (int, error) - GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) + SendMessage(ctx context.Context, userID int, mes *entity.Message) (int, error) + GetMessagesFromChat(ctx context.Context, userID int, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error DeleteMessage(ctx context.Context, userID, mesID int) error - GetMessage(ctx context.Context, messageID int) (*entity.Message, error) + GetMessage(ctx context.Context, userID int, messageID int) (*entity.Message, error) } type messageCase struct { - repo mesRepo.Repository + client mess.MessengerClient } -func New(repo mesRepo.Repository) *messageCase { +func New(repo mess.MessengerClient) *messageCase { return &messageCase{repo} } -func (m *messageCase) SendMessage(ctx context.Context, mes *entity.Message) (int, error) { - return m.repo.AddNewMessage(ctx, mes) +func (m *messageCase) SendMessage(ctx context.Context, userID int, mes *entity.Message) (int, error) { + msgID, err := m.client.SendMessage(setAuthenticatedMetadataCtx(ctx, userID), &mess.Message{ + UserFrom: int64(userID), + UserTo: int64(mes.To), + Content: mes.Content.String, + }) + if err != nil { + return 0, fmt.Errorf("send message by grpc client") + } + return int(msgID.GetId()), nil } -func (m *messageCase) GetMessagesFromChat(ctx context.Context, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) { - feed, err = m.repo.GetMessages(ctx, chat, count, lastID) +func (m *messageCase) GetMessagesFromChat(ctx context.Context, userID int, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) { + feedMsg, err := m.client.MessageFromChat(setAuthenticatedMetadataCtx(ctx, userID), &mess.FeedMessageRequest{ + Chat: &mess.Chat{ + UserID1: int64(chat[0]), + UserID2: int64(chat[1]), + }, + Count: int64(count), + LastID: int64(count), + }) if err != nil { - err = fmt.Errorf("get message: %w", err) + err = fmt.Errorf("get message by : %w", err) } - if len(feed) != 0 { - newLastID = feed[len(feed)-1].ID + if feedMsg == nil { + return nil, 0, err } - return + + return convertFeedMessage(feedMsg), int(feedMsg.LastID), nil } func (m *messageCase) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error { - if ok, err := m.isAvailableForChanges(ctx, userID, mes.ID); err != nil { - return fmt.Errorf("update message: %w", err) - } else if !ok { - return ErrNoAccess + if _, err := m.client.UpdateMessage(setAuthenticatedMetadataCtx(ctx, userID), &mess.Message{ + Id: &mess.MsgID{ + Id: int64(mes.ID), + }, + Content: mes.Content.String, + }); err != nil { + return fmt.Errorf("update messege by grpc client") } - return m.repo.UpdateContentMessage(ctx, mes.ID, mes.Content.String) + return nil } func (m *messageCase) DeleteMessage(ctx context.Context, userID, mesID int) error { - if ok, err := m.isAvailableForChanges(ctx, userID, mesID); err != nil { - return fmt.Errorf("delete message: %w", err) - } else if !ok { - return ErrNoAccess + if _, err := m.client.DeleteMessage(setAuthenticatedMetadataCtx(ctx, userID), &mess.MsgID{Id: int64(mesID)}); err != nil { + return fmt.Errorf("delete messege by grpc client") } - return m.repo.DelMessage(ctx, mesID) + return nil } -func (m *messageCase) GetMessage(ctx context.Context, messageID int) (*entity.Message, error) { - return m.repo.GetMessageByID(ctx, messageID) +func (m *messageCase) GetMessage(ctx context.Context, userID int, messageID int) (*entity.Message, error) { + mes, err := m.client.GetMessage(setAuthenticatedMetadataCtx(ctx, userID), &mess.MsgID{Id: int64(messageID)}) + if err != nil { + return nil, fmt.Errorf("get message by grpc client") + } + return &entity.Message{ + ID: int(mes.GetId().Id), + From: int(mes.GetUserFrom()), + To: int(mes.GetUserTo()), + Content: pgtype.Text{ + String: mes.Content, + Valid: true, + }, + }, nil } func (m *messageCase) GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (entity.FeedUserChats, int, error) { - var newLastID int - feed, err := m.repo.GetUserChats(ctx, userID, count, lastID) - if len(feed) != 0 { - newLastID = feed[len(feed)-1].MessageLastID - } - + feed, err := m.client.UserChatsWithOtherUsers(setAuthenticatedMetadataCtx(ctx, userID), &mess.FeedChatRequest{ + Count: int64(count), + LastID: int64(lastID), + }) + var errRes error if err != nil { - return feed, newLastID, fmt.Errorf("get user chats with other users: %w", err) + errRes = fmt.Errorf("get user chats by grpc client: %w", err) + } + if feed == nil { + return nil, 0, errRes } - return feed, newLastID, nil + return convertFeedChat(feed), int(feed.GetLastID()), errRes +} + +func setAuthenticatedMetadataCtx(ctx context.Context, userID int) context.Context { + return metadata.AppendToOutgoingContext(ctx, messMS.AuthenticatedMetadataKey, strconv.FormatInt(int64(userID), 10)) } From dfc327893b6e2e93114cb1487a7801474ce26f4b Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 29 Nov 2023 21:24:59 +0300 Subject: [PATCH 219/266] dev3: add author_username field in GetCertainBoard --- go.mod | 8 --- go.sum | 26 -------- internal/pkg/delivery/http/v1/board.go | 61 ++++++++++--------- internal/pkg/entity/board/board.go | 2 +- .../pkg/repository/board/mock/board_mock.go | 21 ++++++- .../pkg/repository/board/postgres/repo.go | 16 ++--- internal/pkg/repository/board/repo.go | 2 +- .../repository/message/mock/message_mock.go | 15 +++++ internal/pkg/usecase/board/get.go | 17 +++--- internal/pkg/usecase/board/mock/board_mock.go | 21 ++++++- internal/pkg/usecase/board/usecase.go | 2 +- internal/pkg/usecase/board/usecase_test.go | 16 +++-- .../pkg/usecase/message/mock/message_mock.go | 31 ++++++++++ 13 files changed, 143 insertions(+), 95 deletions(-) diff --git a/go.mod b/go.mod index f8b2c99..f2512ed 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/joho/godotenv v1.5.1 - github.com/labstack/echo/v4 v4.11.3 github.com/microcosm-cc/bluemonday v1.0.26 github.com/pashagolub/pgxmock/v2 v2.12.0 github.com/prometheus/client_golang v1.17.0 @@ -52,7 +51,6 @@ require ( github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/css v1.0.0 // indirect @@ -69,12 +67,9 @@ require ( github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.16.7 // indirect - github.com/labstack/gommon v0.4.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -86,15 +81,12 @@ require ( github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tdewolff/minify/v2 v2.20.5 // indirect github.com/tdewolff/parse/v2 v2.7.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 155b082..c53b878 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,6 @@ github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d h1:HrdwTlHVMdi9nOW7ZnYiLmIT1hJHvipIwM0aX3rKn8I= github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -133,10 +131,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= -github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -147,13 +141,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= @@ -213,11 +200,6 @@ github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9 github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -273,14 +255,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -294,8 +271,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -333,7 +308,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 317e1c4..910062b 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -26,6 +26,7 @@ type BoardData struct { // board view for delivery layer type CertainBoard struct { ID int `json:"board_id" example:"22"` + AuthorID int `json:"author_id" example:"22"` Title string `json:"title" example:"new board"` Description string `json:"description" example:"long desc"` CreatedAt string `json:"created_at" example:"07-11-2023"` @@ -34,6 +35,18 @@ type CertainBoard struct { Tags []string `json:"tags" example:"['love', 'green']"` } +type CertainBoardWithUsername struct { + ID int `json:"board_id" example:"22"` + AuthorID int `json:"author_id" example:"22"` + AuthorUsername string `json:"author_username" example:"Bob"` + Title string `json:"title" example:"new board"` + Description string `json:"description" example:"long desc"` + CreatedAt string `json:"created_at" example:"07-11-2023"` + PinsNumber int `json:"pins_number" example:"12"` + Pins []string `json:"pins" example:"['/pic1', '/pic2']"` + Tags []string `json:"tags" example:"['love', 'green']"` +} + type DeletePinFromBoard struct { PinID int `json:"pin_id" example:"22"` } @@ -41,6 +54,7 @@ type DeletePinFromBoard struct { func ToCertainBoardFromService(board entity.BoardWithContent) CertainBoard { return CertainBoard{ ID: board.BoardInfo.ID, + AuthorID: board.BoardInfo.AuthorID, Title: board.BoardInfo.Title, Description: board.BoardInfo.Description, CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), @@ -50,6 +64,20 @@ func ToCertainBoardFromService(board entity.BoardWithContent) CertainBoard { } } +func ToCertainBoardUsernameFromService(board entity.BoardWithContent, username string) CertainBoardWithUsername { + return CertainBoardWithUsername{ + ID: board.BoardInfo.ID, + AuthorID: board.BoardInfo.AuthorID, + AuthorUsername: username, + Title: board.BoardInfo.Title, + Description: board.BoardInfo.Description, + CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), + PinsNumber: board.PinsNumber, + Pins: board.Pins, + Tags: board.TagTitles, + } +} + func (data *BoardData) Validate() error { if data.Title == nil || *data.Title == "" { return ErrInvalidBoardTitle @@ -167,7 +195,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { return } - board, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) + board, username, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) if err != nil { logger.Info("get certain board", log.F{"message", err.Error()}) code, message := getErrCodeMessage(err) @@ -175,7 +203,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(http.StatusOK, w, "got certain board successfully", ToCertainBoardFromService(board)) + err = responseOk(http.StatusOK, w, "got certain board successfully", ToCertainBoardUsernameFromService(board, username)) if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) @@ -196,7 +224,7 @@ func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Reque board, tagTitles, err := h.boardCase.GetBoardInfoForUpdate(r.Context(), int(boardID)) if err != nil { - logger.Info("get certain board", log.F{"message", err.Error()}) + logger.Info("get certain board info for update", log.F{"message", err.Error()}) code, message := getErrCodeMessage(err) responseError(w, code, message) return @@ -397,30 +425,3 @@ func (h *HandlerHTTP) DeletePinFromBoard(w http.ResponseWriter, r *http.Request) w.Write([]byte(ErrInternalError.Error())) } } - -/* -logger := h.getRequestLogger(r) - - boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) - if err != nil { - logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadUrlParam) - responseError(w, code, message) - return - } - - err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) - if err != nil { - logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) - responseError(w, code, message) - return - } - - err = responseOk(http.StatusOK, w, "deleted board successfully", nil) - if err != nil { - logger.Error(err.Error()) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) - } -*/ diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go index 92c37b6..cf783a5 100644 --- a/internal/pkg/entity/board/board.go +++ b/internal/pkg/entity/board/board.go @@ -8,7 +8,7 @@ import ( type Board struct { ID int `json:"id,omitempty" example:"15"` - AuthorID int `json:"-"` + AuthorID int `json:"author_id,omitempty"` Title string `json:"title" example:"Sunny places"` Description string `json:"description" example:"Sunny places desc"` Public bool `json:"public" example:"true"` diff --git a/internal/pkg/repository/board/mock/board_mock.go b/internal/pkg/repository/board/mock/board_mock.go index d2af370..d1317aa 100644 --- a/internal/pkg/repository/board/mock/board_mock.go +++ b/internal/pkg/repository/board/mock/board_mock.go @@ -80,6 +80,20 @@ func (mr *MockRepositoryMockRecorder) DeleteBoardByID(ctx, boardID interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoardByID", reflect.TypeOf((*MockRepository)(nil).DeleteBoardByID), ctx, boardID) } +// DeletePinFromBoard mocks base method. +func (m *MockRepository) DeletePinFromBoard(ctx context.Context, boardID, pinID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePinFromBoard", ctx, boardID, pinID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePinFromBoard indicates an expected call of DeletePinFromBoard. +func (mr *MockRepositoryMockRecorder) DeletePinFromBoard(ctx, boardID, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromBoard", reflect.TypeOf((*MockRepository)(nil).DeletePinFromBoard), ctx, boardID, pinID) +} + // GetBoardAuthorByBoardID mocks base method. func (m *MockRepository) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) { m.ctrl.T.Helper() @@ -96,12 +110,13 @@ func (mr *MockRepositoryMockRecorder) GetBoardAuthorByBoardID(ctx, boardID inter } // GetBoardByID mocks base method. -func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board.BoardWithContent, error) { +func (m *MockRepository) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board.BoardWithContent, string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBoardByID", ctx, boardID, hasAccess) ret0, _ := ret[0].(board.BoardWithContent) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // GetBoardByID indicates an expected call of GetBoardByID. diff --git a/internal/pkg/repository/board/postgres/repo.go b/internal/pkg/repository/board/postgres/repo.go index 1c92ad1..80b5325 100644 --- a/internal/pkg/repository/board/postgres/repo.go +++ b/internal/pkg/repository/board/postgres/repo.go @@ -114,11 +114,12 @@ func (boardRepo *boardRepoPG) GetBoardsByUserID(ctx context.Context, userID int, return boards, nil } -func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board entity.BoardWithContent, err error) { +func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board entity.BoardWithContent, username string, err error) { getBoardByIdQuery := repo.sqlBuilder. Select( "board.id", "board.author", + "profile.username", "board.title", "COALESCE(board.description, '')", "board.created_at", @@ -126,6 +127,7 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces "COALESCE((ARRAY_AGG(DISTINCT pin.picture) FILTER (WHERE pin.deleted_at IS NULL AND pin.picture IS NOT NULL)), ARRAY[]::TEXT[]) AS pins", "COALESCE(ARRAY_AGG(DISTINCT tag.title) FILTER (WHERE tag.title IS NOT NULL), ARRAY[]::TEXT[]) AS tag_titles"). From("board"). + LeftJoin("profile ON board.author = profile.id"). LeftJoin("board_tag ON board.id = board_tag.board_id"). LeftJoin("tag ON board_tag.tag_id = tag.id"). LeftJoin("membership ON board.id = membership.board_id"). @@ -139,6 +141,7 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces getBoardByIdQuery = getBoardByIdQuery.GroupBy( "board.id", "board.author", + "profile.username", "board.title", "board.description", "board.created_at"). @@ -146,22 +149,21 @@ func (repo *boardRepoPG) GetBoardByID(ctx context.Context, boardID int, hasAcces sqlRow, args, err := getBoardByIdQuery.ToSql() if err != nil { - return entity.BoardWithContent{}, fmt.Errorf("building get board by id query: %w", err) + return entity.BoardWithContent{}, "", fmt.Errorf("building get board by id query: %w", err) } row := repo.db.QueryRow(ctx, sqlRow, args...) board = entity.BoardWithContent{} - err = row.Scan(&board.BoardInfo.ID, &board.BoardInfo.AuthorID, &board.BoardInfo.Title, &board.BoardInfo.Description, &board.BoardInfo.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) + err = row.Scan(&board.BoardInfo.ID, &board.BoardInfo.AuthorID, &username, &board.BoardInfo.Title, &board.BoardInfo.Description, &board.BoardInfo.CreatedAt, &board.PinsNumber, &board.Pins, &board.TagTitles) if err != nil { switch err { case pgx.ErrNoRows: - return entity.BoardWithContent{}, repository.ErrNoData + return entity.BoardWithContent{}, "", repository.ErrNoData default: - return entity.BoardWithContent{}, fmt.Errorf("scan result of get board by id query: %w", err) + return entity.BoardWithContent{}, "", fmt.Errorf("scan result of get board by id query: %w", err) } } - - return board, nil + return board, username, nil } func (repo *boardRepoPG) GetBoardInfoForUpdate(ctx context.Context, boardID int, hasAccess bool) (entity.Board, []string, error) { diff --git a/internal/pkg/repository/board/repo.go b/internal/pkg/repository/board/repo.go index 7c6634f..d6a2b39 100644 --- a/internal/pkg/repository/board/repo.go +++ b/internal/pkg/repository/board/repo.go @@ -11,7 +11,7 @@ import ( type Repository interface { CreateBoard(ctx context.Context, board entity.Board, tagTitles []string) (int, error) GetBoardsByUserID(ctx context.Context, userID int, isAuthor bool, accessableBoardsIDs []int) ([]entity.BoardWithContent, error) - GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board entity.BoardWithContent, err error) + GetBoardByID(ctx context.Context, boardID int, hasAccess bool) (board entity.BoardWithContent, username string, err error) GetBoardAuthorByBoardID(ctx context.Context, boardID int) (int, error) GetContributorsByBoardID(ctx context.Context, boardID int) ([]uEntity.User, error) GetContributorBoardsIDs(ctx context.Context, contributorID int) ([]int, error) diff --git a/internal/pkg/repository/message/mock/message_mock.go b/internal/pkg/repository/message/mock/message_mock.go index 275535a..b7b9a0e 100644 --- a/internal/pkg/repository/message/mock/message_mock.go +++ b/internal/pkg/repository/message/mock/message_mock.go @@ -94,6 +94,21 @@ func (mr *MockRepositoryMockRecorder) GetMessages(ctx, chat, count, lastID inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessages", reflect.TypeOf((*MockRepository)(nil).GetMessages), ctx, chat, count, lastID) } +// GetUserChats mocks base method. +func (m *MockRepository) GetUserChats(ctx context.Context, userID, count, lastID int) (message.FeedUserChats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserChats", ctx, userID, count, lastID) + ret0, _ := ret[0].(message.FeedUserChats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserChats indicates an expected call of GetUserChats. +func (mr *MockRepositoryMockRecorder) GetUserChats(ctx, userID, count, lastID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserChats", reflect.TypeOf((*MockRepository)(nil).GetUserChats), ctx, userID, count, lastID) +} + // UpdateContentMessage mocks base method. func (m *MockRepository) UpdateContentMessage(ctx context.Context, messageID int, newContent string) error { m.ctrl.T.Helper() diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index 9e02946..e2555cd 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -42,20 +42,20 @@ func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username str return boards, nil } -func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (entity.BoardWithContent, error) { +func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (entity.BoardWithContent, string, error) { boardAuthorID, err := bCase.boardRepo.GetBoardAuthorByBoardID(ctx, boardID) if err != nil { switch err { case repository.ErrNoData: - return entity.BoardWithContent{}, ErrNoSuchBoard + return entity.BoardWithContent{}, "", ErrNoSuchBoard default: - return entity.BoardWithContent{}, fmt.Errorf("get certain board: %w", err) + return entity.BoardWithContent{}, "", fmt.Errorf("get certain board: %w", err) } } boardContributors, err := bCase.boardRepo.GetContributorsByBoardID(ctx, boardID) if err != nil { - return entity.BoardWithContent{}, fmt.Errorf("get certain board: %w", err) + return entity.BoardWithContent{}, "", fmt.Errorf("get certain board: %w", err) } boardContributorsIDs := make([]int, 0, len(boardContributors)) @@ -70,18 +70,17 @@ func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (en hasAccess = true } - board, err := bCase.boardRepo.GetBoardByID(ctx, boardID, hasAccess) + board, username, err := bCase.boardRepo.GetBoardByID(ctx, boardID, hasAccess) if err != nil { switch err { case repository.ErrNoData: - return entity.BoardWithContent{}, ErrNoSuchBoard + return entity.BoardWithContent{}, "", ErrNoSuchBoard default: - return entity.BoardWithContent{}, fmt.Errorf("get certain board: %w", err) + return entity.BoardWithContent{}, "", fmt.Errorf("get certain board: %w", err) } } - board.Sanitize(bCase.sanitizer) - return board, nil + return board, username, nil } func isContributor(contributorsIDs []int, userID int) bool { diff --git a/internal/pkg/usecase/board/mock/board_mock.go b/internal/pkg/usecase/board/mock/board_mock.go index 39845c3..1b14189 100644 --- a/internal/pkg/usecase/board/mock/board_mock.go +++ b/internal/pkg/usecase/board/mock/board_mock.go @@ -79,6 +79,20 @@ func (mr *MockUsecaseMockRecorder) DeleteCertainBoard(ctx, boardID interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCertainBoard", reflect.TypeOf((*MockUsecase)(nil).DeleteCertainBoard), ctx, boardID) } +// DeletePinFromBoard mocks base method. +func (m *MockUsecase) DeletePinFromBoard(ctx context.Context, boardID, pinID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePinFromBoard", ctx, boardID, pinID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePinFromBoard indicates an expected call of DeletePinFromBoard. +func (mr *MockUsecaseMockRecorder) DeletePinFromBoard(ctx, boardID, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePinFromBoard", reflect.TypeOf((*MockUsecase)(nil).DeletePinFromBoard), ctx, boardID, pinID) +} + // FixPinsOnBoard mocks base method. func (m *MockUsecase) FixPinsOnBoard(ctx context.Context, boardID int, pinIds []int, userID int) error { m.ctrl.T.Helper() @@ -125,12 +139,13 @@ func (mr *MockUsecaseMockRecorder) GetBoardsByUsername(ctx, username interface{} } // GetCertainBoard mocks base method. -func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board.BoardWithContent, error) { +func (m *MockUsecase) GetCertainBoard(ctx context.Context, boardID int) (board.BoardWithContent, string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCertainBoard", ctx, boardID) ret0, _ := ret[0].(board.BoardWithContent) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // GetCertainBoard indicates an expected call of GetCertainBoard. diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index 2fefc44..a00f980 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -15,7 +15,7 @@ import ( type Usecase interface { CreateNewBoard(ctx context.Context, newBoard entity.Board, tagTitles []string) (int, error) GetBoardsByUsername(ctx context.Context, username string) ([]entity.BoardWithContent, error) - GetCertainBoard(ctx context.Context, boardID int) (entity.BoardWithContent, error) + GetCertainBoard(ctx context.Context, boardID int) (entity.BoardWithContent, string, error) GetBoardInfoForUpdate(ctx context.Context, boardID int) (entity.Board, []string, error) UpdateBoardInfo(ctx context.Context, updatedBoard entity.Board, tagTitles []string) error DeleteCertainBoard(ctx context.Context, boardID int) error diff --git a/internal/pkg/usecase/board/usecase_test.go b/internal/pkg/usecase/board/usecase_test.go index f3c20dd..251d70c 100644 --- a/internal/pkg/usecase/board/usecase_test.go +++ b/internal/pkg/usecase/board/usecase_test.go @@ -339,6 +339,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { GetBoardByID GetBoardByID hasAccess bool expBoard entity.BoardWithContent + expUsername string wantErr bool expErr error }{ @@ -363,7 +364,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { PinsNumber: 1, Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, - }, nil).Times(1) + }, "user", nil).Times(1) }, hasAccess: true, expBoard: entity.BoardWithContent{ @@ -377,6 +378,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, }, + expUsername: "user", }, { name: "private board, valid board id, request from contributor", @@ -399,7 +401,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { PinsNumber: 1, Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, - }, nil).Times(1) + }, "user", nil).Times(1) }, hasAccess: true, expBoard: entity.BoardWithContent{ @@ -413,6 +415,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, }, + expUsername: "user", }, { name: "private board, valid board id, request from not author, not contributor", @@ -425,7 +428,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{}, repository.ErrNoData).Times(1) + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{}, "", repository.ErrNoData).Times(1) }, hasAccess: false, expBoard: entity.BoardWithContent{}, @@ -443,7 +446,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { mockRepo.EXPECT().GetContributorsByBoardID(ctx, boardID).Return([]uEntity.User{{ID: 123}}, nil).Times(1) }, GetBoardByID: func(mockRepo *mock_board.MockRepository, ctx context.Context, boardID int, hasAccess bool) { - mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{}, repository.ErrNoData).Times(1) + mockRepo.EXPECT().GetBoardByID(ctx, boardID, hasAccess).Return(entity.BoardWithContent{}, "", repository.ErrNoData).Times(1) }, hasAccess: false, expBoard: entity.BoardWithContent{}, @@ -471,7 +474,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { PinsNumber: 1, Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, - }, nil).Times(1) + }, "user", nil).Times(1) }, hasAccess: false, expBoard: entity.BoardWithContent{ @@ -485,6 +488,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { Pins: []string{"/pic1"}, TagTitles: []string{"good", "bad"}, }, + expUsername: "user", }, { name: "invalid board id", @@ -514,7 +518,7 @@ func TestBoardUsecase_GetCertainBoard(t *testing.T) { test.GetBoardByID(mockBoardRepo, test.inCtx, test.boardID, test.hasAccess) boardUsecase := New(log, mockBoardRepo, nil, sanitizer) - board, err := boardUsecase.GetCertainBoard(test.inCtx, test.boardID) + board, _, err := boardUsecase.GetCertainBoard(test.inCtx, test.boardID) if test.wantErr { require.EqualError(t, err, test.expErr.Error()) diff --git a/internal/pkg/usecase/message/mock/message_mock.go b/internal/pkg/usecase/message/mock/message_mock.go index 449be90..65e877a 100644 --- a/internal/pkg/usecase/message/mock/message_mock.go +++ b/internal/pkg/usecase/message/mock/message_mock.go @@ -49,6 +49,21 @@ func (mr *MockUsecaseMockRecorder) DeleteMessage(ctx, userID, mesID interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockUsecase)(nil).DeleteMessage), ctx, userID, mesID) } +// GetMessage mocks base method. +func (m *MockUsecase) GetMessage(ctx context.Context, messageID int) (*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessage", ctx, messageID) + ret0, _ := ret[0].(*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMessage indicates an expected call of GetMessage. +func (mr *MockUsecaseMockRecorder) GetMessage(ctx, messageID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockUsecase)(nil).GetMessage), ctx, messageID) +} + // GetMessagesFromChat mocks base method. func (m *MockUsecase) GetMessagesFromChat(ctx context.Context, chat message.Chat, count, lastID int) ([]message.Message, int, error) { m.ctrl.T.Helper() @@ -65,6 +80,22 @@ func (mr *MockUsecaseMockRecorder) GetMessagesFromChat(ctx, chat, count, lastID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessagesFromChat", reflect.TypeOf((*MockUsecase)(nil).GetMessagesFromChat), ctx, chat, count, lastID) } +// GetUserChatsWithOtherUsers mocks base method. +func (m *MockUsecase) GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (message.FeedUserChats, int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserChatsWithOtherUsers", ctx, userID, count, lastID) + ret0, _ := ret[0].(message.FeedUserChats) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetUserChatsWithOtherUsers indicates an expected call of GetUserChatsWithOtherUsers. +func (mr *MockUsecaseMockRecorder) GetUserChatsWithOtherUsers(ctx, userID, count, lastID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserChatsWithOtherUsers", reflect.TypeOf((*MockUsecase)(nil).GetUserChatsWithOtherUsers), ctx, userID, count, lastID) +} + // SendMessage mocks base method. func (m *MockUsecase) SendMessage(ctx context.Context, mes *message.Message) (int, error) { m.ctrl.T.Helper() From 8591ba1ebb654cc7b61873d7abf4157a0f7e3d15 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 29 Nov 2023 22:34:50 +0300 Subject: [PATCH 220/266] dev3 update: address for metrics servers --- cmd/realtime/main.go | 2 +- configs/prometheus.yml | 6 +++--- internal/app/auth/auth.go | 2 +- internal/app/messenger/messenger.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/realtime/main.go b/cmd/realtime/main.go index 84c0ecb..a68532f 100644 --- a/cmd/realtime/main.go +++ b/cmd/realtime/main.go @@ -44,7 +44,7 @@ func RealTimeRun(log *logger.Logger, addr string) error { } serv := grpc.NewServer(grpc.ChainUnaryInterceptor( - interceptor.Monitoring(metrics, "localhost:8091"), + interceptor.Monitoring(metrics, "0.0.0.0:8091"), interceptor.Logger(log), )) rt.RegisterRealTimeServer(serv, realtime.NewServer(node)) diff --git a/configs/prometheus.yml b/configs/prometheus.yml index 2fcfe33..255697d 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -9,15 +9,15 @@ scrape_configs: - job_name: 'auth' static_configs: - - targets: ['host.docker.internal:8086'] + - targets: ['pinspire.online:8086'] - job_name: 'messenger' static_configs: - - targets: ['host.docker.internal:8096'] + - targets: ['pinspire.online:8096'] - job_name: 'realtime' static_configs: - - targets: ['host.docker.internal:8091'] + - targets: ['pinspire.online:8091'] - job_name: 'node_exporter' static_configs: diff --git a/internal/app/auth/auth.go b/internal/app/auth/auth.go index e5a9aa7..cfcfbd6 100644 --- a/internal/app/auth/auth.go +++ b/internal/app/auth/auth.go @@ -71,7 +71,7 @@ func Run(ctx context.Context, log *logger.Logger, cfg Config) { u := user.New(log, nil, userRepo.NewUserRepoPG(pool)) s := grpc.NewServer(grpc.ChainUnaryInterceptor( - interceptor.Monitoring(metrics, "localhost:8086"), + interceptor.Monitoring(metrics, "0.0.0.0:8086"), interceptor.Logger(log), )) authProto.RegisterAuthServer(s, authMS.New(log, sm, u)) diff --git a/internal/app/messenger/messenger.go b/internal/app/messenger/messenger.go index a363334..f57c930 100644 --- a/internal/app/messenger/messenger.go +++ b/internal/app/messenger/messenger.go @@ -42,7 +42,7 @@ func Run(ctx context.Context, log *logger.Logger) { messageCase := message.New(mesRepo.NewMessageRepo(pool)) server := grpc.NewServer(grpc.ChainUnaryInterceptor( - interceptor.Monitoring(metrics, "localhost:8096"), + interceptor.Monitoring(metrics, "0.0.0.0:8096"), interceptor.Logger(log), interceptor.Auth(), )) From a119062a582933f3458546b0b2e09bfda03732d8 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Tue, 5 Dec 2023 11:57:20 +0300 Subject: [PATCH 221/266] dev3 add: eventType in message response --- internal/pkg/delivery/websocket/websocket.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 6074730..d6d9753 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -91,7 +91,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn h.log.Warn(err.Error()) continue } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]int{"id": id})) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) _, err = h.client.Publish(ctx, &rt.PublishMessage{ Channel: &rt.Channel{ Name: request.Channel.Name, @@ -117,7 +117,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn h.log.Warn(err.Error()) continue } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", nil)) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) _, err = h.client.Publish(ctx, &rt.PublishMessage{ Channel: &rt.Channel{ Name: request.Channel.Name, @@ -142,7 +142,7 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn h.log.Warn(err.Error()) continue } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", nil)) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) _, err = h.client.Publish(ctx, &rt.PublishMessage{ Channel: &rt.Channel{ Name: request.Channel.Name, From d7e827bb438a017386355473446fa19f05fac5f4 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 5 Dec 2023 23:31:10 +0300 Subject: [PATCH 222/266] TP-c01_ci-cd: add Dockerfiles for nginx and microservices, add service deps,healthchecks --- deployments/Dockerfile.auth | 20 +++++++ deployments/Dockerfile.main | 21 +++++++ deployments/Dockerfile.messenger | 20 +++++++ deployments/Dockerfile.realtime | 20 +++++++ deployments/docker-compose.yml | 95 +++++++++++++++++++++++++++++++- 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 deployments/Dockerfile.auth create mode 100644 deployments/Dockerfile.main create mode 100644 deployments/Dockerfile.messenger create mode 100644 deployments/Dockerfile.realtime diff --git a/deployments/Dockerfile.auth b/deployments/Dockerfile.auth new file mode 100644 index 0000000..88735ef --- /dev/null +++ b/deployments/Dockerfile.auth @@ -0,0 +1,20 @@ +FROM golang:1.19.13-alpine AS build + +RUN apk --no-cache add make + +WORKDIR /pinspire_auth + +COPY go.mod go.sum /pinspire_auth/ +RUN go mod download + +COPY . . + +RUN make build_auth + +FROM alpine:latest + +WORKDIR / + +COPY --from=build /pinspire_auth/bin/auth . + +ENTRYPOINT [ "./auth" ] diff --git a/deployments/Dockerfile.main b/deployments/Dockerfile.main new file mode 100644 index 0000000..c595010 --- /dev/null +++ b/deployments/Dockerfile.main @@ -0,0 +1,21 @@ +FROM golang:1.19.13-alpine AS build + +RUN apk --no-cache add make + +WORKDIR /pinspire_main + +COPY go.mod go.sum /pinspire_main/ +RUN go mod download + +COPY . . + +RUN make build + +FROM alpine:latest + +WORKDIR / + +COPY --from=build /pinspire_main/bin/app . +COPY --from=build /pinspire_main/configs configs + +ENTRYPOINT [ "./app" ] diff --git a/deployments/Dockerfile.messenger b/deployments/Dockerfile.messenger new file mode 100644 index 0000000..6fa332f --- /dev/null +++ b/deployments/Dockerfile.messenger @@ -0,0 +1,20 @@ +FROM golang:1.19.13-alpine AS build + +RUN apk --no-cache add make + +WORKDIR /pinspire_messenger + +COPY go.mod go.sum /pinspire_messenger/ +RUN go mod download + +COPY . . + +RUN make build_messenger + +FROM alpine:latest + +WORKDIR / + +COPY --from=build /pinspire_messenger/bin/messenger . + +ENTRYPOINT [ "./messenger" ] diff --git a/deployments/Dockerfile.realtime b/deployments/Dockerfile.realtime new file mode 100644 index 0000000..7c94295 --- /dev/null +++ b/deployments/Dockerfile.realtime @@ -0,0 +1,20 @@ +FROM golang:1.19.13-alpine AS build + +RUN apk --no-cache add make + +WORKDIR /pinspire_realtime + +COPY go.mod go.sum /pinspire_realtime/ +RUN go mod download + +COPY . . + +RUN make build_realtime + +FROM alpine:latest + +WORKDIR / + +COPY --from=build /pinspire_realtime/bin/realtime . + +ENTRYPOINT [ "./realtime" ] diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 8ee69d6..094bbab 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -1,5 +1,6 @@ version: '3.8' + services: postgres: image: postgres:latest @@ -10,6 +11,8 @@ services: - ../db/migrations:/docker-entrypoint-initdb.d ports: - 5432:5432 + healthcheck: + test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"] redis: image: redis:latest @@ -19,6 +22,82 @@ services: command: redis-server /usr/local/etc/redis/redis.conf ports: - 6379:6379 + healthcheck: + test: ["CMD", "redis-cli", "ping"] + + main_service: + build: + context: ./.. + dockerfile: deployments/Dockerfile.main + container_name: pinspireMainService + env_file: + - ../.env + environment: + - POSTGRES_HOST=postgres + - AUTH_SERVICE_HOST=auth_service + - MESSENGER_SERVICE_HOST=messenger_service + - REALTIME_SERVICE_HOST=realtime_service + depends_on: + postgres: + condition: 'service_healthy' + auth_service: + condition: 'service_started' + messenger_service: + condition: 'service_started' + realtime_service: + condition: 'service_started' + ports: + - 8100:8080 + + auth_service: + build: + context: ./.. + dockerfile: deployments/Dockerfile.auth + container_name: pinspireAuthService + env_file: + - ../.env + environment: + - POSTGRES_HOST=postgres + - REDIS_HOST=redis + depends_on: + postgres: + condition: 'service_healthy' + redis: + condition: 'service_healthy' + ports: + - 8086:8086 + # - 8101:8085 + + messenger_service: + build: + context: ./.. + dockerfile: deployments/Dockerfile.messenger + container_name: pinspireMessengerService + env_file: + - ../.env + environment: + - POSTGRES_HOST=postgres + depends_on: + postgres: + condition: 'service_healthy' + ports: + - 8096:8096 + # - 8102:8095 + + realtime_service: + build: + context: ./.. + dockerfile: deployments/Dockerfile.realtime + container_name: pinspireRealtimeService + env_file: + - ../.env + environment: + - KAFKA_BROKER_ADDRESS=kafka + depends_on: + - kafka + ports: + - 8091:8091 + # - 8103:8090 zookeeper: image: bitnami/zookeeper:latest @@ -41,7 +120,7 @@ services: - ALLOW_PLAINTEXT_LISTENER=yes - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://:9092 depends_on: - zookeeper @@ -56,6 +135,8 @@ services: grafana: image: grafana/grafana:latest container_name: pinspireGrafana + env_file: + - ../.env ports: - 3000:3000 volumes: @@ -75,6 +156,18 @@ services: - /:/rootfs:ro ports: - "9100:9100" + + nginx: + image: nginx:latest + container_name: pinspireNginx + volumes: + - '/etc/nginx/sites-available/pinspire.conf:/etc/nginx/conf.d/pinspire.conf:ro' + network_mode: 'host' + depends_on: + main_service: + condition: 'service_started' + realtime_service: + condition: 'service_started' volumes: From f8ec2a3eef23016170bfe649c7613b64a121173f Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 5 Dec 2023 23:33:22 +0300 Subject: [PATCH 223/266] TP-c01_ci-cd: changed prometheus targets, servers addresses from localhost:<port> to :<port>, replaced string servers params with env variables --- cmd/app/config.go | 8 ++++++-- cmd/auth/config.go | 2 +- cmd/realtime/main.go | 2 +- configs/config.yml | 2 +- configs/prometheus.yml | 10 +++++----- internal/app/app.go | 4 +++- internal/app/auth/auth.go | 13 +++++++++---- internal/app/config.go | 8 ++++---- internal/app/messenger/messenger.go | 2 +- internal/app/redis_conn.go | 2 +- internal/microservices/realtime/node.go | 4 +++- internal/pkg/delivery/websocket/websocket.go | 4 +++- 12 files changed, 38 insertions(+), 23 deletions(-) diff --git a/cmd/app/config.go b/cmd/app/config.go index 0cb9a84..42f8ed3 100644 --- a/cmd/app/config.go +++ b/cmd/app/config.go @@ -1,8 +1,12 @@ package main -import "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" +import ( + "os" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" +) var configFiles = app.ConfigFiles{ ServerConfigFile: "configs/config.yml", - AddrAuthServer: "localhost:8085", + AddrAuthServer: os.Getenv("AUTH_SERVICE_HOST") + ":" + os.Getenv("AUTH_SERVICE_PORT"), // "localhost:8085", } diff --git a/cmd/auth/config.go b/cmd/auth/config.go index bf1142d..2ffd53a 100644 --- a/cmd/auth/config.go +++ b/cmd/auth/config.go @@ -3,6 +3,6 @@ package main import "github.com/go-park-mail-ru/2023_2_OND_team/internal/app/auth" var configAuth = auth.Config{ - Addr: "localhost:8085", + Addr: "0.0.0.0:8085", RedisFileConfig: "redis.conf", } diff --git a/cmd/realtime/main.go b/cmd/realtime/main.go index a68532f..1022c65 100644 --- a/cmd/realtime/main.go +++ b/cmd/realtime/main.go @@ -13,7 +13,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -const _address = "localhost:8090" +const _address = "0.0.0.0:8090" func main() { log, err := logger.New() diff --git a/configs/config.yml b/configs/config.yml index 88ea0c1..4d1fda6 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -1,6 +1,6 @@ app: server: - host: 127.0.0.1 + host: 0.0.0.0 port: 8080 https: false certFile: /home/ond_team/cert/fullchain.pem diff --git a/configs/prometheus.yml b/configs/prometheus.yml index 255697d..57406b3 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -5,23 +5,23 @@ global: scrape_configs: - job_name: 'api' static_configs: - - targets: ['host.docker.internal:8080'] + - targets: ['main_service:8080'] - job_name: 'auth' static_configs: - - targets: ['pinspire.online:8086'] + - targets: ['auth_service:8086'] - job_name: 'messenger' static_configs: - - targets: ['pinspire.online:8096'] + - targets: ['messenger_service:8096'] - job_name: 'realtime' static_configs: - - targets: ['pinspire.online:8091'] + - targets: ['realtime_service:8091'] - job_name: 'node_exporter' static_configs: - - targets: ['pinspire.online:9100'] + - targets: ['node_exporter:9100'] - job_name: 'pinspire' scheme: https diff --git a/internal/app/app.go b/internal/app/app.go index 0437156..5d3d18f 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,6 +2,7 @@ package app import ( "context" + "os" "time" "github.com/joho/godotenv" @@ -57,7 +58,8 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer pool.Close() - connMessMS, err := grpc.Dial("localhost:8095", grpc.WithTransportCredentials(insecure.NewCredentials())) + // connMessMS, err := grpc.Dial("localhost:8095", grpc.WithTransportCredentials(insecure.NewCredentials())) + connMessMS, err := grpc.Dial(os.Getenv("MESSENGER_SERVICE_HOST")+":"+os.Getenv("MESSENGER_SERVICE_PORT"), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(err.Error()) return diff --git a/internal/app/auth/auth.go b/internal/app/auth/auth.go index cfcfbd6..ddfc24e 100644 --- a/internal/app/auth/auth.go +++ b/internal/app/auth/auth.go @@ -3,6 +3,7 @@ package auth import ( "context" "net" + "os" "time" "github.com/joho/godotenv" @@ -54,11 +55,15 @@ func Run(ctx context.Context, log *logger.Logger, cfg Config) { ctxRedis, cancelCtxRedis := context.WithTimeout(ctx, _timeoutForConnRedis) defer cancelCtxRedis() - redisCfg, err := app.NewConfig(cfg.RedisFileConfig) - if err != nil { - log.Error(err.Error()) - return + // redisCfg, err := app.NewConfig(cfg.RedisFileConfig) + redisCfg := app.RedisConfig{ + Addr: os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT"), + Password: os.Getenv("REDIS_PASSWORD"), } + // if err != nil { + // log.Error(err.Error()) + // return + // } redisCl, err := app.NewRedisClient(ctxRedis, redisCfg) if err != nil { diff --git a/internal/app/config.go b/internal/app/config.go index 7fa6c87..4fb3bb2 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -11,18 +11,18 @@ type ConfigFiles struct { AddrAuthServer string } -type redisConfig struct { +type RedisConfig struct { Password string Addr string } -func NewConfig(filename string) (redisConfig, error) { +func NewConfig(filename string) (RedisConfig, error) { cfg, err := config.ParseConfig(filename) if err != nil { - return redisConfig{}, fmt.Errorf("new redis config: %w", err) + return RedisConfig{}, fmt.Errorf("new redis config: %w", err) } - return redisConfig{ + return RedisConfig{ Password: cfg.Get("requirepass"), Addr: cfg.Get("host") + ":" + cfg.Get("port"), }, nil diff --git a/internal/app/messenger/messenger.go b/internal/app/messenger/messenger.go index f57c930..27ca153 100644 --- a/internal/app/messenger/messenger.go +++ b/internal/app/messenger/messenger.go @@ -48,7 +48,7 @@ func Run(ctx context.Context, log *logger.Logger) { )) messenger.RegisterMessengerServer(server, messMS.New(log, messageCase)) - l, err := net.Listen("tcp", "localhost:8095") + l, err := net.Listen("tcp", "0.0.0.0:8095") if err != nil { log.Error(err.Error()) return diff --git a/internal/app/redis_conn.go b/internal/app/redis_conn.go index 8a7527c..8ee910e 100644 --- a/internal/app/redis_conn.go +++ b/internal/app/redis_conn.go @@ -7,7 +7,7 @@ import ( redis "github.com/redis/go-redis/v9" ) -func NewRedisClient(ctx context.Context, cfg redisConfig) (*redis.Client, error) { +func NewRedisClient(ctx context.Context, cfg RedisConfig) (*redis.Client, error) { redisCl := redis.NewClient(&redis.Options{ Addr: cfg.Addr, Password: cfg.Password, diff --git a/internal/microservices/realtime/node.go b/internal/microservices/realtime/node.go index db17bf6..f6f39c7 100644 --- a/internal/microservices/realtime/node.go +++ b/internal/microservices/realtime/node.go @@ -2,6 +2,7 @@ package realtime import ( "fmt" + "os" "sync" "google.golang.org/protobuf/proto" @@ -20,7 +21,8 @@ func NewNode() (*Node, error) { node := &Node{} broker, err := NewKafkaBroker(node, KafkaConfig{ - Addres: []string{"localhost:9092"}, + // Addres: []string{"localhost:9092"}, + Addres: []string{os.Getenv("KAFKA_BROKER_ADDRESS") + ":" + os.Getenv("KAFKA_BROKER_PORT")}, PartitionsOnTopic: _numWorkers, MaxNumTopic: 10, }) diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 6074730..c0042de 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "os" "time" "google.golang.org/grpc" @@ -36,7 +37,8 @@ func SetOriginPatterns(patterns []string) Option { } func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { - gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + // gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + gRPCConn, err := grpc.Dial(os.Getenv("REALTIME_SERVICE_HOST"+":"+os.Getenv("REALTIME_SERVICE_PORT")), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(fmt.Errorf("grpc dial: %w", err).Error()) } From e38aacff5591fb0cf49a27bab98964f3619e69d0 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 6 Dec 2023 12:18:03 +0300 Subject: [PATCH 224/266] TP-3a4 add: output files flag for logging --- cmd/app/main.go | 13 ++++++++++++- pkg/logger/option.go | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/cmd/app/main.go b/cmd/app/main.go index 3d4dbb6..65db49a 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -2,12 +2,18 @@ package main import ( "context" + "flag" "fmt" "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +var ( + logOutput = flag.String("log", "stdout", "file paths to write logging output to") + logErrorOutput = flag.String("logerror", "stderr", "path to write internal logger errors to.") +) + // @title Pinspire API // @version 1.0 // @description API for Pinspire project @@ -21,10 +27,15 @@ import ( // @license.url http://www.apache.org/licenses/LICENSE-2.0.html func main() { + flag.Parse() ctxBase, cancel := context.WithCancel(context.Background()) defer cancel() - log, err := logger.New(logger.RFC3339FormatTime()) + log, err := logger.New( + logger.RFC3339FormatTime(), + logger.SetOutputPaths(*logOutput), + logger.SetErrorOutputPaths(*logErrorOutput), + ) if err != nil { fmt.Println(err) return diff --git a/pkg/logger/option.go b/pkg/logger/option.go index 96db06c..9639478 100644 --- a/pkg/logger/option.go +++ b/pkg/logger/option.go @@ -18,3 +18,15 @@ func SetFormatTime(layout string) ConfigOption { func RFC3339FormatTime() ConfigOption { return SetFormatTime(time.RFC3339) } + +func SetOutputPaths(files ...string) ConfigOption { + return func(cfg *zap.Config) { + cfg.OutputPaths = files + } +} + +func SetErrorOutputPaths(files ...string) ConfigOption { + return func(cfg *zap.Config) { + cfg.ErrorOutputPaths = files + } +} From 41431831ea2b9206fa44ac8008e6618cc5856774 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 6 Dec 2023 12:28:17 +0300 Subject: [PATCH 225/266] dev3 add: generate mocks --- .../usecase/message/mock/message_mock.go | 31 ++++++ .../pkg/repository/search/mock/search_mock.go | 81 ++++++++++++++++ internal/pkg/repository/search/repo.go | 1 + .../subscription/mock/subscription_mock.go | 94 ++++++++++++++++++ internal/pkg/repository/subscription/repo.go | 1 + internal/pkg/usecase/auth/mock/auth_mock.go | 95 +++++++++++++++++++ internal/pkg/usecase/auth/usecase.go | 1 + .../pkg/usecase/message/mock/message_mock.go | 24 ++--- .../pkg/usecase/search/mock/search_mock.go | 81 ++++++++++++++++ internal/pkg/usecase/search/usecase.go | 1 + .../subscription/mock/subscription_mock.go | 79 +++++++++++++++ internal/pkg/usecase/subscription/usecase.go | 1 + 12 files changed, 478 insertions(+), 12 deletions(-) create mode 100644 internal/pkg/repository/search/mock/search_mock.go create mode 100644 internal/pkg/repository/subscription/mock/subscription_mock.go create mode 100644 internal/pkg/usecase/auth/mock/auth_mock.go create mode 100644 internal/pkg/usecase/search/mock/search_mock.go create mode 100644 internal/pkg/usecase/subscription/mock/subscription_mock.go diff --git a/internal/microservices/messenger/usecase/message/mock/message_mock.go b/internal/microservices/messenger/usecase/message/mock/message_mock.go index 449be90..65e877a 100644 --- a/internal/microservices/messenger/usecase/message/mock/message_mock.go +++ b/internal/microservices/messenger/usecase/message/mock/message_mock.go @@ -49,6 +49,21 @@ func (mr *MockUsecaseMockRecorder) DeleteMessage(ctx, userID, mesID interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockUsecase)(nil).DeleteMessage), ctx, userID, mesID) } +// GetMessage mocks base method. +func (m *MockUsecase) GetMessage(ctx context.Context, messageID int) (*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessage", ctx, messageID) + ret0, _ := ret[0].(*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMessage indicates an expected call of GetMessage. +func (mr *MockUsecaseMockRecorder) GetMessage(ctx, messageID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockUsecase)(nil).GetMessage), ctx, messageID) +} + // GetMessagesFromChat mocks base method. func (m *MockUsecase) GetMessagesFromChat(ctx context.Context, chat message.Chat, count, lastID int) ([]message.Message, int, error) { m.ctrl.T.Helper() @@ -65,6 +80,22 @@ func (mr *MockUsecaseMockRecorder) GetMessagesFromChat(ctx, chat, count, lastID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessagesFromChat", reflect.TypeOf((*MockUsecase)(nil).GetMessagesFromChat), ctx, chat, count, lastID) } +// GetUserChatsWithOtherUsers mocks base method. +func (m *MockUsecase) GetUserChatsWithOtherUsers(ctx context.Context, userID, count, lastID int) (message.FeedUserChats, int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserChatsWithOtherUsers", ctx, userID, count, lastID) + ret0, _ := ret[0].(message.FeedUserChats) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetUserChatsWithOtherUsers indicates an expected call of GetUserChatsWithOtherUsers. +func (mr *MockUsecaseMockRecorder) GetUserChatsWithOtherUsers(ctx, userID, count, lastID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserChatsWithOtherUsers", reflect.TypeOf((*MockUsecase)(nil).GetUserChatsWithOtherUsers), ctx, userID, count, lastID) +} + // SendMessage mocks base method. func (m *MockUsecase) SendMessage(ctx context.Context, mes *message.Message) (int, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/repository/search/mock/search_mock.go b/internal/pkg/repository/search/mock/search_mock.go new file mode 100644 index 0000000..026411d --- /dev/null +++ b/internal/pkg/repository/search/mock/search_mock.go @@ -0,0 +1,81 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + search "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// GetFilteredBoards mocks base method. +func (m *MockRepository) GetFilteredBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilteredBoards", ctx, opts) + ret0, _ := ret[0].([]search.BoardForSearch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFilteredBoards indicates an expected call of GetFilteredBoards. +func (mr *MockRepositoryMockRecorder) GetFilteredBoards(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilteredBoards", reflect.TypeOf((*MockRepository)(nil).GetFilteredBoards), ctx, opts) +} + +// GetFilteredPins mocks base method. +func (m *MockRepository) GetFilteredPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilteredPins", ctx, opts) + ret0, _ := ret[0].([]search.PinForSearch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFilteredPins indicates an expected call of GetFilteredPins. +func (mr *MockRepositoryMockRecorder) GetFilteredPins(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilteredPins", reflect.TypeOf((*MockRepository)(nil).GetFilteredPins), ctx, opts) +} + +// GetFilteredUsers mocks base method. +func (m *MockRepository) GetFilteredUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilteredUsers", ctx, opts) + ret0, _ := ret[0].([]search.UserForSearch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFilteredUsers indicates an expected call of GetFilteredUsers. +func (mr *MockRepositoryMockRecorder) GetFilteredUsers(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilteredUsers", reflect.TypeOf((*MockRepository)(nil).GetFilteredUsers), ctx, opts) +} diff --git a/internal/pkg/repository/search/repo.go b/internal/pkg/repository/search/repo.go index 98ca7bf..b49c815 100644 --- a/internal/pkg/repository/search/repo.go +++ b/internal/pkg/repository/search/repo.go @@ -6,6 +6,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" ) +//go:generate mockgen -destination=./mock/search_mock.go -package=mock -source=repo.go Repository type Repository interface { GetFilteredUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) GetFilteredPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) diff --git a/internal/pkg/repository/subscription/mock/subscription_mock.go b/internal/pkg/repository/subscription/mock/subscription_mock.go new file mode 100644 index 0000000..28f115a --- /dev/null +++ b/internal/pkg/repository/subscription/mock/subscription_mock.go @@ -0,0 +1,94 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// CreateSubscriptionUser mocks base method. +func (m *MockRepository) CreateSubscriptionUser(ctx context.Context, from, to int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSubscriptionUser", ctx, from, to) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateSubscriptionUser indicates an expected call of CreateSubscriptionUser. +func (mr *MockRepositoryMockRecorder) CreateSubscriptionUser(ctx, from, to interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSubscriptionUser", reflect.TypeOf((*MockRepository)(nil).CreateSubscriptionUser), ctx, from, to) +} + +// DeleteSubscriptionUser mocks base method. +func (m *MockRepository) DeleteSubscriptionUser(ctx context.Context, from, to int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSubscriptionUser", ctx, from, to) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSubscriptionUser indicates an expected call of DeleteSubscriptionUser. +func (mr *MockRepositoryMockRecorder) DeleteSubscriptionUser(ctx, from, to interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSubscriptionUser", reflect.TypeOf((*MockRepository)(nil).DeleteSubscriptionUser), ctx, from, to) +} + +// GetUserSubscribers mocks base method. +func (m *MockRepository) GetUserSubscribers(ctx context.Context, userID, count, lastID, currUserID int) ([]user.SubscriptionUser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserSubscribers", ctx, userID, count, lastID, currUserID) + ret0, _ := ret[0].([]user.SubscriptionUser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserSubscribers indicates an expected call of GetUserSubscribers. +func (mr *MockRepositoryMockRecorder) GetUserSubscribers(ctx, userID, count, lastID, currUserID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserSubscribers", reflect.TypeOf((*MockRepository)(nil).GetUserSubscribers), ctx, userID, count, lastID, currUserID) +} + +// GetUserSubscriptions mocks base method. +func (m *MockRepository) GetUserSubscriptions(ctx context.Context, userID, count, lastID, currUserID int) ([]user.SubscriptionUser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserSubscriptions", ctx, userID, count, lastID, currUserID) + ret0, _ := ret[0].([]user.SubscriptionUser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserSubscriptions indicates an expected call of GetUserSubscriptions. +func (mr *MockRepositoryMockRecorder) GetUserSubscriptions(ctx, userID, count, lastID, currUserID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserSubscriptions", reflect.TypeOf((*MockRepository)(nil).GetUserSubscriptions), ctx, userID, count, lastID, currUserID) +} diff --git a/internal/pkg/repository/subscription/repo.go b/internal/pkg/repository/subscription/repo.go index 7d23950..6c8c841 100644 --- a/internal/pkg/repository/subscription/repo.go +++ b/internal/pkg/repository/subscription/repo.go @@ -6,6 +6,7 @@ import ( userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +//go:generate mockgen -destination=./mock/subscription_mock.go -package=mock -source=repo.go Repository type Repository interface { CreateSubscriptionUser(ctx context.Context, from, to int) error DeleteSubscriptionUser(ctx context.Context, from, to int) error diff --git a/internal/pkg/usecase/auth/mock/auth_mock.go b/internal/pkg/usecase/auth/mock/auth_mock.go new file mode 100644 index 0000000..813ae3b --- /dev/null +++ b/internal/pkg/usecase/auth/mock/auth_mock.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + session "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/session" + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// GetUserIDBySession mocks base method. +func (m *MockUsecase) GetUserIDBySession(ctx context.Context, sess *session.Session) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserIDBySession", ctx, sess) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserIDBySession indicates an expected call of GetUserIDBySession. +func (mr *MockUsecaseMockRecorder) GetUserIDBySession(ctx, sess interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserIDBySession", reflect.TypeOf((*MockUsecase)(nil).GetUserIDBySession), ctx, sess) +} + +// Login mocks base method. +func (m *MockUsecase) Login(ctx context.Context, username, password string) (*session.Session, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", ctx, username, password) + ret0, _ := ret[0].(*session.Session) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Login indicates an expected call of Login. +func (mr *MockUsecaseMockRecorder) Login(ctx, username, password interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockUsecase)(nil).Login), ctx, username, password) +} + +// Logout mocks base method. +func (m *MockUsecase) Logout(ctx context.Context, sess *session.Session) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Logout", ctx, sess) + ret0, _ := ret[0].(error) + return ret0 +} + +// Logout indicates an expected call of Logout. +func (mr *MockUsecaseMockRecorder) Logout(ctx, sess interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockUsecase)(nil).Logout), ctx, sess) +} + +// Register mocks base method. +func (m *MockUsecase) Register(ctx context.Context, user *user.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Register", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// Register indicates an expected call of Register. +func (mr *MockUsecaseMockRecorder) Register(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockUsecase)(nil).Register), ctx, user) +} diff --git a/internal/pkg/usecase/auth/usecase.go b/internal/pkg/usecase/auth/usecase.go index 2c23f5a..49ee9bf 100644 --- a/internal/pkg/usecase/auth/usecase.go +++ b/internal/pkg/usecase/auth/usecase.go @@ -10,6 +10,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) +//go:generate mockgen -destination=./mock/auth_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { Register(ctx context.Context, user *entity.User) error Login(ctx context.Context, username, password string) (*session.Session, error) diff --git a/internal/pkg/usecase/message/mock/message_mock.go b/internal/pkg/usecase/message/mock/message_mock.go index 65e877a..18f0333 100644 --- a/internal/pkg/usecase/message/mock/message_mock.go +++ b/internal/pkg/usecase/message/mock/message_mock.go @@ -50,24 +50,24 @@ func (mr *MockUsecaseMockRecorder) DeleteMessage(ctx, userID, mesID interface{}) } // GetMessage mocks base method. -func (m *MockUsecase) GetMessage(ctx context.Context, messageID int) (*message.Message, error) { +func (m *MockUsecase) GetMessage(ctx context.Context, userID, messageID int) (*message.Message, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMessage", ctx, messageID) + ret := m.ctrl.Call(m, "GetMessage", ctx, userID, messageID) ret0, _ := ret[0].(*message.Message) ret1, _ := ret[1].(error) return ret0, ret1 } // GetMessage indicates an expected call of GetMessage. -func (mr *MockUsecaseMockRecorder) GetMessage(ctx, messageID interface{}) *gomock.Call { +func (mr *MockUsecaseMockRecorder) GetMessage(ctx, userID, messageID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockUsecase)(nil).GetMessage), ctx, messageID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockUsecase)(nil).GetMessage), ctx, userID, messageID) } // GetMessagesFromChat mocks base method. -func (m *MockUsecase) GetMessagesFromChat(ctx context.Context, chat message.Chat, count, lastID int) ([]message.Message, int, error) { +func (m *MockUsecase) GetMessagesFromChat(ctx context.Context, userID int, chat message.Chat, count, lastID int) ([]message.Message, int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMessagesFromChat", ctx, chat, count, lastID) + ret := m.ctrl.Call(m, "GetMessagesFromChat", ctx, userID, chat, count, lastID) ret0, _ := ret[0].([]message.Message) ret1, _ := ret[1].(int) ret2, _ := ret[2].(error) @@ -75,9 +75,9 @@ func (m *MockUsecase) GetMessagesFromChat(ctx context.Context, chat message.Chat } // GetMessagesFromChat indicates an expected call of GetMessagesFromChat. -func (mr *MockUsecaseMockRecorder) GetMessagesFromChat(ctx, chat, count, lastID interface{}) *gomock.Call { +func (mr *MockUsecaseMockRecorder) GetMessagesFromChat(ctx, userID, chat, count, lastID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessagesFromChat", reflect.TypeOf((*MockUsecase)(nil).GetMessagesFromChat), ctx, chat, count, lastID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessagesFromChat", reflect.TypeOf((*MockUsecase)(nil).GetMessagesFromChat), ctx, userID, chat, count, lastID) } // GetUserChatsWithOtherUsers mocks base method. @@ -97,18 +97,18 @@ func (mr *MockUsecaseMockRecorder) GetUserChatsWithOtherUsers(ctx, userID, count } // SendMessage mocks base method. -func (m *MockUsecase) SendMessage(ctx context.Context, mes *message.Message) (int, error) { +func (m *MockUsecase) SendMessage(ctx context.Context, userID int, mes *message.Message) (int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendMessage", ctx, mes) + ret := m.ctrl.Call(m, "SendMessage", ctx, userID, mes) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // SendMessage indicates an expected call of SendMessage. -func (mr *MockUsecaseMockRecorder) SendMessage(ctx, mes interface{}) *gomock.Call { +func (mr *MockUsecaseMockRecorder) SendMessage(ctx, userID, mes interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockUsecase)(nil).SendMessage), ctx, mes) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockUsecase)(nil).SendMessage), ctx, userID, mes) } // UpdateContentMessage mocks base method. diff --git a/internal/pkg/usecase/search/mock/search_mock.go b/internal/pkg/usecase/search/mock/search_mock.go new file mode 100644 index 0000000..dd303a6 --- /dev/null +++ b/internal/pkg/usecase/search/mock/search_mock.go @@ -0,0 +1,81 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + search "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// GetBoards mocks base method. +func (m *MockUsecase) GetBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBoards", ctx, opts) + ret0, _ := ret[0].([]search.BoardForSearch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBoards indicates an expected call of GetBoards. +func (mr *MockUsecaseMockRecorder) GetBoards(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoards", reflect.TypeOf((*MockUsecase)(nil).GetBoards), ctx, opts) +} + +// GetPins mocks base method. +func (m *MockUsecase) GetPins(ctx context.Context, opts *search.SearchOpts) ([]search.PinForSearch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPins", ctx, opts) + ret0, _ := ret[0].([]search.PinForSearch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPins indicates an expected call of GetPins. +func (mr *MockUsecaseMockRecorder) GetPins(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPins", reflect.TypeOf((*MockUsecase)(nil).GetPins), ctx, opts) +} + +// GetUsers mocks base method. +func (m *MockUsecase) GetUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsers", ctx, opts) + ret0, _ := ret[0].([]search.UserForSearch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUsers indicates an expected call of GetUsers. +func (mr *MockUsecaseMockRecorder) GetUsers(ctx, opts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsers", reflect.TypeOf((*MockUsecase)(nil).GetUsers), ctx, opts) +} diff --git a/internal/pkg/usecase/search/usecase.go b/internal/pkg/usecase/search/usecase.go index 6b06e24..b6277ca 100644 --- a/internal/pkg/usecase/search/usecase.go +++ b/internal/pkg/usecase/search/usecase.go @@ -9,6 +9,7 @@ import ( "github.com/microcosm-cc/bluemonday" ) +//go:generate mockgen -destination=./mock/search_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { GetUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) GetBoards(ctx context.Context, opts *search.SearchOpts) ([]search.BoardForSearch, error) diff --git a/internal/pkg/usecase/subscription/mock/subscription_mock.go b/internal/pkg/usecase/subscription/mock/subscription_mock.go new file mode 100644 index 0000000..6d3aab8 --- /dev/null +++ b/internal/pkg/usecase/subscription/mock/subscription_mock.go @@ -0,0 +1,79 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// GetSubscriptionInfoForUser mocks base method. +func (m *MockUsecase) GetSubscriptionInfoForUser(ctx context.Context, subOpts *user.SubscriptionOpts) ([]user.SubscriptionUser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSubscriptionInfoForUser", ctx, subOpts) + ret0, _ := ret[0].([]user.SubscriptionUser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSubscriptionInfoForUser indicates an expected call of GetSubscriptionInfoForUser. +func (mr *MockUsecaseMockRecorder) GetSubscriptionInfoForUser(ctx, subOpts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionInfoForUser", reflect.TypeOf((*MockUsecase)(nil).GetSubscriptionInfoForUser), ctx, subOpts) +} + +// SubscribeToUser mocks base method. +func (m *MockUsecase) SubscribeToUser(ctx context.Context, from, to int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubscribeToUser", ctx, from, to) + ret0, _ := ret[0].(error) + return ret0 +} + +// SubscribeToUser indicates an expected call of SubscribeToUser. +func (mr *MockUsecaseMockRecorder) SubscribeToUser(ctx, from, to interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeToUser", reflect.TypeOf((*MockUsecase)(nil).SubscribeToUser), ctx, from, to) +} + +// UnsubscribeFromUser mocks base method. +func (m *MockUsecase) UnsubscribeFromUser(ctx context.Context, from, to int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnsubscribeFromUser", ctx, from, to) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnsubscribeFromUser indicates an expected call of UnsubscribeFromUser. +func (mr *MockUsecaseMockRecorder) UnsubscribeFromUser(ctx, from, to interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnsubscribeFromUser", reflect.TypeOf((*MockUsecase)(nil).UnsubscribeFromUser), ctx, from, to) +} diff --git a/internal/pkg/usecase/subscription/usecase.go b/internal/pkg/usecase/subscription/usecase.go index 4a88d83..a00705f 100644 --- a/internal/pkg/usecase/subscription/usecase.go +++ b/internal/pkg/usecase/subscription/usecase.go @@ -10,6 +10,7 @@ import ( "github.com/microcosm-cc/bluemonday" ) +//go:generate mockgen -destination=./mock/subscription_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { SubscribeToUser(ctx context.Context, from, to int) error UnsubscribeFromUser(ctx context.Context, from, to int) error From f862979bc2f69d009ffdde11aee7077ca306c7ee Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Thu, 7 Dec 2023 21:03:48 +0300 Subject: [PATCH 226/266] TP-c01_ci-cd: add linter configuration --- Makefile | 18 +++- configs/.golangci.yml | 206 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 configs/.golangci.yml diff --git a/Makefile b/Makefile index 7b0652c..ee03e17 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ .PHONY: build run test test_with_coverage cleantest retest doc generate cover_all currcover -.PHONY: build_auth build_realtime build_messenger +.PHONY: build_auth build_realtime build_messenger build_all +.PHONY: .install-linter lint lint-fast ENTRYPOINT=cmd/app/main.go DOC_DIR=./docs @@ -7,6 +8,11 @@ COV_OUT=coverage.out COV_HTML=coverage.html CURRCOVER=github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1 +PROJECT_DIR = $(shell pwd) +PROJECT_BIN = $(PROJECT_DIR)/bin +$(shell [ -f bin ] || mkdir -p $(PROJECT_BIN)) +GOLANGCI_LINT = $(PROJECT_BIN)/golangci-lint + build: go build -o bin/app cmd/app/*.go @@ -19,6 +25,8 @@ build_realtime: build_messenger: go build -o bin/messenger cmd/messenger/*.go +build_all: build build_auth build_realtime build_messenger + run: build ./bin/app @@ -51,3 +59,11 @@ currcover: go test -cover -v -coverprofile=cover.out ${CURRCOVER} go tool cover -html=cover.out -o cover.html +.install-linter: + [ -f $(PROJECT_BIN)/golangci-lint ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(PROJECT_BIN) v1.55.2 + +lint: .install-linter + $(GOLANGCI_LINT) run ./... --config=configs/.golangci.yml + +lint-fast: .install-linter + $(GOLANGCI_LINT) run ./... --fast --config=configs/.golangci.yml diff --git a/configs/.golangci.yml b/configs/.golangci.yml new file mode 100644 index 0000000..cac2d04 --- /dev/null +++ b/configs/.golangci.yml @@ -0,0 +1,206 @@ +# This code is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021 Marat Reymers + +## Golden config for golangci-lint v1.55.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adapt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + skip-dirs: + - .. + + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and names from check. + # Default: [] + exclude: + # std libs + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + # public libs + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + # Ignore comments when counting lines. + # Default false + ignore-comments: true + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "gofrs' package is not go module" + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + exclude-use-default: true + max-same-issues: 50 + + exclude-rules: + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck \ No newline at end of file From ea75fda9e0de36917c696dc38bb50a1425387fad Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Thu, 7 Dec 2023 21:22:28 +0300 Subject: [PATCH 227/266] TP-c01_ci-cd: add named volumes for postgres and redis, add compose.prod.yml for pulling images while deploying --- deployments/Dockerfile.auth | 6 +++--- deployments/Dockerfile.main | 8 ++++---- deployments/Dockerfile.messenger | 6 +++--- deployments/Dockerfile.realtime | 6 +++--- deployments/compose.prod.yml | 14 ++++++++++++++ deployments/docker-compose.yml | 12 ++++++++---- 6 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 deployments/compose.prod.yml diff --git a/deployments/Dockerfile.auth b/deployments/Dockerfile.auth index 88735ef..74b9443 100644 --- a/deployments/Dockerfile.auth +++ b/deployments/Dockerfile.auth @@ -2,9 +2,9 @@ FROM golang:1.19.13-alpine AS build RUN apk --no-cache add make -WORKDIR /pinspire_auth +WORKDIR /pinspire -COPY go.mod go.sum /pinspire_auth/ +COPY go.mod go.sum /pinspire/ RUN go mod download COPY . . @@ -15,6 +15,6 @@ FROM alpine:latest WORKDIR / -COPY --from=build /pinspire_auth/bin/auth . +COPY --from=build /pinspire/bin/auth . ENTRYPOINT [ "./auth" ] diff --git a/deployments/Dockerfile.main b/deployments/Dockerfile.main index c595010..4d500c8 100644 --- a/deployments/Dockerfile.main +++ b/deployments/Dockerfile.main @@ -2,9 +2,9 @@ FROM golang:1.19.13-alpine AS build RUN apk --no-cache add make -WORKDIR /pinspire_main +WORKDIR /pinspire -COPY go.mod go.sum /pinspire_main/ +COPY go.mod go.sum /pinspire/ RUN go mod download COPY . . @@ -15,7 +15,7 @@ FROM alpine:latest WORKDIR / -COPY --from=build /pinspire_main/bin/app . -COPY --from=build /pinspire_main/configs configs +COPY --from=build /pinspire/bin/app . +COPY --from=build /pinspire/configs configs ENTRYPOINT [ "./app" ] diff --git a/deployments/Dockerfile.messenger b/deployments/Dockerfile.messenger index 6fa332f..cdaec41 100644 --- a/deployments/Dockerfile.messenger +++ b/deployments/Dockerfile.messenger @@ -2,9 +2,9 @@ FROM golang:1.19.13-alpine AS build RUN apk --no-cache add make -WORKDIR /pinspire_messenger +WORKDIR /pinspire -COPY go.mod go.sum /pinspire_messenger/ +COPY go.mod go.sum /pinspire/ RUN go mod download COPY . . @@ -15,6 +15,6 @@ FROM alpine:latest WORKDIR / -COPY --from=build /pinspire_messenger/bin/messenger . +COPY --from=build /pinspire/bin/messenger . ENTRYPOINT [ "./messenger" ] diff --git a/deployments/Dockerfile.realtime b/deployments/Dockerfile.realtime index 7c94295..da5610f 100644 --- a/deployments/Dockerfile.realtime +++ b/deployments/Dockerfile.realtime @@ -2,9 +2,9 @@ FROM golang:1.19.13-alpine AS build RUN apk --no-cache add make -WORKDIR /pinspire_realtime +WORKDIR /pinspire -COPY go.mod go.sum /pinspire_realtime/ +COPY go.mod go.sum /pinspire/ RUN go mod download COPY . . @@ -15,6 +15,6 @@ FROM alpine:latest WORKDIR / -COPY --from=build /pinspire_realtime/bin/realtime . +COPY --from=build /pinspire/bin/realtime . ENTRYPOINT [ "./realtime" ] diff --git a/deployments/compose.prod.yml b/deployments/compose.prod.yml new file mode 100644 index 0000000..5156d5d --- /dev/null +++ b/deployments/compose.prod.yml @@ -0,0 +1,14 @@ +version: '3.8' + +services: + main_service: + image: pinspireapp/main:latest + + auth_service: + image: pinspireapp/auth:latest + + messenger_service: + image: pinspireapp/messenger:latest + + realtime_service: + image: pinspireapp/realtime:latest diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 094bbab..d36f370 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -1,6 +1,5 @@ version: '3.8' - services: postgres: image: postgres:latest @@ -9,6 +8,7 @@ services: - ../.env volumes: - ../db/migrations:/docker-entrypoint-initdb.d + - 'postgres_storage:/var/lib/postgresql/data' ports: - 5432:5432 healthcheck: @@ -19,6 +19,7 @@ services: container_name: pinspireRedis volumes: - ../redis.conf:/usr/local/etc/redis/redis.conf + - 'redis_storage:/data' command: redis-server /usr/local/etc/redis/redis.conf ports: - 6379:6379 @@ -65,7 +66,7 @@ services: redis: condition: 'service_healthy' ports: - - 8086:8086 + - 8186:8086 # - 8101:8085 messenger_service: @@ -81,7 +82,7 @@ services: postgres: condition: 'service_healthy' ports: - - 8096:8096 + - 8196:8096 # - 8102:8095 realtime_service: @@ -96,7 +97,7 @@ services: depends_on: - kafka ports: - - 8091:8091 + - 8191:8091 # - 8103:8090 zookeeper: @@ -171,8 +172,11 @@ services: volumes: + postgres_storage: {} + redis_storage: {} zookeeper_data: driver: local kafka_data: driver: local grafana_storage: {} + From 6c8a215a984a03c080ecdf37e7489420d35afc6d Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Thu, 7 Dec 2023 21:39:15 +0300 Subject: [PATCH 228/266] TP-c01_ci-cd: add workflows description, add configuration file for ansible, add inventory in .gitignore --- .github/workflows/ci.yml | 27 ++++++++++++++++ .github/workflows/deployment.yml | 55 ++++++++++++++++++++++++++++++++ .gitignore | 2 ++ configs/playbook.yml | 16 ++++++++++ 4 files changed, 100 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deployment.yml create mode 100644 configs/playbook.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..66b52c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: Start pinspire CI + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Get repository code + uses: actions/checkout@v4 + - name: Test application + run: go test ./... + lint: + runs-on: ubuntu-latest + steps: + - name: Get repository code + uses: actions/checkout@v4 + - name: Lint application + run: make lint + build: + runs-o: ubuntu-latest + steps: + - name: Get repository code + uses: actions/checkout@v4 + - name: Build application + run: make build_all diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml new file mode 100644 index 0000000..43876cf --- /dev/null +++ b/.github/workflows/deployment.yml @@ -0,0 +1,55 @@ +name: Start Pinspire deployment + +on: + workflow_dispatch: + +jobs: + build_images: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: dev3 + - name: Login to DockerHub Registry + run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin + - name: Build docker images of services + run: | + docker build -t pinspireapp/main:latest -f deployments/Dockerfile.main . & + docker build -t pinspireapp/auth:latest -f deployments/Dockerfile.auth . & + docker build -t pinspireapp/realtime:latest -f deployments/Dockerfile.realtime . & + docker build -t pinspireapp/messenger:latest -f deployments/Dockerfile.messenger . & + for p in $(jobs -p); do wait "$p" || { echo "job $p failed" >&2; exit; }; done + - name: Push docker images + run: | + docker push pinspireapp/main:latest & + docker push pinspireapp/auth:latest & + docker push pinspireapp/realtime:latest & + docker push pinspireapp/messenger:latest & + for p in $(jobs -p); do wait "$p" || { echo "job $p failed" >&2; exit; }; done + + deploy: + runs-on: ubuntu-latest + needs: build_images + steps: + - name: fetch changes + uses: appleboy/ssh-action@master + with: + host: pinspire.online + username: ${{ secrets.REMOTE_USERNAME }} + key: ${{ secrets.PRIVATE_KEY }} + script: | + cd ${{ secrets.PINSPIRE_BACKEND_PATH }} + sudo git switch dev3 + sudo git pull + - name: deploy application + uses: appleboy/ssh-action@master + with: + host: pinspire.online + username: ${{ secrets.REMOTE_USERNAME }} + key: ${{ secrets.PRIVATE_KEY }} + script: | + cd ${{ secrets.PINSPIRE_BACKEND_PATH }}/deployments + sudo docker compose down main_service auth_service realtime_service messenger_service + sudo docker rmi pinspireapp/main:latest pinspireapp/auth:latest pinspireapp/realtime:latest pinspireapp/messenger:latest + docker compose --env-file=../.env -f docker-compose.yml -f compose.prod.yml up -d + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1d94894..e33fae4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ testdata/ cert/ .env redis.conf +inventory +script* \ No newline at end of file diff --git a/configs/playbook.yml b/configs/playbook.yml new file mode 100644 index 0000000..c3d8bf0 --- /dev/null +++ b/configs/playbook.yml @@ -0,0 +1,16 @@ +- name: "Provide configuration files" + become: yes + hosts: pinspire + tasks: + - name: "Provide .env file" + copy: + src: ../.env + dest: /home/ond_team/go/src/github.com/go-park-mail-ru/ci-cd/.env + - name: "Provide redis config" + copy: + src: ../redis.conf + dest: /home/ond_team/go/src/github.com/go-park-mail-ru/ci-cd/redis.conf + - name: "Provide nginx config" + copy: + src: /etc/nginx/sites-available/pinspire.conf + dest: /etc/nginx/sites-available/pinspire.conf \ No newline at end of file From 7318fe6d77898364d2574bbf0e39d572d19193a0 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 8 Dec 2023 13:55:34 +0300 Subject: [PATCH 229/266] TP-c01_ci-cd: add kafka healthcheck --- .github/workflows/deployment.yml | 2 +- deployments/docker-compose.yml | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 43876cf..1928b3a 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -51,5 +51,5 @@ jobs: cd ${{ secrets.PINSPIRE_BACKEND_PATH }}/deployments sudo docker compose down main_service auth_service realtime_service messenger_service sudo docker rmi pinspireapp/main:latest pinspireapp/auth:latest pinspireapp/realtime:latest pinspireapp/messenger:latest - docker compose --env-file=../.env -f docker-compose.yml -f compose.prod.yml up -d + docker compose -f docker-compose.yml -f compose.prod.yml up -d \ No newline at end of file diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index d36f370..1aa64f2 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -12,8 +12,12 @@ services: ports: - 5432:5432 healthcheck: - test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"] - + test: ["CMD", "pg_isready"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 15s + redis: image: redis:latest container_name: pinspireRedis @@ -25,6 +29,10 @@ services: - 6379:6379 healthcheck: test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 15s main_service: build: @@ -95,7 +103,8 @@ services: environment: - KAFKA_BROKER_ADDRESS=kafka depends_on: - - kafka + kafka: + condition: 'service_healthy' ports: - 8191:8091 # - 8103:8090 @@ -122,6 +131,14 @@ services: - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://:9092 + healthcheck: + test: | + curl localhost:9092 + [ $(echo $?) = '52' ] && exit 0 || exit -1 + interval: 5s + timeout: 5s + retries: 10 + start_period: 15s depends_on: - zookeeper @@ -179,4 +196,3 @@ volumes: kafka_data: driver: local grafana_storage: {} - From eaa7ece7ea3861d95c9fbd5328b75be74b0a7e8c Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Fri, 8 Dec 2023 14:15:45 +0300 Subject: [PATCH 230/266] TP-c01_ci-cd: deleted nginx config from compose.yml, changed triggers, changed ports --- .github/workflows/ci.yml | 15 ++++++++++++--- .github/workflows/deployment.yml | 13 ++++++++++--- cmd/realtime/main.go | 2 ++ configs/config.yml | 2 +- configs/playbook.yml | 4 ---- configs/prometheus.yml | 2 +- deployments/docker-compose.yml | 20 +++++--------------- internal/microservices/realtime/node.go | 1 - internal/pkg/delivery/websocket/websocket.go | 2 +- 9 files changed, 32 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66b52c8..4a5ad53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,16 @@ name: Start pinspire CI on: - workflow_dispatch: - + workflow_dispatch: {} + push: + branches: + - TP-c01_ci-cd + - dev3 + - dev4 + pull_request: + types: [opened, edited, reopened] + branches: [main, dev4] + jobs: test: runs-on: ubuntu-latest @@ -10,6 +18,7 @@ jobs: - name: Get repository code uses: actions/checkout@v4 - name: Test application + continue-on-error: true run: go test ./... lint: runs-on: ubuntu-latest @@ -19,7 +28,7 @@ jobs: - name: Lint application run: make lint build: - runs-o: ubuntu-latest + runs-on: ubuntu-latest steps: - name: Get repository code uses: actions/checkout@v4 diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 1928b3a..bb54dc3 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -1,7 +1,14 @@ name: Start Pinspire deployment on: - workflow_dispatch: + workflow_dispatch: {} + push: + branches: + - TP-c01_ci-cd + - dev4 + pull_request: + types: [opened, edited, reopened] + branches: [main, dev4] jobs: build_images: @@ -39,7 +46,7 @@ jobs: key: ${{ secrets.PRIVATE_KEY }} script: | cd ${{ secrets.PINSPIRE_BACKEND_PATH }} - sudo git switch dev3 + sudo git switch TP-c01_ci-cd sudo git pull - name: deploy application uses: appleboy/ssh-action@master @@ -51,5 +58,5 @@ jobs: cd ${{ secrets.PINSPIRE_BACKEND_PATH }}/deployments sudo docker compose down main_service auth_service realtime_service messenger_service sudo docker rmi pinspireapp/main:latest pinspireapp/auth:latest pinspireapp/realtime:latest pinspireapp/messenger:latest - docker compose -f docker-compose.yml -f compose.prod.yml up -d + sudo docker compose -f docker-compose.yml -f compose.prod.yml up -d \ No newline at end of file diff --git a/cmd/realtime/main.go b/cmd/realtime/main.go index 1022c65..a01f79f 100644 --- a/cmd/realtime/main.go +++ b/cmd/realtime/main.go @@ -11,11 +11,13 @@ import ( grpcMetrics "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics/grpc" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/grpc/interceptor" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/joho/godotenv" ) const _address = "0.0.0.0:8090" func main() { + godotenv.Load() log, err := logger.New() if err != nil { fmt.Println(err) diff --git a/configs/config.yml b/configs/config.yml index 4d1fda6..87b2acd 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -2,6 +2,6 @@ app: server: host: 0.0.0.0 port: 8080 - https: false + https: true certFile: /home/ond_team/cert/fullchain.pem keyFile: /home/ond_team/cert/privkey.pem diff --git a/configs/playbook.yml b/configs/playbook.yml index c3d8bf0..2daa6b2 100644 --- a/configs/playbook.yml +++ b/configs/playbook.yml @@ -10,7 +10,3 @@ copy: src: ../redis.conf dest: /home/ond_team/go/src/github.com/go-park-mail-ru/ci-cd/redis.conf - - name: "Provide nginx config" - copy: - src: /etc/nginx/sites-available/pinspire.conf - dest: /etc/nginx/sites-available/pinspire.conf \ No newline at end of file diff --git a/configs/prometheus.yml b/configs/prometheus.yml index 57406b3..a65d7b8 100644 --- a/configs/prometheus.yml +++ b/configs/prometheus.yml @@ -5,7 +5,7 @@ global: scrape_configs: - job_name: 'api' static_configs: - - targets: ['main_service:8080'] + - targets: ['main_service:8079'] - job_name: 'auth' static_configs: diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 1aa64f2..44d21c6 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -46,6 +46,9 @@ services: - AUTH_SERVICE_HOST=auth_service - MESSENGER_SERVICE_HOST=messenger_service - REALTIME_SERVICE_HOST=realtime_service + volumes: + - '/home/ond_team/cert/fullchain.pem:/home/ond_team/cert/fullchain.pem:ro' + - '/home/ond_team/cert/privkey.pem:/home/ond_team/cert/privkey.pem:ro' depends_on: postgres: condition: 'service_healthy' @@ -56,7 +59,7 @@ services: realtime_service: condition: 'service_started' ports: - - 8100:8080 + - 8079:8080 auth_service: build: @@ -130,7 +133,7 @@ services: - ALLOW_PLAINTEXT_LISTENER=yes - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://:9092 + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 healthcheck: test: | curl localhost:9092 @@ -175,19 +178,6 @@ services: ports: - "9100:9100" - nginx: - image: nginx:latest - container_name: pinspireNginx - volumes: - - '/etc/nginx/sites-available/pinspire.conf:/etc/nginx/conf.d/pinspire.conf:ro' - network_mode: 'host' - depends_on: - main_service: - condition: 'service_started' - realtime_service: - condition: 'service_started' - - volumes: postgres_storage: {} redis_storage: {} diff --git a/internal/microservices/realtime/node.go b/internal/microservices/realtime/node.go index f6f39c7..53d64cc 100644 --- a/internal/microservices/realtime/node.go +++ b/internal/microservices/realtime/node.go @@ -19,7 +19,6 @@ type Node struct { func NewNode() (*Node, error) { node := &Node{} - broker, err := NewKafkaBroker(node, KafkaConfig{ // Addres: []string{"localhost:9092"}, Addres: []string{os.Getenv("KAFKA_BROKER_ADDRESS") + ":" + os.Getenv("KAFKA_BROKER_PORT")}, diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index c0042de..07635de 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -38,7 +38,7 @@ func SetOriginPatterns(patterns []string) Option { func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { // gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) - gRPCConn, err := grpc.Dial(os.Getenv("REALTIME_SERVICE_HOST"+":"+os.Getenv("REALTIME_SERVICE_PORT")), grpc.WithTransportCredentials(insecure.NewCredentials())) + gRPCConn, err := grpc.Dial((os.Getenv("REALTIME_SERVICE_HOST") + ":" + os.Getenv("REALTIME_SERVICE_PORT")), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(fmt.Errorf("grpc dial: %w", err).Error()) } From 8d07119e3b71dec010e1f951a51d7c61f0aefff6 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sat, 9 Dec 2023 19:55:59 +0300 Subject: [PATCH 231/266] TP-c01_ci-cd: deleted dev3 from triggers --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a5ad53..69188a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: push: branches: - TP-c01_ci-cd - - dev3 - dev4 pull_request: types: [opened, edited, reopened] From df49eed9f32451eb0ef66afcfd88e0bdb8232a5f Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sat, 9 Dec 2023 20:06:28 +0300 Subject: [PATCH 232/266] TP-c01_ci-cd: changed triggers, add step --- .github/workflows/ci.yml | 5 +---- .github/workflows/deployment.yml | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69188a3..e0f2a3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,7 @@ name: Start pinspire CI on: workflow_dispatch: {} - push: - branches: - - TP-c01_ci-cd - - dev4 + push: {} pull_request: types: [opened, edited, reopened] branches: [main, dev4] diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index bb54dc3..9036f2b 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -14,9 +14,8 @@ jobs: build_images: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - ref: dev3 + - name: get repository code + uses: actions/checkout@v4 - name: Login to DockerHub Registry run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin - name: Build docker images of services From 4b6b885f73cd8406711d29c5e5996aeeadadd3b0 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 10 Dec 2023 22:56:05 +0300 Subject: [PATCH 233/266] TP-59b add: comments --- go.mod | 2 +- internal/api/server/router/router.go | 9 + internal/app/app.go | 6 +- internal/pkg/delivery/http/v1/comment.go | 121 ++++++++++ internal/pkg/delivery/http/v1/feed.go | 4 +- internal/pkg/delivery/http/v1/handler.go | 4 + internal/pkg/delivery/http/v1/pin.go | 2 +- internal/pkg/entity/board/board.go | 2 + internal/pkg/entity/board/board_easyjson.go | 142 +++++++++++ internal/pkg/entity/comment/comment.go | 16 ++ .../pkg/entity/comment/comment_easyjson.go | 224 ++++++++++++++++++ internal/pkg/entity/message/message.go | 3 + .../pkg/entity/message/message_easyjson.go | 108 +++++++++ internal/pkg/entity/user/user.go | 2 + .../repository/comment/mock/comment_mock.go | 95 ++++++++ internal/pkg/repository/comment/queries.go | 15 ++ internal/pkg/repository/comment/repo.go | 85 +++++++ internal/pkg/usecase/comment/check.go | 29 +++ .../pkg/usecase/comment/mock/comment_mock.go | 133 +++++++++++ internal/pkg/usecase/comment/usecase.go | 78 ++++++ internal/pkg/usecase/pin/check.go | 11 +- internal/pkg/usecase/pin/usecase.go | 11 +- 22 files changed, 1092 insertions(+), 10 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/comment.go create mode 100644 internal/pkg/entity/board/board_easyjson.go create mode 100644 internal/pkg/entity/comment/comment.go create mode 100644 internal/pkg/entity/comment/comment_easyjson.go create mode 100644 internal/pkg/entity/message/message_easyjson.go create mode 100644 internal/pkg/repository/comment/mock/comment_mock.go create mode 100644 internal/pkg/repository/comment/queries.go create mode 100644 internal/pkg/repository/comment/repo.go create mode 100644 internal/pkg/usecase/comment/check.go create mode 100644 internal/pkg/usecase/comment/mock/comment_mock.go create mode 100644 internal/pkg/usecase/comment/usecase.go diff --git a/go.mod b/go.mod index f2512ed..9f4f39f 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 github.com/joho/godotenv v1.5.1 + github.com/mailru/easyjson v0.7.7 github.com/microcosm-cc/bluemonday v1.0.26 github.com/pashagolub/pgxmock/v2 v2.12.0 github.com/prometheus/client_golang v1.17.0 @@ -69,7 +70,6 @@ require ( github.com/klauspost/compress v1.16.7 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 66b6f54..d9f7608 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -101,6 +101,15 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli r.Delete("/like/{pinID:\\d+}", handler.DeleteLikePin) r.Delete("/delete/{pinID:\\d+}", handler.DeletePin) }) + + r.Route("/comment", func(r chi.Router) { + r.Get("/feed/{pinID:\\d+}", handler.ViewFeedComment) + + r.With(auth.RequireAuth).Group(func(r chi.Router) { + r.Post("/{pinID:\\d+}", handler.WriteComment) + r.Delete("/{commentID:\\d+}", handler.DeleteComment) + }) + }) }) r.Route("/board", func(r chi.Router) { diff --git a/internal/app/app.go b/internal/app/app.go index 0437156..4813c36 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -17,6 +17,7 @@ import ( deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" + commentRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/comment" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" pinRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" searchRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/search/postgres" @@ -24,6 +25,7 @@ import ( userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/comment" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" @@ -66,6 +68,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) messageCase := message.New(messenger.NewMessengerClient(connMessMS)) + pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) conn, err := grpc.Dial(cfg.AddrAuthServer, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { @@ -78,11 +81,12 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ AuhtCase: ac, UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), - PinCase: pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)), + PinCase: pinCase, BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), SearchCase: search.New(log, searchRepo.NewSearchRepoPG(pool), bluemonday.UGCPolicy()), MessageCase: messageCase, + CommentCase: comment.New(commentRepo.NewCommentRepoPG(pool), pinCase), }) wsHandler := deliveryWS.New(log, messageCase, diff --git a/internal/pkg/delivery/http/v1/comment.go b/internal/pkg/delivery/http/v1/comment.go new file mode 100644 index 0000000..60e1b8d --- /dev/null +++ b/internal/pkg/delivery/http/v1/comment.go @@ -0,0 +1,121 @@ +package v1 + +import ( + "fmt" + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/mailru/easyjson" +) + +func (h *HandlerHTTP) WriteComment(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + pinID, err := fetchURLParamInt(r, "pinID") + if err != nil { + err = responseError(w, "parse_url", "the request url could not be get pin id") + if err != nil { + logger.Error(err.Error()) + return + } + } + + comment := &comment.Comment{} + + err = easyjson.UnmarshalFromReader(r.Body, comment) + fmt.Println(comment.Content) + defer r.Body.Close() + if err != nil { + logger.Warn(err.Error()) + + err = responseError(w, "parse_body", "the request body could not be parsed to send a comment") + if err != nil { + logger.Error(err.Error()) + return + } + } + + comment.PinID = pinID + _, err = h.commentCase.PutCommentOnPin(r.Context(), userID, comment) + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "create_comment", "couldn't leave a comment under the selected pin") + } else { + err = responseOk(http.StatusCreated, w, "the comment has been added successfully", nil) + } + if err != nil { + logger.Error(err.Error()) + } +} + +func (h *HandlerHTTP) DeleteComment(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + + commentID, err := fetchURLParamInt(r, "commentID") + if err != nil { + err = responseError(w, "parse_url", "the request url could not be get pin id") + if err != nil { + logger.Error(err.Error()) + return + } + } + + err = h.commentCase.DeleteComment(r.Context(), userID, commentID) + if err != nil { + logger.Error(err.Error()) + err = responseError(w, "delete_comment", "couldn't delete pin comment") + } else { + err = responseOk(http.StatusOK, w, "the comment was successfully deleted", nil) + } + if err != nil { + logger.Error(err.Error()) + } +} + +func (h *HandlerHTTP) ViewFeedComment(w http.ResponseWriter, r *http.Request) { + logger := h.getRequestLogger(r) + userID, ok := r.Context().Value(auth.KeyCurrentUserID).(int) + if !ok { + userID = user.UserUnknown + } + + pinID, err := fetchURLParamInt(r, "pinID") + if err != nil { + err = responseError(w, "parse_url", "the request url could not be get pin id") + if err != nil { + logger.Error(err.Error()) + return + } + } + + count, lastID, err := FetchValidParamForLoadFeed(r.URL) + if err != nil { + err = responseError(w, "query_param", "the parameters for displaying the pin feed could not be extracted from the request") + if err != nil { + logger.Error(err.Error()) + return + } + } + + feed, newLastID, err := h.commentCase.GetFeedCommentOnPin(r.Context(), userID, pinID, count, lastID) + if err != nil && len(feed) == 0 { + err = responseError(w, "feed_view", "error displaying pin comments") + if err != nil { + logger.Error(err.Error()) + } + return + } + + if err != nil { + logger.Error(err.Error()) + } + + err = responseOk(http.StatusOK, w, "feed comment to pin", map[string]any{"comments": feed, "lastID": newLastID}) + if err != nil { + logger.Error(err.Error()) + } +} diff --git a/internal/pkg/delivery/http/v1/feed.go b/internal/pkg/delivery/http/v1/feed.go index 4a32690..02d71a1 100644 --- a/internal/pkg/delivery/http/v1/feed.go +++ b/internal/pkg/delivery/http/v1/feed.go @@ -8,8 +8,8 @@ import ( "strconv" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" - usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -17,7 +17,7 @@ func (h *HandlerHTTP) FeedPins(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) userID, isAuth := r.Context().Value(auth.KeyCurrentUserID).(int) if !isAuth { - userID = usecase.UserUnknown + userID = user.UserUnknown } logger.Info("request on getting feed of pins", log.F{"rawQuery", r.URL.RawQuery}) diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 837e590..736654e 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -3,6 +3,7 @@ package v1 import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/board" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/comment" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/search" @@ -20,6 +21,7 @@ type HandlerHTTP struct { subCase subscription.Usecase searchCase search.Usecase messageCase message.Usecase + commentCase comment.Usecase } func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { @@ -32,6 +34,7 @@ func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { subCase: hub.SubscriptionCase, searchCase: hub.SearchCase, messageCase: hub.MessageCase, + commentCase: hub.CommentCase, } } @@ -43,4 +46,5 @@ type UsecaseHub struct { SubscriptionCase subscription.Usecase SearchCase search.Usecase MessageCase message.Usecase + CommentCase comment.Usecase } diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index be7c2c9..f9ba931 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -168,7 +168,7 @@ func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { userID, ok := r.Context().Value(auth.KeyCurrentUserID).(int) if !ok { - userID = usecase.UserUnknown + userID = user.UserUnknown } pin, err := h.pinCase.ViewAnPin(r.Context(), int(pinID), userID) if err != nil { diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go index cf783a5..3ce9b66 100644 --- a/internal/pkg/entity/board/board.go +++ b/internal/pkg/entity/board/board.go @@ -6,6 +6,8 @@ import ( "github.com/microcosm-cc/bluemonday" ) +//go:generate easyjson board.go +//easyjson:json type Board struct { ID int `json:"id,omitempty" example:"15"` AuthorID int `json:"author_id,omitempty"` diff --git a/internal/pkg/entity/board/board_easyjson.go b/internal/pkg/entity/board/board_easyjson.go new file mode 100644 index 0000000..7e6adbe --- /dev/null +++ b/internal/pkg/entity/board/board_easyjson.go @@ -0,0 +1,142 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package board + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" + time "time" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(in *jlexer.Lexer, out *Board) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "author_id": + out.AuthorID = int(in.Int()) + case "title": + out.Title = string(in.String()) + case "description": + out.Description = string(in.String()) + case "public": + out.Public = bool(in.Bool()) + case "created_at": + if in.IsNull() { + in.Skip() + out.CreatedAt = nil + } else { + if out.CreatedAt == nil { + out.CreatedAt = new(time.Time) + } + if data := in.Raw(); in.Ok() { + in.AddError((*out.CreatedAt).UnmarshalJSON(data)) + } + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(out *jwriter.Writer, in Board) { + out.RawByte('{') + first := true + _ = first + if in.ID != 0 { + const prefix string = ",\"id\":" + first = false + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + if in.AuthorID != 0 { + const prefix string = ",\"author_id\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int(int(in.AuthorID)) + } + { + const prefix string = ",\"title\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Title)) + } + { + const prefix string = ",\"description\":" + out.RawString(prefix) + out.String(string(in.Description)) + } + { + const prefix string = ",\"public\":" + out.RawString(prefix) + out.Bool(bool(in.Public)) + } + if in.CreatedAt != nil { + const prefix string = ",\"created_at\":" + out.RawString(prefix) + out.Raw((*in.CreatedAt).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Board) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Board) MarshalEasyJSON(w *jwriter.Writer) { + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Board) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Board) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(l, v) +} diff --git a/internal/pkg/entity/comment/comment.go b/internal/pkg/entity/comment/comment.go new file mode 100644 index 0000000..493e153 --- /dev/null +++ b/internal/pkg/entity/comment/comment.go @@ -0,0 +1,16 @@ +package comment + +import ( + "github.com/jackc/pgx/v5/pgtype" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" +) + +//go:generate easyjson comment.go +//easyjson:json +type Comment struct { + ID int `json:"id"` + Author *user.User `json:"author"` + PinID int `json:"pinID"` + Content pgtype.Text `json:"content"` +} diff --git a/internal/pkg/entity/comment/comment_easyjson.go b/internal/pkg/entity/comment/comment_easyjson.go new file mode 100644 index 0000000..c3bb7ab --- /dev/null +++ b/internal/pkg/entity/comment/comment_easyjson.go @@ -0,0 +1,224 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package comment + +import ( + json "encoding/json" + user "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(in *jlexer.Lexer, out *Comment) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "author": + if in.IsNull() { + in.Skip() + out.Author = nil + } else { + if out.Author == nil { + out.Author = new(user.User) + } + easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(in, out.Author) + } + case "pinID": + out.PinID = int(in.Int()) + case "content": + if data := in.Raw(); in.Ok() { + in.AddError((out.Content).UnmarshalJSON(data)) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(out *jwriter.Writer, in Comment) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"author\":" + out.RawString(prefix) + if in.Author == nil { + out.RawString("null") + } else { + easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(out, *in.Author) + } + } + { + const prefix string = ",\"pinID\":" + out.RawString(prefix) + out.Int(int(in.PinID)) + } + { + const prefix string = ",\"content\":" + out.RawString(prefix) + out.Raw((in.Content).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Comment) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Comment) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Comment) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Comment) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(l, v) +} +func easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(in *jlexer.Lexer, out *user.User) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "username": + out.Username = string(in.String()) + case "name": + if data := in.Raw(); in.Ok() { + in.AddError((out.Name).UnmarshalJSON(data)) + } + case "surname": + if data := in.Raw(); in.Ok() { + in.AddError((out.Surname).UnmarshalJSON(data)) + } + case "email": + out.Email = string(in.String()) + case "avatar": + out.Avatar = string(in.String()) + case "about_me": + if data := in.Raw(); in.Ok() { + in.AddError((out.AboutMe).UnmarshalJSON(data)) + } + case "password": + out.Password = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(out *jwriter.Writer, in user.User) { + out.RawByte('{') + first := true + _ = first + if in.ID != 0 { + const prefix string = ",\"id\":" + first = false + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"username\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Username)) + } + if true { + const prefix string = ",\"name\":" + out.RawString(prefix) + out.Raw((in.Name).MarshalJSON()) + } + if true { + const prefix string = ",\"surname\":" + out.RawString(prefix) + out.Raw((in.Surname).MarshalJSON()) + } + if in.Email != "" { + const prefix string = ",\"email\":" + out.RawString(prefix) + out.String(string(in.Email)) + } + { + const prefix string = ",\"avatar\":" + out.RawString(prefix) + out.String(string(in.Avatar)) + } + if true { + const prefix string = ",\"about_me\":" + out.RawString(prefix) + out.Raw((in.AboutMe).MarshalJSON()) + } + if in.Password != "" { + const prefix string = ",\"password\":" + out.RawString(prefix) + out.String(string(in.Password)) + } + out.RawByte('}') +} diff --git a/internal/pkg/entity/message/message.go b/internal/pkg/entity/message/message.go index f6f893d..b79a2a2 100644 --- a/internal/pkg/entity/message/message.go +++ b/internal/pkg/entity/message/message.go @@ -6,8 +6,11 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) +//go:generate easyjson message.go + type Chat [2]int +//easyjson:json type Message struct { ID int From int `json:"from"` diff --git a/internal/pkg/entity/message/message_easyjson.go b/internal/pkg/entity/message/message_easyjson.go new file mode 100644 index 0000000..83e16f5 --- /dev/null +++ b/internal/pkg/entity/message/message_easyjson.go @@ -0,0 +1,108 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package message + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMessage(in *jlexer.Lexer, out *Message) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "ID": + out.ID = int(in.Int()) + case "from": + out.From = int(in.Int()) + case "to": + out.To = int(in.Int()) + case "content": + if data := in.Raw(); in.Ok() { + in.AddError((out.Content).UnmarshalJSON(data)) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMessage(out *jwriter.Writer, in Message) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"ID\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"from\":" + out.RawString(prefix) + out.Int(int(in.From)) + } + { + const prefix string = ",\"to\":" + out.RawString(prefix) + out.Int(int(in.To)) + } + { + const prefix string = ",\"content\":" + out.RawString(prefix) + out.Raw((in.Content).MarshalJSON()) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Message) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMessage(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Message) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMessage(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Message) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMessage(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Message) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMessage(l, v) +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 1b270be..70f2e14 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -5,6 +5,8 @@ import ( "github.com/microcosm-cc/bluemonday" ) +const UserUnknown = -1 + type User struct { ID int `json:"id,omitempty" example:"123"` Username string `json:"username" example:"Green"` diff --git a/internal/pkg/repository/comment/mock/comment_mock.go b/internal/pkg/repository/comment/mock/comment_mock.go new file mode 100644 index 0000000..21cd598 --- /dev/null +++ b/internal/pkg/repository/comment/mock/comment_mock.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repo.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + comment "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddComment mocks base method. +func (m *MockRepository) AddComment(ctx context.Context, comment *comment.Comment) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddComment", ctx, comment) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddComment indicates an expected call of AddComment. +func (mr *MockRepositoryMockRecorder) AddComment(ctx, comment interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddComment", reflect.TypeOf((*MockRepository)(nil).AddComment), ctx, comment) +} + +// EditStatusCommentOnDeletedByID mocks base method. +func (m *MockRepository) EditStatusCommentOnDeletedByID(ctx context.Context, id int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EditStatusCommentOnDeletedByID", ctx, id) + ret0, _ := ret[0].(error) + return ret0 +} + +// EditStatusCommentOnDeletedByID indicates an expected call of EditStatusCommentOnDeletedByID. +func (mr *MockRepositoryMockRecorder) EditStatusCommentOnDeletedByID(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditStatusCommentOnDeletedByID", reflect.TypeOf((*MockRepository)(nil).EditStatusCommentOnDeletedByID), ctx, id) +} + +// GetCommensToPin mocks base method. +func (m *MockRepository) GetCommensToPin(ctx context.Context, pinID, lastID, count int) ([]comment.Comment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCommensToPin", ctx, pinID, lastID, count) + ret0, _ := ret[0].([]comment.Comment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCommensToPin indicates an expected call of GetCommensToPin. +func (mr *MockRepositoryMockRecorder) GetCommensToPin(ctx, pinID, lastID, count interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommensToPin", reflect.TypeOf((*MockRepository)(nil).GetCommensToPin), ctx, pinID, lastID, count) +} + +// GetCommentByID mocks base method. +func (m *MockRepository) GetCommentByID(ctx context.Context, id int) (*comment.Comment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCommentByID", ctx, id) + ret0, _ := ret[0].(*comment.Comment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCommentByID indicates an expected call of GetCommentByID. +func (mr *MockRepositoryMockRecorder) GetCommentByID(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommentByID", reflect.TypeOf((*MockRepository)(nil).GetCommentByID), ctx, id) +} diff --git a/internal/pkg/repository/comment/queries.go b/internal/pkg/repository/comment/queries.go new file mode 100644 index 0000000..85057a2 --- /dev/null +++ b/internal/pkg/repository/comment/queries.go @@ -0,0 +1,15 @@ +package comment + +const ( + InsertNewComment = "INSERT INTO comment (author, pin_id, content) VALUES ($1, $2, $3) RETURNING id;" + + UpdateCommentOnDeleted = "UPDATE comment SET deleted_at = now() WHERE id = $1;" + + SelectCommentByID = "SELECT author, pin_id, content FROM comment WHERE id = $1 AND deleted_at IS NULL;" + SelectCommentsByPinID = `SELECT c.id, p.id, p.username, p.avatar, c.content + FROM comment AS c INNER JOIN profile AS p + ON c.author = p.id + WHERE c.pin_id = $1 AND (c.id < $2 OR $2 = 0) AND c.deleted_at IS NULL + ORDER BY c.id DESC + LIMIT $3;` +) diff --git a/internal/pkg/repository/comment/repo.go b/internal/pkg/repository/comment/repo.go new file mode 100644 index 0000000..a002718 --- /dev/null +++ b/internal/pkg/repository/comment/repo.go @@ -0,0 +1,85 @@ +package comment + +import ( + "context" + "errors" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/internal/pgtype" +) + +//go:generate mockgen -destination=./mock/comment_mock.go -package=mock -source=repo.go Repository +type Repository interface { + AddComment(ctx context.Context, comment *entity.Comment) (int, error) + GetCommentByID(ctx context.Context, id int) (*entity.Comment, error) + EditStatusCommentOnDeletedByID(ctx context.Context, id int) error + GetCommensToPin(ctx context.Context, pinID, lastID, count int) ([]entity.Comment, error) +} + +var ErrUserRequired = errors.New("the comment does not have its author specified") + +type commentRepoPG struct { + db pgtype.PgxPoolIface +} + +func NewCommentRepoPG(db pgtype.PgxPoolIface) *commentRepoPG { + return &commentRepoPG{db} +} + +func (c *commentRepoPG) AddComment(ctx context.Context, comment *entity.Comment) (int, error) { + if comment.Author == nil { + return 0, ErrUserRequired + } + + var idInsertedComment int + err := c.db.QueryRow(ctx, InsertNewComment, comment.Author.ID, comment.PinID, comment.Content). + Scan(&idInsertedComment) + if err != nil { + return 0, fmt.Errorf("add comment in storage: %w", err) + } + return idInsertedComment, nil +} + +func (c *commentRepoPG) GetCommentByID(ctx context.Context, id int) (*entity.Comment, error) { + comment := &entity.Comment{ID: id, Author: &user.User{}} + + err := c.db.QueryRow(ctx, SelectCommentByID, id).Scan(&comment.Author.ID, &comment.PinID, &comment.Content) + if err != nil { + return nil, fmt.Errorf("get comment by id from storage: %w", err) + } + + return comment, nil +} + +func (c *commentRepoPG) EditStatusCommentOnDeletedByID(ctx context.Context, id int) error { + if _, err := c.db.Exec(ctx, UpdateCommentOnDeleted, id); err != nil { + return fmt.Errorf("edit status comment on deleted comment by id from storage: %w", err) + } + return nil +} + +func (c *commentRepoPG) GetCommensToPin(ctx context.Context, pinID, lastID, count int) ([]entity.Comment, error) { + rows, err := c.db.Query(ctx, SelectCommentsByPinID, pinID, lastID, count) + if err != nil { + return nil, fmt.Errorf("get comments to pin from storage: %w", err) + } + defer rows.Close() + + cmts := make([]entity.Comment, 0, count) + cmt := entity.Comment{ + Author: &user.User{}, + PinID: pinID, + } + + for rows.Next() { + err = rows.Scan(&cmt.ID, &cmt.Author.ID, &cmt.Author.Username, &cmt.Author.Avatar, &cmt.Content) + if err != nil { + return cmts, fmt.Errorf("scan a comment when getting comments on a pin: %w", err) + } + + cmts = append(cmts, cmt) + } + return cmts, nil +} diff --git a/internal/pkg/usecase/comment/check.go b/internal/pkg/usecase/comment/check.go new file mode 100644 index 0000000..a278b68 --- /dev/null +++ b/internal/pkg/usecase/comment/check.go @@ -0,0 +1,29 @@ +package comment + +import ( + "context" + "errors" + "fmt" +) + +var ErrNotAvailableAction = errors.New("action not available for user") + +func (c *commentCase) isAvailableCommentForDelete(ctx context.Context, userID, commentID int) error { + comment, err := c.repo.GetCommentByID(ctx, commentID) + if err != nil { + return fmt.Errorf("get comment for check available comment for delete: %w", err) + } + + if comment.Author.ID == userID { + return nil + } + + authorPinID, err := c.GetAuthorIdOfThePin(ctx, comment.PinID) + if err != nil { + return fmt.Errorf("get author pin for check availabel comment: %w", err) + } + if authorPinID != userID { + return ErrNotAvailableAction + } + return nil +} diff --git a/internal/pkg/usecase/comment/mock/comment_mock.go b/internal/pkg/usecase/comment/mock/comment_mock.go new file mode 100644 index 0000000..92bec48 --- /dev/null +++ b/internal/pkg/usecase/comment/mock/comment_mock.go @@ -0,0 +1,133 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: usecase.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + comment "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + gomock "github.com/golang/mock/gomock" +) + +// MockUsecase is a mock of Usecase interface. +type MockUsecase struct { + ctrl *gomock.Controller + recorder *MockUsecaseMockRecorder +} + +// MockUsecaseMockRecorder is the mock recorder for MockUsecase. +type MockUsecaseMockRecorder struct { + mock *MockUsecase +} + +// NewMockUsecase creates a new mock instance. +func NewMockUsecase(ctrl *gomock.Controller) *MockUsecase { + mock := &MockUsecase{ctrl: ctrl} + mock.recorder = &MockUsecaseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { + return m.recorder +} + +// DeleteComment mocks base method. +func (m *MockUsecase) DeleteComment(ctx context.Context, userID, commentID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteComment", ctx, userID, commentID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteComment indicates an expected call of DeleteComment. +func (mr *MockUsecaseMockRecorder) DeleteComment(ctx, userID, commentID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteComment", reflect.TypeOf((*MockUsecase)(nil).DeleteComment), ctx, userID, commentID) +} + +// GetFeedCommentOnPin mocks base method. +func (m *MockUsecase) GetFeedCommentOnPin(ctx context.Context, userID, pinID, count, lastID int) ([]comment.Comment, int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFeedCommentOnPin", ctx, userID, pinID, count, lastID) + ret0, _ := ret[0].([]comment.Comment) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetFeedCommentOnPin indicates an expected call of GetFeedCommentOnPin. +func (mr *MockUsecaseMockRecorder) GetFeedCommentOnPin(ctx, userID, pinID, count, lastID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFeedCommentOnPin", reflect.TypeOf((*MockUsecase)(nil).GetFeedCommentOnPin), ctx, userID, pinID, count, lastID) +} + +// PutCommentOnPin mocks base method. +func (m *MockUsecase) PutCommentOnPin(ctx context.Context, userID int, comment *comment.Comment) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutCommentOnPin", ctx, userID, comment) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PutCommentOnPin indicates an expected call of PutCommentOnPin. +func (mr *MockUsecaseMockRecorder) PutCommentOnPin(ctx, userID, comment interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCommentOnPin", reflect.TypeOf((*MockUsecase)(nil).PutCommentOnPin), ctx, userID, comment) +} + +// MockavailablePinChecker is a mock of availablePinChecker interface. +type MockavailablePinChecker struct { + ctrl *gomock.Controller + recorder *MockavailablePinCheckerMockRecorder +} + +// MockavailablePinCheckerMockRecorder is the mock recorder for MockavailablePinChecker. +type MockavailablePinCheckerMockRecorder struct { + mock *MockavailablePinChecker +} + +// NewMockavailablePinChecker creates a new mock instance. +func NewMockavailablePinChecker(ctrl *gomock.Controller) *MockavailablePinChecker { + mock := &MockavailablePinChecker{ctrl: ctrl} + mock.recorder = &MockavailablePinCheckerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockavailablePinChecker) EXPECT() *MockavailablePinCheckerMockRecorder { + return m.recorder +} + +// GetAuthorIdOfThePin mocks base method. +func (m *MockavailablePinChecker) GetAuthorIdOfThePin(ctx context.Context, pinID int) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAuthorIdOfThePin", ctx, pinID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAuthorIdOfThePin indicates an expected call of GetAuthorIdOfThePin. +func (mr *MockavailablePinCheckerMockRecorder) GetAuthorIdOfThePin(ctx, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorIdOfThePin", reflect.TypeOf((*MockavailablePinChecker)(nil).GetAuthorIdOfThePin), ctx, pinID) +} + +// IsAvailablePinForViewingUser mocks base method. +func (m *MockavailablePinChecker) IsAvailablePinForViewingUser(ctx context.Context, userID, pinID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAvailablePinForViewingUser", ctx, userID, pinID) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsAvailablePinForViewingUser indicates an expected call of IsAvailablePinForViewingUser. +func (mr *MockavailablePinCheckerMockRecorder) IsAvailablePinForViewingUser(ctx, userID, pinID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailablePinForViewingUser", reflect.TypeOf((*MockavailablePinChecker)(nil).IsAvailablePinForViewingUser), ctx, userID, pinID) +} diff --git a/internal/pkg/usecase/comment/usecase.go b/internal/pkg/usecase/comment/usecase.go new file mode 100644 index 0000000..c2d5d78 --- /dev/null +++ b/internal/pkg/usecase/comment/usecase.go @@ -0,0 +1,78 @@ +package comment + +import ( + "context" + "fmt" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + commentRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/comment" +) + +//go:generate mockgen -destination=./mock/comment_mock.go -package=mock -source=usecase.go Usecase +type Usecase interface { + PutCommentOnPin(ctx context.Context, userID int, comment *entity.Comment) (int, error) + GetFeedCommentOnPin(ctx context.Context, userID, pinID, count, lastID int) ([]entity.Comment, int, error) + DeleteComment(ctx context.Context, userID, commentID int) error +} + +type availablePinChecker interface { + IsAvailablePinForViewingUser(ctx context.Context, userID, pinID int) error + GetAuthorIdOfThePin(ctx context.Context, pinID int) (int, error) +} + +type commentCase struct { + availablePinChecker + + repo commentRepo.Repository +} + +func New(repo commentRepo.Repository, checker availablePinChecker) *commentCase { + return &commentCase{checker, repo} +} + +func (c *commentCase) PutCommentOnPin(ctx context.Context, userID int, comment *entity.Comment) (int, error) { + err := c.IsAvailablePinForViewingUser(ctx, userID, comment.PinID) + if err != nil { + return 0, fmt.Errorf("put comment on not available pin: %w", err) + } + + comment.Author = &user.User{ID: userID} + + id, err := c.repo.AddComment(ctx, comment) + if err != nil { + return 0, fmt.Errorf("put comment on available pin: %w", err) + } + return id, nil +} + +func (c *commentCase) GetFeedCommentOnPin(ctx context.Context, userID, pinID, count, lastID int) ([]entity.Comment, int, error) { + err := c.IsAvailablePinForViewingUser(ctx, userID, pinID) + if err != nil { + return nil, 0, fmt.Errorf("put comment on not available pin: %w", err) + } + + feed, err := c.repo.GetCommensToPin(ctx, pinID, lastID, count) + if err != nil { + err = fmt.Errorf("get feed comment on pin: %w", err) + } + + var newLastID int + if len(feed) > 0 { + newLastID = feed[len(feed)-1].ID + } + return feed, newLastID, err +} + +func (c *commentCase) DeleteComment(ctx context.Context, userID, commentID int) error { + err := c.isAvailableCommentForDelete(ctx, userID, commentID) + if err != nil { + return fmt.Errorf("check available delete comment: %w", err) + } + + err = c.repo.EditStatusCommentOnDeletedByID(ctx, commentID) + if err != nil { + return fmt.Errorf("delete comment: %w", err) + } + return nil +} diff --git a/internal/pkg/usecase/pin/check.go b/internal/pkg/usecase/pin/check.go index 396a45d..4d0a751 100644 --- a/internal/pkg/usecase/pin/check.go +++ b/internal/pkg/usecase/pin/check.go @@ -6,6 +6,7 @@ import ( "fmt" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" ) var ( @@ -18,8 +19,6 @@ var ( const MaxSizeBatchPin = 100 -const UserUnknown = -1 - func (p *pinCase) IsAvailablePinForFixOnBoard(ctx context.Context, pinID, userID int) error { pin, err := p.repo.GetPinByID(ctx, pinID, false) if err != nil { @@ -55,7 +54,7 @@ func (p *pinCase) isAvailablePinForViewingUser(ctx context.Context, pin *entity. if pin.Public || pin.Author.ID == userID { return nil } - if userID == UserUnknown { + if userID == user.UserUnknown { return ErrPinNotAccess } @@ -71,9 +70,13 @@ func (p *pinCase) isAvailablePinForViewingUser(ctx context.Context, pin *entity. } func (p *pinCase) isAvailablePinForSetLike(ctx context.Context, pinID, userID int) error { + return p.IsAvailablePinForViewingUser(ctx, userID, pinID) +} + +func (p *pinCase) IsAvailablePinForViewingUser(ctx context.Context, userID, pinID int) error { pin, err := p.repo.GetPinByID(ctx, pinID, false) if err != nil { - return fmt.Errorf("get a pin to check for the availability of a like: %w", err) + return fmt.Errorf("get a pin to check for the availability: %w", err) } return p.isAvailablePinForViewingUser(ctx, pin, userID) diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 6385475..bb76131 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -8,6 +8,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -97,9 +98,17 @@ func (p *pinCase) ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinCo return pin.FeedPin{}, ErrForbiddenAction } - if !hasBoard && (userID == UserUnknown || !hasUser || userID != user) && cfg.Protection != pin.FeedProtectionPublic { + if !hasBoard && (userID == userEntity.UserUnknown || !hasUser || userID != user) && cfg.Protection != pin.FeedProtectionPublic { return pin.FeedPin{}, ErrForbiddenAction } return p.repo.GetFeedPins(ctx, cfg) } + +func (u *pinCase) GetAuthorIdOfThePin(ctx context.Context, pinID int) (int, error) { + user, err := u.repo.GetAuthorPin(ctx, pinID) + if err != nil { + return 0, fmt.Errorf("get author id of the pin: %w", err) + } + return user.ID, nil +} From 24e85bdd675f95cf78f7dccd750f7c355430fc99 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 13 Dec 2023 13:41:41 +0300 Subject: [PATCH 234/266] TP-6ec_easyjson: generated easyjson, changed serialization for user, board, search, subscription --- go.sum | 9 + internal/pkg/delivery/http/v1/board.go | 140 ++-- .../v1/{board_errors.go => errors/board.go} | 12 +- .../pkg/delivery/http/v1/errors/general.go | 111 ++++ .../v1/{search_errors.go => errors/search.go} | 2 +- internal/pkg/delivery/http/v1/profile.go | 3 +- internal/pkg/delivery/http/v1/response.go | 134 +--- internal/pkg/delivery/http/v1/search.go | 5 +- .../pkg/delivery/http/v1/structs/board.go | 71 ++ .../http/v1/structs/board_easyjson.go | 605 ++++++++++++++++++ .../http/v1/{ => structs}/board_validation.go | 2 +- .../pkg/delivery/http/v1/structs/response.go | 17 + .../http/v1/structs/response_easyjson.go | 191 ++++++ .../delivery/http/v1/structs/subscription.go | 17 + .../http/v1/structs/subscription_easyjson.go | 97 +++ internal/pkg/delivery/http/v1/subscription.go | 35 +- internal/pkg/entity/board/board.go | 2 + internal/pkg/entity/board/board_easyjson.go | 163 ++++- .../pkg/entity/comment/comment_easyjson.go | 107 +--- internal/pkg/entity/search/search.go | 5 + internal/pkg/entity/search/search_easyjson.go | 312 +++++++++ internal/pkg/entity/user/user.go | 4 + internal/pkg/entity/user/user_easyjson.go | 233 +++++++ 23 files changed, 1913 insertions(+), 364 deletions(-) rename internal/pkg/delivery/http/v1/{board_errors.go => errors/board.go} (83%) create mode 100644 internal/pkg/delivery/http/v1/errors/general.go rename internal/pkg/delivery/http/v1/{search_errors.go => errors/search.go} (96%) create mode 100644 internal/pkg/delivery/http/v1/structs/board.go create mode 100644 internal/pkg/delivery/http/v1/structs/board_easyjson.go rename internal/pkg/delivery/http/v1/{ => structs}/board_validation.go (98%) create mode 100644 internal/pkg/delivery/http/v1/structs/response.go create mode 100644 internal/pkg/delivery/http/v1/structs/response_easyjson.go create mode 100644 internal/pkg/delivery/http/v1/structs/subscription.go create mode 100644 internal/pkg/delivery/http/v1/structs/subscription_easyjson.go create mode 100644 internal/pkg/entity/search/search_easyjson.go create mode 100644 internal/pkg/entity/user/user_easyjson.go diff --git a/go.sum b/go.sum index c53b878..1ec2c4a 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -51,6 +53,7 @@ github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -119,6 +122,9 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= @@ -145,6 +151,9 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 910062b..f55db48 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -2,57 +2,23 @@ package v1 import ( "encoding/json" - "fmt" "net/http" "strconv" "github.com/go-chi/chi/v5" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + "github.com/mailru/easyjson" + errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) var TimeFormat = "2006-01-02" -// data for board creation/update -type BoardData struct { - Title *string `json:"title" example:"new board"` - Description *string `json:"description" example:"long desc"` - Public *bool `json:"public" example:"true"` - Tags []string `json:"tags" example:"['blue', 'car']"` -} - -// board view for delivery layer -type CertainBoard struct { - ID int `json:"board_id" example:"22"` - AuthorID int `json:"author_id" example:"22"` - Title string `json:"title" example:"new board"` - Description string `json:"description" example:"long desc"` - CreatedAt string `json:"created_at" example:"07-11-2023"` - PinsNumber int `json:"pins_number" example:"12"` - Pins []string `json:"pins" example:"['/pic1', '/pic2']"` - Tags []string `json:"tags" example:"['love', 'green']"` -} - -type CertainBoardWithUsername struct { - ID int `json:"board_id" example:"22"` - AuthorID int `json:"author_id" example:"22"` - AuthorUsername string `json:"author_username" example:"Bob"` - Title string `json:"title" example:"new board"` - Description string `json:"description" example:"long desc"` - CreatedAt string `json:"created_at" example:"07-11-2023"` - PinsNumber int `json:"pins_number" example:"12"` - Pins []string `json:"pins" example:"['/pic1', '/pic2']"` - Tags []string `json:"tags" example:"['love', 'green']"` -} - -type DeletePinFromBoard struct { - PinID int `json:"pin_id" example:"22"` -} - -func ToCertainBoardFromService(board entity.BoardWithContent) CertainBoard { - return CertainBoard{ +func ToCertainBoardFromService(board entity.BoardWithContent) structs.CertainBoard { + return structs.CertainBoard{ ID: board.BoardInfo.ID, AuthorID: board.BoardInfo.AuthorID, Title: board.BoardInfo.Title, @@ -64,8 +30,8 @@ func ToCertainBoardFromService(board entity.BoardWithContent) CertainBoard { } } -func ToCertainBoardUsernameFromService(board entity.BoardWithContent, username string) CertainBoardWithUsername { - return CertainBoardWithUsername{ +func ToCertainBoardUsernameFromService(board entity.BoardWithContent, username string) structs.CertainBoardWithUsername { + return structs.CertainBoardWithUsername{ ID: board.BoardInfo.ID, AuthorID: board.BoardInfo.AuthorID, AuthorUsername: username, @@ -78,40 +44,20 @@ func ToCertainBoardUsernameFromService(board entity.BoardWithContent, username s } } -func (data *BoardData) Validate() error { - if data.Title == nil || *data.Title == "" { - return ErrInvalidBoardTitle - } - if data.Description == nil { - data.Description = new(string) - *data.Description = "" - } - if data.Public == nil { - return ErrEmptyPubOpt - } - if !isValidBoardTitle(*data.Title) { - return ErrInvalidBoardTitle - } - if err := checkIsValidTagTitles(data.Tags); err != nil { - return fmt.Errorf("%s: %w", err.Error(), ErrInvalidTagTitles) - } - return nil -} - func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - code, message := getErrCodeMessage(ErrBadContentType) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadContentType) responseError(w, code, message) return } - var newBoard BoardData - err := json.NewDecoder(r.Body).Decode(&newBoard) + var newBoard structs.BoardData + err := easyjson.UnmarshalFromReader(r.Body, &newBoard) defer r.Body.Close() if err != nil { logger.Info("create board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadBody) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadBody) responseError(w, code, message) return } @@ -119,7 +65,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { err = newBoard.Validate() if err != nil { logger.Info("create board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -140,7 +86,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Info("create board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -149,7 +95,7 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } @@ -158,8 +104,8 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { username := chi.URLParam(r, "username") if !isValidUsername(username) { - logger.Info("update board", log.F{"message", ErrInvalidUsername.Error()}) - code, message := getErrCodeMessage(ErrInvalidUsername) + logger.Info("update board", log.F{"message", errHTTP.ErrInvalidUsername.Error()}) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrInvalidUsername) responseError(w, code, message) return } @@ -167,12 +113,12 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { boards, err := h.boardCase.GetBoardsByUsername(r.Context(), username) if err != nil { logger.Info("get user boards", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } - userBoards := make([]CertainBoard, 0, len(boards)) + userBoards := make([]structs.CertainBoard, 0, len(boards)) for _, board := range boards { userBoards = append(userBoards, ToCertainBoardFromService(board)) } @@ -180,7 +126,7 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } @@ -190,7 +136,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("get certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadUrlParam) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadUrlParam) responseError(w, code, message) return } @@ -198,7 +144,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { board, username, err := h.boardCase.GetCertainBoard(r.Context(), int(boardID)) if err != nil { logger.Info("get certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -207,7 +153,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } @@ -217,7 +163,7 @@ func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Reque boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("get certain board info for update", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadUrlParam) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadUrlParam) responseError(w, code, message) return } @@ -225,7 +171,7 @@ func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Reque board, tagTitles, err := h.boardCase.GetBoardInfoForUpdate(r.Context(), int(boardID)) if err != nil { logger.Info("get certain board info for update", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -234,14 +180,14 @@ func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Reque if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - code, message := getErrCodeMessage(ErrBadContentType) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadContentType) responseError(w, code, message) return } @@ -249,17 +195,17 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadUrlParam) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadUrlParam) responseError(w, code, message) return } - var updatedData BoardData - err = json.NewDecoder(r.Body).Decode(&updatedData) + var updatedData structs.BoardData + err = easyjson.UnmarshalFromReader(r.Body, &updatedData) defer r.Body.Close() if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadBody) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadBody) responseError(w, code, message) return } @@ -267,7 +213,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { err = updatedData.Validate() if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -286,7 +232,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { err = h.boardCase.UpdateBoardInfo(r.Context(), updatedBoard, tagTitles) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -295,7 +241,7 @@ func (h *HandlerHTTP) UpdateBoardInfo(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } @@ -305,7 +251,7 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadUrlParam) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadUrlParam) responseError(w, code, message) return } @@ -313,7 +259,7 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { err = h.boardCase.DeleteCertainBoard(r.Context(), int(boardID)) if err != nil { logger.Info("update certain board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -322,7 +268,7 @@ func (h *HandlerHTTP) DeleteBoard(w http.ResponseWriter, r *http.Request) { if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } @@ -390,21 +336,21 @@ func (h *HandlerHTTP) DeletePinFromBoard(w http.ResponseWriter, r *http.Request) boardID, err := strconv.ParseInt(chi.URLParam(r, "boardID"), 10, 64) if err != nil { logger.Info("delete pin from board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(ErrBadUrlParam) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadUrlParam) responseError(w, code, message) return } if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - code, message := getErrCodeMessage(ErrBadContentType) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadContentType) responseError(w, code, message) return } - delPinFromBoard := DeletePinFromBoard{} - err = json.NewDecoder(r.Body).Decode(&delPinFromBoard) + delPinFromBoard := structs.DeletePinFromBoard{} + err = easyjson.UnmarshalFromReader(r.Body, &delPinFromBoard) if err != nil { - code, message := getErrCodeMessage(ErrBadBody) + code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadBody) responseError(w, code, message) return } @@ -413,7 +359,7 @@ func (h *HandlerHTTP) DeletePinFromBoard(w http.ResponseWriter, r *http.Request) err = h.boardCase.DeletePinFromBoard(r.Context(), int(boardID), delPinFromBoard.PinID) if err != nil { logger.Info("delete pin from board", log.F{"message", err.Error()}) - code, message := getErrCodeMessage(err) + code, message := errHTTP.GetErrCodeMessage(err) responseError(w, code, message) return } @@ -422,6 +368,6 @@ func (h *HandlerHTTP) DeletePinFromBoard(w http.ResponseWriter, r *http.Request) if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(ErrInternalError.Error())) + w.Write([]byte(errHTTP.ErrInternalError.Error())) } } diff --git a/internal/pkg/delivery/http/v1/board_errors.go b/internal/pkg/delivery/http/v1/errors/board.go similarity index 83% rename from internal/pkg/delivery/http/v1/board_errors.go rename to internal/pkg/delivery/http/v1/errors/board.go index ab31833..0d2106c 100644 --- a/internal/pkg/delivery/http/v1/board_errors.go +++ b/internal/pkg/delivery/http/v1/errors/board.go @@ -1,4 +1,4 @@ -package v1 +package errors import ( "errors" @@ -16,8 +16,8 @@ var ( ) var ( - wrappedErrors = map[error]string{ErrInvalidTagTitles: "bad_Tagtitles"} - errCodeCompability = map[error]string{ + WrappedErrors = map[error]string{ErrInvalidTagTitles: "bad_Tagtitles"} + ErrCodeCompability = map[error]string{ ErrInvalidBoardTitle: "bad_boardTitle", ErrEmptyTitle: "empty_boardTitle", ErrEmptyPubOpt: "bad_pubOpt", @@ -29,7 +29,7 @@ var ( } ) -func getErrCodeMessage(err error) (string, string) { +func GetErrCodeMessage(err error) (string, string) { var ( code string general, specific bool @@ -40,9 +40,9 @@ func getErrCodeMessage(err error) (string, string) { return code, err.Error() } - code, specific = errCodeCompability[err] + code, specific = ErrCodeCompability[err] if !specific { - for wrappedErr, code_ := range wrappedErrors { + for wrappedErr, code_ := range WrappedErrors { if errors.Is(err, wrappedErr) { specific = true code = code_ diff --git a/internal/pkg/delivery/http/v1/errors/general.go b/internal/pkg/delivery/http/v1/errors/general.go new file mode 100644 index 0000000..d60b0c3 --- /dev/null +++ b/internal/pkg/delivery/http/v1/errors/general.go @@ -0,0 +1,111 @@ +package errors + +import ( + "errors" + "fmt" + "net/http" + + errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" +) + +// for backward compatibility - begin +var ( + ErrBadBody = errors.New("can't parse body, JSON with correct data types is expected") + ErrBadUrlParam = errors.New("bad URL param has been provided") + ErrBadQueryParam = errors.New("invalid query parameters have been provided") + ErrInternalError = errors.New("internal server error occured") + ErrBadContentType = errors.New("application/json is expected") +) + +var ( + generalErrCodeCompability = map[error]string{ + ErrBadBody: "bad_body", + ErrBadQueryParam: "bad_queryParams", + ErrInternalError: "internal_error", + ErrBadContentType: "bad_contentType", + ErrBadUrlParam: "bad_urlParam", + } +) + +// for backward compatibility - end + +type ErrInvalidBody struct{} + +func (e *ErrInvalidBody) Error() string { + return "invalid body" +} + +func (e *ErrInvalidBody) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrInvalidQueryParam struct { + Params map[string]string +} + +func (e *ErrInvalidQueryParam) Error() string { + return fmt.Sprintf("invalid query params: %v", e.Params) +} + +func (e *ErrInvalidQueryParam) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrInvalidContentType struct { + PreferredType string +} + +func (e *ErrInvalidContentType) Error() string { + return fmt.Sprintf("invalid content type, should be %s", e.PreferredType) +} + +func (e *ErrInvalidContentType) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrInvalidUrlParams struct { + Params map[string]string +} + +func (e *ErrInvalidUrlParams) Error() string { + return fmt.Sprintf("invalid URL params: %v", e.Params) +} + +func (e *ErrInvalidUrlParams) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +type ErrMissingBodyParams struct { + Params []string +} + +func (e *ErrMissingBodyParams) Error() string { + return fmt.Sprintf("missing body params: %v", e.Params) +} + +func (e *ErrMissingBodyParams) Type() errPkg.Type { + return errPkg.ErrInvalidInput +} + +func GetCodeStatusHttp(err error) (ErrCode string, httpStatus int) { + + var declaredErr errPkg.DeclaredError + if errors.As(err, &declaredErr) { + switch declaredErr.Type() { + case errPkg.ErrInvalidInput: + return "bad_input", http.StatusBadRequest + case errPkg.ErrNotFound: + return "not_found", http.StatusNotFound + case errPkg.ErrAlreadyExists: + return "already_exists", http.StatusConflict + case errPkg.ErrNoAuth: + return "no_auth", http.StatusUnauthorized + case errPkg.ErrNoAccess: + return "no_access", http.StatusForbidden + case errPkg.ErrTimeout: + return "timeout", http.StatusRequestTimeout + } + } + + return "internal_error", http.StatusInternalServerError +} diff --git a/internal/pkg/delivery/http/v1/search_errors.go b/internal/pkg/delivery/http/v1/errors/search.go similarity index 96% rename from internal/pkg/delivery/http/v1/search_errors.go rename to internal/pkg/delivery/http/v1/errors/search.go index 1fb9f00..74abdec 100644 --- a/internal/pkg/delivery/http/v1/search_errors.go +++ b/internal/pkg/delivery/http/v1/errors/search.go @@ -1,4 +1,4 @@ -package v1 +package errors import errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index b03ed6f..66ab2ab 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/go-chi/chi/v5" + errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -57,7 +58,7 @@ func (h *HandlerHTTP) GetUserInfo(w http.ResponseWriter, r *http.Request) { userIdParam := chi.URLParam(r, "userID") userID, err := strconv.ParseInt(userIdParam, 10, 64) if err != nil { - h.responseErr(w, r, &ErrInvalidUrlParams{map[string]string{"userID": userIdParam}}) + h.responseErr(w, r, &errHTTP.ErrInvalidUrlParams{Params: map[string]string{"userID": userIdParam}}) return } diff --git a/internal/pkg/delivery/http/v1/response.go b/internal/pkg/delivery/http/v1/response.go index 98ab544..7b77059 100644 --- a/internal/pkg/delivery/http/v1/response.go +++ b/internal/pkg/delivery/http/v1/response.go @@ -1,140 +1,26 @@ package v1 import ( - "encoding/json" - "errors" "fmt" "net/http" - errPkg "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/errors" + errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/mailru/easyjson" ) -// for backward compatibility - begin -var ( - ErrBadBody = errors.New("can't parse body, JSON with correct data types is expected") - ErrBadUrlParam = errors.New("bad URL param has been provided") - ErrBadQueryParam = errors.New("invalid query parameters have been provided") - ErrInternalError = errors.New("internal server error occured") - ErrBadContentType = errors.New("application/json is expected") -) - -var ( - generalErrCodeCompability = map[error]string{ - ErrBadBody: "bad_body", - ErrBadQueryParam: "bad_queryParams", - ErrInternalError: "internal_error", - ErrBadContentType: "bad_contentType", - ErrBadUrlParam: "bad_urlParam", - } -) - -// for backward compatibility - end - -type ErrInvalidBody struct{} - -func (e *ErrInvalidBody) Error() string { - return "invalid body" -} - -func (e *ErrInvalidBody) Type() errPkg.Type { - return errPkg.ErrInvalidInput -} - -type ErrInvalidQueryParam struct { - params map[string]string -} - -func (e *ErrInvalidQueryParam) Error() string { - return fmt.Sprintf("invalid query params: %v", e.params) -} - -func (e *ErrInvalidQueryParam) Type() errPkg.Type { - return errPkg.ErrInvalidInput -} - -type ErrInvalidContentType struct { - preferredType string -} - -func (e *ErrInvalidContentType) Error() string { - return fmt.Sprintf("invalid content type, should be %s", e.preferredType) -} - -func (e *ErrInvalidContentType) Type() errPkg.Type { - return errPkg.ErrInvalidInput -} - -type ErrInvalidUrlParams struct { - params map[string]string -} - -func (e *ErrInvalidUrlParams) Error() string { - return fmt.Sprintf("invalid URL params: %v", e.params) -} - -func (e *ErrInvalidUrlParams) Type() errPkg.Type { - return errPkg.ErrInvalidInput -} - -type ErrMissingBodyParams struct { - params []string -} - -func (e *ErrMissingBodyParams) Error() string { - return fmt.Sprintf("missing body params: %v", e.params) -} - -func (e *ErrMissingBodyParams) Type() errPkg.Type { - return errPkg.ErrInvalidInput -} - -func getCodeStatusHttp(err error) (ErrCode string, httpStatus int) { - - var declaredErr errPkg.DeclaredError - if errors.As(err, &declaredErr) { - switch declaredErr.Type() { - case errPkg.ErrInvalidInput: - return "bad_input", http.StatusBadRequest - case errPkg.ErrNotFound: - return "not_found", http.StatusNotFound - case errPkg.ErrAlreadyExists: - return "already_exists", http.StatusConflict - case errPkg.ErrNoAuth: - return "no_auth", http.StatusUnauthorized - case errPkg.ErrNoAccess: - return "no_access", http.StatusForbidden - case errPkg.ErrTimeout: - return "timeout", http.StatusRequestTimeout - } - } - - return "internal_error", http.StatusInternalServerError -} - -type JsonResponse struct { - Status string `json:"status" example:"ok"` - Message string `json:"message" example:"Response message"` - Body interface{} `json:"body" extensions:"x-omitempty"` -} // @name JsonResponse - -type JsonErrResponse struct { - Status string `json:"status" example:"error"` - Message string `json:"message" example:"Error description"` - Code string `json:"code"` -} // @name JsonErrResponse - func SetContentTypeJSON(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") } func responseOk(statusCode int, w http.ResponseWriter, message string, body any) error { - res := JsonResponse{ + res := structs.JsonResponse{ Status: "ok", Message: message, Body: body, } - resBytes, err := json.Marshal(res) + resBytes, err := easyjson.Marshal(res) if err != nil { w.WriteHeader(http.StatusInternalServerError) return fmt.Errorf("responseOk: %w", err) @@ -146,12 +32,12 @@ func responseOk(statusCode int, w http.ResponseWriter, message string, body any) } func responseError(w http.ResponseWriter, code, message string) error { - res := JsonErrResponse{ + res := structs.JsonErrResponse{ Status: "error", Message: message, Code: code, } - resBytes, err := json.Marshal(res) + resBytes, err := easyjson.Marshal(res) if err != nil { return fmt.Errorf("responseError: %w", err) } @@ -162,7 +48,7 @@ func responseError(w http.ResponseWriter, code, message string) error { func (h *HandlerHTTP) responseErr(w http.ResponseWriter, r *http.Request, err error) error { log := logger.GetLoggerFromCtx(r.Context()) - code, status := getCodeStatusHttp(err) + code, status := errHTTP.GetCodeStatusHttp(err) var msg string if status == http.StatusInternalServerError { log.Warnf("unexpected application error: %s", err.Error()) @@ -171,12 +57,12 @@ func (h *HandlerHTTP) responseErr(w http.ResponseWriter, r *http.Request, err er msg = err.Error() } - res := JsonErrResponse{ + res := structs.JsonErrResponse{ Status: "error", Message: msg, Code: code, } - resBytes, err := json.Marshal(res) + resBytes, err := easyjson.Marshal(res) if err != nil { return fmt.Errorf("responseError: %w", err) } diff --git a/internal/pkg/delivery/http/v1/search.go b/internal/pkg/delivery/http/v1/search.go index f798d36..4ec1e79 100644 --- a/internal/pkg/delivery/http/v1/search.go +++ b/internal/pkg/delivery/http/v1/search.go @@ -4,6 +4,7 @@ import ( "net/http" "strconv" + errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" ) @@ -89,7 +90,7 @@ func GetSearchOpts(r *http.Request, sortOpts []string, defaultSortOpt string) (* } if len(invalidParams) > 0 { - return nil, &ErrInvalidQueryParam{params: invalidParams} + return nil, &errHTTP.ErrInvalidQueryParam{Params: invalidParams} } return opts, nil @@ -114,7 +115,7 @@ func GetGeneralOpts(r *http.Request, invalidParams map[string]string) (*search.G opts.Template = template } } else { - return nil, &ErrNoData{} + return nil, &errHTTP.ErrNoData{} } if sortOrder := r.URL.Query().Get("order"); sortOrder != "" { diff --git a/internal/pkg/delivery/http/v1/structs/board.go b/internal/pkg/delivery/http/v1/structs/board.go new file mode 100644 index 0000000..e6aa540 --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/board.go @@ -0,0 +1,71 @@ +package structs + +import ( + "fmt" + + errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" +) + +//go:generate easyjson board.go + +// data for board creation/update +// +//easyjson:json +type BoardData struct { + Title *string `json:"title" example:"new board"` + Description *string `json:"description" example:"long desc"` + Public *bool `json:"public" example:"true"` + Tags []string `json:"tags" example:"['blue', 'car']"` +} + +// board view for delivery layer +// +//easyjson:json +type CertainBoard struct { + ID int `json:"board_id" example:"22"` + AuthorID int `json:"author_id" example:"22"` + Title string `json:"title" example:"new board"` + Description string `json:"description" example:"long desc"` + CreatedAt string `json:"created_at" example:"07-11-2023"` + PinsNumber int `json:"pins_number" example:"12"` + Pins []string `json:"pins" example:"['/pic1', '/pic2']"` + Tags []string `json:"tags" example:"['love', 'green']"` +} + +//easyjson:json +type CertainBoardWithUsername struct { + ID int `json:"board_id" example:"22"` + AuthorID int `json:"author_id" example:"22"` + AuthorUsername string `json:"author_username" example:"Bob"` + Title string `json:"title" example:"new board"` + Description string `json:"description" example:"long desc"` + CreatedAt string `json:"created_at" example:"07-11-2023"` + PinsNumber int `json:"pins_number" example:"12"` + Pins []string `json:"pins" example:"['/pic1', '/pic2']"` + Tags []string `json:"tags" example:"['love', 'green']"` +} + +//easyjson:json +type DeletePinFromBoard struct { + PinID int `json:"pin_id" example:"22"` +} + +func (data *BoardData) Validate() error { + if data.Title == nil || *data.Title == "" { + return errHTTP.ErrInvalidBoardTitle + } + if data.Description == nil { + data.Description = new(string) + *data.Description = "" + } + if data.Public == nil { + return errHTTP.ErrEmptyPubOpt + } + if !isValidBoardTitle(*data.Title) { + return errHTTP.ErrInvalidBoardTitle + } + if err := checkIsValidTagTitles(data.Tags); err != nil { + return fmt.Errorf("%s: %w", err.Error(), errHTTP.ErrInvalidTagTitles) + } + return nil +} diff --git a/internal/pkg/delivery/http/v1/structs/board_easyjson.go b/internal/pkg/delivery/http/v1/structs/board_easyjson.go new file mode 100644 index 0000000..b847d66 --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/board_easyjson.go @@ -0,0 +1,605 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package structs + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(in *jlexer.Lexer, out *DeletePinFromBoard) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "pin_id": + out.PinID = int(in.Int()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(out *jwriter.Writer, in DeletePinFromBoard) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"pin_id\":" + out.RawString(prefix[1:]) + out.Int(int(in.PinID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v DeletePinFromBoard) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v DeletePinFromBoard) MarshalEasyJSON(w *jwriter.Writer) { + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *DeletePinFromBoard) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *DeletePinFromBoard) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(l, v) +} +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(in *jlexer.Lexer, out *CertainBoardWithUsername) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "board_id": + out.ID = int(in.Int()) + case "author_id": + out.AuthorID = int(in.Int()) + case "author_username": + out.AuthorUsername = string(in.String()) + case "title": + out.Title = string(in.String()) + case "description": + out.Description = string(in.String()) + case "created_at": + out.CreatedAt = string(in.String()) + case "pins_number": + out.PinsNumber = int(in.Int()) + case "pins": + if in.IsNull() { + in.Skip() + out.Pins = nil + } else { + in.Delim('[') + if out.Pins == nil { + if !in.IsDelim(']') { + out.Pins = make([]string, 0, 4) + } else { + out.Pins = []string{} + } + } else { + out.Pins = (out.Pins)[:0] + } + for !in.IsDelim(']') { + var v1 string + v1 = string(in.String()) + out.Pins = append(out.Pins, v1) + in.WantComma() + } + in.Delim(']') + } + case "tags": + if in.IsNull() { + in.Skip() + out.Tags = nil + } else { + in.Delim('[') + if out.Tags == nil { + if !in.IsDelim(']') { + out.Tags = make([]string, 0, 4) + } else { + out.Tags = []string{} + } + } else { + out.Tags = (out.Tags)[:0] + } + for !in.IsDelim(']') { + var v2 string + v2 = string(in.String()) + out.Tags = append(out.Tags, v2) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(out *jwriter.Writer, in CertainBoardWithUsername) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"board_id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"author_id\":" + out.RawString(prefix) + out.Int(int(in.AuthorID)) + } + { + const prefix string = ",\"author_username\":" + out.RawString(prefix) + out.String(string(in.AuthorUsername)) + } + { + const prefix string = ",\"title\":" + out.RawString(prefix) + out.String(string(in.Title)) + } + { + const prefix string = ",\"description\":" + out.RawString(prefix) + out.String(string(in.Description)) + } + { + const prefix string = ",\"created_at\":" + out.RawString(prefix) + out.String(string(in.CreatedAt)) + } + { + const prefix string = ",\"pins_number\":" + out.RawString(prefix) + out.Int(int(in.PinsNumber)) + } + { + const prefix string = ",\"pins\":" + out.RawString(prefix) + if in.Pins == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v3, v4 := range in.Pins { + if v3 > 0 { + out.RawByte(',') + } + out.String(string(v4)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"tags\":" + out.RawString(prefix) + if in.Tags == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v5, v6 := range in.Tags { + if v5 > 0 { + out.RawByte(',') + } + out.String(string(v6)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v CertainBoardWithUsername) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v CertainBoardWithUsername) MarshalEasyJSON(w *jwriter.Writer) { + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *CertainBoardWithUsername) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *CertainBoardWithUsername) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(l, v) +} +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs2(in *jlexer.Lexer, out *CertainBoard) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "board_id": + out.ID = int(in.Int()) + case "author_id": + out.AuthorID = int(in.Int()) + case "title": + out.Title = string(in.String()) + case "description": + out.Description = string(in.String()) + case "created_at": + out.CreatedAt = string(in.String()) + case "pins_number": + out.PinsNumber = int(in.Int()) + case "pins": + if in.IsNull() { + in.Skip() + out.Pins = nil + } else { + in.Delim('[') + if out.Pins == nil { + if !in.IsDelim(']') { + out.Pins = make([]string, 0, 4) + } else { + out.Pins = []string{} + } + } else { + out.Pins = (out.Pins)[:0] + } + for !in.IsDelim(']') { + var v7 string + v7 = string(in.String()) + out.Pins = append(out.Pins, v7) + in.WantComma() + } + in.Delim(']') + } + case "tags": + if in.IsNull() { + in.Skip() + out.Tags = nil + } else { + in.Delim('[') + if out.Tags == nil { + if !in.IsDelim(']') { + out.Tags = make([]string, 0, 4) + } else { + out.Tags = []string{} + } + } else { + out.Tags = (out.Tags)[:0] + } + for !in.IsDelim(']') { + var v8 string + v8 = string(in.String()) + out.Tags = append(out.Tags, v8) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs2(out *jwriter.Writer, in CertainBoard) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"board_id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"author_id\":" + out.RawString(prefix) + out.Int(int(in.AuthorID)) + } + { + const prefix string = ",\"title\":" + out.RawString(prefix) + out.String(string(in.Title)) + } + { + const prefix string = ",\"description\":" + out.RawString(prefix) + out.String(string(in.Description)) + } + { + const prefix string = ",\"created_at\":" + out.RawString(prefix) + out.String(string(in.CreatedAt)) + } + { + const prefix string = ",\"pins_number\":" + out.RawString(prefix) + out.Int(int(in.PinsNumber)) + } + { + const prefix string = ",\"pins\":" + out.RawString(prefix) + if in.Pins == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v9, v10 := range in.Pins { + if v9 > 0 { + out.RawByte(',') + } + out.String(string(v10)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"tags\":" + out.RawString(prefix) + if in.Tags == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v11, v12 := range in.Tags { + if v11 > 0 { + out.RawByte(',') + } + out.String(string(v12)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v CertainBoard) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v CertainBoard) MarshalEasyJSON(w *jwriter.Writer) { + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *CertainBoard) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *CertainBoard) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs2(l, v) +} +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs3(in *jlexer.Lexer, out *BoardData) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "title": + if in.IsNull() { + in.Skip() + out.Title = nil + } else { + if out.Title == nil { + out.Title = new(string) + } + *out.Title = string(in.String()) + } + case "description": + if in.IsNull() { + in.Skip() + out.Description = nil + } else { + if out.Description == nil { + out.Description = new(string) + } + *out.Description = string(in.String()) + } + case "public": + if in.IsNull() { + in.Skip() + out.Public = nil + } else { + if out.Public == nil { + out.Public = new(bool) + } + *out.Public = bool(in.Bool()) + } + case "tags": + if in.IsNull() { + in.Skip() + out.Tags = nil + } else { + in.Delim('[') + if out.Tags == nil { + if !in.IsDelim(']') { + out.Tags = make([]string, 0, 4) + } else { + out.Tags = []string{} + } + } else { + out.Tags = (out.Tags)[:0] + } + for !in.IsDelim(']') { + var v13 string + v13 = string(in.String()) + out.Tags = append(out.Tags, v13) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs3(out *jwriter.Writer, in BoardData) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"title\":" + out.RawString(prefix[1:]) + if in.Title == nil { + out.RawString("null") + } else { + out.String(string(*in.Title)) + } + } + { + const prefix string = ",\"description\":" + out.RawString(prefix) + if in.Description == nil { + out.RawString("null") + } else { + out.String(string(*in.Description)) + } + } + { + const prefix string = ",\"public\":" + out.RawString(prefix) + if in.Public == nil { + out.RawString("null") + } else { + out.Bool(bool(*in.Public)) + } + } + { + const prefix string = ",\"tags\":" + out.RawString(prefix) + if in.Tags == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v14, v15 := range in.Tags { + if v14 > 0 { + out.RawByte(',') + } + out.String(string(v15)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BoardData) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BoardData) MarshalEasyJSON(w *jwriter.Writer) { + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BoardData) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BoardData) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs3(l, v) +} diff --git a/internal/pkg/delivery/http/v1/board_validation.go b/internal/pkg/delivery/http/v1/structs/board_validation.go similarity index 98% rename from internal/pkg/delivery/http/v1/board_validation.go rename to internal/pkg/delivery/http/v1/structs/board_validation.go index 0cabf18..6181434 100644 --- a/internal/pkg/delivery/http/v1/board_validation.go +++ b/internal/pkg/delivery/http/v1/structs/board_validation.go @@ -1,4 +1,4 @@ -package v1 +package structs import ( "fmt" diff --git a/internal/pkg/delivery/http/v1/structs/response.go b/internal/pkg/delivery/http/v1/structs/response.go new file mode 100644 index 0000000..e922b82 --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/response.go @@ -0,0 +1,17 @@ +package structs + +//go:generate easyjson subscription.go + +//easyjson:json +type JsonResponse struct { + Status string `json:"status" example:"ok"` + Message string `json:"message" example:"Response message"` + Body interface{} `json:"body" extensions:"x-omitempty"` +} // @name JsonResponse + +//easyjson:json +type JsonErrResponse struct { + Status string `json:"status" example:"error"` + Message string `json:"message" example:"Error description"` + Code string `json:"code"` +} // @name JsonErrResponse diff --git a/internal/pkg/delivery/http/v1/structs/response_easyjson.go b/internal/pkg/delivery/http/v1/structs/response_easyjson.go new file mode 100644 index 0000000..91dfdd8 --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/response_easyjson.go @@ -0,0 +1,191 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package structs + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson6ff3ac1dDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(in *jlexer.Lexer, out *JsonResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "status": + out.Status = string(in.String()) + case "message": + out.Message = string(in.String()) + case "body": + if m, ok := out.Body.(easyjson.Unmarshaler); ok { + m.UnmarshalEasyJSON(in) + } else if m, ok := out.Body.(json.Unmarshaler); ok { + _ = m.UnmarshalJSON(in.Raw()) + } else { + out.Body = in.Interface() + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6ff3ac1dEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(out *jwriter.Writer, in JsonResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + out.String(string(in.Message)) + } + { + const prefix string = ",\"body\":" + out.RawString(prefix) + if m, ok := in.Body.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := in.Body.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(in.Body)) + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v JsonResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6ff3ac1dEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v JsonResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6ff3ac1dEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *JsonResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6ff3ac1dDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *JsonResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6ff3ac1dDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(l, v) +} +func easyjson6ff3ac1dDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(in *jlexer.Lexer, out *JsonErrResponse) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "status": + out.Status = string(in.String()) + case "message": + out.Message = string(in.String()) + case "code": + out.Code = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6ff3ac1dEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(out *jwriter.Writer, in JsonErrResponse) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + out.String(string(in.Message)) + } + { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.String(string(in.Code)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v JsonErrResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6ff3ac1dEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v JsonErrResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6ff3ac1dEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *JsonErrResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6ff3ac1dDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *JsonErrResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6ff3ac1dDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(l, v) +} diff --git a/internal/pkg/delivery/http/v1/structs/subscription.go b/internal/pkg/delivery/http/v1/structs/subscription.go new file mode 100644 index 0000000..73bee4d --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/subscription.go @@ -0,0 +1,17 @@ +package structs + +import errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" + +//go:generate easyjson subscription.go + +//easyjson:json +type SubscriptionAction struct { + To *int `json:"to" example:"2"` +} + +func (s *SubscriptionAction) Validate() error { + if s.To == nil { + return &errHTTP.ErrMissingBodyParams{Params: []string{"to"}} + } + return nil +} diff --git a/internal/pkg/delivery/http/v1/structs/subscription_easyjson.go b/internal/pkg/delivery/http/v1/structs/subscription_easyjson.go new file mode 100644 index 0000000..b1e0fd9 --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/subscription_easyjson.go @@ -0,0 +1,97 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package structs + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonFfbd3743DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(in *jlexer.Lexer, out *SubscriptionAction) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "to": + if in.IsNull() { + in.Skip() + out.To = nil + } else { + if out.To == nil { + out.To = new(int) + } + *out.To = int(in.Int()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonFfbd3743EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(out *jwriter.Writer, in SubscriptionAction) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"to\":" + out.RawString(prefix[1:]) + if in.To == nil { + out.RawString("null") + } else { + out.Int(int(*in.To)) + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v SubscriptionAction) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonFfbd3743EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v SubscriptionAction) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonFfbd3743EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *SubscriptionAction) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonFfbd3743DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *SubscriptionAction) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonFfbd3743DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(l, v) +} diff --git a/internal/pkg/delivery/http/v1/subscription.go b/internal/pkg/delivery/http/v1/subscription.go index 4614476..7444bdf 100644 --- a/internal/pkg/delivery/http/v1/subscription.go +++ b/internal/pkg/delivery/http/v1/subscription.go @@ -1,12 +1,15 @@ package v1 import ( - "encoding/json" "net/http" "strconv" + errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/mailru/easyjson" ) var ( @@ -17,26 +20,16 @@ var ( maxCount = 50 ) -type SubscriptionAction struct { - To *int `json:"to" example:"2"` -} - -func (s *SubscriptionAction) Validate() error { - if s.To == nil { - return &ErrMissingBodyParams{[]string{"to"}} - } - return nil -} - func (h *HandlerHTTP) Subscribe(w http.ResponseWriter, r *http.Request) { if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - h.responseErr(w, r, &ErrInvalidContentType{preferredType: ApplicationJson}) + h.responseErr(w, r, &errHTTP.ErrInvalidContentType{PreferredType: ApplicationJson}) return } - sub := SubscriptionAction{} - if err := json.NewDecoder(r.Body).Decode(&sub); err != nil { - h.responseErr(w, r, &ErrInvalidBody{}) + sub := structs.SubscriptionAction{} + + if err := easyjson.UnmarshalFromReader(r.Body, &sub); err != nil { + h.responseErr(w, r, &errHTTP.ErrInvalidBody{}) return } defer r.Body.Close() @@ -56,13 +49,13 @@ func (h *HandlerHTTP) Subscribe(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) Unsubscribe(w http.ResponseWriter, r *http.Request) { if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { - h.responseErr(w, r, &ErrInvalidContentType{preferredType: ApplicationJson}) + h.responseErr(w, r, &errHTTP.ErrInvalidContentType{PreferredType: ApplicationJson}) return } - sub := SubscriptionAction{} - if err := json.NewDecoder(r.Body).Decode(&sub); err != nil { - h.responseErr(w, r, &ErrInvalidBody{}) + sub := structs.SubscriptionAction{} + if err := easyjson.UnmarshalFromReader(r.Body, &sub); err != nil { + h.responseErr(w, r, &errHTTP.ErrInvalidBody{}) return } defer r.Body.Close() @@ -146,7 +139,7 @@ func GetOpts(r *http.Request) (*userEntity.SubscriptionOpts, error) { opts.Count = maxCount } if len(invalidParams) > 0 { - return nil, &ErrInvalidQueryParam{invalidParams} + return nil, &errHTTP.ErrInvalidQueryParam{invalidParams} } return opts, nil } diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go index 3ce9b66..d695468 100644 --- a/internal/pkg/entity/board/board.go +++ b/internal/pkg/entity/board/board.go @@ -7,6 +7,7 @@ import ( ) //go:generate easyjson board.go + //easyjson:json type Board struct { ID int `json:"id,omitempty" example:"15"` @@ -19,6 +20,7 @@ type Board struct { DeletedAt *time.Time `json:"-"` } +//easyjson:json type BoardWithContent struct { BoardInfo Board PinsNumber int diff --git a/internal/pkg/entity/board/board_easyjson.go b/internal/pkg/entity/board/board_easyjson.go index 7e6adbe..12d08ef 100644 --- a/internal/pkg/entity/board/board_easyjson.go +++ b/internal/pkg/entity/board/board_easyjson.go @@ -18,7 +18,158 @@ var ( _ easyjson.Marshaler ) -func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(in *jlexer.Lexer, out *Board) { +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(in *jlexer.Lexer, out *BoardWithContent) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "BoardInfo": + (out.BoardInfo).UnmarshalEasyJSON(in) + case "PinsNumber": + out.PinsNumber = int(in.Int()) + case "Pins": + if in.IsNull() { + in.Skip() + out.Pins = nil + } else { + in.Delim('[') + if out.Pins == nil { + if !in.IsDelim(']') { + out.Pins = make([]string, 0, 4) + } else { + out.Pins = []string{} + } + } else { + out.Pins = (out.Pins)[:0] + } + for !in.IsDelim(']') { + var v1 string + v1 = string(in.String()) + out.Pins = append(out.Pins, v1) + in.WantComma() + } + in.Delim(']') + } + case "TagTitles": + if in.IsNull() { + in.Skip() + out.TagTitles = nil + } else { + in.Delim('[') + if out.TagTitles == nil { + if !in.IsDelim(']') { + out.TagTitles = make([]string, 0, 4) + } else { + out.TagTitles = []string{} + } + } else { + out.TagTitles = (out.TagTitles)[:0] + } + for !in.IsDelim(']') { + var v2 string + v2 = string(in.String()) + out.TagTitles = append(out.TagTitles, v2) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(out *jwriter.Writer, in BoardWithContent) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"BoardInfo\":" + out.RawString(prefix[1:]) + (in.BoardInfo).MarshalEasyJSON(out) + } + { + const prefix string = ",\"PinsNumber\":" + out.RawString(prefix) + out.Int(int(in.PinsNumber)) + } + { + const prefix string = ",\"Pins\":" + out.RawString(prefix) + if in.Pins == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v3, v4 := range in.Pins { + if v3 > 0 { + out.RawByte(',') + } + out.String(string(v4)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"TagTitles\":" + out.RawString(prefix) + if in.TagTitles == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v5, v6 := range in.TagTitles { + if v5 > 0 { + out.RawByte(',') + } + out.String(string(v6)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BoardWithContent) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BoardWithContent) MarshalEasyJSON(w *jwriter.Writer) { + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BoardWithContent) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BoardWithContent) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(l, v) +} +func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard1(in *jlexer.Lexer, out *Board) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -69,7 +220,7 @@ func easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoa in.Consumed() } } -func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(out *jwriter.Writer, in Board) { +func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard1(out *jwriter.Writer, in Board) { out.RawByte('{') first := true _ = first @@ -120,23 +271,23 @@ func easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoa // MarshalJSON supports json.Marshaler interface func (v Board) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(&w, v) + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v Board) MarshalEasyJSON(w *jwriter.Writer) { - easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(w, v) + easyjson202377feEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *Board) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(&r, v) + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *Board) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard(l, v) + easyjson202377feDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityBoard1(l, v) } diff --git a/internal/pkg/entity/comment/comment_easyjson.go b/internal/pkg/entity/comment/comment_easyjson.go index c3bb7ab..78723f0 100644 --- a/internal/pkg/entity/comment/comment_easyjson.go +++ b/internal/pkg/entity/comment/comment_easyjson.go @@ -47,7 +47,7 @@ func easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityCom if out.Author == nil { out.Author = new(user.User) } - easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(in, out.Author) + (*out.Author).UnmarshalEasyJSON(in) } case "pinID": out.PinID = int(in.Int()) @@ -80,7 +80,7 @@ func easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityCom if in.Author == nil { out.RawString("null") } else { - easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(out, *in.Author) + (*in.Author).MarshalEasyJSON(out) } } { @@ -119,106 +119,3 @@ func (v *Comment) UnmarshalJSON(data []byte) error { func (v *Comment) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityComment(l, v) } -func easyjsonE9abebc9DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(in *jlexer.Lexer, out *user.User) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "id": - out.ID = int(in.Int()) - case "username": - out.Username = string(in.String()) - case "name": - if data := in.Raw(); in.Ok() { - in.AddError((out.Name).UnmarshalJSON(data)) - } - case "surname": - if data := in.Raw(); in.Ok() { - in.AddError((out.Surname).UnmarshalJSON(data)) - } - case "email": - out.Email = string(in.String()) - case "avatar": - out.Avatar = string(in.String()) - case "about_me": - if data := in.Raw(); in.Ok() { - in.AddError((out.AboutMe).UnmarshalJSON(data)) - } - case "password": - out.Password = string(in.String()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjsonE9abebc9EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(out *jwriter.Writer, in user.User) { - out.RawByte('{') - first := true - _ = first - if in.ID != 0 { - const prefix string = ",\"id\":" - first = false - out.RawString(prefix[1:]) - out.Int(int(in.ID)) - } - { - const prefix string = ",\"username\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Username)) - } - if true { - const prefix string = ",\"name\":" - out.RawString(prefix) - out.Raw((in.Name).MarshalJSON()) - } - if true { - const prefix string = ",\"surname\":" - out.RawString(prefix) - out.Raw((in.Surname).MarshalJSON()) - } - if in.Email != "" { - const prefix string = ",\"email\":" - out.RawString(prefix) - out.String(string(in.Email)) - } - { - const prefix string = ",\"avatar\":" - out.RawString(prefix) - out.String(string(in.Avatar)) - } - if true { - const prefix string = ",\"about_me\":" - out.RawString(prefix) - out.Raw((in.AboutMe).MarshalJSON()) - } - if in.Password != "" { - const prefix string = ",\"password\":" - out.RawString(prefix) - out.String(string(in.Password)) - } - out.RawByte('}') -} diff --git a/internal/pkg/entity/search/search.go b/internal/pkg/entity/search/search.go index 76b0c6a..478aabe 100644 --- a/internal/pkg/entity/search/search.go +++ b/internal/pkg/entity/search/search.go @@ -8,6 +8,8 @@ import ( "github.com/microcosm-cc/bluemonday" ) +//go:generate easyjson search.go + type Template string func (t *Template) Validate() bool { @@ -26,12 +28,14 @@ func (t *Template) GetSubStrings(sep string) []string { return strings.Split(string(*t), sep) } +//easyjson:json type BoardForSearch struct { BoardHeader board.Board PinsNumber int `json:"pins_number"` PreviewPins []string `json:"pins"` } +//easyjson:json type PinForSearch struct { ID int `json:"id"` Title string `json:"title"` @@ -39,6 +43,7 @@ type PinForSearch struct { Likes int `json:"likes"` } +//easyjson:json type UserForSearch struct { ID int `json:"id"` Username string `json:"username"` diff --git a/internal/pkg/entity/search/search_easyjson.go b/internal/pkg/entity/search/search_easyjson.go new file mode 100644 index 0000000..497b75f --- /dev/null +++ b/internal/pkg/entity/search/search_easyjson.go @@ -0,0 +1,312 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package search + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch(in *jlexer.Lexer, out *UserForSearch) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "username": + out.Username = string(in.String()) + case "avatar": + out.Avatar = string(in.String()) + case "subscribers": + out.SubsCount = int(in.Int()) + case "is_subscribed": + out.HasSubscribeFromCurUser = bool(in.Bool()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch(out *jwriter.Writer, in UserForSearch) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"username\":" + out.RawString(prefix) + out.String(string(in.Username)) + } + { + const prefix string = ",\"avatar\":" + out.RawString(prefix) + out.String(string(in.Avatar)) + } + { + const prefix string = ",\"subscribers\":" + out.RawString(prefix) + out.Int(int(in.SubsCount)) + } + { + const prefix string = ",\"is_subscribed\":" + out.RawString(prefix) + out.Bool(bool(in.HasSubscribeFromCurUser)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v UserForSearch) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v UserForSearch) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *UserForSearch) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *UserForSearch) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch(l, v) +} +func easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch1(in *jlexer.Lexer, out *PinForSearch) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "title": + out.Title = string(in.String()) + case "picture": + out.Picture = string(in.String()) + case "likes": + out.Likes = int(in.Int()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch1(out *jwriter.Writer, in PinForSearch) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"title\":" + out.RawString(prefix) + out.String(string(in.Title)) + } + { + const prefix string = ",\"picture\":" + out.RawString(prefix) + out.String(string(in.Picture)) + } + { + const prefix string = ",\"likes\":" + out.RawString(prefix) + out.Int(int(in.Likes)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v PinForSearch) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v PinForSearch) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *PinForSearch) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *PinForSearch) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch1(l, v) +} +func easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch2(in *jlexer.Lexer, out *BoardForSearch) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "BoardHeader": + (out.BoardHeader).UnmarshalEasyJSON(in) + case "pins_number": + out.PinsNumber = int(in.Int()) + case "pins": + if in.IsNull() { + in.Skip() + out.PreviewPins = nil + } else { + in.Delim('[') + if out.PreviewPins == nil { + if !in.IsDelim(']') { + out.PreviewPins = make([]string, 0, 4) + } else { + out.PreviewPins = []string{} + } + } else { + out.PreviewPins = (out.PreviewPins)[:0] + } + for !in.IsDelim(']') { + var v1 string + v1 = string(in.String()) + out.PreviewPins = append(out.PreviewPins, v1) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch2(out *jwriter.Writer, in BoardForSearch) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"BoardHeader\":" + out.RawString(prefix[1:]) + (in.BoardHeader).MarshalEasyJSON(out) + } + { + const prefix string = ",\"pins_number\":" + out.RawString(prefix) + out.Int(int(in.PinsNumber)) + } + { + const prefix string = ",\"pins\":" + out.RawString(prefix) + if in.PreviewPins == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v2, v3 := range in.PreviewPins { + if v2 > 0 { + out.RawByte(',') + } + out.String(string(v3)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v BoardForSearch) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v BoardForSearch) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonD4176298EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *BoardForSearch) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *BoardForSearch) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonD4176298DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntitySearch2(l, v) +} diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 70f2e14..5a636fc 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -5,8 +5,11 @@ import ( "github.com/microcosm-cc/bluemonday" ) +//go:generate easyjson user.go + const UserUnknown = -1 +//easyjson:json type User struct { ID int `json:"id,omitempty" example:"123"` Username string `json:"username" example:"Green"` @@ -18,6 +21,7 @@ type User struct { Password string `json:"password,omitempty" example:"pass123"` } // @name User +//easyjson:json type SubscriptionUser struct { ID int `json:"id"` Username string `json:"username"` diff --git a/internal/pkg/entity/user/user_easyjson.go b/internal/pkg/entity/user/user_easyjson.go new file mode 100644 index 0000000..e8b0dae --- /dev/null +++ b/internal/pkg/entity/user/user_easyjson.go @@ -0,0 +1,233 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package user + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(in *jlexer.Lexer, out *User) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "username": + out.Username = string(in.String()) + case "name": + if data := in.Raw(); in.Ok() { + in.AddError((out.Name).UnmarshalJSON(data)) + } + case "surname": + if data := in.Raw(); in.Ok() { + in.AddError((out.Surname).UnmarshalJSON(data)) + } + case "email": + out.Email = string(in.String()) + case "avatar": + out.Avatar = string(in.String()) + case "about_me": + if data := in.Raw(); in.Ok() { + in.AddError((out.AboutMe).UnmarshalJSON(data)) + } + case "password": + out.Password = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(out *jwriter.Writer, in User) { + out.RawByte('{') + first := true + _ = first + if in.ID != 0 { + const prefix string = ",\"id\":" + first = false + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"username\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Username)) + } + if true { + const prefix string = ",\"name\":" + out.RawString(prefix) + out.Raw((in.Name).MarshalJSON()) + } + if true { + const prefix string = ",\"surname\":" + out.RawString(prefix) + out.Raw((in.Surname).MarshalJSON()) + } + if in.Email != "" { + const prefix string = ",\"email\":" + out.RawString(prefix) + out.String(string(in.Email)) + } + { + const prefix string = ",\"avatar\":" + out.RawString(prefix) + out.String(string(in.Avatar)) + } + if true { + const prefix string = ",\"about_me\":" + out.RawString(prefix) + out.Raw((in.AboutMe).MarshalJSON()) + } + if in.Password != "" { + const prefix string = ",\"password\":" + out.RawString(prefix) + out.String(string(in.Password)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v User) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v User) MarshalEasyJSON(w *jwriter.Writer) { + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *User) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *User) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser(l, v) +} +func easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser1(in *jlexer.Lexer, out *SubscriptionUser) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "username": + out.Username = string(in.String()) + case "avatar": + out.Avatar = string(in.String()) + case "is_subscribed": + out.HasSubscribeFromCurUser = bool(in.Bool()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser1(out *jwriter.Writer, in SubscriptionUser) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"username\":" + out.RawString(prefix) + out.String(string(in.Username)) + } + { + const prefix string = ",\"avatar\":" + out.RawString(prefix) + out.String(string(in.Avatar)) + } + { + const prefix string = ",\"is_subscribed\":" + out.RawString(prefix) + out.Bool(bool(in.HasSubscribeFromCurUser)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v SubscriptionUser) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v SubscriptionUser) MarshalEasyJSON(w *jwriter.Writer) { + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *SubscriptionUser) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *SubscriptionUser) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityUser1(l, v) +} From 39924e730d0a5c8c6a8057a5194203439b8ab372 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 13 Dec 2023 14:09:19 +0300 Subject: [PATCH 235/266] TP-1c3 add: notification ws handler --- internal/api/server/router/router.go | 3 +- internal/app/app.go | 10 +- internal/pkg/delivery/websocket/chat.go | 190 ++++++++++++++++++ .../pkg/delivery/websocket/notification.go | 26 +++ internal/pkg/delivery/websocket/websocket.go | 188 +---------------- 5 files changed, 231 insertions(+), 186 deletions(-) create mode 100644 internal/pkg/delivery/websocket/chat.go create mode 100644 internal/pkg/delivery/websocket/notification.go diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index d9f7608..434ed62 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -141,6 +141,7 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli }) r.Mux.With(auth.RequireAuth).Route("/websocket/connect", func(r chi.Router) { - r.Get("/chat", wsHandler.WebSocketConnect) + r.Get("/chat", wsHandler.Chat) + r.Get("/notification", wsHandler.Notification) }) } diff --git a/internal/app/app.go b/internal/app/app.go index 4813c36..549e753 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,6 +11,7 @@ import ( authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" @@ -89,7 +90,14 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { CommentCase: comment.New(commentRepo.NewCommentRepoPG(pool), pinCase), }) - wsHandler := deliveryWS.New(log, messageCase, + connRealtime, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Error(err.Error()) + return + } + defer connRealtime.Close() + + wsHandler := deliveryWS.New(log, messageCase, rt.NewRealTimeClient(connRealtime), deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) diff --git a/internal/pkg/delivery/websocket/chat.go b/internal/pkg/delivery/websocket/chat.go new file mode 100644 index 0000000..1c4bca4 --- /dev/null +++ b/internal/pkg/delivery/websocket/chat.go @@ -0,0 +1,190 @@ +package websocket + +import ( + "context" + "fmt" + "net/http" + + ws "nhooyr.io/websocket" + "nhooyr.io/websocket/wsjson" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" +) + +func (h *HandlerWebSocket) Chat(w http.ResponseWriter, r *http.Request) { + conn, err := h.upgradeWSConnect(w, r) + if err != nil { + h.log.Error(err.Error()) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"status":"error","code":"websocket_connect","message":"fail connect"}`)) + return + } + defer conn.CloseNow() + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) + defer cancel() + + err = h.serveWebSocketConn(ctx, conn, userID) + if err != nil { + h.log.Error(err.Error()) + } +} + +func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn, userID int) error { + request := &Request{} + var err error + for { + err = wsjson.Read(ctx, conn, request) + if err != nil { + h.log.Error(err.Error()) + return fmt.Errorf("read message: %w", err) + } + switch request.Action { + case "Publish": + switch request.Message.Type { + case "create": + mesCopy := &message.Message{} + *mesCopy = request.Message.Message + mesCopy.From = userID + id, err := h.messageCase.SendMessage(ctx, userID, mesCopy) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) + _, err = h.client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: request.Channel.Topic, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_CREATE, + Id: int64(id), + }, + }, + }, + }) + if err != nil { + h.log.Error(err.Error()) + } + case "update": + mesCopy := &message.Message{} + *mesCopy = request.Message.Message + err = h.messageCase.UpdateContentMessage(ctx, userID, mesCopy) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) + _, err = h.client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: request.Channel.Topic, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_UPDATE, + Id: int64(request.Message.Message.ID), + }, + }, + }, + }) + if err != nil { + h.log.Error(err.Error()) + } + + case "delete": + err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) + _, err = h.client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: request.Channel.Topic, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_DELETE, + Id: int64(request.Message.Message.ID), + }, + }, + }, + }) + if err != nil { + h.log.Error(err.Error()) + } + default: + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) + } + case "Subscribe": + err = h.subscribe(ctx, h.client, request, conn, userID) + if err != nil { + h.log.Warn(err.Error()) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "subscribe_fail", "failed to subscribe to the channel", nil)) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "you have successfully subscribed to the channel", nil)) + default: + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported action", nil)) + } + } +} + +func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClient, req *Request, conn *ws.Conn, userID int) error { + sc, err := client.Subscribe(ctx, &rt.Channel{ + Name: req.Channel.Name, + Topic: req.Channel.Topic, + }) + if err != nil { + return fmt.Errorf("subscribe: %w", err) + } + go func() { + for { + obj, err := sc.Recv() + if err != nil { + return + } + mes, ok := obj.Body.(*rt.Message_Object) + if ok { + var msg *message.Message + if mes.Object.Type == rt.EventType_EV_DELETE { + msg = &message.Message{ID: int(mes.Object.Id)} + } else { + msg, err = h.messageCase.GetMessage(ctx, userID, int(mes.Object.Id)) + if err != nil { + h.log.Error(err.Error()) + return + } + } + objType := "" + switch mes.Object.Type { + case rt.EventType_EV_CREATE: + objType = "create" + case rt.EventType_EV_UPDATE: + objType = "update" + case rt.EventType_EV_DELETE: + objType = "delete" + } + err = wsjson.Write(ctx, conn, newMessageFromChannel(req.Channel, "ok", "", Object{ + Type: objType, + Message: *msg, + })) + if err != nil { + h.log.Error(err.Error()) + return + } + } + } + }() + return nil +} diff --git a/internal/pkg/delivery/websocket/notification.go b/internal/pkg/delivery/websocket/notification.go new file mode 100644 index 0000000..03e8981 --- /dev/null +++ b/internal/pkg/delivery/websocket/notification.go @@ -0,0 +1,26 @@ +package websocket + +import ( + "context" + "net/http" + + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +func (h *HandlerWebSocket) Notification(w http.ResponseWriter, r *http.Request) { + conn, err := h.upgradeWSConnect(w, r) + if err != nil { + h.log.Error(err.Error()) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"status":"error","code":"websocket_connect","message":"fail connect"}`)) + return + } + defer conn.CloseNow() + + userID := r.Context().Value(auth.KeyCurrentUserID).(int) + ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) + defer cancel() + + h.log.Info("", logger.F{"user", userID}, logger.F{"context", ctx}) +} diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index d6d9753..18df717 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -1,19 +1,13 @@ package websocket import ( - "context" "fmt" "net/http" "time" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ws "nhooyr.io/websocket" - "nhooyr.io/websocket/wsjson" rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -35,13 +29,7 @@ func SetOriginPatterns(patterns []string) Option { } } -func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { - gRPCConn, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - log.Error(fmt.Errorf("grpc dial: %w", err).Error()) - } - - client := rt.NewRealTimeClient(gRPCConn) +func New(log *log.Logger, mesCase usecase.Usecase, client rt.RealTimeClient, opts ...Option) *HandlerWebSocket { handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase, client: client} for _, opt := range opts { opt(handlerWS) @@ -50,178 +38,10 @@ func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSo return handlerWS } -func (h *HandlerWebSocket) WebSocketConnect(w http.ResponseWriter, r *http.Request) { +func (h *HandlerWebSocket) upgradeWSConnect(w http.ResponseWriter, r *http.Request) (*ws.Conn, error) { conn, err := ws.Accept(w, r, &ws.AcceptOptions{OriginPatterns: h.originPatterns}) if err != nil { - h.log.Error(err.Error()) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"status":"error","code":"websocket_connect","message":"fail connect"}`)) - return - } - defer conn.CloseNow() - - userID := r.Context().Value(auth.KeyCurrentUserID).(int) - ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) - defer cancel() - - err = h.serveWebSocketConn(ctx, conn, userID) - if err != nil { - h.log.Error(err.Error()) - } -} - -func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn, userID int) error { - request := &Request{} - var err error - for { - err = wsjson.Read(ctx, conn, request) - if err != nil { - h.log.Error(err.Error()) - return fmt.Errorf("read message: %w", err) - } - switch request.Action { - case "Publish": - switch request.Message.Type { - case "create": - mesCopy := &message.Message{} - *mesCopy = request.Message.Message - mesCopy.From = userID - id, err := h.messageCase.SendMessage(ctx, userID, mesCopy) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: request.Channel.Topic, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_CREATE, - Id: int64(id), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - case "update": - mesCopy := &message.Message{} - *mesCopy = request.Message.Message - err = h.messageCase.UpdateContentMessage(ctx, userID, mesCopy) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: request.Channel.Topic, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_UPDATE, - Id: int64(request.Message.Message.ID), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - - case "delete": - err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: request.Channel.Topic, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_DELETE, - Id: int64(request.Message.Message.ID), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - default: - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) - } - case "Subscribe": - err = h.subscribe(ctx, h.client, request, conn, userID) - if err != nil { - h.log.Warn(err.Error()) - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "subscribe_fail", "failed to subscribe to the channel", nil)) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "you have successfully subscribed to the channel", nil)) - default: - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported action", nil)) - } - } -} - -func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClient, req *Request, conn *ws.Conn, userID int) error { - sc, err := client.Subscribe(ctx, &rt.Channel{ - Name: req.Channel.Name, - Topic: req.Channel.Topic, - }) - if err != nil { - return fmt.Errorf("subscribe: %w", err) + return nil, fmt.Errorf("upgrade to websocket connect: %w", err) } - go func() { - for { - obj, err := sc.Recv() - if err != nil { - return - } - mes, ok := obj.Body.(*rt.Message_Object) - if ok { - var msg *message.Message - if mes.Object.Type == rt.EventType_EV_DELETE { - msg = &message.Message{ID: int(mes.Object.Id)} - } else { - msg, err = h.messageCase.GetMessage(ctx, userID, int(mes.Object.Id)) - if err != nil { - h.log.Error(err.Error()) - return - } - } - objType := "" - switch mes.Object.Type { - case rt.EventType_EV_CREATE: - objType = "create" - case rt.EventType_EV_UPDATE: - objType = "update" - case rt.EventType_EV_DELETE: - objType = "delete" - } - err = wsjson.Write(ctx, conn, newMessageFromChannel(req.Channel, "ok", "", Object{ - Type: objType, - Message: *msg, - })) - if err != nil { - h.log.Error(err.Error()) - return - } - } - } - }() - return nil + return conn, nil } From 8e1a2a4f3e866f92b7dbe1fd86f568e55074d8d9 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 13 Dec 2023 14:42:01 +0300 Subject: [PATCH 236/266] TP-1c3 update: the subscription is carried out on the backend --- internal/pkg/delivery/websocket/chat.go | 183 +++++++++++------------ internal/pkg/delivery/websocket/types.go | 11 +- 2 files changed, 98 insertions(+), 96 deletions(-) diff --git a/internal/pkg/delivery/websocket/chat.go b/internal/pkg/delivery/websocket/chat.go index 1c4bca4..f7d8b6a 100644 --- a/internal/pkg/delivery/websocket/chat.go +++ b/internal/pkg/delivery/websocket/chat.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strconv" ws "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" @@ -27,14 +28,21 @@ func (h *HandlerWebSocket) Chat(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) defer cancel() - err = h.serveWebSocketConn(ctx, conn, userID) + err = h.subscribe(ctx, conn, userID) + if err != nil { + h.log.Error(err.Error()) + conn.Close(ws.StatusInternalError, "subscribe_fail") + return + } + + err = h.serveChat(ctx, conn, userID) if err != nil { h.log.Error(err.Error()) } } -func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn, userID int) error { - request := &Request{} +func (h *HandlerWebSocket) serveChat(ctx context.Context, conn *ws.Conn, userID int) error { + request := &PublsihRequest{} var err error for { err = wsjson.Read(ctx, conn, request) @@ -42,112 +50,103 @@ func (h *HandlerWebSocket) serveWebSocketConn(ctx context.Context, conn *ws.Conn h.log.Error(err.Error()) return fmt.Errorf("read message: %w", err) } - switch request.Action { - case "Publish": - switch request.Message.Type { - case "create": - mesCopy := &message.Message{} - *mesCopy = request.Message.Message - mesCopy.From = userID - id, err := h.messageCase.SendMessage(ctx, userID, mesCopy) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: request.Channel.Topic, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_CREATE, - Id: int64(id), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - case "update": - mesCopy := &message.Message{} - *mesCopy = request.Message.Message - err = h.messageCase.UpdateContentMessage(ctx, userID, mesCopy) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: request.Channel.Topic, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_UPDATE, - Id: int64(request.Message.Message.ID), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - case "delete": - err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: request.Channel.Topic, + switch request.Message.Type { + case "create": + mesCopy := &message.Message{} + *mesCopy = request.Message.Message + mesCopy.From = userID + id, err := h.messageCase.SendMessage(ctx, userID, mesCopy) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) + _, err = h.client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: _topicChat, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_CREATE, + Id: int64(id), + }, }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_DELETE, - Id: int64(request.Message.Message.ID), - }, + }, + }) + if err != nil { + h.log.Error(err.Error()) + } + case "update": + mesCopy := &message.Message{} + *mesCopy = request.Message.Message + err = h.messageCase.UpdateContentMessage(ctx, userID, mesCopy) + if err != nil { + h.log.Warn(err.Error()) + continue + } + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) + _, err = h.client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: _topicChat, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_UPDATE, + Id: int64(request.Message.Message.ID), }, }, - }) - if err != nil { - h.log.Error(err.Error()) - } - default: - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) + }, + }) + if err != nil { + h.log.Error(err.Error()) } - case "Subscribe": - err = h.subscribe(ctx, h.client, request, conn, userID) + + case "delete": + err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) if err != nil { h.log.Warn(err.Error()) - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "subscribe_fail", "failed to subscribe to the channel", nil)) continue } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "you have successfully subscribed to the channel", nil)) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) + _, err = h.client.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: request.Channel.Name, + Topic: _topicChat, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: rt.EventType_EV_DELETE, + Id: int64(request.Message.Message.ID), + }, + }, + }, + }) + if err != nil { + h.log.Error(err.Error()) + } default: - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported action", nil)) + wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) } } } -func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClient, req *Request, conn *ws.Conn, userID int) error { - sc, err := client.Subscribe(ctx, &rt.Channel{ - Name: req.Channel.Name, - Topic: req.Channel.Topic, +func (h *HandlerWebSocket) subscribe(ctx context.Context, conn *ws.Conn, userID int) error { + channel := Channel{Name: strconv.Itoa(userID)} + + sc, err := h.client.Subscribe(ctx, &rt.Channel{ + Name: channel.Name, + Topic: _topicChat, }) if err != nil { return fmt.Errorf("subscribe: %w", err) } + go func() { for { obj, err := sc.Recv() @@ -175,7 +174,7 @@ func (h *HandlerWebSocket) subscribe(ctx context.Context, client rt.RealTimeClie case rt.EventType_EV_DELETE: objType = "delete" } - err = wsjson.Write(ctx, conn, newMessageFromChannel(req.Channel, "ok", "", Object{ + err = wsjson.Write(ctx, conn, newMessageFromChannel(channel, "ok", "", Object{ Type: objType, Message: *msg, })) diff --git a/internal/pkg/delivery/websocket/types.go b/internal/pkg/delivery/websocket/types.go index b0ff2c9..ddd320a 100644 --- a/internal/pkg/delivery/websocket/types.go +++ b/internal/pkg/delivery/websocket/types.go @@ -2,9 +2,13 @@ package websocket import "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" +const ( + _topicChat = "chat" + _topicNotification = "notification" +) + type Channel struct { - Name string `json:"name"` - Topic string `json:"topic"` + Name string `json:"name"` } type Object struct { @@ -12,9 +16,8 @@ type Object struct { Message message.Message `json:"message"` } -type Request struct { +type PublsihRequest struct { ID int `json:"requestID"` - Action string Channel Channel Message Object } From 103d539f0577476b506ce64e1f5a6e5bb14f2bce Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Wed, 13 Dec 2023 19:47:09 +0300 Subject: [PATCH 237/266] TP-1c3 update: work with realtime for chat --- internal/app/app.go | 18 +- internal/pkg/delivery/http/v1/chat.go | 2 +- internal/pkg/delivery/websocket/chat.go | 173 +++++------------- .../pkg/delivery/websocket/notification.go | 17 +- internal/pkg/delivery/websocket/types.go | 6 +- internal/pkg/delivery/websocket/websocket.go | 6 +- internal/pkg/usecase/message/usecase.go | 129 ++++++++++++- 7 files changed, 200 insertions(+), 151 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 549e753..800466a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -67,8 +67,15 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer connMessMS.Close() + connRealtime, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Error(err.Error()) + return + } + defer connRealtime.Close() + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) - messageCase := message.New(messenger.NewMessengerClient(connMessMS)) + messageCase := message.New(messenger.NewMessengerClient(connMessMS), rt.NewRealTimeClient(connRealtime), log, true) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) conn, err := grpc.Dial(cfg.AddrAuthServer, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -90,14 +97,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { CommentCase: comment.New(commentRepo.NewCommentRepoPG(pool), pinCase), }) - connRealtime, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - log.Error(err.Error()) - return - } - defer connRealtime.Close() - - wsHandler := deliveryWS.New(log, messageCase, rt.NewRealTimeClient(connRealtime), + wsHandler := deliveryWS.New(log, messageCase, deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) diff --git a/internal/pkg/delivery/http/v1/chat.go b/internal/pkg/delivery/http/v1/chat.go index ac6adf5..09e64cb 100644 --- a/internal/pkg/delivery/http/v1/chat.go +++ b/internal/pkg/delivery/http/v1/chat.go @@ -90,7 +90,7 @@ func (h *HandlerHTTP) DeleteMessage(w http.ResponseWriter, r *http.Request) { return } - err = h.messageCase.DeleteMessage(r.Context(), userID, messageID) + err = h.messageCase.DeleteMessage(r.Context(), userID, &message.Message{ID: messageID}) if err != nil { logger.Warn(err.Error()) err = responseError(w, "delete_message", "fail deleting a message") diff --git a/internal/pkg/delivery/websocket/chat.go b/internal/pkg/delivery/websocket/chat.go index f7d8b6a..7c8e098 100644 --- a/internal/pkg/delivery/websocket/chat.go +++ b/internal/pkg/delivery/websocket/chat.go @@ -4,13 +4,10 @@ import ( "context" "fmt" "net/http" - "strconv" ws "nhooyr.io/websocket" "nhooyr.io/websocket/wsjson" - rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" ) @@ -28,7 +25,7 @@ func (h *HandlerWebSocket) Chat(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) defer cancel() - err = h.subscribe(ctx, conn, userID) + err = h.subscribeOnChat(ctx, conn, userID) if err != nil { h.log.Error(err.Error()) conn.Close(ws.StatusInternalError, "subscribe_fail") @@ -36,8 +33,9 @@ func (h *HandlerWebSocket) Chat(w http.ResponseWriter, r *http.Request) { } err = h.serveChat(ctx, conn, userID) - if err != nil { + if err != nil && ws.CloseStatus(err) == -1 { h.log.Error(err.Error()) + conn.Close(ws.StatusInternalError, "serve_chat") } } @@ -51,137 +49,62 @@ func (h *HandlerWebSocket) serveChat(ctx context.Context, conn *ws.Conn, userID return fmt.Errorf("read message: %w", err) } - switch request.Message.Type { - case "create": - mesCopy := &message.Message{} - *mesCopy = request.Message.Message - mesCopy.From = userID - id, err := h.messageCase.SendMessage(ctx, userID, mesCopy) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: _topicChat, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_CREATE, - Id: int64(id), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - case "update": - mesCopy := &message.Message{} - *mesCopy = request.Message.Message - err = h.messageCase.UpdateContentMessage(ctx, userID, mesCopy) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: _topicChat, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_UPDATE, - Id: int64(request.Message.Message.ID), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } + h.handlePublishRequestMessage(ctx, conn, userID, request) + } +} - case "delete": - err = h.messageCase.DeleteMessage(ctx, userID, request.Message.Message.ID) - if err != nil { - h.log.Warn(err.Error()) - continue - } - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) - _, err = h.client.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: request.Channel.Name, - Topic: _topicChat, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: rt.EventType_EV_DELETE, - Id: int64(request.Message.Message.ID), - }, - }, - }, - }) - if err != nil { - h.log.Error(err.Error()) - } - default: - wsjson.Write(ctx, conn, newResponseOnRequest(request.ID, "error", "unsupported", "unsupported eventType", nil)) +func (h *HandlerWebSocket) handlePublishRequestMessage(ctx context.Context, conn *ws.Conn, userID int, r *PublsihRequest) { + switch r.Message.Type { + case "create": + r.Message.Message.From = userID + id, err := h.messageCase.SendMessage(ctx, userID, &r.Message.Message) + if err != nil { + h.log.Warn(err.Error()) + return + } + wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) + + case "update": + err := h.messageCase.UpdateContentMessage(ctx, userID, &r.Message.Message) + if err != nil { + h.log.Warn(err.Error()) + return + } + wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) + + case "delete": + err := h.messageCase.DeleteMessage(ctx, userID, &r.Message.Message) + if err != nil { + h.log.Warn(err.Error()) + return } + wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) + + default: + wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "error", "unsupported", "unsupported eventType", nil)) } } -func (h *HandlerWebSocket) subscribe(ctx context.Context, conn *ws.Conn, userID int) error { - channel := Channel{Name: strconv.Itoa(userID)} - - sc, err := h.client.Subscribe(ctx, &rt.Channel{ - Name: channel.Name, - Topic: _topicChat, - }) +func (h *HandlerWebSocket) subscribeOnChat(ctx context.Context, conn *ws.Conn, userID int) error { + chanEvMsg, err := h.messageCase.SubscribeUserToAllChats(ctx, userID) if err != nil { - return fmt.Errorf("subscribe: %w", err) + return fmt.Errorf("subscribe user on chat: %w", err) } go func() { - for { - obj, err := sc.Recv() - if err != nil { + for eventMessage := range chanEvMsg { + if eventMessage.Err != nil { + h.log.Error(err.Error()) return } - mes, ok := obj.Body.(*rt.Message_Object) - if ok { - var msg *message.Message - if mes.Object.Type == rt.EventType_EV_DELETE { - msg = &message.Message{ID: int(mes.Object.Id)} - } else { - msg, err = h.messageCase.GetMessage(ctx, userID, int(mes.Object.Id)) - if err != nil { - h.log.Error(err.Error()) - return - } - } - objType := "" - switch mes.Object.Type { - case rt.EventType_EV_CREATE: - objType = "create" - case rt.EventType_EV_UPDATE: - objType = "update" - case rt.EventType_EV_DELETE: - objType = "delete" - } - err = wsjson.Write(ctx, conn, newMessageFromChannel(channel, "ok", "", Object{ - Type: objType, - Message: *msg, - })) - if err != nil { - h.log.Error(err.Error()) - return - } + + err = wsjson.Write(ctx, conn, newMessageFromChannel("ok", "", Object{ + Type: eventMessage.Type, + Message: *eventMessage.Message, + })) + if err != nil { + h.log.Error(err.Error()) + return } } }() diff --git a/internal/pkg/delivery/websocket/notification.go b/internal/pkg/delivery/websocket/notification.go index 03e8981..878cd30 100644 --- a/internal/pkg/delivery/websocket/notification.go +++ b/internal/pkg/delivery/websocket/notification.go @@ -4,8 +4,9 @@ import ( "context" "net/http" + ws "nhooyr.io/websocket" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) func (h *HandlerWebSocket) Notification(w http.ResponseWriter, r *http.Request) { @@ -22,5 +23,17 @@ func (h *HandlerWebSocket) Notification(w http.ResponseWriter, r *http.Request) ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) defer cancel() - h.log.Info("", logger.F{"user", userID}, logger.F{"context", ctx}) + err = h.subscribeOnNotification(ctx, conn, userID) + if err != nil && ws.CloseStatus(err) == -1 { + h.log.Error(err.Error()) + conn.Close(ws.StatusInternalError, "subscribe_fail") + } +} + +// func (h *HandlerWebSocket) handleNotification(ctx context.Context, conn *ws.Conn, userID int) { + +// } + +func (h *HandlerWebSocket) subscribeOnNotification(ctx context.Context, conn *ws.Conn, userID int) error { + return nil } diff --git a/internal/pkg/delivery/websocket/types.go b/internal/pkg/delivery/websocket/types.go index ddd320a..0f0572a 100644 --- a/internal/pkg/delivery/websocket/types.go +++ b/internal/pkg/delivery/websocket/types.go @@ -24,7 +24,6 @@ type PublsihRequest struct { type MessageFromChannel struct { Type string `json:"type"` - Channel Channel `json:"channel"` Message ResponseMessage `json:"message"` } @@ -55,10 +54,9 @@ func newResponseOnRequest(id int, status, code, message string, body any) *Respo } } -func newMessageFromChannel(channel Channel, status, code string, v any) *MessageFromChannel { +func newMessageFromChannel(status, code string, v any) *MessageFromChannel { mes := &MessageFromChannel{ - Type: "event", - Channel: channel, + Type: "event", Message: ResponseMessage{ Status: status, Code: code, diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 18df717..46a2272 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -7,7 +7,6 @@ import ( ws "nhooyr.io/websocket" - rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -16,7 +15,6 @@ type HandlerWebSocket struct { originPatterns []string log *log.Logger messageCase usecase.Usecase - client rt.RealTimeClient } type Option func(h *HandlerWebSocket) @@ -29,8 +27,8 @@ func SetOriginPatterns(patterns []string) Option { } } -func New(log *log.Logger, mesCase usecase.Usecase, client rt.RealTimeClient, opts ...Option) *HandlerWebSocket { - handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase, client: client} +func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { + handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase} for _, opt := range opts { opt(handlerWS) } diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 4cf8794..ae99e92 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -10,11 +10,16 @@ import ( "google.golang.org/grpc/metadata" mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" messMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/delivery/grpc" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) var ErrNoAccess = errors.New("there is no access to perform this action") +var ErrRealTimeDisable = errors.New("realtime disable") +var ErrUnknowObj = errors.New("unknow object") //go:generate mockgen -destination=./mock/message_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { @@ -22,16 +27,37 @@ type Usecase interface { SendMessage(ctx context.Context, userID int, mes *entity.Message) (int, error) GetMessagesFromChat(ctx context.Context, userID int, chat entity.Chat, count, lastID int) (feed []entity.Message, newLastID int, err error) UpdateContentMessage(ctx context.Context, userID int, mes *entity.Message) error - DeleteMessage(ctx context.Context, userID, mesID int) error + DeleteMessage(ctx context.Context, userID int, mes *entity.Message) error GetMessage(ctx context.Context, userID int, messageID int) (*entity.Message, error) + SubscribeUserToAllChats(ctx context.Context, userID int) (<-chan EventMessage, error) +} + +const _topicChat = "chat" + +type EventMessage struct { + Type string + Message *entity.Message + Err error +} + +func makeErrEventMessage(err error) EventMessage { + return EventMessage{Err: err} } type messageCase struct { - client mess.MessengerClient + client mess.MessengerClient + rtClient rt.RealTimeClient + log *logger.Logger + realtimeIsEnable bool } -func New(repo mess.MessengerClient) *messageCase { - return &messageCase{repo} +func New(cl mess.MessengerClient, rtClient rt.RealTimeClient, log *logger.Logger, rtEnable bool) *messageCase { + return &messageCase{ + client: cl, + rtClient: rtClient, + log: log, + realtimeIsEnable: rtEnable, + } } func (m *messageCase) SendMessage(ctx context.Context, userID int, mes *entity.Message) (int, error) { @@ -43,6 +69,9 @@ func (m *messageCase) SendMessage(ctx context.Context, userID int, mes *entity.M if err != nil { return 0, fmt.Errorf("send message by grpc client") } + + m.publishToRealTimeServer(ctx, strconv.Itoa(mes.To), int(msgID.GetId()), rt.EventType_EV_CREATE) + return int(msgID.GetId()), nil } @@ -74,13 +103,19 @@ func (m *messageCase) UpdateContentMessage(ctx context.Context, userID int, mes }); err != nil { return fmt.Errorf("update messege by grpc client") } + + m.publishToRealTimeServer(ctx, strconv.Itoa(mes.To), mes.ID, rt.EventType_EV_UPDATE) + return nil } -func (m *messageCase) DeleteMessage(ctx context.Context, userID, mesID int) error { - if _, err := m.client.DeleteMessage(setAuthenticatedMetadataCtx(ctx, userID), &mess.MsgID{Id: int64(mesID)}); err != nil { +func (m *messageCase) DeleteMessage(ctx context.Context, userID int, mes *entity.Message) error { + if _, err := m.client.DeleteMessage(setAuthenticatedMetadataCtx(ctx, userID), &mess.MsgID{Id: int64(mes.ID)}); err != nil { return fmt.Errorf("delete messege by grpc client") } + + m.publishToRealTimeServer(ctx, strconv.Itoa(mes.To), mes.ID, rt.EventType_EV_DELETE) + return nil } @@ -115,6 +150,88 @@ func (m *messageCase) GetUserChatsWithOtherUsers(ctx context.Context, userID, co return convertFeedChat(feed), int(feed.GetLastID()), errRes } +func (m *messageCase) publishToRealTimeServer(ctx context.Context, channelName string, idMsg int, t rt.EventType) { + if !m.realtimeIsEnable { + return + } + + go func() { + _, err := m.rtClient.Publish(ctx, &rt.PublishMessage{ + Channel: &rt.Channel{ + Name: channelName, + Topic: _topicChat, + }, + Message: &rt.Message{ + Body: &rt.Message_Object{ + Object: &rt.EventObject{ + Type: t, + Id: int64(idMsg), + }, + }, + }, + }) + if err != nil { + m.log.Error(err.Error()) + } + }() +} + +func (m *messageCase) SubscribeUserToAllChats(ctx context.Context, userID int) (<-chan EventMessage, error) { + if !m.realtimeIsEnable { + return nil, ErrRealTimeDisable + } + + subClient, err := m.rtClient.Subscribe(ctx, &rt.Channel{ + Name: strconv.Itoa(userID), + Topic: _topicChat, + }) + if err != nil { + return nil, fmt.Errorf("subscribe: %w", err) + } + + chanEvMsg := make(chan EventMessage) + go m.receiveFromSubClient(ctx, userID, subClient, chanEvMsg) + return chanEvMsg, nil +} + +func (m *messageCase) receiveFromSubClient(ctx context.Context, userID int, subClient rt.RealTime_SubscribeClient, chanEvMsg chan<- EventMessage) { + defer close(chanEvMsg) + evMsg := EventMessage{} + for { + obj, err := subClient.Recv() + if err != nil { + chanEvMsg <- makeErrEventMessage(fmt.Errorf("receive from subcribtion client: %w", err)) + return + } + + mes, ok := obj.Body.(*rt.Message_Object) + if !ok { + chanEvMsg <- makeErrEventMessage(ErrUnknowObj) + return + } + + if mes.Object.Type == rt.EventType_EV_DELETE { + evMsg.Message = &message.Message{ID: int(mes.Object.Id)} + } else { + evMsg.Message, err = m.GetMessage(ctx, userID, int(mes.Object.Id)) + if err != nil { + m.log.Error(err.Error()) + } + } + + switch mes.Object.Type { + case rt.EventType_EV_CREATE: + evMsg.Type = "create" + case rt.EventType_EV_UPDATE: + evMsg.Type = "update" + case rt.EventType_EV_DELETE: + evMsg.Type = "delete" + } + + chanEvMsg <- evMsg + } +} + func setAuthenticatedMetadataCtx(ctx context.Context, userID int) context.Context { return metadata.AppendToOutgoingContext(ctx, messMS.AuthenticatedMetadataKey, strconv.FormatInt(int64(userID), 10)) } From 4dcb282e95d5d378ae47fd3fbc5dddc4e8d7ea75 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 14 Dec 2023 11:01:20 +0300 Subject: [PATCH 238/266] TP-1c3 update: proto scheme for a reltime microservice --- api/proto/realtime.proto | 13 +- internal/api/realtime/realtime.pb.go | 297 ++++++++++++++++------ internal/api/realtime/realtime_grpc.pb.go | 10 +- internal/microservices/realtime/server.go | 6 +- internal/pkg/usecase/message/usecase.go | 7 +- 5 files changed, 245 insertions(+), 88 deletions(-) diff --git a/api/proto/realtime.proto b/api/proto/realtime.proto index d04bc35..3fca899 100644 --- a/api/proto/realtime.proto +++ b/api/proto/realtime.proto @@ -8,7 +8,11 @@ package realtime; service RealTime { rpc Publish(PublishMessage) returns (google.protobuf.Empty) {} - rpc Subscribe(Channel) returns (stream Message) {} + rpc Subscribe(Channels) returns (stream Message) {} +} + +message Channels { + repeated Channel chans = 1; } message Channel { @@ -27,10 +31,15 @@ message EventObject { EventType type = 2; } +message EventMap { + int64 type = 1; + map<string, string> m = 2; +} + message Message { oneof body { EventObject object = 1; - string content = 2; + EventMap content = 2; } } diff --git a/internal/api/realtime/realtime.pb.go b/internal/api/realtime/realtime.pb.go index 8e17fe5..3b3ed8f 100644 --- a/internal/api/realtime/realtime.pb.go +++ b/internal/api/realtime/realtime.pb.go @@ -70,6 +70,53 @@ func (EventType) EnumDescriptor() ([]byte, []int) { return file_api_proto_realtime_proto_rawDescGZIP(), []int{0} } +type Channels struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Chans []*Channel `protobuf:"bytes,1,rep,name=chans,proto3" json:"chans,omitempty"` +} + +func (x *Channels) Reset() { + *x = Channels{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_realtime_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Channels) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Channels) ProtoMessage() {} + +func (x *Channels) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_realtime_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Channels.ProtoReflect.Descriptor instead. +func (*Channels) Descriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{0} +} + +func (x *Channels) GetChans() []*Channel { + if x != nil { + return x.Chans + } + return nil +} + type Channel struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -82,7 +129,7 @@ type Channel struct { func (x *Channel) Reset() { *x = Channel{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_realtime_proto_msgTypes[0] + mi := &file_api_proto_realtime_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -95,7 +142,7 @@ func (x *Channel) String() string { func (*Channel) ProtoMessage() {} func (x *Channel) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_realtime_proto_msgTypes[0] + mi := &file_api_proto_realtime_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -108,7 +155,7 @@ func (x *Channel) ProtoReflect() protoreflect.Message { // Deprecated: Use Channel.ProtoReflect.Descriptor instead. func (*Channel) Descriptor() ([]byte, []int) { - return file_api_proto_realtime_proto_rawDescGZIP(), []int{0} + return file_api_proto_realtime_proto_rawDescGZIP(), []int{1} } func (x *Channel) GetTopic() string { @@ -137,7 +184,7 @@ type EventObject struct { func (x *EventObject) Reset() { *x = EventObject{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_realtime_proto_msgTypes[1] + mi := &file_api_proto_realtime_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -150,7 +197,7 @@ func (x *EventObject) String() string { func (*EventObject) ProtoMessage() {} func (x *EventObject) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_realtime_proto_msgTypes[1] + mi := &file_api_proto_realtime_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -163,7 +210,7 @@ func (x *EventObject) ProtoReflect() protoreflect.Message { // Deprecated: Use EventObject.ProtoReflect.Descriptor instead. func (*EventObject) Descriptor() ([]byte, []int) { - return file_api_proto_realtime_proto_rawDescGZIP(), []int{1} + return file_api_proto_realtime_proto_rawDescGZIP(), []int{2} } func (x *EventObject) GetId() int64 { @@ -180,6 +227,61 @@ func (x *EventObject) GetType() EventType { return EventType_EV_CREATE } +type EventMap struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type int64 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + M map[string]string `protobuf:"bytes,2,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *EventMap) Reset() { + *x = EventMap{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_realtime_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EventMap) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventMap) ProtoMessage() {} + +func (x *EventMap) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_realtime_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventMap.ProtoReflect.Descriptor instead. +func (*EventMap) Descriptor() ([]byte, []int) { + return file_api_proto_realtime_proto_rawDescGZIP(), []int{3} +} + +func (x *EventMap) GetType() int64 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *EventMap) GetM() map[string]string { + if x != nil { + return x.M + } + return nil +} + type Message struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -195,7 +297,7 @@ type Message struct { func (x *Message) Reset() { *x = Message{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_realtime_proto_msgTypes[2] + mi := &file_api_proto_realtime_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -208,7 +310,7 @@ func (x *Message) String() string { func (*Message) ProtoMessage() {} func (x *Message) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_realtime_proto_msgTypes[2] + mi := &file_api_proto_realtime_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -221,7 +323,7 @@ func (x *Message) ProtoReflect() protoreflect.Message { // Deprecated: Use Message.ProtoReflect.Descriptor instead. func (*Message) Descriptor() ([]byte, []int) { - return file_api_proto_realtime_proto_rawDescGZIP(), []int{2} + return file_api_proto_realtime_proto_rawDescGZIP(), []int{4} } func (m *Message) GetBody() isMessage_Body { @@ -238,11 +340,11 @@ func (x *Message) GetObject() *EventObject { return nil } -func (x *Message) GetContent() string { +func (x *Message) GetContent() *EventMap { if x, ok := x.GetBody().(*Message_Content); ok { return x.Content } - return "" + return nil } type isMessage_Body interface { @@ -254,7 +356,7 @@ type Message_Object struct { } type Message_Content struct { - Content string `protobuf:"bytes,2,opt,name=content,proto3,oneof"` + Content *EventMap `protobuf:"bytes,2,opt,name=content,proto3,oneof"` } func (*Message_Object) isMessage_Body() {} @@ -273,7 +375,7 @@ type PublishMessage struct { func (x *PublishMessage) Reset() { *x = PublishMessage{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_realtime_proto_msgTypes[3] + mi := &file_api_proto_realtime_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -286,7 +388,7 @@ func (x *PublishMessage) String() string { func (*PublishMessage) ProtoMessage() {} func (x *PublishMessage) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_realtime_proto_msgTypes[3] + mi := &file_api_proto_realtime_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -299,7 +401,7 @@ func (x *PublishMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use PublishMessage.ProtoReflect.Descriptor instead. func (*PublishMessage) Descriptor() ([]byte, []int) { - return file_api_proto_realtime_proto_rawDescGZIP(), []int{3} + return file_api_proto_realtime_proto_rawDescGZIP(), []int{5} } func (x *PublishMessage) GetChannel() *Channel { @@ -323,43 +425,56 @@ var file_api_proto_realtime_proto_rawDesc = []byte{ 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x33, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, - 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x0b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x5e, - 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x61, 0x6c, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x48, 0x00, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x6a, - 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x2b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x2b, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x38, 0x0a, 0x09, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x43, 0x52, - 0x45, 0x41, 0x54, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x44, 0x45, 0x4c, - 0x45, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x55, 0x50, 0x44, 0x41, - 0x54, 0x45, 0x10, 0x02, 0x32, 0x80, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x18, 0x2e, 0x72, - 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, - 0x12, 0x35, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x11, 0x2e, - 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x1a, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x70, 0x61, 0x72, 0x6b, 0x2d, 0x6d, 0x61, - 0x69, 0x6c, 0x2d, 0x72, 0x75, 0x2f, 0x32, 0x30, 0x32, 0x33, 0x5f, 0x32, 0x5f, 0x4f, 0x4e, 0x44, - 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, - 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x22, 0x33, 0x0a, 0x08, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x27, 0x0a, + 0x05, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, + 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x05, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x22, 0x33, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x0b, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x7d, 0x0a, 0x08, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x01, 0x6d, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, + 0x61, 0x70, 0x2e, 0x4d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x01, 0x6d, 0x1a, 0x34, 0x0a, 0x06, + 0x4d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x72, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2e, + 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x4d, 0x61, 0x70, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x06, + 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x6a, 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x2b, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x2a, 0x38, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x0d, 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x45, 0x56, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, + 0x09, 0x45, 0x56, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x32, 0x81, 0x01, 0x0a, + 0x08, 0x52, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x12, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0x11, 0x2e, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, + 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x6f, 0x2d, 0x70, 0x61, 0x72, 0x6b, 0x2d, 0x6d, 0x61, 0x69, 0x6c, 0x2d, 0x72, 0x75, 0x2f, 0x32, + 0x30, 0x32, 0x33, 0x5f, 0x32, 0x5f, 0x4f, 0x4e, 0x44, 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x2f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -375,29 +490,35 @@ func file_api_proto_realtime_proto_rawDescGZIP() []byte { } var file_api_proto_realtime_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_api_proto_realtime_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_api_proto_realtime_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_api_proto_realtime_proto_goTypes = []interface{}{ (EventType)(0), // 0: realtime.EventType - (*Channel)(nil), // 1: realtime.Channel - (*EventObject)(nil), // 2: realtime.EventObject - (*Message)(nil), // 3: realtime.Message - (*PublishMessage)(nil), // 4: realtime.PublishMessage - (*empty.Empty)(nil), // 5: google.protobuf.Empty + (*Channels)(nil), // 1: realtime.Channels + (*Channel)(nil), // 2: realtime.Channel + (*EventObject)(nil), // 3: realtime.EventObject + (*EventMap)(nil), // 4: realtime.EventMap + (*Message)(nil), // 5: realtime.Message + (*PublishMessage)(nil), // 6: realtime.PublishMessage + nil, // 7: realtime.EventMap.MEntry + (*empty.Empty)(nil), // 8: google.protobuf.Empty } var file_api_proto_realtime_proto_depIdxs = []int32{ - 0, // 0: realtime.EventObject.type:type_name -> realtime.EventType - 2, // 1: realtime.Message.object:type_name -> realtime.EventObject - 1, // 2: realtime.PublishMessage.channel:type_name -> realtime.Channel - 3, // 3: realtime.PublishMessage.message:type_name -> realtime.Message - 4, // 4: realtime.RealTime.Publish:input_type -> realtime.PublishMessage - 1, // 5: realtime.RealTime.Subscribe:input_type -> realtime.Channel - 5, // 6: realtime.RealTime.Publish:output_type -> google.protobuf.Empty - 3, // 7: realtime.RealTime.Subscribe:output_type -> realtime.Message - 6, // [6:8] is the sub-list for method output_type - 4, // [4:6] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 2, // 0: realtime.Channels.chans:type_name -> realtime.Channel + 0, // 1: realtime.EventObject.type:type_name -> realtime.EventType + 7, // 2: realtime.EventMap.m:type_name -> realtime.EventMap.MEntry + 3, // 3: realtime.Message.object:type_name -> realtime.EventObject + 4, // 4: realtime.Message.content:type_name -> realtime.EventMap + 2, // 5: realtime.PublishMessage.channel:type_name -> realtime.Channel + 5, // 6: realtime.PublishMessage.message:type_name -> realtime.Message + 6, // 7: realtime.RealTime.Publish:input_type -> realtime.PublishMessage + 1, // 8: realtime.RealTime.Subscribe:input_type -> realtime.Channels + 8, // 9: realtime.RealTime.Publish:output_type -> google.protobuf.Empty + 5, // 10: realtime.RealTime.Subscribe:output_type -> realtime.Message + 9, // [9:11] is the sub-list for method output_type + 7, // [7:9] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_api_proto_realtime_proto_init() } @@ -407,7 +528,7 @@ func file_api_proto_realtime_proto_init() { } if !protoimpl.UnsafeEnabled { file_api_proto_realtime_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Channel); i { + switch v := v.(*Channels); i { case 0: return &v.state case 1: @@ -419,7 +540,7 @@ func file_api_proto_realtime_proto_init() { } } file_api_proto_realtime_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EventObject); i { + switch v := v.(*Channel); i { case 0: return &v.state case 1: @@ -431,7 +552,7 @@ func file_api_proto_realtime_proto_init() { } } file_api_proto_realtime_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Message); i { + switch v := v.(*EventObject); i { case 0: return &v.state case 1: @@ -443,6 +564,30 @@ func file_api_proto_realtime_proto_init() { } } file_api_proto_realtime_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EventMap); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_realtime_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Message); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_realtime_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PublishMessage); i { case 0: return &v.state @@ -455,7 +600,7 @@ func file_api_proto_realtime_proto_init() { } } } - file_api_proto_realtime_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_api_proto_realtime_proto_msgTypes[4].OneofWrappers = []interface{}{ (*Message_Object)(nil), (*Message_Content)(nil), } @@ -465,7 +610,7 @@ func file_api_proto_realtime_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_proto_realtime_proto_rawDesc, NumEnums: 1, - NumMessages: 4, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/api/realtime/realtime_grpc.pb.go b/internal/api/realtime/realtime_grpc.pb.go index 759e3ff..420b61e 100644 --- a/internal/api/realtime/realtime_grpc.pb.go +++ b/internal/api/realtime/realtime_grpc.pb.go @@ -24,7 +24,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RealTimeClient interface { Publish(ctx context.Context, in *PublishMessage, opts ...grpc.CallOption) (*empty.Empty, error) - Subscribe(ctx context.Context, in *Channel, opts ...grpc.CallOption) (RealTime_SubscribeClient, error) + Subscribe(ctx context.Context, in *Channels, opts ...grpc.CallOption) (RealTime_SubscribeClient, error) } type realTimeClient struct { @@ -44,7 +44,7 @@ func (c *realTimeClient) Publish(ctx context.Context, in *PublishMessage, opts . return out, nil } -func (c *realTimeClient) Subscribe(ctx context.Context, in *Channel, opts ...grpc.CallOption) (RealTime_SubscribeClient, error) { +func (c *realTimeClient) Subscribe(ctx context.Context, in *Channels, opts ...grpc.CallOption) (RealTime_SubscribeClient, error) { stream, err := c.cc.NewStream(ctx, &RealTime_ServiceDesc.Streams[0], "/realtime.RealTime/Subscribe", opts...) if err != nil { return nil, err @@ -81,7 +81,7 @@ func (x *realTimeSubscribeClient) Recv() (*Message, error) { // for forward compatibility type RealTimeServer interface { Publish(context.Context, *PublishMessage) (*empty.Empty, error) - Subscribe(*Channel, RealTime_SubscribeServer) error + Subscribe(*Channels, RealTime_SubscribeServer) error mustEmbedUnimplementedRealTimeServer() } @@ -92,7 +92,7 @@ type UnimplementedRealTimeServer struct { func (UnimplementedRealTimeServer) Publish(context.Context, *PublishMessage) (*empty.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Publish not implemented") } -func (UnimplementedRealTimeServer) Subscribe(*Channel, RealTime_SubscribeServer) error { +func (UnimplementedRealTimeServer) Subscribe(*Channels, RealTime_SubscribeServer) error { return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") } func (UnimplementedRealTimeServer) mustEmbedUnimplementedRealTimeServer() {} @@ -127,7 +127,7 @@ func _RealTime_Publish_Handler(srv interface{}, ctx context.Context, dec func(in } func _RealTime_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(Channel) + m := new(Channels) if err := stream.RecvMsg(m); err != nil { return err } diff --git a/internal/microservices/realtime/server.go b/internal/microservices/realtime/server.go index c83d166..0574eef 100644 --- a/internal/microservices/realtime/server.go +++ b/internal/microservices/realtime/server.go @@ -42,7 +42,7 @@ func (s Server) Publish(ctx context.Context, pm *rt.PublishMessage) (*empty.Empt return &empty.Empty{}, nil } -func (s Server) Subscribe(c *rt.Channel, ss rt.RealTime_SubscribeServer) error { +func (s Server) Subscribe(chans *rt.Channels, ss rt.RealTime_SubscribeServer) error { id, err := uuid.NewRandom() if err != nil { return status.Error(codes.Internal, "generate uuid v4") @@ -52,7 +52,9 @@ func (s Server) Subscribe(c *rt.Channel, ss rt.RealTime_SubscribeServer) error { transport: ss, } - s.node.AddSubscriber(c, client) + for _, ch := range chans.GetChans() { + s.node.AddSubscriber(ch, client) + } <-ss.Context().Done() return nil diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index ae99e92..1cce33f 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -181,9 +181,10 @@ func (m *messageCase) SubscribeUserToAllChats(ctx context.Context, userID int) ( return nil, ErrRealTimeDisable } - subClient, err := m.rtClient.Subscribe(ctx, &rt.Channel{ - Name: strconv.Itoa(userID), - Topic: _topicChat, + subClient, err := m.rtClient.Subscribe(ctx, &rt.Channels{ + Chans: []*rt.Channel{ + {Name: strconv.Itoa(userID), Topic: _topicChat}, + }, }) if err != nil { return nil, fmt.Errorf("subscribe: %w", err) From 9538afaec9e84f11e52801a9fc19eee4c48dd64d Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 14 Dec 2023 16:54:14 +0300 Subject: [PATCH 239/266] TP-1c3 update: chat --- internal/app/app.go | 6 +- internal/pkg/delivery/websocket/chat.go | 38 +- internal/pkg/delivery/websocket/socket.go | 37 ++ internal/pkg/delivery/websocket/types.go | 14 +- .../pkg/delivery/websocket/types_easyjson.go | 451 ++++++++++++++++++ internal/pkg/entity/message/message.go | 2 +- .../pkg/entity/message/message_easyjson.go | 14 +- .../pkg/usecase/message/mock/message_mock.go | 24 +- internal/pkg/usecase/message/usecase.go | 103 ++-- internal/pkg/usecase/realtime/chat/chat.go | 114 +++++ internal/pkg/usecase/realtime/realtime.go | 102 ++++ 11 files changed, 800 insertions(+), 105 deletions(-) create mode 100644 internal/pkg/delivery/websocket/socket.go create mode 100644 internal/pkg/delivery/websocket/types_easyjson.go create mode 100644 internal/pkg/usecase/realtime/chat/chat.go create mode 100644 internal/pkg/usecase/realtime/realtime.go diff --git a/internal/app/app.go b/internal/app/app.go index 800466a..83a1b94 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -30,6 +30,8 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime/chat" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/search" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -74,8 +76,10 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer connRealtime.Close() + rtClient := rt.NewRealTimeClient(connRealtime) + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) - messageCase := message.New(messenger.NewMessengerClient(connMessMS), rt.NewRealTimeClient(connRealtime), log, true) + messageCase := message.New(log, messenger.NewMessengerClient(connMessMS), chat.New(realtime.NewRealTimeChatClient(rtClient), log)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) conn, err := grpc.Dial(cfg.AddrAuthServer, grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/internal/pkg/delivery/websocket/chat.go b/internal/pkg/delivery/websocket/chat.go index 7c8e098..8d0ccd4 100644 --- a/internal/pkg/delivery/websocket/chat.go +++ b/internal/pkg/delivery/websocket/chat.go @@ -6,7 +6,6 @@ import ( "net/http" ws "nhooyr.io/websocket" - "nhooyr.io/websocket/wsjson" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" ) @@ -25,67 +24,70 @@ func (h *HandlerWebSocket) Chat(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) defer cancel() - err = h.subscribeOnChat(ctx, conn, userID) + socket := newSocketJSON(conn) + + err = h.subscribeOnChat(ctx, socket, userID) if err != nil { h.log.Error(err.Error()) conn.Close(ws.StatusInternalError, "subscribe_fail") return } - err = h.serveChat(ctx, conn, userID) + err = h.serveChat(ctx, socket, userID) if err != nil && ws.CloseStatus(err) == -1 { h.log.Error(err.Error()) conn.Close(ws.StatusInternalError, "serve_chat") } } -func (h *HandlerWebSocket) serveChat(ctx context.Context, conn *ws.Conn, userID int) error { +func (h *HandlerWebSocket) serveChat(ctx context.Context, rw CtxReadWriter, userID int) error { request := &PublsihRequest{} var err error for { - err = wsjson.Read(ctx, conn, request) + err = rw.Read(ctx, request) if err != nil { h.log.Error(err.Error()) return fmt.Errorf("read message: %w", err) } - h.handlePublishRequestMessage(ctx, conn, userID, request) + h.handlePublishRequestMessage(ctx, rw, userID, request) } } -func (h *HandlerWebSocket) handlePublishRequestMessage(ctx context.Context, conn *ws.Conn, userID int, r *PublsihRequest) { - switch r.Message.Type { +func (h *HandlerWebSocket) handlePublishRequestMessage(ctx context.Context, w CtxWriter, userID int, req *PublsihRequest) { + fmt.Println(req) + switch req.Message.Type { case "create": - r.Message.Message.From = userID - id, err := h.messageCase.SendMessage(ctx, userID, &r.Message.Message) + req.Message.Message.From = userID + id, err := h.messageCase.SendMessage(ctx, userID, &req.Message.Message) if err != nil { h.log.Warn(err.Error()) return } - wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) + w.Write(ctx, newResponseOnRequest(req.ID, "ok", "", "publish success", map[string]any{"id": id, "eventType": "create"})) case "update": - err := h.messageCase.UpdateContentMessage(ctx, userID, &r.Message.Message) + err := h.messageCase.UpdateContentMessage(ctx, userID, &req.Message.Message) if err != nil { h.log.Warn(err.Error()) return } - wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) + w.Write(ctx, newResponseOnRequest(req.ID, "ok", "", "publish success", map[string]string{"eventType": "update"})) case "delete": - err := h.messageCase.DeleteMessage(ctx, userID, &r.Message.Message) + err := h.messageCase.DeleteMessage(ctx, userID, &req.Message.Message) if err != nil { h.log.Warn(err.Error()) return } - wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) + w.Write(ctx, newResponseOnRequest(req.ID, "ok", "", "publish success", map[string]string{"eventType": "delete"})) default: - wsjson.Write(ctx, conn, newResponseOnRequest(r.ID, "error", "unsupported", "unsupported eventType", nil)) + w.Write(ctx, newResponseOnRequest(req.ID, "error", "unsupported", "unsupported eventType", nil)) } } -func (h *HandlerWebSocket) subscribeOnChat(ctx context.Context, conn *ws.Conn, userID int) error { +func (h *HandlerWebSocket) subscribeOnChat(ctx context.Context, w CtxWriter, userID int) error { chanEvMsg, err := h.messageCase.SubscribeUserToAllChats(ctx, userID) if err != nil { return fmt.Errorf("subscribe user on chat: %w", err) @@ -98,7 +100,7 @@ func (h *HandlerWebSocket) subscribeOnChat(ctx context.Context, conn *ws.Conn, u return } - err = wsjson.Write(ctx, conn, newMessageFromChannel("ok", "", Object{ + err = w.Write(ctx, newMessageFromChannel("ok", "", Object{ Type: eventMessage.Type, Message: *eventMessage.Message, })) diff --git a/internal/pkg/delivery/websocket/socket.go b/internal/pkg/delivery/websocket/socket.go new file mode 100644 index 0000000..4269c6a --- /dev/null +++ b/internal/pkg/delivery/websocket/socket.go @@ -0,0 +1,37 @@ +package websocket + +import ( + "context" + + ws "nhooyr.io/websocket" + "nhooyr.io/websocket/wsjson" +) + +type CtxReader interface { + Read(ctx context.Context, v any) error +} + +type CtxWriter interface { + Write(ctx context.Context, v any) error +} + +type CtxReadWriter interface { + CtxReader + CtxWriter +} + +type socketJSON struct { + *ws.Conn +} + +func newSocketJSON(conn *ws.Conn) socketJSON { + return socketJSON{conn} +} + +func (s socketJSON) Write(ctx context.Context, v any) error { + return wsjson.Write(ctx, s.Conn, v) +} + +func (s socketJSON) Read(ctx context.Context, v any) error { + return wsjson.Read(ctx, s.Conn, v) +} diff --git a/internal/pkg/delivery/websocket/types.go b/internal/pkg/delivery/websocket/types.go index 0f0572a..9250d15 100644 --- a/internal/pkg/delivery/websocket/types.go +++ b/internal/pkg/delivery/websocket/types.go @@ -2,14 +2,7 @@ package websocket import "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" -const ( - _topicChat = "chat" - _topicNotification = "notification" -) - -type Channel struct { - Name string `json:"name"` -} +//go:generate easyjson --all type Object struct { Type string `json:"eventType,omitempty"` @@ -17,9 +10,8 @@ type Object struct { } type PublsihRequest struct { - ID int `json:"requestID"` - Channel Channel - Message Object + ID int `json:"requestID"` + Message Object `json:"message"` } type MessageFromChannel struct { diff --git a/internal/pkg/delivery/websocket/types_easyjson.go b/internal/pkg/delivery/websocket/types_easyjson.go new file mode 100644 index 0000000..a31d28b --- /dev/null +++ b/internal/pkg/delivery/websocket/types_easyjson.go @@ -0,0 +1,451 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package websocket + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(in *jlexer.Lexer, out *ResponseOnRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "requestID": + out.ID = int(in.Int()) + case "type": + out.Type = string(in.String()) + case "status": + out.Status = string(in.String()) + case "code": + out.Code = string(in.String()) + case "message": + out.Message = string(in.String()) + case "body": + if m, ok := out.Body.(easyjson.Unmarshaler); ok { + m.UnmarshalEasyJSON(in) + } else if m, ok := out.Body.(json.Unmarshaler); ok { + _ = m.UnmarshalJSON(in.Raw()) + } else { + out.Body = in.Interface() + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(out *jwriter.Writer, in ResponseOnRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"requestID\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"type\":" + out.RawString(prefix) + out.String(string(in.Type)) + } + { + const prefix string = ",\"status\":" + out.RawString(prefix) + out.String(string(in.Status)) + } + if in.Code != "" { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.String(string(in.Code)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + out.String(string(in.Message)) + } + if in.Body != nil { + const prefix string = ",\"body\":" + out.RawString(prefix) + if m, ok := in.Body.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := in.Body.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(in.Body)) + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ResponseOnRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ResponseOnRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ResponseOnRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ResponseOnRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(in *jlexer.Lexer, out *ResponseMessage) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "status": + out.Status = string(in.String()) + case "code": + out.Code = string(in.String()) + case "messageText": + out.MessageText = string(in.String()) + case "eventType": + out.Type = string(in.String()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(out *jwriter.Writer, in ResponseMessage) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + if in.Code != "" { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.String(string(in.Code)) + } + if in.MessageText != "" { + const prefix string = ",\"messageText\":" + out.RawString(prefix) + out.String(string(in.MessageText)) + } + if in.Type != "" { + const prefix string = ",\"eventType\":" + out.RawString(prefix) + out.String(string(in.Type)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ResponseMessage) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ResponseMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ResponseMessage) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ResponseMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(in *jlexer.Lexer, out *PublsihRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "requestID": + out.ID = int(in.Int()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(out *jwriter.Writer, in PublsihRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"requestID\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v PublsihRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v PublsihRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *PublsihRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *PublsihRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(in *jlexer.Lexer, out *Object) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "eventType": + out.Type = string(in.String()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(out *jwriter.Writer, in Object) { + out.RawByte('{') + first := true + _ = first + if in.Type != "" { + const prefix string = ",\"eventType\":" + first = false + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"message\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Object) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Object) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Object) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Object) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(in *jlexer.Lexer, out *MessageFromChannel) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "type": + out.Type = string(in.String()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(out *jwriter.Writer, in MessageFromChannel) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"type\":" + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v MessageFromChannel) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v MessageFromChannel) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *MessageFromChannel) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *MessageFromChannel) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(l, v) +} diff --git a/internal/pkg/entity/message/message.go b/internal/pkg/entity/message/message.go index b79a2a2..ba42c98 100644 --- a/internal/pkg/entity/message/message.go +++ b/internal/pkg/entity/message/message.go @@ -12,7 +12,7 @@ type Chat [2]int //easyjson:json type Message struct { - ID int + ID int `json:"id,omitempty"` From int `json:"from"` To int `json:"to"` Content pgtype.Text `json:"content"` diff --git a/internal/pkg/entity/message/message_easyjson.go b/internal/pkg/entity/message/message_easyjson.go index 83e16f5..91a2ec5 100644 --- a/internal/pkg/entity/message/message_easyjson.go +++ b/internal/pkg/entity/message/message_easyjson.go @@ -36,7 +36,7 @@ func easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMes continue } switch key { - case "ID": + case "id": out.ID = int(in.Int()) case "from": out.From = int(in.Int()) @@ -60,14 +60,20 @@ func easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityMes out.RawByte('{') first := true _ = first - { - const prefix string = ",\"ID\":" + if in.ID != 0 { + const prefix string = ",\"id\":" + first = false out.RawString(prefix[1:]) out.Int(int(in.ID)) } { const prefix string = ",\"from\":" - out.RawString(prefix) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } out.Int(int(in.From)) } { diff --git a/internal/pkg/usecase/message/mock/message_mock.go b/internal/pkg/usecase/message/mock/message_mock.go index 18f0333..3b6d816 100644 --- a/internal/pkg/usecase/message/mock/message_mock.go +++ b/internal/pkg/usecase/message/mock/message_mock.go @@ -9,6 +9,7 @@ import ( reflect "reflect" message "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + message0 "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" gomock "github.com/golang/mock/gomock" ) @@ -36,17 +37,17 @@ func (m *MockUsecase) EXPECT() *MockUsecaseMockRecorder { } // DeleteMessage mocks base method. -func (m *MockUsecase) DeleteMessage(ctx context.Context, userID, mesID int) error { +func (m *MockUsecase) DeleteMessage(ctx context.Context, userID int, mes *message.Message) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteMessage", ctx, userID, mesID) + ret := m.ctrl.Call(m, "DeleteMessage", ctx, userID, mes) ret0, _ := ret[0].(error) return ret0 } // DeleteMessage indicates an expected call of DeleteMessage. -func (mr *MockUsecaseMockRecorder) DeleteMessage(ctx, userID, mesID interface{}) *gomock.Call { +func (mr *MockUsecaseMockRecorder) DeleteMessage(ctx, userID, mes interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockUsecase)(nil).DeleteMessage), ctx, userID, mesID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockUsecase)(nil).DeleteMessage), ctx, userID, mes) } // GetMessage mocks base method. @@ -111,6 +112,21 @@ func (mr *MockUsecaseMockRecorder) SendMessage(ctx, userID, mes interface{}) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockUsecase)(nil).SendMessage), ctx, userID, mes) } +// SubscribeUserToAllChats mocks base method. +func (m *MockUsecase) SubscribeUserToAllChats(ctx context.Context, userID int) (<-chan message0.EventMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubscribeUserToAllChats", ctx, userID) + ret0, _ := ret[0].(<-chan message0.EventMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubscribeUserToAllChats indicates an expected call of SubscribeUserToAllChats. +func (mr *MockUsecaseMockRecorder) SubscribeUserToAllChats(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeUserToAllChats", reflect.TypeOf((*MockUsecase)(nil).SubscribeUserToAllChats), ctx, userID) +} + // UpdateContentMessage mocks base method. func (m *MockUsecase) UpdateContentMessage(ctx context.Context, userID int, mes *message.Message) error { m.ctrl.T.Helper() diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 1cce33f..552d8c8 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -10,10 +10,9 @@ import ( "google.golang.org/grpc/metadata" mess "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" - rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" messMS "github.com/go-park-mail-ru/2023_2_OND_team/internal/microservices/messenger/delivery/grpc" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime/chat" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -46,18 +45,23 @@ func makeErrEventMessage(err error) EventMessage { type messageCase struct { client mess.MessengerClient - rtClient rt.RealTimeClient + realtimeChatCase chat.Usecase log *logger.Logger realtimeIsEnable bool } -func New(cl mess.MessengerClient, rtClient rt.RealTimeClient, log *logger.Logger, rtEnable bool) *messageCase { - return &messageCase{ - client: cl, - rtClient: rtClient, - log: log, - realtimeIsEnable: rtEnable, +func New(log *logger.Logger, cl mess.MessengerClient, rtChatCase chat.Usecase) *messageCase { + m := &messageCase{ + client: cl, + log: log, } + + if rtChatCase != nil { + m.realtimeChatCase = rtChatCase + m.realtimeIsEnable = true + } + + return m } func (m *messageCase) SendMessage(ctx context.Context, userID int, mes *entity.Message) (int, error) { @@ -70,7 +74,9 @@ func (m *messageCase) SendMessage(ctx context.Context, userID int, mes *entity.M return 0, fmt.Errorf("send message by grpc client") } - m.publishToRealTimeServer(ctx, strconv.Itoa(mes.To), int(msgID.GetId()), rt.EventType_EV_CREATE) + if m.realtimeIsEnable { + go m.realtimeChatCase.PublishNewMessage(ctx, mes.To, int(msgID.GetId())) + } return int(msgID.GetId()), nil } @@ -104,7 +110,9 @@ func (m *messageCase) UpdateContentMessage(ctx context.Context, userID int, mes return fmt.Errorf("update messege by grpc client") } - m.publishToRealTimeServer(ctx, strconv.Itoa(mes.To), mes.ID, rt.EventType_EV_UPDATE) + if m.realtimeIsEnable { + go m.realtimeChatCase.PublishUpdateMessage(ctx, mes.To, mes.ID) + } return nil } @@ -114,7 +122,9 @@ func (m *messageCase) DeleteMessage(ctx context.Context, userID int, mes *entity return fmt.Errorf("delete messege by grpc client") } - m.publishToRealTimeServer(ctx, strconv.Itoa(mes.To), mes.ID, rt.EventType_EV_DELETE) + if m.realtimeIsEnable { + go m.realtimeChatCase.PublishDeleteMessage(ctx, mes.To, mes.ID) + } return nil } @@ -150,42 +160,12 @@ func (m *messageCase) GetUserChatsWithOtherUsers(ctx context.Context, userID, co return convertFeedChat(feed), int(feed.GetLastID()), errRes } -func (m *messageCase) publishToRealTimeServer(ctx context.Context, channelName string, idMsg int, t rt.EventType) { - if !m.realtimeIsEnable { - return - } - - go func() { - _, err := m.rtClient.Publish(ctx, &rt.PublishMessage{ - Channel: &rt.Channel{ - Name: channelName, - Topic: _topicChat, - }, - Message: &rt.Message{ - Body: &rt.Message_Object{ - Object: &rt.EventObject{ - Type: t, - Id: int64(idMsg), - }, - }, - }, - }) - if err != nil { - m.log.Error(err.Error()) - } - }() -} - func (m *messageCase) SubscribeUserToAllChats(ctx context.Context, userID int) (<-chan EventMessage, error) { if !m.realtimeIsEnable { return nil, ErrRealTimeDisable } - subClient, err := m.rtClient.Subscribe(ctx, &rt.Channels{ - Chans: []*rt.Channel{ - {Name: strconv.Itoa(userID), Topic: _topicChat}, - }, - }) + subClient, err := m.realtimeChatCase.SubscribeUserToAllChats(ctx, userID) if err != nil { return nil, fmt.Errorf("subscribe: %w", err) } @@ -195,40 +175,31 @@ func (m *messageCase) SubscribeUserToAllChats(ctx context.Context, userID int) ( return chanEvMsg, nil } -func (m *messageCase) receiveFromSubClient(ctx context.Context, userID int, subClient rt.RealTime_SubscribeClient, chanEvMsg chan<- EventMessage) { +func (m *messageCase) receiveFromSubClient(ctx context.Context, userID int, subClient <-chan chat.EventMessageObjectID, chanEvMsg chan<- EventMessage) { defer close(chanEvMsg) - evMsg := EventMessage{} - for { - obj, err := subClient.Recv() - if err != nil { - chanEvMsg <- makeErrEventMessage(fmt.Errorf("receive from subcribtion client: %w", err)) - return - } - mes, ok := obj.Body.(*rt.Message_Object) - if !ok { - chanEvMsg <- makeErrEventMessage(ErrUnknowObj) + var ( + evMsg EventMessage + err error + ) + for msgObjID := range subClient { + if msgObjID.Err != nil { + chanEvMsg <- makeErrEventMessage(fmt.Errorf("receive from subcribtion client: %w", msgObjID.Err)) return } - if mes.Object.Type == rt.EventType_EV_DELETE { - evMsg.Message = &message.Message{ID: int(mes.Object.Id)} + evMsg = EventMessage{ + Type: msgObjID.Type, + } + if evMsg.Type == "delete" { + evMsg.Message = &entity.Message{ID: msgObjID.MessageID} } else { - evMsg.Message, err = m.GetMessage(ctx, userID, int(mes.Object.Id)) + evMsg.Message, err = m.GetMessage(ctx, userID, msgObjID.MessageID) if err != nil { m.log.Error(err.Error()) } } - switch mes.Object.Type { - case rt.EventType_EV_CREATE: - evMsg.Type = "create" - case rt.EventType_EV_UPDATE: - evMsg.Type = "update" - case rt.EventType_EV_DELETE: - evMsg.Type = "delete" - } - chanEvMsg <- evMsg } } diff --git a/internal/pkg/usecase/realtime/chat/chat.go b/internal/pkg/usecase/realtime/chat/chat.go new file mode 100644 index 0000000..eefeccd --- /dev/null +++ b/internal/pkg/usecase/realtime/chat/chat.go @@ -0,0 +1,114 @@ +package chat + +import ( + "context" + "fmt" + "strconv" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +type EventMessageObjectID struct { + Type string + MessageID int + Err error +} + +func makeErrEventMessageObjectID(err error) EventMessageObjectID { + return EventMessageObjectID{Err: err} +} + +type Usecase interface { + PublishNewMessage(ctx context.Context, userToWhom, msgID int) error + PublishUpdateMessage(ctx context.Context, userToWhom, msgID int) error + PublishDeleteMessage(ctx context.Context, userToWhom, msgID int) error + SubscribeUserToAllChats(ctx context.Context, userID int) (<-chan EventMessageObjectID, error) +} + +type realtimeCase struct { + client realtime.RealTimeClient + log *logger.Logger +} + +func New(client realtime.RealTimeClient, log *logger.Logger) *realtimeCase { + return &realtimeCase{client, log} +} + +func (r *realtimeCase) PublishNewMessage(ctx context.Context, userToWhom, msgID int) error { + err := r.publishMessage(ctx, userToWhom, msgID, rt.EventType_EV_CREATE) + if err != nil { + r.log.Error(err.Error()) + return fmt.Errorf("publish new message: %w", err) + } + return nil +} + +func (r *realtimeCase) PublishUpdateMessage(ctx context.Context, userToWhom, msgID int) error { + err := r.publishMessage(ctx, userToWhom, msgID, rt.EventType_EV_UPDATE) + if err != nil { + r.log.Error(err.Error()) + return fmt.Errorf("publish update message: %w", err) + } + return nil +} + +func (r *realtimeCase) PublishDeleteMessage(ctx context.Context, userToWhom, msgID int) error { + err := r.publishMessage(ctx, userToWhom, msgID, rt.EventType_EV_DELETE) + if err != nil { + r.log.Error(err.Error()) + return fmt.Errorf("publish delete message: %w", err) + } + return nil +} + +func (r *realtimeCase) SubscribeUserToAllChats(ctx context.Context, userToWhom int) (<-chan EventMessageObjectID, error) { + chPack, err := r.client.Subscribe(ctx, []string{strconv.Itoa(userToWhom)}) + if err != nil { + return nil, fmt.Errorf("subscribe user to all chats: %w", err) + } + + chanEvMsg := make(chan EventMessageObjectID) + go r.receiveFromSubClient(ctx, chPack, chanEvMsg) + + return chanEvMsg, nil +} + +func (r *realtimeCase) receiveFromSubClient(ctx context.Context, subClient <-chan realtime.Pack, chanEvMsg chan<- EventMessageObjectID) { + defer close(chanEvMsg) + + for pack := range subClient { + if pack.Err != nil { + chanEvMsg <- makeErrEventMessageObjectID(pack.Err) + return + } + + msg, ok := pack.Body.(*rt.Message_Object) + if !ok { + chanEvMsg <- makeErrEventMessageObjectID(realtime.ErrUnknowTypeObject) + return + } + + evMsgID := EventMessageObjectID{MessageID: int(msg.Object.GetId())} + switch msg.Object.GetType() { + case rt.EventType_EV_CREATE: + evMsgID.Type = "create" + case rt.EventType_EV_UPDATE: + evMsgID.Type = "update" + case rt.EventType_EV_DELETE: + evMsgID.Type = "delete" + } + + chanEvMsg <- evMsgID + } +} + +func (r *realtimeCase) publishMessage(ctx context.Context, userID, msgID int, t rt.EventType) error { + return r.client.Publish(ctx, strconv.Itoa(userID), &rt.Message_Object{ + Object: &rt.EventObject{ + Type: t, + Id: int64(msgID), + }, + }) +} diff --git a/internal/pkg/usecase/realtime/realtime.go b/internal/pkg/usecase/realtime/realtime.go new file mode 100644 index 0000000..e5c00df --- /dev/null +++ b/internal/pkg/usecase/realtime/realtime.go @@ -0,0 +1,102 @@ +package realtime + +import ( + "context" + "errors" + "fmt" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" +) + +var ErrUnknowTypeObject = errors.New("unknow type") + +const ( + _topicChat = "chat" + _topicNotification = "notification" +) + +type RealTimeClient interface { + Subscribe(ctx context.Context, nameChans []string) (<-chan Pack, error) + Publish(ctx context.Context, chanName string, object any) error +} + +type Pack struct { + Body any + Err error +} + +type realtimeClient struct { + client rt.RealTimeClient + topic string +} + +func NewRealTimeChatClient(client rt.RealTimeClient) realtimeClient { + return realtimeClient{ + client: client, + topic: _topicChat, + } +} + +func (r realtimeClient) Publish(ctx context.Context, chanName string, object any) error { + pubMsg := &rt.PublishMessage{ + Channel: &rt.Channel{ + Topic: r.topic, + Name: chanName, + }, + Message: &rt.Message{}, + } + + switch body := object.(type) { + case *rt.Message_Object: + pubMsg.Message.Body = body + case *rt.Message_Content: + pubMsg.Message.Body = body + default: + return ErrUnknowTypeObject + } + + _, err := r.client.Publish(ctx, pubMsg) + if err != nil { + return fmt.Errorf("publish as a realtime client: %w", err) + } + return nil +} + +func (r realtimeClient) Subscribe(ctx context.Context, nameChans []string) (<-chan Pack, error) { + chans := &rt.Channels{ + Chans: make([]*rt.Channel, len(nameChans)), + } + + for _, name := range nameChans { + chans.Chans = append(chans.Chans, &rt.Channel{Topic: r.topic, Name: name}) + } + + subClient, err := r.client.Subscribe(ctx, chans) + if err != nil { + return nil, fmt.Errorf("subscribe as a realtime client: %w", err) + } + + ch := make(chan Pack) + go runServeSubscribeClient(subClient, ch) + + return ch, nil +} + +func runServeSubscribeClient(client rt.RealTime_SubscribeClient, ch chan<- Pack) { + defer close(ch) + + var ( + mes *rt.Message + err error + ) + + for { + mes, err = client.Recv() + if err != nil { + ch <- Pack{Err: err} + return + } + + ch <- Pack{Body: mes.GetBody()} + } +} From bf36ddcfa485aee7f786c64d7072914fed6289fa Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 14 Dec 2023 17:02:04 +0300 Subject: [PATCH 240/266] TP-1c3 update: rename request publish struct --- internal/pkg/delivery/websocket/chat.go | 4 +- internal/pkg/delivery/websocket/types.go | 2 +- .../pkg/delivery/websocket/types_easyjson.go | 467 ++---------------- 3 files changed, 33 insertions(+), 440 deletions(-) diff --git a/internal/pkg/delivery/websocket/chat.go b/internal/pkg/delivery/websocket/chat.go index 8d0ccd4..f7719e4 100644 --- a/internal/pkg/delivery/websocket/chat.go +++ b/internal/pkg/delivery/websocket/chat.go @@ -41,7 +41,7 @@ func (h *HandlerWebSocket) Chat(w http.ResponseWriter, r *http.Request) { } func (h *HandlerWebSocket) serveChat(ctx context.Context, rw CtxReadWriter, userID int) error { - request := &PublsihRequest{} + request := &PublishRequest{} var err error for { err = rw.Read(ctx, request) @@ -54,7 +54,7 @@ func (h *HandlerWebSocket) serveChat(ctx context.Context, rw CtxReadWriter, user } } -func (h *HandlerWebSocket) handlePublishRequestMessage(ctx context.Context, w CtxWriter, userID int, req *PublsihRequest) { +func (h *HandlerWebSocket) handlePublishRequestMessage(ctx context.Context, w CtxWriter, userID int, req *PublishRequest) { fmt.Println(req) switch req.Message.Type { case "create": diff --git a/internal/pkg/delivery/websocket/types.go b/internal/pkg/delivery/websocket/types.go index 9250d15..1271683 100644 --- a/internal/pkg/delivery/websocket/types.go +++ b/internal/pkg/delivery/websocket/types.go @@ -9,7 +9,7 @@ type Object struct { Message message.Message `json:"message"` } -type PublsihRequest struct { +type PublishRequest struct { ID int `json:"requestID"` Message Object `json:"message"` } diff --git a/internal/pkg/delivery/websocket/types_easyjson.go b/internal/pkg/delivery/websocket/types_easyjson.go index a31d28b..b0276db 100644 --- a/internal/pkg/delivery/websocket/types_easyjson.go +++ b/internal/pkg/delivery/websocket/types_easyjson.go @@ -1,451 +1,44 @@ -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. +// TEMPORARY AUTOGENERATED FILE: easyjson stub code to make the package +// compilable during generation. -package websocket +package websocket import ( - json "encoding/json" - easyjson "github.com/mailru/easyjson" - jlexer "github.com/mailru/easyjson/jlexer" - jwriter "github.com/mailru/easyjson/jwriter" + "github.com/mailru/easyjson/jwriter" + "github.com/mailru/easyjson/jlexer" ) -// suppress unused package warning -var ( - _ *json.RawMessage - _ *jlexer.Lexer - _ *jwriter.Writer - _ easyjson.Marshaler -) - -func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(in *jlexer.Lexer, out *ResponseOnRequest) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "requestID": - out.ID = int(in.Int()) - case "type": - out.Type = string(in.String()) - case "status": - out.Status = string(in.String()) - case "code": - out.Code = string(in.String()) - case "message": - out.Message = string(in.String()) - case "body": - if m, ok := out.Body.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := out.Body.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - out.Body = in.Interface() - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(out *jwriter.Writer, in ResponseOnRequest) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"requestID\":" - out.RawString(prefix[1:]) - out.Int(int(in.ID)) - } - { - const prefix string = ",\"type\":" - out.RawString(prefix) - out.String(string(in.Type)) - } - { - const prefix string = ",\"status\":" - out.RawString(prefix) - out.String(string(in.Status)) - } - if in.Code != "" { - const prefix string = ",\"code\":" - out.RawString(prefix) - out.String(string(in.Code)) - } - { - const prefix string = ",\"message\":" - out.RawString(prefix) - out.String(string(in.Message)) - } - if in.Body != nil { - const prefix string = ",\"body\":" - out.RawString(prefix) - if m, ok := in.Body.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := in.Body.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(in.Body)) - } - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v ResponseOnRequest) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v ResponseOnRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *ResponseOnRequest) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ResponseOnRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(l, v) -} -func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(in *jlexer.Lexer, out *ResponseMessage) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "status": - out.Status = string(in.String()) - case "code": - out.Code = string(in.String()) - case "messageText": - out.MessageText = string(in.String()) - case "eventType": - out.Type = string(in.String()) - case "message": - (out.Message).UnmarshalEasyJSON(in) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(out *jwriter.Writer, in ResponseMessage) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"status\":" - out.RawString(prefix[1:]) - out.String(string(in.Status)) - } - if in.Code != "" { - const prefix string = ",\"code\":" - out.RawString(prefix) - out.String(string(in.Code)) - } - if in.MessageText != "" { - const prefix string = ",\"messageText\":" - out.RawString(prefix) - out.String(string(in.MessageText)) - } - if in.Type != "" { - const prefix string = ",\"eventType\":" - out.RawString(prefix) - out.String(string(in.Type)) - } - { - const prefix string = ",\"message\":" - out.RawString(prefix) - (in.Message).MarshalEasyJSON(out) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v ResponseMessage) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v ResponseMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *ResponseMessage) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ResponseMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(l, v) -} -func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(in *jlexer.Lexer, out *PublsihRequest) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "requestID": - out.ID = int(in.Int()) - case "message": - (out.Message).UnmarshalEasyJSON(in) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(out *jwriter.Writer, in PublsihRequest) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"requestID\":" - out.RawString(prefix[1:]) - out.Int(int(in.ID)) - } - { - const prefix string = ",\"message\":" - out.RawString(prefix) - (in.Message).MarshalEasyJSON(out) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v PublsihRequest) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v PublsihRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *PublsihRequest) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(&r, v) - return r.Error() -} +func ( MessageFromChannel ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* MessageFromChannel ) UnmarshalJSON([]byte) error { return nil } +func ( MessageFromChannel ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* MessageFromChannel ) UnmarshalEasyJSON(l *jlexer.Lexer) {} -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *PublsihRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(l, v) -} -func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(in *jlexer.Lexer, out *Object) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "eventType": - out.Type = string(in.String()) - case "message": - (out.Message).UnmarshalEasyJSON(in) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(out *jwriter.Writer, in Object) { - out.RawByte('{') - first := true - _ = first - if in.Type != "" { - const prefix string = ",\"eventType\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Type)) - } - { - const prefix string = ",\"message\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - (in.Message).MarshalEasyJSON(out) - } - out.RawByte('}') -} +type EasyJSON_exporter_MessageFromChannel *MessageFromChannel -// MarshalJSON supports json.Marshaler interface -func (v Object) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(&w, v) - return w.Buffer.BuildBytes(), w.Error -} +func ( Object ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* Object ) UnmarshalJSON([]byte) error { return nil } +func ( Object ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* Object ) UnmarshalEasyJSON(l *jlexer.Lexer) {} -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v Object) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(w, v) -} +type EasyJSON_exporter_Object *Object -// UnmarshalJSON supports json.Unmarshaler interface -func (v *Object) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(&r, v) - return r.Error() -} +func ( PublishRequest ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* PublishRequest ) UnmarshalJSON([]byte) error { return nil } +func ( PublishRequest ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* PublishRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *Object) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(l, v) -} -func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(in *jlexer.Lexer, out *MessageFromChannel) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "type": - out.Type = string(in.String()) - case "message": - (out.Message).UnmarshalEasyJSON(in) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(out *jwriter.Writer, in MessageFromChannel) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"type\":" - out.RawString(prefix[1:]) - out.String(string(in.Type)) - } - { - const prefix string = ",\"message\":" - out.RawString(prefix) - (in.Message).MarshalEasyJSON(out) - } - out.RawByte('}') -} +type EasyJSON_exporter_PublishRequest *PublishRequest -// MarshalJSON supports json.Marshaler interface -func (v MessageFromChannel) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(&w, v) - return w.Buffer.BuildBytes(), w.Error -} +func ( ResponseMessage ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* ResponseMessage ) UnmarshalJSON([]byte) error { return nil } +func ( ResponseMessage ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* ResponseMessage ) UnmarshalEasyJSON(l *jlexer.Lexer) {} -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v MessageFromChannel) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(w, v) -} +type EasyJSON_exporter_ResponseMessage *ResponseMessage -// UnmarshalJSON supports json.Unmarshaler interface -func (v *MessageFromChannel) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(&r, v) - return r.Error() -} +func ( ResponseOnRequest ) MarshalJSON() ([]byte, error) { return nil, nil } +func (* ResponseOnRequest ) UnmarshalJSON([]byte) error { return nil } +func ( ResponseOnRequest ) MarshalEasyJSON(w *jwriter.Writer) {} +func (* ResponseOnRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *MessageFromChannel) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(l, v) -} +type EasyJSON_exporter_ResponseOnRequest *ResponseOnRequest From 4626d241778dedd9bafb05235bde2df52aee8910 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 14 Dec 2023 17:03:59 +0300 Subject: [PATCH 241/266] TP-1c3 update: regenerate --- .../pkg/delivery/websocket/types_easyjson.go | 467 ++++++++++++++++-- 1 file changed, 437 insertions(+), 30 deletions(-) diff --git a/internal/pkg/delivery/websocket/types_easyjson.go b/internal/pkg/delivery/websocket/types_easyjson.go index b0276db..11db088 100644 --- a/internal/pkg/delivery/websocket/types_easyjson.go +++ b/internal/pkg/delivery/websocket/types_easyjson.go @@ -1,44 +1,451 @@ -// TEMPORARY AUTOGENERATED FILE: easyjson stub code to make the package -// compilable during generation. +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. -package websocket +package websocket import ( - "github.com/mailru/easyjson/jwriter" - "github.com/mailru/easyjson/jlexer" + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" ) -func ( MessageFromChannel ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* MessageFromChannel ) UnmarshalJSON([]byte) error { return nil } -func ( MessageFromChannel ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* MessageFromChannel ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(in *jlexer.Lexer, out *ResponseOnRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "requestID": + out.ID = int(in.Int()) + case "type": + out.Type = string(in.String()) + case "status": + out.Status = string(in.String()) + case "code": + out.Code = string(in.String()) + case "message": + out.Message = string(in.String()) + case "body": + if m, ok := out.Body.(easyjson.Unmarshaler); ok { + m.UnmarshalEasyJSON(in) + } else if m, ok := out.Body.(json.Unmarshaler); ok { + _ = m.UnmarshalJSON(in.Raw()) + } else { + out.Body = in.Interface() + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(out *jwriter.Writer, in ResponseOnRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"requestID\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"type\":" + out.RawString(prefix) + out.String(string(in.Type)) + } + { + const prefix string = ",\"status\":" + out.RawString(prefix) + out.String(string(in.Status)) + } + if in.Code != "" { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.String(string(in.Code)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + out.String(string(in.Message)) + } + if in.Body != nil { + const prefix string = ",\"body\":" + out.RawString(prefix) + if m, ok := in.Body.(easyjson.Marshaler); ok { + m.MarshalEasyJSON(out) + } else if m, ok := in.Body.(json.Marshaler); ok { + out.Raw(m.MarshalJSON()) + } else { + out.Raw(json.Marshal(in.Body)) + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ResponseOnRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ResponseOnRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ResponseOnRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ResponseOnRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(in *jlexer.Lexer, out *ResponseMessage) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "status": + out.Status = string(in.String()) + case "code": + out.Code = string(in.String()) + case "messageText": + out.MessageText = string(in.String()) + case "eventType": + out.Type = string(in.String()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(out *jwriter.Writer, in ResponseMessage) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"status\":" + out.RawString(prefix[1:]) + out.String(string(in.Status)) + } + if in.Code != "" { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.String(string(in.Code)) + } + if in.MessageText != "" { + const prefix string = ",\"messageText\":" + out.RawString(prefix) + out.String(string(in.MessageText)) + } + if in.Type != "" { + const prefix string = ",\"eventType\":" + out.RawString(prefix) + out.String(string(in.Type)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ResponseMessage) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ResponseMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ResponseMessage) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ResponseMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket1(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(in *jlexer.Lexer, out *PublishRequest) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "requestID": + out.ID = int(in.Int()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(out *jwriter.Writer, in PublishRequest) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"requestID\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v PublishRequest) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v PublishRequest) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *PublishRequest) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(&r, v) + return r.Error() +} -type EasyJSON_exporter_MessageFromChannel *MessageFromChannel +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *PublishRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket2(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(in *jlexer.Lexer, out *Object) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "eventType": + out.Type = string(in.String()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(out *jwriter.Writer, in Object) { + out.RawByte('{') + first := true + _ = first + if in.Type != "" { + const prefix string = ",\"eventType\":" + first = false + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"message\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} -func ( Object ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* Object ) UnmarshalJSON([]byte) error { return nil } -func ( Object ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* Object ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// MarshalJSON supports json.Marshaler interface +func (v Object) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} -type EasyJSON_exporter_Object *Object +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Object) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(w, v) +} -func ( PublishRequest ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* PublishRequest ) UnmarshalJSON([]byte) error { return nil } -func ( PublishRequest ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* PublishRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Object) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(&r, v) + return r.Error() +} -type EasyJSON_exporter_PublishRequest *PublishRequest +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Object) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket3(l, v) +} +func easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(in *jlexer.Lexer, out *MessageFromChannel) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "type": + out.Type = string(in.String()) + case "message": + (out.Message).UnmarshalEasyJSON(in) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(out *jwriter.Writer, in MessageFromChannel) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"type\":" + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"message\":" + out.RawString(prefix) + (in.Message).MarshalEasyJSON(out) + } + out.RawByte('}') +} -func ( ResponseMessage ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* ResponseMessage ) UnmarshalJSON([]byte) error { return nil } -func ( ResponseMessage ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* ResponseMessage ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// MarshalJSON supports json.Marshaler interface +func (v MessageFromChannel) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(&w, v) + return w.Buffer.BuildBytes(), w.Error +} -type EasyJSON_exporter_ResponseMessage *ResponseMessage +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v MessageFromChannel) MarshalEasyJSON(w *jwriter.Writer) { + easyjson6601e8cdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(w, v) +} -func ( ResponseOnRequest ) MarshalJSON() ([]byte, error) { return nil, nil } -func (* ResponseOnRequest ) UnmarshalJSON([]byte) error { return nil } -func ( ResponseOnRequest ) MarshalEasyJSON(w *jwriter.Writer) {} -func (* ResponseOnRequest ) UnmarshalEasyJSON(l *jlexer.Lexer) {} +// UnmarshalJSON supports json.Unmarshaler interface +func (v *MessageFromChannel) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(&r, v) + return r.Error() +} -type EasyJSON_exporter_ResponseOnRequest *ResponseOnRequest +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *MessageFromChannel) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson6601e8cdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryWebsocket4(l, v) +} From 02b3da9c2aec38a2760f7b4f645debd5b1319c32 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 15 Dec 2023 22:23:36 +0300 Subject: [PATCH 242/266] TP-1c3 add: notification after commenting on the pin --- internal/app/app.go | 18 +++- internal/pkg/delivery/http/v1/comment.go | 2 - internal/pkg/delivery/websocket/chat.go | 2 +- .../pkg/delivery/websocket/notification.go | 23 +++- internal/pkg/delivery/websocket/websocket.go | 11 +- internal/pkg/entity/notification/message.go | 26 +++++ .../pkg/entity/notification/notification.go | 86 +++++++++++++++ internal/pkg/entity/notification/template.go | 5 + internal/pkg/entity/notification/type.go | 20 ++++ internal/pkg/notification/comment/comment.go | 57 ++++++++++ internal/pkg/notification/notifier.go | 27 +++++ internal/pkg/repository/comment/queries.go | 6 +- internal/pkg/repository/comment/repo.go | 3 +- internal/pkg/usecase/comment/usecase.go | 36 ++++++- internal/pkg/usecase/pin/usecase.go | 13 ++- internal/pkg/usecase/realtime/chat/chat.go | 2 +- .../usecase/realtime/notification/comment.go | 36 +++++++ .../realtime/notification/notification.go | 101 ++++++++++++++++++ .../usecase/realtime/notification/option.go | 19 ++++ internal/pkg/usecase/realtime/realtime.go | 11 +- 20 files changed, 483 insertions(+), 21 deletions(-) create mode 100644 internal/pkg/entity/notification/message.go create mode 100644 internal/pkg/entity/notification/notification.go create mode 100644 internal/pkg/entity/notification/template.go create mode 100644 internal/pkg/entity/notification/type.go create mode 100644 internal/pkg/notification/comment/comment.go create mode 100644 internal/pkg/notification/notifier.go create mode 100644 internal/pkg/usecase/realtime/notification/comment.go create mode 100644 internal/pkg/usecase/realtime/notification/notification.go create mode 100644 internal/pkg/usecase/realtime/notification/option.go diff --git a/internal/app/app.go b/internal/app/app.go index 83a1b94..57ae57a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -16,7 +16,9 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/server/router" deliveryHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1" deliveryWS "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/websocket" + notify "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/notification" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/metrics" + commentNotify "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/notification/comment" boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board/postgres" commentRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/comment" imgRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" @@ -32,6 +34,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime/chat" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime/notification" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/search" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -78,10 +81,21 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { rtClient := rt.NewRealTimeClient(connRealtime) + commentRepository := commentRepo.NewCommentRepoPG(pool) + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) messageCase := message.New(log, messenger.NewMessengerClient(connMessMS), chat.New(realtime.NewRealTimeChatClient(rtClient), log)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) + notifyBuilder, err := notify.NewWithType(notify.NotifyComment) + if err != nil { + log.Error(err.Error()) + return + } + + notifyCase := notification.New(realtime.NewRealTimeNotificationClient(rtClient), log, + notification.Register(commentNotify.NewCommentNotify(notifyBuilder, comment.New(commentRepository, pinCase, nil), pinCase))) + conn, err := grpc.Dial(cfg.AddrAuthServer, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(err.Error()) @@ -98,10 +112,10 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), SearchCase: search.New(log, searchRepo.NewSearchRepoPG(pool), bluemonday.UGCPolicy()), MessageCase: messageCase, - CommentCase: comment.New(commentRepo.NewCommentRepoPG(pool), pinCase), + CommentCase: comment.New(commentRepo.NewCommentRepoPG(pool), pinCase, notifyCase), }) - wsHandler := deliveryWS.New(log, messageCase, + wsHandler := deliveryWS.New(log, messageCase, notifyCase, deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) diff --git a/internal/pkg/delivery/http/v1/comment.go b/internal/pkg/delivery/http/v1/comment.go index 60e1b8d..d85aa7a 100644 --- a/internal/pkg/delivery/http/v1/comment.go +++ b/internal/pkg/delivery/http/v1/comment.go @@ -1,7 +1,6 @@ package v1 import ( - "fmt" "net/http" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" @@ -26,7 +25,6 @@ func (h *HandlerHTTP) WriteComment(w http.ResponseWriter, r *http.Request) { comment := &comment.Comment{} err = easyjson.UnmarshalFromReader(r.Body, comment) - fmt.Println(comment.Content) defer r.Body.Close() if err != nil { logger.Warn(err.Error()) diff --git a/internal/pkg/delivery/websocket/chat.go b/internal/pkg/delivery/websocket/chat.go index f7719e4..50b0f21 100644 --- a/internal/pkg/delivery/websocket/chat.go +++ b/internal/pkg/delivery/websocket/chat.go @@ -96,7 +96,7 @@ func (h *HandlerWebSocket) subscribeOnChat(ctx context.Context, w CtxWriter, use go func() { for eventMessage := range chanEvMsg { if eventMessage.Err != nil { - h.log.Error(err.Error()) + h.log.Error(eventMessage.Err.Error()) return } diff --git a/internal/pkg/delivery/websocket/notification.go b/internal/pkg/delivery/websocket/notification.go index 878cd30..a24852e 100644 --- a/internal/pkg/delivery/websocket/notification.go +++ b/internal/pkg/delivery/websocket/notification.go @@ -2,6 +2,7 @@ package websocket import ( "context" + "fmt" "net/http" ws "nhooyr.io/websocket" @@ -23,17 +24,31 @@ func (h *HandlerWebSocket) Notification(w http.ResponseWriter, r *http.Request) ctx, cancel := context.WithTimeout(context.Background(), _ctxOnServeConnect) defer cancel() - err = h.subscribeOnNotification(ctx, conn, userID) + socket := newSocketJSON(conn) + + err = h.subscribeOnNotificationAndServe(ctx, socket, userID) if err != nil && ws.CloseStatus(err) == -1 { h.log.Error(err.Error()) conn.Close(ws.StatusInternalError, "subscribe_fail") } } -// func (h *HandlerWebSocket) handleNotification(ctx context.Context, conn *ws.Conn, userID int) { +func (h *HandlerWebSocket) subscribeOnNotificationAndServe(ctx context.Context, w CtxWriter, userID int) error { + chanNotify, err := h.notifySub.SubscribeOnAllNotifications(ctx, userID) + if err != nil { + return fmt.Errorf("subscribe on Notification") + } -// } + for notify := range chanNotify { + if notify.Err() != nil { + return notify.Err() + } + + err = w.Write(ctx, notify) + if err != nil { + h.log.Error(err.Error()) + } + } -func (h *HandlerWebSocket) subscribeOnNotification(ctx context.Context, conn *ws.Conn, userID int) error { return nil } diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 46a2272..3b9bf3d 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -1,20 +1,27 @@ package websocket import ( + "context" "fmt" "net/http" "time" ws "nhooyr.io/websocket" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/notification" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/message" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) +type notifySubscriber interface { + SubscribeOnAllNotifications(ctx context.Context, userID int) (<-chan *notification.NotifyMessage, error) +} + type HandlerWebSocket struct { originPatterns []string log *log.Logger messageCase usecase.Usecase + notifySub notifySubscriber } type Option func(h *HandlerWebSocket) @@ -27,8 +34,8 @@ func SetOriginPatterns(patterns []string) Option { } } -func New(log *log.Logger, mesCase usecase.Usecase, opts ...Option) *HandlerWebSocket { - handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase} +func New(log *log.Logger, mesCase usecase.Usecase, notify notifySubscriber, opts ...Option) *HandlerWebSocket { + handlerWS := &HandlerWebSocket{log: log, messageCase: mesCase, notifySub: notify} for _, opt := range opts { opt(handlerWS) } diff --git a/internal/pkg/entity/notification/message.go b/internal/pkg/entity/notification/message.go new file mode 100644 index 0000000..aaac18b --- /dev/null +++ b/internal/pkg/entity/notification/message.go @@ -0,0 +1,26 @@ +package notification + +//go:generate easyjson +//easyjson:json +type NotifyMessage struct { + Type string `json:"type"` + Content string `json:"content"` + err error +} + +func (n *NotifyMessage) Err() error { + return n.err +} + +func NewNotifyMessage(t NotifyType, content string) *NotifyMessage { + return &NotifyMessage{ + Type: TypeString(t), + Content: content, + } +} + +func NewNotifyMessageWithError(err error) *NotifyMessage { + return &NotifyMessage{ + err: err, + } +} diff --git a/internal/pkg/entity/notification/notification.go b/internal/pkg/entity/notification/notification.go new file mode 100644 index 0000000..a21f772 --- /dev/null +++ b/internal/pkg/entity/notification/notification.go @@ -0,0 +1,86 @@ +package notification + +import ( + "bytes" + "fmt" + "sync" + "text/template" +) + +type NotifyType uint8 + +const _defaultCapBuffer = 128 + +const ( + _ NotifyType = iota + NotifyComment + + _notifyCustom +) + +type notify struct { + NotifyType NotifyType + buf *sync.Pool + tmp *template.Template +} + +func NewWithTemplate(tmp *template.Template) notify { + return notify{ + NotifyType: _notifyCustom, + buf: &sync.Pool{ + New: func() any { return bytes.NewBuffer(make([]byte, 0, _defaultCapBuffer)) }, + }, + tmp: tmp, + } +} + +func NewWithType(t NotifyType) (notify, error) { + content, ok := notifyTypeTemplate[t] + if !ok { + return notify{}, fmt.Errorf("new notify with type %s: %w", TypeString(t), ErrUnknownNotifyType) + } + + res := notify{ + NotifyType: t, + buf: &sync.Pool{ + New: func() any { return bytes.NewBuffer(make([]byte, 0, _defaultCapBuffer)) }, + }, + } + + tmp, err := template.New(TypeString(t)).Parse(content) + if err != nil { + return notify{}, fmt.Errorf("new notify with type %s: %w", TypeString(t), err) + } + + res.tmp = tmp + return res, nil +} + +func (n notify) Type() NotifyType { + return n.NotifyType +} + +func (n notify) BuildNotifyMessage(data any) (*NotifyMessage, error) { + content, err := n.FormatContent(data) + if err != nil { + return nil, fmt.Errorf("build notify message: %w", err) + } + + return NewNotifyMessage(n.NotifyType, content), nil +} + +func (n notify) FormatContent(data any) (string, error) { + buf := n.buf.Get().(*bytes.Buffer) + + defer func() { + buf.Reset() + n.buf.Put(buf) + }() + + err := n.tmp.Execute(buf, data) + if err != nil { + return "", fmt.Errorf("") + } + + return buf.String(), nil +} diff --git a/internal/pkg/entity/notification/template.go b/internal/pkg/entity/notification/template.go new file mode 100644 index 0000000..55ad3cb --- /dev/null +++ b/internal/pkg/entity/notification/template.go @@ -0,0 +1,5 @@ +package notification + +var notifyTypeTemplate = map[NotifyType]string{ + NotifyComment: `Пользователь {{.Username}} оставил комментарий под пином "{{.TitlePin}}".`, +} diff --git a/internal/pkg/entity/notification/type.go b/internal/pkg/entity/notification/type.go new file mode 100644 index 0000000..3050265 --- /dev/null +++ b/internal/pkg/entity/notification/type.go @@ -0,0 +1,20 @@ +package notification + +import "errors" + +var ErrUnknownNotifyType = errors.New("unknown notify type") + +func TypeString(t NotifyType) string { + switch t { + case NotifyComment: + return "comment" + case _notifyCustom: + return "custom" + } + + return "" +} + +func NotifyTemplateByType(t NotifyType) string { + return notifyTypeTemplate[t] +} diff --git a/internal/pkg/notification/comment/comment.go b/internal/pkg/notification/comment/comment.go new file mode 100644 index 0000000..cc7c5c9 --- /dev/null +++ b/internal/pkg/notification/comment/comment.go @@ -0,0 +1,57 @@ +package comment + +import ( + "context" + "fmt" + "strconv" + + comm "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/notification" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/notification" +) + +type commentGetter interface { + GetCommentWithAuthor(ctx context.Context, commentID int) (*comm.Comment, error) +} + +type pinGetter interface { + GetPinWithAuthor(ctx context.Context, pinID int) (*pin.Pin, error) +} + +type commentNotify struct { + notification.NotifyBuilder + + com commentGetter + pin pinGetter +} + +func NewCommentNotify(builder notification.NotifyBuilder, com commentGetter, pin pinGetter) commentNotify { + return commentNotify{builder, com, pin} +} + +func (c commentNotify) Type() entity.NotifyType { + return c.NotifyBuilder.Type() +} + +func (c commentNotify) MessageNotify(data notification.M) (*entity.NotifyMessage, error) { + return c.NotifyBuilder.BuildNotifyMessage(data) +} + +func (c commentNotify) ChannelsNameForSubscribe(_ context.Context, userID int) ([]string, error) { + return []string{strconv.Itoa(userID)}, nil +} + +func (c commentNotify) ChannelNameForPublishWithData(ctx context.Context, commentID int) (string, notification.M, error) { + com, err := c.com.GetCommentWithAuthor(ctx, commentID) + if err != nil { + return "", nil, fmt.Errorf("get comment for receive channel name on publish: %w", err) + } + + pin, err := c.pin.GetPinWithAuthor(ctx, com.PinID) + if err != nil { + return "", nil, fmt.Errorf("get pin for receive channel name on publish: %w", err) + } + + return strconv.Itoa(pin.Author.ID), notification.M{"Username": com.Author.Username, "TitlePin": pin.Title.String}, nil +} diff --git a/internal/pkg/notification/notifier.go b/internal/pkg/notification/notifier.go new file mode 100644 index 0000000..5f3bac8 --- /dev/null +++ b/internal/pkg/notification/notifier.go @@ -0,0 +1,27 @@ +package notification + +import ( + "context" + + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/notification" +) + +type M map[string]string + +type TypeNotifier interface { + Type() entity.NotifyType +} + +type Notifier interface { + TypeNotifier + + ChannelNameForPublishWithData(ctx context.Context, entityID int) (string, M, error) + ChannelsNameForSubscribe(ctx context.Context, userID int) ([]string, error) + MessageNotify(data M) (*entity.NotifyMessage, error) +} + +type NotifyBuilder interface { + TypeNotifier + + BuildNotifyMessage(data any) (*entity.NotifyMessage, error) +} diff --git a/internal/pkg/repository/comment/queries.go b/internal/pkg/repository/comment/queries.go index 85057a2..c944664 100644 --- a/internal/pkg/repository/comment/queries.go +++ b/internal/pkg/repository/comment/queries.go @@ -5,7 +5,11 @@ const ( UpdateCommentOnDeleted = "UPDATE comment SET deleted_at = now() WHERE id = $1;" - SelectCommentByID = "SELECT author, pin_id, content FROM comment WHERE id = $1 AND deleted_at IS NULL;" + SelectCommentByID = `SELECT p.id, p.username, p.avatar, c.pin_id, c.content + FROM comment AS c INNER JOIN profile AS p + ON c.author = p.id + WHERE c.id = $1 AND c.deleted_at IS NULL;` + SelectCommentsByPinID = `SELECT c.id, p.id, p.username, p.avatar, c.content FROM comment AS c INNER JOIN profile AS p ON c.author = p.id diff --git a/internal/pkg/repository/comment/repo.go b/internal/pkg/repository/comment/repo.go index a002718..cfaf576 100644 --- a/internal/pkg/repository/comment/repo.go +++ b/internal/pkg/repository/comment/repo.go @@ -45,7 +45,8 @@ func (c *commentRepoPG) AddComment(ctx context.Context, comment *entity.Comment) func (c *commentRepoPG) GetCommentByID(ctx context.Context, id int) (*entity.Comment, error) { comment := &entity.Comment{ID: id, Author: &user.User{}} - err := c.db.QueryRow(ctx, SelectCommentByID, id).Scan(&comment.Author.ID, &comment.PinID, &comment.Content) + err := c.db.QueryRow(ctx, SelectCommentByID, id). + Scan(&comment.Author.ID, &comment.Author.Username, &comment.Author.Avatar, &comment.PinID, &comment.Content) if err != nil { return nil, fmt.Errorf("get comment by id from storage: %w", err) } diff --git a/internal/pkg/usecase/comment/usecase.go b/internal/pkg/usecase/comment/usecase.go index c2d5d78..05a540b 100644 --- a/internal/pkg/usecase/comment/usecase.go +++ b/internal/pkg/usecase/comment/usecase.go @@ -7,6 +7,7 @@ import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" commentRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/comment" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime/notification" ) //go:generate mockgen -destination=./mock/comment_mock.go -package=mock -source=usecase.go Usecase @@ -14,6 +15,7 @@ type Usecase interface { PutCommentOnPin(ctx context.Context, userID int, comment *entity.Comment) (int, error) GetFeedCommentOnPin(ctx context.Context, userID, pinID, count, lastID int) ([]entity.Comment, int, error) DeleteComment(ctx context.Context, userID, commentID int) error + GetCommentWithAuthor(ctx context.Context, commentID int) (*entity.Comment, error) } type availablePinChecker interface { @@ -24,11 +26,23 @@ type availablePinChecker interface { type commentCase struct { availablePinChecker - repo commentRepo.Repository + notifyCase notification.Usecase + repo commentRepo.Repository + + notifyIsEnable bool } -func New(repo commentRepo.Repository, checker availablePinChecker) *commentCase { - return &commentCase{checker, repo} +func New(repo commentRepo.Repository, checker availablePinChecker, notifyCase notification.Usecase) *commentCase { + comCase := &commentCase{ + availablePinChecker: checker, + repo: repo, + notifyCase: notifyCase, + } + + if notifyCase != nil { + comCase.notifyIsEnable = true + } + return comCase } func (c *commentCase) PutCommentOnPin(ctx context.Context, userID int, comment *entity.Comment) (int, error) { @@ -43,6 +57,13 @@ func (c *commentCase) PutCommentOnPin(ctx context.Context, userID int, comment * if err != nil { return 0, fmt.Errorf("put comment on available pin: %w", err) } + + ctx = context.Background() + + if c.notifyIsEnable { + go c.notifyCase.NotifyCommentLeftOnPin(ctx, id) + } + return id, nil } @@ -76,3 +97,12 @@ func (c *commentCase) DeleteComment(ctx context.Context, userID, commentID int) } return nil } + +func (c *commentCase) GetCommentWithAuthor(ctx context.Context, commentID int) (*entity.Comment, error) { + comment, err := c.repo.GetCommentByID(ctx, commentID) + if err != nil { + return nil, fmt.Errorf("get comment with author: %w", err) + } + + return comment, nil +} diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index bb76131..3a23401 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -105,10 +105,19 @@ func (p *pinCase) ViewFeedPin(ctx context.Context, userID int, cfg pin.FeedPinCo return p.repo.GetFeedPins(ctx, cfg) } -func (u *pinCase) GetAuthorIdOfThePin(ctx context.Context, pinID int) (int, error) { - user, err := u.repo.GetAuthorPin(ctx, pinID) +func (p *pinCase) GetAuthorIdOfThePin(ctx context.Context, pinID int) (int, error) { + user, err := p.repo.GetAuthorPin(ctx, pinID) if err != nil { return 0, fmt.Errorf("get author id of the pin: %w", err) } return user.ID, nil } + +func (p *pinCase) GetPinWithAuthor(ctx context.Context, pinID int) (*pin.Pin, error) { + pin, err := p.repo.GetPinByID(ctx, pinID, true) + if err != nil { + return nil, fmt.Errorf("get a pin with author: %w", err) + } + + return pin, nil +} diff --git a/internal/pkg/usecase/realtime/chat/chat.go b/internal/pkg/usecase/realtime/chat/chat.go index eefeccd..7514d2a 100644 --- a/internal/pkg/usecase/realtime/chat/chat.go +++ b/internal/pkg/usecase/realtime/chat/chat.go @@ -86,7 +86,7 @@ func (r *realtimeCase) receiveFromSubClient(ctx context.Context, subClient <-cha msg, ok := pack.Body.(*rt.Message_Object) if !ok { - chanEvMsg <- makeErrEventMessageObjectID(realtime.ErrUnknowTypeObject) + chanEvMsg <- makeErrEventMessageObjectID(realtime.ErrUnknownTypeObject) return } diff --git a/internal/pkg/usecase/realtime/notification/comment.go b/internal/pkg/usecase/realtime/notification/comment.go new file mode 100644 index 0000000..0208aad --- /dev/null +++ b/internal/pkg/usecase/realtime/notification/comment.go @@ -0,0 +1,36 @@ +package notification + +import ( + "context" + "fmt" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/notification" +) + +func (n *notificationClient) NotifyCommentLeftOnPin(ctx context.Context, commentID int) error { + notifier, ok := n.notifiers[entity.NotifyComment] + if !ok { + n.log.Error(ErrNotifierNotRegistered.Error()) + return ErrNotifierNotRegistered + } + + chanName, data, err := notifier.ChannelNameForPublishWithData(ctx, commentID) + if err != nil { + n.log.Error(err.Error()) + return fmt.Errorf("notify comment left on pin: %w", err) + } + + err = n.client.Publish(ctx, chanName, &rt.Message_Content{ + Content: &rt.EventMap{ + Type: int64(entity.NotifyComment), + M: data, + }, + }) + if err != nil { + n.log.Error(err.Error()) + return fmt.Errorf("publish to client: %w", err) + } + + return nil +} diff --git a/internal/pkg/usecase/realtime/notification/notification.go b/internal/pkg/usecase/realtime/notification/notification.go new file mode 100644 index 0000000..57d5ae2 --- /dev/null +++ b/internal/pkg/usecase/realtime/notification/notification.go @@ -0,0 +1,101 @@ +package notification + +import ( + "context" + "errors" + "fmt" + + rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/notification" + notify "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/notification" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/realtime" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" +) + +var ErrNotifierNotRegistered = errors.New("notifier with this type not registered") + +type Usecase interface { + NotifyCommentLeftOnPin(ctx context.Context, commentID int) error +} + +type notificationClient struct { + client realtime.RealTimeClient + log *logger.Logger + notifiers map[entity.NotifyType]notify.Notifier +} + +func New(cl realtime.RealTimeClient, log *logger.Logger, opts ...Option) *notificationClient { + client := &notificationClient{ + client: cl, + log: log, + notifiers: make(map[entity.NotifyType]notify.Notifier), + } + + for _, opt := range opts { + opt.apply(client) + } + + return client +} + +func (n *notificationClient) SubscribeOnAllNotifications(ctx context.Context, userID int) (<-chan *entity.NotifyMessage, error) { + setChans := make(map[string]struct{}) + for t, notifier := range n.notifiers { + nameChans, err := notifier.ChannelsNameForSubscribe(ctx, userID) + if err != nil { + return nil, fmt.Errorf("receiving name channels for subscribe on %s notifier: %w", entity.TypeString(t), err) + } + + for _, name := range nameChans { + setChans[name] = struct{}{} + } + } + + uniqChans := make([]string, 0, len(setChans)) + + for nameChan := range setChans { + uniqChans = append(uniqChans, nameChan) + } + + chanPack, err := n.client.Subscribe(ctx, uniqChans) + if err != nil { + return nil, fmt.Errorf("subscribe on all notifications: %w", err) + } + + chanNotifyMsg := make(chan *entity.NotifyMessage) + + go n.pipelineNotify(chanPack, chanNotifyMsg) + + return chanNotifyMsg, nil +} + +func (n *notificationClient) pipelineNotify(chRecv <-chan realtime.Pack, chSend chan<- *entity.NotifyMessage) { + defer close(chSend) + + for pack := range chRecv { + if pack.Err != nil { + chSend <- entity.NewNotifyMessageWithError(pack.Err) + return + } + + notifyData, ok := pack.Body.(*rt.Message_Content) + if !ok { + chSend <- entity.NewNotifyMessageWithError(realtime.ErrUnknownTypeObject) + return + } + + notifier, ok := n.notifiers[entity.NotifyType(notifyData.Content.GetType())] + if !ok { + chSend <- entity.NewNotifyMessageWithError(ErrNotifierNotRegistered) + return + } + + msg, err := notifier.MessageNotify(notifyData.Content.GetM()) + if err != nil { + chSend <- entity.NewNotifyMessageWithError(err) + return + } + + chSend <- msg + } +} diff --git a/internal/pkg/usecase/realtime/notification/option.go b/internal/pkg/usecase/realtime/notification/option.go new file mode 100644 index 0000000..0978634 --- /dev/null +++ b/internal/pkg/usecase/realtime/notification/option.go @@ -0,0 +1,19 @@ +package notification + +import notify "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/notification" + +type Option interface { + apply(*notificationClient) +} + +type funcOption func(*notificationClient) + +func (f funcOption) apply(cl *notificationClient) { + f(cl) +} + +func Register(notifier notify.Notifier) Option { + return funcOption(func(cl *notificationClient) { + cl.notifiers[notifier.Type()] = notifier + }) +} diff --git a/internal/pkg/usecase/realtime/realtime.go b/internal/pkg/usecase/realtime/realtime.go index e5c00df..e5af26f 100644 --- a/internal/pkg/usecase/realtime/realtime.go +++ b/internal/pkg/usecase/realtime/realtime.go @@ -8,7 +8,7 @@ import ( rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" ) -var ErrUnknowTypeObject = errors.New("unknow type") +var ErrUnknownTypeObject = errors.New("unknown type") const ( _topicChat = "chat" @@ -37,6 +37,13 @@ func NewRealTimeChatClient(client rt.RealTimeClient) realtimeClient { } } +func NewRealTimeNotificationClient(client rt.RealTimeClient) realtimeClient { + return realtimeClient{ + client: client, + topic: _topicNotification, + } +} + func (r realtimeClient) Publish(ctx context.Context, chanName string, object any) error { pubMsg := &rt.PublishMessage{ Channel: &rt.Channel{ @@ -52,7 +59,7 @@ func (r realtimeClient) Publish(ctx context.Context, chanName string, object any case *rt.Message_Content: pubMsg.Message.Body = body default: - return ErrUnknowTypeObject + return ErrUnknownTypeObject } _, err := r.client.Publish(ctx, pubMsg) From 430f974e3292c8a2030bb979c7fdd78144fd6759 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Fri, 15 Dec 2023 22:34:22 +0300 Subject: [PATCH 243/266] TP-1c3 add: easyjson generation --- .../entity/notification/message_easyjson.go | 92 +++++++++++++++++++ internal/pkg/usecase/comment/usecase.go | 6 +- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/entity/notification/message_easyjson.go diff --git a/internal/pkg/entity/notification/message_easyjson.go b/internal/pkg/entity/notification/message_easyjson.go new file mode 100644 index 0000000..a774aab --- /dev/null +++ b/internal/pkg/entity/notification/message_easyjson.go @@ -0,0 +1,92 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package notification + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityNotification(in *jlexer.Lexer, out *NotifyMessage) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "type": + out.Type = string(in.String()) + case "content": + out.Content = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityNotification(out *jwriter.Writer, in NotifyMessage) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"type\":" + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"content\":" + out.RawString(prefix) + out.String(string(in.Content)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v NotifyMessage) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityNotification(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v NotifyMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjson4086215fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityNotification(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *NotifyMessage) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityNotification(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *NotifyMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4086215fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgEntityNotification(l, v) +} diff --git a/internal/pkg/usecase/comment/usecase.go b/internal/pkg/usecase/comment/usecase.go index 05a540b..b7f4272 100644 --- a/internal/pkg/usecase/comment/usecase.go +++ b/internal/pkg/usecase/comment/usecase.go @@ -3,6 +3,7 @@ package comment import ( "context" "fmt" + "time" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -23,6 +24,8 @@ type availablePinChecker interface { GetAuthorIdOfThePin(ctx context.Context, pinID int) (int, error) } +const _timeoutNotification = 5 * time.Minute + type commentCase struct { availablePinChecker @@ -58,9 +61,8 @@ func (c *commentCase) PutCommentOnPin(ctx context.Context, userID int, comment * return 0, fmt.Errorf("put comment on available pin: %w", err) } - ctx = context.Background() - if c.notifyIsEnable { + ctx, _ = context.WithTimeout(context.Background(), _timeoutNotification) go c.notifyCase.NotifyCommentLeftOnPin(ctx, id) } From ea2a4d0dbe84626d4e699859fbcf20ca5dfd196a Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Sun, 17 Dec 2023 13:07:49 +0300 Subject: [PATCH 244/266] TP-8da update: returned user from and to when deleting message --- internal/pkg/usecase/message/usecase.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 552d8c8..00bc0fc 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -31,8 +31,6 @@ type Usecase interface { SubscribeUserToAllChats(ctx context.Context, userID int) (<-chan EventMessage, error) } -const _topicChat = "chat" - type EventMessage struct { Type string Message *entity.Message @@ -191,13 +189,14 @@ func (m *messageCase) receiveFromSubClient(ctx context.Context, userID int, subC evMsg = EventMessage{ Type: msgObjID.Type, } + + evMsg.Message, err = m.GetMessage(ctx, userID, msgObjID.MessageID) + if err != nil { + m.log.Error(err.Error()) + } + if evMsg.Type == "delete" { - evMsg.Message = &entity.Message{ID: msgObjID.MessageID} - } else { - evMsg.Message, err = m.GetMessage(ctx, userID, msgObjID.MessageID) - if err != nil { - m.log.Error(err.Error()) - } + evMsg.Message.Content.String = "" } chanEvMsg <- evMsg From d63df1b8277f245d81fdd6ea4d7cc4b16e8b5c4b Mon Sep 17 00:00:00 2001 From: Gvidow <96253031+Gvidow@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:41:40 +0300 Subject: [PATCH 245/266] Update websocket.go: delete unused package "os" --- internal/pkg/delivery/websocket/websocket.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/pkg/delivery/websocket/websocket.go b/internal/pkg/delivery/websocket/websocket.go index 5d55376..bd7db73 100644 --- a/internal/pkg/delivery/websocket/websocket.go +++ b/internal/pkg/delivery/websocket/websocket.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "os" "time" ws "nhooyr.io/websocket" From 2d1c6d21c9c9aa2b6f97e67b1c24bfe15188aa5d Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 19 Dec 2023 13:42:56 +0300 Subject: [PATCH 246/266] TP-6ec_easyjson: add rest easyjson --- internal/pkg/delivery/http/v1/auth.go | 8 +- internal/pkg/delivery/http/v1/auth_test.go | 71 +++--- internal/pkg/delivery/http/v1/board.go | 2 +- internal/pkg/delivery/http/v1/comment.go | 3 - internal/pkg/delivery/http/v1/pin.go | 5 +- internal/pkg/delivery/http/v1/pin_test.go | 17 +- internal/pkg/delivery/http/v1/profile.go | 31 +-- internal/pkg/delivery/http/v1/profile_test.go | 5 +- .../pkg/delivery/http/v1/structs/board.go | 43 ++++ .../http/v1/structs/board_validation.go | 48 ---- .../pkg/delivery/http/v1/structs/response.go | 2 +- internal/pkg/delivery/http/v1/structs/user.go | 23 ++ .../delivery/http/v1/structs/user_easyjson.go | 221 ++++++++++++++++++ internal/pkg/usecase/pin/update.go | 11 +- internal/pkg/usecase/pin/update_easyjson.go | 174 ++++++++++++++ internal/pkg/usecase/user/credentials.go | 7 +- .../pkg/usecase/user/credentials_easyjson.go | 92 ++++++++ internal/pkg/usecase/user/info.go | 13 +- internal/pkg/usecase/user/info_easyjson.go | 192 +++++++++++++++ 19 files changed, 828 insertions(+), 140 deletions(-) delete mode 100644 internal/pkg/delivery/http/v1/structs/board_validation.go create mode 100644 internal/pkg/delivery/http/v1/structs/user.go create mode 100644 internal/pkg/delivery/http/v1/structs/user_easyjson.go create mode 100644 internal/pkg/usecase/pin/update_easyjson.go create mode 100644 internal/pkg/usecase/user/credentials_easyjson.go create mode 100644 internal/pkg/usecase/user/info_easyjson.go diff --git a/internal/pkg/delivery/http/v1/auth.go b/internal/pkg/delivery/http/v1/auth.go index 6f536a7..0c0149a 100644 --- a/internal/pkg/delivery/http/v1/auth.go +++ b/internal/pkg/delivery/http/v1/auth.go @@ -1,7 +1,6 @@ package v1 import ( - "encoding/json" "net/http" "time" @@ -10,6 +9,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/mailru/easyjson" ) // Login godoc @@ -56,8 +56,8 @@ func (h *HandlerHTTP) CheckLogin(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) Login(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) - params := usecase.UserCredentials{} - err := json.NewDecoder(r.Body).Decode(&params) + params := &usecase.UserCredentials{} + err := easyjson.UnmarshalFromReader(r.Body, params) defer r.Body.Close() if err != nil { logger.Info("failed to parse parameters", log.F{"error", err.Error()}) @@ -122,7 +122,7 @@ func (h *HandlerHTTP) Signup(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) user := &user.User{} - err := json.NewDecoder(r.Body).Decode(user) + err := easyjson.UnmarshalFromReader(r.Body, user) defer r.Body.Close() if err != nil { logger.Info("failed to parse parameters", log.F{"error", err.Error()}) diff --git a/internal/pkg/delivery/http/v1/auth_test.go b/internal/pkg/delivery/http/v1/auth_test.go index 29949ea..95c6744 100644 --- a/internal/pkg/delivery/http/v1/auth_test.go +++ b/internal/pkg/delivery/http/v1/auth_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/session" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" @@ -45,7 +46,7 @@ func TestCheckLogin(t *testing.T) { badCases := []struct { name string cookie *http.Cookie - expResp JsonErrResponse + expResp structs.JsonErrResponse }{ { "sending empty cookie", @@ -53,7 +54,7 @@ func TestCheckLogin(t *testing.T) { Name: "", Value: "", }, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "no user was found for this session", Code: "no_auth", @@ -65,7 +66,7 @@ func TestCheckLogin(t *testing.T) { Name: "session_key", Value: "doesn't exist", }, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "no user was found for this session", Code: "no_auth", @@ -77,7 +78,7 @@ func TestCheckLogin(t *testing.T) { Name: "session_key", Value: "f4280a941b664d02", }, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "no user was found for this session", Code: "no_auth", @@ -93,7 +94,7 @@ func TestCheckLogin(t *testing.T) { service.CheckLogin(w, req) - var actualResp JsonErrResponse + var actualResp structs.JsonErrResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) }) @@ -116,12 +117,12 @@ func testLogin(t *testing.T) { goodCases := []struct { name string rawBody string - expResp JsonResponse + expResp structs.JsonResponse }{ { "providing correct and valid user credentials", `{"username":"dogsLover", "password":"big_string"}`, - JsonResponse{ + structs.JsonResponse{ Status: "ok", Message: "a new session has been created for the user", Body: nil, @@ -136,7 +137,7 @@ func testLogin(t *testing.T) { service.Login(w, req) - var actualResp JsonResponse + var actualResp structs.JsonResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) require.True(t, checkAuthCookie(w.Result().Cookies())) @@ -146,12 +147,12 @@ func testLogin(t *testing.T) { badCases := []struct { name string rawBody string - expResp JsonErrResponse + expResp structs.JsonErrResponse }{ { "providing invalid credentials - broken body", "{'username': 'dogsLover', 'password': 'big_string'", - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "the correct username and password are expected to be received in JSON format", Code: "parse_body", @@ -160,7 +161,7 @@ func testLogin(t *testing.T) { { "providing invalid credentials - no username", `{"password":"big_string"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "invalid user credentials", Code: "invalid_credentials", @@ -169,7 +170,7 @@ func testLogin(t *testing.T) { { "providing invalid credentials - no password", `{"username":"dogsLover"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "invalid user credentials", Code: "invalid_credentials", @@ -178,7 +179,7 @@ func testLogin(t *testing.T) { { "providing invalid credentials - short username", `{"username":"do", "password":"big_string"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "invalid user credentials", Code: "invalid_credentials", @@ -187,7 +188,7 @@ func testLogin(t *testing.T) { { "providing invalid credentials - long username", `{"username":"dojsbrjfbdrjhbhjldrbgbdrhjgbdjrbgjdhbgjhdbrghbdhj,gbdhjrbgjhdbvkvghkevfghjdvrfhvdhrvbjdfgdrgdr","password":"big_string"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "invalid user credentials", Code: "invalid_credentials", @@ -196,7 +197,7 @@ func testLogin(t *testing.T) { { "providing invalid credentials - short password", `{"username":"dogsLover","password":"bi"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "invalid user credentials", Code: "invalid_credentials", @@ -205,7 +206,7 @@ func testLogin(t *testing.T) { { "providing invalid credentials - long password", `{"username":"dogsLover","password":"biyugsgrusgubskhvfhkdgvfgvdvrjgbsjhgjkshzkljfskfwjkhkfjisuidgoquakflsjuzeofiow3i"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "invalid user credentials", Code: "invalid_credentials", @@ -214,7 +215,7 @@ func testLogin(t *testing.T) { { "providing incorrect credentials - no user with such credentials", `{"username":"dogsLover", "password":"doesn't_exist"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "incorrect user credentials", Code: "bad_credentials", @@ -229,7 +230,7 @@ func testLogin(t *testing.T) { service.Login(w, req) - var actualResp JsonErrResponse + var actualResp structs.JsonErrResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) require.False(t, checkAuthCookie(w.Result().Cookies())) @@ -252,12 +253,12 @@ func testSignUp(t *testing.T) { goodCases := []struct { name string rawBody string - expResp JsonResponse + expResp structs.JsonResponse }{ { "providing correct and valid data for signup", `{"username":"newbie", "password":"getHigh123", "email":"world@uandex.ru"}`, - JsonResponse{ + structs.JsonResponse{ Status: "ok", Message: "the user has been successfully registered", Body: nil, @@ -272,7 +273,7 @@ func testSignUp(t *testing.T) { service.Signup(w, req) - var actualResp JsonResponse + var actualResp structs.JsonResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) }) @@ -281,12 +282,12 @@ func testSignUp(t *testing.T) { badCases := []struct { name string rawBody string - expResp JsonErrResponse + expResp structs.JsonErrResponse }{ { "user with such data already exists", `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "there is already an account with this username or email", Code: "uniq_fields", @@ -295,7 +296,7 @@ func testSignUp(t *testing.T) { { "invalid data - broken body", `{"username":"dogsLover", "password":"big_string", "email":"dogslove@gmail.com"`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "the correct username, email and password are expected to be received in JSON format", Code: "parse_body", @@ -304,7 +305,7 @@ func testSignUp(t *testing.T) { { "invalid data - no username", `{"password":"big_string", "email":"dogslove@gmail.com"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "username", Code: "invalid_params", @@ -313,7 +314,7 @@ func testSignUp(t *testing.T) { { "invalid data - no username, password", `{"email":"dogslove@gmail.com"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "password,username", Code: "invalid_params", @@ -322,7 +323,7 @@ func testSignUp(t *testing.T) { { "invalid data - short username", `{"username":"sh", "password":"big_string", "email":"dogslove@gmail.com"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "username", Code: "invalid_params", @@ -331,7 +332,7 @@ func testSignUp(t *testing.T) { { "invalid data - incorrect email", `{"username":"sh", "password":"big_string", "email":"dog"}`, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "email,username", Code: "invalid_params", @@ -346,7 +347,7 @@ func testSignUp(t *testing.T) { service.Signup(w, req) - var actualResp JsonErrResponse + var actualResp structs.JsonErrResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) }) @@ -368,7 +369,7 @@ func testLogout(t *testing.T) { goodCases := []struct { name string cookie *http.Cookie - expResp JsonResponse + expResp structs.JsonResponse }{ { "user is logged in - providing valid cookie", @@ -376,7 +377,7 @@ func testLogout(t *testing.T) { Name: "session_key", Value: "461afabf38b3147c", }, - JsonResponse{ + structs.JsonResponse{ Status: "ok", Message: "the user has successfully logged out", Body: nil, @@ -392,7 +393,7 @@ func testLogout(t *testing.T) { service.Logout(w, req) - var actualResp JsonResponse + var actualResp structs.JsonResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) }) @@ -401,7 +402,7 @@ func testLogout(t *testing.T) { badCases := []struct { name string cookie *http.Cookie - expResp JsonErrResponse + expResp structs.JsonErrResponse }{ { "user isn't logged in - providing invalid cookie", @@ -409,7 +410,7 @@ func testLogout(t *testing.T) { Name: "not_auth_cookie", Value: "blablalba", }, - JsonErrResponse{ + structs.JsonErrResponse{ Status: "error", Message: "to log out, you must first log in", Code: "no_auth", @@ -425,7 +426,7 @@ func testLogout(t *testing.T) { service.Logout(w, req) - var actualResp JsonErrResponse + var actualResp structs.JsonErrResponse json.NewDecoder(w.Result().Body).Decode(&actualResp) require.Equal(t, tCase.expResp, actualResp) }) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index f55db48..2b02193 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -349,12 +349,12 @@ func (h *HandlerHTTP) DeletePinFromBoard(w http.ResponseWriter, r *http.Request) delPinFromBoard := structs.DeletePinFromBoard{} err = easyjson.UnmarshalFromReader(r.Body, &delPinFromBoard) + defer r.Body.Close() if err != nil { code, message := errHTTP.GetErrCodeMessage(errHTTP.ErrBadBody) responseError(w, code, message) return } - defer r.Body.Close() err = h.boardCase.DeletePinFromBoard(r.Context(), int(boardID), delPinFromBoard.PinID) if err != nil { diff --git a/internal/pkg/delivery/http/v1/comment.go b/internal/pkg/delivery/http/v1/comment.go index 60e1b8d..a5dd834 100644 --- a/internal/pkg/delivery/http/v1/comment.go +++ b/internal/pkg/delivery/http/v1/comment.go @@ -1,7 +1,6 @@ package v1 import ( - "fmt" "net/http" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" @@ -24,9 +23,7 @@ func (h *HandlerHTTP) WriteComment(w http.ResponseWriter, r *http.Request) { } comment := &comment.Comment{} - err = easyjson.UnmarshalFromReader(r.Body, comment) - fmt.Println(comment.Content) defer r.Body.Close() if err != nil { logger.Warn(err.Error()) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index f9ba931..19b2831 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -1,12 +1,12 @@ package v1 import ( - "encoding/json" "net/http" "strconv" "strings" chi "github.com/go-chi/chi/v5" + "github.com/mailru/easyjson" entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" @@ -129,8 +129,7 @@ func (h *HandlerHTTP) EditPin(w http.ResponseWriter, r *http.Request) { _, _ = userID, pinID pinUpdate := &usecase.PinUpdateData{} - - err = json.NewDecoder(r.Body).Decode(pinUpdate) + err = easyjson.UnmarshalFromReader(r.Body, pinUpdate) defer r.Body.Close() if err != nil { logger.Info(err.Error()) diff --git a/internal/pkg/delivery/http/v1/pin_test.go b/internal/pkg/delivery/http/v1/pin_test.go index 9f5bf20..358d6d2 100644 --- a/internal/pkg/delivery/http/v1/pin_test.go +++ b/internal/pkg/delivery/http/v1/pin_test.go @@ -9,6 +9,7 @@ import ( "strconv" "testing" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/ramrepo" pinCase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" @@ -30,11 +31,11 @@ func TestGetPins(t *testing.T) { badCases := []struct { rawURL string - expResp JsonErrResponse + expResp structs.JsonErrResponse }{ { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, 3), - expResp: JsonErrResponse{ + expResp: structs.JsonErrResponse{ Status: "error", Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", @@ -42,7 +43,7 @@ func TestGetPins(t *testing.T) { }, { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, -2, 3), - expResp: JsonErrResponse{ + expResp: structs.JsonErrResponse{ Status: "error", Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", @@ -50,7 +51,7 @@ func TestGetPins(t *testing.T) { }, { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 213123, 3), - expResp: JsonErrResponse{ + expResp: structs.JsonErrResponse{ Status: "error", Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", @@ -58,7 +59,7 @@ func TestGetPins(t *testing.T) { }, { rawURL: fmt.Sprintf("%s?count=%d&lastID=%d", rawUrl, 0, -1), - expResp: JsonErrResponse{ + expResp: structs.JsonErrResponse{ Status: "error", Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", @@ -66,7 +67,7 @@ func TestGetPins(t *testing.T) { }, { rawURL: fmt.Sprintf("%s?count=&lastID=%d", rawUrl, 3), - expResp: JsonErrResponse{ + expResp: structs.JsonErrResponse{ Status: "error", Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", @@ -74,7 +75,7 @@ func TestGetPins(t *testing.T) { }, { rawURL: fmt.Sprintf("%s?lastID=%d", rawUrl, 3), - expResp: JsonErrResponse{ + expResp: structs.JsonErrResponse{ Status: "error", Message: "expected parameters: count(positive integer: [1; 1000]), maxID, minID(positive integers, the absence of these parameters is equal to the value 0)", Code: "bad_params", @@ -90,7 +91,7 @@ func TestGetPins(t *testing.T) { resp := w.Result() body, _ := io.ReadAll(resp.Body) - var actualResp JsonErrResponse + var actualResp structs.JsonErrResponse json.Unmarshal(body, &actualResp) require.Equal(t, tCase.expResp, actualResp) diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index 66ab2ab..be60ccb 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -1,39 +1,22 @@ package v1 import ( - "encoding/json" "fmt" "net/http" "strconv" "github.com/go-chi/chi/v5" errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/mailru/easyjson" ) -type UserInfo struct { - ID int `json:"id" example:"123"` - Username string `json:"username" example:"Snapshot"` - Avatar string `json:"avatar" example:"/pic1"` - Name string `json:"name" example:"Bob"` - Surname string `json:"surname" example:"Dylan"` - About string `json:"about" example:"Cool guy"` - IsSubscribed bool `json:"is_subscribed" example:"true"` - SubsCount int `json:"subscribers" example:"23"` -} - -type ProfileInfo struct { - ID int `json:"id" example:"1"` - Username string `json:"username" example:"baobab"` - Avatar string `json:"avatar" example:"/pic1"` - SubsCount int `json:"subscribers" example:"12"` -} - -func ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount int) UserInfo { - return UserInfo{ +func ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount int) structs.UserInfo { + return structs.UserInfo{ ID: user.ID, Username: user.Username, Avatar: user.Avatar, @@ -45,8 +28,8 @@ func ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount i } } -func ToProfileInfoFromService(user *userEntity.User, subsCount int) ProfileInfo { - return ProfileInfo{ +func ToProfileInfoFromService(user *userEntity.User, subsCount int) structs.ProfileInfo { + return structs.ProfileInfo{ ID: user.ID, Username: user.Username, Avatar: user.Avatar, @@ -83,7 +66,7 @@ func (h *HandlerHTTP) ProfileEditInfo(w http.ResponseWriter, r *http.Request) { userID := r.Context().Value(auth.KeyCurrentUserID).(int) data := &user.ProfileUpdateData{} - err := json.NewDecoder(r.Body).Decode(data) + err := easyjson.UnmarshalFromReader(r.Body, data) defer r.Body.Close() if err != nil { logger.Info("json decode: " + err.Error()) diff --git a/internal/pkg/delivery/http/v1/profile_test.go b/internal/pkg/delivery/http/v1/profile_test.go index d927e7f..5c87e00 100644 --- a/internal/pkg/delivery/http/v1/profile_test.go +++ b/internal/pkg/delivery/http/v1/profile_test.go @@ -11,6 +11,7 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user/mock" @@ -47,11 +48,11 @@ func TestGetProfileInfo(t *testing.T) { res := rec.Result() defer res.Body.Close() - actualBody := JsonResponse{Body: &user.User{}} + actualBody := structs.JsonResponse{Body: &user.User{}} err = json.NewDecoder(res.Body).Decode(&actualBody) require.NoError(t, err) fmt.Println(actualBody.Body) - wantBody := JsonResponse{ + wantBody := structs.JsonResponse{ Status: "ok", Message: "user data has been successfully received", Body: &wantUser, diff --git a/internal/pkg/delivery/http/v1/structs/board.go b/internal/pkg/delivery/http/v1/structs/board.go index e6aa540..9e42ece 100644 --- a/internal/pkg/delivery/http/v1/structs/board.go +++ b/internal/pkg/delivery/http/v1/structs/board.go @@ -2,6 +2,7 @@ package structs import ( "fmt" + "unicode" errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" ) @@ -69,3 +70,45 @@ func (data *BoardData) Validate() error { } return nil } + +func isValidTagTitle(title string) bool { + if len(title) > 20 { + return false + } + + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { + return false + } + } + return true +} + +func checkIsValidTagTitles(titles []string) error { + if len(titles) > 7 { + return fmt.Errorf("too many titles") + } + + invalidTitles := make([]string, 0) + for _, title := range titles { + if !isValidTagTitle(title) { + invalidTitles = append(invalidTitles, title) + } + } + if len(invalidTitles) > 0 { + return fmt.Errorf("%v", invalidTitles) + } + return nil +} + +func isValidBoardTitle(title string) bool { + if len(title) == 0 || len(title) > 40 { + return false + } + for _, sym := range title { + if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { + return false + } + } + return true +} diff --git a/internal/pkg/delivery/http/v1/structs/board_validation.go b/internal/pkg/delivery/http/v1/structs/board_validation.go deleted file mode 100644 index 6181434..0000000 --- a/internal/pkg/delivery/http/v1/structs/board_validation.go +++ /dev/null @@ -1,48 +0,0 @@ -package structs - -import ( - "fmt" - "unicode" -) - -func isValidTagTitle(title string) bool { - if len(title) > 20 { - return false - } - - for _, sym := range title { - if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { - return false - } - } - return true -} - -func checkIsValidTagTitles(titles []string) error { - if len(titles) > 7 { - return fmt.Errorf("too many titles") - } - - invalidTitles := make([]string, 0) - for _, title := range titles { - if !isValidTagTitle(title) { - invalidTitles = append(invalidTitles, title) - } - } - if len(invalidTitles) > 0 { - return fmt.Errorf("%v", invalidTitles) - } - return nil -} - -func isValidBoardTitle(title string) bool { - if len(title) == 0 || len(title) > 40 { - return false - } - for _, sym := range title { - if !(unicode.IsNumber(sym) || unicode.IsLetter(sym) || unicode.IsPunct(sym) || unicode.IsSpace(sym)) { - return false - } - } - return true -} diff --git a/internal/pkg/delivery/http/v1/structs/response.go b/internal/pkg/delivery/http/v1/structs/response.go index e922b82..2be893b 100644 --- a/internal/pkg/delivery/http/v1/structs/response.go +++ b/internal/pkg/delivery/http/v1/structs/response.go @@ -1,6 +1,6 @@ package structs -//go:generate easyjson subscription.go +//go:generate easyjson response.go //easyjson:json type JsonResponse struct { diff --git a/internal/pkg/delivery/http/v1/structs/user.go b/internal/pkg/delivery/http/v1/structs/user.go new file mode 100644 index 0000000..3afc2aa --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/user.go @@ -0,0 +1,23 @@ +package structs + +//go:generate easyjson user.go + +//easyjson:json +type UserInfo struct { + ID int `json:"id" example:"123"` + Username string `json:"username" example:"Snapshot"` + Avatar string `json:"avatar" example:"/pic1"` + Name string `json:"name" example:"Bob"` + Surname string `json:"surname" example:"Dylan"` + About string `json:"about" example:"Cool guy"` + IsSubscribed bool `json:"is_subscribed" example:"true"` + SubsCount int `json:"subscribers" example:"23"` +} + +//easyjson:json +type ProfileInfo struct { + ID int `json:"id" example:"1"` + Username string `json:"username" example:"baobab"` + Avatar string `json:"avatar" example:"/pic1"` + SubsCount int `json:"subscribers" example:"12"` +} diff --git a/internal/pkg/delivery/http/v1/structs/user_easyjson.go b/internal/pkg/delivery/http/v1/structs/user_easyjson.go new file mode 100644 index 0000000..e62ee17 --- /dev/null +++ b/internal/pkg/delivery/http/v1/structs/user_easyjson.go @@ -0,0 +1,221 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package structs + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(in *jlexer.Lexer, out *UserInfo) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "username": + out.Username = string(in.String()) + case "avatar": + out.Avatar = string(in.String()) + case "name": + out.Name = string(in.String()) + case "surname": + out.Surname = string(in.String()) + case "about": + out.About = string(in.String()) + case "is_subscribed": + out.IsSubscribed = bool(in.Bool()) + case "subscribers": + out.SubsCount = int(in.Int()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(out *jwriter.Writer, in UserInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"username\":" + out.RawString(prefix) + out.String(string(in.Username)) + } + { + const prefix string = ",\"avatar\":" + out.RawString(prefix) + out.String(string(in.Avatar)) + } + { + const prefix string = ",\"name\":" + out.RawString(prefix) + out.String(string(in.Name)) + } + { + const prefix string = ",\"surname\":" + out.RawString(prefix) + out.String(string(in.Surname)) + } + { + const prefix string = ",\"about\":" + out.RawString(prefix) + out.String(string(in.About)) + } + { + const prefix string = ",\"is_subscribed\":" + out.RawString(prefix) + out.Bool(bool(in.IsSubscribed)) + } + { + const prefix string = ",\"subscribers\":" + out.RawString(prefix) + out.Int(int(in.SubsCount)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v UserInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v UserInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *UserInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *UserInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs(l, v) +} +func easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(in *jlexer.Lexer, out *ProfileInfo) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = int(in.Int()) + case "username": + out.Username = string(in.String()) + case "avatar": + out.Avatar = string(in.String()) + case "subscribers": + out.SubsCount = int(in.Int()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(out *jwriter.Writer, in ProfileInfo) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.Int(int(in.ID)) + } + { + const prefix string = ",\"username\":" + out.RawString(prefix) + out.String(string(in.Username)) + } + { + const prefix string = ",\"avatar\":" + out.RawString(prefix) + out.String(string(in.Avatar)) + } + { + const prefix string = ",\"subscribers\":" + out.RawString(prefix) + out.Int(int(in.SubsCount)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ProfileInfo) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ProfileInfo) MarshalEasyJSON(w *jwriter.Writer) { + easyjson9e1087fdEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ProfileInfo) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ProfileInfo) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson9e1087fdDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgDeliveryHttpV1Structs1(l, v) +} diff --git a/internal/pkg/usecase/pin/update.go b/internal/pkg/usecase/pin/update.go index fc9281c..790e9e6 100644 --- a/internal/pkg/usecase/pin/update.go +++ b/internal/pkg/usecase/pin/update.go @@ -7,11 +7,14 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/pin" ) +//go:generate easyjson update.go + +//easyjson:json type PinUpdateData struct { - Title *string - Description *string - Public *bool - Tags []string + Title *string `json:"title"` + Description *string `json:"description"` + Public *bool `json:"public"` + Tags []string `json:"tags"` } func (p *pinCase) EditPinByID(ctx context.Context, pinID, userID int, updateData *PinUpdateData) error { diff --git a/internal/pkg/usecase/pin/update_easyjson.go b/internal/pkg/usecase/pin/update_easyjson.go new file mode 100644 index 0000000..c3930bc --- /dev/null +++ b/internal/pkg/usecase/pin/update_easyjson.go @@ -0,0 +1,174 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package pin + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson2d86586fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecasePin(in *jlexer.Lexer, out *PinUpdateData) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "title": + if in.IsNull() { + in.Skip() + out.Title = nil + } else { + if out.Title == nil { + out.Title = new(string) + } + *out.Title = string(in.String()) + } + case "description": + if in.IsNull() { + in.Skip() + out.Description = nil + } else { + if out.Description == nil { + out.Description = new(string) + } + *out.Description = string(in.String()) + } + case "public": + if in.IsNull() { + in.Skip() + out.Public = nil + } else { + if out.Public == nil { + out.Public = new(bool) + } + *out.Public = bool(in.Bool()) + } + case "tags": + if in.IsNull() { + in.Skip() + out.Tags = nil + } else { + in.Delim('[') + if out.Tags == nil { + if !in.IsDelim(']') { + out.Tags = make([]string, 0, 4) + } else { + out.Tags = []string{} + } + } else { + out.Tags = (out.Tags)[:0] + } + for !in.IsDelim(']') { + var v1 string + v1 = string(in.String()) + out.Tags = append(out.Tags, v1) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson2d86586fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecasePin(out *jwriter.Writer, in PinUpdateData) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"title\":" + out.RawString(prefix[1:]) + if in.Title == nil { + out.RawString("null") + } else { + out.String(string(*in.Title)) + } + } + { + const prefix string = ",\"description\":" + out.RawString(prefix) + if in.Description == nil { + out.RawString("null") + } else { + out.String(string(*in.Description)) + } + } + { + const prefix string = ",\"public\":" + out.RawString(prefix) + if in.Public == nil { + out.RawString("null") + } else { + out.Bool(bool(*in.Public)) + } + } + { + const prefix string = ",\"tags\":" + out.RawString(prefix) + if in.Tags == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v2, v3 := range in.Tags { + if v2 > 0 { + out.RawByte(',') + } + out.String(string(v3)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v PinUpdateData) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson2d86586fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecasePin(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v PinUpdateData) MarshalEasyJSON(w *jwriter.Writer) { + easyjson2d86586fEncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecasePin(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *PinUpdateData) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson2d86586fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecasePin(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *PinUpdateData) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson2d86586fDecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecasePin(l, v) +} diff --git a/internal/pkg/usecase/user/credentials.go b/internal/pkg/usecase/user/credentials.go index c8d95d6..9e3857c 100644 --- a/internal/pkg/usecase/user/credentials.go +++ b/internal/pkg/usecase/user/credentials.go @@ -1,6 +1,9 @@ package user +//go:generate easyjson credentials.go + +//easyjson:json type UserCredentials struct { - Username string - Password string + Username string `json:"username"` + Password string `json:"password"` } diff --git a/internal/pkg/usecase/user/credentials_easyjson.go b/internal/pkg/usecase/user/credentials_easyjson.go new file mode 100644 index 0000000..d303bee --- /dev/null +++ b/internal/pkg/usecase/user/credentials_easyjson.go @@ -0,0 +1,92 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package user + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson5b679028DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(in *jlexer.Lexer, out *UserCredentials) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "username": + out.Username = string(in.String()) + case "password": + out.Password = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson5b679028EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(out *jwriter.Writer, in UserCredentials) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"username\":" + out.RawString(prefix[1:]) + out.String(string(in.Username)) + } + { + const prefix string = ",\"password\":" + out.RawString(prefix) + out.String(string(in.Password)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v UserCredentials) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson5b679028EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v UserCredentials) MarshalEasyJSON(w *jwriter.Writer) { + easyjson5b679028EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *UserCredentials) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson5b679028DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *UserCredentials) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson5b679028DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(l, v) +} diff --git a/internal/pkg/usecase/user/info.go b/internal/pkg/usecase/user/info.go index 1993261..b504f5f 100644 --- a/internal/pkg/usecase/user/info.go +++ b/internal/pkg/usecase/user/info.go @@ -1,10 +1,13 @@ package user +//go:generate easyjson info.go + +//easyjson:json type ProfileUpdateData struct { - Username *string - Email *string - Name *string - Surname *string + Username *string `json:"username"` + Email *string `json:"email"` + Name *string `json:"name"` + Surname *string `json:"surname"` AboutMe *string `json:"about_me"` - Password *string + Password *string `json:"password"` } diff --git a/internal/pkg/usecase/user/info_easyjson.go b/internal/pkg/usecase/user/info_easyjson.go new file mode 100644 index 0000000..af29bb5 --- /dev/null +++ b/internal/pkg/usecase/user/info_easyjson.go @@ -0,0 +1,192 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package user + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonDdc53814DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(in *jlexer.Lexer, out *ProfileUpdateData) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "username": + if in.IsNull() { + in.Skip() + out.Username = nil + } else { + if out.Username == nil { + out.Username = new(string) + } + *out.Username = string(in.String()) + } + case "email": + if in.IsNull() { + in.Skip() + out.Email = nil + } else { + if out.Email == nil { + out.Email = new(string) + } + *out.Email = string(in.String()) + } + case "name": + if in.IsNull() { + in.Skip() + out.Name = nil + } else { + if out.Name == nil { + out.Name = new(string) + } + *out.Name = string(in.String()) + } + case "surname": + if in.IsNull() { + in.Skip() + out.Surname = nil + } else { + if out.Surname == nil { + out.Surname = new(string) + } + *out.Surname = string(in.String()) + } + case "about_me": + if in.IsNull() { + in.Skip() + out.AboutMe = nil + } else { + if out.AboutMe == nil { + out.AboutMe = new(string) + } + *out.AboutMe = string(in.String()) + } + case "password": + if in.IsNull() { + in.Skip() + out.Password = nil + } else { + if out.Password == nil { + out.Password = new(string) + } + *out.Password = string(in.String()) + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonDdc53814EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(out *jwriter.Writer, in ProfileUpdateData) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"username\":" + out.RawString(prefix[1:]) + if in.Username == nil { + out.RawString("null") + } else { + out.String(string(*in.Username)) + } + } + { + const prefix string = ",\"email\":" + out.RawString(prefix) + if in.Email == nil { + out.RawString("null") + } else { + out.String(string(*in.Email)) + } + } + { + const prefix string = ",\"name\":" + out.RawString(prefix) + if in.Name == nil { + out.RawString("null") + } else { + out.String(string(*in.Name)) + } + } + { + const prefix string = ",\"surname\":" + out.RawString(prefix) + if in.Surname == nil { + out.RawString("null") + } else { + out.String(string(*in.Surname)) + } + } + { + const prefix string = ",\"about_me\":" + out.RawString(prefix) + if in.AboutMe == nil { + out.RawString("null") + } else { + out.String(string(*in.AboutMe)) + } + } + { + const prefix string = ",\"password\":" + out.RawString(prefix) + if in.Password == nil { + out.RawString("null") + } else { + out.String(string(*in.Password)) + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ProfileUpdateData) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonDdc53814EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ProfileUpdateData) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonDdc53814EncodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ProfileUpdateData) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonDdc53814DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ProfileUpdateData) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonDdc53814DecodeGithubComGoParkMailRu20232ONDTeamInternalPkgUsecaseUser(l, v) +} From febce68a1a7c9e007036929ce7068baa188c83a9 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 19 Dec 2023 14:02:00 +0300 Subject: [PATCH 247/266] dev4: removed pull request trigger for deploy workflow --- .github/workflows/deployment.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 9036f2b..7c26ded 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -4,12 +4,9 @@ on: workflow_dispatch: {} push: branches: - - TP-c01_ci-cd + - main - dev4 - pull_request: - types: [opened, edited, reopened] - branches: [main, dev4] - + jobs: build_images: runs-on: ubuntu-latest From e3d14774a2972d3fad1f849f382ce18e0a2f1497 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 19 Dec 2023 15:53:51 +0300 Subject: [PATCH 248/266] dev4: replaced auth service host:port string with env variables, moved auth config to main.go --- cmd/app/config.go | 12 ------------ cmd/app/main.go | 7 +++++++ internal/app/app.go | 6 ++---- 3 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 cmd/app/config.go diff --git a/cmd/app/config.go b/cmd/app/config.go deleted file mode 100644 index 42f8ed3..0000000 --- a/cmd/app/config.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "os" - - "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" -) - -var configFiles = app.ConfigFiles{ - ServerConfigFile: "configs/config.yml", - AddrAuthServer: os.Getenv("AUTH_SERVICE_HOST") + ":" + os.Getenv("AUTH_SERVICE_PORT"), // "localhost:8085", -} diff --git a/cmd/app/main.go b/cmd/app/main.go index 65db49a..c571572 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -4,9 +4,11 @@ import ( "context" "flag" "fmt" + "os" "github.com/go-park-mail-ru/2023_2_OND_team/internal/app" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" + "github.com/joho/godotenv" ) var ( @@ -27,6 +29,7 @@ var ( // @license.url http://www.apache.org/licenses/LICENSE-2.0.html func main() { + godotenv.Load() flag.Parse() ctxBase, cancel := context.WithCancel(context.Background()) defer cancel() @@ -42,5 +45,9 @@ func main() { } defer log.Sync() + configFiles := app.ConfigFiles{ + ServerConfigFile: "configs/config.yml", + AddrAuthServer: os.Getenv("AUTH_SERVICE_HOST") + ":" + os.Getenv("AUTH_SERVICE_PORT"), // "localhost:8085", + } app.Run(ctxBase, log, configFiles) } diff --git a/internal/app/app.go b/internal/app/app.go index a561039..e8c25a4 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,7 +5,6 @@ import ( "os" "time" - "github.com/joho/godotenv" "github.com/microcosm-cc/bluemonday" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -47,8 +46,6 @@ var _timeoutForConnPG = 5 * time.Second const uploadFiles = "upload/" func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { - godotenv.Load() - metrics := metrics.New("pinspire") err := metrics.Registry() if err != nil { @@ -74,7 +71,8 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer connMessMS.Close() - connRealtime, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + // connRealtime, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) + connRealtime, err := grpc.Dial(os.Getenv("REALTIME_SERVICE_HOST")+":"+os.Getenv("REALTIME_SERVICE_PORT"), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(err.Error()) return From 0170995b7a8046e6aad0ac8dae4d73cb2267fec5 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 19 Dec 2023 19:04:42 +0300 Subject: [PATCH 249/266] TP-87a_filtration: add filtration of pin content --- .gitignore | 1 + go.mod | 24 +- go.sum | 569 ++++++++++++++++++++++- internal/app/app.go | 17 +- internal/pkg/delivery/http/v1/pin.go | 7 +- internal/pkg/usecase/image/filtration.go | 80 ++++ internal/pkg/usecase/image/usecase.go | 30 +- internal/pkg/usecase/pin/usecase.go | 5 +- internal/pkg/usecase/user/profile.go | 2 +- 9 files changed, 700 insertions(+), 35 deletions(-) create mode 100644 internal/pkg/usecase/image/filtration.go diff --git a/.gitignore b/.gitignore index e33fae4..ad9c3ba 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ cert/ .env redis.conf inventory +keyVision.json script* \ No newline at end of file diff --git a/go.mod b/go.mod index 9f4f39f..89b662c 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,15 @@ module github.com/go-park-mail-ru/2023_2_OND_team go 1.19 require ( + cloud.google.com/go/vision v1.2.0 + cloud.google.com/go/vision/v2 v2.7.5 github.com/IBM/sarama v1.42.1 github.com/Masterminds/squirrel v1.5.4 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/go-chi/chi/v5 v5.0.10 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.4.0 github.com/jackc/pgx/v5 v5.4.3 github.com/joho/godotenv v1.5.1 github.com/mailru/easyjson v0.7.7 @@ -27,12 +29,16 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/image v0.13.0 - google.golang.org/grpc v1.59.0 + google.golang.org/grpc v1.60.0 google.golang.org/protobuf v1.31.0 nhooyr.io/websocket v1.8.10 ) require ( + cloud.google.com/go v0.111.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/longrunning v0.5.4 // indirect github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect @@ -53,7 +59,11 @@ require ( github.com/go-openapi/swag v0.22.4 // indirect github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -81,14 +91,20 @@ require ( github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tdewolff/minify/v2 v2.20.5 // indirect github.com/tdewolff/parse/v2 v2.7.3 // indirect + go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/api v0.149.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect star-tex.org/x/tex v0.4.0 // indirect diff --git a/go.sum b/go.sum index 1ec2c4a..12a2086 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,67 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/vision v1.2.0 h1:/CsSTkbmO9HC8iQpxbK8ATms3OQaX3YQUeTMGCxlaK4= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.7.5 h1:T/ujUghvEaTb+YnFY/jiYwVAkMbIC8EieK0CJo6B4vg= +cloud.google.com/go/vision/v2 v2.7.5/go.mod h1:GcviprJLFfK9OLf0z8Gm6lQb6ZFUulvpZws+mm6yPLM= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= @@ -8,9 +70,9 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -26,8 +88,23 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -44,16 +121,30 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-fonts/latin-modern v0.3.1 h1:/cT8A7uavYKvglYXvrdDw4oS5ZLkcOU22fa2HJ1/JVM= github.com/go-fonts/latin-modern v0.3.1/go.mod h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0= github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -75,22 +166,97 @@ github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d/go.mod h1:evDB github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= 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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +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= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -98,6 +264,10 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -122,9 +292,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= @@ -151,9 +320,6 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= @@ -164,6 +330,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= @@ -176,17 +343,20 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -209,8 +379,26 @@ github.com/tdewolff/parse/v2 v2.7.3/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9 github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/config v1.4.0 h1:upnMPpMm6WlbZtXoasNkK4f0FhxwS+W4Iqz5oNznehQ= go.uber.org/config v1.4.0/go.mod h1:aCyrMHmUAc/s2h9sv1koP84M9ZF/4K+g2oleyESO/Ig= @@ -223,47 +411,180 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -272,23 +593,76 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191104232314-dc038396d1f0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= @@ -297,12 +671,163 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 h1:EWIeHfGuUf00zrVZGEgYFxok7plSAXBGcH7NNdMAWvA= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3 h1:kzJAXnzZoFbe5bhZd4zjUuHos/I31yH4thfMb/13oVY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -312,6 +837,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -319,8 +845,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= star-tex.org/x/tex v0.4.0 h1:AXUwgpnHLCxZUWW3qrmjv6ezNhH3PjUVBuLLejz2cgU= star-tex.org/x/tex v0.4.0/go.mod h1:w91ycsU/DkkCr7GWr60GPWqp3gn2U+6VX71T0o8k8qE= diff --git a/internal/app/app.go b/internal/app/app.go index e8c25a4..1e22e8c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + vision "cloud.google.com/go/vision/v2/apiv1" authProto "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/messenger" rt "github.com/go-park-mail-ru/2023_2_OND_team/internal/api/realtime" @@ -41,7 +42,10 @@ import ( log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) -var _timeoutForConnPG = 5 * time.Second +var ( + _timeoutForConnPG = 5 * time.Second + timeoutCloudVisionAPI = 10 * time.Second +) const uploadFiles = "upload/" @@ -83,7 +87,16 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { commentRepository := commentRepo.NewCommentRepoPG(pool) - imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles)) + os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "keyVision.json") + visionCtx, cancel := context.WithTimeout(ctx, timeoutCloudVisionAPI) + defer cancel() + visionClient, err := vision.NewImageAnnotatorClient(visionCtx) + if err != nil { + log.Error(err.Error()) + return + } + + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles), visionClient) messageCase := message.New(log, messenger.NewMessengerClient(connMessMS), chat.New(realtime.NewRealTimeChatClient(rtClient), log)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index 19b2831..a0868b3 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -11,6 +11,7 @@ import ( entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" + img "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/image" usecase "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/pin" ) @@ -73,7 +74,11 @@ func (h *HandlerHTTP) CreateNewPin(w http.ResponseWriter, r *http.Request) { err = h.pinCase.CreateNewPin(r.Context(), newPin, mime.Header.Get("Content-Type"), mime.Size, picture) if err != nil { logger.Error(err.Error()) - err = responseError(w, "add_pin", "failed to create pin") + if err == img.ErrExplicitImage { + err = responseError(w, "explicit_pin", err.Error()) + } else { + err = responseError(w, "add_pin", "failed to create pin") + } } else { err = responseOk(http.StatusCreated, w, "pin successfully created", nil) } diff --git a/internal/pkg/usecase/image/filtration.go b/internal/pkg/usecase/image/filtration.go new file mode 100644 index 0000000..856cfa7 --- /dev/null +++ b/internal/pkg/usecase/image/filtration.go @@ -0,0 +1,80 @@ +package image + +import ( + "context" + "errors" + "strings" + + pb "cloud.google.com/go/vision/v2/apiv1/visionpb" +) + +var ( + maxAnnotationsNumber int32 = 15 + explicitLabels = []string{"goose", "duck"} + ErrExplicitImage = errors.New("Image content doesn't comply with service policy") +) + +func CheckAnnotations(annotation *pb.SafeSearchAnnotation) bool { + if annotation.GetAdult() >= pb.Likelihood_LIKELY || + annotation.GetMedical() >= pb.Likelihood_LIKELY || + annotation.GetRacy() >= pb.Likelihood_LIKELY || + annotation.GetViolence() >= pb.Likelihood_LIKELY || + annotation.GetSpoof() >= pb.Likelihood_LIKELY { + return true + } + return false +} + +func GetImageLabels(annotations []*pb.EntityAnnotation) []string { + imgLabels := make([]string, 0, len(annotations)) + for _, label := range annotations { + imgLabels = append(imgLabels, label.GetDescription()) + } + return imgLabels +} + +func CheckCertainLabels(explicitLabels, imgLabels []string) bool { + for _, label := range explicitLabels { + if HasExplicitLabel(label, imgLabels) { + return true + } + } + return false +} + +func HasExplicitLabel(explicitLabel string, imgLabels []string) bool { + for _, label := range imgLabels { + if strings.Contains(strings.ToLower(label), strings.ToLower(explicitLabel)) { + return true + } + } + return false +} + +func CheckExplicit(resp *pb.AnnotateImageResponse, explicitLabels []string) error { + if CheckCertainLabels(explicitLabels, GetImageLabels(resp.GetLabelAnnotations())) || + CheckAnnotations(resp.GetSafeSearchAnnotation()) { + return ErrExplicitImage + } + return nil +} + +func (img *imageCase) FilterImage(ctx context.Context, imgBytes []byte, explicitLabels []string) error { + req := &pb.BatchAnnotateImagesRequest{ + Requests: []*pb.AnnotateImageRequest{ + { + Image: &pb.Image{Content: imgBytes}, + Features: []*pb.Feature{ + {Type: pb.Feature_LABEL_DETECTION, MaxResults: maxAnnotationsNumber}, + {Type: pb.Feature_SAFE_SEARCH_DETECTION, MaxResults: maxAnnotationsNumber}, + }, + }, + }, + } + resp, err := img.visionClient.BatchAnnotateImages(ctx, req) + if err != nil { + return err + } + + return CheckExplicit(resp.GetResponses()[0], explicitLabels) +} diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go index 799ed62..100d106 100644 --- a/internal/pkg/usecase/image/usecase.go +++ b/internal/pkg/usecase/image/usecase.go @@ -2,10 +2,12 @@ package image import ( "bytes" + "context" "errors" "fmt" "io" + vision "cloud.google.com/go/vision/v2/apiv1" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" valid "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image" @@ -14,33 +16,43 @@ import ( const PrefixURLImage = "https://pinspire.online:8081/" -var ErrInvalidImage = errors.New("invalid images") -var ErrUploadFile = errors.New("file upload failed") +var ( + ErrInvalidImage = errors.New("invalid images") + ErrUploadFile = errors.New("file upload failed") +) //go:generate mockgen -destination=./mock/image_mock.go -package=mock -source=usecase.go Usecase type Usecase interface { - UploadImage(path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) + UploadImage(ctx context.Context, path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) } type imageCase struct { - log *log.Logger - repo repo.Repository + log *log.Logger + repo repo.Repository + visionClient *vision.ImageAnnotatorClient } -func New(log *log.Logger, repo repo.Repository) *imageCase { - return &imageCase{log, repo} +func New(log *log.Logger, repo repo.Repository, visionClient *vision.ImageAnnotatorClient) *imageCase { + return &imageCase{log, repo, visionClient} } -func (img *imageCase) UploadImage(path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) { +func (img *imageCase) UploadImage(ctx context.Context, path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) { buf := bytes.NewBuffer(nil) extension, ok := valid.IsValidImage(io.TeeReader(image, buf), mimeType, check) if !ok { return "", ErrInvalidImage } - io.Copy(buf, image) + err := img.FilterImage(ctx, buf.Bytes(), explicitLabels) + if err != nil { + if err == ErrExplicitImage { + return "", err + } + return "", fmt.Errorf("upload image: %w", err) + } + filename, written, err := img.repo.SaveImage(path, extension, buf) if err != nil { return "", fmt.Errorf("upload image: %w", err) diff --git a/internal/pkg/usecase/pin/usecase.go b/internal/pkg/usecase/pin/usecase.go index 3a23401..f288769 100644 --- a/internal/pkg/usecase/pin/usecase.go +++ b/internal/pkg/usecase/pin/usecase.go @@ -46,8 +46,11 @@ func New(log *log.Logger, imgCase image.Usecase, repo repo.Repository) *pinCase } func (p *pinCase) CreateNewPin(ctx context.Context, pin *entity.Pin, mimeTypePicture string, sizePicture int64, picture io.Reader) error { - picturePin, err := p.UploadImage("pins/", mimeTypePicture, sizePicture, picture, check.BothSidesFallIntoRange(100, 6000)) + picturePin, err := p.UploadImage(ctx, "pins/", mimeTypePicture, sizePicture, picture, check.BothSidesFallIntoRange(100, 6000)) if err != nil { + if err == image.ErrExplicitImage { + return err + } return fmt.Errorf("uploading an avatar when creating pin: %w", err) } pin.Picture = picturePin diff --git a/internal/pkg/usecase/user/profile.go b/internal/pkg/usecase/user/profile.go index 1a5d1e4..414c4a0 100644 --- a/internal/pkg/usecase/user/profile.go +++ b/internal/pkg/usecase/user/profile.go @@ -16,7 +16,7 @@ import ( var ErrBadBody = errors.New("bad body avatar") func (u *userCase) UpdateUserAvatar(ctx context.Context, userID int, mimeTypeAvatar string, sizeAvatar int64, avatar io.Reader) error { - avatarProfile, err := u.UploadImage("avatars/", mimeTypeAvatar, sizeAvatar, avatar, check.BothSidesFallIntoRange(200, 1800)) + avatarProfile, err := u.UploadImage(ctx, "avatars/", mimeTypeAvatar, sizeAvatar, avatar, check.BothSidesFallIntoRange(200, 1800)) if err != nil { return fmt.Errorf("uploading an avatar when updating avatar profile: %w", err) } From ff133463b44d49e28407382d1076021a027d7970 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Tue, 19 Dec 2023 19:06:24 +0300 Subject: [PATCH 250/266] TP-87a_filtration: add task with cloud api token provision, add '2023_2_OND_team' folder to dest --- configs/playbook.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/configs/playbook.yml b/configs/playbook.yml index 2daa6b2..c43f92a 100644 --- a/configs/playbook.yml +++ b/configs/playbook.yml @@ -5,8 +5,22 @@ - name: "Provide .env file" copy: src: ../.env - dest: /home/ond_team/go/src/github.com/go-park-mail-ru/ci-cd/.env + dest: /home/ond_team/go/src/github.com/go-park-mail-ru/{{ item }}/.env + with_items: + - ci-cd + - 2023_2_OND_team - name: "Provide redis config" copy: src: ../redis.conf - dest: /home/ond_team/go/src/github.com/go-park-mail-ru/ci-cd/redis.conf + dest: /home/ond_team/go/src/github.com/go-park-mail-ru/{{ item }}/redis.conf + with_items: + - ci-cd + - 2023_2_OND_team + - name: "Provide key for Google Cloud Vision API" + copy: + src: ../keyVision.json + dest: /home/ond_team/go/src/github.com/go-park-mail-ru/{{ item }}/keyVision.json + with_items: + - ci-cd + - 2023_2_OND_team + From 63118112f0e643caa9143112bbd577ce028a5002 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 11:19:09 +0300 Subject: [PATCH 251/266] dev4: replaced google vision client with image filter interface --- internal/app/app.go | 2 +- internal/pkg/usecase/image/filtration.go | 17 +++++++++++++++-- internal/pkg/usecase/image/usecase.go | 13 ++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 1e22e8c..87ea2f6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -96,7 +96,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { return } - imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles), visionClient) + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles), image.NewFilter(visionClient)) messageCase := message.New(log, messenger.NewMessengerClient(connMessMS), chat.New(realtime.NewRealTimeChatClient(rtClient), log)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) diff --git a/internal/pkg/usecase/image/filtration.go b/internal/pkg/usecase/image/filtration.go index 856cfa7..8b73710 100644 --- a/internal/pkg/usecase/image/filtration.go +++ b/internal/pkg/usecase/image/filtration.go @@ -5,6 +5,7 @@ import ( "errors" "strings" + vision "cloud.google.com/go/vision/v2/apiv1" pb "cloud.google.com/go/vision/v2/apiv1/visionpb" ) @@ -14,6 +15,18 @@ var ( ErrExplicitImage = errors.New("Image content doesn't comply with service policy") ) +type ImageFilter interface { + Filter(ctx context.Context, imgBytes []byte, explicitLabels []string) error +} + +type googleVision struct { + visionClient *vision.ImageAnnotatorClient +} + +func NewFilter(client *vision.ImageAnnotatorClient) *googleVision { + return &googleVision{client} +} + func CheckAnnotations(annotation *pb.SafeSearchAnnotation) bool { if annotation.GetAdult() >= pb.Likelihood_LIKELY || annotation.GetMedical() >= pb.Likelihood_LIKELY || @@ -59,7 +72,7 @@ func CheckExplicit(resp *pb.AnnotateImageResponse, explicitLabels []string) erro return nil } -func (img *imageCase) FilterImage(ctx context.Context, imgBytes []byte, explicitLabels []string) error { +func (vision *googleVision) Filter(ctx context.Context, imgBytes []byte, explicitLabels []string) error { req := &pb.BatchAnnotateImagesRequest{ Requests: []*pb.AnnotateImageRequest{ { @@ -71,7 +84,7 @@ func (img *imageCase) FilterImage(ctx context.Context, imgBytes []byte, explicit }, }, } - resp, err := img.visionClient.BatchAnnotateImages(ctx, req) + resp, err := vision.visionClient.BatchAnnotateImages(ctx, req) if err != nil { return err } diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go index 100d106..5ace111 100644 --- a/internal/pkg/usecase/image/usecase.go +++ b/internal/pkg/usecase/image/usecase.go @@ -7,7 +7,6 @@ import ( "fmt" "io" - vision "cloud.google.com/go/vision/v2/apiv1" repo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/image" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" valid "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image" @@ -27,13 +26,13 @@ type Usecase interface { } type imageCase struct { - log *log.Logger - repo repo.Repository - visionClient *vision.ImageAnnotatorClient + log *log.Logger + repo repo.Repository + filter ImageFilter } -func New(log *log.Logger, repo repo.Repository, visionClient *vision.ImageAnnotatorClient) *imageCase { - return &imageCase{log, repo, visionClient} +func New(log *log.Logger, repo repo.Repository, filter ImageFilter) *imageCase { + return &imageCase{log, repo, filter} } func (img *imageCase) UploadImage(ctx context.Context, path string, mimeType string, size int64, image io.Reader, check check.CheckSize) (string, error) { @@ -45,7 +44,7 @@ func (img *imageCase) UploadImage(ctx context.Context, path string, mimeType str } io.Copy(buf, image) - err := img.FilterImage(ctx, buf.Bytes(), explicitLabels) + err := img.filter.Filter(ctx, buf.Bytes(), explicitLabels) if err != nil { if err == ErrExplicitImage { return "", err From e4dc4784a6f91446c365d923de6f26dae8787a65 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 11:27:37 +0300 Subject: [PATCH 252/266] de4: changed deployment branch, add cloud api token to the .env file --- .github/workflows/deployment.yml | 2 +- configs/playbook.yml | 8 -------- internal/app/app.go | 14 ++++++++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 7c26ded..97709e8 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -42,7 +42,7 @@ jobs: key: ${{ secrets.PRIVATE_KEY }} script: | cd ${{ secrets.PINSPIRE_BACKEND_PATH }} - sudo git switch TP-c01_ci-cd + sudo git switch dev4 sudo git pull - name: deploy application uses: appleboy/ssh-action@master diff --git a/configs/playbook.yml b/configs/playbook.yml index c43f92a..2e134dc 100644 --- a/configs/playbook.yml +++ b/configs/playbook.yml @@ -16,11 +16,3 @@ with_items: - ci-cd - 2023_2_OND_team - - name: "Provide key for Google Cloud Vision API" - copy: - src: ../keyVision.json - dest: /home/ond_team/go/src/github.com/go-park-mail-ru/{{ item }}/keyVision.json - with_items: - - ci-cd - - 2023_2_OND_team - diff --git a/internal/app/app.go b/internal/app/app.go index 87ea2f6..5f69dac 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,10 +2,13 @@ package app import ( "context" + "encoding/base64" + "fmt" "os" "time" "github.com/microcosm-cc/bluemonday" + "google.golang.org/api/option" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -87,10 +90,17 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { commentRepository := commentRepo.NewCommentRepoPG(pool) - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "keyVision.json") visionCtx, cancel := context.WithTimeout(ctx, timeoutCloudVisionAPI) defer cancel() - visionClient, err := vision.NewImageAnnotatorClient(visionCtx) + + token, err := base64.StdEncoding.DecodeString(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")) + fmt.Println(string(token)) + if err != nil { + log.Error(err.Error()) + return + } + + visionClient, err := vision.NewImageAnnotatorClient(visionCtx, option.WithCredentialsJSON(token)) if err != nil { log.Error(err.Error()) return From cae7718e5d6ac7014fe47d7d8719612448cb40e9 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 13:49:26 +0300 Subject: [PATCH 253/266] dev4: add images volume for main service container --- deployments/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 44d21c6..dc87d2d 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -49,6 +49,7 @@ services: volumes: - '/home/ond_team/cert/fullchain.pem:/home/ond_team/cert/fullchain.pem:ro' - '/home/ond_team/cert/privkey.pem:/home/ond_team/cert/privkey.pem:ro' + - '/home/ond_team/go/src/github.com/go-park-mail-ru/ci-cd/upload:/upload' depends_on: postgres: condition: 'service_healthy' From e71947b9215c31086230a0e62118efb3fb0b807a Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 15:58:48 +0300 Subject: [PATCH 254/266] dev4: changed makefile variable, included all branches in CI --- .github/workflows/ci.yml | 1 - Makefile | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0f2a3f..863258e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: push: {} pull_request: types: [opened, edited, reopened] - branches: [main, dev4] jobs: test: diff --git a/Makefile b/Makefile index ee03e17..f054d18 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,7 @@ COV_OUT=coverage.out COV_HTML=coverage.html CURRCOVER=github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1 -PROJECT_DIR = $(shell pwd) -PROJECT_BIN = $(PROJECT_DIR)/bin +PROJECT_BIN = $(CURDIR)/bin $(shell [ -f bin ] || mkdir -p $(PROJECT_BIN)) GOLANGCI_LINT = $(PROJECT_BIN)/golangci-lint From a57dbecd992dd544e73a84f3039257fe2d60dd0b Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 19:55:30 +0300 Subject: [PATCH 255/266] dev4: add skip_files for linter --- configs/.golangci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/configs/.golangci.yml b/configs/.golangci.yml index cac2d04..095c0a9 100644 --- a/configs/.golangci.yml +++ b/configs/.golangci.yml @@ -11,8 +11,11 @@ run: # Timeout for analysis, e.g. 30s, 5m. # Default: 1m timeout: 3m - skip-dirs: - - .. + skip-files: + - internal/pkg/delivery/http/v1/auth.go + - internal/pkg/usecase/image/filtration.go + - internal/pkg/usecase/pin/check.go + - internal/pkg/usecase/user/auth.go From 39f6e2f43722df36d6baf0b276b66a8638ea9e9d Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 20:37:12 +0300 Subject: [PATCH 256/266] dev4: fix comment --- internal/pkg/repository/comment/repo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/pkg/repository/comment/repo.go b/internal/pkg/repository/comment/repo.go index cfaf576..f0b3715 100644 --- a/internal/pkg/repository/comment/repo.go +++ b/internal/pkg/repository/comment/repo.go @@ -75,6 +75,7 @@ func (c *commentRepoPG) GetCommensToPin(ctx context.Context, pinID, lastID, coun } for rows.Next() { + cmt.Author = &user.User{} err = rows.Scan(&cmt.ID, &cmt.Author.ID, &cmt.Author.Username, &cmt.Author.Avatar, &cmt.Content) if err != nil { return cmts, fmt.Errorf("scan a comment when getting comments on a pin: %w", err) From 2a28e24f1c3f00c6907927d9c492bd10e9f06109 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Wed, 20 Dec 2023 20:42:40 +0300 Subject: [PATCH 257/266] dev4: fix linter --- configs/.golangci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/configs/.golangci.yml b/configs/.golangci.yml index 095c0a9..52e544b 100644 --- a/configs/.golangci.yml +++ b/configs/.golangci.yml @@ -11,11 +11,8 @@ run: # Timeout for analysis, e.g. 30s, 5m. # Default: 1m timeout: 3m - skip-files: - - internal/pkg/delivery/http/v1/auth.go - - internal/pkg/usecase/image/filtration.go - - internal/pkg/usecase/pin/check.go - - internal/pkg/usecase/user/auth.go + skip-dirs: + .. From d582b3fe51b6734043d23f93941f3d989ac9d2a7 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 24 Dec 2023 13:25:09 +0300 Subject: [PATCH 258/266] dev4: added profanityCensor, changed XSS sanitizer, added convertedHTTP for structs mapping, xss/profanity sanitizing, added image filtration by image text --- go.mod | 3 +- go.sum | 13 ++ internal/app/app.go | 23 +-- internal/pkg/delivery/http/v1/board.go | 34 +---- internal/pkg/delivery/http/v1/chat.go | 2 +- internal/pkg/delivery/http/v1/comment.go | 2 +- internal/pkg/delivery/http/v1/converter.go | 136 ++++++++++++++++++ internal/pkg/delivery/http/v1/handler.go | 4 +- internal/pkg/delivery/http/v1/pin.go | 2 +- internal/pkg/delivery/http/v1/profile.go | 30 +--- internal/pkg/delivery/http/v1/search.go | 6 +- .../pkg/delivery/http/v1/structs/board.go | 22 +++ internal/pkg/delivery/http/v1/structs/user.go | 19 +++ internal/pkg/delivery/http/v1/subscription.go | 2 +- internal/pkg/entity/board/board.go | 15 +- internal/pkg/entity/comment/comment.go | 13 ++ internal/pkg/entity/message/message.go | 10 ++ internal/pkg/entity/pin/pin.go | 17 +++ internal/pkg/entity/search/search.go | 21 +-- internal/pkg/entity/user/user.go | 27 +++- internal/pkg/usecase/board/get.go | 4 - internal/pkg/usecase/board/update.go | 4 - internal/pkg/usecase/board/usecase.go | 6 +- internal/pkg/usecase/image/filtration.go | 36 +++-- internal/pkg/usecase/search/usecase.go | 21 +-- internal/pkg/usecase/subscription/get.go | 4 - internal/pkg/usecase/subscription/usecase.go | 12 +- internal/pkg/validation/censor.go | 105 ++++++++++++++ internal/pkg/validation/xss.go | 19 +++ pkg/levenstein/levenstein.go | 37 +++++ 30 files changed, 495 insertions(+), 154 deletions(-) create mode 100644 internal/pkg/delivery/http/v1/converter.go create mode 100644 internal/pkg/validation/censor.go create mode 100644 internal/pkg/validation/xss.go create mode 100644 pkg/levenstein/levenstein.go diff --git a/go.mod b/go.mod index 89b662c..64ddf05 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( cloud.google.com/go/longrunning v0.5.4 // indirect github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/TwiN/go-away v1.6.12 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/benoitkugler/textprocessing v0.0.3 // indirect @@ -98,7 +99,7 @@ require ( golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/api v0.149.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 12a2086..2c71421 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,10 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/TwiN/go-away v1.6.12 h1:80AjDyeTjfQaSFYbALzRcDKMAmxKW0a5PoxwXKZlW2A= +github.com/TwiN/go-away v1.6.12/go.mod h1:MpvIC9Li3minq+CGgbgUDvQ9tDaeW35k5IXZrF9MVas= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -143,6 +147,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -292,8 +297,11 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= @@ -320,6 +328,9 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pashagolub/pgxmock/v2 v2.12.0 h1:IVRmQtVFNCoq7NOZ+PdfvB6fwnLJmEuWDhnc3yrDxBs= github.com/pashagolub/pgxmock/v2 v2.12.0/go.mod h1:D3YslkN/nJ4+umVqWmbwfSXugJIjPMChkGBG47OJpNw= @@ -606,6 +617,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/app/app.go b/internal/app/app.go index 5f69dac..bdcbea2 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -3,10 +3,10 @@ package app import ( "context" "encoding/base64" - "fmt" "os" "time" + goaway "github.com/TwiN/go-away" "github.com/microcosm-cc/bluemonday" "google.golang.org/api/option" "google.golang.org/grpc" @@ -42,6 +42,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/search" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/subscription" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" + validate "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" ) @@ -70,7 +71,6 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer pool.Close() - // connMessMS, err := grpc.Dial("localhost:8095", grpc.WithTransportCredentials(insecure.NewCredentials())) connMessMS, err := grpc.Dial(os.Getenv("MESSENGER_SERVICE_HOST")+":"+os.Getenv("MESSENGER_SERVICE_PORT"), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(err.Error()) @@ -78,7 +78,6 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { } defer connMessMS.Close() - // connRealtime, err := grpc.Dial("localhost:8090", grpc.WithTransportCredentials(insecure.NewCredentials())) connRealtime, err := grpc.Dial(os.Getenv("REALTIME_SERVICE_HOST")+":"+os.Getenv("REALTIME_SERVICE_PORT"), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Error(err.Error()) @@ -94,19 +93,23 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { defer cancel() token, err := base64.StdEncoding.DecodeString(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")) - fmt.Println(string(token)) if err != nil { log.Error(err.Error()) return } - visionClient, err := vision.NewImageAnnotatorClient(visionCtx, option.WithCredentialsJSON(token)) if err != nil { log.Error(err.Error()) return } - imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles), image.NewFilter(visionClient)) + profanityCensor := goaway.NewProfanityDetector().WithCustomDictionary( + append(goaway.DefaultProfanities, validate.GetLabels()...), + goaway.DefaultFalsePositives, + goaway.DefaultFalseNegatives, + ) + + imgCase := image.New(log, imgRepo.NewImageRepoFS(uploadFiles), image.NewFilter(visionClient, validate.NewCensor(profanityCensor))) messageCase := message.New(log, messenger.NewMessengerClient(connMessMS), chat.New(realtime.NewRealTimeChatClient(rtClient), log)) pinCase := pin.New(log, imgCase, pinRepo.NewPinRepoPG(pool)) @@ -127,13 +130,13 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { defer conn.Close() ac := auth.New(authProto.NewAuthClient(conn)) - handler := deliveryHTTP.New(log, deliveryHTTP.UsecaseHub{ + handler := deliveryHTTP.New(log, deliveryHTTP.NewConverterHTTP(validate.NewSanitizerXSS(bluemonday.UGCPolicy()), validate.NewCensor(profanityCensor)), deliveryHTTP.UsecaseHub{ AuhtCase: ac, UserCase: user.New(log, imgCase, userRepo.NewUserRepoPG(pool)), PinCase: pinCase, - BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), - SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool), bluemonday.UGCPolicy()), - SearchCase: search.New(log, searchRepo.NewSearchRepoPG(pool), bluemonday.UGCPolicy()), + BoardCase: board.New(log, boardRepo.NewBoardRepoPG(pool), userRepo.NewUserRepoPG(pool)), + SubscriptionCase: subscription.New(log, subRepo.NewSubscriptionRepoPG(pool), userRepo.NewUserRepoPG(pool)), + SearchCase: search.New(log, searchRepo.NewSearchRepoPG(pool)), MessageCase: messageCase, CommentCase: comment.New(commentRepo.NewCommentRepoPG(pool), pinCase, notifyCase), }) diff --git a/internal/pkg/delivery/http/v1/board.go b/internal/pkg/delivery/http/v1/board.go index 2b02193..ba7e9e3 100644 --- a/internal/pkg/delivery/http/v1/board.go +++ b/internal/pkg/delivery/http/v1/board.go @@ -17,33 +17,6 @@ import ( var TimeFormat = "2006-01-02" -func ToCertainBoardFromService(board entity.BoardWithContent) structs.CertainBoard { - return structs.CertainBoard{ - ID: board.BoardInfo.ID, - AuthorID: board.BoardInfo.AuthorID, - Title: board.BoardInfo.Title, - Description: board.BoardInfo.Description, - CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), - PinsNumber: board.PinsNumber, - Pins: board.Pins, - Tags: board.TagTitles, - } -} - -func ToCertainBoardUsernameFromService(board entity.BoardWithContent, username string) structs.CertainBoardWithUsername { - return structs.CertainBoardWithUsername{ - ID: board.BoardInfo.ID, - AuthorID: board.BoardInfo.AuthorID, - AuthorUsername: username, - Title: board.BoardInfo.Title, - Description: board.BoardInfo.Description, - CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), - PinsNumber: board.PinsNumber, - Pins: board.Pins, - Tags: board.TagTitles, - } -} - func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { logger := h.getRequestLogger(r) if contentType := r.Header.Get("Content-Type"); contentType != ApplicationJson { @@ -73,7 +46,6 @@ func (h *HandlerHTTP) CreateNewBoard(w http.ResponseWriter, r *http.Request) { tagTitles := make([]string, 0) if newBoard.Tags != nil { tagTitles = append(tagTitles, newBoard.Tags...) - } authorID := r.Context().Value(auth.KeyCurrentUserID).(int) @@ -120,7 +92,7 @@ func (h *HandlerHTTP) GetUserBoards(w http.ResponseWriter, r *http.Request) { userBoards := make([]structs.CertainBoard, 0, len(boards)) for _, board := range boards { - userBoards = append(userBoards, ToCertainBoardFromService(board)) + userBoards = append(userBoards, h.converter.ToCertainBoardFromService(&board)) } err = responseOk(http.StatusOK, w, "got user boards successfully", userBoards) if err != nil { @@ -149,7 +121,7 @@ func (h *HandlerHTTP) GetCertainBoard(w http.ResponseWriter, r *http.Request) { return } - err = responseOk(http.StatusOK, w, "got certain board successfully", ToCertainBoardUsernameFromService(board, username)) + err = responseOk(http.StatusOK, w, "got certain board successfully", h.converter.ToCertainBoardUsernameFromService(&board, username)) if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) @@ -176,7 +148,7 @@ func (h *HandlerHTTP) GetBoardInfoForUpdate(w http.ResponseWriter, r *http.Reque return } - err = responseOk(http.StatusOK, w, "got certain board successfully", map[string]interface{}{"board": board, "tags": tagTitles}) + err = responseOk(http.StatusOK, w, "got certain board successfully", map[string]interface{}{"board": h.converter.ToBoardFromService(&board), "tags": tagTitles}) if err != nil { logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/pkg/delivery/http/v1/chat.go b/internal/pkg/delivery/http/v1/chat.go index 09e64cb..bf7b229 100644 --- a/internal/pkg/delivery/http/v1/chat.go +++ b/internal/pkg/delivery/http/v1/chat.go @@ -170,7 +170,7 @@ func (h *HandlerHTTP) GetMessagesFromChat(w http.ResponseWriter, r *http.Request logger.Warn(err.Error()) } err = responseOk(http.StatusOK, w, "messages received successfully", map[string]any{ - "messages": feed, + "messages": h.converter.ToMessagesFromService(feed), "lastID": newLastID, }) if err != nil { diff --git a/internal/pkg/delivery/http/v1/comment.go b/internal/pkg/delivery/http/v1/comment.go index a5dd834..3f0e25f 100644 --- a/internal/pkg/delivery/http/v1/comment.go +++ b/internal/pkg/delivery/http/v1/comment.go @@ -111,7 +111,7 @@ func (h *HandlerHTTP) ViewFeedComment(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) } - err = responseOk(http.StatusOK, w, "feed comment to pin", map[string]any{"comments": feed, "lastID": newLastID}) + err = responseOk(http.StatusOK, w, "feed comment to pin", map[string]any{"comments": h.converter.ToCommentsFromService(feed), "lastID": newLastID}) if err != nil { logger.Error(err.Error()) } diff --git a/internal/pkg/delivery/http/v1/converter.go b/internal/pkg/delivery/http/v1/converter.go new file mode 100644 index 0000000..c5c9346 --- /dev/null +++ b/internal/pkg/delivery/http/v1/converter.go @@ -0,0 +1,136 @@ +package v1 + +import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" + entity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/comment" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/message" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/pin" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" +) + +type converterHTTP struct { + sanitizer validation.SanitizerXSS + censor validation.ProfanityCensor +} + +func NewConverterHTTP(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) converterHTTP { + return converterHTTP{sanitizer, censor} +} + +func (c *converterHTTP) ToCertainBoardFromService(board *entity.BoardWithContent) structs.CertainBoard { + b := structs.CertainBoard{ + ID: board.BoardInfo.ID, + AuthorID: board.BoardInfo.AuthorID, + Title: board.BoardInfo.Title, + Description: board.BoardInfo.Description, + CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), + PinsNumber: board.PinsNumber, + Pins: board.Pins, + Tags: board.TagTitles, + } + b.Sanitize(c.sanitizer, c.censor) + return b +} + +func (c *converterHTTP) ToCertainBoardUsernameFromService(board *entity.BoardWithContent, username string) structs.CertainBoardWithUsername { + b := structs.CertainBoardWithUsername{ + ID: board.BoardInfo.ID, + AuthorID: board.BoardInfo.AuthorID, + AuthorUsername: username, + Title: board.BoardInfo.Title, + Description: board.BoardInfo.Description, + CreatedAt: board.BoardInfo.CreatedAt.Format(TimeFormat), + PinsNumber: board.PinsNumber, + Pins: board.Pins, + Tags: board.TagTitles, + } + b.Sanitize(c.sanitizer, c.censor) + return b +} + +func (c *converterHTTP) ToBoardFromService(board *entity.Board) *entity.Board { + board.Sanitize(c.sanitizer, c.censor) + return board +} + +func (c *converterHTTP) ToUsersForSearchFromService(users []search.UserForSearch) []search.UserForSearch { + for id := range users { + users[id].Sanitize(c.sanitizer, c.censor) + } + return users +} + +func (c *converterHTTP) ToBoardsForSearchFromService(boards []search.BoardForSearch) []search.BoardForSearch { + for id := range boards { + boards[id].Sanitize(c.sanitizer, c.censor) + } + return boards +} + +func (c *converterHTTP) ToPinsForSearchFromService(pins []search.PinForSearch) []search.PinForSearch { + for id := range pins { + pins[id].Sanitize(c.sanitizer, c.censor) + } + return pins +} + +func (c *converterHTTP) ToSubscriptionUsersFromService(users []user.SubscriptionUser) []user.SubscriptionUser { + for id := range users { + users[id].Sanitize(c.sanitizer, c.censor) + } + return users +} + +func (c *converterHTTP) ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount int) structs.UserInfo { + u := structs.UserInfo{ + ID: user.ID, + Username: user.Username, + Avatar: user.Avatar, + Name: user.Name.String, + Surname: user.Surname.String, + About: user.AboutMe.String, + IsSubscribed: isSubscribed, + SubsCount: subsCount, + } + u.Sanitize(c.sanitizer, c.censor) + return u +} + +func (c *converterHTTP) ToProfileInfoFromService(user *userEntity.User, subsCount int) structs.ProfileInfo { + p := structs.ProfileInfo{ + ID: user.ID, + Username: user.Username, + Avatar: user.Avatar, + SubsCount: subsCount, + } + p.Sanitize(c.sanitizer, c.censor) + return p +} + +func (c *converterHTTP) ToUserFromService(user *userEntity.User) *userEntity.User { + user.Sanitize(c.sanitizer, c.censor) + return user +} + +func (c *converterHTTP) ToPinFromService(pin *pin.Pin) *pin.Pin { + pin.Sanitize(c.sanitizer, c.censor) + return pin +} + +func (c *converterHTTP) ToMessagesFromService(mes []message.Message) []message.Message { + for id := range mes { + mes[id].Sanitize(c.sanitizer, c.censor) + } + return mes +} + +func (c *converterHTTP) ToCommentsFromService(comments []comment.Comment) []comment.Comment { + for id := range comments { + comments[id].Sanitize(c.sanitizer, c.censor) + } + return comments +} diff --git a/internal/pkg/delivery/http/v1/handler.go b/internal/pkg/delivery/http/v1/handler.go index 736654e..ef7cf15 100644 --- a/internal/pkg/delivery/http/v1/handler.go +++ b/internal/pkg/delivery/http/v1/handler.go @@ -14,6 +14,7 @@ import ( type HandlerHTTP struct { log *logger.Logger + converter converterHTTP authCase auth.Usecase userCase user.Usecase pinCase pin.Usecase @@ -24,9 +25,10 @@ type HandlerHTTP struct { commentCase comment.Usecase } -func New(log *logger.Logger, hub UsecaseHub) *HandlerHTTP { +func New(log *logger.Logger, converter converterHTTP, hub UsecaseHub) *HandlerHTTP { return &HandlerHTTP{ log: log, + converter: converter, authCase: hub.AuhtCase, userCase: hub.UserCase, pinCase: hub.PinCase, diff --git a/internal/pkg/delivery/http/v1/pin.go b/internal/pkg/delivery/http/v1/pin.go index a0868b3..783f811 100644 --- a/internal/pkg/delivery/http/v1/pin.go +++ b/internal/pkg/delivery/http/v1/pin.go @@ -179,7 +179,7 @@ func (h *HandlerHTTP) ViewPin(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "edit_pin", "internal error") } else { - err = responseOk(http.StatusOK, w, "pin was successfully received", pin) + err = responseOk(http.StatusOK, w, "pin was successfully received", h.converter.ToPinFromService(pin)) } if err != nil { logger.Error(err.Error()) diff --git a/internal/pkg/delivery/http/v1/profile.go b/internal/pkg/delivery/http/v1/profile.go index be60ccb..7e812f6 100644 --- a/internal/pkg/delivery/http/v1/profile.go +++ b/internal/pkg/delivery/http/v1/profile.go @@ -7,36 +7,12 @@ import ( "github.com/go-chi/chi/v5" errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" - "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/structs" - userEntity "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/middleware/auth" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/usecase/user" log "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" "github.com/mailru/easyjson" ) -func ToUserInfoFromService(user *userEntity.User, isSubscribed bool, subsCount int) structs.UserInfo { - return structs.UserInfo{ - ID: user.ID, - Username: user.Username, - Avatar: user.Avatar, - Name: user.Name.String, - Surname: user.Surname.String, - About: user.AboutMe.String, - IsSubscribed: isSubscribed, - SubsCount: subsCount, - } -} - -func ToProfileInfoFromService(user *userEntity.User, subsCount int) structs.ProfileInfo { - return structs.ProfileInfo{ - ID: user.ID, - Username: user.Username, - Avatar: user.Avatar, - SubsCount: subsCount, - } -} - func (h *HandlerHTTP) GetUserInfo(w http.ResponseWriter, r *http.Request) { userIdParam := chi.URLParam(r, "userID") userID, err := strconv.ParseInt(userIdParam, 10, 64) @@ -47,7 +23,7 @@ func (h *HandlerHTTP) GetUserInfo(w http.ResponseWriter, r *http.Request) { if user, isSubscribed, subsCount, err := h.userCase.GetUserInfo(r.Context(), int(userID)); err != nil { h.responseErr(w, r, err) - } else if err := responseOk(http.StatusOK, w, "got user info successfully", ToUserInfoFromService(user, isSubscribed, subsCount)); err != nil { + } else if err := responseOk(http.StatusOK, w, "got user info successfully", h.converter.ToUserInfoFromService(user, isSubscribed, subsCount)); err != nil { h.responseErr(w, r, err) } } @@ -55,7 +31,7 @@ func (h *HandlerHTTP) GetUserInfo(w http.ResponseWriter, r *http.Request) { func (h *HandlerHTTP) GetProfileHeaderInfo(w http.ResponseWriter, r *http.Request) { if user, subsCount, err := h.userCase.GetProfileInfo(r.Context()); err != nil { h.responseErr(w, r, err) - } else if err := responseOk(http.StatusOK, w, "got profile info successfully", ToProfileInfoFromService(user, subsCount)); err != nil { + } else if err := responseOk(http.StatusOK, w, "got profile info successfully", h.converter.ToProfileInfoFromService(user, subsCount)); err != nil { h.responseErr(w, r, err) } } @@ -148,7 +124,7 @@ func (h *HandlerHTTP) GetProfileInfo(w http.ResponseWriter, r *http.Request) { logger.Error(err.Error()) err = responseError(w, "get_info", "failed to get user information") } else { - err = responseOk(http.StatusOK, w, "user data has been successfully received", user) + err = responseOk(http.StatusOK, w, "user data has been successfully received", h.converter.ToUserFromService(user)) } if err != nil { diff --git a/internal/pkg/delivery/http/v1/search.go b/internal/pkg/delivery/http/v1/search.go index 4ec1e79..ae72332 100644 --- a/internal/pkg/delivery/http/v1/search.go +++ b/internal/pkg/delivery/http/v1/search.go @@ -36,7 +36,7 @@ func (h *HandlerHTTP) SearchUsers(w http.ResponseWriter, r *http.Request) { if users, err := h.searchCase.GetUsers(r.Context(), opts); err != nil { h.responseErr(w, r, err) - } else if err := responseOk(http.StatusOK, w, "got users sucessfully", users); err != nil { + } else if err := responseOk(http.StatusOK, w, "got users sucessfully", h.converter.ToUsersForSearchFromService(users)); err != nil { h.responseErr(w, r, err) } } @@ -50,7 +50,7 @@ func (h *HandlerHTTP) SearchBoards(w http.ResponseWriter, r *http.Request) { if boards, err := h.searchCase.GetBoards(r.Context(), opts); err != nil { h.responseErr(w, r, err) - } else if err := responseOk(http.StatusOK, w, "got boards sucessfully", boards); err != nil { + } else if err := responseOk(http.StatusOK, w, "got boards sucessfully", h.converter.ToBoardsForSearchFromService(boards)); err != nil { h.responseErr(w, r, err) } } @@ -64,7 +64,7 @@ func (h *HandlerHTTP) SearchPins(w http.ResponseWriter, r *http.Request) { if pins, err := h.searchCase.GetPins(r.Context(), opts); err != nil { h.responseErr(w, r, err) - } else if err := responseOk(http.StatusOK, w, "got pins sucessfully", pins); err != nil { + } else if err := responseOk(http.StatusOK, w, "got pins sucessfully", h.converter.ToPinsForSearchFromService(pins)); err != nil { h.responseErr(w, r, err) } } diff --git a/internal/pkg/delivery/http/v1/structs/board.go b/internal/pkg/delivery/http/v1/structs/board.go index 9e42ece..4c04755 100644 --- a/internal/pkg/delivery/http/v1/structs/board.go +++ b/internal/pkg/delivery/http/v1/structs/board.go @@ -5,6 +5,7 @@ import ( "unicode" errHTTP "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/delivery/http/v1/errors" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) //go:generate easyjson board.go @@ -46,6 +47,27 @@ type CertainBoardWithUsername struct { Tags []string `json:"tags" example:"['love', 'green']"` } +func (b *CertainBoard) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if b != nil { + b.Title = sanitizer.Sanitize(censor.Sanitize(b.Title)) + b.Description = sanitizer.Sanitize(censor.Sanitize(b.Description)) + for id, title := range b.Tags { + b.Tags[id] = sanitizer.Sanitize(censor.Sanitize(title)) + } + } +} + +func (b *CertainBoardWithUsername) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if b != nil { + b.Title = sanitizer.Sanitize(censor.Sanitize(b.Title)) + b.Description = sanitizer.Sanitize(censor.Sanitize(b.Description)) + b.AuthorUsername = sanitizer.Sanitize(censor.Sanitize(b.AuthorUsername)) + for id, title := range b.Tags { + b.Tags[id] = sanitizer.Sanitize(censor.Sanitize(title)) + } + } +} + //easyjson:json type DeletePinFromBoard struct { PinID int `json:"pin_id" example:"22"` diff --git a/internal/pkg/delivery/http/v1/structs/user.go b/internal/pkg/delivery/http/v1/structs/user.go index 3afc2aa..ab7d8f8 100644 --- a/internal/pkg/delivery/http/v1/structs/user.go +++ b/internal/pkg/delivery/http/v1/structs/user.go @@ -1,5 +1,9 @@ package structs +import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" +) + //go:generate easyjson user.go //easyjson:json @@ -14,6 +18,15 @@ type UserInfo struct { SubsCount int `json:"subscribers" example:"23"` } +func (u *UserInfo) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if u != nil { + u.Username = sanitizer.Sanitize(censor.Sanitize(u.Username)) + u.Name = sanitizer.Sanitize(censor.Sanitize(u.Name)) + u.Surname = sanitizer.Sanitize(censor.Sanitize(u.Surname)) + u.About = sanitizer.Sanitize(censor.Sanitize(u.About)) + } +} + //easyjson:json type ProfileInfo struct { ID int `json:"id" example:"1"` @@ -21,3 +34,9 @@ type ProfileInfo struct { Avatar string `json:"avatar" example:"/pic1"` SubsCount int `json:"subscribers" example:"12"` } + +func (p *ProfileInfo) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if p != nil { + p.Username = sanitizer.Sanitize(censor.Sanitize(p.Username)) + } +} diff --git a/internal/pkg/delivery/http/v1/subscription.go b/internal/pkg/delivery/http/v1/subscription.go index 7444bdf..7e18482 100644 --- a/internal/pkg/delivery/http/v1/subscription.go +++ b/internal/pkg/delivery/http/v1/subscription.go @@ -81,7 +81,7 @@ func (h *HandlerHTTP) GetSubscriptionInfoForUser(w http.ResponseWriter, r *http. if users, err := h.subCase.GetSubscriptionInfoForUser(r.Context(), opts); err != nil { h.responseErr(w, r, err) - } else if err := responseOk(http.StatusOK, w, "got subscription info successfully", users); err != nil { + } else if err := responseOk(http.StatusOK, w, "got subscription info successfully", h.converter.ToSubscriptionUsersFromService(users)); err != nil { h.responseErr(w, r, err) } } diff --git a/internal/pkg/entity/board/board.go b/internal/pkg/entity/board/board.go index d695468..237cbc6 100644 --- a/internal/pkg/entity/board/board.go +++ b/internal/pkg/entity/board/board.go @@ -3,7 +3,7 @@ package board import ( "time" - "github.com/microcosm-cc/bluemonday" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) //go:generate easyjson board.go @@ -28,14 +28,9 @@ type BoardWithContent struct { TagTitles []string } -func (b *Board) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(b.Title) - sanitizer.Sanitize(b.Description) -} - -func (b *BoardWithContent) Sanitize(sanitizer *bluemonday.Policy) { - b.BoardInfo.Sanitize(sanitizer) - for id, title := range b.TagTitles { - b.TagTitles[id] = sanitizer.Sanitize(title) +func (b *Board) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if b != nil { + b.Title = sanitizer.Sanitize(censor.Sanitize(b.Title)) + b.Description = sanitizer.Sanitize(censor.Sanitize(b.Description)) } } diff --git a/internal/pkg/entity/comment/comment.go b/internal/pkg/entity/comment/comment.go index 493e153..bd076cd 100644 --- a/internal/pkg/entity/comment/comment.go +++ b/internal/pkg/entity/comment/comment.go @@ -4,6 +4,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) //go:generate easyjson comment.go @@ -14,3 +15,15 @@ type Comment struct { PinID int `json:"pinID"` Content pgtype.Text `json:"content"` } + +func (c *Comment) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if c != nil { + if c.Author != nil { + c.Author.Sanitize(sanitizer, censor) + } + c.Content = pgtype.Text{ + String: sanitizer.Sanitize(censor.Sanitize(c.Content.String)), + Valid: c.Content.Valid, + } + } +} diff --git a/internal/pkg/entity/message/message.go b/internal/pkg/entity/message/message.go index ba42c98..6855287 100644 --- a/internal/pkg/entity/message/message.go +++ b/internal/pkg/entity/message/message.go @@ -4,6 +4,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) //go:generate easyjson message.go @@ -18,6 +19,15 @@ type Message struct { Content pgtype.Text `json:"content"` } +func (m *Message) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if m != nil { + m.Content = pgtype.Text{ + String: sanitizer.Sanitize(m.Content.String), + Valid: m.Content.Valid, + } + } +} + func (m Message) WhatChat() Chat { return Chat{m.From, m.To} } diff --git a/internal/pkg/entity/pin/pin.go b/internal/pkg/entity/pin/pin.go index f547059..b655d1d 100644 --- a/internal/pkg/entity/pin/pin.go +++ b/internal/pkg/entity/pin/pin.go @@ -4,6 +4,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/user" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) type Pin struct { @@ -20,6 +21,22 @@ type Pin struct { DeletedAt pgtype.Timestamptz `json:"-"` } //@name Pin +func (p *Pin) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if p != nil { + if p.Author != nil { + p.Author.Sanitize(sanitizer, censor) + } + p.Title = pgtype.Text{ + String: sanitizer.Sanitize(censor.Sanitize(p.Title.String)), + Valid: p.Title.Valid, + } + p.Description = pgtype.Text{ + String: sanitizer.Sanitize(censor.Sanitize(p.Description.String)), + Valid: p.Description.Valid, + } + } +} + func (p *Pin) SetTitle(title string) { p.Title = pgtype.Text{ String: title, diff --git a/internal/pkg/entity/search/search.go b/internal/pkg/entity/search/search.go index 478aabe..218b7cc 100644 --- a/internal/pkg/entity/search/search.go +++ b/internal/pkg/entity/search/search.go @@ -5,7 +5,7 @@ import ( "unicode" "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/board" - "github.com/microcosm-cc/bluemonday" + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) //go:generate easyjson search.go @@ -52,17 +52,22 @@ type UserForSearch struct { HasSubscribeFromCurUser bool `json:"is_subscribed"` } -func (u *UserForSearch) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(u.Username) +func (u *UserForSearch) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if u != nil { + u.Username = sanitizer.Sanitize(censor.Sanitize(u.Username)) + } } -func (b *BoardForSearch) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(b.BoardHeader.Title) - sanitizer.Sanitize(b.BoardHeader.Description) +func (b *BoardForSearch) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if b != nil { + b.BoardHeader.Sanitize(sanitizer, censor) + } } -func (p *PinForSearch) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(p.Title) +func (p *PinForSearch) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if p != nil { + p.Title = sanitizer.Sanitize(censor.Sanitize(p.Title)) + } } type SearchOpts struct { diff --git a/internal/pkg/entity/user/user.go b/internal/pkg/entity/user/user.go index 5a636fc..d12f37a 100644 --- a/internal/pkg/entity/user/user.go +++ b/internal/pkg/entity/user/user.go @@ -1,8 +1,8 @@ package user import ( + "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" "github.com/jackc/pgx/v5/pgtype" - "github.com/microcosm-cc/bluemonday" ) //go:generate easyjson user.go @@ -21,6 +21,25 @@ type User struct { Password string `json:"password,omitempty" example:"pass123"` } // @name User +func (u *User) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if u != nil { + u.Username = sanitizer.Sanitize(censor.Sanitize(u.Username)) + u.Email = sanitizer.Sanitize(censor.Sanitize(u.Email)) + u.Name = pgtype.Text{ + String: sanitizer.Sanitize(censor.Sanitize(u.Name.String)), + Valid: u.Name.Valid, + } + u.Surname = pgtype.Text{ + String: sanitizer.Sanitize(censor.Sanitize(u.Surname.String)), + Valid: u.Surname.Valid, + } + u.AboutMe = pgtype.Text{ + String: sanitizer.Sanitize(censor.Sanitize(u.AboutMe.String)), + Valid: u.AboutMe.Valid, + } + } +} + //easyjson:json type SubscriptionUser struct { ID int `json:"id"` @@ -29,8 +48,10 @@ type SubscriptionUser struct { HasSubscribeFromCurUser bool `json:"is_subscribed"` } -func (u *SubscriptionUser) Sanitize(sanitizer *bluemonday.Policy) { - sanitizer.Sanitize(u.Username) +func (u *SubscriptionUser) Sanitize(sanitizer validation.SanitizerXSS, censor validation.ProfanityCensor) { + if u != nil { + u.Username = sanitizer.Sanitize(censor.Sanitize(u.Username)) + } } type SubscriptionOpts struct { diff --git a/internal/pkg/usecase/board/get.go b/internal/pkg/usecase/board/get.go index e2555cd..a56467f 100644 --- a/internal/pkg/usecase/board/get.go +++ b/internal/pkg/usecase/board/get.go @@ -36,9 +36,6 @@ func (bCase *boardUsecase) GetBoardsByUsername(ctx context.Context, username str return nil, fmt.Errorf("get boards by user id usecase: %w", err) } - for _, board := range boards { - board.Sanitize(bCase.sanitizer) - } return boards, nil } @@ -79,7 +76,6 @@ func (bCase *boardUsecase) GetCertainBoard(ctx context.Context, boardID int) (en return entity.BoardWithContent{}, "", fmt.Errorf("get certain board: %w", err) } } - board.Sanitize(bCase.sanitizer) return board, username, nil } diff --git a/internal/pkg/usecase/board/update.go b/internal/pkg/usecase/board/update.go index c30180c..686f44e 100644 --- a/internal/pkg/usecase/board/update.go +++ b/internal/pkg/usecase/board/update.go @@ -49,10 +49,6 @@ func (bCase *boardUsecase) GetBoardInfoForUpdate(ctx context.Context, boardID in } } - board.Sanitize(bCase.sanitizer) - for id, title := range tagTitles { - tagTitles[id] = bCase.sanitizer.Sanitize(title) - } return board, tagTitles, nil } diff --git a/internal/pkg/usecase/board/usecase.go b/internal/pkg/usecase/board/usecase.go index a00f980..730e042 100644 --- a/internal/pkg/usecase/board/usecase.go +++ b/internal/pkg/usecase/board/usecase.go @@ -8,7 +8,6 @@ import ( boardRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/board" userRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/microcosm-cc/bluemonday" ) //go:generate mockgen -destination=./mock/board_mock.go -package=mock -source=usecase.go Usecase @@ -28,9 +27,8 @@ type boardUsecase struct { log *logger.Logger boardRepo boardRepo.Repository userRepo userRepo.Repository - sanitizer *bluemonday.Policy } -func New(logger *logger.Logger, boardRepo boardRepo.Repository, userRepo userRepo.Repository, sanitizer *bluemonday.Policy) *boardUsecase { - return &boardUsecase{log: logger, boardRepo: boardRepo, userRepo: userRepo, sanitizer: sanitizer} +func New(logger *logger.Logger, boardRepo boardRepo.Repository, userRepo userRepo.Repository) *boardUsecase { + return &boardUsecase{log: logger, boardRepo: boardRepo, userRepo: userRepo} } diff --git a/internal/pkg/usecase/image/filtration.go b/internal/pkg/usecase/image/filtration.go index 8b73710..457a1af 100644 --- a/internal/pkg/usecase/image/filtration.go +++ b/internal/pkg/usecase/image/filtration.go @@ -7,12 +7,13 @@ import ( vision "cloud.google.com/go/vision/v2/apiv1" pb "cloud.google.com/go/vision/v2/apiv1/visionpb" + validate "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/validation" ) var ( maxAnnotationsNumber int32 = 15 explicitLabels = []string{"goose", "duck"} - ErrExplicitImage = errors.New("Image content doesn't comply with service policy") + ErrExplicitImage = errors.New("image content doesn't comply with service policy") ) type ImageFilter interface { @@ -21,21 +22,19 @@ type ImageFilter interface { type googleVision struct { visionClient *vision.ImageAnnotatorClient + censor validate.ProfanityCensor } -func NewFilter(client *vision.ImageAnnotatorClient) *googleVision { - return &googleVision{client} +func NewFilter(client *vision.ImageAnnotatorClient, censor validate.ProfanityCensor) *googleVision { + return &googleVision{client, censor} } func CheckAnnotations(annotation *pb.SafeSearchAnnotation) bool { - if annotation.GetAdult() >= pb.Likelihood_LIKELY || + return annotation.GetAdult() >= pb.Likelihood_LIKELY || annotation.GetMedical() >= pb.Likelihood_LIKELY || annotation.GetRacy() >= pb.Likelihood_LIKELY || annotation.GetViolence() >= pb.Likelihood_LIKELY || - annotation.GetSpoof() >= pb.Likelihood_LIKELY { - return true - } - return false + annotation.GetSpoof() >= pb.Likelihood_LIKELY } func GetImageLabels(annotations []*pb.EntityAnnotation) []string { @@ -64,15 +63,24 @@ func HasExplicitLabel(explicitLabel string, imgLabels []string) bool { return false } -func CheckExplicit(resp *pb.AnnotateImageResponse, explicitLabels []string) error { +func getTextDescription(resp *pb.AnnotateImageResponse) string { + annotations := resp.GetTextAnnotations() + if len(annotations) == 0 { + return "" + } + return annotations[0].GetDescription() +} + +func CheckExplicit(resp *pb.AnnotateImageResponse, explicitLabels []string, censor validate.ProfanityCensor) error { if CheckCertainLabels(explicitLabels, GetImageLabels(resp.GetLabelAnnotations())) || - CheckAnnotations(resp.GetSafeSearchAnnotation()) { + CheckAnnotations(resp.GetSafeSearchAnnotation()) || + censor.IsProfane(getTextDescription(resp)) { return ErrExplicitImage } return nil } -func (vision *googleVision) Filter(ctx context.Context, imgBytes []byte, explicitLabels []string) error { +func (filter *googleVision) Filter(ctx context.Context, imgBytes []byte, explicitLabels []string) error { req := &pb.BatchAnnotateImagesRequest{ Requests: []*pb.AnnotateImageRequest{ { @@ -80,14 +88,14 @@ func (vision *googleVision) Filter(ctx context.Context, imgBytes []byte, explici Features: []*pb.Feature{ {Type: pb.Feature_LABEL_DETECTION, MaxResults: maxAnnotationsNumber}, {Type: pb.Feature_SAFE_SEARCH_DETECTION, MaxResults: maxAnnotationsNumber}, + {Type: pb.Feature_TEXT_DETECTION}, }, }, }, } - resp, err := vision.visionClient.BatchAnnotateImages(ctx, req) + resp, err := filter.visionClient.BatchAnnotateImages(ctx, req) if err != nil { return err } - - return CheckExplicit(resp.GetResponses()[0], explicitLabels) + return CheckExplicit(resp.GetResponses()[0], explicitLabels, filter.censor) } diff --git a/internal/pkg/usecase/search/usecase.go b/internal/pkg/usecase/search/usecase.go index b6277ca..4cfbea8 100644 --- a/internal/pkg/usecase/search/usecase.go +++ b/internal/pkg/usecase/search/usecase.go @@ -6,7 +6,6 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/entity/search" sRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/search" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/microcosm-cc/bluemonday" ) //go:generate mockgen -destination=./mock/search_mock.go -package=mock -source=usecase.go Usecase @@ -19,11 +18,10 @@ type Usecase interface { type searchUsecase struct { log *logger.Logger searchRepo sRepo.Repository - sanitizer *bluemonday.Policy } -func New(log *logger.Logger, searchRepo sRepo.Repository, sanitizer *bluemonday.Policy) Usecase { - return &searchUsecase{log: log, searchRepo: searchRepo, sanitizer: sanitizer} +func New(log *logger.Logger, searchRepo sRepo.Repository) Usecase { + return &searchUsecase{log: log, searchRepo: searchRepo} } func (u *searchUsecase) GetUsers(ctx context.Context, opts *search.SearchOpts) ([]search.UserForSearch, error) { @@ -31,11 +29,6 @@ func (u *searchUsecase) GetUsers(ctx context.Context, opts *search.SearchOpts) ( if err != nil { return nil, err } - - for id := range users { - users[id].Sanitize(u.sanitizer) - } - return users, nil } @@ -44,11 +37,6 @@ func (u *searchUsecase) GetBoards(ctx context.Context, opts *search.SearchOpts) if err != nil { return nil, err } - - for id := range boards { - boards[id].Sanitize(u.sanitizer) - } - return boards, nil } @@ -57,10 +45,5 @@ func (u *searchUsecase) GetPins(ctx context.Context, opts *search.SearchOpts) ([ if err != nil { return nil, err } - - for id := range pins { - pins[id].Sanitize(u.sanitizer) - } - return pins, nil } diff --git a/internal/pkg/usecase/subscription/get.go b/internal/pkg/usecase/subscription/get.go index d421c6f..2217318 100644 --- a/internal/pkg/usecase/subscription/get.go +++ b/internal/pkg/usecase/subscription/get.go @@ -29,9 +29,5 @@ func (u *subscriptionUsecase) GetSubscriptionInfoForUser(ctx context.Context, su return nil, err } - for id := range users { - users[id].Sanitize(u.sanitizer) - } - return users, nil } diff --git a/internal/pkg/usecase/subscription/usecase.go b/internal/pkg/usecase/subscription/usecase.go index a00705f..d44b5bb 100644 --- a/internal/pkg/usecase/subscription/usecase.go +++ b/internal/pkg/usecase/subscription/usecase.go @@ -7,7 +7,6 @@ import ( subRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/subscription" uRepo "github.com/go-park-mail-ru/2023_2_OND_team/internal/pkg/repository/user" "github.com/go-park-mail-ru/2023_2_OND_team/pkg/logger" - "github.com/microcosm-cc/bluemonday" ) //go:generate mockgen -destination=./mock/subscription_mock.go -package=mock -source=usecase.go Usecase @@ -18,12 +17,11 @@ type Usecase interface { } type subscriptionUsecase struct { - subRepo subRepo.Repository - userRepo uRepo.Repository - log *logger.Logger - sanitizer *bluemonday.Policy + subRepo subRepo.Repository + userRepo uRepo.Repository + log *logger.Logger } -func New(log *logger.Logger, subRepo subRepo.Repository, uRepo uRepo.Repository, sanitizer *bluemonday.Policy) Usecase { - return &subscriptionUsecase{subRepo: subRepo, userRepo: uRepo, log: log, sanitizer: sanitizer} +func New(log *logger.Logger, subRepo subRepo.Repository, uRepo uRepo.Repository) Usecase { + return &subscriptionUsecase{subRepo: subRepo, userRepo: uRepo, log: log} } diff --git a/internal/pkg/validation/censor.go b/internal/pkg/validation/censor.go new file mode 100644 index 0000000..1b6eaf7 --- /dev/null +++ b/internal/pkg/validation/censor.go @@ -0,0 +1,105 @@ +package validation + +import ( + "encoding/base64" + "log" + "strings" + + goaway "github.com/TwiN/go-away" + "github.com/go-park-mail-ru/2023_2_OND_team/pkg/levenstein" +) + +var additionalProfanity = []string{ + "0YXRg9C5", + "0L/QuNC30LTQsA==", + "0YfQu9C10L0=", + "0LLQsNCz0LjQvdCw", + "0LPQvtCy0L3Qvg==", + "0L/QsNGA0LDRiNCw", + "0YHRg9C60LA=", + "0L/QuNGB0YzQutCw", + "0YHQuNGB0YzQutC4", + "0LLQu9Cw0LPQsNC70LjRidC1", + "0L/QtdC90LjRgQ==", + "0LHQu9GP0LTRjA==", + "0YjQu9GO0YXQsA==", + "0L/RgNC+0YHRgtC40YLRg9GC0LrQsA==", + "0L3QuNCz0LXRgA==", + "0L3QtdCz0YA=", + "0YPQt9C60L7Qs9C70LDQt9GL0Lk=", + "0YXQtdGA", + "0LXQsdCw0YLRjA==", + "0YPQtdCx0LDQvQ==", + "0YPQtdCx0L7Qug==", + "0YLRgNCw0YXQsNGC0Yw=", + "0YLQstCw0YDRjA==", + "0L/QuNC00YA=", + "0YPRgNC+0LQ=", + "0L/QuNC30LTQtdGG", + "0YXRg9GP", + "0LfQsNC70YPQv9Cw", + "0L/QuNC00LDRgNCw0YE=", + "0LvQvtGF", + "0LPQsNC90LTQvtC9", + "0LTRgNC+0YfQuNGC0Yw=", + "0LDQvdCw0Ls=", + "0LbQvtC/0LA=", + "0LPQvdC40LTQsA==", + "0YPRiNC70LXQv9C+0Lo=", + "0YHRg9GH0LXQvdGL0Yg=", + "0YHQv9C10YDQvNCw", + "0LHQu9GP0YLRjA==", + "0L/QvtGA0L3Qvg==", + "0YHRgNCw0YLRjA==", + "0YfQvNC+", + "0LTQtdCx0LjQuw==", + "0LrRgNC10YLQuNC9", + "0LXQsdCw", +} + +func GetLabels() []string { + decodedLabels := make([]string, 0, len(additionalProfanity)) + for _, badEncoded := range additionalProfanity { + decoded, err := base64.StdEncoding.DecodeString(badEncoded) + if err != nil { + log.Println(err) + } + decodedLabels = append(decodedLabels, string(decoded)) + } + return decodedLabels +} + +type ProfanityCensor interface { + IsProfane(string) bool + Sanitize(string) string +} + +type defaultCensor struct { + censor *goaway.ProfanityDetector +} + +func isSimilarWithProfane(s string) bool { + for _, badWord := range GetLabels() { + if float64(levenstein.Levenshtein([]rune(s), []rune(badWord))) <= 0.3*float64(len(s)) { + return true + } + } + return false +} + +func (c *defaultCensor) IsProfane(s string) bool { + for _, word := range strings.Fields(s) { + if c.censor.IsProfane(strings.ToLower(word)) || isSimilarWithProfane(strings.ToLower(word)) { + return true + } + } + return false +} + +func (c *defaultCensor) Sanitize(s string) string { + return c.censor.Censor(s) +} + +func NewCensor(censor *goaway.ProfanityDetector) ProfanityCensor { + return &defaultCensor{censor} +} diff --git a/internal/pkg/validation/xss.go b/internal/pkg/validation/xss.go new file mode 100644 index 0000000..5ecce98 --- /dev/null +++ b/internal/pkg/validation/xss.go @@ -0,0 +1,19 @@ +package validation + +import "github.com/microcosm-cc/bluemonday" + +type SanitizerXSS interface { + Sanitize(string) string +} + +type bluemondaySanitizer struct { + sanitizer *bluemonday.Policy +} + +func (san *bluemondaySanitizer) Sanitize(s string) string { + return san.sanitizer.Sanitize(s) +} + +func NewSanitizerXSS(sanitizer *bluemonday.Policy) SanitizerXSS { + return &bluemondaySanitizer{sanitizer} +} diff --git a/pkg/levenstein/levenstein.go b/pkg/levenstein/levenstein.go new file mode 100644 index 0000000..c03a020 --- /dev/null +++ b/pkg/levenstein/levenstein.go @@ -0,0 +1,37 @@ +package levenstein + +func Levenshtein(s1, s2 []rune) int { + col := make([]int, len(s1)+1) + + for y := 1; y <= len(s1); y++ { + col[y] = y + } + for x := 1; x <= len(s2); x++ { + col[0] = x + lastkey := x - 1 + for y := 1; y <= len(s1); y++ { + oldkey := col[y] + var incr int + if s1[y-1] != s2[x-1] { + incr = 1 + } + + col[y] = min(col[y]+1, col[y-1]+1, lastkey+incr) + lastkey = oldkey + } + } + return col[len(s1)] +} + +func min(a, b, c int) int { + if a < b { + if a < c { + return a + } + } else { + if b < c { + return b + } + } + return c +} From 4e60714a5ff77b4b6ecbf592f5a9ae1407d9356d Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Sun, 24 Dec 2023 22:50:25 +0300 Subject: [PATCH 259/266] dev4: add advertised listeners in kafka container environment, add zookeeper healtcheck --- deployments/docker-compose.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index dc87d2d..a06c4cc 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -106,6 +106,7 @@ services: - ../.env environment: - KAFKA_BROKER_ADDRESS=kafka + - KAFKA_BROKER_PORT=29092 depends_on: kafka: condition: 'service_healthy' @@ -122,6 +123,14 @@ services: - "zookeeper_data:/bitnami" environment: - ALLOW_ANONYMOUS_LOGIN=yes + healthcheck: + test: | + curl localhost:2181 + [ $(echo $?) = '52' ] && exit 0 || exit -1 + interval: 5s + timeout: 5s + retries: 10 + start_period: 15s kafka: image: bitnami/kafka:latest @@ -131,10 +140,12 @@ services: volumes: - "kafka_data:/bitnami" environment: - - ALLOW_PLAINTEXT_LISTENER=yes - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CLIENT:PLAINTEXT,EXTERNAL:PLAINTEXT + - KAFKA_CFG_LISTENERS=CLIENT://:29092,EXTERNAL://:9092 + - KAFKA_CFG_ADVERTISED_LISTENERS=CLIENT://kafka:29092,EXTERNAL://localhost:9092 + - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=CLIENT healthcheck: test: | curl localhost:9092 @@ -144,7 +155,8 @@ services: retries: 10 start_period: 15s depends_on: - - zookeeper + zookeeper: + condition: 'service_healthy' prometheus: image: prom/prometheus:latest From d387657a9a49ffcca4063eec47739151dbff45e0 Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 28 Dec 2023 02:43:52 +0300 Subject: [PATCH 260/266] TP-f07 update: fixed a messed up parameter --- internal/pkg/usecase/message/usecase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 00bc0fc..22f5499 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -86,7 +86,7 @@ func (m *messageCase) GetMessagesFromChat(ctx context.Context, userID int, chat UserID2: int64(chat[1]), }, Count: int64(count), - LastID: int64(count), + LastID: int64(lastID), }) if err != nil { err = fmt.Errorf("get message by : %w", err) From aeffef946fc94084850c31e61127115ef302f7e4 Mon Sep 17 00:00:00 2001 From: wonderf00l <maks22036@gmail.com> Date: Thu, 28 Dec 2023 12:04:41 +0300 Subject: [PATCH 261/266] dev4: changed profanity censor --- internal/pkg/validation/censor.go | 44 ++++++++++++++++--------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/internal/pkg/validation/censor.go b/internal/pkg/validation/censor.go index 1b6eaf7..4180389 100644 --- a/internal/pkg/validation/censor.go +++ b/internal/pkg/validation/censor.go @@ -6,27 +6,26 @@ import ( "strings" goaway "github.com/TwiN/go-away" - "github.com/go-park-mail-ru/2023_2_OND_team/pkg/levenstein" ) var additionalProfanity = []string{ - "0YXRg9C5", + "0YXRgw==", "0L/QuNC30LTQsA==", "0YfQu9C10L0=", - "0LLQsNCz0LjQvdCw", + "0LLQsNCz0LjQvQ==", "0LPQvtCy0L3Qvg==", "0L/QsNGA0LDRiNCw", "0YHRg9C60LA=", - "0L/QuNGB0YzQutCw", - "0YHQuNGB0YzQutC4", - "0LLQu9Cw0LPQsNC70LjRidC1", + "0L/QuNGB0YzQug==", + "0YHQuNGB0YzQug==", + "0LLQu9Cw0LPQsNC70LjRiQ==", "0L/QtdC90LjRgQ==", "0LHQu9GP0LTRjA==", - "0YjQu9GO0YXQsA==", - "0L/RgNC+0YHRgtC40YLRg9GC0LrQsA==", + "0YjQu9GO0YU=", + "0L/RgNC+0YHRgtC40YLRg9GC0Lo=", "0L3QuNCz0LXRgA==", "0L3QtdCz0YA=", - "0YPQt9C60L7Qs9C70LDQt9GL0Lk=", + "0YPQt9C60L7Qs9C70LDQt9GL", "0YXQtdGA", "0LXQsdCw0YLRjA==", "0YPQtdCx0LDQvQ==", @@ -37,7 +36,7 @@ var additionalProfanity = []string{ "0YPRgNC+0LQ=", "0L/QuNC30LTQtdGG", "0YXRg9GP", - "0LfQsNC70YPQv9Cw", + "0LfQsNC70YPQvw==", "0L/QuNC00LDRgNCw0YE=", "0LvQvtGF", "0LPQsNC90LTQvtC9", @@ -54,7 +53,19 @@ var additionalProfanity = []string{ "0YfQvNC+", "0LTQtdCx0LjQuw==", "0LrRgNC10YLQuNC9", - "0LXQsdCw", + "cGl6ZGE=", + "0LXQsdCw0Ls=", + "0LPQsNCy0L3Qvg==", + "0LPQvtC90LTQvtC9", + "0YXRg9C1", + "0LXQsdCw0L0=", + "0LXQsdC70LDQvQ==", + "0LXQsdGD0YfQuA==", + "0LXQsdC70LjQstGL", + "0L/QuNC00YDQuNC7", + "0L/QvtGA0L3Rg9GF0LA=", + "0LXQsdC70Y8=", + "0YPQtdCx0LjRidC90Ys=", } func GetLabels() []string { @@ -78,18 +89,9 @@ type defaultCensor struct { censor *goaway.ProfanityDetector } -func isSimilarWithProfane(s string) bool { - for _, badWord := range GetLabels() { - if float64(levenstein.Levenshtein([]rune(s), []rune(badWord))) <= 0.3*float64(len(s)) { - return true - } - } - return false -} - func (c *defaultCensor) IsProfane(s string) bool { for _, word := range strings.Fields(s) { - if c.censor.IsProfane(strings.ToLower(word)) || isSimilarWithProfane(strings.ToLower(word)) { + if c.censor.IsProfane(strings.ToLower(word)) { return true } } From 2f1d1c20d51c7253abbc926c34b340c7737350ea Mon Sep 17 00:00:00 2001 From: Gvidow <arzamachev.art-alex@yandex.ru> Date: Thu, 28 Dec 2023 13:59:35 +0300 Subject: [PATCH 262/266] TP-f07 update: get deleted message --- internal/pkg/usecase/message/usecase.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/pkg/usecase/message/usecase.go b/internal/pkg/usecase/message/usecase.go index 22f5499..a48369c 100644 --- a/internal/pkg/usecase/message/usecase.go +++ b/internal/pkg/usecase/message/usecase.go @@ -193,9 +193,8 @@ func (m *messageCase) receiveFromSubClient(ctx context.Context, userID int, subC evMsg.Message, err = m.GetMessage(ctx, userID, msgObjID.MessageID) if err != nil { m.log.Error(err.Error()) - } - - if evMsg.Type == "delete" { + evMsg.Err = err + } else if evMsg.Type == "delete" { evMsg.Message.Content.String = "" } From ecc73f07d151451677a16b4d4c12dbbf49be6db4 Mon Sep 17 00:00:00 2001 From: Gvidow <96253031+Gvidow@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:01:50 +0300 Subject: [PATCH 263/266] Edit origin (#32) * dev4 update: app.go * dev4 update: router.go --- internal/api/server/router/router.go | 4 ++-- internal/app/app.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/server/router/router.go b/internal/api/server/router/router.go index 434ed62..cb1bfa4 100644 --- a/internal/api/server/router/router.go +++ b/internal/api/server/router/router.go @@ -34,8 +34,8 @@ func (r Router) RegisterRoute(handler *deliveryHTTP.HandlerHTTP, wsHandler *deli cfgCSRF.PathToGet = "/api/v1/csrf" c := cors.New(cors.Options{ - AllowedOrigins: []string{"https://pinspire.online", "https://pinspire.online:1443", - "https://pinspire.online:1444", "https://pinspire.online:1445", "https://pinspire.online:1446", "https://pinspire.online:8081"}, + AllowedOrigins: []string{"https://pinspire.site", "https://pinspire.site:1443", + "https://pinspire.site:1444", "https://pinspire.site:1445", "https://pinspire.site:1446", "https://pinspire.site:8081"}, AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut}, AllowCredentials: true, AllowedHeaders: []string{"content-type", cfgCSRF.Header}, diff --git a/internal/app/app.go b/internal/app/app.go index bdcbea2..f2706ef 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -142,7 +142,7 @@ func Run(ctx context.Context, log *log.Logger, cfg ConfigFiles) { }) wsHandler := deliveryWS.New(log, messageCase, notifyCase, - deliveryWS.SetOriginPatterns([]string{"pinspire.online", "pinspire.online:*"})) + deliveryWS.SetOriginPatterns([]string{"pinspire.site", "pinspire.site:*"})) cfgServ, err := server.NewConfig(cfg.ServerConfigFile) if err != nil { From 9a6b07b5dbb65f8357942f31de6b4a4c5e256fb3 Mon Sep 17 00:00:00 2001 From: wonderf00l <105116952+wonderf00l@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:22:21 +0300 Subject: [PATCH 264/266] Updated host in deployment.yml --- .github/workflows/deployment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 97709e8..9bba43f 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -37,7 +37,7 @@ jobs: - name: fetch changes uses: appleboy/ssh-action@master with: - host: pinspire.online + host: pinspire.site username: ${{ secrets.REMOTE_USERNAME }} key: ${{ secrets.PRIVATE_KEY }} script: | @@ -55,4 +55,4 @@ jobs: sudo docker compose down main_service auth_service realtime_service messenger_service sudo docker rmi pinspireapp/main:latest pinspireapp/auth:latest pinspireapp/realtime:latest pinspireapp/messenger:latest sudo docker compose -f docker-compose.yml -f compose.prod.yml up -d - \ No newline at end of file + From 70ad1401163016c783d24dd91854e147d415121b Mon Sep 17 00:00:00 2001 From: wonderf00l <105116952+wonderf00l@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:24:18 +0300 Subject: [PATCH 265/266] Updated host in last job deployment.yml --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 9bba43f..b2fda69 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -47,7 +47,7 @@ jobs: - name: deploy application uses: appleboy/ssh-action@master with: - host: pinspire.online + host: pinspire.site username: ${{ secrets.REMOTE_USERNAME }} key: ${{ secrets.PRIVATE_KEY }} script: | From 02bb5c28c7308c2158c279788d756bcf9a29c547 Mon Sep 17 00:00:00 2001 From: wonderf00l <105116952+wonderf00l@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:45:17 +0300 Subject: [PATCH 266/266] dev4: Update usecase.go - changed image prefix --- internal/pkg/usecase/image/usecase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/usecase/image/usecase.go b/internal/pkg/usecase/image/usecase.go index 5ace111..8798040 100644 --- a/internal/pkg/usecase/image/usecase.go +++ b/internal/pkg/usecase/image/usecase.go @@ -13,7 +13,7 @@ import ( "github.com/go-park-mail-ru/2023_2_OND_team/pkg/validator/image/check" ) -const PrefixURLImage = "https://pinspire.online:8081/" +const PrefixURLImage = "https://pinspire.site/" var ( ErrInvalidImage = errors.New("invalid images")