cerca

lean forum software (pmc local branch)
Log | Files | Refs | README | LICENSE

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:
Mdatabase/database.go | 47++++++++++++++++++++++++++++++++++-------------
Mhtml/index.html | 2+-
Mhtml/thread.html | 4++--
Mi18n/i18n.go | 2+-
Mserver/server.go | 45+++++++++++++++++++++++++++------------------
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"),