Skip to content

Commit

Permalink
Add go-rest-demo
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Kozlovskiy committed Aug 7, 2023
1 parent 5e07f44 commit 0129a08
Show file tree
Hide file tree
Showing 14 changed files with 1,335 additions and 0 deletions.
131 changes: 131 additions & 0 deletions go-rest-demo/cmd/gin/main.go
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"})

}
99 changes: 99 additions & 0 deletions go-rest-demo/cmd/gin/main_test.http
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");
});
%}
159 changes: 159 additions & 0 deletions go-rest-demo/cmd/gorilla/main.go
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"))
}
Loading

0 comments on commit 0129a08

Please sign in to comment.