package handlers import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "path" "path/filepath" "reflect" "runtime/debug" "strconv" "strings" "gogs.carducci-dante.gov.it/karmen/core/config" "gogs.carducci-dante.gov.it/karmen/core/orm" "gogs.carducci-dante.gov.it/karmen/core/renderer" jwtmiddleware "github.com/auth0/go-jwt-middleware" jwt "github.com/dgrijalva/jwt-go" "github.com/gorilla/mux" "github.com/gorilla/sessions" "github.com/jinzhu/inflection" ) type User struct { Name string Admin bool } type PathPattern struct { PathPattern string RedirectPattern string Methods []string } var ( signingKey = []byte(config.Config.Keys.JWTSigningKey) store = sessions.NewCookieStore([]byte(config.Config.Keys.CookieStoreKey)) jwtCookie = jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { return signingKey, nil }, SigningMethod: jwt.SigningMethodHS256, Extractor: fromCookie, ErrorHandler: onError, }) jwtHeader = jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { return signingKey, nil }, SigningMethod: jwt.SigningMethodHS256, }) ) 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 filePatterns []PathPattern = []PathPattern{ PathPattern{"/%s/{id}/files/{filename}", "", []string{"GET"}}, } ) // 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...) } if modelName(model) == "Job" { for _, pattern := range filePatterns { r.Handle(pattern.Path(pluralizedModelName(model)), jwtCookie.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) } r.Handle("/documents/{id}/execute", jwtCookie.Handler(recoverHandler(executeHandler()))).Methods("GET") // 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 executeHandler() http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { format := r.URL.Query().Get("format") data, err := orm.GetDocumentExecute(mux.Vars(r), r) if err != nil { renderer.Render[format](w, r, err) } else { renderer.Render[format](w, r, data, r.URL.Query()) } } return http.HandlerFunc(fn) } func homeHandler() http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/teachers?format=html&tpl_layout=teachers&tpl_content=teachers", http.StatusSeeOther) } return http.HandlerFunc(fn) }