-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Sergey Kozlovskiy
committed
Aug 7, 2023
1 parent
5e07f44
commit 0129a08
Showing
14 changed files
with
1,335 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package main | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/gosimple/slug" | ||
"github.com/xNok/go-rest-demo/pkg/recipes" | ||
) | ||
|
||
func main() { | ||
// Create Gin router | ||
router := gin.Default() | ||
|
||
// Instantiate recipe Handler and provide a data store | ||
store := recipes.NewMemStore() | ||
recipesHandler := NewRecipesHandler(store) | ||
|
||
// Register Routes | ||
router.GET("/", homePage) | ||
router.GET("/recipes", recipesHandler.ListRecipes) | ||
router.POST("/recipes", recipesHandler.CreateRecipe) | ||
router.GET("/recipes/:id", recipesHandler.GetRecipe) | ||
router.PUT("/recipes/:id", recipesHandler.UpdateRecipe) | ||
router.DELETE("/recipes/:id", recipesHandler.DeleteRecipe) | ||
|
||
// Start the server | ||
router.Run() | ||
} | ||
|
||
func homePage(c *gin.Context) { | ||
c.String(http.StatusOK, "This is my home page") | ||
} | ||
|
||
type RecipesHandler struct { | ||
store recipeStore | ||
} | ||
|
||
func NewRecipesHandler(s recipeStore) *RecipesHandler { | ||
return &RecipesHandler{ | ||
store: s, | ||
} | ||
} | ||
|
||
type recipeStore interface { | ||
Add(name string, recipe recipes.Recipe) error | ||
Get(name string) (recipes.Recipe, error) | ||
List() (map[string]recipes.Recipe, error) | ||
Update(name string, recipe recipes.Recipe) error | ||
Remove(name string) error | ||
} | ||
|
||
func (h RecipesHandler) CreateRecipe(c *gin.Context) { | ||
// Get request body and convert it to recipes.Recipe | ||
var recipe recipes.Recipe | ||
if err := c.ShouldBindJSON(&recipe); err != nil { | ||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
return | ||
} | ||
|
||
// create a url friendly name | ||
id := slug.Make(recipe.Name) | ||
|
||
// add to the store | ||
h.store.Add(id, recipe) | ||
|
||
// return success payload | ||
c.JSON(http.StatusOK, gin.H{"status": "success"}) | ||
} | ||
|
||
func (h RecipesHandler) ListRecipes(c *gin.Context) { | ||
r, err := h.store.List() | ||
if err != nil { | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
} | ||
|
||
c.JSON(200, r) | ||
} | ||
|
||
func (h RecipesHandler) GetRecipe(c *gin.Context) { | ||
id := c.Param("id") | ||
|
||
recipe, err := h.store.Get(id) | ||
if err != nil { | ||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) | ||
} | ||
|
||
c.JSON(200, recipe) | ||
} | ||
|
||
func (h RecipesHandler) UpdateRecipe(c *gin.Context) { | ||
// Get request body and convert it to recipes.Recipe | ||
var recipe recipes.Recipe | ||
if err := c.ShouldBindJSON(&recipe); err != nil { | ||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
return | ||
} | ||
|
||
id := c.Param("id") | ||
|
||
err := h.store.Update(id, recipe) | ||
if err != nil { | ||
if err == recipes.NotFoundErr { | ||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) | ||
return | ||
} | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
return | ||
} | ||
|
||
// return success payload | ||
c.JSON(http.StatusOK, gin.H{"status": "success"}) | ||
} | ||
|
||
func (h RecipesHandler) DeleteRecipe(c *gin.Context) { | ||
id := c.Param("id") | ||
|
||
err := h.store.Remove(id) | ||
if err != nil { | ||
if err == recipes.NotFoundErr { | ||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) | ||
return | ||
} | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
return | ||
} | ||
|
||
// return success payload | ||
c.JSON(http.StatusOK, gin.H{"status": "success"}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
### | ||
GET http://localhost:8080/ | ||
|
||
### | ||
POST http://localhost:8080/recipes | ||
Content-Type: application/json | ||
|
||
{ | ||
"name": "Ham and cheese toasties", | ||
"ingredients": [ | ||
{ | ||
"name": "bread" | ||
},{ | ||
"name": "ham" | ||
},{ | ||
"name": "cheese" | ||
} | ||
] | ||
} | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 200, "Response status is not 200"); | ||
}); | ||
%} | ||
|
||
### | ||
GET http://localhost:8080/recipes | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 200, "Response status is not 200"); | ||
client.assert(JSON.stringify(response.body) === "{\"ham-and-cheese-toasties\":{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"}]}}", "Body match expected response") | ||
}); | ||
%} | ||
|
||
### | ||
GET http://localhost:8080/recipes/ham-and-cheese-toasties | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 200, "Response status is not 200"); | ||
client.assert(JSON.stringify(response.body) === "{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"}]}", "Body match expected response") | ||
}); | ||
%} | ||
|
||
### | ||
PUT http://localhost:8080/recipes/ham-and-cheese-toasties | ||
Content-Type: application/json | ||
|
||
{ | ||
"name": "Ham and cheese toasties", | ||
"ingredients": [ | ||
{ | ||
"name": "bread" | ||
},{ | ||
"name": "ham" | ||
},{ | ||
"name": "cheese" | ||
},{ | ||
"name": "butter" | ||
} | ||
] | ||
} | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 200, "Response status is not 200"); | ||
}); | ||
%} | ||
|
||
### | ||
GET http://localhost:8080/recipes/ham-and-cheese-toasties | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 200, "Response status is not 200"); | ||
client.assert(JSON.stringify(response.body) === "{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"},{\"name\":\"butter\"}]}", "Body match expected response") | ||
|
||
}); | ||
%} | ||
|
||
### | ||
DELETE http://localhost:8080/recipes/ham-and-cheese-toasties | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 200, "Response status is not 200"); | ||
}); | ||
%} | ||
|
||
### | ||
GET http://localhost:8080/recipes/ham-and-cheese-toasties | ||
|
||
> {% | ||
client.test("Request executed successfully", function() { | ||
client.assert(response.status === 404, "Response status is not 404"); | ||
}); | ||
%} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/gorilla/mux" | ||
"github.com/gosimple/slug" | ||
"github.com/xNok/go-rest-demo/pkg/recipes" | ||
) | ||
|
||
func main() { | ||
// create the Store and Recipe Handler | ||
store := recipes.NewMemStore() | ||
recipesHandler := NewRecipesHandler(store) | ||
home := homeHandler{} | ||
|
||
router := mux.NewRouter() | ||
|
||
router.HandleFunc("/", home.ServeHTTP) | ||
router.HandleFunc("/recipes", recipesHandler.ListRecipes).Methods("GET") | ||
router.HandleFunc("/recipes", recipesHandler.CreateRecipe).Methods("POST") | ||
router.HandleFunc("/recipes/{id}", recipesHandler.GetRecipe).Methods("GET") | ||
router.HandleFunc("/recipes/{id}", recipesHandler.UpdateRecipe).Methods("PUT") | ||
router.HandleFunc("/recipes/{id}", recipesHandler.DeleteRecipe).Methods("DELETE") | ||
|
||
http.ListenAndServe(":8010", router) | ||
} | ||
|
||
func InternalServerErrorHandler(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
w.Write([]byte("500 Internal Server Error")) | ||
} | ||
|
||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusNotFound) | ||
w.Write([]byte("404 Not Found")) | ||
} | ||
|
||
type RecipesHandler struct { | ||
store recipeStore | ||
} | ||
|
||
func NewRecipesHandler(s recipeStore) *RecipesHandler { | ||
return &RecipesHandler{ | ||
store: s, | ||
} | ||
} | ||
|
||
type recipeStore interface { | ||
Add(name string, recipe recipes.Recipe) error | ||
Get(name string) (recipes.Recipe, error) | ||
List() (map[string]recipes.Recipe, error) | ||
Update(name string, recipe recipes.Recipe) error | ||
Remove(name string) error | ||
} | ||
|
||
func (h *RecipesHandler) CreateRecipe(w http.ResponseWriter, r *http.Request) { | ||
// Recipe object that will be populated from json payload | ||
var recipe recipes.Recipe | ||
|
||
if err := json.NewDecoder(r.Body).Decode(&recipe); err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
resourceID := slug.Make(recipe.Name) | ||
|
||
if err := h.store.Add(resourceID, recipe); err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
} | ||
|
||
func (h *RecipesHandler) ListRecipes(w http.ResponseWriter, r *http.Request) { | ||
recipes, err := h.store.List() | ||
|
||
jsonBytes, err := json.Marshal(recipes) | ||
if err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
w.Write(jsonBytes) | ||
} | ||
|
||
func (h *RecipesHandler) GetRecipe(w http.ResponseWriter, r *http.Request) { | ||
id := mux.Vars(r)["id"] | ||
|
||
recipe, err := h.store.Get(id) | ||
if err != nil { | ||
if err == recipes.NotFoundErr { | ||
NotFoundHandler(w, r) | ||
return | ||
} | ||
|
||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
jsonBytes, err := json.Marshal(recipe) | ||
if err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
w.Write(jsonBytes) | ||
} | ||
|
||
func (h *RecipesHandler) UpdateRecipe(w http.ResponseWriter, r *http.Request) { | ||
id := mux.Vars(r)["id"] | ||
|
||
// Recipe object that will be populated from json payload | ||
var recipe recipes.Recipe | ||
if err := json.NewDecoder(r.Body).Decode(&recipe); err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
if err := h.store.Update(id, recipe); err != nil { | ||
if err == recipes.NotFoundErr { | ||
NotFoundHandler(w, r) | ||
return | ||
} | ||
|
||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
jsonBytes, err := json.Marshal(recipe) | ||
if err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
w.Write(jsonBytes) | ||
} | ||
|
||
func (h *RecipesHandler) DeleteRecipe(w http.ResponseWriter, r *http.Request) { | ||
id := mux.Vars(r)["id"] | ||
|
||
if err := h.store.Remove(id); err != nil { | ||
InternalServerErrorHandler(w, r) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
} | ||
|
||
type homeHandler struct{} | ||
|
||
func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte("This is my home page")) | ||
} |
Oops, something went wrong.