Add companion app
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import express from 'express';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
|
||||
const RSS_URL = 'https://anchor.fm/s/11068d290/podcast/rss';
|
||||
const PORT = Number(process.env.PORT || 5181);
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
trimValues: true
|
||||
});
|
||||
|
||||
function toArray(value) {
|
||||
if (!value) return [];
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
function cleanText(input) {
|
||||
if (!input) return '';
|
||||
return String(input)
|
||||
.replace(/<[^>]*>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeItem(item, index) {
|
||||
const title = cleanText(item.title) || `Episode ${index + 1}`;
|
||||
const summary = cleanText(item['content:encoded'] || item.description) || 'New episode now available.';
|
||||
const link = item.link || item.guid || RSS_URL;
|
||||
|
||||
const date = item.pubDate ? new Date(item.pubDate) : null;
|
||||
const publishedAt = date && !Number.isNaN(date.getTime()) ? date.toISOString() : null;
|
||||
|
||||
return {
|
||||
id: cleanText(item.guid) || link,
|
||||
title,
|
||||
summary,
|
||||
scripture: '',
|
||||
reflectionPrompt: '',
|
||||
prayerFocus: '',
|
||||
challenge: '',
|
||||
link,
|
||||
publishedAt
|
||||
};
|
||||
}
|
||||
|
||||
async function readFeedEpisodes() {
|
||||
const response = await fetch(RSS_URL, {
|
||||
headers: {
|
||||
'User-Agent': 'Siteforge-Companion/1.0'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`RSS request failed with ${response.status}`);
|
||||
}
|
||||
|
||||
const xml = await response.text();
|
||||
const parsed = parser.parse(xml);
|
||||
const items = toArray(parsed?.rss?.channel?.item);
|
||||
return items.map(normalizeItem).slice(0, 24);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get('/api/episodes', async (_req, res) => {
|
||||
try {
|
||||
const episodes = await readFeedEpisodes();
|
||||
res.json({
|
||||
source: 'rss',
|
||||
rssUrl: RSS_URL,
|
||||
updatedAt: new Date().toISOString(),
|
||||
episodes
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(502).json({
|
||||
source: 'rss',
|
||||
rssUrl: RSS_URL,
|
||||
error: error instanceof Error ? error.message : 'Unable to load RSS feed',
|
||||
episodes: []
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (isProd) {
|
||||
const distDir = path.resolve(__dirname, 'dist');
|
||||
app.use(express.static(distDir));
|
||||
|
||||
app.get('*', (_req, res) => {
|
||||
res.sendFile(path.join(distDir, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
const mode = isProd ? 'production' : 'development';
|
||||
console.log(`Companion API listening on http://localhost:${PORT} (${mode})`);
|
||||
});
|
||||
Reference in New Issue
Block a user