sites moved to configuration instead of db

This commit is contained in:
me 2025-03-15 11:47:10 +02:00
parent f2ded06594
commit bbe4dc87ab
11 changed files with 142 additions and 81 deletions

View file

@ -0,0 +1,24 @@
{
"domain": "localhost",
"port": 8080,
"db_path": "./ucs.db",
"sites": {
"localhost": {
"info": {
"id": 2,
"url": "localhost",
"comment_token": "שלום"
},
"cors": ["localhost"],
"limits_per_second": {
"get": 500,
"post": 20
},
"max_lengths": {
"user": 20,
"website": 100,
"message": 200
}
}
}
}

View file

@ -12,6 +12,7 @@
"@blackglory/better-sqlite3-migrations": "^0.1.19", "@blackglory/better-sqlite3-migrations": "^0.1.19",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^11.8.1",
"compression": "^1.8.0", "compression": "^1.8.0",
"config": "^3.3.12",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.21.2", "express": "^4.21.2",
"express-rate-limit": "^7.5.0", "express-rate-limit": "^7.5.0",
@ -378,6 +379,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/config": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz",
"integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==",
"license": "MIT",
"dependencies": {
"json5": "^2.2.3"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -977,6 +990,18 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/justypes": { "node_modules/justypes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/justypes/-/justypes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/justypes/-/justypes-3.1.2.tgz",

View file

@ -18,6 +18,7 @@
"@blackglory/better-sqlite3-migrations": "^0.1.19", "@blackglory/better-sqlite3-migrations": "^0.1.19",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^11.8.1",
"compression": "^1.8.0", "compression": "^1.8.0",
"config": "^3.3.12",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.21.2", "express": "^4.21.2",
"express-rate-limit": "^7.5.0", "express-rate-limit": "^7.5.0",

View file

@ -14,7 +14,7 @@ router.use(express.json());
// POST // POST
router.post('/:site/*', utils.limiter(20), (req, res) => { router.post('/:site/*', utils.limiter(20), (req, res) => {
const site = req.params.site; const site_url = req.params.site;
const path = req.params[0]; const path = req.params[0];
if (!req.body.token || !req.body.message) { if (!req.body.token || !req.body.message) {
@ -22,30 +22,27 @@ router.post('/:site/*', utils.limiter(20), (req, res) => {
return; return;
} }
let object = { let comment = {
user: utils.escapeHtml(req.body.name) || "Anonymous", user: utils.escapeHtml(req.body.name) || "Anonymous",
user_website: utils.escapeHtml(req.body.website) || null, user_website: utils.escapeHtml(req.body.website) || null,
message: utils.escapeHtml(req.body.message), message: utils.escapeHtml(req.body.message),
reply_to: req.body.reply_to || null, reply_to: req.body.reply_to || null,
site,
path,
}; };
// validation // validation
const user_token = req.body.token; const user_token = req.body.token;
const site_info = db.siteInfo(site); const site = db.siteInfo(site_url);
console.log(site_info.comment_token, site_info.message_length_limit);
if (user_token !== site_info.comment_token) { if (user_token !== site.info.comment_token) {
res.status(403).json("תשובת סינון שגויה."); res.status(403).json("תשובת סינון שגויה.");
} else if (object.user.length > utils.MAX_LENGTHS.username) { } else if (object.user.length > site.max_lengths.user) {
res.status(400).json("שם משתמש ארוך מדי."); res.status(400).json("שם משתמש ארוך מדי.");
} else if (object.user_website > utils.MAX_LENGTHS.user_website) { } else if (object.user_website > site.max_lengths.website) {
res.status(400).json("כתובת אתר ארוכה מדי."); res.status(400).json("כתובת אתר ארוכה מדי.");
} else if (object.message > site_info.message_length_limit) { } else if (object.message > site.max_lengths.message) {
res.status(400).json("הודעה ארוכה מדי."); res.status(400).json("הודעה ארוכה מדי.");
} else { } else {
const comment = db.insertPageComment(object); const comment = db.insertPageComment(site_url, path, comment);
res.json(comment); res.json(comment);
} }

12
backend/src/config.mjs Normal file
View file

@ -0,0 +1,12 @@
import config from 'config';
const configuration = config.util.toObject();
// console.log(JSON.stringify(configuration));
export default {
config: configuration,
getSite: (site_url) => {
return config.sites[site_url];
}
};

View file

@ -2,6 +2,7 @@ import Database from 'better-sqlite3';
import { migrate } from '@blackglory/better-sqlite3-migrations'; import { migrate } from '@blackglory/better-sqlite3-migrations';
import utils from './utils.mjs'; import utils from './utils.mjs';
import config from './config.mjs';
// class // class
@ -12,14 +13,14 @@ export class DB {
siteInfo(site) { siteInfo(site) {
return getSiteInfo(this.my_db, site); return getSiteInfo(this.my_db, site);
} }
siteComments(site) { siteComments(site_url) {
return getSiteComments(this.my_db, site); return getSiteComments(this.my_db, site_url);
} }
pageComments(site, page) { pageComments(site_url, page) {
return getPageComments(this.my_db, site, page); return getPageComments(this.my_db, site_url, page);
} }
insertPageComment(comment) { insertPageComment(site_url, path, comment) {
insertPageComment(this.my_db, comment); insertPageComment(this.my_db, site_url, path, comment);
} }
} }
@ -32,13 +33,6 @@ function migrations() {
return [ return [
{ version: 1, { version: 1,
up: ` up: `
CREATE TABLE site (
id integer primary key autoincrement,
url text not null,
comment_token text not null,
length_limit integer
);
CREATE TABLE comment ( CREATE TABLE comment (
id integer not null, id integer not null,
site integer not null, site integer not null,
@ -48,13 +42,11 @@ CREATE TABLE comment (
message text not null, message text not null,
published text default (datetime('now')), published text default (datetime('now')),
reply_to integer, reply_to integer,
FOREIGN KEY(site) REFERENCES site(id),
PRIMARY KEY (site, path, id) PRIMARY KEY (site, path, id)
); );
`, `,
down: ` down: `
DROP TABLE comment; DROP TABLE comment;
DROP TABLE site;
` `
} }
]; ];
@ -63,7 +55,7 @@ DROP TABLE site;
// setup // setup
function createDB() { function createDB() {
const db = new Database(process.env.DB || utils.DEFAULT_DB_PATH); const db = new Database(utils.db_path);
db.pragma('journal_mode = WAL'); db.pragma('journal_mode = WAL');
migrate(db, migrations(), 1); migrate(db, migrations(), 1);
return db; return db;
@ -71,25 +63,31 @@ function createDB() {
// queries // queries
function getSiteInfo(db, site) { function getSiteInfo(db, site_url) {
return db.prepare(`SELECT comment_token, length_limit as message_length_limit FROM site WHERE url = ?`).get(site); const site = config.getSite(site_url);
if (!site || !site.info) { throw "Unknown site" }
return site;
} }
function insertPageComment(db, object) { function insertPageComment(db, site_url, path, comment) {
const site = config.getSite(site_url);
if (!site || !site.info.id) { throw "Unknown site" }
let object = {...comment, site_id: site.info.id, path: path };
const stmt = db.prepare(` const stmt = db.prepare(`
INSERT INTO comment(id, site, path, user, user_website, message, reply_to) INSERT INTO comment(id, site, path, user, user_website, message, reply_to)
SELECT SELECT
( SELECT count(*) ( SELECT count(*)
FROM (SELECT * FROM comment WHERE path = @path) c FROM comment
JOIN (SELECT id FROM site WHERE url = @site) s WHERE site = @site_id AND path = @path
ON s.id = c.site
), ),
( SELECT id FROM site WHERE url = @site ), @site_id,
@path, @path,
@user, @user,
@user_website, @user_website,
@message, @message,
@reply_to @reply_to
RETURNING RETURNING
id as id, id as id,
user, user,
@ -102,40 +100,41 @@ function insertPageComment(db, object) {
return stmt.all(object); return stmt.all(object);
} }
function getPageComments(db, site, path) { function getPageComments(db, site_url, path) {
const site = config.getSite(site_url);
if (!site || !site.info.id) { return []; }
const stmt = db.prepare(` const stmt = db.prepare(`
SELECT SELECT
c.id as id, id,
user, user,
user_website, user_website,
message, message,
published, published,
reply_to reply_to
FROM FROM comment
(SELECT id from site where url = @site) s WHERE site = @site_id AND path = @path
JOIN (SELECT * FROM comment WHERE path = @path) c ORDER BY id
ON c.site = s.id
ORDER BY c.id
;`); ;`);
return stmt.all({ site, path }); return stmt.all({ site_id: site.info.id, path });
} }
function getSiteComments(db, site) { function getSiteComments(db, site_url) {
const site = config.getSite(site_url);
if (!site || !site.info.id) { return []; }
const stmt = db.prepare(` const stmt = db.prepare(`
SELECT SELECT
c.id, id,
s.url as site, @site_url as site,
c.path, path,
c.user, user,
c.user_website, user_website,
c.message, message,
c.published published
FROM FROM
(SELECT id, url from site where url = @site) s comment
JOIN comment c WHERE site = @site_id
ON c.site = s.id ORDER BY published DESC
ORDER BY c.published DESC
; ;
`); `);
return stmt.all({ site }); return stmt.all({ site_id: site.info.id, site_url });
} }

View file

@ -12,13 +12,14 @@ export default router;
router.use(express.json()); router.use(express.json());
const domain = process.env.DOMAIN || utils.DEFAULT_DOMAIN; const domain = utils.domain;
router.get('/:site', utils.limiter(500), (req, res) => { router.get('/:site', utils.limiter(500), (req, res) => {
const site = req.params.site; const site = req.params.site;
var feed = new Feed({ var feed = new Feed({
title: 'UCS', title: 'UCS',
generator: 'UCS',
description: 'תגובות עבור האתר ' + site, description: 'תגובות עבור האתר ' + site,
id: domain + '/feed/' + site, id: domain + '/feed/' + site,
link: domain, link: domain,
@ -49,6 +50,7 @@ router.get('/:site/*', utils.limiter(500), (req, res) => {
var feed = new Feed({ var feed = new Feed({
title: 'UCS', title: 'UCS',
generator: 'UCS',
description: 'תגובות עבור הדף ' + site + '/' + path, description: 'תגובות עבור הדף ' + site + '/' + path,
id: domain + '/feed/' + site, id: domain + '/feed/' + site,
link: domain, link: domain,

View file

@ -19,7 +19,7 @@ app.use('/atom', feed);
// Listen // Listen
const port = process.env.PORT || utils.DEFAULT_PORT; const port = utils.port;
app.listen(port, () => { app.listen(port, () => {
console.log(`Listening on port ${port}`); console.log(`Listening on port ${port}`);

View file

@ -22,7 +22,7 @@ app.use(
app.use(morgan('combined')); app.use(morgan('combined'));
const corsOptions = { const corsOptions = {
origin: utils.DEFAULT_CORS, origin: utils.cors,
optionsSuccessStatus: 200 optionsSuccessStatus: 200
}; };

View file

@ -1,24 +1,26 @@
import RateLimit from 'express-rate-limit'; import RateLimit from 'express-rate-limit';
import config from './config.mjs';
// Constants // Constants
export const MAX_LENGTHS = { const domain = process.env.DOMAIN || config.domain || "localhost";
username: 100,
user_website: 200,
message_body: 1000
};
export const DEFAULT_PORT = 8080; const port = process.env.PORT || config.port || 8080;
export const DEFAULT_CORS = ["alloca.space", "www.alloca.space"]; const db_path = process.env.DB || config.db_path || "ucs.db";
export const DEFAULT_DOMAIN = "comments.alloca.space"; const cors = (function() {
let origins = new Set();
export const DEFAULT_DB_PATH = "ucs.db"; for (const site in config.sites) {
config.sites[site].cors.forEach(origin => {origins.add(origin) });
}
return Array.from(origins);
})();
// functions // functions
export function escapeHtml(unsafe) { function escapeHtml(unsafe) {
return unsafe return unsafe
.replace(/&/g, "&") .replace(/&/g, "&")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
@ -29,18 +31,17 @@ export function escapeHtml(unsafe) {
// limiters // limiters
export const limiter = limit => RateLimit({ const limiter = limit => RateLimit({
windowMs: 1 * 60 * 1000, windowMs: 1 * 60 * 1000,
max: limit, max: limit,
}); });
export default { export default {
MAX_LENGTHS, domain,
DEFAULT_PORT, port,
DEFAULT_CORS, db_path,
DEFAULT_DOMAIN, cors,
DEFAULT_DB_PATH,
escapeHtml, escapeHtml,
limiter limiter
}; };

View file

@ -20,7 +20,7 @@ cp ../frontend/dist/assets/* public/
PACKAGE="ucs-$(cat package.json | jq -r .version).tar.gz" PACKAGE="ucs-$(cat package.json | jq -r .version).tar.gz"
tar czvf "$PACKAGE" package.json package-lock.json src/ node_modules/ public/ tar czvf "$PACKAGE" package.json package-lock.json node_modules/ src/ config/ public/
cd .. cd ..
mv backend/"$PACKAGE" . mv backend/"$PACKAGE" .
echo "$PACKAGE created." echo "$PACKAGE created."