Handle drafts and private/unlisted posts
This commit is contained in:
parent
b127c61dd7
commit
34d444ab96
18
core/auth.go
18
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, "/")
|
||||
}
|
||||
|
|
87
core/blog.go
87
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)
|
||||
|
|
21
core/constants.go
Normal file
21
core/constants.go
Normal file
|
@ -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"
|
||||
)
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -82,20 +82,37 @@
|
|||
<div class="card-body">
|
||||
<h4 class="card-title">About</h4>
|
||||
|
||||
{{ if .LoggedIn }}
|
||||
Hello, {{ .CurrentUser.Username }}.<br>
|
||||
<a href="/logout">Log out</a><br>
|
||||
{{ if .CurrentUser.Admin }}
|
||||
<a href="/admin">Admin center</a>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<a href="/login">Log in</a>
|
||||
{{ end }}
|
||||
|
||||
<p>Hello, world!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ if .LoggedIn }}
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h4 class="cart-title">Control Center</h4>
|
||||
|
||||
<p>
|
||||
Logged in as: <a href="/u/{{ .CurrentUser.Username }}">{{ .CurrentUser.Username }}</a>
|
||||
</p>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
{{ if .CurrentUser.Admin }}
|
||||
<li class="list-item"><a href="/admin">Admin Center</a></li>
|
||||
{{ end }}
|
||||
<li class="list-item"><a href="/logout">Log out</a></li>
|
||||
</ul>
|
||||
|
||||
<h5>Manage Blog</h5>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li class="list-item"><a href="/blog/edit">Post Blog Entry</a></li>
|
||||
<li class="list-item"><a href="/blog/drafts">View Drafts</a></li>
|
||||
<li class="list-item"><a href="/blog/private">View Private</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Archives</h4>
|
||||
|
@ -153,6 +170,15 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Back to top</a>
|
||||
</li>
|
||||
{{ if .LoggedIn }}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/logout">Log out</a>
|
||||
</li>
|
||||
{{ else }}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/login">Log in</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
|
|
|
@ -56,16 +56,6 @@
|
|||
> Markdown
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input type="radio"
|
||||
class="form-check-input"
|
||||
name="content-type"
|
||||
value="markdown+html"
|
||||
{{ if eq .ContentType "markdown+html" }}checked{{ end }}
|
||||
> Markdown + HTML
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input type="radio"
|
||||
|
@ -99,6 +89,7 @@
|
|||
<select name="privacy" class="form-control">
|
||||
<option value="public"{{ if eq .Privacy "public" }} selected{{ end }}>Public: everybody can see this post</option>
|
||||
<option value="private"{{ if eq .Privacy "private" }} selected{{ end }}>Private: only site admins can see this post</option>
|
||||
<option value="unlisted"{{ if eq .Privacy "unlisted" }} selected{{ end }}>Unlisted: only those with the direct link can see it</option>
|
||||
<option value="draft"{{ if eq .Privacy "draft" }} selected{{ end }}>Draft: don't show this post on the blog anywhere</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,13 @@
|
|||
{{ end }}
|
||||
|
||||
<div class="blog-meta">
|
||||
{{ if eq $p.Privacy "private" }}
|
||||
<span class="blog-private">[private]</span>
|
||||
{{ else if eq $p.Privacy "draft" }}
|
||||
<span class="blog-draft">[draft]</span>
|
||||
{{ else if eq $p.Privacy "unlisted" }}
|
||||
<span class="blog-unlisted">[unlisted]</span>
|
||||
{{ end }}
|
||||
<span title="{{ $p.Created.Format "Jan 2 2006 @ 15:04:05 MST" }}">
|
||||
{{ $p.Created.Format "January 2, 2006" }}
|
||||
</span>
|
||||
|
|
|
@ -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 <pre> tags */
|
||||
.markdown code {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<form name="login" action="/login" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{ .CSRF }}">
|
||||
<input type="hidden" name="next" value="{{ .Data.NextURL }}">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input type="text" name="username" class="form-control" placeholder="Username">
|
||||
|
|
Loading…
Reference in New Issue
Block a user