├── 01_intro
└── 01_hello
│ └── main.go
├── 02_routing
├── 01_simple_routing
│ └── main.go
├── 02_simple_routing_username
│ └── main.go
├── 03_http_mux
│ └── main.go
├── 04_http_mux_404
│ └── main.go
└── 05_gorilla_mux
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── 03_tools
└── 01_air
│ ├── README
│ ├── go.mod
│ └── main.go
├── 04_json
├── 01_json_marshal
│ └── main.go
├── 02_json_unmarshal
│ └── main.go
├── 03_json_struct_generate
│ ├── README
│ └── main.go
├── 04_json_response
│ └── main.go
├── 05_json_fix
│ └── main.go
├── 06_json_request
│ └── main.go
└── 07_json_to_map
│ └── main.go
├── 05_validation
├── 01_validation
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── 02_lang
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── 03_i18n
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── 07_forms
├── 01_form
│ ├── form.html
│ └── main.go
└── 02_form_file
│ ├── form.html
│ └── main.go
├── 07_middleware
├── 01_middleware
│ └── main.go
├── 02_middleware_order
│ └── main.go
├── 03_middleware_gorilla_mux
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── 04_logging
│ └── main.go
└── 05_auth
│ ├── README
│ └── main.go
├── 08_templates
├── 01_template
│ ├── main.go
│ └── templates
│ │ ├── 1.txt
│ │ ├── 2.txt
│ │ └── 3.txt
├── 02_template_layout
│ ├── main.go
│ └── templates
│ │ ├── layout1.txt
│ │ ├── layout2.txt
│ │ ├── page1.txt
│ │ └── page2.txt
├── 03_template_funcmap
│ ├── main.go
│ └── some.txt
├── 04_template_html
│ ├── index.html
│ └── main.go
└── 05_template_parse
│ └── main.go
├── 09_database
├── 01_pgx
│ ├── .env
│ ├── db.sql
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── 02_gorm
│ ├── .env
│ ├── db.sql
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── 03_goose
│ ├── .env
│ ├── cmd
│ │ ├── migrate_gorm
│ │ │ └── main.go
│ │ └── migrate_pgx
│ │ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ └── migrations
│ │ ├── 20221115125338_init.sql
│ │ └── 20221115125345_photo.sql
├── 04_golang_migrate
│ ├── .env
│ ├── README
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── migrations
│ │ ├── 000001_init.down.sql
│ │ ├── 000001_init.up.sql
│ │ ├── 000002_photos.down.sql
│ │ └── 000002_photos.up.sql
└── README
├── 10_testing
├── 01_basic
│ ├── go.mod
│ ├── main.go
│ └── utils
│ │ ├── utils.go
│ │ └── utils_test.go
├── 02_stdlib
│ ├── .env
│ ├── .env.test
│ ├── cmd
│ │ └── server
│ │ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal
│ │ ├── app
│ │ │ └── app.go
│ │ ├── handlers
│ │ │ ├── ping.go
│ │ │ ├── ping_test.go
│ │ │ ├── post.go
│ │ │ └── post_test.go
│ │ ├── models
│ │ │ └── post.go
│ │ ├── rest
│ │ │ └── rest.go
│ │ └── tests
│ │ │ ├── db.go
│ │ │ └── fs.go
│ └── migrations
│ │ ├── 01_init.down.sql
│ │ └── 01_init.up.sql
├── 04_testify
│ ├── .env
│ ├── .env.test
│ ├── cmd
│ │ └── server
│ │ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal
│ │ ├── app
│ │ │ └── app.go
│ │ ├── handlers
│ │ │ ├── ping.go
│ │ │ ├── ping_test.go
│ │ │ ├── post.go
│ │ │ └── post_test.go
│ │ ├── models
│ │ │ └── post.go
│ │ ├── rest
│ │ │ └── rest.go
│ │ └── tests
│ │ │ ├── db.go
│ │ │ └── fs.go
│ └── migrations
│ │ ├── 01_init.down.sql
│ │ └── 01_init.up.sql
└── 05_apitest
│ ├── .env
│ ├── .env.test
│ ├── cmd
│ └── server
│ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal
│ ├── app
│ │ └── app.go
│ ├── handlers
│ │ ├── ping.go
│ │ ├── ping_test.go
│ │ ├── post.go
│ │ └── post_test.go
│ ├── models
│ │ └── post.go
│ ├── rest
│ │ └── rest.go
│ └── tests
│ │ ├── app.go
│ │ ├── db.go
│ │ └── fs.go
│ └── migrations
│ ├── 01_init.down.sql
│ └── 01_init.up.sql
└── 11_additional
└── 01_serve_static
├── main.go
└── static
├── css
└── style.css
├── img
└── cat.jpg
├── index.html
└── js
└── main.js
/01_intro/01_hello/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type MyHandler struct {
8 | }
9 |
10 | func (MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
11 | w.Write([]byte("Hello world!"))
12 | }
13 |
14 | func main() {
15 | err := http.ListenAndServe(":3000", MyHandler{})
16 | if err != nil {
17 | panic(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/02_routing/01_simple_routing/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type MyHandler struct {
8 | }
9 |
10 | func (MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
11 | if r.URL.Path == "/" {
12 | w.Write([]byte("Home"))
13 | return
14 | }
15 |
16 | if r.URL.Path == "/hello" {
17 | w.Write([]byte("Hello, user"))
18 | return
19 | }
20 |
21 | w.WriteHeader(http.StatusNotFound)
22 | w.Write([]byte("404 Page Not Found"))
23 | }
24 |
25 | func main() {
26 | err := http.ListenAndServe(":3000", MyHandler{})
27 | if err != nil {
28 | panic(err)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/02_routing/02_simple_routing_username/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strings"
7 | )
8 |
9 | type MyHandler struct {
10 | }
11 |
12 | func (MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
13 | if r.URL.Path == "/" {
14 | w.Write([]byte("Home"))
15 | return
16 | }
17 |
18 | if strings.HasPrefix(r.URL.Path, "/hello/") {
19 | name := strings.Split(r.URL.Path, "/")[2]
20 | w.Write([]byte(fmt.Sprintf("Hello, %s", name)))
21 | return
22 | }
23 |
24 | w.WriteHeader(http.StatusNotFound)
25 | w.Write([]byte("404 Page Not Found"))
26 | }
27 |
28 | func main() {
29 | err := http.ListenAndServe(":3000", MyHandler{})
30 | if err != nil {
31 | panic(err)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/02_routing/03_http_mux/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strings"
7 | )
8 |
9 | func main() {
10 | http.HandleFunc("/", index)
11 | http.HandleFunc("/hello/", hello)
12 |
13 | err := http.ListenAndServe(":3000", nil)
14 | if err != nil {
15 | panic(err)
16 | }
17 | }
18 |
19 | func index(w http.ResponseWriter, r *http.Request) {
20 | w.Write([]byte("Home"))
21 | }
22 |
23 | func hello(w http.ResponseWriter, r *http.Request) {
24 | name := strings.Split(r.URL.Path, "/")[2]
25 | w.Write([]byte(fmt.Sprintf("Hello, %s", name)))
26 | }
27 |
--------------------------------------------------------------------------------
/02_routing/04_http_mux_404/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | func main() {
11 | mux := http.NewServeMux()
12 |
13 | mux.HandleFunc("/", index)
14 | mux.HandleFunc("/hello/", hello)
15 |
16 | err := http.ListenAndServe(":3000", mux)
17 | if err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func index(w http.ResponseWriter, r *http.Request) {
23 | if r.URL.Path != "/" {
24 | handler404(w, r)
25 | return
26 | }
27 | w.Write([]byte("Home"))
28 | }
29 |
30 | func hello(w http.ResponseWriter, r *http.Request) {
31 | pathRegexp := regexp.MustCompile(`^/hello/\w+$`)
32 | if !pathRegexp.MatchString(r.URL.Path) {
33 | handler404(w, r)
34 | return
35 | }
36 | username := strings.Split(r.URL.Path, "/")[2]
37 | fmt.Fprintf(w, "Hello, %s", username)
38 | }
39 |
40 | func handler404(w http.ResponseWriter, r *http.Request) {
41 | w.WriteHeader(http.StatusNotFound)
42 | w.Write([]byte("404 Page Not Found"))
43 | }
44 |
--------------------------------------------------------------------------------
/02_routing/05_gorilla_mux/go.mod:
--------------------------------------------------------------------------------
1 | module go-course/gorilla_mux
2 |
3 | go 1.19
4 |
5 | require github.com/gorilla/mux v1.8.0 // indirect
6 |
--------------------------------------------------------------------------------
/02_routing/05_gorilla_mux/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
3 |
--------------------------------------------------------------------------------
/02_routing/05_gorilla_mux/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 |
8 | "github.com/gorilla/mux"
9 | )
10 |
11 | func main() {
12 | r := mux.NewRouter()
13 | r.HandleFunc("/", home)
14 | r.HandleFunc("/hello/{username}", hello)
15 | r.HandleFunc(`/product/{id:\d+}`, product)
16 | r.HandleFunc(`/form`, form).Methods("POST", "PUT")
17 | r.NotFoundHandler = http.HandlerFunc(handler404)
18 |
19 | err := http.ListenAndServe(":3000", r)
20 | if err != nil {
21 | panic(err)
22 | }
23 | }
24 |
25 | func home(w http.ResponseWriter, r *http.Request) {
26 | io.WriteString(w, "Home")
27 | }
28 |
29 | func hello(w http.ResponseWriter, r *http.Request) {
30 | vars := mux.Vars(r)
31 | username := vars["username"]
32 | fmt.Fprintf(w, "Hello %s!", username)
33 | }
34 |
35 | func product(w http.ResponseWriter, r *http.Request) {
36 | vars := mux.Vars(r)
37 | id := vars["id"]
38 | fmt.Fprintf(w, "Product ID %s", id)
39 | }
40 |
41 | func form(w http.ResponseWriter, r *http.Request) {
42 | io.WriteString(w, "Form")
43 | }
44 |
45 | func handler404(w http.ResponseWriter, r *http.Request) {
46 | w.WriteHeader(http.StatusNotFound)
47 | io.WriteString(w, "404 Page Not Found")
48 | }
49 |
--------------------------------------------------------------------------------
/03_tools/01_air/README:
--------------------------------------------------------------------------------
1 | https://github.com/cosmtrek/air
2 |
3 | https://www.npmjs.com/package/nodemon
4 |
5 | nodemon -e go -x 'go run main.go || exit 1' --signal SIGTERM
--------------------------------------------------------------------------------
/03_tools/01_air/go.mod:
--------------------------------------------------------------------------------
1 | module go-course/air
2 |
3 | go 1.19
4 |
--------------------------------------------------------------------------------
/03_tools/01_air/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | func index(w http.ResponseWriter, r *http.Request) {
8 | w.Write([]byte("Air"))
9 | }
10 |
11 | func main() {
12 | err := http.ListenAndServe(":3000", http.HandlerFunc(index))
13 | if err != nil {
14 | panic(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/04_json/01_json_marshal/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type User struct {
9 | Id int `json:"id"`
10 | Name string `json:"name"`
11 | }
12 |
13 | func main() {
14 | user1 := User{Name: "Ivan", Id: 555}
15 | bytes, _ := json.Marshal(user1)
16 | fmt.Println(string(bytes))
17 | }
18 |
--------------------------------------------------------------------------------
/04_json/02_json_unmarshal/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | var DATA = `
9 | {
10 | "id": 55,
11 | "price": 3000,
12 | "items": [
13 | {
14 | "name": "snowbord",
15 | "number": 1
16 | },
17 | {
18 | "name": "ball",
19 | "number": 4
20 | }
21 | ]
22 | }
23 | `
24 |
25 | type Order struct {
26 | Id int `json:"id"`
27 | Price int `json:"price"`
28 | Items []Item `json:"items"`
29 | }
30 |
31 | type Item struct {
32 | Name string `json:"name"`
33 | Number int `json:"number"`
34 | }
35 |
36 | func main() {
37 | var order1 Order
38 |
39 | err := json.Unmarshal([]byte(DATA), &order1)
40 | if err != nil {
41 | panic(err)
42 | }
43 |
44 | fmt.Printf("%v", order1)
45 | }
46 |
--------------------------------------------------------------------------------
/04_json/03_json_struct_generate/README:
--------------------------------------------------------------------------------
1 | https://json2struct.mervine.net/
2 | https://mholt.github.io/json-to-go/
3 |
--------------------------------------------------------------------------------
/04_json/03_json_struct_generate/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | var DATA = `
9 | {
10 | "id": 55,
11 | "price": 3000,
12 | "items": [
13 | {
14 | "name": "snowbord",
15 | "number": 1
16 | },
17 | {
18 | "name": "ball",
19 | "number": 4
20 | }
21 | ]
22 | }
23 | `
24 |
25 | type Order struct {
26 | ID int64 `json:"id"`
27 | Items []struct {
28 | Name string `json:"name"`
29 | Number int64 `json:"number"`
30 | } `json:"items"`
31 | Price int64 `json:"price"`
32 | }
33 |
34 | func main() {
35 | var order1 Order
36 | err := json.Unmarshal([]byte(DATA), &order1)
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | bytes, err := json.Marshal(order1)
42 | if err != nil {
43 | panic(err)
44 | }
45 | fmt.Println(string(bytes))
46 | }
47 |
--------------------------------------------------------------------------------
/04_json/04_json_response/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | type User struct {
9 | Id int `json:"id"`
10 | Name string `json:"name"`
11 | }
12 |
13 | func main() {
14 | http.HandleFunc("/user", UserHandler)
15 | err := http.ListenAndServe(":3000", nil)
16 | if err != nil {
17 | panic(err)
18 | }
19 | }
20 |
21 | func WriteJson(w http.ResponseWriter, status int, v any) error {
22 | w.Header().Set("Content-Type", "application/json")
23 | return json.NewEncoder(w).Encode(v)
24 | }
25 |
26 | func UserHandler(w http.ResponseWriter, r *http.Request) {
27 | user1 := User{Name: "Andrey", Id: 555}
28 | err := WriteJson(w, http.StatusOK, user1)
29 |
30 | if err != nil {
31 | WriteJson(w, http.StatusInternalServerError, map[string]any{
32 | "ok": false,
33 | "error": err.Error(),
34 | })
35 | return
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/04_json/05_json_fix/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | type User struct {
9 | Id int `json:"id"`
10 | Name string `json:"name"`
11 | }
12 |
13 | func main() {
14 | http.HandleFunc("/user", UserHandler)
15 | err := http.ListenAndServe(":3000", nil)
16 | if err != nil {
17 | panic(err)
18 | }
19 | }
20 |
21 | func WriteJson(w http.ResponseWriter, status int, v any) error {
22 | w.Header().Set("Content-Type", "application/json")
23 | return json.NewEncoder(w).Encode(v)
24 | }
25 |
26 | func UserHandler(w http.ResponseWriter, r *http.Request) {
27 | user1 := User{Name: "Andrey", Id: 555}
28 | err := WriteJson(w, http.StatusOK, user1)
29 |
30 | if err != nil {
31 | WriteJson(w, http.StatusInternalServerError, map[string]any{
32 | "ok": false,
33 | "error": err.Error(),
34 | })
35 | return
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/04_json/06_json_request/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | type User struct {
10 | Id int `json:"id"`
11 | Name string `json:"name"`
12 | }
13 |
14 | func main() {
15 | http.HandleFunc("/user", UserHandler)
16 | err := http.ListenAndServe(":3000", nil)
17 | if err != nil {
18 | panic(err)
19 | }
20 | }
21 |
22 | func WriteJson(w http.ResponseWriter, status int, v any) error {
23 | w.Header().Set("Content-Type", "application/json")
24 | return json.NewEncoder(w).Encode(v)
25 | }
26 |
27 | func UserHandler(w http.ResponseWriter, r *http.Request) {
28 | if r.Method != http.MethodPost {
29 | WriteJson(w, http.StatusMethodNotAllowed, map[string]any{
30 | "ok": false,
31 | "error": "method not allowed",
32 | })
33 | return
34 | }
35 |
36 | var user User
37 | err := json.NewDecoder(r.Body).Decode(&user)
38 | if err != nil {
39 | WriteJson(w, http.StatusInternalServerError, map[string]any{
40 | "ok": false,
41 | "error": err.Error(),
42 | })
43 | return
44 | }
45 |
46 | fmt.Printf("user %v", user)
47 |
48 | WriteJson(w, http.StatusOK, map[string]any{
49 | "ok": true,
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/04_json/07_json_to_map/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | var JSON_STRING = `
9 | {
10 | "id": 55,
11 | "price": 3000,
12 | "items": [
13 | {
14 | "name": "snowbord",
15 | "number": 1
16 | },
17 | {
18 | "name": "ball",
19 | "number": 4
20 | }
21 | ]
22 | }
23 | `
24 |
25 | func main() {
26 | var data map[string]any
27 |
28 | err := json.Unmarshal([]byte(JSON_STRING), &data)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | id, _ := data["id"].(float64)
34 |
35 | fmt.Printf("id: %v", id)
36 | }
37 |
--------------------------------------------------------------------------------
/05_validation/01_validation/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
7 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
8 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/05_validation/01_validation/go.sum:
--------------------------------------------------------------------------------
1 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
2 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
5 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
6 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
7 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
10 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
12 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
13 |
--------------------------------------------------------------------------------
/05_validation/01_validation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 |
8 | validation "github.com/go-ozzo/ozzo-validation/v4"
9 | "github.com/go-ozzo/ozzo-validation/v4/is"
10 | )
11 |
12 | type User struct {
13 | Id int `json:"id"`
14 | Name string `json:"name"`
15 | Email string `json:"email"`
16 | Phone string `json:"phone"`
17 | }
18 |
19 | func (u User) Validate() error {
20 | return validation.ValidateStruct(&u,
21 | validation.Field(&u.Name, validation.Required, validation.Length(2, 50)),
22 | validation.Field(&u.Email, validation.Required),
23 | validation.Field(&u.Phone, is.E164),
24 | )
25 | }
26 |
27 | func main() {
28 | http.HandleFunc("/user", UserHandler)
29 | err := http.ListenAndServe(":3000", nil)
30 | if err != nil {
31 | panic(err)
32 | }
33 | }
34 |
35 | func WriteJson(w http.ResponseWriter, status int, v any) error {
36 | w.Header().Set("Content-Type", "application/json")
37 | return json.NewEncoder(w).Encode(v)
38 | }
39 |
40 | func UserHandler(w http.ResponseWriter, r *http.Request) {
41 | if r.Method != http.MethodPost {
42 | WriteJson(w, http.StatusMethodNotAllowed, map[string]any{
43 | "ok": false,
44 | "error": "method not allowed",
45 | })
46 | return
47 | }
48 |
49 | var user User
50 | err := json.NewDecoder(r.Body).Decode(&user)
51 | if err != nil {
52 | WriteJson(w, http.StatusInternalServerError, map[string]any{
53 | "ok": false,
54 | "error": err.Error(),
55 | })
56 | return
57 | }
58 |
59 | err = user.Validate()
60 | if err != nil {
61 | WriteJson(w, http.StatusBadRequest, map[string]any{
62 | "ok": false,
63 | "error": err.Error(),
64 | })
65 | return
66 | }
67 |
68 | fmt.Printf("user %v", user)
69 |
70 | WriteJson(w, http.StatusOK, map[string]any{
71 | "ok": true,
72 | })
73 | }
74 |
--------------------------------------------------------------------------------
/05_validation/02_lang/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
7 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
8 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/05_validation/02_lang/go.sum:
--------------------------------------------------------------------------------
1 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
2 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
5 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
6 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
7 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
10 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
12 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
13 |
--------------------------------------------------------------------------------
/05_validation/02_lang/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 |
8 | validation "github.com/go-ozzo/ozzo-validation/v4"
9 | "github.com/go-ozzo/ozzo-validation/v4/is"
10 | )
11 |
12 | type User struct {
13 | Id int `json:"id" ozzo:"id"`
14 | Name string `json:"name" ozzo:"имя"`
15 | Email string `json:"email" ozzo:"почта"`
16 | Phone string `json:"phone" ozzo:"телефон"`
17 | }
18 |
19 | func (u User) Validate() error {
20 | return validation.ValidateStruct(&u,
21 | validation.Field(&u.Name, validation.Required,
22 | validation.Length(2, 50).Error("длинна должна быть от 2 до 50 символов")),
23 | validation.Field(&u.Email, validation.Required,
24 | is.Email.Error("неверный адрес почты")),
25 | validation.Field(&u.Phone, is.E164.Error("неверный номер")),
26 | )
27 | }
28 |
29 | func main() {
30 | validation.ErrorTag = "ozzo"
31 |
32 | http.HandleFunc("/user", UserHandler)
33 | err := http.ListenAndServe(":3000", nil)
34 | if err != nil {
35 | panic(err)
36 | }
37 | }
38 |
39 | func WriteJson(w http.ResponseWriter, status int, v any) error {
40 | w.Header().Set("Content-Type", "application/json")
41 | return json.NewEncoder(w).Encode(v)
42 | }
43 |
44 | func UserHandler(w http.ResponseWriter, r *http.Request) {
45 | if r.Method != http.MethodPost {
46 | WriteJson(w, http.StatusMethodNotAllowed, map[string]any{
47 | "ok": false,
48 | "error": "method not allowed",
49 | })
50 | return
51 | }
52 |
53 | var user User
54 | err := json.NewDecoder(r.Body).Decode(&user)
55 | if err != nil {
56 | WriteJson(w, http.StatusInternalServerError, map[string]any{
57 | "ok": false,
58 | "error": err.Error(),
59 | })
60 | return
61 | }
62 |
63 | err = user.Validate()
64 | if err != nil {
65 | WriteJson(w, http.StatusBadRequest, map[string]any{
66 | "ok": false,
67 | "error": err.Error(),
68 | })
69 | return
70 | }
71 |
72 | fmt.Printf("user %v", user)
73 |
74 | WriteJson(w, http.StatusOK, map[string]any{
75 | "ok": true,
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/05_validation/03_i18n/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
7 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
8 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/05_validation/03_i18n/go.sum:
--------------------------------------------------------------------------------
1 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
2 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
5 | github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
6 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
7 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
10 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
12 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
13 |
--------------------------------------------------------------------------------
/05_validation/03_i18n/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 |
8 | validation "github.com/go-ozzo/ozzo-validation/v4"
9 | "github.com/go-ozzo/ozzo-validation/v4/is"
10 | )
11 |
12 | type User struct {
13 | Id int `json:"id"`
14 | Name string `json:"name"`
15 | Email string `json:"email"`
16 | Phone string `json:"phone"`
17 | }
18 |
19 | func (u User) Validate() error {
20 | return validation.ValidateStruct(&u,
21 | validation.Field(&u.Name, validation.Required, validation.Length(2, 50)),
22 | validation.Field(&u.Email, validation.Required),
23 | validation.Field(&u.Phone, is.E164),
24 | )
25 | }
26 |
27 | func main() {
28 | http.HandleFunc("/user", UserHandler)
29 | err := http.ListenAndServe(":3000", nil)
30 | if err != nil {
31 | panic(err)
32 | }
33 | }
34 |
35 | func WriteJson(w http.ResponseWriter, status int, v any) error {
36 | w.Header().Set("Content-Type", "application/json")
37 | return json.NewEncoder(w).Encode(v)
38 | }
39 |
40 | func UserHandler(w http.ResponseWriter, r *http.Request) {
41 | if r.Method != http.MethodPost {
42 | WriteJson(w, http.StatusMethodNotAllowed, map[string]any{
43 | "ok": false,
44 | "error": "method not allowed",
45 | })
46 | return
47 | }
48 |
49 | var user User
50 | err := json.NewDecoder(r.Body).Decode(&user)
51 | if err != nil {
52 | WriteJson(w, http.StatusInternalServerError, map[string]any{
53 | "ok": false,
54 | "error": err.Error(),
55 | })
56 | return
57 | }
58 |
59 | err = user.Validate()
60 |
61 | errors, ok := err.(validation.Errors)
62 | if ok {
63 | for fieldName, err := range errors {
64 | verr, ok := err.(validation.Error)
65 | if ok {
66 | fmt.Printf("%#v %#v %#v\n", fieldName, verr.Code(), verr.Error())
67 | }
68 | }
69 | }
70 |
71 | if err != nil {
72 | WriteJson(w, http.StatusBadRequest, map[string]any{
73 | "ok": false,
74 | "error": err.Error(),
75 | })
76 | return
77 | }
78 |
79 | fmt.Printf("user %v", user)
80 |
81 | WriteJson(w, http.StatusOK, map[string]any{
82 | "ok": true,
83 | })
84 | }
85 |
--------------------------------------------------------------------------------
/07_forms/01_form/form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Form
6 |
7 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/07_forms/01_form/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | http.HandleFunc("/form", FormHandler)
10 | err := http.ListenAndServe(":3000", nil)
11 | if err != nil {
12 | panic(err)
13 | }
14 | }
15 |
16 | func FormHandler(w http.ResponseWriter, r *http.Request) {
17 | if r.Method != http.MethodPost {
18 | io.WriteString(w, "405 Method Not Allowed")
19 | return
20 | }
21 |
22 | foo := r.FormValue("foo")
23 |
24 | io.WriteString(w, "OK: "+foo)
25 | }
26 |
--------------------------------------------------------------------------------
/07_forms/02_form_file/form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Form
6 |
7 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/07_forms/02_form_file/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "net/http"
8 | "path"
9 | )
10 |
11 | func main() {
12 | http.HandleFunc("/form", FormHandler)
13 | err := http.ListenAndServe(":3000", nil)
14 | if err != nil {
15 | panic(err)
16 | }
17 | }
18 |
19 | func FormHandler(w http.ResponseWriter, r *http.Request) {
20 | err := r.ParseMultipartForm(10 * 1024 * 1024)
21 | if err != nil {
22 | w.WriteHeader(http.StatusBadRequest)
23 | io.WriteString(w, err.Error())
24 | return
25 | }
26 |
27 | file, header, err := r.FormFile("myfile")
28 | if err != nil {
29 | w.WriteHeader(http.StatusInternalServerError)
30 | io.WriteString(w, err.Error())
31 | return
32 | }
33 | defer file.Close()
34 |
35 | fmt.Println("filename", header.Filename)
36 | fmt.Println("MIME Type", header.Header["Content-Type"])
37 | fmt.Println("Size", header.Size)
38 |
39 | ext := path.Ext(header.Filename)
40 | tempFile, err := ioutil.TempFile("/tmp", "*"+ext)
41 | if err != nil {
42 | w.WriteHeader(http.StatusInternalServerError)
43 | io.WriteString(w, err.Error())
44 | return
45 | }
46 | defer tempFile.Close()
47 |
48 | fmt.Println("save to", tempFile.Name())
49 |
50 | bytes, err := io.ReadAll(file)
51 | if err != nil {
52 | w.WriteHeader(http.StatusInternalServerError)
53 | io.WriteString(w, err.Error())
54 | return
55 | }
56 |
57 | _, err = tempFile.Write(bytes)
58 | if err != nil {
59 | w.WriteHeader(http.StatusInternalServerError)
60 | io.WriteString(w, err.Error())
61 | return
62 | }
63 |
64 | fmt.Fprintf(w, "File uploaded!")
65 | }
66 |
--------------------------------------------------------------------------------
/07_middleware/01_middleware/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | mux := http.NewServeMux()
10 |
11 | mux.HandleFunc("/", HomeHandler)
12 | mux.HandleFunc("/foo", FooHandler)
13 |
14 | handler := MyMiddleware(mux)
15 | handler = SecondMiddleware(handler)
16 |
17 | err := http.ListenAndServe(":3000", handler)
18 | if err != nil {
19 | panic(err)
20 | }
21 | }
22 |
23 | func HomeHandler(w http.ResponseWriter, r *http.Request) {
24 | fmt.Println("home")
25 | w.Write([]byte("Home"))
26 | }
27 |
28 | func FooHandler(w http.ResponseWriter, r *http.Request) {
29 | fmt.Println("foo")
30 | w.Write([]byte("Foo"))
31 | }
32 |
33 | func MyMiddleware(handler http.Handler) http.Handler {
34 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35 | fmt.Println("before")
36 | handler.ServeHTTP(w, r)
37 | fmt.Println("after")
38 | })
39 | }
40 |
41 | func SecondMiddleware(handler http.Handler) http.Handler {
42 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43 | fmt.Println("before 2")
44 | handler.ServeHTTP(w, r)
45 | fmt.Println("after 2")
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/07_middleware/02_middleware_order/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | mux := http.NewServeMux()
10 |
11 | mux.HandleFunc("/", HomeHandler)
12 | mux.HandleFunc("/foo", FooHandler)
13 |
14 | middlewares := []func(http.Handler) http.Handler{
15 | MyMiddleware,
16 | SecondMiddleware,
17 | }
18 |
19 | handler := http.Handler(mux)
20 | for i := len(middlewares) - 1; i >= 0; i-- {
21 | handler = middlewares[i](handler)
22 | }
23 |
24 | err := http.ListenAndServe(":3000", handler)
25 | if err != nil {
26 | panic(err)
27 | }
28 | }
29 |
30 | func HomeHandler(w http.ResponseWriter, r *http.Request) {
31 | fmt.Println("home")
32 | w.Write([]byte("Home"))
33 | }
34 |
35 | func FooHandler(w http.ResponseWriter, r *http.Request) {
36 | fmt.Println("foo")
37 | w.Write([]byte("Foo"))
38 | }
39 |
40 | func MyMiddleware(handler http.Handler) http.Handler {
41 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
42 | fmt.Println("before")
43 | handler.ServeHTTP(w, r)
44 | fmt.Println("after")
45 | })
46 | }
47 |
48 | func SecondMiddleware(handler http.Handler) http.Handler {
49 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
50 | fmt.Println("before 2")
51 | handler.ServeHTTP(w, r)
52 | fmt.Println("after 2")
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/07_middleware/03_middleware_gorilla_mux/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require github.com/gorilla/mux v1.8.0 // indirect
6 |
--------------------------------------------------------------------------------
/07_middleware/03_middleware_gorilla_mux/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
3 |
--------------------------------------------------------------------------------
/07_middleware/03_middleware_gorilla_mux/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/gorilla/mux"
8 | )
9 |
10 | func main() {
11 | r := mux.NewRouter()
12 |
13 | r.HandleFunc("/", HomeHandler)
14 | r.HandleFunc("/foo", FooHandler)
15 |
16 | r.Use(MyMiddleware)
17 | r.Use(SecondMiddleware)
18 |
19 | err := http.ListenAndServe(":3000", r)
20 | if err != nil {
21 | panic(err)
22 | }
23 | }
24 |
25 | func HomeHandler(w http.ResponseWriter, r *http.Request) {
26 | fmt.Println("home")
27 | w.Write([]byte("Home"))
28 | }
29 |
30 | func FooHandler(w http.ResponseWriter, r *http.Request) {
31 | fmt.Println("foo")
32 | w.Write([]byte("Foo"))
33 | }
34 |
35 | func MyMiddleware(next http.Handler) http.Handler {
36 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37 | fmt.Println("before")
38 | next.ServeHTTP(w, r)
39 | fmt.Println("after")
40 | })
41 | }
42 |
43 | func SecondMiddleware(next http.Handler) http.Handler {
44 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45 | fmt.Println("before 2")
46 | next.ServeHTTP(w, r)
47 | fmt.Println("after 2")
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/07_middleware/04_logging/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | mux := http.NewServeMux()
11 |
12 | mux.HandleFunc("/", HomeHandler)
13 | mux.HandleFunc("/foo", FooHandler)
14 |
15 | middlewares := []func(http.Handler) http.Handler{
16 | LoggingMiddleware,
17 | SecondMiddleware,
18 | }
19 |
20 | handler := http.Handler(mux)
21 | for i := len(middlewares) - 1; i >= 0; i-- {
22 | handler = middlewares[i](handler)
23 | }
24 |
25 | err := http.ListenAndServe(":3000", handler)
26 | if err != nil {
27 | panic(err)
28 | }
29 | }
30 |
31 | func HomeHandler(w http.ResponseWriter, r *http.Request) {
32 | fmt.Println("home")
33 | w.Write([]byte("Home"))
34 | }
35 |
36 | func FooHandler(w http.ResponseWriter, r *http.Request) {
37 | fmt.Println("foo")
38 | w.WriteHeader(400)
39 | w.Write([]byte("Foo"))
40 | }
41 |
42 | type MyResponseWriter struct {
43 | http.ResponseWriter
44 | StatusCode int
45 | }
46 |
47 | func (w *MyResponseWriter) WriteHeader(statusCode int) {
48 | w.ResponseWriter.WriteHeader(statusCode)
49 | w.StatusCode = statusCode
50 | }
51 |
52 | func LoggingMiddleware(handler http.Handler) http.Handler {
53 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
54 | w2 := &MyResponseWriter{ResponseWriter: w, StatusCode: http.StatusOK}
55 | handler.ServeHTTP(w2, r)
56 | log.Printf("%s [%d]\n", r.RequestURI, w2.StatusCode)
57 | })
58 | }
59 |
60 | func SecondMiddleware(handler http.Handler) http.Handler {
61 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62 | handler.ServeHTTP(w, r)
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/07_middleware/05_auth/README:
--------------------------------------------------------------------------------
1 | curl --cookie "session=123" localhost:3000/getme
--------------------------------------------------------------------------------
/07_middleware/05_auth/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "io"
8 | "log"
9 | "net/http"
10 | )
11 |
12 | func main() {
13 | mux := http.NewServeMux()
14 |
15 | mux.HandleFunc("/", HomeHandler)
16 | mux.HandleFunc("/getme", GetMeHandler)
17 |
18 | middlewares := []func(http.Handler) http.Handler{
19 | LoggingMiddleware,
20 | AuthMiddleware,
21 | }
22 |
23 | handler := http.Handler(mux)
24 | for i := len(middlewares) - 1; i >= 0; i-- {
25 | handler = middlewares[i](handler)
26 | }
27 |
28 | err := http.ListenAndServe(":3000", handler)
29 | if err != nil {
30 | panic(err)
31 | }
32 | }
33 |
34 | type User struct {
35 | Id int
36 | Name string
37 | }
38 |
39 | func HomeHandler(w http.ResponseWriter, r *http.Request) {
40 | WriteJSON(w, map[string]any{
41 | "ok": true,
42 | })
43 | }
44 |
45 | func GetMeHandler(w http.ResponseWriter, r *http.Request) {
46 | user, _ := r.Context().Value("user").(User)
47 |
48 | if user.Id == 0 {
49 | w.WriteHeader(http.StatusUnauthorized)
50 | WriteJSON(w, map[string]any{
51 | "ok": false,
52 | "error": "unauthorized",
53 | })
54 | return
55 | }
56 |
57 | WriteJSON(w, map[string]any{
58 | "ok": true,
59 | "user": user,
60 | })
61 | }
62 |
63 | func WriteJSON(w io.Writer, v any) {
64 | bytes, _ := json.Marshal(v)
65 | w.Write(bytes)
66 | }
67 |
68 | type MyResponseWriter struct {
69 | http.ResponseWriter
70 | StatusCode int
71 | }
72 |
73 | func (w *MyResponseWriter) WriteHeader(statusCode int) {
74 | w.ResponseWriter.WriteHeader(statusCode)
75 | w.StatusCode = statusCode
76 | }
77 |
78 | func LoggingMiddleware(handler http.Handler) http.Handler {
79 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
80 | w2 := &MyResponseWriter{ResponseWriter: w, StatusCode: http.StatusOK}
81 | handler.ServeHTTP(w2, r)
82 | log.Printf("%s [%d]\n", r.RequestURI, w2.StatusCode)
83 | })
84 | }
85 |
86 | func AuthMiddleware(handler http.Handler) http.Handler {
87 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88 | cookie, _ := r.Cookie("session")
89 | if cookie != nil {
90 | sessionId := cookie.Value
91 | user, _ := GetUserBySessionId(sessionId)
92 |
93 | ctx := r.Context()
94 | ctx = context.WithValue(ctx, "user", user)
95 | r = r.WithContext(ctx)
96 | }
97 |
98 | handler.ServeHTTP(w, r)
99 | })
100 | }
101 |
102 | func GetUserBySessionId(sessionId string) (User, error) {
103 | if sessionId == "123" {
104 | return User{Id: 1, Name: "admin"}, nil
105 | }
106 | return User{}, errors.New("session not found")
107 | }
108 |
--------------------------------------------------------------------------------
/08_templates/01_template/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | func main() {
9 | tmpl, err := template.ParseGlob("templates/*")
10 | if err != nil {
11 | panic(err)
12 | }
13 |
14 | type Data struct {
15 | SomeInt int
16 | SomeString string
17 | SomeSlice []string
18 | SomeMap map[string]string
19 | }
20 | data := Data{
21 | SomeInt: 42,
22 | SomeString: "вкусные яблоки",
23 | SomeSlice: []string{"яблоко", "груша", "виноград"},
24 | SomeMap: map[string]string{"aa": "11", "bb": "22", "cc": "33"},
25 | }
26 |
27 | tmpl.ExecuteTemplate(os.Stdout, "1.txt", data)
28 | }
29 |
--------------------------------------------------------------------------------
/08_templates/01_template/templates/1.txt:
--------------------------------------------------------------------------------
1 | some text
2 |
3 | {{ . }}
4 |
5 | more text
6 |
--------------------------------------------------------------------------------
/08_templates/01_template/templates/2.txt:
--------------------------------------------------------------------------------
1 | {{range .SomeSlice}}
2 | {{.}}
3 | {{end}}
4 |
5 | ---------------
6 |
7 | {{range $x := .SomeSlice}}
8 | {{$x}}
9 | {{end}}
10 |
11 | -----------------
12 |
13 | {{range $k, $v := .SomeMap}}
14 | {{$k}}: {{$v}}
15 | {{end}}
--------------------------------------------------------------------------------
/08_templates/01_template/templates/3.txt:
--------------------------------------------------------------------------------
1 | {{range .SomeSlice}}
2 | {{if eq . "груша" }}
3 | !!!{{.}}!!!
4 | {{else}}
5 | {{.}}
6 | {{end}}
7 | {{end}}
8 |
--------------------------------------------------------------------------------
/08_templates/02_template_layout/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | func main() {
9 | tmpl, err := template.ParseFiles(
10 | "templates/layout1.txt",
11 | "templates/page1.txt",
12 | )
13 | if err != nil {
14 | panic(err)
15 | }
16 |
17 | data := map[string]any{
18 | "text": "some text",
19 | "number": 42,
20 | }
21 |
22 | tmpl.ExecuteTemplate(os.Stdout, "layout1.txt", data)
23 | }
24 |
--------------------------------------------------------------------------------
/08_templates/02_template_layout/templates/layout1.txt:
--------------------------------------------------------------------------------
1 | header
2 |
3 | {{template "main"}}
4 |
5 | footer
6 |
7 |
--------------------------------------------------------------------------------
/08_templates/02_template_layout/templates/layout2.txt:
--------------------------------------------------------------------------------
1 | {{block "header" .}}
2 | Header default.
3 | {{end}}
4 |
5 | {{block "main" .}}
6 | Main default.
7 | {{end}}
8 |
9 | Footer
10 |
11 |
--------------------------------------------------------------------------------
/08_templates/02_template_layout/templates/page1.txt:
--------------------------------------------------------------------------------
1 | {{define "main"}}
2 | page 1
3 | {{end}}
4 |
--------------------------------------------------------------------------------
/08_templates/02_template_layout/templates/page2.txt:
--------------------------------------------------------------------------------
1 | {{define "header"}}
2 | header 2
3 | {{end}}
4 |
5 | {{define "main"}}
6 | page 2
7 | {{end}}
8 |
--------------------------------------------------------------------------------
/08_templates/03_template_funcmap/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | func main() {
9 | funcMap := map[string]any{
10 | "add": func(a int, b int) int {
11 | return a + b
12 | },
13 | }
14 |
15 | tmpl, err := template.New("").Funcs(funcMap).ParseFiles("some.txt")
16 | if err != nil {
17 | panic(err)
18 | }
19 |
20 | data := map[string]any{
21 | "text": "some text",
22 | "number": 42,
23 | }
24 |
25 | tmpl.ExecuteTemplate(os.Stdout, "some.txt", data)
26 | }
27 |
--------------------------------------------------------------------------------
/08_templates/03_template_funcmap/some.txt:
--------------------------------------------------------------------------------
1 | result is {{ add .number 22 }}
--------------------------------------------------------------------------------
/08_templates/04_template_html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{.Name}}
4 |
5 |
--------------------------------------------------------------------------------
/08_templates/04_template_html/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "text/template"
7 | )
8 |
9 | func main() {
10 | http.HandleFunc("/", HomeHandler)
11 |
12 | err := http.ListenAndServe(":3000", nil)
13 | if err != nil {
14 | panic(err)
15 | }
16 |
17 | }
18 |
19 | func HomeHandler(w http.ResponseWriter, r *http.Request) {
20 | tmpl, err := template.ParseFiles("index.html")
21 | if err != nil {
22 | w.WriteHeader(http.StatusInternalServerError)
23 | io.WriteString(w, err.Error())
24 | return
25 | }
26 |
27 | type User struct {
28 | Name string
29 | }
30 | user := User{Name: "Ivan"}
31 |
32 | tmpl.ExecuteTemplate(w, "index.html", user)
33 | }
34 |
--------------------------------------------------------------------------------
/08_templates/05_template_parse/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "text/template"
6 | )
7 |
8 | func main() {
9 |
10 | tmpl, err := template.New("some.txt").Parse(`
11 | header
12 | {{.text}}
13 | footer
14 | `)
15 | if err != nil {
16 | panic(err)
17 | }
18 |
19 | data := map[string]any{
20 | "text": "some text",
21 | "number": 42,
22 | }
23 |
24 | tmpl.ExecuteTemplate(os.Stdout, "some.txt", data)
25 | }
26 |
--------------------------------------------------------------------------------
/09_database/01_pgx/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
2 |
--------------------------------------------------------------------------------
/09_database/01_pgx/db.sql:
--------------------------------------------------------------------------------
1 | create table users(
2 | id serial primary key,
3 | name varchar(50) not null,
4 | email varchar(100) not null
5 | );
6 |
7 | insert into users(name, email) values
8 | ('ivan', 'ivan@mail.ru'),
9 | ('andrey', 'andrey@gmail.com'),
10 | ('john', 'andrey@gmail.com'),
11 | ('slava', 'slava@example.com'),
12 | ('alex', 'alex@testserver')
13 | ;
14 |
15 | create table photos(
16 | id serial primary key,
17 | user_id int not null references users(id) on delete cascade,
18 | filename varchar(1024),
19 | width int not null,
20 | height int not null,
21 | created_at timestamp with time zone default current_timestamp
22 | );
23 |
24 | insert into photos(user_id, filename, width, height) values
25 | (1, 'cat.jpg', 1920, 1080),
26 | (1, 'dog.jpg', 1920, 1080),
27 | (2, 'pine.jpg', 1280, 720),
28 | (2, 'banana.jpg', 1280, 720),
29 | (2, 'tomato.jpg', 1280, 720),
30 | (3, 'parrot.jpg', 800, 600),
31 | (3, 'fish.jpg', 800, 600)
32 | ;
--------------------------------------------------------------------------------
/09_database/01_pgx/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/jackc/pgx/v5 v5.1.0
7 | github.com/joho/godotenv v1.4.0
8 | )
9 |
10 | require (
11 | github.com/jackc/pgpassfile v1.0.0 // indirect
12 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
13 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
14 | golang.org/x/text v0.3.8 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/09_database/01_pgx/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
4 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
5 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
6 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
7 | github.com/jackc/pgx/v5 v5.1.0 h1:Z7pLKUb65HK6m18No8GGKT87K34NhIIEHa86rRdjxbU=
8 | github.com/jackc/pgx/v5 v5.1.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
9 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
10 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
14 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
15 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
16 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
17 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
18 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
19 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
20 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
22 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
23 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
24 |
--------------------------------------------------------------------------------
/09_database/01_pgx/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "time"
9 |
10 | "github.com/jackc/pgx/v5"
11 | "github.com/joho/godotenv"
12 | )
13 |
14 | func main() {
15 | err := godotenv.Load()
16 | if err != nil {
17 | log.Fatal("Error loading .env file")
18 | }
19 |
20 | databaseUrl := os.Getenv("DATABASE_URL")
21 | if databaseUrl == "" {
22 | panic("DATABASE_URL is empty")
23 | }
24 |
25 | conn, err := pgx.Connect(context.Background(), databaseUrl)
26 | if err != nil {
27 | fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
28 | os.Exit(1)
29 | }
30 | defer conn.Close(context.Background())
31 |
32 | user, err := GetUser(conn, 2)
33 | if err != nil {
34 | panic(err)
35 | }
36 | fmt.Printf("get user: %+v", user)
37 |
38 | // users, err := GetUsers(conn)
39 | // if err != nil {
40 | // panic(err)
41 | // }
42 | // fmt.Printf("get users: %+v", users)
43 |
44 | // userId, err := InsertUser(conn, User{Name: "Test", Email: "test@test"})
45 | // if err != nil {
46 | // panic(err)
47 | // }
48 | // fmt.Printf("new user id: %d", userId)
49 |
50 | // rowsAffected, err := DeleteUser(conn, 1)
51 | // if err != nil {
52 | // panic(err)
53 | // }
54 | // fmt.Printf("delete users: %d", rowsAffected)
55 |
56 | }
57 |
58 | type User struct {
59 | Id int
60 | Name string
61 | Email string
62 | Photos []Photo
63 | }
64 |
65 | type Photo struct {
66 | UserId int
67 | Filename string
68 | Width int
69 | Height int
70 | CreatedAt time.Time
71 | }
72 |
73 | func GetUser(conn *pgx.Conn, userId int) (User, error) {
74 | var user User
75 | row := conn.QueryRow(
76 | context.Background(),
77 | "select id, name, email from users where id=$1",
78 | userId,
79 | )
80 | err := row.Scan(
81 | &user.Id,
82 | &user.Name,
83 | &user.Email,
84 | )
85 | if err != nil {
86 | return user, err
87 | }
88 | return user, nil
89 | }
90 |
91 | func GetUsers(conn *pgx.Conn) ([]User, error) {
92 | users := make([]User, 0)
93 |
94 | rows, err := conn.Query(
95 | context.Background(),
96 | "select id, name, email from users",
97 | )
98 | if err != nil {
99 | return users, err
100 | }
101 | defer rows.Close()
102 |
103 | for rows.Next() {
104 | var user User
105 | err := rows.Scan(
106 | &user.Id,
107 | &user.Name,
108 | &user.Email,
109 | )
110 | if err != nil {
111 | return []User{}, err
112 | }
113 | users = append(users, user)
114 | }
115 |
116 | return users, nil
117 | }
118 |
119 | func InsertUser(conn *pgx.Conn, user User) (int, error) {
120 | var id int
121 | err := conn.QueryRow(
122 | context.Background(),
123 | "insert into users(name, email) values($1, $2) returning id",
124 | user.Name, user.Email,
125 | ).Scan(&id)
126 | if err != nil {
127 | return 0, err
128 | }
129 | return id, nil
130 | }
131 |
132 | func DeleteUser(conn *pgx.Conn, userId int) (int, error) {
133 | tag, err := conn.Exec(
134 | context.Background(),
135 | "delete from users where id=$1",
136 | userId,
137 | )
138 | if err != nil {
139 | return 0, err
140 | }
141 | return int(tag.RowsAffected()), nil
142 | }
143 |
--------------------------------------------------------------------------------
/09_database/02_gorm/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
2 |
--------------------------------------------------------------------------------
/09_database/02_gorm/db.sql:
--------------------------------------------------------------------------------
1 | create table users(
2 | id serial primary key,
3 | name varchar(50) not null,
4 | email varchar(100) not null
5 | );
6 |
7 | insert into users(name, email) values
8 | ('ivan', 'ivan@mail.ru'),
9 | ('andrey', 'andrey@gmail.com'),
10 | ('john', 'andrey@gmail.com'),
11 | ('slava', 'slava@example.com'),
12 | ('alex', 'alex@testserver')
13 | ;
14 |
15 | create table photos(
16 | id serial primary key,
17 | user_id int not null references users(id) on delete cascade,
18 | filename varchar(1024),
19 | width int not null,
20 | height int not null,
21 | created_at timestamp with time zone default current_timestamp
22 | );
23 |
24 | insert into photos(user_id, filename, width, height) values
25 | (1, 'cat.jpg', 1920, 1080),
26 | (1, 'dog.jpg', 1920, 1080),
27 | (2, 'pine.jpg', 1280, 720),
28 | (2, 'banana.jpg', 1280, 720),
29 | (2, 'tomato.jpg', 1280, 720),
30 | (3, 'parrot.jpg', 800, 600),
31 | (3, 'fish.jpg', 800, 600)
32 | ;
--------------------------------------------------------------------------------
/09_database/02_gorm/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/joho/godotenv v1.4.0
7 | gorm.io/driver/postgres v1.4.5
8 | gorm.io/gorm v1.24.1
9 | )
10 |
11 | require (
12 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
13 | github.com/jackc/pgconn v1.13.0 // indirect
14 | github.com/jackc/pgio v1.0.0 // indirect
15 | github.com/jackc/pgpassfile v1.0.0 // indirect
16 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
17 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
18 | github.com/jackc/pgtype v1.12.0 // indirect
19 | github.com/jackc/pgx/v4 v4.17.2 // indirect
20 | github.com/jinzhu/inflection v1.0.0 // indirect
21 | github.com/jinzhu/now v1.1.4 // indirect
22 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
23 | golang.org/x/text v0.3.7 // indirect
24 | )
25 |
--------------------------------------------------------------------------------
/09_database/02_gorm/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
5 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
6 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
13 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
14 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
15 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
16 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
17 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
18 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
19 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
20 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
21 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
22 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
23 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
24 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
25 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
26 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
27 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
28 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
29 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
30 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
31 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
32 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
33 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
34 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
35 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
36 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
37 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
38 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
39 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
40 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
41 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
42 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
43 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
44 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
45 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
46 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
47 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
48 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
49 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
50 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
51 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
52 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
53 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
54 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
55 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
56 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
57 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
58 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
59 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
60 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
61 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
62 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
63 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
64 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
65 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
66 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
67 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
68 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
69 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
70 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
71 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
72 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
73 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
74 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
75 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
76 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
77 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
78 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
79 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
80 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
81 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
82 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
83 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
84 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
85 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
86 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
87 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
88 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
89 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
90 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
91 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
92 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
93 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
94 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
95 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
96 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
97 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
98 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
99 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
100 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
101 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
102 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
103 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
104 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
105 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
106 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
107 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
108 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
109 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
110 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
111 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
112 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
113 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
114 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
115 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
116 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
117 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
118 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
119 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
120 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
121 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
122 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
123 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
124 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
125 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
126 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
127 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
128 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
129 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
130 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
131 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
132 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
133 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
134 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
135 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
136 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
137 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
138 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
139 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
140 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
141 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
142 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
143 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
144 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
145 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
146 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
147 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
148 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
149 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
150 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
151 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
152 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
153 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
154 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
155 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
156 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
157 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
158 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
159 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
160 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
161 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
162 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
163 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
164 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
165 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
166 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
167 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
168 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
169 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
170 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
171 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
172 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
173 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
174 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
175 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
176 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
177 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
178 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
179 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
180 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
181 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
182 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
183 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
184 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
185 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
186 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
187 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
188 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
189 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
190 | gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
191 | gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
192 | gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
193 | gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
194 | gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
195 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
196 |
--------------------------------------------------------------------------------
/09_database/02_gorm/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "os"
8 | "time"
9 |
10 | "github.com/joho/godotenv"
11 | "gorm.io/driver/postgres"
12 | "gorm.io/gorm"
13 | "gorm.io/gorm/logger"
14 | )
15 |
16 | type User struct {
17 | Id int
18 | Name string
19 | Email string
20 | Photos []Photo
21 | }
22 |
23 | func (User) TableName() string {
24 | return "users"
25 | }
26 |
27 | type Photo struct {
28 | UserId int
29 | Filename string
30 | Width int
31 | Height int
32 | CreatedAt time.Time
33 | }
34 |
35 | func (Photo) TableName() string {
36 | return "photos"
37 | }
38 |
39 | var MyLogger = logger.New(
40 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
41 | logger.Config{
42 | SlowThreshold: time.Second, // Slow SQL threshold
43 | LogLevel: logger.Info, // Log level
44 | IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
45 | Colorful: true, // Disable color
46 | },
47 | )
48 |
49 | func main() {
50 | err := godotenv.Load()
51 | if err != nil {
52 | panic("Error loading .env file")
53 | }
54 |
55 | databaseUrl := os.Getenv("DATABASE_URL")
56 | if databaseUrl == "" {
57 | panic("DATABASE_URL is empty")
58 | }
59 |
60 | db, err := gorm.Open(postgres.Open(databaseUrl), &gorm.Config{Logger: MyLogger})
61 | if err != nil {
62 | panic("failed to connect database")
63 | }
64 |
65 | // user, err := GetUser(db, 222)
66 | // if err != nil {
67 | // panic(err)
68 | // }
69 | // fmt.Printf("user: %+v\n", user)
70 |
71 | users, err := GetUsers(db)
72 | if err != nil {
73 | panic(err)
74 | }
75 | PrintJson(users)
76 |
77 | // userId, err := InsertUser(db, User{Name: "AAA", Email: "aaa@bbb.cc"})
78 | // if err != nil {
79 | // panic(err)Photo
80 | // }
81 | // fmt.Printf("new user id: %d\n", userId)
82 |
83 | // rowsAffected, err := DeleteUser(db, 2)
84 | // if err != nil {
85 | // panic(err)
86 | // }
87 | // fmt.Println("rows deleted: ", rowsAffected)
88 |
89 | }
90 |
91 | func PrintJson(v any) {
92 | bytes, _ := json.MarshalIndent(v, "", " ")
93 | fmt.Println(string(bytes))
94 | }
95 |
96 | func GetUser(db *gorm.DB, userId int) (User, error) {
97 | var user User
98 | err := db.Take(&user, userId).Error
99 | return user, err
100 | }
101 |
102 | func GetUserByName(db *gorm.DB, name string) (User, error) {
103 | var user User
104 | err := db.Where("name = ?", name).Take(&user).Error
105 | return user, err
106 | }
107 |
108 | func GetUsers(db *gorm.DB) ([]User, error) {
109 | users := make([]User, 0)
110 | err := db.Preload("Photos").Find(&users).Error
111 | return users, err
112 | }
113 |
114 | func InsertUser(db *gorm.DB, user User) (int, error) {
115 | err := db.Create(&user).Error
116 | return user.Id, err
117 | }
118 |
119 | func DeleteUser(db *gorm.DB, userId int) (int, error) {
120 | tx := db.Delete(&User{}, userId)
121 | return int(tx.RowsAffected), tx.Error
122 | }
123 |
--------------------------------------------------------------------------------
/09_database/03_goose/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
2 |
--------------------------------------------------------------------------------
/09_database/03_goose/cmd/migrate_gorm/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/joho/godotenv"
8 |
9 | "gorm.io/driver/postgres"
10 | "gorm.io/gorm"
11 |
12 | "github.com/pressly/goose/v3"
13 | )
14 |
15 | func main() {
16 | err := godotenv.Load()
17 | if err != nil {
18 | log.Fatal("Error loading .env file")
19 | }
20 |
21 | databaseUrl := os.Getenv("DATABASE_URL")
22 | if databaseUrl == "" {
23 | panic("DATABASE_URL is empty")
24 | }
25 |
26 | db, err := gorm.Open(postgres.Open(databaseUrl), &gorm.Config{})
27 | if err != nil {
28 | panic("failed to connect database")
29 | }
30 |
31 | sqlDB, err := db.DB()
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | if err := goose.SetDialect("postgres"); err != nil {
37 | panic(err)
38 | }
39 |
40 | if err := goose.Up(sqlDB, "migrations"); err != nil {
41 | panic(err)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/09_database/03_goose/cmd/migrate_pgx/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "log"
7 | "os"
8 |
9 | "github.com/joho/godotenv"
10 |
11 | _ "github.com/jackc/pgx/v5/stdlib"
12 |
13 | "github.com/pressly/goose/v3"
14 | )
15 |
16 | func main() {
17 | err := godotenv.Load()
18 | if err != nil {
19 | log.Fatal("Error loading .env file")
20 | }
21 |
22 | databaseUrl := os.Getenv("DATABASE_URL")
23 | if databaseUrl == "" {
24 | panic("DATABASE_URL is empty")
25 | }
26 |
27 | db, err := sql.Open("pgx", databaseUrl)
28 | if err != nil {
29 | fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
30 | os.Exit(1)
31 | }
32 | defer db.Close()
33 |
34 | if err := goose.SetDialect("postgres"); err != nil {
35 | panic(err)
36 | }
37 |
38 | if err := goose.Up(db, "migrations"); err != nil {
39 | panic(err)
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/09_database/03_goose/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/jackc/pgx/v5 v5.1.0
7 | github.com/joho/godotenv v1.4.0
8 | github.com/pressly/goose/v3 v3.7.0
9 | gorm.io/driver/postgres v1.4.5
10 | gorm.io/gorm v1.24.1
11 | )
12 |
13 | require (
14 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
15 | github.com/jackc/pgconn v1.13.0 // indirect
16 | github.com/jackc/pgio v1.0.0 // indirect
17 | github.com/jackc/pgpassfile v1.0.0 // indirect
18 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
19 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
20 | github.com/jackc/pgtype v1.12.0 // indirect
21 | github.com/jackc/pgx/v4 v4.17.2 // indirect
22 | github.com/jinzhu/inflection v1.0.0 // indirect
23 | github.com/jinzhu/now v1.1.4 // indirect
24 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
25 | golang.org/x/text v0.3.8 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/09_database/03_goose/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
5 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
6 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
13 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
14 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
15 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
16 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
17 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
18 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
19 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
20 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
21 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
22 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
23 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
24 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
25 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
26 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
27 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
28 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
29 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
30 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
31 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
32 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
33 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
34 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
35 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
36 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
37 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
38 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
39 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
40 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
41 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
42 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
43 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
44 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
45 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
46 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
47 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
48 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
49 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
50 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
51 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
52 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
53 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
54 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
55 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
56 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
57 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
58 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
59 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
60 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
61 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
62 | github.com/jackc/pgx/v5 v5.1.0 h1:Z7pLKUb65HK6m18No8GGKT87K34NhIIEHa86rRdjxbU=
63 | github.com/jackc/pgx/v5 v5.1.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
64 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
65 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
66 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
67 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
68 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
69 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
70 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
71 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
72 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
73 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
74 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
75 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
76 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
77 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
78 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
79 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
80 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
81 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
82 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
83 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
84 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
85 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
86 | github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
87 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
88 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
89 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
90 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
91 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
92 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
93 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
94 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
95 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
96 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
97 | github.com/pressly/goose/v3 v3.7.0 h1:jblaZul15uCIEKHRu5KUdA+5wDA7E60JC0TOthdrtf8=
98 | github.com/pressly/goose/v3 v3.7.0/go.mod h1:N5gqPdIzdxf3BiPWdmoPreIwHStkxsvKWE5xjUvfYNk=
99 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
100 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
101 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
102 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
103 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
104 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
105 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
106 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
107 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
108 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
109 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
110 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
111 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
112 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
113 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
114 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
115 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
116 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
117 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
118 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
119 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
120 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
121 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
122 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
123 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
124 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
125 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
126 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
127 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
128 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
129 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
130 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
131 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
132 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
133 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
134 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
135 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
136 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
137 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
138 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
139 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
140 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
141 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
142 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
143 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
144 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
145 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
146 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
147 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
148 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
149 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
150 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
151 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
152 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
153 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
154 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
155 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
156 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
157 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
158 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
159 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
160 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
161 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
162 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
163 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
164 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
165 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
166 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
167 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
168 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
169 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
170 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
171 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
172 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
173 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
174 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
175 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
176 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
177 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
178 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
179 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
180 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
181 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
182 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
183 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
184 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
185 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
186 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
187 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
188 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
189 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
190 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
191 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
192 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
193 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
194 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
195 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
196 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
197 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
198 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
199 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
200 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
201 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
202 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
203 | gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
204 | gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
205 | gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
206 | gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
207 | gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
208 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
209 | lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
210 | modernc.org/cc/v3 v3.36.1 h1:CICrjwr/1M4+6OQ4HJZ/AHxjcwe67r5vPUF518MkO8A=
211 | modernc.org/ccgo/v3 v3.16.8 h1:G0QNlTqI5uVgczBWfGKs7B++EPwCfXPWGD2MdeKloDs=
212 | modernc.org/libc v1.16.19 h1:S8flPn5ZeXx6iw/8yNa986hwTQDrY8RXU7tObZuAozo=
213 | modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
214 | modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
215 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
216 | modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
217 | modernc.org/strutil v1.1.2 h1:iFBDH6j1Z0bN/Q9udJnnFoFpENA4252qe/7/5woE5MI=
218 | modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
219 |
--------------------------------------------------------------------------------
/09_database/03_goose/migrations/20221115125338_init.sql:
--------------------------------------------------------------------------------
1 | -- +goose Up
2 | -- +goose StatementBegin
3 | create table users(
4 | id serial primary key,
5 | name varchar(50) not null,
6 | email varchar(100) not null
7 | );
8 |
9 | insert into users(name, email) values
10 | ('ivan', 'ivan@mail.ru'),
11 | ('andrey', 'andrey@gmail.com'),
12 | ('john', 'andrey@gmail.com'),
13 | ('slava', 'slava@example.com'),
14 | ('alex', 'alex@testserver')
15 | ;
16 |
17 | -- +goose StatementEnd
18 |
19 | -- +goose Down
20 | -- +goose StatementBegin
21 | drop table users;
22 | -- +goose StatementEnd
23 |
--------------------------------------------------------------------------------
/09_database/03_goose/migrations/20221115125345_photo.sql:
--------------------------------------------------------------------------------
1 | -- +goose Up
2 | -- +goose StatementBegin
3 | create table photos(
4 | id serial primary key,
5 | user_id int not null references users(id) on delete cascade,
6 | filename varchar(1024),
7 | width int not null,
8 | height int not null,
9 | created_at timestamp with time zone default current_timestamp
10 | );
11 |
12 | insert into photos(user_id, filename, width, height) values
13 | (1, 'cat.jpg', 1920, 1080),
14 | (1, 'dog.jpg', 1920, 1080),
15 | (2, 'pine.jpg', 1280, 720),
16 | (2, 'banana.jpg', 1280, 720),
17 | (2, 'tomato.jpg', 1280, 720),
18 | (3, 'parrot.jpg', 800, 600),
19 | (3, 'fish.jpg', 800, 600)
20 | ;
21 | -- +goose StatementEnd
22 |
23 | -- +goose Down
24 | -- +goose StatementBegin
25 | drop table photos;
26 | -- +goose StatementEnd
27 |
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
2 |
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/README:
--------------------------------------------------------------------------------
1 | migrate create -ext sql -dir ./migrations -seq init
2 | migrate create -ext sql -dir ./migrations -seq photos
3 | migrate -source file://./migrations -database postgres://postgres:123@localhost:5432/go_dev up
4 |
5 |
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/golang-migrate/migrate/v4 v4.15.2
7 | github.com/joho/godotenv v1.4.0
8 | github.com/lib/pq v1.10.0
9 | )
10 |
11 | require (
12 | github.com/hashicorp/errwrap v1.1.0 // indirect
13 | github.com/hashicorp/go-multierror v1.1.1 // indirect
14 | go.uber.org/atomic v1.7.0 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "log"
7 | "os"
8 |
9 | "github.com/joho/godotenv"
10 |
11 | migrate "github.com/golang-migrate/migrate/v4"
12 | "github.com/golang-migrate/migrate/v4/database/postgres"
13 | _ "github.com/golang-migrate/migrate/v4/source/file"
14 | _ "github.com/lib/pq"
15 | )
16 |
17 | func main() {
18 | err := godotenv.Load()
19 | if err != nil {
20 | log.Fatal("Error loading .env file")
21 | }
22 |
23 | databaseUrl := os.Getenv("DATABASE_URL")
24 | if databaseUrl == "" {
25 | panic("DATABASE_URL is empty")
26 | }
27 |
28 | db, err := sql.Open("postgres", databaseUrl)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | driver, err := postgres.WithInstance(db, &postgres.Config{})
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | m, err := migrate.NewWithDatabaseInstance(
39 | "file://./migrations",
40 | "postgres", driver)
41 | if err != nil {
42 | panic(err)
43 | }
44 |
45 | err = m.Up()
46 | if err != nil {
47 | if err != migrate.ErrNoChange {
48 | panic(err)
49 | }
50 | }
51 | fmt.Println("ok")
52 | }
53 |
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/migrations/000001_init.down.sql:
--------------------------------------------------------------------------------
1 | drop table users;
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/migrations/000001_init.up.sql:
--------------------------------------------------------------------------------
1 | create table users(
2 | id serial primary key,
3 | name varchar(50) not null,
4 | email varchar(100) not null
5 | );
6 |
7 | insert into users(name, email) values
8 | ('ivan', 'ivan@mail.ru'),
9 | ('andrey', 'andrey@gmail.com'),
10 | ('john', 'andrey@gmail.com'),
11 | ('slava', 'slava@example.com'),
12 | ('alex', 'alex@testserver')
13 | ;
14 |
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/migrations/000002_photos.down.sql:
--------------------------------------------------------------------------------
1 | drop table photos;
--------------------------------------------------------------------------------
/09_database/04_golang_migrate/migrations/000002_photos.up.sql:
--------------------------------------------------------------------------------
1 | create table photos(
2 | id serial primary key,
3 | user_id int not null references users(id) on delete cascade,
4 | filename varchar(1024),
5 | width int not null,
6 | height int not null,
7 | created_at timestamp with time zone default current_timestamp
8 | );
9 |
10 | insert into photos(user_id, filename, width, height) values
11 | (1, 'cat.jpg', 1920, 1080),
12 | (1, 'dog.jpg', 1920, 1080),
13 | (2, 'pine.jpg', 1280, 720),
14 | (2, 'banana.jpg', 1280, 720),
15 | (2, 'tomato.jpg', 1280, 720),
16 | (3, 'parrot.jpg', 800, 600),
17 | (3, 'fish.jpg', 800, 600)
18 | ;
19 |
--------------------------------------------------------------------------------
/09_database/README:
--------------------------------------------------------------------------------
1 | # database/sql
2 | https://pkg.go.dev/database/sql
3 | https://github.com/golang/go/wiki/SQLDrivers
4 | https://github.com/golang/go/wiki/SQLInterface
5 |
6 | # pgx
7 | https://github.com/jackc/pgx
8 | https://github.com/jackc/pgx/wiki/Getting-started-with-pgx
9 | https://github.com/jackc/pgx/wiki/Getting-started-with-pgx-through-database-sql
10 |
11 | # GORM
12 | https://gorm.io/docs/
13 | https://gorm.io/docs/connecting_to_the_database.html
14 | https://gorm.io/docs/sql_builder.html
15 | https://gorm.io/docs/generic_interface.html
16 |
17 | # goose
18 | https://github.com/pressly/goose
19 |
20 | # golang-migrate
21 | https://github.com/golang-migrate
22 | https://github.com/golang-migrate/migrate/blob/master/GETTING_STARTED.md
--------------------------------------------------------------------------------
/10_testing/01_basic/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
--------------------------------------------------------------------------------
/10_testing/01_basic/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/10_testing/01_basic/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func Foo() int {
4 | return 0
5 | }
6 |
--------------------------------------------------------------------------------
/10_testing/01_basic/utils/utils_test.go:
--------------------------------------------------------------------------------
1 | package utils_test
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestFoo(t *testing.T) {
8 | defer func() {
9 | t.Log("defer")
10 | }()
11 |
12 | // t.Log("2")
13 | // t.FailNow()
14 |
15 | t.Log("1")
16 | t.Fatal("something happen")
17 | t.Log("2")
18 | }
19 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
--------------------------------------------------------------------------------
/10_testing/02_stdlib/.env.test:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev_test
--------------------------------------------------------------------------------
/10_testing/02_stdlib/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "example/internal/app"
5 | "fmt"
6 | )
7 |
8 | func main() {
9 | app := app.NewApp()
10 | err := app.Config.Load(".env")
11 | if err != nil {
12 | panic(err)
13 | }
14 |
15 | err = app.Setup()
16 | if err != nil {
17 | panic(err)
18 | }
19 |
20 | fmt.Println("Starting is running.")
21 |
22 | err = app.Run()
23 | if err != nil {
24 | panic(err)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/gorilla/mux v1.8.0
7 | github.com/joho/godotenv v1.4.0
8 | gorm.io/driver/postgres v1.4.5
9 | gorm.io/gorm v1.24.1
10 | )
11 |
12 | require (
13 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
14 | github.com/jackc/pgconn v1.13.0 // indirect
15 | github.com/jackc/pgio v1.0.0 // indirect
16 | github.com/jackc/pgpassfile v1.0.0 // indirect
17 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
18 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
19 | github.com/jackc/pgtype v1.12.0 // indirect
20 | github.com/jackc/pgx/v4 v4.17.2 // indirect
21 | github.com/jinzhu/inflection v1.0.0 // indirect
22 | github.com/jinzhu/now v1.1.5 // indirect
23 | golang.org/x/crypto v0.3.0 // indirect
24 | golang.org/x/text v0.4.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
5 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
6 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
13 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
14 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
15 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
16 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
17 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
18 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
19 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
20 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
21 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
22 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
23 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
24 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
25 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
26 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
27 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
28 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
29 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
30 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
31 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
32 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
33 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
34 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
35 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
36 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
37 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
38 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
39 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
40 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
41 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
42 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
43 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
44 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
45 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
46 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
47 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
48 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
49 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
50 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
51 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
52 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
53 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
54 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
55 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
56 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
57 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
58 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
59 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
60 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
61 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
62 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
63 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
64 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
65 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
66 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
67 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
68 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
69 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
70 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
71 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
72 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
73 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
74 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
75 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
76 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
77 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
78 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
79 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
80 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
81 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
82 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
83 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
84 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
85 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
86 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
87 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
88 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
89 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
90 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
91 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
92 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
95 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
96 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
97 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
98 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
99 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
100 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
101 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
102 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
103 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
104 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
105 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
106 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
107 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
108 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
109 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
110 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
111 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
112 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
113 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
114 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
115 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
116 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
117 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
118 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
119 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
120 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
121 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
122 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
123 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
124 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
125 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
126 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
127 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
128 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
129 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
130 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
131 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
132 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
133 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
134 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
135 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
136 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
137 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
138 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
139 | golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
140 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
141 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
142 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
143 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
144 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
145 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
146 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
147 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
148 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
149 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
150 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
151 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
152 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
153 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
154 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
155 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
156 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
157 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
158 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
159 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
160 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
161 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
162 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
163 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
164 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
165 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
166 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
167 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
168 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
169 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
170 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
171 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
172 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
173 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
174 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
175 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
176 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
177 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
178 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
179 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
180 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
181 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
182 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
183 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
184 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
185 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
186 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
187 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
188 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
189 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
190 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
191 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
192 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
193 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
194 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
195 | gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
196 | gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
197 | gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
198 | gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
199 | gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
200 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
201 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/app/app.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "errors"
5 | "example/internal/handlers"
6 | "net/http"
7 | "os"
8 |
9 | "github.com/gorilla/mux"
10 | "github.com/joho/godotenv"
11 |
12 | "gorm.io/driver/postgres"
13 | "gorm.io/gorm"
14 | )
15 |
16 | type App struct {
17 | Config Config
18 | Router *mux.Router
19 | DB *gorm.DB
20 | }
21 |
22 | type Config struct {
23 | DatabaseUrl string
24 | }
25 |
26 | func (config *Config) Load(filename string) error {
27 | err := godotenv.Load(filename)
28 | if err != nil {
29 | return errors.New("Error loading " + filename)
30 | }
31 |
32 | config.DatabaseUrl = os.Getenv("DATABASE_URL")
33 | if config.DatabaseUrl == "" {
34 | return errors.New("missing DATABASE_URL")
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func NewApp() App {
41 | return App{}
42 | }
43 |
44 | func (app *App) Setup() error {
45 | db, err := gorm.Open(postgres.Open(app.Config.DatabaseUrl), &gorm.Config{})
46 | if err != nil {
47 | return err
48 | }
49 |
50 | r := mux.NewRouter()
51 | r.HandleFunc("/post", handlers.GetPosts(db)).Methods("GET")
52 | r.HandleFunc("/post", handlers.CreatePost(db)).Methods("POST")
53 | r.HandleFunc(`/post/{id:\d+}`, handlers.GetPost(db)).Methods("GET")
54 | r.HandleFunc(`/post/{id:\d+}`, handlers.UpdatePost(db)).Methods("PUT")
55 | r.HandleFunc("/post", handlers.DeletePost(db)).Methods("DELETE")
56 | r.HandleFunc("/ping", handlers.Ping)
57 |
58 | app.Router = r
59 | app.DB = db
60 |
61 | return nil
62 | }
63 |
64 | func (app *App) Teardown() error {
65 | sqlDB, err := app.DB.DB()
66 | if err != nil {
67 | return err
68 | }
69 | return sqlDB.Close()
70 | }
71 |
72 | func (app *App) Run() error {
73 | return http.ListenAndServe(":3000", app.Router)
74 | }
75 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/handlers/ping.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | )
7 |
8 | func Ping(w http.ResponseWriter, r *http.Request) {
9 | io.WriteString(w, "OK")
10 | }
11 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/handlers/ping_test.go:
--------------------------------------------------------------------------------
1 | package handlers_test
2 |
3 | import (
4 | "example/internal/handlers"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 | )
9 |
10 | func TestPing(t *testing.T) {
11 | req, err := http.NewRequest("GET", "/ping", nil)
12 | if err != nil {
13 | t.Fatal(err)
14 | }
15 |
16 | w := httptest.NewRecorder()
17 | handlers.Ping(w, req)
18 |
19 | if w.Code != http.StatusOK {
20 | t.Fatalf("expected status %d, got %d", http.StatusOK, w.Code)
21 | }
22 |
23 | content := w.Body.String()
24 | expected := "OK"
25 |
26 | if content != expected {
27 | t.Fatalf(`expected content "%s", got %s`, expected, content)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/handlers/post.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "example/internal/models"
7 | "example/internal/rest"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/gorilla/mux"
12 | "gorm.io/gorm"
13 | )
14 |
15 | func GetPosts(db *gorm.DB) http.HandlerFunc {
16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 | var posts []models.Post
18 | err := db.Order("id desc").Find(&posts).Error
19 | if err != nil {
20 | rest.WriteError(w, http.StatusInternalServerError, err)
21 | return
22 | }
23 |
24 | rest.WriteJSON(w, http.StatusOK, rest.Response{
25 | Ok: true,
26 | Result: posts,
27 | })
28 | })
29 | }
30 |
31 | func GetPost(db *gorm.DB) http.HandlerFunc {
32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33 | vars := mux.Vars(r)
34 | idStr := vars["id"]
35 | id, err := strconv.Atoi(idStr)
36 | if err != nil {
37 | rest.WriteError(w, http.StatusBadRequest, err)
38 | return
39 | }
40 |
41 | var post models.Post
42 | err = db.Take(&post, id).Error
43 | if err != nil {
44 | if err == gorm.ErrRecordNotFound {
45 | rest.WriteError(w, http.StatusNotFound, errors.New("not found"))
46 | return
47 | }
48 | rest.WriteError(w, http.StatusInternalServerError, err)
49 | return
50 | }
51 |
52 | rest.WriteJSON(w, http.StatusOK, rest.Response{
53 | Ok: true,
54 | Result: post,
55 | })
56 | })
57 | }
58 |
59 | func CreatePost(db *gorm.DB) http.HandlerFunc {
60 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61 | var post models.Post
62 | err := json.NewDecoder(r.Body).Decode(&post)
63 | if err != nil {
64 | rest.WriteError(w, http.StatusBadRequest, err)
65 | return
66 | }
67 |
68 | // TODO: validation
69 |
70 | err = db.Create(&post).Error
71 | if err != nil {
72 | rest.WriteError(w, http.StatusInternalServerError, err)
73 | return
74 | }
75 |
76 | rest.WriteJSON(w, http.StatusCreated, rest.Response{
77 | Ok: true,
78 | Result: post.Id,
79 | })
80 | })
81 | }
82 |
83 | func UpdatePost(db *gorm.DB) http.HandlerFunc {
84 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85 | vars := mux.Vars(r)
86 | idStr := vars["id"]
87 | id, err := strconv.Atoi(idStr)
88 | if err != nil {
89 | rest.WriteError(w, http.StatusBadRequest, err)
90 | return
91 | }
92 |
93 | var post models.Post
94 | err = json.NewDecoder(r.Body).Decode(&post)
95 | if err != nil {
96 | rest.WriteError(w, http.StatusBadRequest, err)
97 | return
98 | }
99 | post.Id = id
100 |
101 | // TODO: validation
102 |
103 | err = db.Select("*").Updates(post).Error
104 | if err != nil {
105 | rest.WriteError(w, http.StatusInternalServerError, err)
106 | return
107 | }
108 |
109 | rest.WriteJSON(w, http.StatusOK, rest.Response{
110 | Ok: true,
111 | Result: post.Id,
112 | })
113 | })
114 | }
115 |
116 | func DeletePost(db *gorm.DB) http.HandlerFunc {
117 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 | vars := mux.Vars(r)
119 | idStr := vars["id"]
120 | id, err := strconv.Atoi(idStr)
121 | if err != nil {
122 | rest.WriteError(w, http.StatusBadRequest, err)
123 | return
124 | }
125 |
126 | err = db.Delete(&models.Post{}, id).Error
127 | if err != nil {
128 | if err == gorm.ErrRecordNotFound {
129 | rest.WriteError(w, http.StatusNotFound, errors.New("not found"))
130 | return
131 | }
132 | rest.WriteError(w, http.StatusInternalServerError, err)
133 | return
134 | }
135 |
136 | rest.WriteJSON(w, http.StatusOK, rest.Response{
137 | Ok: true,
138 | })
139 | })
140 | }
141 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/handlers/post_test.go:
--------------------------------------------------------------------------------
1 | package handlers_test
2 |
3 | import (
4 | "encoding/json"
5 | "example/internal/app"
6 | "example/internal/models"
7 | "example/internal/rest"
8 | "example/internal/tests"
9 | "fmt"
10 | "net/http"
11 | "net/http/httptest"
12 | "path/filepath"
13 | "testing"
14 | )
15 |
16 | func TestGetPost(t *testing.T) {
17 | var err error
18 |
19 | app := app.NewApp()
20 |
21 | configPath := filepath.Join(tests.GetProjectRoot(), ".env.test")
22 | err = app.Config.Load(configPath)
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 |
27 | err = app.Setup()
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 | defer app.Teardown()
32 |
33 | db := app.DB
34 |
35 | tests.SetupDB(db)
36 | defer tests.TeardownDB(db)
37 |
38 | post := models.Post{
39 | Title: "some title",
40 | Text: "some content",
41 | }
42 | err = db.Create(&post).Error
43 | if err != nil {
44 | t.Fatal(err)
45 | }
46 |
47 | url := fmt.Sprintf("/post/%d", post.Id)
48 |
49 | req, err := http.NewRequest("GET", url, nil)
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 |
54 | w := httptest.NewRecorder()
55 |
56 | // handler := handlers.GetPost(db)
57 | handler := app.Router
58 | handler.ServeHTTP(w, req)
59 |
60 | if w.Code != http.StatusOK {
61 | t.Fatalf("status code: expected %d, got %d", http.StatusOK, w.Code)
62 | }
63 |
64 | type PostReponse struct {
65 | rest.Response
66 | Result models.Post `json:"result"`
67 | }
68 | var response PostReponse
69 |
70 | json.Unmarshal(w.Body.Bytes(), &response)
71 |
72 | if response.Result.Title != "some title" {
73 | t.Fatalf(`title: expected "%s", got "%s"`,
74 | "some content", response.Result.Title)
75 | }
76 |
77 | if response.Result.Text != "some content" {
78 | t.Fatalf(`text: expected "%s", got "%s"`,
79 | "some content", response.Result.Text)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/models/post.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Post struct {
6 | Id int `json:"id"`
7 | Title string `json:"title"`
8 | Text string `json:"text"`
9 |
10 | CreatedAt time.Time `json:"createdAt"`
11 | UpdatedAt time.Time `json:"updatedAt"`
12 | }
13 |
14 | func (Post) TableName() string {
15 | return "posts"
16 | }
17 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/rest/rest.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | type Response struct {
9 | Ok bool `json:"ok"`
10 | Result any `json:"result,omitempty"`
11 | Error string `json:"error,omitempty"`
12 | }
13 |
14 | func WriteJSON(w http.ResponseWriter, status int, v any) {
15 | bytes, _ := json.Marshal(v)
16 | w.WriteHeader(status)
17 | w.Write(bytes)
18 | }
19 |
20 | func WriteError(w http.ResponseWriter, status int, err error) {
21 | WriteJSON(w, status, Response{
22 | Ok: false,
23 | Error: err.Error(),
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/tests/db.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "io/ioutil"
5 | "path/filepath"
6 | "sort"
7 | "strings"
8 |
9 | "gorm.io/gorm"
10 | )
11 |
12 | func SetupDB(db *gorm.DB) error {
13 | pattern := filepath.Join(GetProjectRoot(), "migrations", "*.up.sql")
14 | sql, err := ConcatMigrations(pattern)
15 | if err != nil {
16 | return err
17 | }
18 |
19 | err = db.Exec(sql).Error
20 | if err != nil {
21 | TeardownDB(db)
22 | err = db.Exec(sql).Error
23 | if err != nil {
24 | return err
25 | }
26 | }
27 |
28 | return nil
29 | }
30 |
31 | func TeardownDB(db *gorm.DB) error {
32 | pattern := filepath.Join(GetProjectRoot(), "migrations", "*.down.sql")
33 | sql, err := ConcatMigrations(pattern)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | err = db.Exec(sql).Error
39 | if err != nil {
40 | return err
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func ConcatMigrations(pattern string) (string, error) {
47 | filenames, err := filepath.Glob(pattern)
48 | if err != nil {
49 | return "", err
50 | }
51 |
52 | sort.Strings(filenames)
53 |
54 | var contents []string
55 | for _, filename := range filenames {
56 | bytes, err := ioutil.ReadFile(filename)
57 | if err != nil {
58 | return "", err
59 | }
60 | contents = append(contents, string(bytes))
61 | }
62 | return strings.Join(contents, "\n\n"), nil
63 | }
64 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/internal/tests/fs.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | func GetProjectRoot() string {
10 | path, err := os.Getwd()
11 | if err != nil {
12 | panic(err)
13 | }
14 |
15 | for {
16 | _, err = os.Stat(filepath.Join(path, ".env"))
17 | if errors.Is(err, os.ErrNotExist) {
18 | path = filepath.Dir(path)
19 | continue
20 | }
21 | if err != nil {
22 | panic(err)
23 | }
24 |
25 | break
26 | }
27 |
28 | return path
29 | }
30 |
--------------------------------------------------------------------------------
/10_testing/02_stdlib/migrations/01_init.down.sql:
--------------------------------------------------------------------------------
1 | drop table posts;
--------------------------------------------------------------------------------
/10_testing/02_stdlib/migrations/01_init.up.sql:
--------------------------------------------------------------------------------
1 | create table posts(
2 | id serial primary key,
3 | title varchar(256) not null,
4 | text text not null,
5 | created_at timestamp with time zone default current_timestamp,
6 | updated_at timestamp with time zone default current_timestamp
7 | );
8 |
--------------------------------------------------------------------------------
/10_testing/04_testify/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
--------------------------------------------------------------------------------
/10_testing/04_testify/.env.test:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev_test
--------------------------------------------------------------------------------
/10_testing/04_testify/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "example/internal/app"
5 | "fmt"
6 | )
7 |
8 | func main() {
9 | app := app.NewApp()
10 | err := app.Config.Load(".env")
11 | if err != nil {
12 | panic(err)
13 | }
14 |
15 | err = app.Setup()
16 | if err != nil {
17 | panic(err)
18 | }
19 |
20 | fmt.Println("Starting is running.")
21 |
22 | err = app.Run()
23 | if err != nil {
24 | panic(err)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/10_testing/04_testify/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/gorilla/mux v1.8.0
7 | github.com/joho/godotenv v1.4.0
8 | gorm.io/driver/postgres v1.4.5
9 | gorm.io/gorm v1.24.1
10 | )
11 |
12 | require (
13 | github.com/davecgh/go-spew v1.1.1 // indirect
14 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
15 | github.com/jackc/pgconn v1.13.0 // indirect
16 | github.com/jackc/pgio v1.0.0 // indirect
17 | github.com/jackc/pgpassfile v1.0.0 // indirect
18 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
19 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
20 | github.com/jackc/pgtype v1.12.0 // indirect
21 | github.com/jackc/pgx/v4 v4.17.2 // indirect
22 | github.com/jinzhu/inflection v1.0.0 // indirect
23 | github.com/jinzhu/now v1.1.5 // indirect
24 | github.com/pmezard/go-difflib v1.0.0 // indirect
25 | github.com/stretchr/testify v1.8.1 // indirect
26 | golang.org/x/crypto v0.3.0 // indirect
27 | golang.org/x/text v0.4.0 // indirect
28 | gopkg.in/yaml.v3 v3.0.1 // indirect
29 | )
30 |
--------------------------------------------------------------------------------
/10_testing/04_testify/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
5 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
6 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
13 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
14 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
15 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
16 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
17 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
18 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
19 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
20 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
21 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
22 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
23 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
24 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
25 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
26 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
27 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
28 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
29 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
30 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
31 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
32 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
33 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
34 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
35 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
36 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
37 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
38 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
39 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
40 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
41 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
42 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
43 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
44 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
45 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
46 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
47 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
48 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
49 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
50 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
51 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
52 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
53 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
54 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
55 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
56 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
57 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
58 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
59 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
60 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
61 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
62 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
63 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
64 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
65 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
66 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
67 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
68 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
69 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
70 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
71 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
72 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
73 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
74 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
75 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
76 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
77 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
78 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
79 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
80 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
81 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
82 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
83 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
84 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
85 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
86 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
87 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
88 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
89 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
90 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
91 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
92 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
95 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
96 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
97 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
98 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
99 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
100 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
101 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
102 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
103 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
104 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
105 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
106 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
107 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
108 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
109 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
110 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
111 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
112 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
113 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
114 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
115 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
116 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
117 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
118 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
119 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
120 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
121 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
122 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
123 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
124 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
125 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
126 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
127 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
128 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
129 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
130 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
131 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
132 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
133 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
134 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
135 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
136 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
137 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
138 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
139 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
140 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
141 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
142 | golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
143 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
144 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
145 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
146 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
147 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
148 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
149 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
150 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
151 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
152 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
153 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
154 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
155 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
156 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
157 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
158 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
159 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
160 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
161 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
162 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
163 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
164 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
165 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
166 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
167 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
168 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
169 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
170 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
171 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
172 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
173 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
174 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
175 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
176 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
177 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
178 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
179 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
180 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
181 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
182 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
183 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
184 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
185 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
186 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
187 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
188 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
189 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
190 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
191 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
192 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
193 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
194 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
195 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
196 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
197 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
198 | gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
199 | gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
200 | gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
201 | gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
202 | gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
203 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
204 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/app/app.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "errors"
5 | "example/internal/handlers"
6 | "net/http"
7 | "os"
8 |
9 | "github.com/gorilla/mux"
10 | "github.com/joho/godotenv"
11 |
12 | "gorm.io/driver/postgres"
13 | "gorm.io/gorm"
14 | )
15 |
16 | type App struct {
17 | Config Config
18 | Router *mux.Router
19 | DB *gorm.DB
20 | }
21 |
22 | type Config struct {
23 | DatabaseUrl string
24 | }
25 |
26 | func (config *Config) Load(filename string) error {
27 | err := godotenv.Load(filename)
28 | if err != nil {
29 | return errors.New("Error loading " + filename)
30 | }
31 |
32 | config.DatabaseUrl = os.Getenv("DATABASE_URL")
33 | if config.DatabaseUrl == "" {
34 | return errors.New("missing DATABASE_URL")
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func NewApp() App {
41 | return App{}
42 | }
43 |
44 | func (app *App) Setup() error {
45 | db, err := gorm.Open(postgres.Open(app.Config.DatabaseUrl), &gorm.Config{})
46 | if err != nil {
47 | return err
48 | }
49 |
50 | r := mux.NewRouter()
51 | r.HandleFunc("/post", handlers.GetPosts(db)).Methods("GET")
52 | r.HandleFunc("/post", handlers.CreatePost(db)).Methods("POST")
53 | r.HandleFunc(`/post/{id:\d+}`, handlers.GetPost(db)).Methods("GET")
54 | r.HandleFunc(`/post/{id:\d+}`, handlers.UpdatePost(db)).Methods("PUT")
55 | r.HandleFunc("/post", handlers.DeletePost(db)).Methods("DELETE")
56 | r.HandleFunc("/ping", handlers.Ping)
57 |
58 | app.Router = r
59 | app.DB = db
60 |
61 | return nil
62 | }
63 |
64 | func (app *App) Teardown() error {
65 | sqlDB, err := app.DB.DB()
66 | if err != nil {
67 | return err
68 | }
69 | return sqlDB.Close()
70 | }
71 |
72 | func (app *App) Run() error {
73 | return http.ListenAndServe(":3000", app.Router)
74 | }
75 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/handlers/ping.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | )
7 |
8 | func Ping(w http.ResponseWriter, r *http.Request) {
9 | io.WriteString(w, "OK")
10 | }
11 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/handlers/ping_test.go:
--------------------------------------------------------------------------------
1 | package handlers_test
2 |
3 | import (
4 | "example/internal/handlers"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestPing(t *testing.T) {
14 | req, err := http.NewRequest("GET", "/ping", nil)
15 | assert.Nil(t, err)
16 |
17 | w := httptest.NewRecorder()
18 | handlers.Ping(w, req)
19 |
20 | require.Equal(t, http.StatusOK, w.Code)
21 | require.Equal(t, "OK", w.Body.String())
22 | }
23 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/handlers/post.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "example/internal/models"
7 | "example/internal/rest"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/gorilla/mux"
12 | "gorm.io/gorm"
13 | )
14 |
15 | func GetPosts(db *gorm.DB) http.HandlerFunc {
16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 | var posts []models.Post
18 | err := db.Order("id desc").Find(&posts).Error
19 | if err != nil {
20 | rest.WriteError(w, http.StatusInternalServerError, err)
21 | return
22 | }
23 |
24 | rest.WriteJSON(w, http.StatusOK, rest.Response{
25 | Ok: true,
26 | Result: posts,
27 | })
28 | })
29 | }
30 |
31 | func GetPost(db *gorm.DB) http.HandlerFunc {
32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33 | vars := mux.Vars(r)
34 | idStr := vars["id"]
35 | id, err := strconv.Atoi(idStr)
36 | if err != nil {
37 | rest.WriteError(w, http.StatusBadRequest, err)
38 | return
39 | }
40 |
41 | var post models.Post
42 | err = db.Take(&post, id).Error
43 | if err != nil {
44 | if err == gorm.ErrRecordNotFound {
45 | rest.WriteError(w, http.StatusNotFound, errors.New("not found"))
46 | return
47 | }
48 | rest.WriteError(w, http.StatusInternalServerError, err)
49 | return
50 | }
51 |
52 | rest.WriteJSON(w, http.StatusOK, rest.Response{
53 | Ok: true,
54 | Result: post,
55 | })
56 | })
57 | }
58 |
59 | func CreatePost(db *gorm.DB) http.HandlerFunc {
60 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61 | var post models.Post
62 | err := json.NewDecoder(r.Body).Decode(&post)
63 | if err != nil {
64 | rest.WriteError(w, http.StatusBadRequest, err)
65 | return
66 | }
67 |
68 | // TODO: validation
69 |
70 | err = db.Create(&post).Error
71 | if err != nil {
72 | rest.WriteError(w, http.StatusInternalServerError, err)
73 | return
74 | }
75 |
76 | rest.WriteJSON(w, http.StatusCreated, rest.Response{
77 | Ok: true,
78 | Result: post.Id,
79 | })
80 | })
81 | }
82 |
83 | func UpdatePost(db *gorm.DB) http.HandlerFunc {
84 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85 | vars := mux.Vars(r)
86 | idStr := vars["id"]
87 | id, err := strconv.Atoi(idStr)
88 | if err != nil {
89 | rest.WriteError(w, http.StatusBadRequest, err)
90 | return
91 | }
92 |
93 | var post models.Post
94 | err = json.NewDecoder(r.Body).Decode(&post)
95 | if err != nil {
96 | rest.WriteError(w, http.StatusBadRequest, err)
97 | return
98 | }
99 | post.Id = id
100 |
101 | // TODO: validation
102 |
103 | err = db.Select("*").Updates(post).Error
104 | if err != nil {
105 | rest.WriteError(w, http.StatusInternalServerError, err)
106 | return
107 | }
108 |
109 | rest.WriteJSON(w, http.StatusOK, rest.Response{
110 | Ok: true,
111 | Result: post.Id,
112 | })
113 | })
114 | }
115 |
116 | func DeletePost(db *gorm.DB) http.HandlerFunc {
117 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 | vars := mux.Vars(r)
119 | idStr := vars["id"]
120 | id, err := strconv.Atoi(idStr)
121 | if err != nil {
122 | rest.WriteError(w, http.StatusBadRequest, err)
123 | return
124 | }
125 |
126 | err = db.Delete(&models.Post{}, id).Error
127 | if err != nil {
128 | if err == gorm.ErrRecordNotFound {
129 | rest.WriteError(w, http.StatusNotFound, errors.New("not found"))
130 | return
131 | }
132 | rest.WriteError(w, http.StatusInternalServerError, err)
133 | return
134 | }
135 |
136 | rest.WriteJSON(w, http.StatusOK, rest.Response{
137 | Ok: true,
138 | })
139 | })
140 | }
141 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/handlers/post_test.go:
--------------------------------------------------------------------------------
1 | package handlers_test
2 |
3 | import (
4 | "encoding/json"
5 | "example/internal/app"
6 | "example/internal/models"
7 | "example/internal/rest"
8 | "example/internal/tests"
9 | "fmt"
10 | "net/http"
11 | "net/http/httptest"
12 | "path/filepath"
13 | "testing"
14 |
15 | "github.com/stretchr/testify/assert"
16 | )
17 |
18 | func TestGetPost(t *testing.T) {
19 | var err error
20 |
21 | app := app.NewApp()
22 |
23 | configPath := filepath.Join(tests.GetProjectRoot(), ".env.test")
24 | err = app.Config.Load(configPath)
25 | assert.Nil(t, err)
26 |
27 | err = app.Setup()
28 | assert.Nil(t, err)
29 | defer app.Teardown()
30 |
31 | db := app.DB
32 |
33 | tests.SetupDB(db)
34 | defer tests.TeardownDB(db)
35 |
36 | post := models.Post{
37 | Title: "some title",
38 | Text: "some content",
39 | }
40 | err = db.Create(&post).Error
41 | assert.Nil(t, err)
42 |
43 | url := fmt.Sprintf("/post/%d", post.Id)
44 |
45 | req, err := http.NewRequest("GET", url, nil)
46 | assert.Nil(t, err)
47 |
48 | w := httptest.NewRecorder()
49 |
50 | // handler := handlers.GetPost(db)
51 | handler := app.Router
52 | handler.ServeHTTP(w, req)
53 |
54 | assert.Equal(t, http.StatusOK, w.Code)
55 |
56 | type PostReponse struct {
57 | rest.Response
58 | Result models.Post `json:"result"`
59 | }
60 | var response PostReponse
61 |
62 | json.Unmarshal(w.Body.Bytes(), &response)
63 |
64 | assert.Equal(t, "some title", response.Result.Title)
65 | assert.Equal(t, "some content", response.Result.Text)
66 | }
67 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/models/post.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Post struct {
6 | Id int `json:"id"`
7 | Title string `json:"title"`
8 | Text string `json:"text"`
9 |
10 | CreatedAt time.Time `json:"createdAt"`
11 | UpdatedAt time.Time `json:"updatedAt"`
12 | }
13 |
14 | func (Post) TableName() string {
15 | return "posts"
16 | }
17 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/rest/rest.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | type Response struct {
9 | Ok bool `json:"ok"`
10 | Result any `json:"result,omitempty"`
11 | Error string `json:"error,omitempty"`
12 | }
13 |
14 | func WriteJSON(w http.ResponseWriter, status int, v any) {
15 | bytes, _ := json.Marshal(v)
16 | w.WriteHeader(status)
17 | w.Write(bytes)
18 | }
19 |
20 | func WriteError(w http.ResponseWriter, status int, err error) {
21 | WriteJSON(w, status, Response{
22 | Ok: false,
23 | Error: err.Error(),
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/tests/db.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "io/ioutil"
5 | "path/filepath"
6 | "sort"
7 | "strings"
8 |
9 | "gorm.io/gorm"
10 | )
11 |
12 | func SetupDB(db *gorm.DB) error {
13 | pattern := filepath.Join(GetProjectRoot(), "migrations", "*.up.sql")
14 | sql, err := ConcatMigrations(pattern)
15 | if err != nil {
16 | return err
17 | }
18 |
19 | err = db.Exec(sql).Error
20 | if err != nil {
21 | TeardownDB(db)
22 | err = db.Exec(sql).Error
23 | if err != nil {
24 | return err
25 | }
26 | }
27 |
28 | return nil
29 | }
30 |
31 | func TeardownDB(db *gorm.DB) error {
32 | pattern := filepath.Join(GetProjectRoot(), "migrations", "*.down.sql")
33 | sql, err := ConcatMigrations(pattern)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | err = db.Exec(sql).Error
39 | if err != nil {
40 | return err
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func ConcatMigrations(pattern string) (string, error) {
47 | filenames, err := filepath.Glob(pattern)
48 | if err != nil {
49 | return "", err
50 | }
51 |
52 | sort.Strings(filenames)
53 |
54 | var contents []string
55 | for _, filename := range filenames {
56 | bytes, err := ioutil.ReadFile(filename)
57 | if err != nil {
58 | return "", err
59 | }
60 | contents = append(contents, string(bytes))
61 | }
62 | return strings.Join(contents, "\n\n"), nil
63 | }
64 |
--------------------------------------------------------------------------------
/10_testing/04_testify/internal/tests/fs.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | func GetProjectRoot() string {
10 | path, err := os.Getwd()
11 | if err != nil {
12 | panic(err)
13 | }
14 |
15 | for {
16 | _, err = os.Stat(filepath.Join(path, ".env"))
17 | if errors.Is(err, os.ErrNotExist) {
18 | path = filepath.Dir(path)
19 | continue
20 | }
21 | if err != nil {
22 | panic(err)
23 | }
24 |
25 | break
26 | }
27 |
28 | return path
29 | }
30 |
--------------------------------------------------------------------------------
/10_testing/04_testify/migrations/01_init.down.sql:
--------------------------------------------------------------------------------
1 | drop table posts;
--------------------------------------------------------------------------------
/10_testing/04_testify/migrations/01_init.up.sql:
--------------------------------------------------------------------------------
1 | create table posts(
2 | id serial primary key,
3 | title varchar(256) not null,
4 | text text not null,
5 | created_at timestamp with time zone default current_timestamp,
6 | updated_at timestamp with time zone default current_timestamp
7 | );
8 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev
--------------------------------------------------------------------------------
/10_testing/05_apitest/.env.test:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:123@localhost:5432/go_dev_test
--------------------------------------------------------------------------------
/10_testing/05_apitest/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "example/internal/app"
5 | "fmt"
6 | )
7 |
8 | func main() {
9 | app := app.NewApp()
10 | err := app.Config.Load(".env")
11 | if err != nil {
12 | panic(err)
13 | }
14 |
15 | err = app.Setup()
16 | if err != nil {
17 | panic(err)
18 | }
19 |
20 | fmt.Println("Starting is running.")
21 |
22 | err = app.Run()
23 | if err != nil {
24 | panic(err)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/go.mod:
--------------------------------------------------------------------------------
1 | module example
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/gorilla/mux v1.8.0
7 | github.com/joho/godotenv v1.4.0
8 | gorm.io/driver/postgres v1.4.5
9 | gorm.io/gorm v1.24.1
10 | )
11 |
12 | require (
13 | github.com/PaesslerAG/gval v1.0.0 // indirect
14 | github.com/PaesslerAG/jsonpath v0.1.1 // indirect
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
17 | github.com/jackc/pgconn v1.13.0 // indirect
18 | github.com/jackc/pgio v1.0.0 // indirect
19 | github.com/jackc/pgpassfile v1.0.0 // indirect
20 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
21 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
22 | github.com/jackc/pgtype v1.12.0 // indirect
23 | github.com/jackc/pgx/v4 v4.17.2 // indirect
24 | github.com/jinzhu/inflection v1.0.0 // indirect
25 | github.com/jinzhu/now v1.1.5 // indirect
26 | github.com/pmezard/go-difflib v1.0.0 // indirect
27 | github.com/steinfletcher/apitest v1.5.14 // indirect
28 | github.com/steinfletcher/apitest-jsonpath v1.7.1 // indirect
29 | github.com/stretchr/testify v1.8.1 // indirect
30 | golang.org/x/crypto v0.3.0 // indirect
31 | golang.org/x/text v0.4.0 // indirect
32 | gopkg.in/yaml.v3 v3.0.1 // indirect
33 | )
34 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8=
5 | github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
6 | github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
7 | github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
8 | github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
9 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
10 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
11 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
12 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
13 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
18 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
19 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
20 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
21 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
22 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
23 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
24 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
25 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
26 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
27 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
28 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
29 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
30 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
31 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
32 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
33 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
34 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
35 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
36 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
37 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
38 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
39 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
40 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
41 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
42 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
43 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
44 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
45 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
46 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
47 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
48 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
49 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
50 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
51 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
52 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
53 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
54 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
55 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
56 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
57 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
58 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
59 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
60 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
61 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
62 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
63 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
64 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
65 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
66 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
67 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
68 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
69 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
70 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
71 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
72 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
73 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
74 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
75 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
76 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
77 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
78 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
79 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
80 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
81 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
82 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
83 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
84 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
85 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
86 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
87 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
88 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
89 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
90 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
91 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
92 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
93 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
94 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
95 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
96 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
97 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
98 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
99 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
100 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
101 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
102 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
103 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
104 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
105 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
106 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
107 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
108 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
109 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
110 | github.com/steinfletcher/apitest v1.5.10/go.mod h1:cf7Bneo52IIAgpqhP8xaLlzWgAiQ9fHtsDMjeDnZ3so=
111 | github.com/steinfletcher/apitest v1.5.14 h1:18t0UtxdKf0OPfeP5omB85m23l1E3/tN3i93Rtw9Kp4=
112 | github.com/steinfletcher/apitest v1.5.14/go.mod h1:mF+KnYaIkuHM0C4JgGzkIIOJAEjo+EA5tTjJ+bHXnQc=
113 | github.com/steinfletcher/apitest-jsonpath v1.7.1 h1:dj0Z/7DJt2Ts/R52OeVbnMp+gegcf2X7F+/l75HBfsY=
114 | github.com/steinfletcher/apitest-jsonpath v1.7.1/go.mod h1:FzU2i3ZIyIdF6yBI8a0DZ10gCC6jhiD1yAdU0nCz5Do=
115 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
116 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
117 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
118 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
119 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
120 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
121 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
122 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
123 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
124 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
125 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
126 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
127 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
128 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
129 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
130 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
131 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
132 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
133 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
134 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
135 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
136 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
137 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
138 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
139 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
140 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
141 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
142 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
143 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
144 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
145 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
146 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
147 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
148 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
149 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
150 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
151 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
152 | golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
153 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
154 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
155 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
156 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
157 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
158 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
159 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
160 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
161 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
162 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
163 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
164 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
165 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
166 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
167 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
168 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
169 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
170 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
171 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
172 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
173 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
174 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
175 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
176 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
177 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
178 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
179 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
180 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
181 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
182 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
183 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
184 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
185 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
186 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
187 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
188 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
189 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
190 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
191 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
192 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
193 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
194 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
195 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
196 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
197 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
198 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
199 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
200 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
201 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
202 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
203 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
204 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
205 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
206 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
207 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
208 | gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
209 | gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
210 | gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
211 | gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
212 | gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
213 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
214 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/app/app.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "errors"
5 | "example/internal/handlers"
6 | "net/http"
7 | "os"
8 |
9 | "github.com/gorilla/mux"
10 | "github.com/joho/godotenv"
11 |
12 | "gorm.io/driver/postgres"
13 | "gorm.io/gorm"
14 | )
15 |
16 | type App struct {
17 | Config Config
18 | Router *mux.Router
19 | DB *gorm.DB
20 | }
21 |
22 | type Config struct {
23 | DatabaseUrl string
24 | }
25 |
26 | func (config *Config) Load(filename string) error {
27 | err := godotenv.Load(filename)
28 | if err != nil {
29 | return errors.New("Error loading " + filename)
30 | }
31 |
32 | config.DatabaseUrl = os.Getenv("DATABASE_URL")
33 | if config.DatabaseUrl == "" {
34 | return errors.New("missing DATABASE_URL")
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func NewApp() App {
41 | return App{}
42 | }
43 |
44 | func (app *App) Setup() error {
45 | db, err := gorm.Open(postgres.Open(app.Config.DatabaseUrl), &gorm.Config{})
46 | if err != nil {
47 | return err
48 | }
49 |
50 | r := mux.NewRouter()
51 | r.HandleFunc("/post", handlers.GetPosts(db)).Methods("GET")
52 | r.HandleFunc("/post", handlers.CreatePost(db)).Methods("POST")
53 | r.HandleFunc(`/post/{id:\d+}`, handlers.GetPost(db)).Methods("GET")
54 | r.HandleFunc(`/post/{id:\d+}`, handlers.UpdatePost(db)).Methods("PUT")
55 | r.HandleFunc("/post", handlers.DeletePost(db)).Methods("DELETE")
56 | r.HandleFunc("/ping", handlers.Ping)
57 |
58 | app.Router = r
59 | app.DB = db
60 |
61 | return nil
62 | }
63 |
64 | func (app *App) Teardown() error {
65 | sqlDB, err := app.DB.DB()
66 | if err != nil {
67 | return err
68 | }
69 | return sqlDB.Close()
70 | }
71 |
72 | func (app *App) Run() error {
73 | return http.ListenAndServe(":3000", app.Router)
74 | }
75 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/handlers/ping.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | )
7 |
8 | func Ping(w http.ResponseWriter, r *http.Request) {
9 | io.WriteString(w, "OK")
10 | }
11 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/handlers/ping_test.go:
--------------------------------------------------------------------------------
1 | package handlers_test
2 |
3 | import (
4 | "example/internal/handlers"
5 | "net/http"
6 | "testing"
7 |
8 | "github.com/steinfletcher/apitest"
9 | )
10 |
11 | func TestPing(t *testing.T) {
12 | apitest.
13 | HandlerFunc(handlers.Ping).
14 | Get("/ping").
15 | Expect(t).
16 | Status(http.StatusOK).
17 | Body("OK").
18 | End()
19 | }
20 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/handlers/post.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "example/internal/models"
7 | "example/internal/rest"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/gorilla/mux"
12 | "gorm.io/gorm"
13 | )
14 |
15 | func GetPosts(db *gorm.DB) http.HandlerFunc {
16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 | var posts []models.Post
18 | err := db.Order("id desc").Find(&posts).Error
19 | if err != nil {
20 | rest.WriteError(w, http.StatusInternalServerError, err)
21 | return
22 | }
23 |
24 | rest.WriteJSON(w, http.StatusOK, rest.Response{
25 | Ok: true,
26 | Result: posts,
27 | })
28 | })
29 | }
30 |
31 | func GetPost(db *gorm.DB) http.HandlerFunc {
32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33 | vars := mux.Vars(r)
34 | idStr := vars["id"]
35 | id, err := strconv.Atoi(idStr)
36 | if err != nil {
37 | rest.WriteError(w, http.StatusBadRequest, err)
38 | return
39 | }
40 |
41 | var post models.Post
42 | err = db.Take(&post, id).Error
43 | if err != nil {
44 | if err == gorm.ErrRecordNotFound {
45 | rest.WriteError(w, http.StatusNotFound, errors.New("not found"))
46 | return
47 | }
48 | rest.WriteError(w, http.StatusInternalServerError, err)
49 | return
50 | }
51 |
52 | rest.WriteJSON(w, http.StatusOK, rest.Response{
53 | Ok: true,
54 | Result: post,
55 | })
56 | })
57 | }
58 |
59 | func CreatePost(db *gorm.DB) http.HandlerFunc {
60 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61 | var post models.Post
62 | err := json.NewDecoder(r.Body).Decode(&post)
63 | if err != nil {
64 | rest.WriteError(w, http.StatusBadRequest, err)
65 | return
66 | }
67 |
68 | // TODO: validation
69 |
70 | err = db.Create(&post).Error
71 | if err != nil {
72 | rest.WriteError(w, http.StatusInternalServerError, err)
73 | return
74 | }
75 |
76 | rest.WriteJSON(w, http.StatusCreated, rest.Response{
77 | Ok: true,
78 | Result: post.Id,
79 | })
80 | })
81 | }
82 |
83 | func UpdatePost(db *gorm.DB) http.HandlerFunc {
84 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85 | vars := mux.Vars(r)
86 | idStr := vars["id"]
87 | id, err := strconv.Atoi(idStr)
88 | if err != nil {
89 | rest.WriteError(w, http.StatusBadRequest, err)
90 | return
91 | }
92 |
93 | var post models.Post
94 | err = json.NewDecoder(r.Body).Decode(&post)
95 | if err != nil {
96 | rest.WriteError(w, http.StatusBadRequest, err)
97 | return
98 | }
99 | post.Id = id
100 |
101 | // TODO: validation
102 |
103 | err = db.Select("*").Updates(post).Error
104 | if err != nil {
105 | rest.WriteError(w, http.StatusInternalServerError, err)
106 | return
107 | }
108 |
109 | rest.WriteJSON(w, http.StatusOK, rest.Response{
110 | Ok: true,
111 | Result: post.Id,
112 | })
113 | })
114 | }
115 |
116 | func DeletePost(db *gorm.DB) http.HandlerFunc {
117 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 | vars := mux.Vars(r)
119 | idStr := vars["id"]
120 | id, err := strconv.Atoi(idStr)
121 | if err != nil {
122 | rest.WriteError(w, http.StatusBadRequest, err)
123 | return
124 | }
125 |
126 | err = db.Delete(&models.Post{}, id).Error
127 | if err != nil {
128 | if err == gorm.ErrRecordNotFound {
129 | rest.WriteError(w, http.StatusNotFound, errors.New("not found"))
130 | return
131 | }
132 | rest.WriteError(w, http.StatusInternalServerError, err)
133 | return
134 | }
135 |
136 | rest.WriteJSON(w, http.StatusOK, rest.Response{
137 | Ok: true,
138 | })
139 | })
140 | }
141 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/handlers/post_test.go:
--------------------------------------------------------------------------------
1 | package handlers_test
2 |
3 | import (
4 | "example/internal/models"
5 | "example/internal/tests"
6 | "fmt"
7 | "net/http"
8 | "testing"
9 |
10 | "github.com/steinfletcher/apitest"
11 | jsonpath "github.com/steinfletcher/apitest-jsonpath"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestGetPost(t *testing.T) {
16 | app := tests.AppSetup(t)
17 | defer tests.AppTeardown(app)
18 |
19 | db := app.DB
20 |
21 | post := models.Post{
22 | Title: "some title",
23 | Text: "some content",
24 | }
25 | err := db.Create(&post).Error
26 | assert.Nil(t, err)
27 |
28 | url := fmt.Sprintf("/post/%d", post.Id)
29 |
30 | apitest.
31 | Handler(app.Router).
32 | Get(url).
33 | Expect(t).
34 | Status(http.StatusOK).
35 | Header("Content-Type", "application/json").
36 | Assert(
37 | jsonpath.Chain().
38 | Equal("ok", true).
39 | Equal("result.title", "some title").
40 | Equal("result.text", "some content").
41 | End(),
42 | ).
43 | End()
44 | }
45 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/models/post.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | type Post struct {
6 | Id int `json:"id"`
7 | Title string `json:"title"`
8 | Text string `json:"text"`
9 |
10 | CreatedAt time.Time `json:"createdAt"`
11 | UpdatedAt time.Time `json:"updatedAt"`
12 | }
13 |
14 | func (Post) TableName() string {
15 | return "posts"
16 | }
17 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/rest/rest.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | type Response struct {
9 | Ok bool `json:"ok"`
10 | Result any `json:"result,omitempty"`
11 | Error string `json:"error,omitempty"`
12 | }
13 |
14 | func WriteJSON(w http.ResponseWriter, status int, v any) {
15 | w.Header().Set("Content-Type", "application/json")
16 | bytes, _ := json.Marshal(v)
17 | w.WriteHeader(status)
18 | w.Write(bytes)
19 | }
20 |
21 | func WriteError(w http.ResponseWriter, status int, err error) {
22 | WriteJSON(w, status, Response{
23 | Ok: false,
24 | Error: err.Error(),
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/tests/app.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "example/internal/app"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func AppSetup(t *testing.T) *app.App {
12 | app := app.NewApp()
13 |
14 | configPath := filepath.Join(GetProjectRoot(), ".env.test")
15 | err := app.Config.Load(configPath)
16 | require.Nil(t, err)
17 |
18 | err = app.Setup()
19 | require.Nil(t, err)
20 |
21 | MigrateUp(app.DB)
22 |
23 | return &app
24 | }
25 |
26 | func AppTeardown(app *app.App) {
27 | MigrateDown(app.DB)
28 | app.Teardown()
29 | }
30 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/tests/db.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "io/ioutil"
5 | "path/filepath"
6 | "sort"
7 | "strings"
8 |
9 | "gorm.io/gorm"
10 | )
11 |
12 | func MigrateUp(db *gorm.DB) error {
13 | pattern := filepath.Join(GetProjectRoot(), "migrations", "*.up.sql")
14 | sql, err := ConcatMigrations(pattern)
15 | if err != nil {
16 | return err
17 | }
18 |
19 | err = db.Exec(sql).Error
20 | if err != nil {
21 | MigrateDown(db)
22 | err = db.Exec(sql).Error
23 | if err != nil {
24 | return err
25 | }
26 | }
27 |
28 | return nil
29 | }
30 |
31 | func MigrateDown(db *gorm.DB) error {
32 | pattern := filepath.Join(GetProjectRoot(), "migrations", "*.down.sql")
33 | sql, err := ConcatMigrations(pattern)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | err = db.Exec(sql).Error
39 | if err != nil {
40 | return err
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func ConcatMigrations(pattern string) (string, error) {
47 | filenames, err := filepath.Glob(pattern)
48 | if err != nil {
49 | return "", err
50 | }
51 |
52 | sort.Strings(filenames)
53 |
54 | var contents []string
55 | for _, filename := range filenames {
56 | bytes, err := ioutil.ReadFile(filename)
57 | if err != nil {
58 | return "", err
59 | }
60 | contents = append(contents, string(bytes))
61 | }
62 | return strings.Join(contents, "\n\n"), nil
63 | }
64 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/internal/tests/fs.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | func GetProjectRoot() string {
10 | path, err := os.Getwd()
11 | if err != nil {
12 | panic(err)
13 | }
14 |
15 | for {
16 | _, err = os.Stat(filepath.Join(path, ".env"))
17 | if errors.Is(err, os.ErrNotExist) {
18 | path = filepath.Dir(path)
19 | continue
20 | }
21 | if err != nil {
22 | panic(err)
23 | }
24 |
25 | break
26 | }
27 |
28 | return path
29 | }
30 |
--------------------------------------------------------------------------------
/10_testing/05_apitest/migrations/01_init.down.sql:
--------------------------------------------------------------------------------
1 | drop table posts;
--------------------------------------------------------------------------------
/10_testing/05_apitest/migrations/01_init.up.sql:
--------------------------------------------------------------------------------
1 | create table posts(
2 | id serial primary key,
3 | title varchar(256) not null,
4 | text text not null,
5 | created_at timestamp with time zone default current_timestamp,
6 | updated_at timestamp with time zone default current_timestamp
7 | );
8 |
--------------------------------------------------------------------------------
/11_additional/01_serve_static/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | fs := http.FileServer(http.Dir("./static"))
11 | staticHandler := http.StripPrefix("/static/", fs)
12 |
13 | http.HandleFunc("/ping", PingHandler)
14 | http.Handle("/static/", staticHandler)
15 |
16 | log.Print("Listening on :3000...")
17 | err := http.ListenAndServe(":3000", nil)
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
23 | func PingHandler(w http.ResponseWriter, r *http.Request) {
24 | io.WriteString(w, "OK")
25 | }
26 |
--------------------------------------------------------------------------------
/11_additional/01_serve_static/static/css/style.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-size: 36px;
3 | color: rgb(46, 199, 33);
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/11_additional/01_serve_static/static/img/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/un1t/go-web-course/c1951466a1bb2e1aa200e95e92d04498ed0a27a0/11_additional/01_serve_static/static/img/cat.jpg
--------------------------------------------------------------------------------
/11_additional/01_serve_static/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Serve static example
9 |
10 |
11 |
12 |
13 | Serve static
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/11_additional/01_serve_static/static/js/main.js:
--------------------------------------------------------------------------------
1 | console.log('hello')
2 |
--------------------------------------------------------------------------------