Publish HTML quickly. https://html.house
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

223 lines
5.7 KiB

  1. package htmlhouse
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "regexp"
  8. "strings"
  9. "time"
  10. "github.com/gorilla/mux"
  11. "github.com/writeas/impart"
  12. "github.com/writeas/nerds/store"
  13. "github.com/writeas/web-core/bots"
  14. )
  15. func createHouse(app *app, w http.ResponseWriter, r *http.Request) error {
  16. html := r.FormValue("html")
  17. if strings.TrimSpace(html) == "" {
  18. return impart.HTTPError{http.StatusBadRequest, "Supply something to publish."}
  19. }
  20. houseID := store.GenerateFriendlyRandomString(8)
  21. _, err := app.db.Exec("INSERT INTO houses (id, html) VALUES (?, ?)", houseID, html)
  22. if err != nil {
  23. return err
  24. }
  25. if err = app.session.writeToken(w, houseID); err != nil {
  26. return err
  27. }
  28. resUser := newSessionInfo(houseID)
  29. return impart.WriteSuccess(w, resUser, http.StatusCreated)
  30. }
  31. func renovateHouse(app *app, w http.ResponseWriter, r *http.Request) error {
  32. vars := mux.Vars(r)
  33. houseID := vars["house"]
  34. html := r.FormValue("html")
  35. if strings.TrimSpace(html) == "" {
  36. return impart.HTTPError{http.StatusBadRequest, "Supply something to publish."}
  37. }
  38. authHouseID, err := app.session.readToken(r)
  39. if err != nil {
  40. return err
  41. }
  42. if authHouseID != houseID {
  43. return impart.HTTPError{http.StatusUnauthorized, "Bad token for this ⌂ house ⌂."}
  44. }
  45. _, err = app.db.Exec("UPDATE houses SET html = ? WHERE id = ?", html, houseID)
  46. if err != nil {
  47. return err
  48. }
  49. if err = app.session.writeToken(w, houseID); err != nil {
  50. return err
  51. }
  52. resUser := newSessionInfo(houseID)
  53. return impart.WriteSuccess(w, resUser, http.StatusOK)
  54. }
  55. func getHouseStats(app *app, houseID string) (*time.Time, int64, error) {
  56. var created time.Time
  57. var views int64
  58. err := app.db.QueryRow("SELECT created, view_count FROM houses WHERE id = ?", houseID).Scan(&created, &views)
  59. switch {
  60. case err == sql.ErrNoRows:
  61. return nil, 0, impart.HTTPError{http.StatusNotFound, "Return to sender. Address unknown."}
  62. case err != nil:
  63. fmt.Printf("Couldn't fetch: %v\n", err)
  64. return nil, 0, err
  65. }
  66. return &created, views, nil
  67. }
  68. func getHouseHTML(app *app, houseID string) (string, error) {
  69. var html string
  70. err := app.db.QueryRow("SELECT html FROM houses WHERE id = ?", houseID).Scan(&html)
  71. switch {
  72. case err == sql.ErrNoRows:
  73. return "", impart.HTTPError{http.StatusNotFound, "Return to sender. Address unknown."}
  74. case err != nil:
  75. fmt.Printf("Couldn't fetch: %v\n", err)
  76. return "", err
  77. }
  78. return html, nil
  79. }
  80. var (
  81. htmlReg = regexp.MustCompile("<html( ?.*)>")
  82. )
  83. func getHouse(app *app, w http.ResponseWriter, r *http.Request) error {
  84. vars := mux.Vars(r)
  85. houseID := vars["house"]
  86. // Fetch HTML
  87. html, err := getHouseHTML(app, houseID)
  88. if err != nil {
  89. if err, ok := err.(impart.HTTPError); ok {
  90. if err.Status == http.StatusNotFound {
  91. page, err := ioutil.ReadFile(app.cfg.StaticDir + "/404.html")
  92. if err != nil {
  93. page = []byte("<!DOCTYPE html><html><body>HTMLlot.</body></html>")
  94. }
  95. fmt.Fprintf(w, "%s", page)
  96. return nil
  97. }
  98. }
  99. return err
  100. }
  101. // Add nofollow meta tag
  102. if strings.Index(html, "<head>") == -1 {
  103. html = htmlReg.ReplaceAllString(html, "<html$1><head></head>")
  104. }
  105. html = strings.Replace(html, "<head>", "<head><meta name=\"robots\" content=\"nofollow\" />", 1)
  106. // Add links back to HTMLhouse
  107. watermark := "<div style='position: absolute;top:16px;right:16px;'><a href='/'>&lt;&#8962;/&gt;</a> &middot; <a href='/edit/" + houseID + ".html'>edit</a></div>"
  108. if strings.Index(html, "</body>") == -1 {
  109. html = strings.Replace(html, "</html>", "</body></html>", 1)
  110. }
  111. html = strings.Replace(html, "</body>", fmt.Sprintf("%s</body>", watermark), 1)
  112. // Print HTML, with sanity check in case someone did something crazy
  113. if strings.Index(html, "<a href='/'>&lt;&#8962;/&gt;</a>") == -1 {
  114. fmt.Fprintf(w, "%s%s", html, watermark)
  115. } else {
  116. fmt.Fprintf(w, "%s", html)
  117. }
  118. if r.Method != "HEAD" && !bots.IsBot(r.UserAgent()) {
  119. app.db.Exec("UPDATE houses SET view_count = view_count + 1 WHERE id = ?", houseID)
  120. }
  121. return nil
  122. }
  123. func viewHouseStats(app *app, w http.ResponseWriter, r *http.Request) error {
  124. vars := mux.Vars(r)
  125. houseID := vars["house"]
  126. created, views, err := getHouseStats(app, houseID)
  127. if err != nil {
  128. if err, ok := err.(impart.HTTPError); ok {
  129. if err.Status == http.StatusNotFound {
  130. // TODO: put this logic in one place (shared with getHouse func)
  131. page, err := ioutil.ReadFile(app.cfg.StaticDir + "/404.html")
  132. if err != nil {
  133. page = []byte("<!DOCTYPE html><html><body>HTMLlot.</body></html>")
  134. }
  135. fmt.Fprintf(w, "%s", page)
  136. return nil
  137. }
  138. }
  139. return err
  140. }
  141. viewsLbl := "view"
  142. if views != 1 {
  143. viewsLbl = "views"
  144. }
  145. app.templates["stats"].ExecuteTemplate(w, "stats", &HouseStats{
  146. ID: houseID,
  147. Stats: []Stat{
  148. Stat{
  149. Data: fmt.Sprintf("%d", views),
  150. Label: viewsLbl,
  151. },
  152. Stat{
  153. Data: created.Format(time.RFC1123),
  154. Label: "created",
  155. },
  156. },
  157. })
  158. return nil
  159. }
  160. func viewHouses(app *app, w http.ResponseWriter, r *http.Request) error {
  161. houses, err := getPublicHouses(app)
  162. if err != nil {
  163. fmt.Fprintf(w, ":(")
  164. return err
  165. }
  166. app.templates["browse"].ExecuteTemplate(w, "browse", struct{ Houses *[]PublicHouse }{houses})
  167. return nil
  168. }
  169. func getPublicHouses(app *app) (*[]PublicHouse, error) {
  170. houses := []PublicHouse{}
  171. rows, err := app.db.Query("SELECT house_id, title, thumb_url FROM publichouses WHERE approved = 1 ORDER BY updated DESC LIMIT 10")
  172. switch {
  173. case err == sql.ErrNoRows:
  174. return nil, impart.HTTPError{http.StatusNotFound, "Return to sender. Address unknown."}
  175. case err != nil:
  176. fmt.Printf("Couldn't fetch: %v\n", err)
  177. return nil, err
  178. }
  179. defer rows.Close()
  180. house := &PublicHouse{}
  181. for rows.Next() {
  182. err = rows.Scan(&house.ID, &house.Title, &house.ThumbURL)
  183. houses = append(houses, *house)
  184. }
  185. return &houses, nil
  186. }