Most API features now work

- Switched from Vestigo to ozzo-routing in a misguided attempt to get JSON to parse as a form; not switching back at this point
- Fixed error with request SQL for individual requests
- Parsing JSON body works now

Still need to handle "no rows found" for journal/answered lists; this isn't an error for new users
This commit is contained in:
Daniel J. Summers 2018-05-26 22:18:26 -05:00
parent bad430fc37
commit 79ced40470
4 changed files with 107 additions and 77 deletions

View File

@ -152,10 +152,13 @@ func AddNote(userID, reqID, note string) int {
// Answered retrieves all answered requests for the given user.
func Answered(userID string) []JournalRequest {
rows, err := db.Query(currentRequestSQL+
`WHERE "userId" = $1
` WHERE "userId" = $1
AND "lastStatus" = 'Answered'
ORDER BY "asOf" DESC`,
userID)
if err == sql.ErrNoRows {
return make([]JournalRequest, 0)
}
if err != nil {
log.Print(err)
return nil
@ -168,9 +171,9 @@ func Answered(userID string) []JournalRequest {
func ByID(userID, reqID string) (*JournalRequest, bool) {
req := JournalRequest{}
err := db.QueryRow(currentRequestSQL+
`WHERE "requestId" = $1
AND "userId = $2`,
userID, reqID).Scan(
` WHERE "requestId" = $1
AND "userId" = $2`,
reqID, userID).Scan(
&req.RequestID, &req.Text, &req.AsOf, &req.LastStatus,
)
if err != nil {
@ -235,7 +238,7 @@ func FullByID(userID, reqID string) (*JournalRequest, bool) {
// Journal retrieves the current user's active prayer journal.
func Journal(userID string) []JournalRequest {
rows, err := db.Query(journalSQL, userID)
rows, err := db.Query(journalSQL+` ORDER BY "asOf"`, userID)
if err != nil {
log.Print(err)
return nil

View File

@ -9,132 +9,155 @@ import (
"github.com/danieljsummers/myPrayerJournal/src/api/data"
jwt "github.com/dgrijalva/jwt-go"
"github.com/husobee/vestigo"
routing "github.com/go-ozzo/ozzo-routing"
)
/* Support */
// Set the content type, the HTTP error code, and return the error message.
func sendError(w http.ResponseWriter, r *http.Request, err error) {
func sendError(c *routing.Context, err error) error {
w := c.Response
w.Header().Set("Content-Type", "application/json; encoding=UTF-8")
w.WriteHeader(http.StatusInternalServerError)
if err := json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}); err != nil {
log.Print("Error creating error JSON: " + err.Error())
}
return err
}
// Set the content type and return the JSON to the user.
func sendJSON(w http.ResponseWriter, r *http.Request, result interface{}) {
func sendJSON(c *routing.Context, result interface{}) error {
w := c.Response
w.Header().Set("Content-Type", "application/json; encoding=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(result); err != nil {
sendError(w, r, err)
return sendError(c, err)
}
return nil
}
// Parse the request body as JSON.
func parseJSON(c *routing.Context) (map[string]interface{}, error) {
payload := make(map[string]interface{})
if err := json.NewDecoder(c.Request.Body).Decode(&payload); err != nil {
log.Println("Error decoding JSON:", err)
return payload, err
}
return payload, nil
}
// userID is a convenience function to extract the subscriber ID from the user's JWT.
// NOTE: Do not call this from public routes; there are a lot of type assertions that won't be true if the request
// hasn't gone through the authorization process.
func userID(r *http.Request) string {
return r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)["sub"].(string)
func userID(c *routing.Context) string {
return c.Request.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)["sub"].(string)
}
/* Handlers */
// GET: /api/journal/
func journal(w http.ResponseWriter, r *http.Request) {
reqs := data.Journal(userID(r))
func journal(c *routing.Context) error {
reqs := data.Journal(userID(c))
if reqs == nil {
reqs = []data.JournalRequest{}
}
sendJSON(w, r, reqs)
return sendJSON(c, reqs)
}
// POST: /api/request/
func requestAdd(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
sendError(w, r, err)
func requestAdd(c *routing.Context) error {
payload, err := parseJSON(c)
if err != nil {
return sendError(c, err)
}
result, ok := data.AddNew(userID(r), r.FormValue("requestText"))
result, ok := data.AddNew(userID(c), payload["requestText"].(string))
if !ok {
sendError(w, r, errors.New("error adding request"))
return sendError(c, errors.New("error adding request"))
}
sendJSON(w, r, result)
return sendJSON(c, result)
}
// GET: /api/request/:id
func requestGet(w http.ResponseWriter, r *http.Request) {
request, ok := data.ByID(userID(r), vestigo.Param(r, "id"))
// GET: /api/request/<id>
func requestGet(c *routing.Context) error {
request, ok := data.ByID(userID(c), c.Param("id"))
if !ok {
sendError(w, r, errors.New("error retrieving request"))
return sendError(c, errors.New("error retrieving request"))
}
sendJSON(w, r, request)
return sendJSON(c, request)
}
// GET: /api/request/:id/complete
func requestGetComplete(w http.ResponseWriter, r *http.Request) {
request, ok := data.FullByID(userID(r), vestigo.Param(r, "id"))
// GET: /api/request/<id>/complete
func requestGetComplete(c *routing.Context) error {
request, ok := data.FullByID(userID(c), c.Param("id"))
if !ok {
sendError(w, r, errors.New("error retrieving request"))
return sendError(c, errors.New("error retrieving request"))
}
request.Notes = data.NotesByID(userID(r), vestigo.Param(r, "id"))
sendJSON(w, r, request)
request.Notes = data.NotesByID(userID(c), c.Param("id"))
return sendJSON(c, request)
}
// GET: /api/request/:id/full
func requestGetFull(w http.ResponseWriter, r *http.Request) {
request, ok := data.FullByID(userID(r), vestigo.Param(r, "id"))
// GET: /api/request/<id>/full
func requestGetFull(c *routing.Context) error {
request, ok := data.FullByID(userID(c), c.Param("id"))
if !ok {
sendError(w, r, errors.New("error retrieving request"))
return sendError(c, errors.New("error retrieving request"))
}
sendJSON(w, r, request)
return sendJSON(c, request)
}
// POST: /api/request/:id/history
func requestAddHistory(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
sendError(w, r, err)
// POST: /api/request/<id>/history
func requestAddHistory(c *routing.Context) error {
payload, err := parseJSON(c)
if err != nil {
return sendError(c, err)
}
w.WriteHeader(data.AddHistory(userID(r), vestigo.Param(r, "id"), r.FormValue("status"), r.FormValue("updateText")))
c.Response.WriteHeader(
data.AddHistory(userID(c), c.Param("id"), payload["status"].(string), payload["updateText"].(string)))
return nil
}
// POST: /api/request/:id/note
func requestAddNote(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
sendError(w, r, err)
// POST: /api/request/<id>/note
func requestAddNote(c *routing.Context) error {
payload, err := parseJSON(c)
if err != nil {
return sendError(c, err)
}
w.WriteHeader(data.AddNote(userID(r), vestigo.Param(r, "id"), r.FormValue("notes")))
c.Response.WriteHeader(data.AddNote(userID(c), c.Param("id"), payload["notes"].(string)))
return nil
}
// GET: /api/request/:id/notes
func requestGetNotes(w http.ResponseWriter, r *http.Request) {
notes := data.NotesByID(userID(r), vestigo.Param(r, "id"))
// GET: /api/request/<id>/notes
func requestGetNotes(c *routing.Context) error {
notes := data.NotesByID(userID(c), c.Param("id"))
if notes == nil {
w.WriteHeader(http.StatusNotFound)
return
c.Response.WriteHeader(http.StatusNotFound)
return errors.New("Not Found")
}
sendJSON(w, r, notes)
return sendJSON(c, notes)
}
// GET: /api/request/answered
func requestsAnswered(w http.ResponseWriter, r *http.Request) {
reqs := data.Answered(userID(r))
func requestsAnswered(c *routing.Context) error {
reqs := data.Answered(userID(c))
if reqs == nil {
reqs = []data.JournalRequest{}
}
sendJSON(w, r, reqs)
return sendJSON(c, reqs)
}
// GET: /*
func staticFiles(w http.ResponseWriter, r *http.Request) {
func staticFiles(c *routing.Context) error {
// serve index for known routes handled client-side by the app
r := c.Request
w := c.Response
for _, prefix := range ClientPrefixes {
if strings.HasPrefix(r.URL.Path, prefix) {
w.Header().Add("Content-Type", "text/html")
http.ServeFile(w, r, "./public/index.html")
return
return nil
}
}
// 404 here is fine; quit hacking, y'all...
http.ServeFile(w, r, "./public"+r.URL.Path)
return nil
}

View File

@ -5,11 +5,14 @@ import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/auth0/go-jwt-middleware"
jwt "github.com/dgrijalva/jwt-go"
"github.com/husobee/vestigo"
"github.com/go-ozzo/ozzo-routing"
"github.com/go-ozzo/ozzo-routing/access"
"github.com/go-ozzo/ozzo-routing/fault"
)
// AuthConfig contains the Auth0 configuration passed from the "auth" JSON object.
@ -96,25 +99,24 @@ var authZero = jwtmiddleware.New(jwtmiddleware.Options{
SigningMethod: jwt.SigningMethodRS256,
})
// authMiddleware is a wrapper for the Auth0 middleware above with a signature Vestigo recognizes.
func authMiddleware(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := authZero.CheckJWT(w, r)
if err == nil {
f(w, r)
}
}
// authMiddleware is a wrapper for the Auth0 middleware above with a signature ozzo-routing recognizes.
func authMiddleware(c *routing.Context) error {
return authZero.CheckJWT(c.Response, c.Request)
}
// NewRouter returns a configured router to handle all incoming requests.
func NewRouter(cfg *AuthConfig) *vestigo.Router {
func NewRouter(cfg *AuthConfig) *routing.Router {
authCfg = cfg
router := vestigo.NewRouter()
router := routing.New()
router.Use(
access.Logger(log.Printf), // TODO: remove before go-live
fault.Recovery(log.Printf),
)
for _, route := range routes {
if route.IsPublic {
router.Add(route.Method, route.Pattern, route.Func)
router.To(route.Method, route.Pattern, route.Func)
} else {
router.Add(route.Method, route.Pattern, route.Func, authMiddleware)
router.To(route.Method, route.Pattern, authMiddleware, route.Func)
}
}
return router

View File

@ -3,6 +3,8 @@ package routes
import (
"net/http"
routing "github.com/go-ozzo/ozzo-routing"
)
// Route is a route served in the application.
@ -10,7 +12,7 @@ type Route struct {
Name string
Method string
Pattern string
Func http.HandlerFunc
Func routing.Handler
IsPublic bool
}
@ -36,42 +38,42 @@ var routes = Routes{
Route{
"GetRequestByID",
http.MethodGet,
"/api/request/:id",
"/api/request/<id>",
requestGet,
false,
},
Route{
"GetCompleteRequestByID",
http.MethodGet,
"/api/request/:id/complete",
"/api/request/<id>/complete",
requestGetComplete,
false,
},
Route{
"GetFullRequestByID",
http.MethodGet,
"/api/request/:id/full",
"/api/request/<id>/full",
requestGetFull,
false,
},
Route{
"AddNewHistoryEntry",
http.MethodPost,
"/api/request/:id/history",
"/api/request/<id>/history",
requestAddHistory,
false,
},
Route{
"AddNewNote",
http.MethodPost,
"/api/request/:id/note",
"/api/request/<id>/note",
requestAddNote,
false,
},
Route{
"GetNotesForRequest",
http.MethodGet,
"/api/request/:id/notes",
"/api/request/<id>/notes",
requestGetNotes,
false,
},