add supporters section to homepage

Shows paying squadrons as pill links (SHORT // LONG → /squadrons/<short>) above the footer, with a 15-min server-side cache backed by entitlements DB + SQUADRONS.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
deploy
2026-05-27 12:38:58 +00:00
parent f042fd4d8a
commit 95f38a9b0d
3 changed files with 65 additions and 2 deletions
+46 -1
View File
@@ -98,6 +98,7 @@ fs.mkdirSync(REPLAYS_ROOT, { recursive: true });
const LEGACY_REPLAYS_ROOT = path.join(__dirname, '..', 'replays');
const ENTITLEMENTS_DB_PATH = path.join(STORAGE_ROOT, 'entitlements.db');
const SQUADRONS_DB_PATH = path.join(STORAGE_ROOT, 'squadrons.db');
const SQUADRONS_JSON_PATH = path.join(STORAGE_ROOT, 'SQUADRONS.json');
const entitlementsDb = new sqlite3.Database(ENTITLEMENTS_DB_PATH);
const squadronsDb = fs.existsSync(SQUADRONS_DB_PATH)
? new sqlite3.Database(SQUADRONS_DB_PATH, sqlite3.OPEN_READONLY)
@@ -824,12 +825,56 @@ app.use((req, res, next) => {
next();
});
// ── Supporters (paying squadrons) ────────────────────────────────────────────
let _supportersCache = null;
let _supportersCacheAt = 0;
const SUPPORTERS_TTL = 15 * 60 * 1000;
function getSupporters() {
if (_supportersCache && Date.now() - _supportersCacheAt < SUPPORTERS_TTL) {
return Promise.resolve(_supportersCache);
}
return new Promise((resolve) => {
let squads;
try {
squads = JSON.parse(fs.readFileSync(SQUADRONS_JSON_PATH, 'utf8'));
} catch {
return resolve([]);
}
entitlementsDb.all(
`SELECT guild_id FROM guild_entitlements WHERE status='active'
UNION SELECT guild_id FROM discord_entitlements
UNION SELECT guild_id FROM manual_entitlements WHERE expires_at > strftime('%s','now')`,
[],
(err, rows) => {
if (err) return resolve([]);
const paying = new Set(rows.map(r => r.guild_id));
const seen = new Map();
for (const [gid, sq] of Object.entries(squads)) {
if (paying.has(gid) && !seen.has(sq.SQ_ShortHand_Name)) {
seen.set(sq.SQ_ShortHand_Name, sq.SQ_LongHandName);
}
}
const result = [...seen.entries()]
.map(([short, long]) => ({ short, long }))
.sort((a, b) => a.long.localeCompare(b.long));
_supportersCache = result;
_supportersCacheAt = Date.now();
resolve(result);
}
);
});
}
// Routes
app.get('/', (req, res) => {
app.get('/', async (req, res) => {
const siteUrl = (process.env.PRODUCTION_DOMAIN || 'https://sre.pawjob.us').replace(/\/$/, '');
const supporters = await getSupporters().catch(() => []);
res.render('index', {
botName: 'Toothless SQB Bot',
supporters,
metaTitle: "Toothless' SRE Bot",
metaDescription: 'The Best Squadron Battles Bot.',
metaUrl: `${siteUrl}/`,