diff --git a/core/auth.go b/core/auth.go index 16fcf39..30a6394 100644 --- a/core/auth.go +++ b/core/auth.go @@ -22,9 +22,16 @@ func (b *Blog) Login(w http.ResponseWriter, r *http.Request, u *users.User) erro // LoginHandler shows and handles the login page. func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) { - vars := &Vars{ - Form: forms.Setup{}, + vars := NewVars() + vars.Form = forms.Setup{} + + var nextURL string + if r.Method == http.MethodPost { + nextURL = r.FormValue("next") + } else { + nextURL = r.URL.Query().Get("next") } + vars.Data["NextURL"] = nextURL if r.Method == http.MethodPost { form := &forms.Login{ @@ -46,10 +53,9 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) { b.Login(w, r, user) // A next URL given? TODO: actually get to work - next := r.FormValue("next") - log.Info("Redirect after login to: %s", next) - if len(next) > 0 && next[0] == '/' { - b.Redirect(w, next) + log.Info("Redirect after login to: %s", nextURL) + if len(nextURL) > 0 && nextURL[0] == '/' { + b.Redirect(w, nextURL) } else { b.Redirect(w, "/") } diff --git a/core/blog.go b/core/blog.go index f14690c..4aac45d 100644 --- a/core/blog.go +++ b/core/blog.go @@ -28,11 +28,14 @@ type PostMeta struct { func (b *Blog) BlogRoutes(r *mux.Router) { // Public routes r.HandleFunc("/blog", b.BlogIndex) + r.HandleFunc("/tagged/{tag}", b.Tagged) // Login-required routers. loginRouter := mux.NewRouter() loginRouter.HandleFunc("/blog/edit", b.EditBlog) loginRouter.HandleFunc("/blog/delete", b.DeletePost) + loginRouter.HandleFunc("/blog/drafts", b.Drafts) + loginRouter.HandleFunc("/blog/private", b.PrivatePosts) r.PathPrefix("/blog").Handler( negroni.New( negroni.HandlerFunc(b.LoginRequired), @@ -52,6 +55,33 @@ func (b *Blog) BlogRoutes(r *mux.Router) { // BlogIndex renders the main index page of the blog. func (b *Blog) BlogIndex(w http.ResponseWriter, r *http.Request) { + b.PartialIndex(w, r, "", "") +} + +// Tagged lets you browse blog posts by category. +func (b *Blog) Tagged(w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + tag, ok := params["tag"] + if !ok { + b.BadRequest(w, r, "Missing category in URL") + } + + b.PartialIndex(w, r, tag, "") +} + +// Drafts renders an index view of only draft posts. Login required. +func (b *Blog) Drafts(w http.ResponseWriter, r *http.Request) { + b.PartialIndex(w, r, "", DRAFT) +} + +// PrivatePosts renders an index view of only private posts. Login required. +func (b *Blog) PrivatePosts(w http.ResponseWriter, r *http.Request) { + b.PartialIndex(w, r, "", PRIVATE) +} + +// PartialIndex handles common logic for blog index views. +func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request, + tag, privacy string) { v := NewVars(map[interface{}]interface{}{}) // Get the blog index. @@ -60,9 +90,52 @@ func (b *Blog) BlogIndex(w http.ResponseWriter, r *http.Request) { // The set of blog posts to show. var pool []posts.Post for _, post := range idx.Posts { + // Limiting by a specific privacy setting? (drafts or private only) + if privacy != "" { + switch privacy { + case DRAFT: + if post.Privacy != DRAFT { + continue + } + case PRIVATE: + if post.Privacy != PRIVATE && post.Privacy != UNLISTED { + continue + } + } + } else { + // Exclude certain posts in generic index views. + if (post.Privacy == PRIVATE || post.Privacy == UNLISTED) && !b.LoggedIn(r) { + continue + } else if post.Privacy == DRAFT { + continue + } + } + + // Limit by tag? + if tag != "" { + var tagMatch bool + if tag != "" { + for _, check := range post.Tags { + if check == tag { + tagMatch = true + break + } + } + } + + if !tagMatch { + continue + } + } + pool = append(pool, post) } + if len(pool) == 0 { + b.NotFound(w, r, "No blog posts were found.") + return + } + sort.Sort(sort.Reverse(posts.ByUpdated(pool))) // Query parameters. @@ -106,7 +179,7 @@ func (b *Blog) BlogIndex(w http.ResponseWriter, r *http.Request) { } // Render the post. - if post.ContentType == "markdown" { + if post.ContentType == string(MARKDOWN) { rendered = template.HTML(b.RenderTrustedMarkdown(post.Body)) } else { rendered = template.HTML(post.Body) @@ -138,6 +211,14 @@ func (b *Blog) viewPost(w http.ResponseWriter, r *http.Request, fragment string) return err } + // Handle post privacy. + if post.Privacy == PRIVATE || post.Privacy == DRAFT { + if !b.LoggedIn(r) { + b.NotFound(w, r) + return nil + } + } + v := NewVars(map[interface{}]interface{}{ "Post": post, }) @@ -170,7 +251,7 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool) template.HTML { // Render the post to HTML. var rendered template.HTML - if p.ContentType == "markdown" { + if p.ContentType == string(MARKDOWN) { rendered = template.HTML(b.RenderTrustedMarkdown(p.Body)) } else { rendered = template.HTML(p.Body) @@ -234,7 +315,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) { // Previewing, or submitting? switch r.FormValue("submit") { case "preview": - if post.ContentType == "markdown" || post.ContentType == "markdown+html" { + if post.ContentType == string(MARKDOWN) { v.Data["preview"] = template.HTML(b.RenderMarkdown(post.Body)) } else { v.Data["preview"] = template.HTML(post.Body) diff --git a/core/constants.go b/core/constants.go new file mode 100644 index 0000000..ba1bcb0 --- /dev/null +++ b/core/constants.go @@ -0,0 +1,21 @@ +package core + +// PostPrivacy values. +type PostPrivacy string + +// ContentType values +type ContentType string + +// Post privacy constants. +const ( + PUBLIC PostPrivacy = "public" + PRIVATE = "private" + UNLISTED = "unlisted" + DRAFT = "draft" +) + +// Content types for blog posts. +const ( + MARKDOWN ContentType = "markdown" + HTML ContentType = "html" +) diff --git a/core/middleware.go b/core/middleware.go index 375d3bb..7330712 100644 --- a/core/middleware.go +++ b/core/middleware.go @@ -76,7 +76,18 @@ func (b *Blog) CurrentUser(r *http.Request) (*users.User, error) { return u, err } - return &users.User{}, errors.New("not authenticated") + return &users.User{ + Admin: false, + }, errors.New("not authenticated") +} + +// LoggedIn returns whether the current user is logged in to an account. +func (b *Blog) LoggedIn(r *http.Request) bool { + session := b.Session(r) + if loggedIn, ok := session.Values["logged-in"].(bool); ok && loggedIn { + return true + } + return false } // AuthMiddleware loads the user's authentication state. diff --git a/core/models/posts/posts.go b/core/models/posts/posts.go index b57ac6e..f25d80f 100644 --- a/core/models/posts/posts.go +++ b/core/models/posts/posts.go @@ -82,7 +82,7 @@ func (p *Post) Validate() error { p.ContentType != "html" { return errors.New("invalid setting for ContentType") } - if p.Privacy != "public" && p.Privacy != "draft" && p.Privacy != "private" { + if p.Privacy != "public" && p.Privacy != "draft" && p.Privacy != "private" && p.Privacy != "unlisted" { return errors.New("invalid setting for Privacy") } return nil diff --git a/core/templates.go b/core/templates.go index 24d8901..f05add1 100644 --- a/core/templates.go +++ b/core/templates.go @@ -60,6 +60,10 @@ func (v *Vars) LoadDefaults(b *Blog, w http.ResponseWriter, r *http.Request) { v.Title = s.Site.Title v.Path = r.URL.Path + user, err := b.CurrentUser(r) + v.CurrentUser = user + v.LoggedIn = err == nil + // Add any flashed messages from the endpoint controllers. session := b.Session(r) if flashes := session.Flashes(); len(flashes) > 0 { @@ -71,14 +75,6 @@ func (v *Vars) LoadDefaults(b *Blog, w http.ResponseWriter, r *http.Request) { } v.CSRF = b.GenerateCSRFToken(w, r, session) - - ctx := r.Context() - if user, ok := ctx.Value(userKey).(*users.User); ok { - if user.ID > 0 { - v.LoggedIn = true - v.CurrentUser = user - } - } } // TemplateVars is an interface that describes the template variable struct. diff --git a/root/.layout.gohtml b/root/.layout.gohtml index 3da8880..7b1de6a 100644 --- a/root/.layout.gohtml +++ b/root/.layout.gohtml @@ -82,20 +82,37 @@

About

- {{ if .LoggedIn }} - Hello, {{ .CurrentUser.Username }}.
- Log out
- {{ if .CurrentUser.Admin }} - Admin center - {{ end }} - {{ else }} - Log in - {{ end }} -

Hello, world!

+ {{ if .LoggedIn }} +
+
+

Control Center

+ +

+ Logged in as: {{ .CurrentUser.Username }} +

+ + + +
Manage Blog
+ + +
+
+ {{ end }} +

Archives

@@ -153,6 +170,15 @@ + {{ if .LoggedIn }} + + {{ else }} + + {{ end }}
diff --git a/root/blog/edit.gohtml b/root/blog/edit.gohtml index a443a33..d8e2ded 100644 --- a/root/blog/edit.gohtml +++ b/root/blog/edit.gohtml @@ -56,16 +56,6 @@ > Markdown
-
- -
diff --git a/root/blog/entry.partial.gohtml b/root/blog/entry.partial.gohtml index 4ff0575..f59c5b9 100644 --- a/root/blog/entry.partial.gohtml +++ b/root/blog/entry.partial.gohtml @@ -8,6 +8,13 @@ {{ end }}
+ {{ if eq $p.Privacy "private" }} + [private] + {{ else if eq $p.Privacy "draft" }} + [draft] + {{ else if eq $p.Privacy "unlisted" }} + [unlisted] + {{ end }} {{ $p.Created.Format "January 2, 2006" }} diff --git a/root/css/blog-core.css b/root/css/blog-core.css index cace1ba..7f64550 100644 --- a/root/css/blog-core.css +++ b/root/css/blog-core.css @@ -16,6 +16,12 @@ a.blog-title { font-size: smaller; margin-bottom: 2rem; } +.blog-meta .blog-private, .blog-meta .blog-unlisted { + color: #F00; +} +.blog-meta .blog-draft { + color: #909; +} /* Code blocks treated as
 tags */
 .markdown code {
diff --git a/root/login.gohtml b/root/login.gohtml
index f67e400..e0becfc 100644
--- a/root/login.gohtml
+++ b/root/login.gohtml
@@ -4,6 +4,7 @@
 
 
+