├── 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 |
8 | 9 |

10 | 11 |

12 | 13 |
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 |
9 | 10 |

11 | 12 |
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 | --------------------------------------------------------------------------------