A web blog and personal homepage engine written in Go.
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.

194 lines
4.8 KiB

  1. package controllers
  2. import (
  3. "fmt"
  4. "html/template"
  5. "net/http"
  6. "strings"
  7. "time"
  8. "git.kirsle.net/apps/gophertype/pkg/authentication"
  9. "git.kirsle.net/apps/gophertype/pkg/console"
  10. "git.kirsle.net/apps/gophertype/pkg/glue"
  11. "git.kirsle.net/apps/gophertype/pkg/mail"
  12. "git.kirsle.net/apps/gophertype/pkg/markdown"
  13. "git.kirsle.net/apps/gophertype/pkg/models"
  14. "git.kirsle.net/apps/gophertype/pkg/responses"
  15. "git.kirsle.net/apps/gophertype/pkg/session"
  16. "git.kirsle.net/apps/gophertype/pkg/settings"
  17. "github.com/albrow/forms"
  18. "github.com/gorilla/mux"
  19. "github.com/kirsle/blog/src/log"
  20. )
  21. func init() {
  22. glue.Register(glue.Endpoint{
  23. Path: "/ask",
  24. Methods: []string{"GET", "POST"},
  25. Handler: QuestionHandler,
  26. })
  27. glue.Register(glue.Endpoint{
  28. Path: "/ask/answer",
  29. Methods: []string{"POST"},
  30. Middleware: []mux.MiddlewareFunc{
  31. authentication.LoginRequired,
  32. },
  33. Handler: AnswerHandler,
  34. })
  35. }
  36. // QuestionHandler implements the "Ask Me Anything" at the URL "/ask"
  37. func QuestionHandler(w http.ResponseWriter, r *http.Request) {
  38. var (
  39. v = responses.NewTemplateVars(w, r)
  40. ses = session.Get(r)
  41. )
  42. // Load their cached name from any previous comments they may have posted.
  43. name, _ := ses.Values["c.name"].(string)
  44. q := models.Questions.New()
  45. q.Name = name
  46. for r.Method == http.MethodPost {
  47. form, _ := forms.Parse(r)
  48. q.ParseForm(r)
  49. // Validate form parameters.
  50. val := form.Validator()
  51. val.Require("question")
  52. if val.HasErrors() {
  53. v.ValidationError = val.ErrorMap()
  54. v.V["Error"] = "Missing required form fields."
  55. break
  56. }
  57. // Cache their name in their session for future comments/asks.
  58. ses.Values["c.name"] = q.Name
  59. ses.Save(r, w)
  60. // Save the question.
  61. err := q.Save()
  62. if err != nil {
  63. log.Error("Error saving neq eustion: %s", err)
  64. responses.Error(w, r, http.StatusInternalServerError, "Error saving question: "+err.Error())
  65. return
  66. }
  67. // Email the site admin.
  68. if name == "" {
  69. name = "Anonymous"
  70. }
  71. subject := fmt.Sprintf("Ask Me Anything (%s) from %s", settings.Current.Title, name)
  72. go mail.EmailAdmins(mail.Email{
  73. Subject: subject,
  74. Template: "_builtin/email/generic.gohtml",
  75. Data: map[string]interface{}{
  76. "Subject": subject,
  77. "Message": template.HTML(
  78. markdown.RenderMarkdown(fmt.Sprintf(
  79. "%s\n\nAnswer this at %s",
  80. q.Question,
  81. strings.TrimSuffix(settings.Current.BaseURL, "/")+"/ask",
  82. )),
  83. ),
  84. },
  85. })
  86. session.Flash(w, r, "Your question has been recorded!")
  87. responses.Redirect(w, r, "/ask")
  88. return
  89. }
  90. // If logged in, load the pending questions.
  91. if authentication.LoggedIn(r) {
  92. pending, err := models.Questions.Pending()
  93. if err != nil {
  94. console.Error("Error loading pending questions: %s", err)
  95. }
  96. v.V["Pending"] = pending
  97. }
  98. // Load the recently answered questions for public users.
  99. recent, err := models.Questions.RecentlyAnswered(10)
  100. if err != nil {
  101. console.Error("Error loading recently answered questions: %s", err)
  102. }
  103. v.V["Q"] = q
  104. v.V["Recent"] = recent
  105. responses.RenderTemplate(w, r, "_builtin/questions.gohtml", v)
  106. }
  107. // AnswerHandler handles answering (and deleting) questions.
  108. func AnswerHandler(w http.ResponseWriter, r *http.Request) {
  109. v := responses.NewTemplateVars(w, r)
  110. CurrentUser, _ := authentication.CurrentUser(r)
  111. // Validate form parameters.
  112. form, _ := forms.Parse(r)
  113. val := form.Validator()
  114. val.Require("id")
  115. val.Require("answer")
  116. val.Require("submit")
  117. if val.HasErrors() {
  118. v.ValidationError = val.ErrorMap()
  119. v.V["Error"] = "Missing required form fields."
  120. responses.RenderTemplate(w, r, "_builtin/questions.gohtml", v)
  121. return
  122. }
  123. // Look up the question.
  124. q, err := models.Questions.Load(form.GetInt("id"))
  125. if err != nil {
  126. responses.Error(w, r, http.StatusInternalServerError, err.Error())
  127. return
  128. }
  129. // Handle submit actions.
  130. switch form.Get("submit") {
  131. case "answer":
  132. // Prepare a Markdown themed blog post for this answer.
  133. name := q.Name
  134. if name == "" {
  135. name = "Anonymous"
  136. }
  137. post := models.Post{
  138. Title: "Ask",
  139. ContentType: "markdown",
  140. Privacy: models.Public,
  141. EnableComments: true,
  142. AuthorID: CurrentUser.ID,
  143. Tags: []models.TaggedPost{
  144. models.TaggedPost{Tag: "ask"},
  145. },
  146. Fragment: fmt.Sprintf("ask-%s",
  147. time.Now().Format("20060102150405"),
  148. ),
  149. Body: fmt.Sprintf(
  150. "> **%s** asks:\n\n> %s\n\n%s\n",
  151. name,
  152. strings.Replace(q.Question, "\n", "\n> ", 0),
  153. form.Get("answer"),
  154. ),
  155. }
  156. post.Save()
  157. // Associate the question to this post ID.
  158. q.PostID = post.ID
  159. q.Answered = true
  160. q.Save()
  161. // Send the admin to the post edit page.
  162. responses.Redirect(w, r, "/"+post.Fragment)
  163. case "delete":
  164. q.Delete()
  165. session.Flash(w, r, "Question deleted!")
  166. responses.Redirect(w, r, "/ask")
  167. default:
  168. responses.BadRequest(w, r, "Invalid submit method.")
  169. }
  170. }