From 496cfab1e8867d5a8ff54402b848a6a38d03b1b0 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Thu, 14 Nov 2019 13:39:21 -0500 Subject: Fix simplify APIResponse struct using interface{} Combines APIResponseItem and APIResponseList into one struct. Hopefully this allows for the combination of some response code (a function that sends a APIResponse that can be used by each http method) --- backend/main.go | 92 +++++++++++++++++++++----------------------------------- backend/todo.txt | 3 +- 2 files changed, 36 insertions(+), 59 deletions(-) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 57e6ab2..96bb824 100644 --- a/backend/main.go +++ b/backend/main.go @@ -10,27 +10,16 @@ import _ "github.com/lib/pq" import "database/sql" import "encoding/json" -type APIError struct { +type APIStatus struct { Code int Msg string } -type APIDataIds struct { - Ids interface{} +type APIResponse struct { + Status APIStatus + Data interface{} } -type APIDataRecipe struct { - Recipe interface{} -} - -type APIResponseList struct { - Status APIError - Data []APIDataIds -} - -type APIResponseItem struct { - Status APIError - Data []APIDataRecipe } func RecipeList(w http.ResponseWriter, r *http.Request) { @@ -48,11 +37,10 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { } } - resp := APIResponseList{ - Status: APIError{Code: 200, Msg: "Successful Request"}, - Data: make([]APIDataIds, 0), + resp := APIResponse{ + Status: APIStatus{Code: 200, Msg: "Successful Request"}, + Data: ids, } - resp.Data = append(resp.Data, APIDataIds{Ids: ids}) w.Header().Set("Content-Type", "application/json; charset=UTF-8") @@ -80,11 +68,10 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnprocessableEntity) w.Header().Set("Content-Type", "application/json; charset=UTF-8") - resp := APIResponseItem{ - Status: APIError{ + resp := APIResponse{ + Status: APIStatus{ Code: http.StatusUnprocessableEntity, Msg: "Invalid Recipe"}, - Data: make([]APIDataRecipe, 0), } err := json.NewEncoder(w).Encode(resp) @@ -97,14 +84,12 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { err = AddRecipeDB(recipe, db) if err != nil { fmt.Println(err) - resp := APIResponseItem{ - Status: APIError{Code: http.StatusBadRequest, + resp := APIResponse{ + Status: APIStatus{Code: http.StatusBadRequest, Msg: "Recipe could not be added"}, - Data: make([]APIDataRecipe, 0), + Data: recipe, } - resp.Data = append(resp.Data, APIDataRecipe{recipe}) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusBadRequest) @@ -114,14 +99,12 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { return } - resp := APIResponseItem{ - Status: APIError{Code: http.StatusCreated, + resp := APIResponse{ + Status: APIStatus{Code: http.StatusCreated, Msg: "Recipe added successfully"}, - Data: make([]APIDataRecipe, 0), + Data: recipe, } - resp.Data = append(resp.Data, APIDataRecipe{recipe}) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(resp); err != nil { @@ -131,8 +114,8 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { return } - resp := APIResponseItem{ - Status: APIError{Code: http.StatusMethodNotAllowed, + resp := APIResponse{ + Status: APIStatus{Code: http.StatusMethodNotAllowed, Msg: "Invalid method"}, Data: nil, } @@ -164,13 +147,12 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { msg = "Successful" } - resp := APIResponseItem{ - Status: APIError{Code: status, Msg: msg}, - Data: make([]APIDataRecipe, 0), + resp := APIResponse{ + Status: APIStatus{Code: status, Msg: msg}, } if status == http.StatusOK { - resp.Data = append(resp.Data, APIDataRecipe{recipe}) + resp.Data = recipe } w.Header().Set("Content-Type", "application/json; charset=UTF-8") @@ -192,8 +174,8 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { } else { status = http.StatusConflict } - resp := APIResponseItem{ - Status: APIError{Code: status, Msg: "Cannot add to specific resource"}, + resp := APIResponse{ + Status: APIStatus{Code: status, Msg: "Cannot add to specific resource"}, Data: nil, } @@ -222,11 +204,10 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { fmt.Println(err) w.WriteHeader(http.StatusUnprocessableEntity) w.Header().Set("Content-Type", "application/json; charset=UTF-8") - resp := APIResponseItem{ - Status: APIError{ + resp := APIResponse{ + Status: APIStatus{ Code: http.StatusUnprocessableEntity, Msg: "Invalid Recipe"}, - Data: make([]APIDataRecipe, 0), } if err := json.NewEncoder(w).Encode(resp); err != nil { panic(err) @@ -239,14 +220,12 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { err = UpdateRecipeDB(recipe, db) if err != nil { fmt.Println(err) - resp := APIResponseItem{ - Status: APIError{Code: http.StatusBadRequest, + resp := APIResponse{ + Status: APIStatus{Code: http.StatusBadRequest, Msg: "Recipe could not be updated"}, - Data: make([]APIDataRecipe, 0), + Data: recipe, } - resp.Data = append(resp.Data, APIDataRecipe{recipe}) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusBadRequest) @@ -256,14 +235,12 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { return } - resp := APIResponseItem{ - Status: APIError{Code: http.StatusCreated, + resp := APIResponse{ + Status: APIStatus{Code: http.StatusCreated, Msg: "Recipe added successfully"}, - Data: make([]APIDataRecipe, 0), + Data: recipe, } - resp.Data = append(resp.Data, APIDataRecipe{recipe}) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(resp); err != nil { @@ -289,9 +266,8 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { msg = "Recipe Deleted Successfully" } - resp := APIResponseItem{ - Status: APIError{Code: status, Msg: msg}, - Data: make([]APIDataRecipe, 0), + resp := APIResponse{ + Status: APIStatus{Code: status, Msg: msg}, } w.Header().Set("Content-Type", @@ -304,8 +280,8 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { return } - resp := APIResponseItem{ - Status: APIError{Code: http.StatusMethodNotAllowed, + resp := APIResponse{ + Status: APIStatus{Code: http.StatusMethodNotAllowed, Msg: "Invalid method"}, Data: nil, } diff --git a/backend/todo.txt b/backend/todo.txt index cb074a4..b2d928b 100644 --- a/backend/todo.txt +++ b/backend/todo.txt @@ -1 +1,2 @@ -Add Error responses for incorrect methods +Refactor Response creation +Handle PSV parsing (rm empty string at end, or rm last pipe when creating item) -- cgit v1.1 From 7f253d927fc9a0469791a5d4684904acb24c00c5 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Fri, 22 Nov 2019 11:00:55 -0500 Subject: Add readme for backend --- backend/readme.adoc | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 backend/readme.adoc (limited to 'backend') diff --git a/backend/readme.adoc b/backend/readme.adoc new file mode 100644 index 0000000..0dec0b2 --- /dev/null +++ b/backend/readme.adoc @@ -0,0 +1,197 @@ +Backend API +=========== +Tucker Evans +v1.0, November 22, 2019 + +This REST API allows you to access recipe information in our database with +simple HTTP requests. There is currently no authentication/authorization of +clients. It return recipes in JSON format together with some status information +about the request. + +JSON format +----------- +The current implementation expects (and returns) recipes in the form: + +.Recipe JSON +[source,json] +---- +{ + "Id": 0, + "Title": "Recipe Title", + "Desc": "Recipe Description", + "Photos": [ + "photo_url_1", + "photo_url_2" + ], + "Serving_size": 0, + "Cook_time": 0, + "Rating": 0, + "Num_cooked": 0, + "Keywords": [ + "keyword 1", + "keyword 2", + "keyword 3" + ], + "Ingredients": [ + { + "Name": "Ingredient 1 Name", + "Amount": 1.0, + "Unit": "Ingredient Units" + }, + ], + "Steps": [ + { + "Num": 0, + "Desc": "Step Instructions/Description", + "Time": 0 + } + ] +} + +---- +[NOTE] +`"Id"` is not required for a POST request, and will be ignored. + +[IMPORTANT] +Keywords and Photo URLs are currently stored as pipe separated values, the +parsing of which is not complete and as such there is a extra empty string +(`""`) is appended to these lists in the response (it is not required in +requests) + +.Response JSON +[source,json] +---- +{ + "Status": { + "Code": 200, + "Msg": "Successful" + }, + "Data": "" +} +---- +[NOTE] +Data will either be a Recipe object or a list of recipe ids (null is also a +valid value). + +Status Codes +~~~~~~~~~~~~ +Status codes are based on https://httpstatuses.com/[HTTP status codes]. + +.Currently Used +- 200 OK +- 201 Created +- 400 Bad Request +- 404 Not Found +- 405 Method Not Allowed +- 409 Conflict +- 422 Unprocessable Entity +- _500 Internal Server Error_ (not yet implemented) + +The messages included in the status section are meant to be human readable +descriptions of any error. + +Usage +----- +This api is currently availiable with a base URL of +http://api.recipebuddy.xyz:8888. + +CRUD Interface +~~~~~~~~~~~~~~ + +NOTE: Examples are run with a database that contains 1 recipe (you can see the + initial contents of this recipe in the read example). + +Create +^^^^^^ +Creating a recipe is done by sending a `POST` HTTP request to the location +http://api.recipebuddy.xyz:8888/recipes[`/recipes`], with a body containing a +recipe object in JSON form: +[source,bash] +---- +$ curl -X POST api.recipebuddy.xyz:8888/recipes -d ' +{ + "Title":"Test Recipe 2", + "Desc":"This is a descripiton for the test recipe", + "Photos":["photo_url_1","photo_url_2"], + "Serving_size":0, + "Cook_time":60, + "Rating":5, + "Keywords":["keyword_1", "keyword_2","keyword_3"], + "Ingredients":[ + {"Name":"INGR 1","Amount":2.5,"Unit":"cups"}, + {"Name":"INGR 2","Amount":1,"Unit":"oz"} + ], + "Steps":[ + {"Num":1,"Desc":"Step 1: Do this first","Time":10} + ] +}' + +{"Status":{"Code":201,"Msg":"Recipe added successfully"},"Data":{"Id":2,"Title":"Test Recipe 2","Desc":"This is a descripiton for the test recipe","Photos":["photo_url_1","photo_url_2"],"Serving_size":0,"Cook_time":60,"Rating":5,"Num_cooked":0,"Keywords":["keyword_1","keyword_2","keyword_3"],"Ingredients":[{"Name":"INGR 1","Amount":2.5,"Unit":"cups"},{"Name":"INGR 2","Amount":1,"Unit":"oz"}],"Steps":[{"Num":1,"Desc":"Step 1: Do this first","Time":10}]}} +---- + +Read +^^^^ +Reading a recipe is done by sending a `GET` HTTP request to the location +http://api.recipebuddy.xyz:8888/recipes/0[`/recipes/{id}`], the HTTP body is ignored. + +[source,bash] +---- +$ curl -X GET api.recipebuddy.xyz:8888/recipes/1 + +{"Status":{"Code":200,"Msg":"Successful"},"Data":{"Id":1,"Title":"Test Recipe","Desc":"This is a descripiton for the test recipe","Photos":["photo_url_1","photo_url_2",""],"Serving_size":0,"Cook_time":60,"Rating":5,"Num_cooked":0,"Keywords":["keyword_1","keyword_2","keyword_3",""],"Ingredients":[{"Name":"INGR 1","Amount":2.5,"Unit":"cups"},{"Name":"INGR 2","Amount":1,"Unit":"oz"}],"Steps":[{"Num":1,"Desc":"Step 1: Do this first","Time":10}]}} +---- + +To access a list of all recipe ids in the database send a `GET` request to +http://api.recipebuddy.xyz:8888/recipes[`/recipes`], the HTTP body is ignored. +[source,bash] +---- +curl -X GET api.recipebuddy.xyz:8888/recipes +{"Status":{"Code":200,"Msg":"Successful Request"},"Data":[1,2]} +---- + +Update +^^^^^^ +Updating a recipe is done by sending a `PUT` HTTP request to +http://api.recipebuddy.xyz:8888/recipes/0[`recipes/{id}`], the HTTP body should be a +complete recipe in JSON form. +[source,bash] +---- +$ curl -X PUT localhost:8888/recipes/1 -d ' +{ + "Id": 1, + "Title":"Test Recipe 1", + "Desc":"This is a descripiton for the test recipe", + "Photos":[ "photo_url_1", "photo_url_2" ], + "Serving_size":0, + "Cook_time":60, + "Rating":5, + "Keywords":[ "keyword_1", "keyword_2", "keyword_3" ], + "Ingredients":[ + { "Name":"INGR 1", "Amount":2.5, "Unit":"cups" }, + { "Name":"INGR 2", "Amount":1, "Unit":"oz" } + ], + "Steps":[ + { "Num":0, "Desc":"Step 1: Do this first", "Time":10 } + ] +}' + +{"Status":{"Code":201,"Msg":"Recipe added successfully"},"Data":{"Id":1,"Title":"Test Recipe 1","Desc":"This is a descripiton for the test recipe","Photos":["photo_url_1","photo_url_2"],"Serving_size":0,"Cook_time":60,"Rating":5,"Num_cooked":0,"Keywords":["keyword_1","keyword_2","keyword_3"],"Ingredients":[{"Name":"INGR 1","Amount":2.5,"Unit":"cups"},{"Name":"INGR 2","Amount":1,"Unit":"oz"}],"Steps":[{"Num":0,"Desc":"Step 1: Do this first","Time":10}]}} + +---- +[WARNING] +Any recipe information not included in the request will be removed from the +database. + +Delete +^^^^^^ +Deleting a recipe is done by sending a `DELETE` HTTP request to +http://api.recipebuddy.xyz:8888/recipes/0[`recipes/{id}`], the HTTP body is ignored. +[source,bash] +---- +$ curl -X DELETE api.recipebuddy.xyz:8888/recipes/2 +{"Status":{"Code":200,"Msg":"Recipe Deleted Successfully"},"Data":null} +$ curl -X GET api.recipebuddy.xyz:8888/recipes +{"Status":{"Code":200,"Msg":"Successful Request"},"Data":[1]} +---- +[WARNING] +This is currently a *HARD* delete. -- cgit v1.1 From 0f7b0118c5a8290ed4d230a8e4bfc96daafea116 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Wed, 27 Nov 2019 14:09:05 -0500 Subject: Add consntructor for APIResponse --- backend/main.go | 8 ++++++++ backend/todo.txt | 1 + 2 files changed, 9 insertions(+) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 96bb824..4766de2 100644 --- a/backend/main.go +++ b/backend/main.go @@ -20,6 +20,14 @@ type APIResponse struct { Data interface{} } +func MakeAPIResponse(status int, msg string, data interface{}) *APIResponse { + return &APIResponse{ + Status: APIStatus{ + Code: status, + Msg: msg, + }, + Data: data, + } } func RecipeList(w http.ResponseWriter, r *http.Request) { diff --git a/backend/todo.txt b/backend/todo.txt index b2d928b..ae57ec0 100644 --- a/backend/todo.txt +++ b/backend/todo.txt @@ -1,2 +1,3 @@ Refactor Response creation Handle PSV parsing (rm empty string at end, or rm last pipe when creating item) +Fix Update (steps != 0) -- cgit v1.1 From b402ebba4f9c93a0040972748d66f9c4c6b02eb7 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Wed, 27 Nov 2019 22:45:26 -0500 Subject: Add CORS headers to backend responses Access-Control-Allow-Origin:* header added to API responses to allow angular to access API. Note: This allows all domains to access this API through browser javascript See for a description of CORS --- backend/main.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 4766de2..0a95fc8 100644 --- a/backend/main.go +++ b/backend/main.go @@ -31,6 +31,8 @@ func MakeAPIResponse(status int, msg string, data interface{}) *APIResponse { } func RecipeList(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") //Enable CORS + if r.Method == "GET" { var ids []int var id int @@ -137,6 +139,8 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { } func SingleRecipe(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") //Enable CORS + recipe_id, err := strconv.Atoi(r.URL.Path[len("/recipes/"):]) if err != nil { fmt.Println("Not a valid ID") -- cgit v1.1 From 510f3d83f0da041e90d358c796eb0c209b265f30 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Thu, 28 Nov 2019 07:35:18 -0500 Subject: Fix default case for http method check Moves default case (Method Not Allowed) into else clause so it is not necessary to return from the previous if's --- only for checking http method, error checking if statements can/should be returning where needed. --- backend/main.go | 54 ++++++++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 30 deletions(-) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 0a95fc8..0d22b80 100644 --- a/backend/main.go +++ b/backend/main.go @@ -58,7 +58,6 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { if err := json.NewEncoder(w).Encode(resp); err != nil { panic(err) } - return } else if r.Method == "POST" { var recipe *Recipe @@ -121,20 +120,19 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { panic(err) } - return - } - - resp := APIResponse{ - Status: APIStatus{Code: http.StatusMethodNotAllowed, - Msg: "Invalid method"}, - Data: nil, - } + } else { + resp := APIResponse{ + Status: APIStatus{Code: http.StatusMethodNotAllowed, + Msg: "Invalid method"}, + Data: nil, + } - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusMethodNotAllowed) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) + w.Header().Set("Content-Type", + "application/json; charset=UTF-8") + w.WriteHeader(http.StatusMethodNotAllowed) + if err := json.NewEncoder(w).Encode(resp); err != nil { + panic(err) + } } } @@ -173,7 +171,6 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { panic(err) } - return } else if r.Method == "POST" { var status int row := db.QueryRow(`SELECT id FROM recipes WHERE id = $1`, @@ -197,7 +194,6 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { if err := json.NewEncoder(w).Encode(resp); err != nil { panic(err) } - return } else if r.Method == "PUT" { var recipe *Recipe @@ -259,7 +255,6 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { panic(err) } - return } else if r.Method == "DELETE" { res, err := db.Exec(`DELETE FROM recipes where id = $1`, @@ -289,20 +284,19 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { panic(err) } - return - } - - resp := APIResponse{ - Status: APIStatus{Code: http.StatusMethodNotAllowed, - Msg: "Invalid method"}, - Data: nil, - } + } else { + resp := APIResponse{ + Status: APIStatus{Code: http.StatusMethodNotAllowed, + Msg: "Invalid method"}, + Data: nil, + } - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusMethodNotAllowed) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) + w.Header().Set("Content-Type", + "application/json; charset=UTF-8") + w.WriteHeader(http.StatusMethodNotAllowed) + if err := json.NewEncoder(w).Encode(resp); err != nil { + panic(err) + } } } -- cgit v1.1 From 4a7b855e85eb9e1dd04c7d9b0ede1f387bee31bc Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Thu, 28 Nov 2019 07:55:00 -0500 Subject: Fix error handling on database connect Now panics when can't connect to database and splits handling of errors from Open() and Ping() as separate issues. --- backend/main.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 0d22b80..9639b74 100644 --- a/backend/main.go +++ b/backend/main.go @@ -317,8 +317,13 @@ func main() { dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", DB_USER, DB_PASSWORD, DB_NAME) db, err = sql.Open("postgres", dbinfo) - if err != nil || db.Ping() != nil { - fmt.Println("Error connecting to database") + if err != nil { + panic(err) + } + + err = db.Ping() + if err != nil { + panic(err) } http.HandleFunc("/recipes", RecipeList) -- cgit v1.1 From 950e9f1bd29ffdc82740e9ac237089eb61a25661 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Thu, 28 Nov 2019 07:59:27 -0500 Subject: Add sendResponse function sendResponse function implements creating and sending a APIRespones so that this code will not be rewritten for every response situation i.e. the different http methods. --- backend/main.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 9639b74..5b0e8e7 100644 --- a/backend/main.go +++ b/backend/main.go @@ -30,9 +30,21 @@ func MakeAPIResponse(status int, msg string, data interface{}) *APIResponse { } } -func RecipeList(w http.ResponseWriter, r *http.Request) { +func sendResponse(w http.ResponseWriter, code int, msg string, data interface{}) { w.Header().Set("Access-Control-Allow-Origin", "*") //Enable CORS + w.WriteHeader(code) + + resp := MakeAPIResponse(code, msg, data) + + w.Header().Set("Content-Type", + "application/json; charset=UTF-8") + + if err := json.NewEncoder(w).Encode(resp); err != nil { + panic(err) + } +} +func RecipeList(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { var ids []int var id int -- cgit v1.1 From 9e1ab63bb039b72cdd2da98c3814366ec1851c7a Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Fri, 29 Nov 2019 17:58:41 -0500 Subject: Fix duplicated response code Replace response setup/send code w/ calls to sendResponse() --- backend/main.go | 175 +++++++++----------------------------------------------- 1 file changed, 26 insertions(+), 149 deletions(-) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 5b0e8e7..5ec4ce9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -59,17 +59,8 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { } } - resp := APIResponse{ - Status: APIStatus{Code: 200, Msg: "Successful Request"}, - Data: ids, - } + sendResponse(w, http.StatusOK, "Successful Request", ids) - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } } else if r.Method == "POST" { var recipe *Recipe @@ -86,65 +77,26 @@ func RecipeList(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &recipe) if err != nil { fmt.Println(err) - w.WriteHeader(http.StatusUnprocessableEntity) - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - resp := APIResponse{ - Status: APIStatus{ - Code: http.StatusUnprocessableEntity, - Msg: "Invalid Recipe"}, - } - - err := json.NewEncoder(w).Encode(resp) - if err != nil { - panic(err) - } + sendResponse(w, http.StatusUnprocessableEntity, + "Invalid Recipe", nil) return } err = AddRecipeDB(recipe, db) if err != nil { fmt.Println(err) - resp := APIResponse{ - Status: APIStatus{Code: http.StatusBadRequest, - Msg: "Recipe could not be added"}, - Data: recipe, - } - - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusBadRequest) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } + sendResponse(w, http.StatusBadRequest, + "Recipe could not be added", recipe) return } - resp := APIResponse{ - Status: APIStatus{Code: http.StatusCreated, - Msg: "Recipe added successfully"}, - Data: recipe, - } - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusCreated) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } + sendResponse(w, http.StatusCreated, "Recipe added successfully", + recipe) } else { - resp := APIResponse{ - Status: APIStatus{Code: http.StatusMethodNotAllowed, - Msg: "Invalid method"}, - Data: nil, - } + sendResponse(w, http.StatusMethodNotAllowed, "Invalid method", + nil) - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusMethodNotAllowed) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } } } @@ -157,30 +109,12 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { return } if r.Method == "GET" { - var status int - var msg string - recipe := RecipeFromId(recipe_id, db) if recipe == nil { - status = http.StatusNotFound - msg = "Recipe not Found" + sendResponse(w, http.StatusNotFound, "Recipe not Found", + nil) } else { - status = http.StatusOK - msg = "Successful" - } - - resp := APIResponse{ - Status: APIStatus{Code: status, Msg: msg}, - } - - if status == http.StatusOK { - resp.Data = recipe - } - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) + sendResponse(w, http.StatusOK, "Successful", recipe) } } else if r.Method == "POST" { @@ -195,17 +129,9 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { } else { status = http.StatusConflict } - resp := APIResponse{ - Status: APIStatus{Code: status, Msg: "Cannot add to specific resource"}, - Data: nil, - } - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(status) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } + sendResponse(w, status, "Cannot add to specific resource", + nil) } else if r.Method == "PUT" { var recipe *Recipe @@ -222,16 +148,8 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { err = json.Unmarshal(body, &recipe) if err != nil { fmt.Println(err) - w.WriteHeader(http.StatusUnprocessableEntity) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - resp := APIResponse{ - Status: APIStatus{ - Code: http.StatusUnprocessableEntity, - Msg: "Invalid Recipe"}, - } - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } + sendResponse(w, http.StatusUnprocessableEntity, + "Invalid Recipe", nil) return } @@ -240,32 +158,14 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { err = UpdateRecipeDB(recipe, db) if err != nil { fmt.Println(err) - resp := APIResponse{ - Status: APIStatus{Code: http.StatusBadRequest, - Msg: "Recipe could not be updated"}, - Data: recipe, - } + sendResponse(w, http.StatusBadRequest, + "Recipe could not be updated", recipe) - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusBadRequest) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } return } - resp := APIResponse{ - Status: APIStatus{Code: http.StatusCreated, - Msg: "Recipe added successfully"}, - Data: recipe, - } - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusCreated) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } + sendResponse(w, http.StatusCreated, "Recipe added successfully", + recipe) } else if r.Method == "DELETE" { @@ -275,40 +175,17 @@ func SingleRecipe(w http.ResponseWriter, r *http.Request) { panic(err) } - var status int - var msg string if ra, _ := res.RowsAffected(); ra == 0 { - status = http.StatusNotFound - msg = "Recipe Not found" + sendResponse(w, http.StatusNotFound, "Recipe Not found", + nil) } else { - status = http.StatusOK - msg = "Recipe Deleted Successfully" - } - - resp := APIResponse{ - Status: APIStatus{Code: status, Msg: msg}, - } - - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) + sendResponse(w, http.StatusOK, + "Recipe Deleted Successfully", nil) } } else { - resp := APIResponse{ - Status: APIStatus{Code: http.StatusMethodNotAllowed, - Msg: "Invalid method"}, - Data: nil, - } - - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - w.WriteHeader(http.StatusMethodNotAllowed) - if err := json.NewEncoder(w).Encode(resp); err != nil { - panic(err) - } + sendResponse(w, http.StatusMethodNotAllowed, "Invalid method", + nil) } } -- cgit v1.1 From 30ccc1f1c4a0ba64e16a0ef306f5b4c71df5760f Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Fri, 29 Nov 2019 19:46:43 -0500 Subject: Fix Content-Type header Content-Type header was not included in response because WriteHeader() was called before setting Content-Type. --- backend/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'backend') diff --git a/backend/main.go b/backend/main.go index 5ec4ce9..0bd5ebf 100644 --- a/backend/main.go +++ b/backend/main.go @@ -32,13 +32,13 @@ func MakeAPIResponse(status int, msg string, data interface{}) *APIResponse { func sendResponse(w http.ResponseWriter, code int, msg string, data interface{}) { w.Header().Set("Access-Control-Allow-Origin", "*") //Enable CORS + w.Header().Set("Content-Type", + "application/json; charset=UTF-8") + w.WriteHeader(code) resp := MakeAPIResponse(code, msg, data) - w.Header().Set("Content-Type", - "application/json; charset=UTF-8") - if err := json.NewEncoder(w).Encode(resp); err != nil { panic(err) } -- cgit v1.1 From 74855d03f44adf2e77d5d6af38e18e1cfe45e700 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Mon, 2 Dec 2019 10:52:47 -0500 Subject: Fix changes json encoding to match frontend --- backend/recipe.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'backend') diff --git a/backend/recipe.go b/backend/recipe.go index fed83ca..8589beb 100644 --- a/backend/recipe.go +++ b/backend/recipe.go @@ -5,29 +5,30 @@ import "errors" import "strings" type Ingredient struct { - Name string - Amount float64 - Unit string + Name string `json:"name"` + Amount float64 `json:"amount"` + Unit string `json:"units"` + Type string `json:"type"` } type Step struct { - Num int - Desc string - Time int + Num int `json:"index"` //Is this needed + Desc string `json:"instructions"` + Time int `json:"timer"` } type Recipe struct { - Id int - Title string - Desc string - Photos []string - Serving_size int - Cook_time int - Rating int - Num_cooked int - Keywords []string - Ingredients []Ingredient - Steps []Step + Id int `json:"id"` + Title string `json:"name"` + Desc string `json:"description"` + Photos []string `json:"photos"` + Serving_size int `json:"servingSize"` + Cook_time int `json:"cookTime"` + Rating int `json:"rating"` + Num_cooked int `json:"timesCooked"` + Keywords []string `json:"tags"` + Ingredients []Ingredient `json:"ingredients"` + Steps []Step `json:"steps"` } func MakeRecipe() *Recipe { -- cgit v1.1 From ba3fc1b1ec14035009cbfc642e04fbf1034ce735 Mon Sep 17 00:00:00 2001 From: Tucker Evans Date: Mon, 2 Dec 2019 16:40:03 -0500 Subject: Fix remove Num from Step struct The index of the array can be used instead. --- backend/recipe.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'backend') diff --git a/backend/recipe.go b/backend/recipe.go index 8589beb..a3191c3 100644 --- a/backend/recipe.go +++ b/backend/recipe.go @@ -1,7 +1,6 @@ package main import "database/sql" -import "errors" import "strings" type Ingredient struct { @@ -12,7 +11,6 @@ type Ingredient struct { } type Step struct { - Num int `json:"index"` //Is this needed Desc string `json:"instructions"` Time int `json:"timer"` } @@ -115,13 +113,12 @@ func RecipeFromId(id int, db *sql.DB) *Recipe { var num, timer int rows_steps, err := db.Query(`SELECT step, description, timer - FROM steps WHERE recipe_id = $1`, id) + FROM steps WHERE recipe_id = $1 ORDER BY step`, id) defer rows_steps.Close() if err == nil { for rows_steps.Next() { rows_steps.Scan(&num, &desc, &timer) step = Step{ - Num: num, Desc: desc, Time: timer, } @@ -190,7 +187,7 @@ func AddRecipeDB(r *Recipe, db *sql.DB) error { res, err := tx.Exec(`INSERT INTO steps (step, description, timer, recipe_id) VALUES ($1, $2, $3, $4)`, - step.Num, + i, step.Desc, step.Time, id, @@ -280,10 +277,6 @@ func UpdateRecipeDB(r *Recipe, db *sql.DB) error { } for i, step := range r.Steps { - if step.Num != 0 { - tx.Rollback() - return errors.New("invalid json Recipe") - } _, err := tx.Exec(`INSERT INTO steps (step, description, timer, recipe_id) VALUES ($1, $2, $3, $4) -- cgit v1.1