├── .gitignore ├── README.md └── rest-api-tutorial ├── README.md ├── cmd └── main │ └── app.go ├── config.yml ├── data.sql ├── go.mod ├── go.sum ├── internal ├── apperror │ ├── error.go │ └── middleware.go ├── author │ ├── db │ │ └── postgresql.go │ ├── dto.go │ ├── handler.go │ ├── model.go │ └── storage.go ├── book │ ├── db │ │ ├── model.go │ │ └── postgresql.go │ ├── dto.go │ ├── model.go │ └── storage.go ├── config │ └── config.go └── handlers │ └── handler.go ├── pkg ├── client │ ├── mongodb │ │ └── mongodb.go │ └── postgresql │ │ └── postgresql.go ├── logging │ └── logging.go └── utils │ └── repeatable.go └── test-api.http /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | /.DS_Store 17 | /rest-api-tutorial/.idea 18 | /rest-api-tutorial/logs 19 | /rest-api-tutorial/build 20 | /rest-api-tutorial/.DS_Store 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-advanced-lessons 2 | Репозиторий с исходным кодом, который привязан к серии роликов по языку Go на моем YouTube канале The Art of Development. https://www.youtube.com/channel/UCkeO0vkJAu74LaNud9j89aw 3 | -------------------------------------------------------------------------------- /rest-api-tutorial/README.md: -------------------------------------------------------------------------------- 1 | # rest-api-tutorial 2 | 3 | # user-service 4 | 5 | # REST API 6 | 7 | GET /users -- list of users -- 200, 404, 500 8 | GET /users/:id -- user by id -- 200, 404, 500 9 | POST /users/:id -- create user -- 204, 4xx, Header Location: url 10 | PUT /users/:id -- fully update user -- 204/200, 404, 400, 500 11 | PATCH /users/:id -- partially update user -- 204/200, 404, 400, 500 12 | DELETE /users/:id -- delete user by id -- 204, 404, 400 -------------------------------------------------------------------------------- /rest-api-tutorial/cmd/main/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/julienschmidt/httprouter" 7 | "net" 8 | "net/http" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | author2 "restapi-lesson/internal/author" 13 | author "restapi-lesson/internal/author/db" 14 | "restapi-lesson/internal/config" 15 | "restapi-lesson/pkg/client/postgresql" 16 | "restapi-lesson/pkg/logging" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | logger := logging.GetLogger() 22 | logger.Info("create router") 23 | router := httprouter.New() 24 | 25 | cfg := config.GetConfig() 26 | 27 | postgreSQLClient, err := postgresql.NewClient(context.TODO(), 3, cfg.Storage) 28 | if err != nil { 29 | logger.Fatalf("%v", err) 30 | } 31 | repository := author.NewRepository(postgreSQLClient, logger) 32 | a := author2.Author{ 33 | Name: "OK", 34 | } 35 | err = repository.Create(context.TODO(), &a) 36 | if err != nil { 37 | logger.Fatal(err) 38 | } 39 | 40 | logger.Info("register author handler") 41 | authorHandler := author2.NewHandler(repository, logger) 42 | authorHandler.Register(router) 43 | 44 | start(router, cfg) 45 | } 46 | 47 | func start(router *httprouter.Router, cfg *config.Config) { 48 | logger := logging.GetLogger() 49 | logger.Info("start application") 50 | 51 | var listener net.Listener 52 | var listenErr error 53 | 54 | if cfg.Listen.Type == "sock" { 55 | logger.Info("detect app path") 56 | appDir, err := filepath.Abs(filepath.Dir(os.Args[0])) 57 | if err != nil { 58 | logger.Fatal(err) 59 | } 60 | logger.Info("create socket") 61 | socketPath := path.Join(appDir, "app.sock") 62 | 63 | logger.Info("listen unix socket") 64 | listener, listenErr = net.Listen("unix", socketPath) 65 | logger.Infof("server is listening unix socket: %s", socketPath) 66 | } else { 67 | logger.Info("listen tcp") 68 | listener, listenErr = net.Listen("tcp", fmt.Sprintf("%s:%s", cfg.Listen.BindIP, cfg.Listen.Port)) 69 | logger.Infof("server is listening port %s:%s", cfg.Listen.BindIP, cfg.Listen.Port) 70 | } 71 | 72 | if listenErr != nil { 73 | logger.Fatal(listenErr) 74 | } 75 | 76 | server := &http.Server{ 77 | Handler: router, 78 | WriteTimeout: 15 * time.Second, 79 | ReadTimeout: 15 * time.Second, 80 | } 81 | 82 | logger.Fatal(server.Serve(listener)) 83 | } 84 | -------------------------------------------------------------------------------- /rest-api-tutorial/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | is_debug: true 4 | listen: 5 | type: port 6 | port: 1234 7 | storage: 8 | host: localhost 9 | port: 5432 10 | database: postgres 11 | username: postgres 12 | password: postgres -------------------------------------------------------------------------------- /rest-api-tutorial/data.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS author CASCADE; 2 | DROP TABLE IF EXISTS book CASCADE; 3 | DROP TABLE IF EXISTS book_authors CASCADE; 4 | 5 | CREATE TABLE public.author 6 | ( 7 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 8 | name VARCHAR(100) NOT NULL 9 | ); 10 | 11 | CREATE TABLE public.book 12 | ( 13 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 14 | name VARCHAR(100) NOT NULL, 15 | age INT 16 | ); 17 | 18 | CREATE TABLE public.book_authors 19 | ( 20 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 21 | book_id UUID NOT NULL, 22 | author_id UUID NOT NULL, 23 | 24 | CONSTRAINT book_fk FOREIGN KEY (book_id) REFERENCES public.book (id), 25 | CONSTRAINT author_fk FOREIGN KEY (author_id) REFERENCES public.author (id), 26 | CONSTRAINT book_author_unique UNIQUE (book_id, author_id) 27 | ); 28 | 29 | INSERT INTO author (id, name) 30 | VALUES ('707f69e0-edac-4c3e-bb7f-918d3f190e19', 'Народ'); 31 | INSERT INTO author (id, name) 32 | VALUES ('1ad0596d-e43d-4093-a7fe-a6c1074f6219', 'Джоан Роулинг'); 33 | INSERT INTO author (id, name) 34 | VALUES ('62af3986-0963-465c-8a86-dd23ac240523', 'Джек Лондон'); 35 | 36 | INSERT INTO book (id, name, age) 37 | VALUES ('e51083cf-ee8e-4ee1-adfe-fbcbd5c5574c', 'колобок', 1000); 38 | INSERT INTO book (id, name, age) 39 | VALUES ('dcb2f994-dc9d-4743-8d50-9ac253930768', 'гарри поттер', 22); 40 | INSERT INTO book (id, name) 41 | VALUES ('39b9c138-5e2b-481b-8827-af0001ed53e0', 'брилианты'); 42 | 43 | 44 | -- kolobok 45 | INSERT INTO book_authors (book_id, author_id) 46 | VALUES ('e51083cf-ee8e-4ee1-adfe-fbcbd5c5574c', '1ad0596d-e43d-4093-a7fe-a6c1074f6219'); 47 | INSERT INTO book_authors (book_id, author_id) 48 | VALUES ('e51083cf-ee8e-4ee1-adfe-fbcbd5c5574c', '707f69e0-edac-4c3e-bb7f-918d3f190e19'); 49 | 50 | -- HP 51 | INSERT INTO book_authors (book_id, author_id) 52 | VALUES ('dcb2f994-dc9d-4743-8d50-9ac253930768', '707f69e0-edac-4c3e-bb7f-918d3f190e19'); 53 | INSERT INTO book_authors (book_id, author_id) 54 | VALUES ('dcb2f994-dc9d-4743-8d50-9ac253930768', '62af3986-0963-465c-8a86-dd23ac240523'); 55 | INSERT INTO book_authors (book_id, author_id) 56 | VALUES ('dcb2f994-dc9d-4743-8d50-9ac253930768', '1ad0596d-e43d-4093-a7fe-a6c1074f6219'); 57 | 58 | 59 | 60 | DTO DTO 61 | LAYER_A --> LAYER_B --> LAYER_C 62 | 63 | HANDLER (JSON-->DTO-->) SERVICE --> DAO -->DB-->DTO-->MODEL--> SERVICE 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /rest-api-tutorial/go.mod: -------------------------------------------------------------------------------- 1 | module restapi-lesson 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.4.1 // indirect 7 | github.com/ilyakaznacheev/cleanenv v1.2.5 8 | github.com/jackc/pgconn v1.10.1 // indirect 9 | github.com/jackc/pgx/v4 v4.14.1 10 | github.com/julienschmidt/httprouter v1.3.0 11 | github.com/sirupsen/logrus v1.8.1 12 | go.mongodb.org/mongo-driver v1.7.2 13 | gopkg.in/yaml.v2 v2.4.0 // indirect 14 | olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /rest-api-tutorial/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= 3 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 5 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 6 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 7 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 8 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 9 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 14 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 15 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 16 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 17 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 18 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 19 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= 20 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 21 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 22 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 23 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 24 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 25 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 26 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 27 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= 28 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= 29 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 30 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 31 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= 32 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= 33 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 34 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 35 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 36 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 37 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 38 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 39 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= 40 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 41 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= 42 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 43 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 44 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 45 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 46 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 47 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 48 | github.com/ilyakaznacheev/cleanenv v1.2.5 h1:/SlcF9GaIvefWqFJzsccGG/NJdoaAwb7Mm7ImzhO3DM= 49 | github.com/ilyakaznacheev/cleanenv v1.2.5/go.mod h1:/i3yhzwZ3s7hacNERGFwvlhwXMDcaqwIzmayEhbRplk= 50 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 51 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 52 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 53 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 54 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 55 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 56 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 57 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 58 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 59 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 60 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 61 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 62 | github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= 63 | github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 64 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 65 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 66 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 67 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 68 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 69 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 70 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 71 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 72 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 73 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 74 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 75 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 76 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 77 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 78 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 79 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 80 | github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= 81 | github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 82 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 83 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 84 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 85 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 86 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 87 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 88 | github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0= 89 | github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 90 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 91 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 92 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 93 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 94 | github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU= 95 | github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M= 96 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 97 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 98 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 99 | github.com/jackc/puddle v1.2.0 h1:DNDKdn/pDrWvDWyT2FYvpZVE81OAhWrjCv19I9n108Q= 100 | github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 101 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 102 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 103 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 104 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 105 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 106 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 107 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 108 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= 109 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 110 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 111 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 112 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 113 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 114 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 115 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 116 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 117 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 118 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 119 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 120 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 121 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= 122 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 123 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 124 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 125 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 126 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 127 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 128 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 129 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 130 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 131 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 132 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 133 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 134 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 135 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 136 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 137 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 138 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 139 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 140 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 141 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 142 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 143 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 144 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 145 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 146 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 147 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 148 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 149 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 150 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 151 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 152 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 153 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 154 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 155 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 156 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 157 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 158 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 159 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 160 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 161 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 162 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 163 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 164 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 165 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 166 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 167 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 168 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 169 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 170 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 171 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 172 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 173 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 174 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 175 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 176 | go.mongodb.org/mongo-driver v1.7.2 h1:pFttQyIiJUHEn50YfZgC9ECjITMT44oiN36uArf/OFg= 177 | go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= 178 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 179 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 180 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 181 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 182 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 183 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 184 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 185 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 186 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 187 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 188 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 189 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 190 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 191 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 192 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 193 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 194 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 195 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 196 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 197 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 198 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 199 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 200 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= 201 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 202 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 203 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 204 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 205 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 206 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 207 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 208 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 209 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 210 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 211 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 212 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 213 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 214 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 215 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 216 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 217 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 218 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 219 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 220 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 221 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 222 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 224 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 226 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 229 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 230 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 231 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 232 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 233 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 234 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 235 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 236 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 237 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 238 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 239 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 240 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 241 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 242 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 243 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 244 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 245 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 246 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 247 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 248 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 249 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 250 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 251 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 252 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 253 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 254 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 255 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 256 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 257 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 258 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 259 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 260 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 261 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 262 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 263 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 264 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 265 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 266 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 267 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 268 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 269 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 270 | olympos.io/encoding/edn v0.0.0-20200308123125-93e3b8dd0e24/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= 271 | olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= 272 | olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= 273 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/apperror/error.go: -------------------------------------------------------------------------------- 1 | package apperror 2 | 3 | import "encoding/json" 4 | 5 | var ( 6 | ErrNotFound = NewAppError(nil, "not found", "", "US-000003") 7 | ) 8 | 9 | type AppError struct { 10 | Err error `json:"-"` 11 | Message string `json:"message,omitempty"` 12 | DeveloperMessage string `json:"developer_message,omitempty"` 13 | Code string `json:"code,omitempty"` 14 | } 15 | 16 | func (e *AppError) Error() string { 17 | return e.Message 18 | } 19 | 20 | func (e *AppError) Unwrap() error { return e.Err } 21 | 22 | func (e *AppError) Marshal() []byte { 23 | marshal, err := json.Marshal(e) 24 | if err != nil { 25 | return nil 26 | } 27 | return marshal 28 | } 29 | 30 | func NewAppError(err error, message, developerMessage, code string) *AppError { 31 | return &AppError{ 32 | Err: err, 33 | Message: message, 34 | DeveloperMessage: developerMessage, 35 | Code: code, 36 | } 37 | } 38 | 39 | func systemError(err error) *AppError { 40 | return NewAppError(err, "internal system error", err.Error(), "US-000000") 41 | } 42 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/apperror/middleware.go: -------------------------------------------------------------------------------- 1 | package apperror 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | ) 7 | 8 | type appHandler func(w http.ResponseWriter, r *http.Request) error 9 | 10 | func Middleware(h appHandler) http.HandlerFunc { 11 | return func(w http.ResponseWriter, r *http.Request) { 12 | w.Header().Set("Content-Type", "application/json") 13 | 14 | var appErr *AppError 15 | err := h(w, r) 16 | if err != nil { 17 | if errors.As(err, &appErr) { 18 | if errors.Is(err, ErrNotFound) { 19 | w.WriteHeader(http.StatusNotFound) 20 | w.Write(ErrNotFound.Marshal()) 21 | return 22 | } 23 | 24 | err = err.(*AppError) 25 | w.WriteHeader(http.StatusBadRequest) 26 | w.Write(appErr.Marshal()) 27 | return 28 | } 29 | 30 | w.WriteHeader(http.StatusTeapot) 31 | w.Write(systemError(err).Marshal()) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/author/db/postgresql.go: -------------------------------------------------------------------------------- 1 | package author 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/jackc/pgconn" 8 | "restapi-lesson/internal/author" 9 | "restapi-lesson/pkg/client/postgresql" 10 | "restapi-lesson/pkg/logging" 11 | "strings" 12 | ) 13 | 14 | type repository struct { 15 | client postgresql.Client 16 | logger *logging.Logger 17 | } 18 | 19 | func formatQuery(q string) string { 20 | return strings.ReplaceAll(strings.ReplaceAll(q, "\t", ""), "\n", " ") 21 | } 22 | 23 | func (r *repository) Create(ctx context.Context, author *author.Author) error { 24 | q := ` 25 | INSERT INTO author 26 | (name, age) 27 | VALUES 28 | ($1, $2) 29 | RETURNING id 30 | ` 31 | r.logger.Trace(fmt.Sprintf("SQL Query: %s", formatQuery(q))) 32 | if err := r.client.QueryRow(ctx, q, author.Name, 123).Scan(&author.ID); err != nil { 33 | var pgErr *pgconn.PgError 34 | if errors.As(err, &pgErr) { 35 | pgErr = err.(*pgconn.PgError) 36 | newErr := fmt.Errorf(fmt.Sprintf("SQL Error: %s, Detail: %s, Where: %s, Code: %s, SQLState: %s", pgErr.Message, pgErr.Detail, pgErr.Where, pgErr.Code, pgErr.SQLState())) 37 | r.logger.Error(newErr) 38 | return newErr 39 | } 40 | return err 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func (r *repository) FindAll(ctx context.Context) (u []author.Author, err error) { 47 | q := ` 48 | SELECT id, name FROM public.author; 49 | ` 50 | r.logger.Trace(fmt.Sprintf("SQL Query: %s", formatQuery(q))) 51 | 52 | rows, err := r.client.Query(ctx, q) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | authors := make([]author.Author, 0) 58 | 59 | for rows.Next() { 60 | var ath author.Author 61 | 62 | err = rows.Scan(&ath.ID, &ath.Name) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | authors = append(authors, ath) 68 | } 69 | 70 | if err = rows.Err(); err != nil { 71 | return nil, err 72 | } 73 | 74 | return authors, nil 75 | } 76 | 77 | func (r *repository) FindOne(ctx context.Context, id string) (author.Author, error) { 78 | q := ` 79 | SELECT id, name FROM public.author WHERE id = $1 80 | ` 81 | r.logger.Trace(fmt.Sprintf("SQL Query: %s", formatQuery(q))) 82 | 83 | var ath author.Author 84 | err := r.client.QueryRow(ctx, q, id).Scan(&ath.ID, &ath.Name) 85 | if err != nil { 86 | return author.Author{}, err 87 | } 88 | 89 | return ath, nil 90 | } 91 | 92 | func (r *repository) Update(ctx context.Context, user author.Author) error { 93 | //TODO implement me 94 | panic("implement me") 95 | } 96 | 97 | func (r *repository) Delete(ctx context.Context, id string) error { 98 | //TODO implement me 99 | panic("implement me") 100 | } 101 | 102 | func NewRepository(client postgresql.Client, logger *logging.Logger) author.Repository { 103 | return &repository{ 104 | client: client, 105 | logger: logger, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/author/dto.go: -------------------------------------------------------------------------------- 1 | package author 2 | 3 | type CreateAuthorDTO struct { 4 | Name string `json:"name"` 5 | } 6 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/author/handler.go: -------------------------------------------------------------------------------- 1 | package author 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/julienschmidt/httprouter" 7 | "net/http" 8 | "restapi-lesson/internal/apperror" 9 | "restapi-lesson/internal/handlers" 10 | "restapi-lesson/pkg/logging" 11 | ) 12 | 13 | const ( 14 | authorsURL = "/authors" 15 | authorURL = "/authors/:uuid" 16 | ) 17 | 18 | type handler struct { 19 | logger *logging.Logger 20 | repository Repository 21 | } 22 | 23 | func NewHandler(repository Repository, logger *logging.Logger) handlers.Handler { 24 | return &handler{ 25 | repository: repository, 26 | logger: logger, 27 | } 28 | } 29 | 30 | func (h *handler) Register(router *httprouter.Router) { 31 | router.HandlerFunc(http.MethodGet, authorsURL, apperror.Middleware(h.GetList)) 32 | } 33 | 34 | func (h *handler) GetList(w http.ResponseWriter, r *http.Request) error { 35 | all, err := h.repository.FindAll(context.TODO()) 36 | if err != nil { 37 | w.WriteHeader(400) 38 | return err 39 | } 40 | 41 | allBytes, err := json.Marshal(all) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | w.WriteHeader(http.StatusOK) 47 | w.Write(allBytes) 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/author/model.go: -------------------------------------------------------------------------------- 1 | package author 2 | 3 | type Author struct { 4 | ID string `json:"id"` 5 | Name string `json:"name"` 6 | } 7 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/author/storage.go: -------------------------------------------------------------------------------- 1 | package author 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Repository interface { 8 | Create(ctx context.Context, author *Author) error 9 | FindAll(ctx context.Context) (u []Author, err error) 10 | FindOne(ctx context.Context, id string) (Author, error) 11 | Update(ctx context.Context, user Author) error 12 | Delete(ctx context.Context, id string) error 13 | } 14 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/book/db/model.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "restapi-lesson/internal/author" 6 | "restapi-lesson/internal/book" 7 | ) 8 | 9 | type Book struct { 10 | ID string `json:"id"` 11 | Name string `json:"name"` 12 | Age sql.NullInt32 `json:"age"` 13 | Authors []author.Author `json:"authors"` 14 | } 15 | 16 | func (m *Book) ToDomain() book.Book { 17 | b := book.Book{ 18 | ID: m.ID, 19 | Name: m.Name, 20 | } 21 | if m.Age.Valid { 22 | b.Age = int(m.Age.Int32) 23 | } 24 | 25 | return b 26 | } 27 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/book/db/postgresql.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "restapi-lesson/internal/book" 6 | "restapi-lesson/pkg/client/postgresql" 7 | "restapi-lesson/pkg/logging" 8 | ) 9 | 10 | type repository struct { 11 | client postgresql.Client 12 | logger *logging.Logger 13 | } 14 | 15 | func NewRepository(client postgresql.Client, logger *logging.Logger) book.Repository { 16 | return &repository{ 17 | client: client, 18 | logger: logger, 19 | } 20 | } 21 | 22 | func (r *repository) FindAll(ctx context.Context) (u []book.Book, err error) { 23 | q := ` 24 | SELECT id, name, age FROM public.book; 25 | ` 26 | 27 | rows, err := r.client.Query(ctx, q) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | books := make([]book.Book, 0) 33 | 34 | for rows.Next() { 35 | var bk Book 36 | 37 | err = rows.Scan(&bk.ID, &bk.Name, &bk.Age) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | books = append(books, bk.ToDomain()) 43 | } 44 | 45 | if err = rows.Err(); err != nil { 46 | return nil, err 47 | } 48 | 49 | return books, nil 50 | } 51 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/book/dto.go: -------------------------------------------------------------------------------- 1 | package book 2 | 3 | type CreateBookDTO struct { 4 | Name string `json:"name"` 5 | AuthorID int `json:"author_id"` 6 | } 7 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/book/model.go: -------------------------------------------------------------------------------- 1 | package book 2 | 3 | import "restapi-lesson/internal/author" 4 | 5 | type Book struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | Age int `json:"age"` 9 | Authors []author.Author `json:"authors"` 10 | } 11 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/book/storage.go: -------------------------------------------------------------------------------- 1 | package book 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Repository interface { 8 | FindAll(ctx context.Context) (u []Book, err error) 9 | } 10 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ilyakaznacheev/cleanenv" 5 | "restapi-lesson/pkg/logging" 6 | "sync" 7 | ) 8 | 9 | type Config struct { 10 | IsDebug *bool `yaml:"is_debug" env-required:"true"` 11 | Listen struct { 12 | Type string `yaml:"type" env-default:"port"` 13 | BindIP string `yaml:"bind_ip" env-default:"127.0.0.1"` 14 | Port string `yaml:"port" env-default:"8080"` 15 | } `yaml:"listen"` 16 | Storage StorageConfig `yaml:"storage"` 17 | } 18 | 19 | type StorageConfig struct { 20 | Host string `json:"host"` 21 | Port string `json:"port"` 22 | Database string `json:"database"` 23 | Username string `json:"username"` 24 | Password string `json:"password"` 25 | } 26 | 27 | var instance *Config 28 | var once sync.Once 29 | 30 | func GetConfig() *Config { 31 | once.Do(func() { 32 | logger := logging.GetLogger() 33 | logger.Info("read application configuration") 34 | instance = &Config{} 35 | if err := cleanenv.ReadConfig("config.yml", instance); err != nil { 36 | help, _ := cleanenv.GetDescription(instance, nil) 37 | logger.Info(help) 38 | logger.Fatal(err) 39 | } 40 | }) 41 | return instance 42 | } 43 | -------------------------------------------------------------------------------- /rest-api-tutorial/internal/handlers/handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "github.com/julienschmidt/httprouter" 4 | 5 | type Handler interface { 6 | Register(router *httprouter.Router) 7 | } 8 | -------------------------------------------------------------------------------- /rest-api-tutorial/pkg/client/mongodb/mongodb.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go.mongodb.org/mongo-driver/mongo" 7 | "go.mongodb.org/mongo-driver/mongo/options" 8 | ) 9 | 10 | func NewClient(ctx context.Context, host, port, username, password, database, authDB string) (db *mongo.Database, err error) { 11 | var mongoDBURL string 12 | var isAuth bool 13 | if username == "" && password == "" { 14 | mongoDBURL = fmt.Sprintf("mongodb://%s:%s", host, port) 15 | } else { 16 | isAuth = true 17 | mongoDBURL = fmt.Sprintf("mongodb://%s:%s@%s:%s", username, password, host, port) 18 | } 19 | 20 | clientOptions := options.Client().ApplyURI(mongoDBURL) 21 | if isAuth { 22 | if authDB == "" { 23 | authDB = database 24 | } 25 | clientOptions.SetAuth(options.Credential{ 26 | AuthSource: authDB, 27 | Username: username, 28 | Password: password, 29 | }) 30 | } 31 | 32 | client, err := mongo.Connect(ctx, clientOptions) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to connect to mongoDB due to error: %v", err) 35 | } 36 | 37 | if err = client.Ping(ctx, nil); err != nil { 38 | return nil, fmt.Errorf("failed to ping mongoDB due to error: %v", err) 39 | } 40 | 41 | return client.Database(database), nil 42 | } 43 | -------------------------------------------------------------------------------- /rest-api-tutorial/pkg/client/postgresql/postgresql.go: -------------------------------------------------------------------------------- 1 | package postgresql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/jackc/pgconn" 7 | "github.com/jackc/pgx/v4" 8 | "github.com/jackc/pgx/v4/pgxpool" 9 | "log" 10 | "restapi-lesson/internal/config" 11 | "restapi-lesson/pkg/utils" 12 | "time" 13 | ) 14 | 15 | type Client interface { 16 | Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error) 17 | Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) 18 | QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row 19 | Begin(ctx context.Context) (pgx.Tx, error) 20 | } 21 | 22 | func NewClient(ctx context.Context, maxAttempts int, sc config.StorageConfig) (pool *pgxpool.Pool, err error) { 23 | dsn := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s", sc.Username, sc.Password, sc.Host, sc.Port, sc.Database) 24 | err = repeatable.DoWithTries(func() error { 25 | ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 26 | defer cancel() 27 | 28 | pool, err = pgxpool.Connect(ctx, dsn) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | }, maxAttempts, 5*time.Second) 35 | 36 | if err != nil { 37 | log.Fatal("error do with tries postgresql") 38 | } 39 | 40 | return pool, nil 41 | } 42 | -------------------------------------------------------------------------------- /rest-api-tutorial/pkg/logging/logging.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sirupsen/logrus" 6 | "io" 7 | "os" 8 | "path" 9 | "runtime" 10 | ) 11 | 12 | type writerHook struct { 13 | Writer []io.Writer 14 | LogLevels []logrus.Level 15 | } 16 | 17 | func (hook *writerHook) Fire(entry *logrus.Entry) error { 18 | line, err := entry.String() 19 | if err != nil { 20 | return err 21 | } 22 | for _, w := range hook.Writer { 23 | w.Write([]byte(line)) 24 | } 25 | return err 26 | } 27 | 28 | func (hook *writerHook) Levels() []logrus.Level { 29 | return hook.LogLevels 30 | } 31 | 32 | var e *logrus.Entry 33 | 34 | type Logger struct { 35 | *logrus.Entry 36 | } 37 | 38 | func GetLogger() *Logger { 39 | return &Logger{e} 40 | } 41 | 42 | func (l *Logger) GetLoggerWithField(k string, v interface{}) *Logger { 43 | return &Logger{l.WithField(k, v)} 44 | } 45 | 46 | func init() { 47 | l := logrus.New() 48 | l.SetReportCaller(true) 49 | l.Formatter = &logrus.TextFormatter{ 50 | CallerPrettyfier: func(frame *runtime.Frame) (function string, file string) { 51 | filename := path.Base(frame.File) 52 | return fmt.Sprintf("%s()", frame.Function), fmt.Sprintf("%s:%d", filename, frame.Line) 53 | }, 54 | DisableColors: false, 55 | FullTimestamp: true, 56 | } 57 | 58 | err := os.MkdirAll("logs", 0644) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | allFile, err := os.OpenFile("logs/all.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | l.SetOutput(io.Discard) 69 | 70 | l.AddHook(&writerHook{ 71 | Writer: []io.Writer{allFile, os.Stdout}, 72 | LogLevels: logrus.AllLevels, 73 | }) 74 | 75 | l.SetLevel(logrus.TraceLevel) 76 | 77 | e = logrus.NewEntry(l) 78 | } 79 | -------------------------------------------------------------------------------- /rest-api-tutorial/pkg/utils/repeatable.go: -------------------------------------------------------------------------------- 1 | package repeatable 2 | 3 | import "time" 4 | 5 | func DoWithTries(fn func() error, attemtps int, delay time.Duration) (err error) { 6 | for attemtps > 0 { 7 | if err = fn(); err != nil { 8 | time.Sleep(delay) 9 | attemtps-- 10 | 11 | continue 12 | } 13 | 14 | return nil 15 | } 16 | 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /rest-api-tutorial/test-api.http: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | GET http://localhost:1234/authors 4 | Content-Type: application/json 5 | 6 | ### 7 | 8 | GET http://localhost:1234/users/123 9 | Content-Type: application/json 10 | 11 | ### 12 | 13 | GET http://localhost:1234/users 14 | Content-Type: application/json 15 | 16 | ### 17 | 18 | POST http://localhost:1234/users 19 | Content-Type: application/json 20 | 21 | {} 22 | 23 | > {% 24 | client.test("Request executed successfully", function() { 25 | client.assert(response.status === 201, "Response status is not 201"); 26 | }); 27 | %} 28 | 29 | ### 30 | 31 | PUT http://localhost:1234/users/1 32 | Content-Type: application/json 33 | 34 | {} 35 | 36 | > {% 37 | client.test("Request executed successfully", function() { 38 | client.assert(response.status === 204, "Response status is not 204"); 39 | }); 40 | %} 41 | 42 | ### 43 | 44 | PATCH http://localhost:1234/users/1 45 | Content-Type: application/json 46 | 47 | {} 48 | 49 | > {% 50 | client.test("Request executed successfully", function() { 51 | client.assert(response.status === 204, "Response status is not 204"); 52 | }); 53 | %} 54 | 55 | ### 56 | 57 | DELETE http://localhost:1234/users/1 58 | Content-Type: application/json 59 | 60 | {} 61 | 62 | > {% 63 | client.test("Request executed successfully", function() { 64 | client.assert(response.status === 204, "Response status is not 204"); 65 | }); 66 | %} 67 | 68 | --------------------------------------------------------------------------------