From 95f38a9b0d38b65540724975bc4940a137e193e5 Mon Sep 17 00:00:00 2001 From: deploy Date: Wed, 27 May 2026 12:38:58 +0000 Subject: [PATCH] add supporters section to homepage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shows paying squadrons as pill links (SHORT // LONG → /squadrons/) above the footer, with a 15-min server-side cache backed by entitlements DB + SQUADRONS.json. Co-Authored-By: Claude Sonnet 4.6 --- web/locales/en.json | 3 ++- web/server.js | 47 ++++++++++++++++++++++++++++++++++++++++++++- web/views/index.ejs | 17 ++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/web/locales/en.json b/web/locales/en.json index 053b8b4..a49dcf9 100644 --- a/web/locales/en.json +++ b/web/locales/en.json @@ -102,7 +102,8 @@ "ctaReign": "Ready to R3IGN again?", "ctaMeow": "Meowww", "ctaPurr": "Purrr", - "ctaRawr": "Rawr" + "ctaRawr": "Rawr", + "supportedBy": "Supported by" }, "docs": { "title": "Documentation", diff --git a/web/server.js b/web/server.js index 041d174..8a8d25f 100644 --- a/web/server.js +++ b/web/server.js @@ -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}/`, diff --git a/web/views/index.ejs b/web/views/index.ejs index 83d4eb8..a8a9e1e 100644 --- a/web/views/index.ejs +++ b/web/views/index.ejs @@ -366,6 +366,23 @@ + + <% if (typeof supporters !== 'undefined' && supporters.length > 0) { %> +
+
+

<%= t('home.supportedBy') %>

+
+ <% supporters.forEach(function(sq) { %> + + <%= sq.short %> // <%= sq.long %> + + <% }); %> +
+
+
+ <% } %> + <%- include('partials/footer') %>