123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- 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)
- }
|