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:
parent
bad430fc37
commit
79ced40470
@ -152,10 +152,13 @@ func AddNote(userID, reqID, note string) int {
|
|||||||
// Answered retrieves all answered requests for the given user.
|
// Answered retrieves all answered requests for the given user.
|
||||||
func Answered(userID string) []JournalRequest {
|
func Answered(userID string) []JournalRequest {
|
||||||
rows, err := db.Query(currentRequestSQL+
|
rows, err := db.Query(currentRequestSQL+
|
||||||
`WHERE "userId" = $1
|
` WHERE "userId" = $1
|
||||||
AND "lastStatus" = 'Answered'
|
AND "lastStatus" = 'Answered'
|
||||||
ORDER BY "asOf" DESC`,
|
ORDER BY "asOf" DESC`,
|
||||||
userID)
|
userID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return make([]JournalRequest, 0)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
@ -168,9 +171,9 @@ func Answered(userID string) []JournalRequest {
|
|||||||
func ByID(userID, reqID string) (*JournalRequest, bool) {
|
func ByID(userID, reqID string) (*JournalRequest, bool) {
|
||||||
req := JournalRequest{}
|
req := JournalRequest{}
|
||||||
err := db.QueryRow(currentRequestSQL+
|
err := db.QueryRow(currentRequestSQL+
|
||||||
`WHERE "requestId" = $1
|
` WHERE "requestId" = $1
|
||||||
AND "userId = $2`,
|
AND "userId" = $2`,
|
||||||
userID, reqID).Scan(
|
reqID, userID).Scan(
|
||||||
&req.RequestID, &req.Text, &req.AsOf, &req.LastStatus,
|
&req.RequestID, &req.Text, &req.AsOf, &req.LastStatus,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -235,7 +238,7 @@ func FullByID(userID, reqID string) (*JournalRequest, bool) {
|
|||||||
|
|
||||||
// Journal retrieves the current user's active prayer journal.
|
// Journal retrieves the current user's active prayer journal.
|
||||||
func Journal(userID string) []JournalRequest {
|
func Journal(userID string) []JournalRequest {
|
||||||
rows, err := db.Query(journalSQL, userID)
|
rows, err := db.Query(journalSQL+` ORDER BY "asOf"`, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,132 +9,155 @@ import (
|
|||||||
|
|
||||||
"github.com/danieljsummers/myPrayerJournal/src/api/data"
|
"github.com/danieljsummers/myPrayerJournal/src/api/data"
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
"github.com/husobee/vestigo"
|
routing "github.com/go-ozzo/ozzo-routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Support */
|
/* Support */
|
||||||
|
|
||||||
// Set the content type, the HTTP error code, and return the error message.
|
// 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.Header().Set("Content-Type", "application/json; encoding=UTF-8")
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
if err := json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}); err != nil {
|
if err := json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}); err != nil {
|
||||||
log.Print("Error creating error JSON: " + err.Error())
|
log.Print("Error creating error JSON: " + err.Error())
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the content type and return the JSON to the user.
|
// 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.Header().Set("Content-Type", "application/json; encoding=UTF-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
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.
|
// 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
|
// 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.
|
// hasn't gone through the authorization process.
|
||||||
func userID(r *http.Request) string {
|
func userID(c *routing.Context) string {
|
||||||
return r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)["sub"].(string)
|
return c.Request.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)["sub"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handlers */
|
/* Handlers */
|
||||||
|
|
||||||
// GET: /api/journal/
|
// GET: /api/journal/
|
||||||
func journal(w http.ResponseWriter, r *http.Request) {
|
func journal(c *routing.Context) error {
|
||||||
reqs := data.Journal(userID(r))
|
reqs := data.Journal(userID(c))
|
||||||
if reqs == nil {
|
if reqs == nil {
|
||||||
reqs = []data.JournalRequest{}
|
reqs = []data.JournalRequest{}
|
||||||
}
|
}
|
||||||
sendJSON(w, r, reqs)
|
return sendJSON(c, reqs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /api/request/
|
// POST: /api/request/
|
||||||
func requestAdd(w http.ResponseWriter, r *http.Request) {
|
func requestAdd(c *routing.Context) error {
|
||||||
if err := r.ParseForm(); err != nil {
|
payload, err := parseJSON(c)
|
||||||
sendError(w, r, err)
|
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 {
|
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
|
// GET: /api/request/<id>
|
||||||
func requestGet(w http.ResponseWriter, r *http.Request) {
|
func requestGet(c *routing.Context) error {
|
||||||
request, ok := data.ByID(userID(r), vestigo.Param(r, "id"))
|
request, ok := data.ByID(userID(c), c.Param("id"))
|
||||||
if !ok {
|
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
|
// GET: /api/request/<id>/complete
|
||||||
func requestGetComplete(w http.ResponseWriter, r *http.Request) {
|
func requestGetComplete(c *routing.Context) error {
|
||||||
request, ok := data.FullByID(userID(r), vestigo.Param(r, "id"))
|
request, ok := data.FullByID(userID(c), c.Param("id"))
|
||||||
if !ok {
|
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"))
|
request.Notes = data.NotesByID(userID(c), c.Param("id"))
|
||||||
sendJSON(w, r, request)
|
return sendJSON(c, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/request/:id/full
|
// GET: /api/request/<id>/full
|
||||||
func requestGetFull(w http.ResponseWriter, r *http.Request) {
|
func requestGetFull(c *routing.Context) error {
|
||||||
request, ok := data.FullByID(userID(r), vestigo.Param(r, "id"))
|
request, ok := data.FullByID(userID(c), c.Param("id"))
|
||||||
if !ok {
|
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
|
// POST: /api/request/<id>/history
|
||||||
func requestAddHistory(w http.ResponseWriter, r *http.Request) {
|
func requestAddHistory(c *routing.Context) error {
|
||||||
if err := r.ParseForm(); err != nil {
|
payload, err := parseJSON(c)
|
||||||
sendError(w, r, err)
|
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
|
// POST: /api/request/<id>/note
|
||||||
func requestAddNote(w http.ResponseWriter, r *http.Request) {
|
func requestAddNote(c *routing.Context) error {
|
||||||
if err := r.ParseForm(); err != nil {
|
payload, err := parseJSON(c)
|
||||||
sendError(w, r, err)
|
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
|
// GET: /api/request/<id>/notes
|
||||||
func requestGetNotes(w http.ResponseWriter, r *http.Request) {
|
func requestGetNotes(c *routing.Context) error {
|
||||||
notes := data.NotesByID(userID(r), vestigo.Param(r, "id"))
|
notes := data.NotesByID(userID(c), c.Param("id"))
|
||||||
if notes == nil {
|
if notes == nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
c.Response.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return errors.New("Not Found")
|
||||||
}
|
}
|
||||||
sendJSON(w, r, notes)
|
return sendJSON(c, notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /api/request/answered
|
// GET: /api/request/answered
|
||||||
func requestsAnswered(w http.ResponseWriter, r *http.Request) {
|
func requestsAnswered(c *routing.Context) error {
|
||||||
reqs := data.Answered(userID(r))
|
reqs := data.Answered(userID(c))
|
||||||
if reqs == nil {
|
if reqs == nil {
|
||||||
reqs = []data.JournalRequest{}
|
reqs = []data.JournalRequest{}
|
||||||
}
|
}
|
||||||
sendJSON(w, r, reqs)
|
return sendJSON(c, reqs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: /*
|
// 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
|
// serve index for known routes handled client-side by the app
|
||||||
|
r := c.Request
|
||||||
|
w := c.Response
|
||||||
for _, prefix := range ClientPrefixes {
|
for _, prefix := range ClientPrefixes {
|
||||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
http.ServeFile(w, r, "./public/index.html")
|
http.ServeFile(w, r, "./public/index.html")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 404 here is fine; quit hacking, y'all...
|
// 404 here is fine; quit hacking, y'all...
|
||||||
http.ServeFile(w, r, "./public"+r.URL.Path)
|
http.ServeFile(w, r, "./public"+r.URL.Path)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/auth0/go-jwt-middleware"
|
"github.com/auth0/go-jwt-middleware"
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
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.
|
// AuthConfig contains the Auth0 configuration passed from the "auth" JSON object.
|
||||||
@ -96,25 +99,24 @@ var authZero = jwtmiddleware.New(jwtmiddleware.Options{
|
|||||||
SigningMethod: jwt.SigningMethodRS256,
|
SigningMethod: jwt.SigningMethodRS256,
|
||||||
})
|
})
|
||||||
|
|
||||||
// authMiddleware is a wrapper for the Auth0 middleware above with a signature Vestigo recognizes.
|
// authMiddleware is a wrapper for the Auth0 middleware above with a signature ozzo-routing recognizes.
|
||||||
func authMiddleware(f http.HandlerFunc) http.HandlerFunc {
|
func authMiddleware(c *routing.Context) error {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return authZero.CheckJWT(c.Response, c.Request)
|
||||||
err := authZero.CheckJWT(w, r)
|
|
||||||
if err == nil {
|
|
||||||
f(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouter returns a configured router to handle all incoming requests.
|
// NewRouter returns a configured router to handle all incoming requests.
|
||||||
func NewRouter(cfg *AuthConfig) *vestigo.Router {
|
func NewRouter(cfg *AuthConfig) *routing.Router {
|
||||||
authCfg = cfg
|
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 {
|
for _, route := range routes {
|
||||||
if route.IsPublic {
|
if route.IsPublic {
|
||||||
router.Add(route.Method, route.Pattern, route.Func)
|
router.To(route.Method, route.Pattern, route.Func)
|
||||||
} else {
|
} else {
|
||||||
router.Add(route.Method, route.Pattern, route.Func, authMiddleware)
|
router.To(route.Method, route.Pattern, authMiddleware, route.Func)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return router
|
return router
|
||||||
|
@ -3,6 +3,8 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
routing "github.com/go-ozzo/ozzo-routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Route is a route served in the application.
|
// Route is a route served in the application.
|
||||||
@ -10,7 +12,7 @@ type Route struct {
|
|||||||
Name string
|
Name string
|
||||||
Method string
|
Method string
|
||||||
Pattern string
|
Pattern string
|
||||||
Func http.HandlerFunc
|
Func routing.Handler
|
||||||
IsPublic bool
|
IsPublic bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,42 +38,42 @@ var routes = Routes{
|
|||||||
Route{
|
Route{
|
||||||
"GetRequestByID",
|
"GetRequestByID",
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
"/api/request/:id",
|
"/api/request/<id>",
|
||||||
requestGet,
|
requestGet,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
Route{
|
Route{
|
||||||
"GetCompleteRequestByID",
|
"GetCompleteRequestByID",
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
"/api/request/:id/complete",
|
"/api/request/<id>/complete",
|
||||||
requestGetComplete,
|
requestGetComplete,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
Route{
|
Route{
|
||||||
"GetFullRequestByID",
|
"GetFullRequestByID",
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
"/api/request/:id/full",
|
"/api/request/<id>/full",
|
||||||
requestGetFull,
|
requestGetFull,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
Route{
|
Route{
|
||||||
"AddNewHistoryEntry",
|
"AddNewHistoryEntry",
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
"/api/request/:id/history",
|
"/api/request/<id>/history",
|
||||||
requestAddHistory,
|
requestAddHistory,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
Route{
|
Route{
|
||||||
"AddNewNote",
|
"AddNewNote",
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
"/api/request/:id/note",
|
"/api/request/<id>/note",
|
||||||
requestAddNote,
|
requestAddNote,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
Route{
|
Route{
|
||||||
"GetNotesForRequest",
|
"GetNotesForRequest",
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
"/api/request/:id/notes",
|
"/api/request/<id>/notes",
|
||||||
requestGetNotes,
|
requestGetNotes,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user