├── README.md ├── cmd └── web │ ├── main │ └── main.go ├── docs ├── docs.go ├── models.go └── swagger.json ├── go.mod ├── go.sum ├── mocks └── pets.go └── pkg ├── app ├── pet.go └── pet_test.go ├── db ├── inmem.go └── mongo.go ├── domain └── pet.go └── http ├── handler.go ├── helper.go ├── http_test.go ├── middleware.go ├── models.go └── route.go /README.md: -------------------------------------------------------------------------------- 1 | getpets -> A simple application to demonstrate standard package layout of go app -------------------------------------------------------------------------------- /cmd/web/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-vignesh/getpets/f765866218a2544cfa7e5d7a9ca0c32772fb8f2e/cmd/web/main -------------------------------------------------------------------------------- /cmd/web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | _ "github.com/d-vignesh/getpets/docs" 9 | "github.com/d-vignesh/getpets/mocks" 10 | "github.com/d-vignesh/getpets/pkg/app" 11 | "github.com/d-vignesh/getpets/pkg/db" 12 | ihttp "github.com/d-vignesh/getpets/pkg/http" 13 | "github.com/go-chi/chi" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | func main() { 18 | args := os.Args 19 | op := "server" 20 | if len(args) > 1 { 21 | op = args[0] 22 | } 23 | if err := run(op); err != nil { 24 | fmt.Println(fmt.Errorf("error - server failed to start. err: %v", err)) 25 | } 26 | } 27 | 28 | func run(op string) error { 29 | if op == "http_test" { 30 | // mocking the pet svc to test our http routes 31 | svc := mocks.PetSvc{} 32 | h := ihttp.NewHandler(svc) 33 | r := chi.NewRouter() 34 | ihttp.Routes(r, h) 35 | return http.ListenAndServe(":6000", r) 36 | } 37 | 38 | // tying up all the components together and running the server 39 | db, err := db.NewMongoStore() 40 | if err != nil { 41 | return errors.Wrap(err, "unable to intialize db") 42 | } 43 | svc := app.NewPetSvc(db) 44 | h := ihttp.NewHandler(svc) 45 | r := chi.NewRouter() 46 | ihttp.Routes(r, h) 47 | fmt.Println("starting server") 48 | return http.ListenAndServe(":9000", r) 49 | } 50 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // swagger: "2.0" 2 | // info: 3 | // title: Pet API 4 | // description: Spec Documentation for pet service. 5 | // version: 1.0.0 6 | // 7 | // schemes: 8 | // - http 9 | // 10 | // BasePath: / 11 | // Host: localhost:9096 12 | // 13 | // Consumes: 14 | // - application/json 15 | // 16 | // Produces: 17 | // - application/json 18 | // 19 | // swagger:meta 20 | package docs 21 | -------------------------------------------------------------------------------- /docs/models.go: -------------------------------------------------------------------------------- 1 | package docs 2 | 3 | import "github.com/d-vignesh/getpets/pkg/domain" 4 | 5 | // model for PetID param 6 | // swagger:parameters GetPet DeletePet 7 | type PetIDParam struct { 8 | // The id of the pet 9 | // 10 | // in:path 11 | // required:true 12 | ID int `json:"id"` 13 | } 14 | 15 | // model for error response 16 | // swagger:response ErrorResponse 17 | type ErrorResponse struct { 18 | // in:body 19 | Body struct { 20 | Msg string `json:"message"` 21 | } `json:"body"` 22 | } 23 | 24 | // model for get pet response 25 | // swagger:response GetPetResponse 26 | type GetPetResponse struct { 27 | // in:body 28 | Body struct { 29 | Msg string `json:"message"` 30 | Data domain.Pet `json:"data"` 31 | } `json:"body"` 32 | } 33 | 34 | // model for pet category query param 35 | // swagger:parameters ListPets 36 | type PetCategoryQueryParam struct { 37 | // pet category to filter 38 | // 39 | // in:query 40 | Category string `json:"category"` 41 | } 42 | 43 | // model for list pets response 44 | // swagger:response ListPetsResponse 45 | type ListPetsResponse struct { 46 | // in:body 47 | Body struct { 48 | Msg string `json:"message"` 49 | Data []domain.Pet `json:"data"` 50 | } 51 | } 52 | 53 | // model for add pet request 54 | // swagger:parameters AddPet 55 | type AddPetRequest struct { 56 | // in: body 57 | // required: true 58 | Body domain.Pet `json:"body"` 59 | } 60 | 61 | // model for add success response without data 62 | // swagger:response SuccessRespWithoutData 63 | type SuccessRespWithoutData struct { 64 | // in:body 65 | Body struct { 66 | Msg string `json:"message"` 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "consumes": [ 3 | "application/json" 4 | ], 5 | "produces": [ 6 | "application/json" 7 | ], 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "info:\ntitle: Pet API\ndescription: Spec Documentation for pet service.", 11 | "title": "swagger: \"2.0\"", 12 | "version": "1.0.0" 13 | }, 14 | "host": "localhost:9096", 15 | "basePath": "/", 16 | "paths": { 17 | "/pets": { 18 | "get": { 19 | "description": "provides the details of all pets", 20 | "tags": [ 21 | "Pet" 22 | ], 23 | "operationId": "ListPets", 24 | "parameters": [ 25 | { 26 | "type": "string", 27 | "x-go-name": "Category", 28 | "description": "pet category to filter", 29 | "name": "category", 30 | "in": "query" 31 | } 32 | ], 33 | "responses": { 34 | "200": { 35 | "$ref": "#/responses/ListPetsResponse" 36 | }, 37 | "500": { 38 | "$ref": "#/responses/ErrorResponse" 39 | } 40 | } 41 | }, 42 | "post": { 43 | "description": "add a new pet detail", 44 | "tags": [ 45 | "Pet" 46 | ], 47 | "operationId": "AddPet", 48 | "parameters": [ 49 | { 50 | "x-go-name": "Body", 51 | "name": "body", 52 | "in": "body", 53 | "required": true, 54 | "schema": { 55 | "$ref": "#/definitions/Pet" 56 | } 57 | } 58 | ], 59 | "responses": { 60 | "200": { 61 | "$ref": "#/responses/SuccessRespWithoutData" 62 | }, 63 | "500": { 64 | "$ref": "#/responses/ErrorResponse" 65 | } 66 | } 67 | } 68 | }, 69 | "/pets/{id}": { 70 | "get": { 71 | "description": "provides the detail of the pet with the given id", 72 | "tags": [ 73 | "Pet" 74 | ], 75 | "operationId": "GetPet", 76 | "parameters": [ 77 | { 78 | "type": "integer", 79 | "format": "int64", 80 | "x-go-name": "ID", 81 | "description": "The id of the pet", 82 | "name": "id", 83 | "in": "path", 84 | "required": true 85 | } 86 | ], 87 | "responses": { 88 | "200": { 89 | "$ref": "#/responses/GetPetResponse" 90 | }, 91 | "400": { 92 | "$ref": "#/responses/ErrorResponse" 93 | }, 94 | "500": { 95 | "$ref": "#/responses/ErrorResponse" 96 | } 97 | } 98 | }, 99 | "delete": { 100 | "description": "delete the pet detail with given id", 101 | "tags": [ 102 | "Pet" 103 | ], 104 | "operationId": "DeletePet", 105 | "parameters": [ 106 | { 107 | "type": "integer", 108 | "format": "int64", 109 | "x-go-name": "ID", 110 | "description": "The id of the pet", 111 | "name": "id", 112 | "in": "path", 113 | "required": true 114 | } 115 | ], 116 | "responses": { 117 | "200": { 118 | "$ref": "#/responses/SuccessRespWithoutData" 119 | }, 120 | "400": { 121 | "$ref": "#/responses/ErrorResponse" 122 | }, 123 | "500": { 124 | "$ref": "#/responses/ErrorResponse" 125 | } 126 | } 127 | } 128 | } 129 | }, 130 | "definitions": { 131 | "Contact": { 132 | "type": "object", 133 | "properties": { 134 | "city": { 135 | "type": "string", 136 | "x-go-name": "City" 137 | }, 138 | "owner": { 139 | "type": "string", 140 | "x-go-name": "Owner" 141 | }, 142 | "phone": { 143 | "type": "string", 144 | "x-go-name": "Phone" 145 | }, 146 | "state": { 147 | "type": "string", 148 | "x-go-name": "State" 149 | } 150 | }, 151 | "x-go-package": "github.com/d-vignesh/getpets/pkg/domain" 152 | }, 153 | "Pet": { 154 | "type": "object", 155 | "properties": { 156 | "age": { 157 | "type": "integer", 158 | "format": "int64", 159 | "x-go-name": "Age" 160 | }, 161 | "breed": { 162 | "type": "string", 163 | "x-go-name": "Breed" 164 | }, 165 | "category": { 166 | "type": "string", 167 | "x-go-name": "Category" 168 | }, 169 | "colors": { 170 | "type": "string", 171 | "x-go-name": "Colors" 172 | }, 173 | "contact": { 174 | "$ref": "#/definitions/Contact" 175 | }, 176 | "gender": { 177 | "type": "string", 178 | "x-go-name": "Gender" 179 | }, 180 | "id": { 181 | "type": "string", 182 | "format": "uuid", 183 | "x-go-name": "ID" 184 | }, 185 | "price": { 186 | "type": "number", 187 | "format": "double", 188 | "x-go-name": "Price" 189 | } 190 | }, 191 | "x-go-package": "github.com/d-vignesh/getpets/pkg/domain" 192 | } 193 | }, 194 | "responses": { 195 | "ErrorResponse": { 196 | "description": "model for error response", 197 | "schema": { 198 | "type": "object", 199 | "properties": { 200 | "message": { 201 | "type": "string", 202 | "x-go-name": "Msg" 203 | } 204 | } 205 | } 206 | }, 207 | "GetPetResponse": { 208 | "description": "model for get pet response", 209 | "schema": { 210 | "type": "object", 211 | "properties": { 212 | "data": { 213 | "$ref": "#/definitions/Pet" 214 | }, 215 | "message": { 216 | "type": "string", 217 | "x-go-name": "Msg" 218 | } 219 | } 220 | } 221 | }, 222 | "ListPetsResponse": { 223 | "description": "model for list pets response", 224 | "schema": { 225 | "type": "object", 226 | "properties": { 227 | "data": { 228 | "type": "array", 229 | "items": { 230 | "$ref": "#/definitions/Pet" 231 | }, 232 | "x-go-name": "Data" 233 | }, 234 | "message": { 235 | "type": "string", 236 | "x-go-name": "Msg" 237 | } 238 | } 239 | } 240 | }, 241 | "SuccessRespWithoutData": { 242 | "description": "model for add success response without data", 243 | "schema": { 244 | "type": "object", 245 | "properties": { 246 | "message": { 247 | "type": "string", 248 | "x-go-name": "Msg" 249 | } 250 | } 251 | } 252 | } 253 | } 254 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/d-vignesh/getpets 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-openapi/runtime v0.25.0 8 | github.com/go-playground/validator v9.31.0+incompatible 9 | github.com/google/uuid v1.3.0 10 | github.com/pkg/errors v0.9.1 11 | github.com/stretchr/testify v1.8.0 12 | go.mongodb.org/mongo-driver v1.8.3 13 | gopkg.in/go-playground/assert.v1 v1.2.1 14 | ) 15 | 16 | require ( 17 | github.com/PuerkitoBio/purell v1.1.1 // indirect 18 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 19 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/go-openapi/analysis v0.21.2 // indirect 22 | github.com/go-openapi/errors v0.20.2 // indirect 23 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 24 | github.com/go-openapi/jsonreference v0.19.6 // indirect 25 | github.com/go-openapi/loads v0.21.1 // indirect 26 | github.com/go-openapi/spec v0.20.4 // indirect 27 | github.com/go-openapi/strfmt v0.21.2 // indirect 28 | github.com/go-openapi/swag v0.21.1 // indirect 29 | github.com/go-openapi/validate v0.21.0 // indirect 30 | github.com/go-playground/locales v0.14.0 // indirect 31 | github.com/go-playground/universal-translator v0.18.0 // indirect 32 | github.com/go-stack/stack v1.8.1 // indirect 33 | github.com/golang/snappy v0.0.1 // indirect 34 | github.com/google/go-cmp v0.5.6 // indirect 35 | github.com/josharian/intern v1.0.0 // indirect 36 | github.com/klauspost/compress v1.13.6 // indirect 37 | github.com/leodido/go-urn v1.2.1 // indirect 38 | github.com/mailru/easyjson v0.7.7 // indirect 39 | github.com/mitchellh/mapstructure v1.4.3 // indirect 40 | github.com/oklog/ulid v1.3.1 // indirect 41 | github.com/pmezard/go-difflib v1.0.0 // indirect 42 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 43 | github.com/xdg-go/scram v1.0.2 // indirect 44 | github.com/xdg-go/stringprep v1.0.2 // indirect 45 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 46 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 47 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 48 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 49 | golang.org/x/text v0.3.7 // indirect 50 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 51 | gopkg.in/yaml.v2 v2.4.0 // indirect 52 | gopkg.in/yaml.v3 v3.0.1 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= 3 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 4 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 5 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 6 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 7 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 8 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 14 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 15 | github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU= 16 | github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= 17 | github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= 18 | github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= 19 | github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= 20 | github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= 21 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 22 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 23 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 24 | github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= 25 | github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= 26 | github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0= 27 | github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= 28 | github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= 29 | github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= 30 | github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= 31 | github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= 32 | github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= 33 | github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= 34 | github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os= 35 | github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= 36 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 37 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 38 | github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= 39 | github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 40 | github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI= 41 | github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= 42 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 43 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 44 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 45 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 46 | github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= 47 | github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= 48 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 49 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 50 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 51 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 52 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 53 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= 54 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 55 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 56 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 57 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 58 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 59 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 60 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 61 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= 62 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= 63 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 64 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 65 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= 66 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= 67 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 68 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 69 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 70 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 71 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 72 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 73 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= 74 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 75 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 76 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 77 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 78 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 79 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 80 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 81 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 82 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 83 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 84 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 85 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 86 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 87 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 88 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 89 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 90 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 91 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 92 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 93 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 94 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 95 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 96 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 97 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 98 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 99 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 100 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 101 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 102 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 103 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 104 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 105 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 106 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 107 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 108 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 109 | github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= 110 | github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 111 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 112 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 113 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 114 | github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= 115 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 116 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 117 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 118 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 119 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 120 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 121 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 122 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 123 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 124 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 125 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 126 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 127 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 128 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 129 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 130 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 131 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 132 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 133 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 134 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 135 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 136 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 137 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 138 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 139 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 140 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 141 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 142 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 143 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 144 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 145 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 146 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 147 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 148 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 149 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 150 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 151 | go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= 152 | go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= 153 | go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4= 154 | go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 155 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 156 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 157 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 158 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 159 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 160 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= 161 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 162 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 163 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 164 | golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 165 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= 166 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 167 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 168 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 169 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 170 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 171 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 172 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 173 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 174 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 175 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 176 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 177 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 178 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 179 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 180 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 181 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 182 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 183 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 184 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 185 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 186 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 187 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 188 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 189 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 190 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 191 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 192 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 193 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 194 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 195 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 196 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 197 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 198 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 199 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 200 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 201 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 202 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 203 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 204 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 205 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 206 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 207 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 208 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 209 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 210 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 211 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 212 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 213 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 214 | -------------------------------------------------------------------------------- /mocks/pets.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "github.com/d-vignesh/getpets/pkg/domain" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | // mock for pet service 9 | type PetSvc struct { 10 | GetPetResp domain.Pet 11 | GetPetErr error 12 | ListPetResp []*domain.Pet 13 | ListPetErr error 14 | CreatePetErr error 15 | DeletePetErr error 16 | } 17 | 18 | func (ps PetSvc) Get(uuid.UUID) (*domain.Pet, error) { 19 | return &ps.GetPetResp, ps.GetPetErr 20 | } 21 | 22 | func (ps PetSvc) List(string) ([]*domain.Pet, error) { 23 | return ps.ListPetResp, ps.ListPetErr 24 | } 25 | 26 | func (ps PetSvc) Create(*domain.Pet) error { 27 | return ps.CreatePetErr 28 | } 29 | 30 | func (ps PetSvc) Delete(uuid.UUID) error { 31 | return ps.DeletePetErr 32 | } 33 | -------------------------------------------------------------------------------- /pkg/app/pet.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/d-vignesh/getpets/pkg/domain" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | // petSvc implements domain.PetSvc 9 | type petSvc struct { 10 | DB domain.PetDB 11 | } 12 | 13 | func NewPetSvc(db domain.PetDB) domain.PetSvc { 14 | return petSvc{ 15 | DB: db, 16 | } 17 | } 18 | 19 | func (ps petSvc) Get(id uuid.UUID) (*domain.Pet, error) { 20 | return ps.DB.Get(id) 21 | 22 | } 23 | 24 | func (ps petSvc) List(category string) ([]*domain.Pet, error) { 25 | return ps.DB.List(category) 26 | } 27 | 28 | func (ps petSvc) Create(pet *domain.Pet) error { 29 | pet.ID = uuid.New() 30 | return ps.DB.Create(pet) 31 | } 32 | 33 | func (ps petSvc) Delete(id uuid.UUID) error { 34 | return ps.DB.Delete(id) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/app/pet_test.go: -------------------------------------------------------------------------------- 1 | package app_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/d-vignesh/getpets/pkg/app" 7 | "github.com/d-vignesh/getpets/pkg/db" 8 | "github.com/d-vignesh/getpets/pkg/domain" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCreatePet(t *testing.T) { 13 | // mocking the storage with inMemStore to test the service layer 14 | petDB := db.NewInMem() 15 | petSvc := app.NewPetSvc(petDB) 16 | testCases := []struct { 17 | desc string 18 | pet *domain.Pet 19 | err error 20 | }{ 21 | { 22 | desc: "success", 23 | pet: &domain.Pet{ 24 | Category: "dog", 25 | Breed: "labour", 26 | Age: 2, 27 | Gender: "male", 28 | Colors: "black", 29 | Contact: domain.Contact{ 30 | Owner: "bigshow", 31 | Phone: "123456789", 32 | }, 33 | Price: 10000, 34 | }, 35 | }, 36 | } 37 | for _, tc := range testCases { 38 | t.Run(tc.desc, func(t *testing.T) { 39 | err := petSvc.Create(tc.pet) 40 | assert.NoError(t, err) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/db/inmem.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/d-vignesh/getpets/pkg/domain" 5 | "github.com/google/uuid" 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | // inMemStore implements domain.PetDB with an memory storage 10 | type inMemStore struct { 11 | Pets map[uuid.UUID]*domain.Pet 12 | } 13 | 14 | func NewInMem() domain.PetDB { 15 | return inMemStore{ 16 | Pets: make(map[uuid.UUID]*domain.Pet), 17 | } 18 | } 19 | 20 | func (im inMemStore) Get(id uuid.UUID) (*domain.Pet, error) { 21 | pet, exists := im.Pets[id] 22 | if !exists { 23 | return nil, errors.Errorf("no pet found with id: %s", id) 24 | } 25 | return pet, nil 26 | } 27 | 28 | func (im inMemStore) List(category string) ([]*domain.Pet, error) { 29 | pets := []*domain.Pet{} 30 | for _, p := range im.Pets { 31 | if p.Category == category { 32 | pets = append(pets, p) 33 | } 34 | } 35 | return pets, nil 36 | } 37 | 38 | func (im inMemStore) Create(pet *domain.Pet) error { 39 | im.Pets[pet.ID] = pet 40 | return nil 41 | } 42 | 43 | func (im inMemStore) Delete(id uuid.UUID) error { 44 | delete(im.Pets, id) 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/db/mongo.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/d-vignesh/getpets/pkg/domain" 8 | "github.com/google/uuid" 9 | "go.mongodb.org/mongo-driver/bson" 10 | "go.mongodb.org/mongo-driver/mongo" 11 | "go.mongodb.org/mongo-driver/mongo/options" 12 | ) 13 | 14 | // mongoStore implements domain.PetDB with an mongo storage 15 | type mongoStore struct { 16 | Client *mongo.Client 17 | } 18 | 19 | func NewMongoStore() (domain.PetDB, error) { 20 | uri := os.Getenv("mongo_uri") 21 | client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return mongoStore{Client: client}, nil 26 | } 27 | 28 | func (ms mongoStore) Get(id uuid.UUID) (*domain.Pet, error) { 29 | col := ms.Client.Database("sellpetdb").Collection("pets") 30 | filter := bson.M{"id": id} 31 | pet := domain.Pet{} 32 | err := col.FindOne(context.TODO(), filter).Decode(&pet) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return &pet, nil 37 | } 38 | 39 | func (ms mongoStore) List(category string) ([]*domain.Pet, error) { 40 | col := ms.Client.Database("sellpetdb").Collection("pets") 41 | filter := bson.M{"category": category} 42 | pets := []*domain.Pet{} 43 | cursor, err := col.Find(context.TODO(), filter) 44 | if err != nil { 45 | return nil, err 46 | } 47 | if err := cursor.All(context.TODO(), &pets); err != nil { 48 | return nil, err 49 | } 50 | return pets, nil 51 | } 52 | 53 | func (ms mongoStore) Create(pet *domain.Pet) error { 54 | col := ms.Client.Database("sellpetdb").Collection("pets") 55 | _, err := col.InsertOne(context.TODO(), pet) 56 | return err 57 | } 58 | 59 | func (ms mongoStore) Delete(id uuid.UUID) error { 60 | col := ms.Client.Database("sellpetdb").Collection("pets") 61 | filter := bson.M{"id": id} 62 | _, err := col.DeleteOne(context.TODO(), filter) 63 | return err 64 | } 65 | -------------------------------------------------------------------------------- /pkg/domain/pet.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | ) 6 | 7 | type Pet struct { 8 | ID uuid.UUID `json:"id" bson:"id,omitempty"` 9 | Category string `json:"category" bson:"category,omitempty"` 10 | Breed string `json:"breed" bson:"breed,omitempty"` 11 | Age int `json:"age" bson:"age,omitempty"` 12 | Gender string `json:"gender" bson:"gender,omitempty"` 13 | Colors string `json:"colors" bson:"colors,omitempty"` 14 | Contact Contact `json:"contact" bson:"contact,omitempty"` 15 | Price float64 `json:"price" bson:"price,omitempty"` 16 | } 17 | 18 | type Contact struct { 19 | Owner string `json:"owner" bson:"owner,omitempty"` 20 | Phone string `json:"phone" bson:"phone,omitempty"` 21 | City string `json:"city" bson:"city,omitempty"` 22 | State string `json:"state" bson:"state,omitempty"` 23 | } 24 | 25 | type PetSvc interface { 26 | Get(id uuid.UUID) (*Pet, error) 27 | List(category string) ([]*Pet, error) 28 | Create(p *Pet) error 29 | Delete(id uuid.UUID) error 30 | } 31 | 32 | type PetDB interface { 33 | Get(id uuid.UUID) (*Pet, error) 34 | List(category string) ([]*Pet, error) 35 | Create(p *Pet) error 36 | Delete(id uuid.UUID) error 37 | } 38 | -------------------------------------------------------------------------------- /pkg/http/handler.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/d-vignesh/getpets/pkg/domain" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type Handler struct { 12 | Svc domain.PetSvc 13 | } 14 | 15 | func NewHandler(svc domain.PetSvc) *Handler { 16 | return &Handler{ 17 | Svc: svc, 18 | } 19 | } 20 | 21 | // swagger:route GET /pets/{id} Pet GetPet 22 | // provides the detail of the pet with the given id 23 | // responses: 24 | // 400: ErrorResponse 25 | // 500: ErrorResponse 26 | // 200: GetPetResponse 27 | func (h *Handler) GetPet(w http.ResponseWriter, r *http.Request) { 28 | ID := r.Context().Value("id").(string) 29 | fmt.Println("pet id : ", ID) 30 | petID, err := uuid.Parse(ID) 31 | if err != nil { 32 | resp := Resp{ 33 | Code: http.StatusBadRequest, 34 | Msg: "invalid pet_id provided in url param", 35 | } 36 | respond(w, r, &resp) 37 | return 38 | } 39 | if petID == uuid.Nil { 40 | resp := Resp{ 41 | Code: http.StatusBadRequest, 42 | Msg: "please provide the pet id to retrieve", 43 | } 44 | respond(w, r, &resp) 45 | return 46 | } 47 | pet, err := h.Svc.Get(petID) 48 | if err != nil { 49 | fmt.Println(fmt.Errorf("error - fetching pet detail from db failed, err : %v", err)) 50 | resp := Resp{ 51 | Code: http.StatusInternalServerError, 52 | Msg: "fetching pet detail failed. please try again later", 53 | } 54 | respond(w, r, &resp) 55 | return 56 | } 57 | resp := Resp{ 58 | Code: http.StatusOK, 59 | Msg: "success", 60 | Data: pet, 61 | } 62 | respond(w, r, &resp) 63 | } 64 | 65 | // swagger:route GET /pets Pet ListPets 66 | // provides the details of all pets 67 | // responses: 68 | // 500: ErrorResponse 69 | // 200: ListPetsResponse 70 | func (h *Handler) ListPets(w http.ResponseWriter, r *http.Request) { 71 | query := r.Context().Value(QUERY).(*ListPetsQuery) 72 | pets, err := h.Svc.List(query.Category) 73 | if err != nil { 74 | fmt.Println(fmt.Errorf("error - fetching pet details for given category from db failed, err : %v", err)) 75 | resp := Resp{ 76 | Code: http.StatusInternalServerError, 77 | Msg: "fetching all pets detail failed. please try again later", 78 | } 79 | respond(w, r, &resp) 80 | return 81 | } 82 | resp := Resp{ 83 | Code: http.StatusOK, 84 | Msg: "success", 85 | Data: pets, 86 | } 87 | respond(w, r, &resp) 88 | } 89 | 90 | // swagger:route POST /pets Pet AddPet 91 | // add a new pet detail 92 | // responses: 93 | // 500: ErrorResponse 94 | // 200: SuccessRespWithoutData 95 | func (h *Handler) AddPet(w http.ResponseWriter, r *http.Request) { 96 | req := r.Context().Value(BODY).(*domain.Pet) 97 | err := h.Svc.Create(req) 98 | if err != nil { 99 | fmt.Println(fmt.Errorf("error - adding new pet detail to db failed, err : %v", err)) 100 | resp := Resp{ 101 | Code: http.StatusInternalServerError, 102 | Msg: "adding new pet failed. please try again later", 103 | } 104 | respond(w, r, &resp) 105 | return 106 | } 107 | resp := Resp{ 108 | Code: http.StatusOK, 109 | Msg: "success", 110 | } 111 | respond(w, r, &resp) 112 | } 113 | 114 | // swagger:route DELETE /pets/{id} Pet DeletePet 115 | // delete the pet detail with given id 116 | // responses: 117 | // 400: ErrorResponse 118 | // 500: ErrorResponse 119 | // 200: SuccessRespWithoutData 120 | func (h *Handler) DeletePet(w http.ResponseWriter, r *http.Request) { 121 | ID := r.Context().Value("id").(string) 122 | fmt.Println("pet id : ", ID) 123 | petID, err := uuid.Parse(ID) 124 | if err != nil { 125 | resp := Resp{ 126 | Code: http.StatusBadRequest, 127 | Msg: "invalid pet_id provided in url param", 128 | } 129 | respond(w, r, &resp) 130 | return 131 | } 132 | if petID == uuid.Nil { 133 | resp := Resp{ 134 | Code: http.StatusBadRequest, 135 | Msg: "please provide the pet id to retrieve", 136 | } 137 | respond(w, r, &resp) 138 | return 139 | } 140 | err = h.Svc.Delete(petID) 141 | if err != nil { 142 | fmt.Println(fmt.Errorf("error - deleting pet record from db failed, err: %v", err)) 143 | resp := Resp{ 144 | Code: http.StatusInternalServerError, 145 | Msg: "unable to delete the pet details. please try again later", 146 | } 147 | respond(w, r, &resp) 148 | return 149 | } 150 | resp := Resp{ 151 | Code: http.StatusOK, 152 | Msg: "success", 153 | } 154 | respond(w, r, &resp) 155 | } 156 | -------------------------------------------------------------------------------- /pkg/http/helper.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | func respond(w http.ResponseWriter, r *http.Request, resp *Resp) { 10 | w.Header().Set("Content-Type", "application/json") 11 | w.WriteHeader(resp.Code) 12 | bts, err := json.Marshal(resp) 13 | if err != nil { 14 | fmt.Println(fmt.Errorf("marshalling response to json failed, err: %v", err)) 15 | return 16 | } 17 | _, err = w.Write(bts) 18 | if err != nil { 19 | fmt.Println(fmt.Errorf("writing response to response-writer failed, err: %v", err)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/http/http_test.go: -------------------------------------------------------------------------------- 1 | package http_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "gopkg.in/go-playground/assert.v1" 11 | ) 12 | 13 | func TestPetRoutes(t *testing.T) { 14 | tt := []struct { 15 | desc string 16 | method string 17 | uri string 18 | payload []byte 19 | headers map[string]string 20 | StatusCode int 21 | }{ 22 | { 23 | desc: "create pet", 24 | method: "POST", 25 | uri: "/pets/", 26 | payload: []byte(`{"category": "dog", "breed": "labour", "age": 2, "gender": "male", 27 | "colors": "black", "contact": {"owner": "bigshow", "phone": "123456789"}, "price": 10000}`), 28 | headers: nil, 29 | StatusCode: 200, 30 | }, 31 | { 32 | desc: "list pets", 33 | method: "GET", 34 | uri: "/pets?category=dog", 35 | payload: nil, 36 | headers: nil, 37 | StatusCode: 200, 38 | }, 39 | } 40 | 41 | for _, tc := range tt { 42 | t.Run(tc.desc, func(t *testing.T) { 43 | var payload io.Reader 44 | if tc.payload != nil { 45 | payload = bytes.NewBuffer(tc.payload) 46 | } 47 | req, err := http.NewRequest(tc.method, "localhost:6000"+tc.uri, payload) 48 | assert.NoError(t, err) 49 | 50 | for k, v := range tc.headers { 51 | req.Header.Set(k, v) 52 | } 53 | 54 | res, err := http.DefaultClient.Do(req) 55 | assert.NoError(t, err) 56 | 57 | assert.Equal(t, tc.StatusCode, res.StatusCode) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/http/middleware.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | 10 | "github.com/go-chi/chi" 11 | "github.com/go-playground/validator" 12 | ) 13 | 14 | func ValidateURLParam(params ...string) func(http.Handler) http.Handler { 15 | return func(next http.Handler) http.Handler { 16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | ctx := r.Context() 18 | for _, param := range params { 19 | paramVal := chi.URLParam(r, param) 20 | if paramVal == "" { 21 | w.WriteHeader(http.StatusBadRequest) 22 | w.Write([]byte(fmt.Sprintf("url param %s no provided", param))) 23 | return 24 | } 25 | ctx = context.WithValue(ctx, param, paramVal) 26 | } 27 | next.ServeHTTP(w, r.WithContext(ctx)) 28 | }) 29 | } 30 | } 31 | 32 | func ValidateQueryParam(dst interface{}) func(http.Handler) http.Handler { 33 | return func(next http.Handler) http.Handler { 34 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 35 | t := reflect.New(reflect.TypeOf(dst)).Interface() 36 | err := json.NewDecoder(r.Body).Decode(t) 37 | if err != nil { 38 | resp := Resp{ 39 | Code: http.StatusBadRequest, 40 | Msg: fmt.Sprintf("invalid query param. %s", err.Error()), 41 | } 42 | respond(w, r, &resp) 43 | return 44 | } 45 | err = validator.New().Struct(t) 46 | if err != nil { 47 | resp := Resp{ 48 | Code: http.StatusBadRequest, 49 | Msg: fmt.Sprintf("invalid query param. %s", err.Error()), 50 | } 51 | respond(w, r, &resp) 52 | return 53 | } 54 | ctx := context.WithValue(r.Context(), QUERY, t) 55 | next.ServeHTTP(w, r.WithContext(ctx)) 56 | }) 57 | } 58 | } 59 | 60 | func ValidateBody(dst interface{}) func(http.Handler) http.Handler { 61 | return func(next http.Handler) http.Handler { 62 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 | t := reflect.New(reflect.TypeOf(dst)).Interface() 64 | err := json.NewDecoder(r.Body).Decode(t) 65 | if err != nil { 66 | resp := Resp{ 67 | Code: http.StatusBadRequest, 68 | Msg: fmt.Sprintf("invalid request body. %s", err.Error()), 69 | } 70 | respond(w, r, &resp) 71 | return 72 | } 73 | err = validator.New().Struct(t) 74 | if err != nil { 75 | resp := Resp{ 76 | Code: http.StatusBadRequest, 77 | Msg: fmt.Sprintf("invalid request body. %s", err.Error()), 78 | } 79 | respond(w, r, &resp) 80 | return 81 | } 82 | ctx := context.WithValue(r.Context(), BODY, t) 83 | next.ServeHTTP(w, r.WithContext(ctx)) 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/http/models.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | type ContextKey string 4 | 5 | const ( 6 | QUERY = ContextKey("query") 7 | BODY = ContextKey("body") 8 | ) 9 | 10 | type Resp struct { 11 | Code int `json:"-"` 12 | Msg string `json:"message"` 13 | Data interface{} `json:"data"` 14 | } 15 | 16 | type ListPetsQuery struct { 17 | Category string `json:"category"` 18 | } 19 | -------------------------------------------------------------------------------- /pkg/http/route.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | 7 | "github.com/d-vignesh/getpets/pkg/domain" 8 | "github.com/go-chi/chi" 9 | redocmiddleware "github.com/go-openapi/runtime/middleware" 10 | ) 11 | 12 | func Routes(r chi.Router, h *Handler) { 13 | opts := redocmiddleware.RedocOpts{Path: "docs", SpecURL: "swagger.json"} 14 | docsHandler := redocmiddleware.Redoc(opts, nil) 15 | r.Handle("/docs", docsHandler) 16 | r.Get("/swagger.json", func(w http.ResponseWriter, r *http.Request) { 17 | spec, err := ioutil.ReadFile("docs/swagger.json") 18 | if err != nil { 19 | w.WriteHeader(http.StatusInternalServerError) 20 | w.Write([]byte(err.Error())) 21 | return 22 | } 23 | w.Write(spec) 24 | }) 25 | r.Route("/pets", func(r chi.Router) { 26 | r.With(ValidateQueryParam(ListPetsQuery{})).Get("/", h.ListPets) 27 | r.With(ValidateBody(domain.Pet{})).Post("/", h.AddPet) 28 | r.Route("/{id}", func(r chi.Router) { 29 | r.Use(ValidateURLParam("id")) 30 | r.Get("/", h.GetPet) 31 | r.Delete("/", h.DeletePet) 32 | }) 33 | }) 34 | } 35 | --------------------------------------------------------------------------------