Guestbook Comment System — Design Spec

Overview

A page-specific guestbook/comment system for hellahung.com, built entirely on Cloudflare's stack (Pages Functions + D1). Every page gets a retro-styled comment section at the bottom (above the footer) where visitors can leave messages and reply to existing comments. All submissions require manual admin approval before going live.

Stack

Data Model

comments table

Column Type Notes
id INTEGER Primary key, autoincrement
page_slug TEXT Which page the comment belongs to (e.g. "bio")
name TEXT Display name of the commenter
message TEXT Comment body
status TEXT pending / approved / rejected
is_admin INTEGER 1 if posted by admin as a verified name
created_at TEXT ISO 8601 timestamp

replies table

Column Type Notes
id INTEGER Primary key, autoincrement
comment_id INTEGER FK to comments.id
name TEXT Display name of the replier
message TEXT Reply body
status TEXT pending / approved / rejected
is_admin INTEGER 1 if posted by admin as a verified name
created_at TEXT ISO 8601 timestamp

restricted_names table

Column Type Notes
id INTEGER Primary key, autoincrement
name TEXT Case-insensitive restricted name (e.g. "hellahung", "fudster")

admin_passwords table

Column Type Notes
id INTEGER Primary key, autoincrement
label TEXT Human-readable label (e.g. "Fudster")
password_hash TEXT Hashed password (bcrypt or similar)

API Endpoints (Pages Functions)

All live under functions/api/.

Public endpoints

Method Path Description
GET /api/comments?page={slug}&p={pageNum} Fetch approved comments for a page (5 per page, with their approved replies)
POST /api/comments Submit a new comment (requires Turnstile token)
POST /api/replies Submit a reply to a comment (requires Turnstile token)

Admin endpoints (password-protected)

Method Path Description
POST /api/admin/login Authenticate with password, returns session token
GET /api/admin/pending List all pending comments and replies
POST /api/admin/moderate Approve or reject a comment/reply by ID
POST /api/admin/post Post as a verified/restricted name (skips approval, gets badge)
GET /api/admin/names List restricted names
POST /api/admin/names Add a restricted name
DELETE /api/admin/names/:id Remove a restricted name
GET /api/admin/passwords List admin passwords (labels only, not hashes)
POST /api/admin/passwords Add an admin password
DELETE /api/admin/passwords/:id Remove an admin password

Visitor Flow

  1. Visitor scrolls to bottom of any page — sees the guestbook section
  2. Approved comments are displayed, 5 per page, with page number navigation (1, 2, 3...)
  3. Each comment shows its approved replies nested flat underneath (1 level deep, unlimited replies per comment)
  4. Visitor fills in name + message, solves Turnstile CAPTCHA
  5. On submit:
  6. Visitor can click "Reply" on any comment to expand an inline reply form (same flow: name + message + Turnstile)

Admin Flow

  1. Admin navigates to /admin — sees a login form
  2. Enters their password — authenticated via hashed comparison against admin_passwords table
  3. Admin dashboard shows:
  4. Session managed via a simple token stored in a cookie or localStorage

Guestbook UI

Placement

Styling

Pagination

SEO / Googlebot Support

Security

File Structure (New Files)

functions/
  _middleware.js              # Bot detection + SSR for comments
  api/
    comments.js               # GET (list) + POST (submit) comments
    replies.js                # POST submit reply
    admin/
      login.js                # POST authenticate
      pending.js              # GET pending items
      moderate.js             # POST approve/reject
      post.js                 # POST as verified name
      names.js                # GET/POST/DELETE restricted names
      passwords.js            # GET/POST/DELETE admin passwords
db/
  schema.sql                  # D1 schema (all tables)
  seed.sql                    # Initial restricted names + first admin password
admin.html                    # Admin dashboard page
_includes/
  guestbook.njk               # Guestbook partial (injected into layouts)

Out of Scope (for now)