195 lines
4.5 KiB
Go
195 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"github.com/gosimple/slug"
|
|
"github.com/xNok/go-rest-demo/pkg/recipes"
|
|
)
|
|
|
|
var (
|
|
RecipeRe = regexp.MustCompile(`^/recipes/*$`)
|
|
RecipeReWithID = regexp.MustCompile(`^/recipes/([a-z0-9]+(?:-[a-z0-9]+)+)$`)
|
|
)
|
|
|
|
func main() {
|
|
|
|
// create the Store and Recipe Handler
|
|
store := recipes.NewMemStore()
|
|
recipesHandler := NewRecipesHandler(store)
|
|
|
|
// Create a new request multiplexer
|
|
// Takes incoming requests and dispatch them to the matching handlers
|
|
mux := http.NewServeMux()
|
|
|
|
// Register the routes and handlers
|
|
mux.Handle("/", &homeHandler{})
|
|
mux.Handle("/recipes", recipesHandler)
|
|
mux.Handle("/recipes/", recipesHandler)
|
|
|
|
// Run the server
|
|
http.ListenAndServe(":8080", mux)
|
|
}
|
|
|
|
type homeHandler struct{}
|
|
|
|
func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("This is my home page"))
|
|
}
|
|
|
|
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"))
|
|
}
|
|
|
|
// recipeStore represent a data storage containing recipes
|
|
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
|
|
}
|
|
|
|
// RecipesHandler implements http.Handler and dispatch request to the store
|
|
type RecipesHandler struct {
|
|
store recipeStore
|
|
}
|
|
|
|
func NewRecipesHandler(s recipeStore) *RecipesHandler {
|
|
return &RecipesHandler{
|
|
store: s,
|
|
}
|
|
}
|
|
|
|
func (h *RecipesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == http.MethodPost && RecipeRe.MatchString(r.URL.Path):
|
|
h.CreateRecipe(w, r)
|
|
return
|
|
case r.Method == http.MethodGet && RecipeRe.MatchString(r.URL.Path):
|
|
h.ListRecipes(w, r)
|
|
return
|
|
case r.Method == http.MethodGet && RecipeReWithID.MatchString(r.URL.Path):
|
|
h.GetRecipe(w, r)
|
|
return
|
|
case r.Method == http.MethodPut && RecipeReWithID.MatchString(r.URL.Path):
|
|
h.UpdateRecipe(w, r)
|
|
return
|
|
case r.Method == http.MethodDelete && RecipeReWithID.MatchString(r.URL.Path):
|
|
h.DeleteRecipe(w, r)
|
|
return
|
|
default:
|
|
NotFoundHandler(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
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) {
|
|
recipesList, err := h.store.List()
|
|
|
|
jsonBytes, err := json.Marshal(recipesList)
|
|
if err != nil {
|
|
InternalServerErrorHandler(w, r)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(jsonBytes)
|
|
}
|
|
|
|
func (h *RecipesHandler) GetRecipe(w http.ResponseWriter, r *http.Request) {
|
|
matches := RecipeReWithID.FindStringSubmatch(r.URL.Path)
|
|
if len(matches) < 2 {
|
|
InternalServerErrorHandler(w, r)
|
|
return
|
|
}
|
|
|
|
recipe, err := h.store.Get(matches[1])
|
|
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) {
|
|
matches := RecipeReWithID.FindStringSubmatch(r.URL.Path)
|
|
if len(matches) < 2 {
|
|
InternalServerErrorHandler(w, r)
|
|
return
|
|
}
|
|
|
|
// 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(matches[1], recipe); err != nil {
|
|
if err == recipes.NotFoundErr {
|
|
NotFoundHandler(w, r)
|
|
return
|
|
}
|
|
InternalServerErrorHandler(w, r)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func (h *RecipesHandler) DeleteRecipe(w http.ResponseWriter, r *http.Request) {
|
|
matches := RecipeReWithID.FindStringSubmatch(r.URL.Path)
|
|
if len(matches) < 2 {
|
|
InternalServerErrorHandler(w, r)
|
|
return
|
|
}
|
|
|
|
if err := h.store.Remove(matches[1]); err != nil {
|
|
InternalServerErrorHandler(w, r)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|