commit 8d09b0c1c46630d42b662920bdb83682774e4b9d
parent e53078b9caa0c5acca3822849d1ae60d8a2f1d60
Author: cblgh <cblgh@cblgh.org>
Date: Fri, 9 Aug 2024 11:11:04 +0200
refactor private threads to use bools over ints, improve error checking
also added a title attribute to the locked key icon that reads out
"Private" on hover.
Diffstat:
5 files changed, 65 insertions(+), 35 deletions(-)
diff --git a/database/database.go b/database/database.go
@@ -190,7 +190,7 @@ func (d DB) Exec(stmt string, args ...interface{}) (sql.Result, error) {
return d.db.Exec(stmt, args...)
}
-func (d DB) CreateThread(title, content string, authorid, topicid int, private int) (int, error) {
+func (d DB) CreateThread(title, content string, authorid, topicid int, isPrivate bool) (int, error) {
ed := util.Describe("create thread")
// create the new thread in a transaction spanning two statements
tx, err := d.db.BeginTx(context.Background(), &sql.TxOptions{}) // proper tx options?
@@ -201,6 +201,10 @@ func (d DB) CreateThread(title, content string, authorid, topicid int, private i
RETURNING id`
replyStmt := `INSERT INTO posts (content, publishtime, threadid, authorid) VALUES (?, ?, ?, ?)`
var threadid int
+ private := 0
+ if isPrivate {
+ private = 1
+ }
err = tx.QueryRow(threadStmt, title, publish, topicid, authorid, private).Scan(&threadid)
if err = ed.Eout(err, "add thread %s (private: %d) by %d in topic %d", title, private, authorid, topicid); err != nil {
_ = tx.Rollback()
@@ -238,11 +242,15 @@ func (d DB) DeleteThread() {}
func (d DB) MoveThread() {}
// TODO(2021-12-28): return error if non-existent thread
-func (d DB) GetThread(threadid int) []Post {
+func (d DB) GetThread(threadid int) ([]Post, error) {
// TODO: make edit work if no edit timestamp detected e.g.
// (sql: Scan error on column index 3, name "lastedit": unsupported Scan, storing driver.Value type <nil> into type
// *time.Time)
+ exists, err := d.CheckThreadExists(threadid)
+ if err != nil || !exists {
+ return []Post{}, errors.New(fmt.Sprintf("GetThread: threadid %d did not exist", threadid))
+ }
// join with:
// users table to get user name
// threads table to get thread title
@@ -270,7 +278,7 @@ func (d DB) GetThread(threadid int) []Post {
}
posts = append(posts, data)
}
- return posts
+ return posts, nil
}
func (d DB) GetPost(postid int) (Post, error) {
@@ -291,7 +299,7 @@ type Thread struct {
Title string
Author string
Slug string
- Private int
+ Private bool
ID int
Publish time.Time
PostID int
@@ -300,7 +308,7 @@ type Thread struct {
// get a list of threads
// NOTE: this query is setting thread.Author not by thread creator, but latest poster. if this becomes a problem, revert
// its use and employ Thread.PostID to perform another query for each thread to get the post author name (wrt server.go:GenerateRSS)
-func (d DB) ListThreads(sortByPost bool, private int) []Thread {
+func (d DB) ListThreads(sortByPost bool, includePrivate bool) []Thread {
query := `
SELECT count(t.id), t.title, t.id, t.private, u.name, p.publishtime, p.id FROM threads t
INNER JOIN users u on u.id = p.authorid
@@ -314,9 +322,9 @@ func (d DB) ListThreads(sortByPost bool, private int) []Thread {
if sortByPost {
orderBy = `ORDER BY max(p.id) DESC`
}
- where := `WHERE t.private IN (0,1)`
- if private == 0 {
- where = `WHERE t.private = 0`
+ where := `WHERE t.private = 0`
+ if includePrivate {
+ where = `WHERE t.private IN (0,1)`
}
query = fmt.Sprintf(query, where, orderBy)
@@ -330,23 +338,31 @@ func (d DB) ListThreads(sortByPost bool, private int) []Thread {
var postCount int
var data Thread
+ var isPrivate int
var threads []Thread
for rows.Next() {
- if err := rows.Scan(&postCount, &data.Title, &data.ID, &data.Private, &data.Author, &data.Publish, &data.PostID); err != nil {
+ if err := rows.Scan(&postCount, &data.Title, &data.ID, &isPrivate, &data.Author, &data.Publish, &data.PostID); err != nil {
log.Fatalln(util.Eout(err, "list threads: read in data via scan"))
}
+ data.Private = (isPrivate == 1)
data.Slug = util.GetThreadSlug(data.ID, data.Title, postCount)
threads = append(threads, data)
}
return threads
}
-func (d DB) IsThreadPrivate(threadId int) int {
+func (d DB) IsThreadPrivate(threadid int) (bool, error) {
+ exists, err := d.CheckThreadExists(threadid)
+
+ if err != nil || !exists {
+ return true, errors.New(fmt.Sprintf("IsThreadPrivate: threadid %d did not exist", threadid))
+ }
+
var private int
stmt := `SELECT private FROM threads where id = ?`
- err := d.db.QueryRow(stmt, threadId).Scan(&private)
- util.Check(err, "querying if private thread %d", threadId)
- return private
+ err = d.db.QueryRow(stmt, threadid).Scan(&private)
+ util.Check(err, "querying if private thread %d", threadid)
+ return private == 1, nil
}
func (d DB) AddPost(content string, threadid, authorid int) (postID int) {
@@ -455,6 +471,11 @@ func (d DB) CheckUsernameExists(username string) (bool, error) {
return d.existsQuery(stmt, username)
}
+func (d DB) CheckThreadExists(threadid int) (bool, error) {
+ stmt := `SELECT 1 FROM threads WHERE id = ?`
+ return d.existsQuery(stmt, threadid)
+}
+
func (d DB) UpdateUserName(userid int, newname string) {
stmt := `UPDATE users SET name = ? WHERE id = ?`
_, err := d.Exec(stmt, newname, userid)
diff --git a/html/index.html b/html/index.html
@@ -6,7 +6,7 @@
{{ range $index, $thread := .Data.Threads }}
<h2>
<a href="{{$thread.Slug}}">{{ $thread.Title }}</a>
- {{ if eq $thread.Private 1}} ⚿ {{ end }}
+ {{ if $thread.Private }} <span title='{{ "Private" | translate }}'>⚿</span> {{ end }}
</h2>
{{ end }}
</main>
diff --git a/html/thread.html b/html/thread.html
@@ -1,8 +1,8 @@
{{ template "head" . }}
<main>
<h1>{{ .Data.Title }}</h1>
- {{ if eq .Data.Private 1}}
- <p>{{ "PostPrivate" | translate }}</p>
+ {{ if .Data.Private }}
+ <p><i>{{ "PostPrivate" | translate }}</i></p>
{{ end }}
{{ $userID := .LoggedInID }}
{{ $threadURL := .Data.ThreadURL }}
diff --git a/i18n/i18n.go b/i18n/i18n.go
@@ -131,7 +131,7 @@ var English = map[string]string{
"NewThreadCreateError": "Error creating thread",
"NewThreadCreateErrorMessage": "There was a database error when creating the thread, apologies.",
"PostEdit": "Post preview",
- "PostPrivate": "This is a private thread, only logged-in users can see it and read its posts",
+ "PostPrivate": "This is a private thread, only logged-in users can see it and read its posts.",
"AriaPostMeta": "Post meta",
"AriaDeletePost": "Delete this post",
diff --git a/server/server.go b/server/server.go
@@ -81,7 +81,7 @@ type ThreadData struct {
Title string
Posts []database.Post
ThreadURL string
- Private int
+ Private bool
}
type EditPostData struct {
@@ -319,21 +319,34 @@ func (h *RequestHandler) ThreadRoute(res http.ResponseWriter, req *http.Request)
// TODO(2022-01-30): find a solution for either:
// * scrolling to thread bottom (and maintaining the same slug, important for visited state in browser)
// * passing data to signal "your post was successfully added" (w/o impacting visited state / url)
- posts := h.db.GetThread(threadid)
+ posts, err := h.db.GetThread(threadid)
+
+ if err != nil {
+ h.renderGenericMessage(res, req, threadMissingData)
+ return
+ }
+
newSlug := util.GetThreadSlug(threadid, posts[0].ThreadTitle, len(posts))
// update the rss feed
h.rssFeed = GenerateRSS(h.db, h.config)
http.Redirect(res, req, newSlug, http.StatusFound)
return
}
- // TODO (2022-01-07):
- // * handle error
- isPrivate := h.db.IsThreadPrivate(threadid)
- if (isPrivate == 1) && !loggedIn {
+
+ // check if we're dealing with a private thread. this can return an error if the thread id does not exist
+ isPrivate, err := h.db.IsThreadPrivate(threadid)
+
+ if err != nil || (isPrivate && !loggedIn) {
+ h.renderGenericMessage(res, req, threadMissingData)
+ return
+ }
+ thread, err := h.db.GetThread(threadid)
+
+ if err != nil {
h.renderGenericMessage(res, req, threadMissingData)
return
}
- thread := h.db.GetThread(threadid)
+
data := ThreadData{Posts: thread, ThreadURL: req.URL.Path, Private: isPrivate}
view := TemplateData{Data: &data, IsAdmin: isAdmin, QuickNav: loggedIn, HasRSS: h.config.RSS.URL != "", LoggedIn: loggedIn, LoggedInID: userid}
if len(thread) > 0 {
@@ -367,12 +380,9 @@ func (h RequestHandler) IndexRoute(res http.ResponseWriter, req *http.Request) {
sortby := q[0]
mostRecentPost = sortby == "posts"
}
+ includePrivateThreads := loggedIn
// show index listing
- private := 0
- if loggedIn {
- private = 1
- }
- threads := h.db.ListThreads(mostRecentPost, private)
+ threads := h.db.ListThreads(mostRecentPost, includePrivateThreads)
view := TemplateData{Data: IndexData{threads}, IsAdmin: isAdmin, HasRSS: h.config.RSS.URL != "", LoggedIn: loggedIn, Title: h.translator.Translate("Threads")}
h.renderView(res, "index", view)
}
@@ -395,7 +405,9 @@ func GenerateRSS(db *database.DB, config types.Config) string {
}
// TODO (2022-12-08): augment ListThreads to choose getting author of latest post or thread creator (currently latest
// post always)
- threads := db.ListThreads(true, 0)
+ sortByPost := true
+ includePrivateThreads := false
+ threads := db.ListThreads(sortByPost, includePrivateThreads)
entries := make([]string, len(threads))
for i, t := range threads {
fulltime := t.Publish.Format(rfc822RSS)
@@ -725,14 +737,11 @@ func (h *RequestHandler) NewThreadRoute(res http.ResponseWriter, req *http.Reque
// Handle POST (=>
title := req.PostFormValue("title")
content := req.PostFormValue("content")
+ isPrivate := req.PostFormValue("isPrivate") == "1"
- private := 0
- if req.PostFormValue("isPrivate") == "1" {
- private = 1
- }
// TODO (2022-01-10): unstub topicid, once we have other topics :)
// the new thread was created: forward info to database
- threadid, err := h.db.CreateThread(title, content, userid, 1, private)
+ threadid, err := h.db.CreateThread(title, content, userid, 1, isPrivate)
if err != nil {
data := GenericMessageData{
Title: h.translator.Translate("NewThreadCreateError"),