Improve photo gallery with artist names and infinite scrolling
Refactor photo gallery to include artist names extracted from filenames, implement pagination with infinite scrolling, and optimize image loading. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 79e0a082-1752-4a36-8483-997b7269c4f3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/nFw7xof Replit-Helium-Checkpoint-Created: true
This commit is contained in:
+75
-76
@@ -12,6 +12,20 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import https from "https";
|
||||
|
||||
const apiCache = new Map<string, { data: any; timestamp: number }>();
|
||||
function getCached<T>(key: string, ttlMs: number): T | null {
|
||||
const entry = apiCache.get(key);
|
||||
if (entry && Date.now() - entry.timestamp < ttlMs) return entry.data as T;
|
||||
return null;
|
||||
}
|
||||
function setCache(key: string, data: any) {
|
||||
apiCache.set(key, { data, timestamp: Date.now() });
|
||||
}
|
||||
function getStale<T>(key: string): T | null {
|
||||
const entry = apiCache.get(key);
|
||||
return entry ? (entry.data as T) : null;
|
||||
}
|
||||
|
||||
const uploadDir = path.join(process.cwd(), "client/public/uploads");
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
@@ -159,6 +173,10 @@ export async function registerRoutes(
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const perPage = parseInt(req.query.perPage as string) || 20;
|
||||
const search = req.query.search as string || "";
|
||||
const cacheKey = `videos_${page}_${perPage}_${search}`;
|
||||
const cached = getCached<any>(cacheKey, 30 * 60 * 1000);
|
||||
if (cached) return res.json(cached);
|
||||
|
||||
let path = `/library/${LIBRARY_ID}/videos?page=${page}&itemsPerPage=${perPage}&orderBy=date`;
|
||||
if (search) path += `&search=${encodeURIComponent(search)}`;
|
||||
const data = await bunnyFetch(path);
|
||||
@@ -177,8 +195,12 @@ export async function registerRoutes(
|
||||
category: v.category || "",
|
||||
};
|
||||
});
|
||||
res.json({ items: videos, totalItems: data.totalItems, currentPage: data.currentPage });
|
||||
const result = { items: videos, totalItems: data.totalItems, currentPage: data.currentPage };
|
||||
setCache(cacheKey, result);
|
||||
res.json(result);
|
||||
} catch (err: any) {
|
||||
const stale = getStale<any>(`videos_${req.query.page || 1}_${req.query.perPage || 20}_${req.query.search || ""}`);
|
||||
if (stale) return res.json(stale);
|
||||
res.status(500).json({ message: err.message });
|
||||
}
|
||||
});
|
||||
@@ -342,98 +364,75 @@ export async function registerRoutes(
|
||||
}
|
||||
});
|
||||
|
||||
// News feed - Volksmusik/Schlager news from Google News RSS
|
||||
function parseRssItems(xml: string): { title: string; link: string; source: string; pubDate: string }[] {
|
||||
const items: { title: string; link: string; source: string; pubDate: string }[] = [];
|
||||
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
|
||||
let match;
|
||||
while ((match = itemRegex.exec(xml)) !== null && items.length < 10) {
|
||||
const block = match[1];
|
||||
const title = block.match(/<title>(.*?)<\/title>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "") || "";
|
||||
const link = block.match(/<link>(.*?)<\/link>/)?.[1] || "";
|
||||
const source = block.match(/<source[^>]*>(.*?)<\/source>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "") || "";
|
||||
const pubDateRaw = block.match(/<pubDate>(.*?)<\/pubDate>/)?.[1] || "";
|
||||
let pubDate = "";
|
||||
try {
|
||||
const d = new Date(pubDateRaw);
|
||||
const diffH = Math.floor((Date.now() - d.getTime()) / 3600000);
|
||||
if (diffH < 1) pubDate = "Gerade eben";
|
||||
else if (diffH < 24) pubDate = `vor ${diffH} Std.`;
|
||||
else pubDate = `vor ${Math.floor(diffH / 24)} T.`;
|
||||
} catch { pubDate = ""; }
|
||||
if (title && link) items.push({ title, link, source, pubDate });
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async function fetchRssFeed(rssUrl: string): Promise<string> {
|
||||
const resp = await fetch(rssUrl, {
|
||||
headers: { "User-Agent": "Mozilla/5.0 (compatible; FolxTV/1.0)" },
|
||||
redirect: "follow",
|
||||
});
|
||||
if (!resp.ok) throw new Error(`RSS fetch failed: ${resp.status}`);
|
||||
return await resp.text();
|
||||
}
|
||||
|
||||
app.get("/api/news-feed", async (_req, res) => {
|
||||
const cacheKey = "news-feed";
|
||||
const cached = getCached<any[]>(cacheKey, 15 * 60 * 1000);
|
||||
if (cached) return res.json(cached);
|
||||
|
||||
try {
|
||||
const topics = ["Volksmusik", "Schlager+Musik", "Oberkrainer"];
|
||||
const topic = topics[Math.floor(Date.now() / 3600000) % topics.length];
|
||||
const rssUrl = `https://news.google.com/rss/search?q=${topic}&hl=de&gl=DE&ceid=DE:de`;
|
||||
|
||||
const response = await new Promise<string>((resolve, reject) => {
|
||||
https.get(rssUrl, (resp) => {
|
||||
let data = "";
|
||||
resp.on("data", (chunk: Buffer) => (data += chunk.toString()));
|
||||
resp.on("end", () => resolve(data));
|
||||
resp.on("error", reject);
|
||||
}).on("error", reject);
|
||||
});
|
||||
|
||||
const items: { title: string; link: string; source: string; pubDate: string }[] = [];
|
||||
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
|
||||
let match;
|
||||
while ((match = itemRegex.exec(response)) !== null && items.length < 10) {
|
||||
const block = match[1];
|
||||
const title = block.match(/<title>(.*?)<\/title>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "") || "";
|
||||
const link = block.match(/<link>(.*?)<\/link>/)?.[1] || "";
|
||||
const source = block.match(/<source[^>]*>(.*?)<\/source>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "") || "";
|
||||
const pubDateRaw = block.match(/<pubDate>(.*?)<\/pubDate>/)?.[1] || "";
|
||||
|
||||
let pubDate = "";
|
||||
try {
|
||||
const d = new Date(pubDateRaw);
|
||||
const diffH = Math.floor((Date.now() - d.getTime()) / 3600000);
|
||||
if (diffH < 1) pubDate = "Gerade eben";
|
||||
else if (diffH < 24) pubDate = `vor ${diffH} Std.`;
|
||||
else pubDate = `vor ${Math.floor(diffH / 24)} T.`;
|
||||
} catch {
|
||||
pubDate = "";
|
||||
}
|
||||
|
||||
if (title && link) {
|
||||
items.push({ title, link, source, pubDate });
|
||||
}
|
||||
}
|
||||
|
||||
const xml = await fetchRssFeed(rssUrl);
|
||||
const items = parseRssItems(xml);
|
||||
if (items.length > 0) setCache(cacheKey, items);
|
||||
res.json(items);
|
||||
} catch (err: any) {
|
||||
res.json([]);
|
||||
console.log("[news-feed] RSS fetch error:", err.message);
|
||||
const stale = getStale<any[]>(cacheKey);
|
||||
res.json(stale || []);
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/breaking-news", async (_req, res) => {
|
||||
const cacheKey = "breaking-news";
|
||||
const cached = getCached<any[]>(cacheKey, 15 * 60 * 1000);
|
||||
if (cached) return res.json(cached);
|
||||
|
||||
try {
|
||||
const topics = ["Nachrichten+Deutschland", "Nachrichten+Oesterreich", "Nachrichten+Europa", "Wirtschaft+aktuell", "Sport+aktuell"];
|
||||
const topic = topics[Math.floor(Date.now() / 3600000) % topics.length];
|
||||
const rssUrl = `https://news.google.com/rss/search?q=${topic}&hl=de&gl=DE&ceid=DE:de`;
|
||||
|
||||
const response = await new Promise<string>((resolve, reject) => {
|
||||
https.get(rssUrl, (resp) => {
|
||||
let data = "";
|
||||
resp.on("data", (chunk: Buffer) => (data += chunk.toString()));
|
||||
resp.on("end", () => resolve(data));
|
||||
resp.on("error", reject);
|
||||
}).on("error", reject);
|
||||
});
|
||||
|
||||
const items: { title: string; link: string; source: string; pubDate: string }[] = [];
|
||||
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
|
||||
let match;
|
||||
while ((match = itemRegex.exec(response)) !== null && items.length < 10) {
|
||||
const block = match[1];
|
||||
const title = block.match(/<title>(.*?)<\/title>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "") || "";
|
||||
const link = block.match(/<link>(.*?)<\/link>/)?.[1] || "";
|
||||
const source = block.match(/<source[^>]*>(.*?)<\/source>/)?.[1]?.replace(/<!\[CDATA\[|\]\]>/g, "") || "";
|
||||
const pubDateRaw = block.match(/<pubDate>(.*?)<\/pubDate>/)?.[1] || "";
|
||||
|
||||
let pubDate = "";
|
||||
try {
|
||||
const d = new Date(pubDateRaw);
|
||||
const diffH = Math.floor((Date.now() - d.getTime()) / 3600000);
|
||||
if (diffH < 1) pubDate = "Gerade eben";
|
||||
else if (diffH < 24) pubDate = `vor ${diffH} Std.`;
|
||||
else pubDate = `vor ${Math.floor(diffH / 24)} T.`;
|
||||
} catch {
|
||||
pubDate = "";
|
||||
}
|
||||
|
||||
if (title && link) {
|
||||
items.push({ title, link, source, pubDate });
|
||||
}
|
||||
}
|
||||
|
||||
const xml = await fetchRssFeed(rssUrl);
|
||||
const items = parseRssItems(xml);
|
||||
if (items.length > 0) setCache(cacheKey, items);
|
||||
res.json(items);
|
||||
} catch (err: any) {
|
||||
res.json([]);
|
||||
console.log("[breaking-news] RSS fetch error:", err.message);
|
||||
const stale = getStale<any[]>(cacheKey);
|
||||
res.json(stale || []);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user