Skip to content

Commit

Permalink
GET, POST, PATCH, DELETE
Browse files Browse the repository at this point in the history
  • Loading branch information
depado committed Jan 14, 2016
1 parent 9883869 commit c065ce8
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 40 deletions.
12 changes: 12 additions & 0 deletions hateoas/hateoas.go
Original file line number Original file line Diff line number Diff line change
@@ -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 changes: 8 additions & 6 deletions main.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )


const currentAPIVersion = "1"

func main() { func main() {
var err error var err error


Expand All @@ -21,14 +23,14 @@ func main() {
// r.LoadHTMLGlob("templates/*") // r.LoadHTMLGlob("templates/*")
// r.Static("/static", "./assets") // 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("/", entry.List)
// entryr.GET("/:id", entry.Get) entryEndpoint.GET("/:id", entry.Get)
// entryr.PATCH("/:id", entry.Patch) entryEndpoint.PATCH("/:id", entry.Patch)
// entryr.PUT("/:id", entry.Put) entryEndpoint.DELETE("/:id", entry.Delete)
// entryr.DELETE("/:id", entry.Delete)
} }


r.Run(":8080") r.Run(":8080")
Expand Down
11 changes: 11 additions & 0 deletions models/entry/database.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -48,3 +48,14 @@ func (e Entry) Delete() error {
return b.Delete([]byte(strconv.Itoa(e.ID))) 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)))
})
}
156 changes: 127 additions & 29 deletions models/entry/entry.go
Original file line number Original file line Diff line number Diff line change
@@ -1,14 +1,18 @@
package entry package entry


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


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


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


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


// APIError represents a single API Error // Validate validates that all the required files are not empty.
type APIError struct { func (e Entry) Validate() hateoas.Errors {
ID int `json:"id,omitempty"` var errors hateoas.Errors
Status int `json:"status,omitempty"` if e.Title == "" {
Title string `json:"title,omitempty"` errors = append(errors, hateoas.Error{
Detail string `json:"detail,omitempty"` 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 // Links represent a list of links
type APIErrors []APIError 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 // Post is the handler to POST a new Entry
func Post(c *gin.Context) { func Post(c *gin.Context) {
var err error var err error
var errors APIErrors var json = Wrapper{}
var json Entry


if err = c.BindJSON(&json); err == nil { if err = c.BindJSON(&json); err == nil {
if json.Markdown == "" { errors := json.Data.Attributes.Validate()
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",
})
}
if len(errors) > 0 { if len(errors) > 0 {
c.JSON(http.StatusBadRequest, gin.H{"errors": errors}) c.JSON(http.StatusBadRequest, Wrapper{Errors: &errors})
return return
} }
if err = json.Save(); err != nil { if err = json.Data.Attributes.Save(); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "bad request"}) json.Data = nil
json.Errors = &hateoas.Errors{hateoas.Error{Status: http.StatusInternalServerError, Title: "could not save entry"}}
c.JSON(http.StatusInternalServerError, json)
} else { } 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 { } 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)
} }
6 changes: 1 addition & 5 deletions models/entry/marshal.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import "encoding/json"


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


// Decode loads an Entry from json // Decode loads an Entry from json
Expand Down

0 comments on commit c065ce8

Please sign in to comment.