handlers.go 6.0 KB

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