handlers.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. package handlers
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "path"
  9. "path/filepath"
  10. "reflect"
  11. "runtime/debug"
  12. "strconv"
  13. "strings"
  14. "gogs.carducci-dante.gov.it/karmen/core/config"
  15. "gogs.carducci-dante.gov.it/karmen/core/orm"
  16. "gogs.carducci-dante.gov.it/karmen/core/renderer"
  17. jwtmiddleware "github.com/auth0/go-jwt-middleware"
  18. jwt "github.com/dgrijalva/jwt-go"
  19. "github.com/gorilla/mux"
  20. "github.com/gorilla/sessions"
  21. "github.com/jinzhu/inflection"
  22. )
  23. type User struct {
  24. Name string
  25. Admin bool
  26. }
  27. type PathPattern struct {
  28. PathPattern string
  29. RedirectPattern string
  30. Methods []string
  31. }
  32. var (
  33. signingKey = []byte(config.Config.Keys.JWTSigningKey)
  34. store = sessions.NewCookieStore([]byte(config.Config.Keys.CookieStoreKey))
  35. jwtCookie = jwtmiddleware.New(jwtmiddleware.Options{
  36. ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
  37. return signingKey, nil
  38. },
  39. SigningMethod: jwt.SigningMethodHS256,
  40. Extractor: fromCookie,
  41. ErrorHandler: onError,
  42. })
  43. jwtHeader = jwtmiddleware.New(jwtmiddleware.Options{
  44. ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
  45. return signingKey, nil
  46. },
  47. SigningMethod: jwt.SigningMethodHS256,
  48. })
  49. )
  50. func (pp PathPattern) RedirectPath(model string, id ...uint) string {
  51. if len(id) > 0 {
  52. return fmt.Sprintf(pp.RedirectPattern, model, id[0], model)
  53. }
  54. return fmt.Sprintf(pp.RedirectPattern, model, model)
  55. }
  56. func (pp PathPattern) Path(model string) string {
  57. return fmt.Sprintf(pp.PathPattern, model)
  58. }
  59. func modelName(s interface{}) string {
  60. if t := reflect.TypeOf(s); t.Kind() == reflect.Ptr {
  61. return t.Elem().Name()
  62. } else {
  63. return t.Name()
  64. }
  65. }
  66. func pluralizedModelName(s interface{}) string {
  67. return inflection.Plural(strings.ToLower(modelName(s)))
  68. }
  69. // Generate CRUD handlers for models
  70. func generateHandler(r *mux.Router, model interface{}) {
  71. var (
  72. patterns []PathPattern = []PathPattern{
  73. PathPattern{"/%s", "", []string{"GET"}},
  74. PathPattern{"/%s/{id}", "", []string{"GET"}},
  75. PathPattern{"/%s/create/", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
  76. PathPattern{"/%s/{id}/update", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
  77. PathPattern{"/%s/{id}/delete", "/%s?format=html&tpl_layout=base&tpl_content=%s", []string{"DELETE"}},
  78. }
  79. apiPatterns []PathPattern
  80. executePatterns []PathPattern = []PathPattern{
  81. PathPattern{"/%s/{id}/execute", "", []string{"GET"}},
  82. }
  83. filePatterns []PathPattern = []PathPattern{
  84. PathPattern{"/%s/{id}/files/{filename}", "", []string{"GET"}},
  85. }
  86. )
  87. // Generate API patterns prefixing "api" path
  88. for _, p := range patterns {
  89. apiPatterns = append(apiPatterns, PathPattern{path.Join("/", "api", p.PathPattern), "", []string{"GET"}})
  90. }
  91. // Install standard paths
  92. for _, pattern := range patterns {
  93. log.Printf("Register handler %s", pattern.Path(pluralizedModelName(model)))
  94. r.Handle(pattern.Path(pluralizedModelName(model)), jwtCookie.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
  95. }
  96. // Install API paths
  97. for _, pattern := range apiPatterns {
  98. log.Printf("Register handler %s", pattern.Path(pluralizedModelName(model)))
  99. r.Handle(pattern.Path(pluralizedModelName(model)), jwtHeader.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
  100. }
  101. if model == "documents" {
  102. for _, pattern := range executePatterns {
  103. log.Printf("Register handler %s", pattern.Path(pluralizedModelName(model)))
  104. r.Handle(pattern.Path(pluralizedModelName(model)), jwtCookie.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
  105. }
  106. }
  107. if model == "jobs" {
  108. for _, pattern := range filePatterns {
  109. log.Printf("Register handler %s", pattern.Path(pluralizedModelName(model)))
  110. r.Handle(pattern.Path(pluralizedModelName(model)), jwtCookie.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
  111. }
  112. }
  113. }
  114. func Handlers(models []interface{}) *mux.Router {
  115. r := mux.NewRouter()
  116. // Authentication
  117. r.Handle("/login", loginHandler())
  118. r.Handle("/logout", logoutHandler())
  119. // Dashboard
  120. r.Handle("/", jwtCookie.Handler(recoverHandler(homeHandler())))
  121. // Generate CRUD handlers
  122. for _, model := range models {
  123. generateHandler(r, model)
  124. }
  125. r.Handle("/documents/{id}/execute", jwtCookie.Handler(recoverHandler(executeHandler()))).Methods("GET")
  126. // Token handling
  127. r.Handle("/get_token", tokenHandler())
  128. // Static file server
  129. r.PathPrefix("/").Handler(http.FileServer(http.Dir("./dist/")))
  130. return r
  131. }
  132. func onError(w http.ResponseWriter, r *http.Request, err string) {
  133. http.Redirect(w, r, "/login?tpl_layout=login&tpl_content=login", http.StatusTemporaryRedirect)
  134. }
  135. func respondWithStaticFile(w http.ResponseWriter, filename string) error {
  136. f, err := ioutil.ReadFile(filepath.Join("public/html", filename))
  137. if err != nil {
  138. return err
  139. }
  140. w.Write(f)
  141. return nil
  142. }
  143. func fromCookie(r *http.Request) (string, error) {
  144. session, err := store.Get(r, "login-session")
  145. if err != nil {
  146. return "", nil
  147. }
  148. if session.Values["token"] == nil {
  149. return "", nil
  150. }
  151. token := session.Values["token"].([]uint8)
  152. return string(token), nil
  153. }
  154. func recoverHandler(next http.Handler) http.Handler {
  155. fn := func(w http.ResponseWriter, r *http.Request) {
  156. defer func() {
  157. if err := recover(); err != nil {
  158. panicMsg := fmt.Sprintf("PANIC: %v\n\n== STACKTRACE ==\n%s", err, debug.Stack())
  159. log.Print(panicMsg)
  160. http.Error(w, panicMsg, http.StatusInternalServerError)
  161. }
  162. }()
  163. next.ServeHTTP(w, r)
  164. }
  165. return http.HandlerFunc(fn)
  166. }
  167. func get(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
  168. format := r.URL.Query().Get("format")
  169. getFn, err := orm.GetFunc(pattern.Path(model))
  170. if err != nil {
  171. log.Println("Error:", err)
  172. respondWithError(w, r, err)
  173. } else {
  174. data, err := getFn(mux.Vars(r), r)
  175. if err != nil {
  176. renderer.Render[format](w, r, err)
  177. } else {
  178. renderer.Render[format](w, r, data, r.URL.Query())
  179. }
  180. }
  181. }
  182. func post(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
  183. var (
  184. data interface{}
  185. err error
  186. )
  187. respFormat := renderer.GetContentFormat(r)
  188. postFn, err := orm.GetFunc(pattern.Path(model))
  189. if err != nil {
  190. respondWithError(w, r, err)
  191. } else {
  192. data, err = postFn(mux.Vars(r), r)
  193. if err != nil {
  194. respondWithError(w, r, err)
  195. } else if pattern.RedirectPattern != "" {
  196. if id := mux.Vars(r)["id"]; id != "" {
  197. modelId, _ := strconv.Atoi(id)
  198. http.Redirect(w, r, pattern.RedirectPath(model, uint(modelId)), http.StatusSeeOther)
  199. } else {
  200. http.Redirect(w, r, pattern.RedirectPath(model, data.(orm.IDer).GetID()), http.StatusSeeOther)
  201. }
  202. } else {
  203. renderer.Render[respFormat](w, r, data.(orm.IDer).GetID())
  204. }
  205. }
  206. }
  207. func delete(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
  208. var (
  209. data interface{}
  210. err error
  211. )
  212. respFormat := renderer.GetContentFormat(r)
  213. postFn, err := orm.GetFunc(pattern.Path(model))
  214. if err != nil {
  215. renderer.Render[r.URL.Query().Get("format")](w, r, err)
  216. }
  217. data, err = postFn(mux.Vars(r), r)
  218. if err != nil {
  219. renderer.Render["html"](w, r, err)
  220. } else if pattern.RedirectPattern != "" {
  221. var data struct {
  222. RedirectUrl string `json:"redirect_url"`
  223. }
  224. data.RedirectUrl = pattern.RedirectPath(model)
  225. w.Header().Set("Content-Type", "application/json")
  226. json.NewEncoder(w).Encode(data)
  227. } else {
  228. renderer.Render[respFormat](w, r, data.(orm.IDer).GetID())
  229. }
  230. }
  231. func respondWithError(w http.ResponseWriter, r *http.Request, err error) {
  232. respFormat := renderer.GetContentFormat(r)
  233. w.WriteHeader(http.StatusInternalServerError)
  234. renderer.Render[respFormat](w, r, err)
  235. }
  236. func modelHandler(model string, pattern PathPattern) http.Handler {
  237. fn := func(w http.ResponseWriter, r *http.Request) {
  238. switch r.Method {
  239. case "GET":
  240. get(w, r, model, pattern)
  241. case "POST":
  242. post(w, r, model, pattern)
  243. case "DELETE":
  244. delete(w, r, model, pattern)
  245. }
  246. }
  247. return http.HandlerFunc(fn)
  248. }
  249. func executeHandler() http.Handler {
  250. fn := func(w http.ResponseWriter, r *http.Request) {
  251. switch r.Method {
  252. case "GET":
  253. get(w, r, model, pattern)
  254. case "POST":
  255. post(w, r, model, pattern)
  256. case "DELETE":
  257. delete(w, r, model, pattern)
  258. }
  259. }
  260. return http.HandlerFunc(fn)
  261. }
  262. func homeHandler() http.Handler {
  263. fn := func(w http.ResponseWriter, r *http.Request) {
  264. http.Redirect(w, r, "/teachers?format=html&tpl_layout=teachers&tpl_content=teachers", http.StatusSeeOther)
  265. }
  266. return http.HandlerFunc(fn)
  267. }