package controllers import ( "bytes" "fmt" "html/template" "net/http" "strconv" "strings" "git.kirsle.net/apps/gophertype/pkg/authentication" "git.kirsle.net/apps/gophertype/pkg/glue" "git.kirsle.net/apps/gophertype/pkg/markdown" "git.kirsle.net/apps/gophertype/pkg/models" "git.kirsle.net/apps/gophertype/pkg/responses" "git.kirsle.net/apps/gophertype/pkg/session" "git.kirsle.net/apps/gophertype/pkg/settings" "github.com/albrow/forms" "github.com/gorilla/mux" ) func init() { glue.Register(glue.Endpoint{ Path: "/blog", Methods: []string{"GET"}, Handler: BlogIndex(models.Public, false), }) glue.Register(glue.Endpoint{ Path: "/tagged", Methods: []string{"GET"}, Handler: TagIndex, }) glue.Register(glue.Endpoint{ Path: "/tagged/{tag}", Methods: []string{"GET"}, Handler: BlogIndex(models.Public, true), }) glue.Register(glue.Endpoint{ Path: "/archive", Methods: []string{"GET"}, Handler: BlogArchive, }) glue.Register(glue.Endpoint{ Path: "/blog/random", Methods: []string{"GET"}, Handler: BlogRandom, }) glue.Register(glue.Endpoint{ Path: "/blog/drafts", Middleware: []mux.MiddlewareFunc{ authentication.LoginRequired, }, Methods: []string{"GET"}, Handler: BlogIndex(models.Draft, false), }) glue.Register(glue.Endpoint{ Path: "/blog/private", Middleware: []mux.MiddlewareFunc{ authentication.LoginRequired, }, Methods: []string{"GET"}, Handler: BlogIndex(models.Private, false), }) glue.Register(glue.Endpoint{ Path: "/blog/unlisted", Middleware: []mux.MiddlewareFunc{ authentication.LoginRequired, }, Methods: []string{"GET"}, Handler: BlogIndex(models.Unlisted, false), }) glue.Register(glue.Endpoint{ Path: "/blog/edit", Methods: []string{"GET", "POST"}, Middleware: []mux.MiddlewareFunc{ authentication.LoginRequired, }, Handler: EditPost, }) } // BlogIndex handles all of the top-level blog index routes: // - /blog // - /tagged/{tag} // - /blog/unlisted // - /blog/drafts // - /blog/private func BlogIndex(privacy string, tagged bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var ( v = responses.NewTemplateVars(w, r) tagName string // Multitag values. isMultitag bool include []string exclude []string ) // Tagged view? if tagged { params := mux.Vars(r) tagName = params["tag"] } // Page title to use. var title = "Blog" if tagged { // Check for use of multi-tags. if strings.Contains(tagName, ",") { title = "Tagged Posts" if inc, exc, err := models.ParseMultitag(tagName); err == nil { isMultitag = true include = inc exclude = exc } } else { title = "Tagged as: " + tagName } } else if privacy == models.Draft { title = "Drafts" } else if privacy == models.Unlisted { title = "Unlisted" } else if privacy == models.Private { title = "Private" } v.V["title"] = title v.V["tag"] = tagName v.V["Multitag"] = struct { Is bool Include []string Exclude []string }{ isMultitag, include, exclude, } v.V["privacy"] = privacy responses.RenderTemplate(w, r, "_builtin/blog/index.gohtml", v) } } // PostFragment at "/" for viewing blog entries. func PostFragment(w http.ResponseWriter, r *http.Request) { fragment := strings.Trim(r.URL.Path, "/") post, err := models.Posts.LoadFragment(fragment) if err != nil { responses.NotFound(w, r) return } // Is it a private post and are we logged in? if post.Privacy != models.Public && post.Privacy != models.Unlisted && !authentication.LoggedIn(r) { responses.Forbidden(w, r, "Permission denied to view that post.") return } v := responses.NewTemplateVars(w, r) v.V["post"] = post // Render the body. if post.ContentType == models.Markdown { v.V["rendered"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body)) } else { v.V["rendered"] = template.HTML(post.Body) } responses.RenderTemplate(w, r, "_builtin/blog/view-post.gohtml", v) } // PartialBlogIndex is a template function to embed a blog index view on any page. func PartialBlogIndex(r *http.Request, tag, privacy string) template.HTML { html := bytes.NewBuffer([]byte{}) v := responses.NewTemplateVars(html, r) page, _ := strconv.Atoi(r.FormValue("page")) var ( posts models.PagedPosts err error ) if tag != "" { posts, err = models.Posts.GetPostsByTag(tag, privacy, page, settings.Current.PostsPerPage) } else { posts, err = models.Posts.GetIndexPosts(privacy, page, settings.Current.PostsPerPage) } if err != nil && err.Error() != "sql: no rows in result set" { return template.HTML(fmt.Sprintf("[BlogIndex: %s]", err)) } v.V["posts"] = posts.Posts v.V["paging"] = posts responses.PartialTemplate(html, r, "_builtin/blog/index.partial.gohtml", v) return template.HTML(html.String()) } // TagIndex for "/tagged" to return all tags sorted by popularity. func TagIndex(w http.ResponseWriter, r *http.Request) { // If not logged in, only summarize public post tags. var public = !authentication.LoggedIn(r) tags := models.SummarizeTags(public) v := responses.NewTemplateVars(w, r) v.V["tags"] = tags responses.RenderTemplate(w, r, "_builtin/blog/tags.gohtml", v) } // BlogArchive shows the archive page of ALL blog posts. func BlogArchive(w http.ResponseWriter, r *http.Request) { v := responses.NewTemplateVars(w, r) // Show private and unlisted posts? showPrivate := authentication.LoggedIn(r) archive, err := models.Posts.GetArchive(showPrivate) if err != nil { responses.Error(w, r, http.StatusInternalServerError, err.Error()) return } v.V["archive"] = archive responses.RenderTemplate(w, r, "_builtin/blog/archive.gohtml", v) } // BlogRandom handles the /blog/random route and picks a random post. func BlogRandom(w http.ResponseWriter, r *http.Request) { post, err := models.Posts.LoadRandom(models.Public) if err != nil { responses.Error(w, r, http.StatusInternalServerError, err.Error()) return } responses.Redirect(w, r, "/"+post.Fragment) } // EditPost at "/blog/edit" func EditPost(w http.ResponseWriter, r *http.Request) { v := responses.NewTemplateVars(w, r) v.V["preview"] = "" // The blog post we're working with. var post = models.Posts.New() var isNew = true // Editing an existing post? if r.FormValue("id") != "" { id, _ := strconv.Atoi(r.FormValue("id")) if p, err := models.Posts.Load(id); err == nil { post = p isNew = false } } // POST handler: create the admin account. for r.Method == http.MethodPost { form, _ := forms.Parse(r) // Validate form parameters. val := form.Validator() val.Require("title") val.Require("body") post.ParseForm(form) if val.HasErrors() { v.ValidationError = val.ErrorMap() break } // Previewing or submitting the post? switch form.Get("submit") { case "preview": if post.ContentType == models.Markdown { v.V["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body)) } else { v.V["preview"] = template.HTML(post.Body) } case "post": author, _ := authentication.CurrentUser(r) post.AuthorID = author.ID // When editing, allow to not touch the Last Updated time. var resetUpdated bool = !isNew && form.GetBool("no-update") == true err := post.Save() if resetUpdated { post.SetUpdated(post.CreatedAt) } if err != nil { v.Error = err } else { session.Flash(w, r, "Post created!") responses.Redirect(w, r, "/"+post.Fragment) } } break } v.V["post"] = post v.V["isNew"] = isNew responses.RenderTemplate(w, r, "_builtin/blog/edit.gohtml", v) }