fix cache invalidation

This commit is contained in:
Andrea Vos 2021-07-03 01:15:44 +02:00
parent c3f22ae36d
commit 38c5247ded
8 changed files with 81 additions and 37 deletions

View File

@ -6,12 +6,12 @@ import {buildDict, now, shuffle, handleErrorAsync, buildLocaleList} from "../../
import locales from '../../src/locales';
import {calculateStats, statsFile} from '../../src/stats';
import fs from 'fs';
import cache from "../../src/cache";
import { caches } from "../../src/cache";
const router = Router();
router.get('/admin/list', handleErrorAsync(async (req, res) => {
return res.json(await cache('main', 'admins.js', 10, async () => {
return res.json(await caches.admins.fetch(async () => {
const admins = await req.db.all(SQL`
SELECT u.username, p.teamName, p.locale, u.id, u.email, u.avatarSource
FROM users u
@ -47,7 +47,7 @@ router.get('/admin/list', handleErrorAsync(async (req, res) => {
}));
router.get('/admin/list/footer', handleErrorAsync(async (req, res) => {
return res.json(shuffle(await cache('main', 'footer.js', 10, async () => {
return res.json(shuffle(await caches.adminsFooter.fetch(async () => {
const fromDb = await req.db.all(SQL`
SELECT u.username, p.footerName, p.footerAreas, p.locale
FROM users u

View File

@ -6,8 +6,7 @@ import avatar from '../avatar';
import {buildPronoun, parsePronouns} from "../../src/buildPronoun";
import {loadTsv} from "../../src/tsv";
import {handleErrorAsync} from "../../src/helpers";
import cache from "../../src/cache";
import fs from "fs";
import { CacheObject } from "../../src/cache";
const translations = loadSuml('translations');
@ -38,7 +37,7 @@ router.get('/banner/:pronounName*.png', handleErrorAsync(async (req, res) => {
const pronounName = req.params.pronounName + req.params[0];
const result = await cache('banner', `${pronounName}.png`, 24 * 60, async () => {
const result = await new CacheObject('banner', `${pronounName}.png`, 24 * 60).fetch(async () => {
registerFont('static/fonts/quicksand-v21-latin-ext_latin-regular.ttf', {family: 'Quicksand', weight: 'regular'});
registerFont('static/fonts/quicksand-v21-latin-ext_latin-700.ttf', {family: 'Quicksand', weight: 'bold'});

View File

@ -1,12 +1,12 @@
import { Router } from 'express';
import {handleErrorAsync, shuffle} from "../../src/helpers";
import fs from 'fs';
import cache from "../../src/cache";
import { caches } from "../../src/cache";
const router = Router();
router.get('/blog', handleErrorAsync(async (req, res) => {
return res.json(await cache('main', 'blog.js', Infinity, async () => {
return res.json(await caches.blog.fetch(async () => {
const dir = __dirname + '/../../data/blog';
const posts = [];
fs.readdirSync(dir).forEach(file => {

View File

@ -2,6 +2,7 @@ import { Router } from 'express';
import SQL from 'sql-template-strings';
import {ulid} from "ulid";
import {isTroll, handleErrorAsync} from "../../src/helpers";
import { caches } from "../../src/cache";
const approve = async (db, id) => {
const { base_id } = await db.get(SQL`SELECT base_id FROM inclusive WHERE id=${id}`);
@ -17,19 +18,22 @@ const approve = async (db, id) => {
SET approved = 1, base_id = NULL
WHERE id = ${id}
`);
await caches.inclusive.invalidate();
}
const router = Router();
router.get('/inclusive', handleErrorAsync(async (req, res) => {
return res.json(await req.db.all(SQL`
SELECT i.*, u.username AS author FROM inclusive i
LEFT JOIN users u ON i.author_id = u.id
WHERE i.locale = ${global.config.locale}
AND i.approved >= ${req.isGranted('inclusive') ? 0 : 1}
AND i.deleted = 0
ORDER BY i.approved, i.insteadOf
`));
return res.json(await caches.inclusive.fetch(async () => {
return await req.db.all(SQL`
SELECT i.*, u.username AS author FROM inclusive i
LEFT JOIN users u ON i.author_id = u.id
WHERE i.locale = ${global.config.locale}
AND i.approved >= ${req.isGranted('inclusive') ? 0 : 1}
AND i.deleted = 0
ORDER BY i.approved, i.insteadOf
`);
}));
}));
router.get('/inclusive/search/:term', handleErrorAsync(async (req, res) => {
@ -79,6 +83,8 @@ router.post('/inclusive/hide/:id', handleErrorAsync(async (req, res) => {
WHERE id = ${req.params.id}
`);
await caches.inclusive.invalidate();
return res.json('ok');
}));
@ -103,6 +109,8 @@ router.post('/inclusive/remove/:id', handleErrorAsync(async (req, res) => {
WHERE id = ${req.params.id}
`);
await caches.inclusive.invalidate();
return res.json('ok');
}));

View File

@ -4,6 +4,7 @@ import {ulid} from "ulid";
import {createCanvas, loadImage, registerFont} from "canvas";
import {loadSuml} from "../loader";
import {handleErrorAsync, isTroll} from "../../src/helpers";
import { caches } from "../../src/cache";
const translations = loadSuml('translations');
@ -21,6 +22,7 @@ const approve = async (db, id) => {
SET approved = 1, base_id = NULL
WHERE id = ${id}
`);
await caches.nouns.invalidate();
}
const addVersions = async (req, nouns) => {
@ -69,14 +71,16 @@ const selectFragment = (sourcesMap, keyAndFragment) => {
const router = Router();
router.get('/nouns', handleErrorAsync(async (req, res) => {
return res.json(await addVersions(req, await req.db.all(SQL`
SELECT n.*, u.username AS author FROM nouns n
LEFT JOIN users u ON n.author_id = u.id
WHERE n.locale = ${global.config.locale}
AND n.deleted = 0
AND n.approved >= ${req.isGranted('nouns') ? 0 : 1}
ORDER BY n.approved, n.masc
`)));
return res.json(await caches.nouns.fetch(async () => {
return await addVersions(req, await req.db.all(SQL`
SELECT n.*, u.username AS author FROM nouns n
LEFT JOIN users u ON n.author_id = u.id
WHERE n.locale = ${global.config.locale}
AND n.deleted = 0
AND n.approved >= ${req.isGranted('nouns') ? 0 : 1}
ORDER BY n.approved, n.masc
`))
}));
}));
router.get('/nouns/search/:term', handleErrorAsync(async (req, res) => {
@ -127,6 +131,8 @@ router.post('/nouns/hide/:id', handleErrorAsync(async (req, res) => {
WHERE id = ${req.params.id}
`);
await caches.nouns.invalidate();
return res.json('ok');
}));
@ -151,6 +157,8 @@ router.post('/nouns/remove/:id', handleErrorAsync(async (req, res) => {
WHERE id = ${req.params.id}
`);
await caches.nouns.invalidate();
return res.json('ok');
}));

View File

@ -4,6 +4,7 @@ import md5 from "js-md5";
import {ulid} from "ulid";
import avatar from "../avatar";
import {handleErrorAsync} from "../../src/helpers";
import { caches } from "../../src/cache";
const normalise = s => s.trim().toLowerCase();
@ -102,6 +103,11 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
)`);
}
if (req.body.teamName) {
await caches.admins.invalidate();
await caches.adminsFooter.invalidate();
}
return res.json(await fetchProfiles(req.db, req.user.username, true));
}));

View File

@ -2,7 +2,7 @@ import { Router } from 'express';
import SQL from 'sql-template-strings';
import {ulid} from "ulid";
import {isTroll, handleErrorAsync} from "../../src/helpers";
import cache from "../../src/cache";
import { caches } from "../../src/cache";
const approve = async (db, id) => {
const { base_id } = await db.get(SQL`SELECT base_id FROM terms WHERE id=${id}`);
@ -18,12 +18,13 @@ const approve = async (db, id) => {
SET approved = 1, base_id = NULL
WHERE id = ${id}
`);
await caches.terms.invalidate();
}
const router = Router();
router.get('/terms', handleErrorAsync(async (req, res) => {
return res.json(await cache('main', 'terms.js', 10, () => {
return res.json(await caches.terms.fetch(() => {
return req.db.all(SQL`
SELECT i.*, u.username AS author FROM terms i
LEFT JOIN users u ON i.author_id = u.id
@ -82,6 +83,8 @@ router.post('/terms/hide/:id', handleErrorAsync(async (req, res) => {
WHERE id = ${req.params.id}
`);
await caches.terms.invalidate();
return res.json('ok');
}));
@ -106,6 +109,8 @@ router.post('/terms/remove/:id', handleErrorAsync(async (req, res) => {
WHERE id = ${req.params.id}
`);
await caches.terms.invalidate();
return res.json('ok');
}));

View File

@ -1,18 +1,36 @@
import fs from 'fs';
export default async (dir, filename, maxAgeMinutes, generator) => {
const cacheDir = `${__dirname}/../cache/${dir}`
fs.mkdirSync(cacheDir, { recursive: true });
const cacheFilename = `${cacheDir}/${filename}`;
if (fs.existsSync(cacheFilename) && fs.statSync(cacheFilename).mtimeMs >= (new Date() - maxAgeMinutes*60*1000)) {
const content = fs.readFileSync(cacheFilename);
return filename.endsWith('.js') ? JSON.parse(content) : content;
export class CacheObject {
constructor(dir, filename, maxAgeMinutes) {
const cacheDir = `${__dirname}/../cache/${dir}`
fs.mkdirSync(cacheDir, { recursive: true });
this.path = `${cacheDir}/${filename}`;
this.maxAgeMinutes = maxAgeMinutes;
}
const result = await generator();
async fetch(generator) {
if (fs.existsSync(this.path) && fs.statSync(this.path).mtimeMs >= (new Date() - this.maxAgeMinutes*60*1000)) {
const content = fs.readFileSync(this.path);
return this.path.endsWith('.js') ? JSON.parse(content) : content;
}
fs.writeFileSync(cacheFilename, filename.endsWith('.js') ? JSON.stringify(result) : result);
const result = await generator();
return result;
fs.writeFileSync(this.path, this.path.endsWith('.js') ? JSON.stringify(result) : result);
return result;
}
async invalidate() {
fs.unlinkSync(this.path);
}
}
export const caches = {
admins: new CacheObject('main', 'admins.js', 10),
adminsFooter: new CacheObject('main', 'footer.js', 10),
blog: new CacheObject('main', 'blog.js', Infinity),
nouns: new CacheObject('main', 'nouns.js', 10),
terms: new CacheObject('main', 'terms.js', 10),
inclusive: new CacheObject('main', 'inclusive.js', 10),
}