handlers.go 7.5 KB

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