Figuring this shit out
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: correct SQL syntax and wire up client-side UI logic

- Fix CREATE TABLE typo (NOTE → NOT EXISTS) and rename user_id to user_did
- Add pnpm-workspace.yaml for better-sqlite3 and esbuild build deps
- Implement fetchEntries, postEntry, removeEntry API helpers
- Add renderEntries, updateAuthUI, escapeHtml DOM helpers
- Wire up handleLogin, handleLogout, handleSubmit, handleDelete
- Add delete button (owner-only), error display, and entry rendering
- Format index.html CSS for readability

aria f2770fbd f0ed7737

+218 -35
+3
pnpm-workspace.yaml
··· 1 + onlyBuiltDependencies: 2 + - better-sqlite3 3 + - esbuild
+2 -2
src/db.ts
··· 29 29 30 30 // Setup new db 31 31 db.exec(` 32 - CREATE TABLE IF NOTE EXISTS entries ( 32 + CREATE TABLE IF NOT EXISTS entries ( 33 33 id INTEGER PRIMARY KEY AUTOINCREMENT, 34 - user_id TEXT NOT NULL 34 + user_did TEXT NOT NULL, 35 35 user_handle TEXT NOT NULL, 36 36 message TEXT NOT NULL, 37 37 created_at TEXT NOT NULL DEFAULT (datetime('now'))
+213 -33
src/public/index.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 + 3 4 <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 5 + <meta charset="UTF-8" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 7 <title>AT Protocol Guestbook</title> 7 8 <style> 8 9 /* Keeping styles minimal and inline for now. 9 10 No build tools, no CSS frameworks — just plain CSS. */ 10 - * { margin: 0; padding: 0; box-sizing: border-box; } 11 + * { 12 + margin: 0; 13 + padding: 0; 14 + box-sizing: border-box; 15 + } 11 16 12 17 body { 13 - font-family: system-ui, -apple-system, sans-serif; 18 + font-family: 19 + system-ui, 20 + -apple-system, 21 + sans-serif; 14 22 max-width: 640px; 15 23 margin: 0 auto; 16 24 padding: 2rem 1rem; ··· 18 26 color: #1a1a1a; 19 27 } 20 28 21 - h1 { margin-bottom: 0.5rem; } 29 + h1 { 30 + margin-bottom: 0.5rem; 31 + } 22 32 23 33 .subtitle { 24 34 color: #666; ··· 41 51 width: 260px; 42 52 } 43 53 44 - .auth-section button, .entry-form button { 54 + .auth-section button, 55 + .entry-form button { 45 56 padding: 0.5rem 1rem; 46 57 background: #0066ff; 47 58 color: white; ··· 51 62 cursor: pointer; 52 63 } 53 64 54 - .auth-section button:hover, .entry-form button:hover { 65 + .auth-section button:hover, 66 + .entry-form button:hover { 55 67 background: #0052cc; 56 68 } 57 69 ··· 70 82 margin-bottom: 0.5rem; 71 83 } 72 84 73 - .entries { list-style: none; } 85 + .entries { 86 + list-style: none; 87 + } 74 88 75 89 .entries li { 76 90 background: white; ··· 102 116 } 103 117 </style> 104 118 </head> 119 + 105 120 <body> 106 121 <h1>Guestbook</h1> 107 - <p class="subtitle">Sign in with your Bluesky handle to leave a message.</p> 122 + <p class="subtitle"> 123 + Sign in with your Bluesky handle to leave a message. 124 + </p> 108 125 109 126 <!-- Auth section: shown when not logged in --> 110 127 <div class="auth-section" id="auth-logged-out"> ··· 115 132 <!-- Auth section: shown when logged in --> 116 133 <div class="auth-section" id="auth-logged-in" style="display: none;"> 117 134 <span>Signed in as <strong id="display-handle"></strong></span> 118 - <button onclick="handleLogout()" style="background: #666; margin-left: 1rem;">Sign Out</button> 135 + <button class="secondary" onclick="handleLogout()">Sign Out</button> 119 136 </div> 120 137 121 138 <!-- New entry form: only visible when logged in --> 122 139 <div class="entry-form" id="entry-form" style="display: none;"> 123 140 <textarea id="message-input" placeholder="Leave a message..."></textarea> 124 141 <button onclick="handleSubmit()">Post</button> 142 + <p class="error" id="submit-error" style="display: none;"></p> 125 143 </div> 126 144 127 145 <!-- Guestbook entries --> ··· 132 150 <p id="status">Loading entries...</p> 133 151 134 152 <script> 135 - // ========================================================= 136 - // This is where our client-side logic will live. 137 - // For now, it's just placeholder functions so the page 138 - // doesn't throw errors when you click buttons. 139 - // 140 - // We'll fill these in as we build out the server routes. 141 - // ========================================================= 153 + // === 154 + // state 155 + // === 156 + 157 + let currentUser = null; 158 + 159 + // === 160 + // API helpers 161 + // === 162 + 163 + async function fetchEntries() { 164 + const response = await fetch("/api/entries"); 165 + if (!response.ok) { 166 + throw new Error("Failed to fetch entries"); 167 + } 168 + 169 + return await response.json(); 170 + } 171 + 172 + async function postEntry(message) { 173 + const response = await fetch("api/entries", { 174 + method: "POST", 175 + headers: { "Content-Type": "application/json" }, 176 + body: JSON.stringify({ 177 + user_did: currentUser.did, 178 + user_handle: currentUser.handle, 179 + message, 180 + }), 181 + }); 182 + 183 + if (!response.ok) { 184 + const err = await response.json(); 185 + throw new Error(err.error || "Failed to post entry"); 186 + } 187 + 188 + return await response.json(); 189 + } 190 + 191 + async function removeEntry(id) { 192 + const response = await fetch(`/api/entries/${id}`, { 193 + method: "DELETE", 194 + headers: { "Content-Type": "application/json" }, 195 + body: JSON.stringify({ 196 + user_did: currentUser.did, 197 + }), 198 + }); 199 + 200 + if (!response.ok && response.status !== 204) { 201 + throw new Error("Failed to delete entry"); 202 + } 203 + } 204 + 205 + // === 206 + // DOM Updates 207 + // a framework will handle this in actual project? right?? RIGHT?!!!?? 208 + // === 209 + 210 + function renderEntries(entries) { 211 + const list = document.getElementById("entries-list"); 212 + const status = document.getElementById("status"); 213 + 214 + if (entries.length === 0) { 215 + list.innerHTML = ""; 216 + status.textContent = 217 + "No entries yet. Be the first to sign!"; 218 + status.style.display = "block"; 219 + return; 220 + } 221 + 222 + status.style.display = "none"; 223 + 224 + list.innerHTML = entries 225 + .map((entry) => { 226 + // only shows delete button if user is allowed to delete. 227 + const canDelete = 228 + currentUser && currentUser.did === entry.user_did; 229 + 230 + const deleteBtn = canDelete 231 + ? `<button class="delete-btn" onclick="handleDelete(${entry.id})">delete</button>` 232 + : ""; 233 + 234 + const date = new Date(entry.created_at); 235 + const dateStr = date.toLocaleDateString("en-US", { 236 + month: "short", 237 + day: "numeric", 238 + year: "numeric", 239 + }); 240 + 241 + return ` 242 + <li> 243 + <div class="entry-header"> 244 + <div> 245 + <span class="entry-handle">@${escapeHtml(entry.user_handle)}</span> 246 + <span class="entry-date">${dateStr}</span> 247 + </div> 248 + ${deleteBtn} 249 + </div> 250 + <p class="entry-message">${escapeHtml(entry.message)}</p> 251 + </li> 252 + `; 253 + }) 254 + .join(""); // .join("") is like .collect::<String>() in Rust 255 + } 256 + 257 + function updateAuthUI() { 258 + const loggedOut = document.getElementById("auth-logged-out"); 259 + const loggedIn = document.getElementById("auth-logged-in"); 260 + const entryForm = document.getElementById("entry-form"); 261 + const displayHandle = document.getElementById("display-handle"); 262 + 263 + if (currentUser) { 264 + loggedOut.style.display = "none"; 265 + loggedIn.style.display = "block"; 266 + entryForm.style.display = "block"; 267 + displayHandle.textContent = `@${currentUser.handle}`; 268 + } else { 269 + loggedOut.style.display = "block"; 270 + loggedIn.style.display = "none"; 271 + entryForm.style.display = "none"; 272 + } 273 + } 274 + 275 + function escapeHtml(text) { 276 + const div = document.createElement("div"); 277 + div.textContent = text; 278 + return div.innerHTML; 279 + } 280 + 281 + // === 282 + // Event handlers 283 + // === 142 284 143 285 async function loadEntries() { 144 - // TODO: fetch entries from GET /api/entries 145 - document.getElementById("status").textContent = 146 - "No entries yet. Be the first to sign!"; 286 + try { 287 + const entries = await fetchEntries(); 288 + renderEntries(entries); 289 + } catch (err) { 290 + document.getElementById("status").textContent = 291 + "Failed to load entries. Is the server running?"; 292 + console.error("Load entries error:", err); 293 + } 147 294 } 148 295 149 296 function handleLogin() { 150 - const handle = document.getElementById("handle-input").value.trim(); 297 + const input = document.getElementById("handle-input"); 298 + const handle = input.value.trim(); 151 299 if (!handle) return; 152 300 153 - // TODO: redirect to our server's OAuth start endpoint 154 - // For now, just log it so we can confirm the UI works 155 - console.log("Login requested for handle:", handle); 156 - alert("OAuth not wired up yet! Handle: " + handle); 301 + // TODO: ACTUALLY DO PROPER LOGIN SHIT!!! 302 + currentUser = { 303 + did: "did:plc:fake-" + handle.replace(/\./g, "-"), 304 + handle: handle, 305 + }; 306 + 307 + updateAuthUI(); 308 + 309 + loadEntries(); 157 310 } 158 311 159 312 function handleLogout() { 160 - // TODO: call POST /api/auth/logout 161 - console.log("Logout requested"); 313 + currentUser = null; 314 + updateAuthUI(); 315 + loadEntries(); 162 316 } 163 317 164 318 async function handleSubmit() { 165 - const message = document.getElementById("message-input").value.trim(); 319 + const input = document.getElementById("message-input"); 320 + const errorEl = document.getElementById("submit-error"); 321 + const message = input.value.trim(); 322 + 166 323 if (!message) return; 167 324 168 - // TODO: call POST /api/entries with the message 169 - console.log("Submit entry:", message); 325 + try { 326 + errorEl.style.display = "none"; 327 + await postEntry(message); 328 + input.value = ""; 329 + await loadEntries(); 330 + } catch (err) { 331 + errorEl.textContent = err.message; 332 + errorEl.style.display = "block"; 333 + console.error("Submit error:", err); 334 + } 170 335 } 171 336 172 - // Load entries when the page loads 337 + async function handleDelete(id) { 338 + if (!confirm("Delete this entry?")) return; 339 + 340 + try { 341 + await removeEntry(id); 342 + await loadEntries(); 343 + } catch (err) { 344 + console.error("Delete error:", err); 345 + alert("Failed to delete entry"); 346 + } 347 + } 348 + 349 + // === 350 + // Init 351 + // === 173 352 loadEntries(); 174 353 </script> 175 354 </body> 176 - </html> 355 + 356 + </html>