main.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package main
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "strconv"
  9. "strings"
  10. log "github.com/Sirupsen/logrus"
  11. haikunator "github.com/atrox/haikunatorgo"
  12. "github.com/gin-contrib/sessions"
  13. "github.com/gin-gonic/gin"
  14. "github.com/msoedov/hacker-slides/files"
  15. )
  16. const sessionHeader = "slide-session"
  17. func Header(c *gin.Context, key string) string {
  18. if values, _ := c.Request.Header[key]; len(values) > 0 {
  19. return values[0]
  20. }
  21. return ""
  22. }
  23. func BasicAuth() gin.HandlerFunc {
  24. realm := "Authorization Required"
  25. realm = "Basic realm=" + strconv.Quote(realm)
  26. user := os.Getenv("USER")
  27. password := os.Getenv("PASSWORD")
  28. enabled := isEnabled(user, password)
  29. if enabled {
  30. log.Warn("Auth mode enabled")
  31. log.Warn(fmt.Sprintf("Visit http://%s:%s@0.0.0.0:8080", user, password))
  32. }
  33. return func(c *gin.Context) {
  34. header := Header(c, "Authorization")
  35. if enabled && header != authorizationHeader(user, password) {
  36. // Credentials doesn't match, we return 401 and abort handlers chain.
  37. c.Header("WWW-Authenticate", realm)
  38. c.AbortWithStatus(401)
  39. return
  40. }
  41. c.Next()
  42. }
  43. }
  44. func isEnabled(user, password string) bool {
  45. switch {
  46. case user == "":
  47. return false
  48. case password == "":
  49. return false
  50. default:
  51. return true
  52. }
  53. }
  54. func authorizationHeader(user, password string) string {
  55. base := user + ":" + password
  56. return "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
  57. }
  58. func SlidePath(name string) string {
  59. return fmt.Sprintf("slides/%s.md", name)
  60. }
  61. func NewApp() *gin.Engine {
  62. r := gin.Default()
  63. store := sessions.NewCookieStore([]byte("secret"))
  64. r.Use(sessions.Sessions(sessionHeader, store))
  65. r.Use(BasicAuth())
  66. r.LoadHTMLGlob("templates/*.tmpl")
  67. r.Static("/static", "./static")
  68. r.GET("/", func(c *gin.Context) {
  69. isNew := c.Query("new")
  70. latest := files.LatestFileIn("slides")
  71. log.WithFields(log.Fields{
  72. "name": latest,
  73. "isNew": isNew,
  74. }).Info("Restoring latest point")
  75. var path, name string
  76. if latest == "" || isNew != "" {
  77. haikunator := haikunator.New()
  78. haikunator.TokenLength = 0
  79. name = haikunator.Haikunate()
  80. } else {
  81. name = strings.Replace(latest, ".md", "", 1)
  82. }
  83. path = SlidePath(name)
  84. log.WithFields(log.Fields{
  85. "path": path,
  86. }).Info("A new session")
  87. session := sessions.Default(c)
  88. session.Set("name", path)
  89. session.Save()
  90. c.Writer.Header().Set("Location", fmt.Sprintf("/stash/edit/%s", name))
  91. c.HTML(302, "index.tmpl", gin.H{
  92. "pubTo": path,
  93. })
  94. })
  95. mustHaveSession := func(c *gin.Context) (string, error) {
  96. session := sessions.Default(c)
  97. val := session.Get("name")
  98. emptySession := errors.New("Emtpy session")
  99. if val == nil {
  100. c.String(400, "No context")
  101. return "", emptySession
  102. }
  103. log.WithFields(log.Fields{
  104. "path": val,
  105. }).Info("Got session")
  106. path, ok := val.(string)
  107. if !ok {
  108. c.String(400, "No context")
  109. return "", emptySession
  110. }
  111. return path, nil
  112. }
  113. r.GET("/slides.md", func(c *gin.Context) {
  114. path, err := mustHaveSession(c)
  115. if err != nil {
  116. return
  117. }
  118. if _, err := os.Stat(path); err != nil {
  119. // copy sample markdown file to the path
  120. body, err := ioutil.ReadFile("initial-slides.md")
  121. if err != nil {
  122. panic(err)
  123. }
  124. ioutil.WriteFile(path, body, 0644)
  125. c.String(200, string(body))
  126. return
  127. }
  128. body, err := ioutil.ReadFile(path)
  129. if err != nil {
  130. panic(err)
  131. }
  132. c.String(200, string(body))
  133. })
  134. r.PUT("/slides.md", func(c *gin.Context) {
  135. path, err := mustHaveSession(c)
  136. if err != nil {
  137. return
  138. }
  139. body, _ := ioutil.ReadAll(c.Request.Body)
  140. ioutil.WriteFile(path, body, 0644)
  141. log.WithFields(log.Fields{
  142. "size": len(body),
  143. "file": path,
  144. }).Info("Wrote to file")
  145. c.String(200, "")
  146. })
  147. r.GET("/stash", func(c *gin.Context) {
  148. files, err := ioutil.ReadDir("slides")
  149. if err != nil {
  150. log.Fatal(err)
  151. }
  152. var stash []string
  153. for _, file := range files {
  154. stash = append(stash, file.Name())
  155. }
  156. c.HTML(200, "stash.tmpl", gin.H{
  157. "stash": stash,
  158. })
  159. })
  160. r.GET("/stash/edit/:name", func(c *gin.Context) {
  161. name := c.Param("name")
  162. log.WithFields(log.Fields{
  163. "name": name,
  164. }).Info("Restore session?")
  165. if strings.HasSuffix(name, ".md") {
  166. name = name[0 : len(name)-3]
  167. }
  168. path := SlidePath(name)
  169. session := sessions.Default(c)
  170. session.Set("name", path)
  171. session.Save()
  172. c.HTML(200, "index.tmpl", gin.H{
  173. "pubTo": path,
  174. })
  175. })
  176. r.GET("/published/slides/:name", func(c *gin.Context) {
  177. name := c.Param("name")
  178. log.WithFields(log.Fields{
  179. "name": name,
  180. }).Info("Published")
  181. if strings.HasSuffix(name, ".md") {
  182. name = name[0 : len(name)-3]
  183. }
  184. path := SlidePath(name)
  185. session := sessions.Default(c)
  186. session.Set("name", path)
  187. session.Save()
  188. c.HTML(200, "slides.tmpl", gin.H{
  189. "pubTo": path,
  190. })
  191. })
  192. return r
  193. }
  194. func main() {
  195. r := NewApp()
  196. port := "8080"
  197. if len(os.Args) > 1 {
  198. port = os.Args[1]
  199. } else {
  200. envPort := os.Getenv("PORT")
  201. if len(envPort) > 0 {
  202. port = envPort
  203. }
  204. }
  205. log.Info("Started http://0.0.0.0:8080")
  206. r.Run(fmt.Sprintf(":%s", port))
  207. }