main.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "sort"
  8. "strings"
  9. haikunator "github.com/atrox/haikunatorgo"
  10. "github.com/gin-contrib/sessions"
  11. "github.com/gin-contrib/sessions/cookie"
  12. "github.com/gin-gonic/gin"
  13. cache "github.com/hashicorp/golang-lru"
  14. "github.com/msoedov/hacker-slides/auth"
  15. "github.com/msoedov/hacker-slides/files"
  16. log "github.com/sirupsen/logrus"
  17. )
  18. const sessionHeader = "slide-session"
  19. func SlidePath(name string) string {
  20. return fmt.Sprintf("slides/%s.md", name)
  21. }
  22. func NewApp() *gin.Engine {
  23. r := gin.Default()
  24. store := cookie.NewStore([]byte("secret"))
  25. arc, err := cache.NewARC(10)
  26. if err != nil {
  27. log.Fatalf("Failied to allocate cache %#v", err)
  28. }
  29. r.Use(sessions.Sessions(sessionHeader, store))
  30. r.Use(auth.BasicAuth())
  31. r.LoadHTMLGlob("templates/*.tmpl")
  32. r.Static("/static", "./static")
  33. r.Static("/images", "./slides/images")
  34. r.GET("/", func(c *gin.Context) {
  35. isNew := c.Query("new")
  36. latest := files.LatestFileIn("slides")
  37. log.WithFields(log.Fields{
  38. "name": latest,
  39. "isNew": isNew,
  40. }).Info("Restoring latest point")
  41. var path, name string
  42. if latest == "" || isNew != "" {
  43. haikunator := haikunator.New()
  44. haikunator.TokenLength = 0
  45. name = haikunator.Haikunate()
  46. } else {
  47. name = strings.Replace(latest, ".md", "", 1)
  48. }
  49. path = SlidePath(name)
  50. log.WithFields(log.Fields{
  51. "path": path,
  52. }).Info("A new session")
  53. session := sessions.Default(c)
  54. session.Set("name", path)
  55. session.Save()
  56. c.Writer.Header().Set("Location", fmt.Sprintf("/stash/edit/%s", name))
  57. c.HTML(302, "index.tmpl", gin.H{
  58. "pubTo": path,
  59. })
  60. })
  61. mustHaveSession := func(c *gin.Context) (string, error) {
  62. session := sessions.Default(c)
  63. val := session.Get("name")
  64. emptySession := errors.New("Empty session")
  65. if val == nil {
  66. c.String(400, "No context")
  67. return "", emptySession
  68. }
  69. log.WithFields(log.Fields{
  70. "path": val,
  71. }).Info("Got session")
  72. path, ok := val.(string)
  73. if !ok {
  74. c.String(400, "No context")
  75. return "", emptySession
  76. }
  77. return path, nil
  78. }
  79. r.GET("/slides.md", func(c *gin.Context) {
  80. path, err := mustHaveSession(c)
  81. if err != nil {
  82. return
  83. }
  84. if _, err := os.Stat(path); err != nil {
  85. // copy sample markdown file to the path
  86. body, err := ioutil.ReadFile("initial-slides.md")
  87. if err != nil {
  88. panic(err)
  89. }
  90. ioutil.WriteFile(path, body, 0644)
  91. c.String(200, string(body))
  92. return
  93. }
  94. var slide string
  95. cached, ok := arc.Get(path)
  96. if ok {
  97. slide = string(cached.([]byte))
  98. } else {
  99. body, err := ioutil.ReadFile(path)
  100. if err != nil {
  101. log.Errorf("Failied to read file %#v", err)
  102. c.Abort()
  103. return
  104. }
  105. slide = string(body)
  106. }
  107. c.String(200, slide)
  108. })
  109. r.PUT("/slides.md", func(c *gin.Context) {
  110. path, err := mustHaveSession(c)
  111. if err != nil {
  112. return
  113. }
  114. body, _ := ioutil.ReadAll(c.Request.Body)
  115. arc.Add(path, body)
  116. go ioutil.WriteFile(path, body, 0644)
  117. log.WithFields(log.Fields{
  118. "size": len(body),
  119. "file": path,
  120. }).Info("Async wrote to file")
  121. c.String(200, "")
  122. })
  123. r.GET("/stash", func(c *gin.Context) {
  124. files, err := ioutil.ReadDir("slides")
  125. if err != nil {
  126. log.Fatal(err)
  127. }
  128. sort.Slice(files, func(i, j int) bool {
  129. return files[i].ModTime().Unix() > files[j].ModTime().Unix()
  130. })
  131. var stash []string
  132. for _, file := range files {
  133. if file.IsDir() {
  134. continue
  135. }
  136. stash = append(stash, file.Name())
  137. }
  138. c.HTML(200, "stash.tmpl", gin.H{
  139. "stash": stash,
  140. })
  141. })
  142. r.GET("/stash/edit/:name", func(c *gin.Context) {
  143. name := c.Param("name")
  144. log.WithFields(log.Fields{
  145. "name": name,
  146. }).Info("Restore session?")
  147. if strings.HasSuffix(name, ".md") {
  148. name = name[0 : len(name)-3]
  149. }
  150. path := SlidePath(name)
  151. session := sessions.Default(c)
  152. session.Set("name", path)
  153. session.Save()
  154. c.HTML(200, "index.tmpl", gin.H{
  155. "pubTo": path,
  156. })
  157. })
  158. r.GET("/published/slides/:name", func(c *gin.Context) {
  159. name := c.Param("name")
  160. log.WithFields(log.Fields{
  161. "name": name,
  162. }).Info("Published")
  163. if strings.HasSuffix(name, ".md") {
  164. name = name[0 : len(name)-3]
  165. }
  166. path := SlidePath(name)
  167. session := sessions.Default(c)
  168. session.Set("name", path)
  169. session.Save()
  170. c.HTML(200, "slides.tmpl", gin.H{
  171. "pubTo": path,
  172. })
  173. })
  174. return r
  175. }
  176. func main() {
  177. r := NewApp()
  178. port := "8080"
  179. if len(os.Args) > 1 {
  180. port = os.Args[1]
  181. } else {
  182. envPort := os.Getenv("PORT")
  183. if len(envPort) > 0 {
  184. port = envPort
  185. }
  186. }
  187. log.Info("Started http://0.0.0.0:8080")
  188. r.Run(fmt.Sprintf(":%s", port))
  189. }