Skip to content
Permalink
Browse files

GET, POST, PATCH, DELETE

  • Loading branch information...
Depado committed Jan 14, 2016
1 parent 9883869 commit c065ce85d83a95de2a40bf001404ae33e1acef34
Showing with 159 additions and 40 deletions.
  1. +12 −0 hateoas/hateoas.go
  2. +8 −6 main.go
  3. +11 −0 models/entry/database.go
  4. +127 −29 models/entry/entry.go
  5. +1 −5 models/entry/marshal.go
@@ -0,0 +1,12 @@
package hateoas

// Error is an HATEOAS error
type Error struct {
ID int `json:"id,omitempty"`
Status int `json:"status,omitempty"`
Title string `json:"title,omitempty"`
Detail string `json:"detail,omitempty"`
}

// Errors is a slice of HATEOAS errors
type Errors []Error
14 main.go
@@ -8,6 +8,8 @@ import (
"github.com/gin-gonic/gin"
)

const currentAPIVersion = "1"

func main() {
var err error

@@ -21,14 +23,14 @@ func main() {
// r.LoadHTMLGlob("templates/*")
// r.Static("/static", "./assets")

entryr := r.Group("/entry")
currentAPI := r.Group("/api/v" + currentAPIVersion)
entryEndpoint := currentAPI.Group("/entry")
{
entryr.POST("/", entry.Post)
entryEndpoint.POST("/", entry.Post)
// entryr.GET("/", entry.List)
// entryr.GET("/:id", entry.Get)
// entryr.PATCH("/:id", entry.Patch)
// entryr.PUT("/:id", entry.Put)
// entryr.DELETE("/:id", entry.Delete)
entryEndpoint.GET("/:id", entry.Get)
entryEndpoint.PATCH("/:id", entry.Patch)
entryEndpoint.DELETE("/:id", entry.Delete)
}

r.Run(":8080")
@@ -48,3 +48,14 @@ func (e Entry) Delete() error {
return b.Delete([]byte(strconv.Itoa(e.ID)))
})
}

// Get retrieves an Entry from the database.
func (e *Entry) Get(key string) error {
if !database.Main.Opened {
return fmt.Errorf("Database must be opened first.")
}
return database.Main.DB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(Bucket))
return e.Decode(b.Get([]byte(key)))
})
}
@@ -1,14 +1,18 @@
package entry

import (
"fmt"
"net/http"
"strconv"

"github.com/Depado/govue/hateoas"
"github.com/gin-gonic/gin"
)

// Bucket is the name of the bucket storing all the entries
const Bucket = "entries"
const (
Bucket = "entries"
Type = "entry"
)

// Entry is the main struct
type Entry struct {
@@ -17,46 +21,140 @@ type Entry struct {
Markdown string `json:"markdown"`
}

// APIError represents a single API Error
type APIError struct {
ID int `json:"id,omitempty"`
Status int `json:"status,omitempty"`
Title string `json:"title,omitempty"`
Detail string `json:"detail,omitempty"`
// Validate validates that all the required files are not empty.
func (e Entry) Validate() hateoas.Errors {
var errors hateoas.Errors
if e.Title == "" {
errors = append(errors, hateoas.Error{
Status: http.StatusBadRequest,
Title: "title field is required",
})
}
if e.Markdown == "" {
errors = append(errors, hateoas.Error{
Status: http.StatusBadRequest,
Title: "markdown field is required",
})
}
return errors
}

// Data contains the Type of the request and the Attributes
type Data struct {
Type string `json:"type,omitempty"`
Attributes *Entry `json:"attributes,omitempty"`
Links *Links `json:"links,omitempty"`
}

// APIErrors represent multiple API Errors
type APIErrors []APIError
// Links represent a list of links
type Links map[string]string

// Wrapper is the HATEOAS wrapper
type Wrapper struct {
Data *Data `json:"data,omitempty"`
Errors *hateoas.Errors `json:"errors,omitempty"`
}

// Post is the handler to POST a new Entry
func Post(c *gin.Context) {
var err error
var errors APIErrors
var json Entry
var json = Wrapper{}

if err = c.BindJSON(&json); err == nil {
if json.Markdown == "" {
errors = append(errors, APIError{
Status: http.StatusBadRequest,
Title: "markdown field is required",
})
}
if json.Title == "" {
errors = append(errors, APIError{
Status: http.StatusBadRequest,
Title: "title field is required",
})
}
errors := json.Data.Attributes.Validate()
if len(errors) > 0 {
c.JSON(http.StatusBadRequest, gin.H{"errors": errors})
c.JSON(http.StatusBadRequest, Wrapper{Errors: &errors})
return
}
if err = json.Save(); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "bad request"})
if err = json.Data.Attributes.Save(); err != nil {
json.Data = nil
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "could not save entry"}}
c.JSON(http.StatusInternalServerError, json)
} else {
c.JSON(http.StatusCreated, gin.H{"data": json})
json.Data.Links = &Links{"self": c.Request.URL.RequestURI() + strconv.Itoa(json.Data.Attributes.ID)}
c.JSON(http.StatusCreated, json)
}
} else {
c.JSON(http.StatusForbidden, gin.H{"errors": fmt.Sprintf("%s", err)})
json.Data = nil
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "Bad json format"}}
c.JSON(http.StatusBadRequest, json)
}
}

// Get is the handler to GET an existing entry
func Get(c *gin.Context) {
var err error
var e Entry
var json = Wrapper{}

id := c.Param("id")
if err = e.Get(id); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusNotFound, Title: "id could not be found"}}
c.JSON(http.StatusNotFound, json)
return
}
if e.ID, err = strconv.Atoi(id); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "id can't be parsed"}}
c.JSON(http.StatusInternalServerError, json)
}
json.Data = &Data{Type: Type, Attributes: &e}
c.JSON(http.StatusFound, json)
}

// Patch is used to update a resource.
func Patch(c *gin.Context) {
var err error
var e Entry
var json = Wrapper{}

id := c.Param("id")
if err = e.Get(id); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusNotFound, Title: "id could not be found"}}
c.JSON(http.StatusNotFound, json)
return
}
if e.ID, err = strconv.Atoi(id); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "id can't be parsed"}}
c.JSON(http.StatusInternalServerError, json)
return
}
json.Data = &Data{Type: Type, Attributes: &e}
if err = c.BindJSON(&json); err == nil {
if err = json.Data.Attributes.Save(); err != nil {
json.Data = nil
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "could not save entry"}}
c.JSON(http.StatusInternalServerError, json)
} else {
c.JSON(http.StatusCreated, json)
}
} else {
json.Data = nil
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "Bad json format"}}
c.JSON(http.StatusBadRequest, json)
}
}

// Delete deletes a resource
func Delete(c *gin.Context) {
var err error
var e Entry
var json = Wrapper{}

id := c.Param("id")
if err = e.Get(id); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusNotFound, Title: "id could not be found"}}
c.JSON(http.StatusNotFound, json)
return
}
if e.ID, err = strconv.Atoi(id); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "id can't be parsed"}}
c.JSON(http.StatusInternalServerError, json)
return
}
if err = e.Delete(); err != nil {
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "couldn't delete resource"}}
c.JSON(http.StatusInternalServerError, json)
return
}
c.AbortWithStatus(http.StatusNoContent)
}
@@ -4,11 +4,7 @@ import "encoding/json"

// Encode dumps an Entry to json.
func (e Entry) Encode() ([]byte, error) {
enc, err := json.Marshal(e)
if err != nil {
return nil, err
}
return enc, nil
return json.Marshal(e)
}

// Decode loads an Entry from json

0 comments on commit c065ce8

Please sign in to comment.
You can’t perform that action at this time.