handlers.go 7.6 KB

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