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:
+2
-1
@@ -102,7 +102,8 @@
|
||||
"ctaReign": "Ready to R3IGN again?",
|
||||
"ctaMeow": "Meowww",
|
||||
"ctaPurr": "Purrr",
|
||||
"ctaRawr": "Rawr"
|
||||
"ctaRawr": "Rawr",
|
||||
"supportedBy": "Supported by"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Documentation",
|
||||
|
||||
+46
-1
@@ -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}/`,
|
||||
|
||||
@@ -366,6 +366,23 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Supporters -->
|
||||
<% if (typeof supporters !== 'undefined' && supporters.length > 0) { %>
|
||||
<section class="py-10 border-t border-[rgba(144,238,144,0.06)]">
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8">
|
||||
<p class="text-center text-xs uppercase tracking-widest text-muted opacity-50 mb-5"><%= t('home.supportedBy') %></p>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
<% supporters.forEach(function(sq) { %>
|
||||
<a href="/squadrons/<%= encodeURIComponent(sq.short) %>"
|
||||
class="px-3 py-1 rounded-full text-[11px] font-medium border border-[rgba(144,238,144,0.15)] text-muted hover:text-accent hover:border-[rgba(144,238,144,0.35)] transition-colors">
|
||||
<span class="opacity-60"><%= sq.short %></span> // <%= sq.long %>
|
||||
</a>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<% } %>
|
||||
|
||||
<!-- Footer -->
|
||||
<%- include('partials/footer') %>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user