add custom usernames to guestbook

")

")

=> /guestbook Back to guestbook")
")

")
entryn", name))
=> /guestbook/setname Set custom name

")
> %s

", name, txt))
")

=> /guestbook Back to guestbook")
> %s
", createdAt.Format("2006-01-02"), name, message))
")
This commit is contained in:
Travis Shears 2025-10-09 21:03:33 +02:00
parent ff32bb9c48
commit a5e480746e
3 changed files with 83 additions and 12 deletions

20
LICENSE.md Normal file
View file

@ -0,0 +1,20 @@
ANTI-CAPITALIST SOFTWARE LICENSE (v 1.4)
Copyright © 2022 Travis Shears
This is anti-capitalist software, released for free use by individuals and organizations that do not operate by capitalist principles.
Permission is hereby granted, free of charge, to any person or organization (the "User") obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, distribute, and/or sell copies of the Software, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all copies or modified versions of the Software.
2. The User is one of the following:
- An individual person, laboring for themselves
- A non-profit organization
- An educational institution
- An organization that seeks shared profit for all of its members, and allows non-members to set the cost of their labor
3. If the User is an organization with owners, then all owners are workers and all workers are owners with equal equity and/or equal vote.
4. If the User is an organization, then the User is not law enforcement or military, or working for or under either.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -55,6 +55,8 @@ func (book *GuestBook) HandleRequest(w gemini.ResponseWriter, req *gemini.Reques
book.serveIndex(w, req)
case path == "/guestbook/new":
book.serveNewEntry(w, req)
case path == "/guestbook/setname":
book.serveSetName(w, req)
case path == "/guestbook/admin":
book.serveAdmin(w, req)
case strings.HasPrefix(path, "/guestbook/approve"):
@ -92,6 +94,7 @@ func (book *GuestBook) serveDelete(w gemini.ResponseWriter, req *gemini.Request)
}
entryId := req.URL.Query().Get("id")
slog.Info("Deleting", "userId", userId, "entryId", entryId)
_, err = book.db.Exec("DELETE FROM guestbook WHERE id = ?", entryId)
if err != nil {
slog.Error("Error deleting guestbook entry", "error", err)
@ -101,6 +104,39 @@ func (book *GuestBook) serveDelete(w gemini.ResponseWriter, req *gemini.Request)
book.serveAdmin(w, req)
}
func (book *GuestBook) serveSetName(w gemini.ResponseWriter, req *gemini.Request) {
if req.Certificate() == nil {
w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required")
return
}
if req.URL.RawQuery == "" {
w.WriteStatusMsg(gemini.StatusPlainInput, "Name:")
return
}
userId, err := UserIDFromCert(req.Certificate())
newName := req.URL.RawQuery
slog.Info("Setting new name for guestbook user", "userId", userId, "newName", newName)
if err != nil {
slog.Error("Error creating user id from cert", "error", err)
w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error generating user ID")
return
}
slog.Info("Setting name", "newName", newName, "userId", userId)
_, err = book.db.Exec("UPDATE guestbook SET name = ? WHERE user_id = ?", newName, userId)
if err != nil {
slog.Error("Error setting name for guestbook entry", "error", err)
w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error setting name for guestbook entry")
return
}
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
var content strings.Builder
content.WriteString("# Success!\n")
content.WriteString("Updated the username for you entry / entries if you have mutiple using the same idenitiy\n\n")
content.WriteString("\n\n=> /guestbook Back to guestbook")
w.WriteBody([]byte(content.String()))
}
func (book *GuestBook) serveApprove(w gemini.ResponseWriter, req *gemini.Request) {
if req.Certificate() == nil {
w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required")
@ -139,29 +175,46 @@ func (book *GuestBook) serveNewEntry(w gemini.ResponseWriter, req *gemini.Reques
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
name := req.Certificate().Subject.CommonName
userId, err := UserIDFromCert(req.Certificate())
slog.Info("User ID created", "user_id", userId)
if err != nil {
slog.Error("Error creating user id from cert", "error", err)
w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error generating user ID")
return
}
txt := req.URL.RawQuery
_, err = book.db.Exec("INSERT INTO guestbook(name, user_id, approved, message) VALUES(?, ?, ?, ?)", name, userId, false, txt)
result, err := book.db.Exec("INSERT INTO guestbook(name, user_id, approved, message) VALUES(?, ?, ?, ?)", name, userId, false, txt)
if err != nil {
slog.Error("Error writing to database", "error", err)
w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error writing to database")
return
}
_, err = result.LastInsertId()
if err != nil {
slog.Error("Error getting last insert ID", "error", err)
w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error getting inserted ID")
return
}
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
var content strings.Builder
content.WriteString("# Success!\n")
content.WriteString("Your entry was added to the guestbook\n\n")
content.WriteString(fmt.Sprintf("username: %s\nentry:\n> %s\n\n", name, txt))
content.WriteString(fmt.Sprintf("username: %s\n", name))
content.WriteString("This username was taken from your identity.\n=> /guestbook/setname Set custom name\n\n")
content.WriteString(fmt.Sprintf("entry:\n> %s\n\n", txt))
content.WriteString("For now since the entry has not been approved it will appear in the list with xxxxx in place of chars.\n")
content.WriteString("\n\n=> /guestbook Back to guestbook")
w.WriteBody([]byte(content.String()))
}
func censorString(s string) string {
masked := []rune(s)
for i, c := range masked {
if c != ' ' {
masked[i] = 'x'
}
}
return string(masked)
}
func (book *GuestBook) serveIndex(w gemini.ResponseWriter, req *gemini.Request) {
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
var content strings.Builder
@ -188,14 +241,8 @@ func (book *GuestBook) serveIndex(w gemini.ResponseWriter, req *gemini.Request)
var message string
rows.Scan(&id, &name, &approved, &createdAt, &message)
if !approved {
// Overwrite all non-space characters in message with "x"
masked := []rune(message)
for i, c := range masked {
if c != ' ' {
masked[i] = 'x'
}
}
message = string(masked)
message = censorString(message)
name = censorString(name)
}
content.WriteString(fmt.Sprintf("%s :: %s\n> %s\n", createdAt.Format("2006-01-02"), name, message))
content.WriteString("\n")

View file

@ -6,8 +6,12 @@ Hey gemspace! Thanks for visiting my capsule. Why not leave a message?
=> /guestbook/new Sign the guestbook
=> /guestbook/admin Admin
=> /codeview/raw/personal-gemini-capsule/src/branch/main/internal/guestbook/guestbook.go View code for this page
Messages like "xxxxx xxxx" mean they are awaiting my approval. Once approved, the actual text will be rendered.
== 2025.10.09 ==
Addressing the issue gdorn raised I've added the option to set a username.
------------------------