package main import ( "database/sql" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "time" "github.com/gorilla/mux" "github.com/jmoiron/sqlx" ) const StatusHandled = 0 type Book struct { BookID string CategoryID string Title string Slug string Description string LikeCount int Creation time.Time LastUpdate time.Time Deleted bool } type NewBook struct { Title string Description string } type Context struct { DB *sqlx.DB } func New(db *sql.DB) *Context { return &Context{DB} } func readjson(r io.ReadCloser, v interface{}) error { data, err := ioutil.ReadAll(r) if err != nil { return err } return json.Unmarshal(data, v) } func (c *Context) json(rw http.ResponseWriter, r *http.Request, v interface{}) (err string, code int) { data, err := json.Marshal(v) if err != nil { return err.Error(), http.StatusInternalServerError } rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusOK) rw.Write(data) return "", StatusHandled } func (c *Context) Books(rw http.ResponseWriter, r *http.Request) (err string, code int) { vars, db := mux.Vars(r), c.DB switch r.Method { case "GET": books := &Books{} category := vars["category"] if category == "" { http.Error(rw, fmt.Sprintf("Category cannot be empty."), http.StatusBadRequest) return } err := db.Select(books, "SELECT * FROM book WHERE categoryid=?", category) if err != nil { return fmt.Sprintf(`Error searching for books: %v`, err), http.StatusInternalServerError } return c.json(rw, r, books) default: return fmt.Sprintf("Invalid method: %s", r.Method), http.StatusForbidden } } func (c *Context) ByCategory(rw http.ResponseWriter, r *http.Request) (err string, code int) { vars, db := mux.Vars(r), c.DB slug := vars["slug"] category := vars["category"] switch r.Method { case "GET": book := &Book{} if slug == "" || cat == "" { return fmt.Sprintf("Slug or category cannot be empty."), http.StatusBadRequest } err = db.Get(book, "SELECT * FROM book WHERE slug=? AND categoryid=?", slug, cat) if err != nil { return fmt.Sprintf(`Error searching for books: %v`, err), http.StatusInternalServerError } return c.json(rw, r, book) case "POST": book := &NewBook{} err := readjson(r.Body, book) if err != nil { return fmt.Sprintf("Invalid book."), http.StatusBadRequest } _, err = db.Exec(` INSERT INTO book (categoryid, title, slug, description, likecount, creation, lastupdate, deleted) VALUES (?, ?, ?, ?, 0, NOW(), NOW(), false)`, category, book.Title, slug(book.Title), book.Description) if err != nil { return fmt.Sprintf("Error inserting the book: %s", err.Error()), http.StatusInternalServerError } return "", http.StatusOK default: return fmt.Sprintf("Invalid method: %s", r.Method), http.StatusForbidden } } func (c *Context) BookLike(rw http.ResponseWriter, r *http.Request) (err string, code int) { vars, db := mux.Vars(r), c.DB switch r.Method { case "POST": id := vars["bookid"] if id == "" { return fmt.Sprintf("Book id cannot be empty."), http.StatusBadRequest } _, err = db.Exec(`UPDATE book SET likecount=likecount+1 WHERE bookid=?`, id) if err != nil { return fmt.Sprintf("Error inserting the book: %s", err.Error()), http.StatusInternalServerError } return "", http.StatusOK default: return fmt.Sprintf("Invalid method: %s", r.Method), http.StatusForbidden } } var Router = mux.NewRouter() type Handler func(rw http.ResponseWriter, r *http.Request) (err string, code int) func route(path string, fn Handler) { Router.HandleFunc(path, func(rw http.ResponseWriter, r *http.Request) { err, code := fn(rw, r) if code != StatusHandled { http.Error(rw, err, code) } }) } func main() { db := sqlx.Open("sqlite3", "memory") c := &Context{db} route("categories/{category}/books/{slug}", c.ByCategory) route("categories/{category}/books/{bookid}/like", c.BookLike) http.Handle("/api/", Router) http.ListenAndServe(":3000", nil) }