handlers.go 7.0 KB

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