From 36c56b96050e6a718a75a1559f553110fd767d8f Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Wed, 2 Feb 2022 09:10:57 +1030 Subject: [PATCH] Initial commit --- .gitignore | 2 + api/jsonVerify.go | 87 ++++++++++ api/petApiHandlers.go | 331 +++++++++++++++++++++++++++++++++++++ api/petApiHandlers_test.go | 76 +++++++++ api/routes.go | 25 +++ database/db.go | 35 ++++ database/pet.go | 158 ++++++++++++++++++ go.mod | 25 +++ go.sum | 199 ++++++++++++++++++++++ main.go | 29 ++++ models/pet.go | 36 ++++ util/downloadFile.go | 38 +++++ 12 files changed, 1041 insertions(+) create mode 100644 .gitignore create mode 100644 api/jsonVerify.go create mode 100644 api/petApiHandlers.go create mode 100644 api/petApiHandlers_test.go create mode 100644 api/routes.go create mode 100644 database/db.go create mode 100644 database/pet.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 models/pet.go create mode 100644 util/downloadFile.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c8e7a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/uploads/* +test* diff --git a/api/jsonVerify.go b/api/jsonVerify.go new file mode 100644 index 0000000..498303c --- /dev/null +++ b/api/jsonVerify.go @@ -0,0 +1,87 @@ +package api + +import ( + "math" + "reflect" +) + +// isIntegerType returns whether the type is an integer and if it's unsigned. +// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328 +func isIntegerType(t reflect.Type) (yes bool, unsigned bool) { + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + yes = true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + yes = true + unsigned = true + } + + return +} + +// isFloatType returns true if the type is a floating point. Note that this doesn't +// care about the value -- unmarshaling the number "0" gives a float, not an int. +// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319 +func isFloatType(t reflect.Type) (yes bool) { + switch t.Kind() { + case reflect.Float32, reflect.Float64: + yes = true + } + + return +} + +// CanConvert returns whether value v is convertible to type t. +// +// If t is a pointer and v is not nil, it checks if v is convertible to the type that +// t points to. +// Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags) +// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191 +func CanConvert(t reflect.Type, v reflect.Value) bool { + isPtr := t.Kind() == reflect.Ptr + isStruct := t.Kind() == reflect.Struct + dstType := t + + // Check if v is a nil value. + if !v.IsValid() || (v.CanAddr() && v.IsNil()) { + return isPtr + } + + // If the dst is a pointer, check if we can convert to the type it's pointing to. + if isPtr { + dstType = t.Elem() + isStruct = t.Elem().Kind() == reflect.Struct + } + + // If the dst is a struct, we should check its nested fields. + if isStruct { + return v.Kind() == reflect.Map + } + + if t.Kind() == reflect.Slice { + return v.Kind() == reflect.Slice + } + + if !v.Type().ConvertibleTo(dstType) { + return false + } + + // Handle converting to an integer type. + if dstInt, unsigned := isIntegerType(dstType); dstInt { + if isFloatType(v.Type()) { + f := v.Float() + + if math.Trunc(f) != f { + return false + } else if unsigned && f < 0 { + return false + } + } else if srcInt, _ := isIntegerType(v.Type()); srcInt { + if unsigned && v.Int() < 0 { + return false + } + } + } + + return true +} diff --git a/api/petApiHandlers.go b/api/petApiHandlers.go new file mode 100644 index 0000000..cf852bf --- /dev/null +++ b/api/petApiHandlers.go @@ -0,0 +1,331 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + + "git.tovijaeschke.xyz/tovi/JumboPetstore/database" + "git.tovijaeschke.xyz/tovi/JumboPetstore/models" + + schema "github.com/Kangaroux/go-map-schema" + "github.com/gorilla/mux" +) + +func deserialsePetJson(data []byte) (models.Pet, error) { + var ( + petData models.Pet = models.Pet{} + jsonStructureTest map[string]interface{} = make(map[string]interface{}) + jsonStructureTestResults *schema.CompareResults + err error + ) + + // Verify the JSON has the correct structure + json.Unmarshal(data, &jsonStructureTest) + jsonStructureTestResults, err = schema.CompareMapToStruct( + &petData, + jsonStructureTest, + &schema.CompareOpts{ + ConvertibleFunc: CanConvert, + TypeNameFunc: schema.DetailedTypeName, + }) + if err != nil { + return petData, err + } + + if len(jsonStructureTestResults.MismatchedFields) > 0 { + fmt.Println(jsonStructureTestResults.MismatchedFields) + return petData, errors.New("MismatchedFields found when deserializing data") + } + + if len(jsonStructureTestResults.MissingFields) > 0 { + fmt.Println(jsonStructureTestResults.MissingFields) + return petData, errors.New("MissingFields found when deserializing data") + } + + // Deserialize the JSON into the struct + err = json.Unmarshal(data, &petData) + return petData, err +} + +func genericJsonReturn(w http.ResponseWriter, code int, msg, typ string) { + var ( + responseJsonMap map[string]interface{} + responseJson []byte + err error + ) + + responseJsonMap = make(map[string]interface{}) + + w.WriteHeader(code) + responseJsonMap["code"] = code + responseJsonMap["type"] = typ + responseJsonMap["message"] = msg + responseJson, err = json.MarshalIndent(responseJsonMap, "", " ") + if err != nil { + log.Printf("Error occured creating response: %s\n", err.Error()) + } + w.Write(responseJson) +} + +func PetHandlerCreateUpdate(w http.ResponseWriter, r *http.Request) { + var ( + requestBody []byte + petData models.Pet + returnJson []byte + err error + ) + + log.Printf("Pet handler recieved %s request", r.Method) + + requestBody, err = ioutil.ReadAll(r.Body) + if err != nil { + log.Printf("Error encountered reading POST body: %s\n", err.Error()) + genericJsonReturn(w, 500, "An error occured", "unknown") + return + } + + petData, err = deserialsePetJson(requestBody) + if err != nil { + log.Printf("Invalid data provided to pet POST API: %s\n", err.Error()) + genericJsonReturn(w, 405, "Invalid data", "unknown") + return + } + + switch r.Method { + case "POST": + petData, err = database.CreatePet(petData) + if err != nil { + panic(err) + } + break + case "PUT": + petData, err = database.UpdatePet(petData) + if err != nil { + panic(err) + } + break + } + + returnJson, err = json.MarshalIndent(petData, "", " ") + if err != nil { + log.Printf("Error encountered when creating pet record: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) + w.Write(returnJson) +} + +func PetHandlerId(w http.ResponseWriter, r *http.Request) { + var ( + petData models.Pet + urlVars map[string]string + returnJson []byte + notFoundJson map[string]interface{} = make(map[string]interface{}) + id_str string + id int + ok bool + err error + ) + + urlVars = mux.Vars(r) + id_str, ok = urlVars["petId"] + if !ok { + log.Printf("Error encountered getting id\n") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + id, err = strconv.Atoi(id_str) + if err != nil { + log.Printf("Error encountered converting id to string: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + petData = database.GetPetById(id) + if petData.Id == 0 { + log.Printf("Could not find pet with id %d\n", id) + w.WriteHeader(http.StatusNotFound) + notFoundJson["code"] = 404 + notFoundJson["type"] = "unknown" + notFoundJson["message"] = "not found" + returnJson, err = json.MarshalIndent(notFoundJson, "", " ") + w.Write(returnJson) + return + } + + switch r.Method { + case "GET": + returnJson, err = json.MarshalIndent(petData, "", " ") + if err != nil { + log.Printf("Error encountered when creating pet record: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) + w.Write(returnJson) + break + case "POST": + err = r.ParseForm() + if err != nil { + log.Printf("Error encountered parsing form: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + if r.FormValue("name") != "" { + petData.Name = r.FormValue("name") + } + if r.FormValue("status") != "" { + petData.Status = r.FormValue("status") + } + + _, err = database.UpdatePet(petData) + if err != nil { + log.Printf("Error updating pet: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + break + case "DELETE": + log.Printf("Marking pet %d as deleted\n", id) + + database.DeletePet(petData) + + w.WriteHeader(http.StatusOK) + notFoundJson["code"] = 200 + notFoundJson["type"] = "unknown" + notFoundJson["message"] = id_str + returnJson, err = json.MarshalIndent(notFoundJson, "", " ") + w.Write(returnJson) + + break + } +} + +func PetHandlerFindByStatus(w http.ResponseWriter, r *http.Request) { + var ( + status string + petData []models.Pet + returnJson []byte + err error + ) + + status = r.URL.Query().Get("status") + if status != "available" && status != "pending" && status != "sold" { + log.Printf("Invalid status provided to /pet/findByStatus") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + petData, err = database.GetPetsByStatus(status) + if err != nil { + log.Printf("An error occured in GetPetsByStatus") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + returnJson, err = json.MarshalIndent(petData, "", " ") + if err != nil { + log.Printf("An error occured while serializing pet data to JSON") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + w.Write(returnJson) + +} + +func PetHandlerUploadImage(w http.ResponseWriter, r *http.Request) { + var ( + urlVars map[string]string + file multipart.File + handler *multipart.FileHeader + tempFile *os.File + nameSplit []string + fileBytes []byte + id_str string + id int + ok bool + err error + ) + + urlVars = mux.Vars(r) + id_str, ok = urlVars["petId"] + if !ok { + log.Printf("Error encountered getting id\n") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + id, err = strconv.Atoi(id_str) + if err != nil { + log.Printf("Error encountered converting id to string: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + + fmt.Println(id) + + // Upload 10Mb files + r.ParseMultipartForm(10 << 20) + file, handler, err = r.FormFile("file") + if err != nil { + log.Printf("Error retrieving file: %s\n", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - An error has occured\n")) + return + } + defer file.Close() + + nameSplit = strings.Split(handler.Filename, ".") + + tempFile, err = ioutil.TempFile("./uploads", "upload-*."+nameSplit[len(nameSplit)-1]) + if err != nil { + fmt.Println(err) + return + } + defer tempFile.Close() + + fileBytes, err = ioutil.ReadAll(file) + if err != nil { + fmt.Println(err) + return + } + + tempFile.Write(fileBytes) + + err = database.AddPhotoToPet(id, filepath.Base(tempFile.Name())) + if err != nil { + fmt.Println(err) + return + } + + fmt.Fprintf(w, "Successfully Uploaded File\n") +} diff --git a/api/petApiHandlers_test.go b/api/petApiHandlers_test.go new file mode 100644 index 0000000..19ea249 --- /dev/null +++ b/api/petApiHandlers_test.go @@ -0,0 +1,76 @@ +package api + +import ( + "testing" +) + +func TestDeserializePetJson(t *testing.T) { + jsonData := ` +{ + "id": 5, + "category": { + "id": 6, + "name": "test_category" + }, + "name": "doggie", + "photoUrls": [ + "photo_url_test" + ], + "tags": [ + { + "id": 0, + "name": "test_tags" + } + ], + "status": "available" +} +` + + petData, err := deserialsePetJson(jsonData) + if err != nil { + t.Error(err.Error()) + return + } + + if petData.Id != 5 { + t.Errorf("Id was incorrect, got: %d, want: %d", petData.Id, 5) + } + + if petData.Name != "doggie" { + t.Errorf("Name was incorrect, got: %s, want: %s", petData.Name, "doggie") + } + + if petData.Status != "available" { + t.Errorf("Status was incorrect, got: %s, want: %s", petData.Status, "doggie") + } + + if petData.Category.Id != 6 { + t.Errorf("Category Id was incorrect, got: %d, want: %d", petData.Category.Id, 6) + } + + if petData.Category.Name != "test_category" { + t.Errorf("Category Name was incorrect, got: %s, want: %s", petData.Category.Name, "test_category") + } + + if len(petData.PhotoUrls) != 1 && petData.PhotoUrls[0] != "photo_url_test" { + t.Errorf("PhotoUrls was incorrect, length: %d", len(petData.PhotoUrls)) + } + + if len(petData.Tags) != 1 && petData.Tags[0].Id != 0 && petData.Tags[0].Name != "test_tags" { + t.Errorf("Tags was incorrect, length: %d", len(petData.Tags)) + } +} + +func TestDeserializePetJsonFail(t *testing.T) { + jsonData := ` +{ + "invalid": "data" +} +` + + _, err := deserialsePetJson(jsonData) + if err == nil { + t.Error("Invalid data was provided, expected error was not thrown.") + t.Error(err.Error()) + } +} diff --git a/api/routes.go b/api/routes.go new file mode 100644 index 0000000..8721c27 --- /dev/null +++ b/api/routes.go @@ -0,0 +1,25 @@ +package api + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +func InitApiEndpoints() *mux.Router { + var ( + router *mux.Router + ) + + router = mux.NewRouter() + + // Define routes for pet api + router.HandleFunc("/pet", PetHandlerCreateUpdate).Methods("POST", "PUT") + router.HandleFunc("/pet/findByStatus", PetHandlerFindByStatus).Methods("GET") + router.HandleFunc("/pet/{petId}", PetHandlerId).Methods("GET", "POST", "DELETE") + router.HandleFunc("/pet/{petId}/uploadImage", PetHandlerUploadImage).Methods("POST") + + router.PathPrefix("/images/").Handler(http.FileServer(http.Dir("./uploads/"))) + + return router +} diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..25e6f4c --- /dev/null +++ b/database/db.go @@ -0,0 +1,35 @@ +package database + +import ( + "log" + + "git.tovijaeschke.xyz/tovi/JumboPetstore/models" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +const ( + dbUrl = "postgres://postgres:@localhost:5432/petstore" +) + +var ( + DB *gorm.DB +) + +func Init() { + var ( + err error + ) + + DB, err = gorm.Open(postgres.Open(dbUrl), &gorm.Config{}) + + if err != nil { + log.Fatalln(err) + } + + DB.AutoMigrate(&models.PetCategory{}) + DB.AutoMigrate(&models.PetTag{}) + DB.AutoMigrate(&models.PetPhoto{}) + DB.AutoMigrate(&models.Pet{}) +} diff --git a/database/pet.go b/database/pet.go new file mode 100644 index 0000000..e027e43 --- /dev/null +++ b/database/pet.go @@ -0,0 +1,158 @@ +package database + +import ( + "fmt" + + "git.tovijaeschke.xyz/tovi/JumboPetstore/models" + "git.tovijaeschke.xyz/tovi/JumboPetstore/util" + "gorm.io/gorm/clause" +) + +func CreatePet(petData models.Pet) (models.Pet, error) { + var ( + photoUrl string + fileName string + err error + ) + + err = DB.Omit("PhotoUrlJson").Create(&petData).Error + if err != nil { + return petData, err + } + + for _, photoUrl = range petData.PhotoUrlJson { + fileName, err = util.DownloadFile(photoUrl) + if err != nil { + return petData, err + } + + petData.PhotoUrls = append(petData.PhotoUrls, models.PetPhoto{ + PetId: petData.Id, + FileName: fileName, + }) + } + + petData, err = UpdatePet(petData) + if err != nil { + return petData, err + } + + return petData, err +} + +func UpdatePet(petData models.Pet) (models.Pet, error) { + var ( + photoUrl string + err error + ) + + err = DB.Model(&models.Pet{}). + Where("id = ?", petData.Id). + Omit("PhotoUrlJson"). + Updates(&petData). + Error + if err != nil { + return petData, err + } + + // Delete existing pet photos + err = DB.Where("pet_id = ?", petData.ID). + Delete(&models.PetPhoto{}). + Error + if err != nil { + return petData, err + } + + // Update pet photos from PUT data + for _, photoUrl = range petData.PhotoUrlJson { + err = DB.Create(&models.PetPhoto{ + PetId: petData.Id, + Url: photoUrl, + }).Error + if err != nil { + return petData, err + } + } + + return petData, err +} + +func GetPetById(id int) models.Pet { + var ( + petData models.Pet + petPhoto models.PetPhoto + photoUrls []string + ) + + DB.Preload(clause.Associations).First(&petData, "id = ?", id) + + for _, petPhoto = range petData.PhotoUrls { + photoUrls = append(photoUrls, "/images/"+petPhoto.FileName) + } + + petData.PhotoUrlJson = photoUrls + + fmt.Println(petData) + + return petData +} + +func DeletePet(petData models.Pet) { + DB.Where("pet_id = ?", petData.Id).Delete(&petData.Categories) + DB.Where("pet_id = ?", petData.Id).Delete(&petData.Tags) + DB.Where("pet_id = ?", petData.Id).Delete(&petData.PhotoUrls) + DB.Delete(&petData) +} + +func GetPetsByStatus(status string) ([]models.Pet, error) { + var ( + petDatas []models.Pet + petData models.Pet + petPhoto models.PetPhoto + photoUrls []string + i int + err error + ) + + err = DB.Where("status = ?", status).Find(&petDatas).Error + if err != nil { + return petDatas, err + } + + for i, petData = range petDatas { + err = DB.Where("pet_id = ?", petData.Id).Find(&petDatas[i].Tags).Error + if err != nil { + return petDatas, err + } + + err = DB.Where("pet_id = ?", petData.Id).Find(&petDatas[i].PhotoUrls).Error + if err != nil { + return petDatas, err + } + + for _, petPhoto = range petDatas[i].PhotoUrls { + photoUrls = append(photoUrls, "/images/"+petPhoto.FileName) + } + + petDatas[i].PhotoUrlJson = photoUrls + } + + return petDatas, err +} + +func AddPhotoToPet(id int, fileName string) error { + var ( + petData models.Pet + err error + ) + + petData = GetPetById(id) + + petData.PhotoUrls = append(petData.PhotoUrls, models.PetPhoto{ + PetId: id, + FileName: fileName, + }) + _, err = UpdatePet(petData) + + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0233833 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module git.tovijaeschke.xyz/tovi/JumboPetstore + +go 1.17 + +require ( + github.com/Kangaroux/go-map-schema v0.6.1 + github.com/gorilla/mux v1.8.0 + gorm.io/driver/postgres v1.2.3 + gorm.io/gorm v1.22.5 +) + +require ( + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.10.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.2.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.9.1 // indirect + github.com/jackc/pgx/v4 v4.14.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.4 // indirect + golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3345667 --- /dev/null +++ b/go.sum @@ -0,0 +1,199 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Kangaroux/go-map-schema v0.6.1 h1:jXpOzi7kNFC6M8QSvJuI7xeDxObBrVHwA3D6vSrxuG4= +github.com/Kangaroux/go-map-schema v0.6.1/go.mod h1:56jN+6h/N8Pmn5D+JL9gREOvZTlVEAvXtXyLd/NRjh4= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0= +github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= +github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU= +github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= +gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= +gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU= +gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/main.go b/main.go new file mode 100644 index 0000000..dfc8c32 --- /dev/null +++ b/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "log" + "net/http" + + "git.tovijaeschke.xyz/tovi/JumboPetstore/api" + "git.tovijaeschke.xyz/tovi/JumboPetstore/database" + + "github.com/gorilla/mux" +) + +func main() { + var ( + router *mux.Router + ) + + log.Println("Initializing database and running AutoMigrate...") + + database.Init() + + log.Println("Initializing Petstore API...") + + // Initialize API endpoints + router = api.InitApiEndpoints() + + // Start and listen to requests + http.ListenAndServe(":8080", router) +} diff --git a/models/pet.go b/models/pet.go new file mode 100644 index 0000000..f6e8740 --- /dev/null +++ b/models/pet.go @@ -0,0 +1,36 @@ +package models + +import "gorm.io/gorm" + +type PetCategory struct { + gorm.Model `json:"-"` + Id int `json:"id" gorm:"primary_key"` + PetId int `json:"-"` + Name string `json:"name"` +} + +type PetTag struct { + gorm.Model `json:"-"` + Id int `json:"id" gorm:"primary_key"` + PetId int `json:"-"` + Name string `json:"name"` +} + +type PetPhoto struct { + gorm.Model `json:"-"` + Id int `gorm:"primary_key"` + PetId int `json:"-"` + Url string `gorm:"-"` + FileName string `json:"-"` +} + +type Pet struct { + gorm.Model `json:"-"` + Id int `json:"id" gorm:"primary_key"` + Name string `json:"name"` + Categories PetCategory `json:"category" gorm:"ForeignKey:PetId"` + PhotoUrlJson []string `json:"photoUrls" gorm:"-"` + PhotoUrls []PetPhoto `json:"-" gorm:"ForeignKey:PetId"` + Tags []PetTag `json:"tags" gorm:"ForeignKey:PetId"` + Status string `json:"status"` +} diff --git a/util/downloadFile.go b/util/downloadFile.go new file mode 100644 index 0000000..1554c36 --- /dev/null +++ b/util/downloadFile.go @@ -0,0 +1,38 @@ +package util + +import ( + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" +) + +func DownloadFile(url string) (string, error) { + var ( + nameSplit []string + resp *http.Response + tempFile *os.File + err error + ) + + // Used to get the file extension + // This could be improved with a mime-type lookup + nameSplit = strings.Split(url, ".") + + resp, err = http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + tempFile, err = ioutil.TempFile("./uploads", "upload-*."+nameSplit[len(nameSplit)-1]) + if err != nil { + return "", err + } + defer tempFile.Close() + + _, err = io.Copy(tempFile, resp.Body) + return filepath.Base(tempFile.Name()), err +}