import express from 'express'; import Database from 'better-sqlite3'; import { migrate } from '@blackglory/better-sqlite3-migrations'; // Constants const MAX_LENGTHS = { username: 100, user_website: 200, message_body: 1000 }; // functions function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // db const db = new Database('ucs.db'); // , { verbose: console.log }); db.pragma('journal_mode = WAL'); const migrations = [ { version: 1, up: ` CREATE TABLE site ( id integer primary key autoincrement, url text not null, comment_token text not null, length_limit integer ) STRICT; CREATE TABLE comment ( id integer not null, site integer not null, path text not null, user text not null, user_website text, message text not null, published text default (datetime('now')), reply_to integer, FOREIGN KEY(site) REFERENCES site(id), PRIMARY KEY (site, path, id) ) STRICT; `, down: ` DROP TABLE comment; DROP TABLE site; ` } ]; migrate(db, migrations, 1); // server const app = express(); const port = 8080; app.use(express.static("public")); app.use(express.urlencoded({ extended: true })); app.get('/', (req, res) => { res.send(`
Hello, ${req.get('User-Agent')}!
`); }); // GET app.get('/url/:site/*', (req, res) => { const site = req.params.site; const path = req.params[0]; const stmt = db.prepare(` SELECT c.id as id, user, user_website, message, published, reply_to FROM (SELECT id from site where url = @site) s JOIN (SELECT * FROM comment WHERE path = @path) c ON c.site = s.id ORDER BY c.id ;`); const comments = stmt.all({ site, path }); res.json(comments); }); app.use(express.json()); // POST app.post('/url/:site/*', (req, res) => { const site = req.params.site; const path = req.params[0]; let object = { user: escapeHtml(req.body.user), user_website: escapeHtml(req.body.user_website), message: escapeHtml(req.body.message), reply_to: req.body.reply_to || null, site, path, }; // validation const user_token = req.body.token; const site_info = db.prepare(`SELECT comment_token, length_limit as message_length_limit FROM site WHERE url = ?`).get(site); console.log(site_info.comment_token, site_info.message_length_limit); if (user_token !== site_info.comment_token) { res.status(403).json("Wrong token."); } else if (object.user.length > MAX_LENGTHS.username) { res.status(400).json("Username is too long."); } else if (object.user_website > MAX_LENGTHS.user_website) { res.status(400).json("User website is too long."); } else if (object.message > site_info.message_length_limit) { res.status(400).json("Message body is too long."); } else { const stmt = db.prepare(` INSERT INTO comment(id, site, path, user, user_website, message, reply_to) SELECT ( SELECT count(*) FROM (SELECT * FROM comment WHERE path = @path) c JOIN (SELECT id FROM site WHERE url = @site) s ON s.id = c.id ), ( SELECT id FROM site WHERE url = @site ), @path, @user, @user_website, @message, @reply_to RETURNING id as id, user, user_website, message, published, reply_to ; `); const comment = stmt.all(object); res.json(comment); } }); app.listen(port, () => { console.log(`Listening on port ${port}`); });