handlers.go 6.0 KB

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