const express = require('express'); const fs = require('node:fs'); const path = require('node:path'); const { DatabaseSync } = require('node:sqlite'); const app = express(); const bootedAt = new Date(); const dataDir = process.env.TOKAY_DATA_DIR || path.join(__dirname, 'data'); fs.mkdirSync(dataDir, { recursive: true }); const database = new DatabaseSync(path.join(dataDir, 'guestbook.sqlite')); database.exec(` CREATE TABLE IF NOT EXISTS entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, message TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ) `); const entryCount = database.prepare('SELECT COUNT(*) AS count FROM entries').get().count; if (entryCount === 0) { database.prepare('INSERT INTO entries (name, message) VALUES (?, ?)') .run('Your host', 'Welcome. Sign below.'); } app.use(express.urlencoded({ extended: false })); const escapeHtml = (value) => String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); const page = ({ error = '', draft = {} } = {}) => { const entries = database.prepare(` SELECT id, name, message, created_at FROM entries ORDER BY id DESC `).all(); const cards = entries.map((entry) => `

${escapeHtml(entry.message)}

`).join(''); return ` The guestbook

The guestbook

Leave a note for whoever arrives next.

${error ? `

${escapeHtml(error)}

` : ''}

${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}

${cards}

Running since ${escapeHtml(bootedAt.toISOString())}

`; }; app.get('/health', (_request, response) => response.json({ ok: true })); app.get('/', (_request, response) => response.type('html').send(page())); app.post('/sign', (request, response) => { const name = String(request.body.name || '').trim(); const message = String(request.body.message || '').trim(); if (!name || !message) { return response.status(400).type('html').send(page({ error: 'Add your name and a note.', draft: { name, message }, })); } database.prepare('INSERT INTO entries (name, message) VALUES (?, ?)') .run(name.slice(0, 60), message.slice(0, 500)); return response.redirect(303, '/'); }); const port = Number(process.env.PORT || 3000); const server = app.listen(port, '0.0.0.0', () => { console.log(`Guestbook listening on port ${port}`); }); const shutdown = () => server.close(() => { database.close(); process.exit(0); }); process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown);