handlers.go 5.9 KB

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