308 lines
6.9 KiB
Go
308 lines
6.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime/debug"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.andreafazzi.eu/andrea/oef/orm"
|
|
"git.andreafazzi.eu/andrea/oef/renderer"
|
|
|
|
jwt "github.com/dgrijalva/jwt-go"
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/jinzhu/inflection"
|
|
)
|
|
|
|
type PathPattern struct {
|
|
PathPattern string
|
|
RedirectPattern string
|
|
Methods []string
|
|
}
|
|
|
|
func (pp PathPattern) RedirectPath(model string, id ...uint) string {
|
|
if len(id) > 0 {
|
|
return fmt.Sprintf(pp.RedirectPattern, model, id[0], model)
|
|
}
|
|
return fmt.Sprintf(pp.RedirectPattern, model, model)
|
|
}
|
|
|
|
func (pp PathPattern) Path(model string) string {
|
|
return fmt.Sprintf(pp.PathPattern, model)
|
|
}
|
|
|
|
func modelName(s interface{}) string {
|
|
if t := reflect.TypeOf(s); t.Kind() == reflect.Ptr {
|
|
return t.Elem().Name()
|
|
} else {
|
|
return t.Name()
|
|
}
|
|
}
|
|
|
|
func pluralizedModelName(s interface{}) string {
|
|
return inflection.Plural(strings.ToLower(modelName(s)))
|
|
}
|
|
|
|
// Generate CRUD handlers for models
|
|
func generateHandler(r *mux.Router, model interface{}) {
|
|
var (
|
|
patterns []PathPattern = []PathPattern{
|
|
PathPattern{"/%s", "", []string{"GET"}},
|
|
PathPattern{"/%s/{id}", "", []string{"GET"}},
|
|
PathPattern{"/%s/create/", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
|
|
PathPattern{"/%s/{id}/update", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
|
|
PathPattern{"/%s/{id}/delete", "/%s?format=html&tpl_layout=base&tpl_content=%s", []string{"DELETE"}},
|
|
}
|
|
|
|
apiPatterns []PathPattern
|
|
)
|
|
|
|
// Generate API patterns prefixing "api" path
|
|
|
|
for _, p := range patterns {
|
|
apiPatterns = append(apiPatterns, PathPattern{path.Join("/", "api", p.PathPattern), "", p.Methods})
|
|
}
|
|
|
|
// Install standard paths
|
|
|
|
for _, pattern := range patterns {
|
|
r.Handle(
|
|
pattern.Path(
|
|
pluralizedModelName(model),
|
|
),
|
|
jwtCookie.Handler(
|
|
recoverHandler(
|
|
modelHandler(
|
|
pluralizedModelName(model),
|
|
pattern,
|
|
)))).Methods(pattern.Methods...)
|
|
}
|
|
|
|
// Install API paths
|
|
|
|
for _, pattern := range apiPatterns {
|
|
r.Handle(pattern.Path(
|
|
pluralizedModelName(model),
|
|
),
|
|
jwtHeader.Handler(
|
|
recoverHandler(
|
|
modelHandler(
|
|
pluralizedModelName(model),
|
|
pattern,
|
|
)))).Methods(pattern.Methods...)
|
|
}
|
|
|
|
}
|
|
|
|
func Handlers(models []interface{}) *mux.Router {
|
|
r := mux.NewRouter()
|
|
|
|
// Authentication
|
|
|
|
r.Handle("/login", loginHandler())
|
|
r.Handle("/logout", logoutHandler())
|
|
|
|
// Dashboard
|
|
|
|
r.Handle("/", jwtCookie.Handler(recoverHandler(homeHandler())))
|
|
|
|
// Generate CRUD handlers
|
|
|
|
for _, model := range models {
|
|
generateHandler(r, model)
|
|
}
|
|
|
|
// Token handling
|
|
|
|
r.Handle("/get_token", tokenHandler())
|
|
|
|
// Static file server
|
|
|
|
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./dist/")))
|
|
|
|
return r
|
|
}
|
|
|
|
func onError(w http.ResponseWriter, r *http.Request, err string) {
|
|
http.Redirect(w, r, "/login?tpl_layout=login&tpl_content=login", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func respondWithStaticFile(w http.ResponseWriter, filename string) error {
|
|
f, err := ioutil.ReadFile(filepath.Join("public/html", filename))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.Write(f)
|
|
return nil
|
|
}
|
|
|
|
func fromCookie(r *http.Request) (string, error) {
|
|
session, err := store.Get(r, "login-session")
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
|
|
if session.Values["token"] == nil {
|
|
return "", nil
|
|
}
|
|
|
|
token := session.Values["token"].([]uint8)
|
|
|
|
return string(token), nil
|
|
}
|
|
|
|
func recoverHandler(next http.Handler) http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
panicMsg := fmt.Sprintf("PANIC: %v\n\n== STACKTRACE ==\n%s", err, debug.Stack())
|
|
log.Print(panicMsg)
|
|
http.Error(w, panicMsg, http.StatusInternalServerError)
|
|
}
|
|
}()
|
|
next.ServeHTTP(w, r)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func get(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
|
|
format := r.URL.Query().Get("format")
|
|
getFn, err := orm.GetFunc(pattern.Path(model))
|
|
if err != nil {
|
|
log.Println("Error:", err)
|
|
respondWithError(w, r, err)
|
|
} else {
|
|
data, err := getFn(mux.Vars(r), r)
|
|
if err != nil {
|
|
renderer.Render[format](w, r, err)
|
|
} else {
|
|
renderer.Render[format](w, r, data, r.URL.Query())
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func post(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
|
|
var (
|
|
data interface{}
|
|
err error
|
|
)
|
|
|
|
respFormat := renderer.GetContentFormat(r)
|
|
postFn, err := orm.GetFunc(pattern.Path(model))
|
|
|
|
if err != nil {
|
|
respondWithError(w, r, err)
|
|
} else {
|
|
data, err = postFn(mux.Vars(r), r)
|
|
if err != nil {
|
|
respondWithError(w, r, err)
|
|
} else if pattern.RedirectPattern != "" {
|
|
if id := mux.Vars(r)["id"]; id != "" {
|
|
modelId, _ := strconv.Atoi(id)
|
|
http.Redirect(w, r, pattern.RedirectPath(model, uint(modelId)), http.StatusSeeOther)
|
|
} else {
|
|
http.Redirect(w, r, pattern.RedirectPath(model, data.(orm.IDer).GetID()), http.StatusSeeOther)
|
|
}
|
|
} else {
|
|
renderer.Render[respFormat](w, r, data.(orm.IDer).GetID())
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func delete(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
|
|
var (
|
|
data interface{}
|
|
err error
|
|
)
|
|
|
|
respFormat := renderer.GetContentFormat(r)
|
|
|
|
postFn, err := orm.GetFunc(pattern.Path(model))
|
|
if err != nil {
|
|
renderer.Render[r.URL.Query().Get("format")](w, r, err)
|
|
}
|
|
data, err = postFn(mux.Vars(r), r)
|
|
if err != nil {
|
|
renderer.Render["html"](w, r, err)
|
|
} else if pattern.RedirectPattern != "" {
|
|
var data struct {
|
|
RedirectUrl string `json:"redirect_url"`
|
|
}
|
|
data.RedirectUrl = pattern.RedirectPath(model)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(data)
|
|
} else {
|
|
renderer.Render[respFormat](w, r, data.(orm.IDer).GetID())
|
|
}
|
|
}
|
|
|
|
func respondWithError(w http.ResponseWriter, r *http.Request, err error) {
|
|
respFormat := renderer.GetContentFormat(r)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
renderer.Render[respFormat](w, r, err)
|
|
}
|
|
|
|
func modelHandler(model string, pattern PathPattern) http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// Replace "api" prefix
|
|
pattern.PathPattern = strings.Replace(pattern.PathPattern, "/api", "", -1)
|
|
|
|
switch r.Method {
|
|
case "GET":
|
|
get(w, r, model, pattern)
|
|
|
|
case "POST":
|
|
post(w, r, model, pattern)
|
|
|
|
case "DELETE":
|
|
delete(w, r, model, pattern)
|
|
}
|
|
}
|
|
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func homeHandler() http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
claims := r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
|
|
|
|
switch claims["role"] {
|
|
|
|
case "participant":
|
|
http.Redirect(
|
|
w,
|
|
r,
|
|
fmt.Sprintf(
|
|
"/participants/%s?format=html&tpl_layout=base&tpl_content=participants_show",
|
|
claims["user_id"].(string)),
|
|
http.StatusSeeOther,
|
|
)
|
|
|
|
case "school":
|
|
// To be implemented.
|
|
|
|
default:
|
|
http.Redirect(
|
|
w,
|
|
r,
|
|
"/contests?format=html&tpl_layout=base&tpl_content=contests",
|
|
http.StatusSeeOther,
|
|
)
|
|
}
|
|
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|