Controller docs, RSVP counts, comment integration
This commit is contained in:
parent
a3ba16c9b2
commit
5c4f5612f8
15
internal/controllers/admin/admin_doc.go
Normal file
15
internal/controllers/admin/admin_doc.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
Package admin provides controllers for admin features of the app.
|
||||
|
||||
Routes
|
||||
|
||||
Admin Only
|
||||
/admin/ Admin index page
|
||||
/admin/settings Manage app settings
|
||||
/admin/editor Web page editor
|
||||
|
||||
Related Models
|
||||
|
||||
users
|
||||
*/
|
||||
package admin
|
21
internal/controllers/authctl/authctl_doc.go
Normal file
21
internal/controllers/authctl/authctl_doc.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Package authctl implements the authentication controllers.
|
||||
|
||||
Routes
|
||||
|
||||
/login Log in to a user account
|
||||
/logout Log out
|
||||
/account Account home page (to edit profile or reset password)
|
||||
/age-verify If the blog implements age gating
|
||||
|
||||
Related Models
|
||||
|
||||
users
|
||||
|
||||
Age Gating
|
||||
|
||||
If the blog marks itself as NSFW, visitors to the blog must verify their age
|
||||
to enter. The middleware that controls this is at `middleware/age-gate.go`.
|
||||
The controller is here at `controllers/authctl/gate-gate.go`
|
||||
*/
|
||||
package authctl
|
48
internal/controllers/comments/comments_doc.go
Normal file
48
internal/controllers/comments/comments_doc.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Package comments implements the controllers for the commenting system.
|
||||
|
||||
Routes
|
||||
|
||||
/comments Main comment handler
|
||||
/comments/subscription Manage subscription to comment threads
|
||||
/comments/quick-delete Quickly delete spam comments from admin email
|
||||
|
||||
Related Models
|
||||
|
||||
comments
|
||||
|
||||
Description
|
||||
|
||||
Comments are a generic comment thread system that can be placed on any page.
|
||||
They are automatically attached to blog posts (unless you disable comments on
|
||||
them) but they can be used anywhere. A guestbook, on the events pages, on any
|
||||
custom pages, etc.
|
||||
|
||||
Every comment thread has a unique ID, so some automated threads have name spaces,
|
||||
like "blog-$id".
|
||||
|
||||
Subscriptions
|
||||
|
||||
When users leave a comment with their e-mail address, they may opt in to getting
|
||||
notified about future comments left on the same thread.
|
||||
|
||||
Go Template Function
|
||||
|
||||
You can create a comment form on a page in Go templates like this:
|
||||
|
||||
func RenderComments(r *http.Request, subject string, ids ...string) template.HTML
|
||||
{{ RenderComments .Request "Title" "id part" "id part" "id part..." }}
|
||||
|
||||
The subject is used in the notification e-mail. The ID strings are joined together
|
||||
by dashes and you can have as many as you need. Examples:
|
||||
|
||||
Blog posts in the format `blog-<postID>` like `blog-42`
|
||||
{{ RenderComments .Request .Data.Title "blog" .Data.IDString }}
|
||||
|
||||
Events in the format `event-<eventID>` like `event-2`
|
||||
{{ RenderComments .Request "My Big Party" "event" "2" }}
|
||||
|
||||
Custom ID for a guestbook
|
||||
{{ RenderComments .Request "Guestbook" "guestbook" }}
|
||||
*/
|
||||
package comments
|
|
@ -49,6 +49,8 @@ func contactAuthHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// Authenticate the contact in the session.
|
||||
session := sessions.Get(r)
|
||||
session.Values["contact-id"] = c.ID
|
||||
session.Values["c.name"] = c.Name() // comment form values auto-filled nicely
|
||||
session.Values["c.email"] = c.Email
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Error("contactAuthHandler: save session error: %s", err)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"github.com/kirsle/blog/internal/middleware/auth"
|
||||
"github.com/kirsle/blog/internal/render"
|
||||
"github.com/kirsle/blog/internal/responses"
|
||||
"github.com/kirsle/blog/models/comments"
|
||||
"github.com/kirsle/blog/models/events"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
@ -85,11 +87,47 @@ func viewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Count up the RSVP statuses, and also look for the authed contact's RSVP.
|
||||
var (
|
||||
countGoing int
|
||||
countMaybe int
|
||||
countNotGoing int
|
||||
countInvited int
|
||||
)
|
||||
for _, rsvp := range event.RSVP {
|
||||
if authedContact.ID != 0 && rsvp.ContactID == authedContact.ID {
|
||||
v["authedRVSP"] = rsvp
|
||||
}
|
||||
|
||||
switch rsvp.Status {
|
||||
case events.StatusGoing:
|
||||
countGoing++
|
||||
case events.StatusMaybe:
|
||||
countMaybe++
|
||||
case events.StatusNotGoing:
|
||||
countNotGoing++
|
||||
default:
|
||||
countInvited++
|
||||
}
|
||||
}
|
||||
v["countGoing"] = countGoing
|
||||
v["countMaybe"] = countMaybe
|
||||
v["countNotGoing"] = countNotGoing
|
||||
v["countInvited"] = countInvited
|
||||
|
||||
// If we're posting, are we RSVPing?
|
||||
if r.Method == http.MethodPost {
|
||||
action := r.PostFormValue("action")
|
||||
switch action {
|
||||
case "answer-rsvp":
|
||||
// Subscribe them to the comment thread on this page if we have an email.
|
||||
if authedContact.Email != "" {
|
||||
thread := fmt.Sprintf("event-%d", event.ID)
|
||||
log.Info("events.viewHandler: subscribe email %s to thread %s", authedContact.Email, thread)
|
||||
ml := comments.LoadMailingList()
|
||||
ml.Subscribe(thread, authedContact.Email)
|
||||
}
|
||||
|
||||
answer := r.PostFormValue("submit")
|
||||
for _, rsvp := range event.RSVP {
|
||||
if rsvp.ContactID == authedContact.ID {
|
||||
|
|
73
internal/controllers/events/events_doc.go
Normal file
73
internal/controllers/events/events_doc.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Package events provides controllers for the event system.
|
||||
|
||||
Routes
|
||||
|
||||
Admin Only
|
||||
/e/admin/edit Edit an event
|
||||
/e/admin/invite/<event_id> Manage invitations and contacts to an event
|
||||
/e/admin/ Event admin index
|
||||
|
||||
Public
|
||||
/e/<event_fragment> Public URL for event page
|
||||
/c/logout Logout authenticated contact
|
||||
/c/<user_secret> Authenticate a contact
|
||||
|
||||
Related Models
|
||||
|
||||
contacts
|
||||
events
|
||||
|
||||
Description
|
||||
|
||||
Events provide basic features of event planning software, including
|
||||
(for the admin user of the site):
|
||||
|
||||
* View, edit, delete events.
|
||||
* Events have a title, datetime range, Markdown body, etc.
|
||||
* You can invite users (contacts) to the event.
|
||||
|
||||
Contacts are their own distinct entity in the database, separate from Users
|
||||
(which are the website admin user accounts, with passwords).
|
||||
|
||||
When you invite people to an event, you create new Contact entries for the
|
||||
people who don't have them yet or invite the ones who exist. Each Contact
|
||||
uniquely groups a first and last name, e-mail address and SMS number.
|
||||
|
||||
When you send out invite e-mail or SMS messages, each Contact is given their own
|
||||
personal link to view the event details. The link goes to the URL
|
||||
`/c/<user_secret>?e=<event_id>`, where "user_secret" is a secret random string
|
||||
generated on their Contact object (to identify the Contact) and "event_id" is
|
||||
the ID number of the event.
|
||||
|
||||
The Contact Authenticator endpoint at `/c/<user_secret>` "authenticates" them
|
||||
in their browser session by setting the session key "contact.id" -- this is only
|
||||
of any interest to the Events controller anyway.
|
||||
|
||||
Events, Contacts, and RSVPs
|
||||
|
||||
There is an Event row for every distinct event, and a single Contact row for
|
||||
every distinct person.
|
||||
|
||||
RSVP's are how we marry Events to their invited Contacts, *and* how we track
|
||||
the RSVP response of each contact.
|
||||
|
||||
When a user clicks the link in their invite e-mail, their browser authenticates
|
||||
as their Contact (it greets them by their name and shows the buttons to respond
|
||||
to the event). When they click "Going" or "Not Going", the server knows which
|
||||
contact they are and can find them on the RSVP list, and mark their status
|
||||
accordingly.
|
||||
|
||||
Comment Form
|
||||
|
||||
Events have comment forms using the thread format "event-<id>", like "event-1"
|
||||
for the first event. When an authenticated Contact (one who clicked an email
|
||||
link) interacts with the Response Form, we auto-subscribe their e-mail to the
|
||||
comment form. This way anybody leaving comments on the page will naturally
|
||||
notify the users who have 1) awareness of the event, 2) have given an answer
|
||||
about it.
|
||||
|
||||
They can easily unsubscribe from the comment thread as normal for the blog's
|
||||
commenting system.
|
||||
*/
|
||||
package events
|
45
internal/controllers/posts/posts_doc.go
Normal file
45
internal/controllers/posts/posts_doc.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Package postctl implements all the web blog features.
|
||||
|
||||
Routes
|
||||
|
||||
Public
|
||||
/blog Blog index
|
||||
/blog.rss RSS feed
|
||||
/blog.atom Atom feed
|
||||
/archive Blog archives
|
||||
/tagged Index of all blog tags
|
||||
/tagged/<tag> View posts by tag
|
||||
/<fragment> View blog entry by its URL fragment
|
||||
|
||||
Admin Only
|
||||
/blog/edit Create or edit blog post
|
||||
/blog/delete Confirm deletion of blog post
|
||||
/blog/drafts View all draft entries
|
||||
/blog/private View all private entries
|
||||
|
||||
Related Models
|
||||
|
||||
posts
|
||||
|
||||
Description
|
||||
|
||||
Each post is in its own JsonDB document at `posts/entries/<id>.json` and
|
||||
contains all its data (title, body, tags, timestamps, etc.)
|
||||
|
||||
For faster retrieval and caching of overall post data, there is a Blog Index
|
||||
that gets saved in JsonDB at `posts/index.json`. The index summarizes ALL of
|
||||
the blog posts by caching their basic details (ID, URL fragment, title,
|
||||
tags, created time). This document is used for getting a narrower list of posts
|
||||
to work with, for index pages (with pagination), "by tagged" pages, etc.
|
||||
|
||||
Usually the front-end settles on 5 or 10 posts it wants to render, and it only
|
||||
had to look at the index. For the archive view where it only needs the blog
|
||||
titles, it already has these too. For the posts where it needs the full body,
|
||||
it has the IDs and can just select each one pretty quickly.
|
||||
|
||||
In case anything goes wrong with the blog index, you can always delete the
|
||||
`posts/index.json` and it will be re-generated from scratch in a one-time scan
|
||||
of the entire posts DB (opening every document).
|
||||
*/
|
||||
package postctl
|
|
@ -24,6 +24,7 @@
|
|||
<p class="small">
|
||||
[<a href="/c/logout?next={{ $.Request.URL.Path }}">not {{ $authedContact.Name }}?</a>]
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
@ -56,6 +57,15 @@
|
|||
|
||||
<h4 class="mt-4">Invited</h4>
|
||||
|
||||
{{ if $.Data.countGoing }}
|
||||
<p class="text-muted">
|
||||
<em>{{ $.Data.countGoing }}
|
||||
{{ if eq $.Data.countGoing 1 }}person is{{ else }}people are{{ end }}
|
||||
going:
|
||||
</em>
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
<div style="max-height: 500px; overflow: auto">
|
||||
<ul class="list-group">
|
||||
{{ range .RSVP }}
|
||||
|
@ -105,6 +115,9 @@
|
|||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<p class="text-muted mt-2">
|
||||
{{ $.Data.countInvited }} invited.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user