Restored to '3905f52571b4fa7e048e16d7a3b94ee66307d072'
Replit-Restored-To: 3905f52571
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"access_token": "sl.u.AGWH7nNDNxu6n0TGi2319rCdcQYXkcyuFER6OlIPEJgMxmrw66-u-NwXroP7EBFd1Vgm3tAFViiSt0rx2Y_iGgqBv9bt40L-w13r2m00xK0FKE5HKjhCSEm1VDnHxPjjJQ4pnsgOnLsLPR6NMvuHWwE_zFQF5xOOFaMo9SrFcTX7tVmI0-lXer9nHo6ar8XJi7lDzcdA-lX_GMgK1sk6xt_fZJ5wJ0bwiV9NA-0f5hsWa2IOP00dkccZAJI6u3B-4e8Z_Rn0eY1SdPQlqyK6mFM1IGtavSQQeREuvKhtHy0xCaC4MJwPUV9ctfJ6Z181FaOrjoUfd37XLcEZL9DkAeamw_HAU1AFALH9DWJpWgcIKFPjYfCc9Pp4KkHaqG1pCQIRe4XUuW-A56TM9lNoWbmepbJza1n0jl-NvdqbPkXyxXL9t9oSIF2r_SfSokgGPWnycaKmAHl2IK5duiglVyTPjnWFDZrdBKgqMGGCKd_bZ2JSvGMGzXoWGI09oSNkYBToCf4SIbXc4kkiW6FrbRVPB44NDQElWD34oA8dkdFM8wuA3Tes8wMnBrixoW__YQN-BQhiR3FyeNGuOFWGc5k2ZeRljztj1RgNQKKEqe73G-o7fdjw6BwtMectTu-iKarjtDBq2AlKGpinBT5cRb6ndIqnRy7xNFL-BkQ1n-9W3b4uB4h3fYEsVk0tFFPuWQUboA71f60EATJMmGto1j8uTAiqmJK0rjoKm2vbeKfIebLPMqsTuvXltEnTeVz_wOvIm1k1PpTfnautsdJrDx6IrUavGUQ3h1w_dF3pF9K5svXhQZ_XfjTTW-aSQ2lqfDgfxZV6Osqd-8rq00LT0jQIwJhKIwloIXNpfK-IJbh7DuUJHQmGAFeLb7nurSO-Rd9qJDvp9gWxQWEBPPncFrGt23ERBuWaSrB6kO181SszyrxX-4-4s4D6Q98ZGd_5mGZu_rVCbIek_3LTIqoTP_cYtGLx4p4UhybJ-X_OIyfkiAafmuKP8A-Xy5W26tXyYVt6XcdmQb3MPs6GwkGjGutQNcJ7B0uBELrs2PAqWoWb_uL7OwKtUSj2pNeqagnZjhUqvkGTZPRnAE91z9VTvtBzLllj1TrT22-k-KXhzmED8tVOa4mjpzsMhKVp74Fh1fntQmFvBFttPpDmORKigcyqA__587g8lHtsNf1R9vXnVogeN6DSwi5n1taZRpbHoAQvJ-jIxdIfirvmN2kzKgmBYBWGm32ViO0vBoWqeR150ka1Zzl0i6aDBNXuD271BIDIAmdPvKx_7pXzGvR-6i3yIEP6uaTVIvzzIAZz3hDmag",
|
||||
"access_token": "sl.u.AGWJ8sxDwmUlPT2LcHxWop31dWjgQBdBEFRmm7UFkk6NrkdA_Wcjn5cmlJ1JSNcwQEOv6seTzt5_1NbyWc-12KujpapH4aMNY75sYMTZcklc63GAgoUFouTQFvTW1Dd4UR2uEiP8z09MU3DX-trdEy9CfAXt4gilBx3apibz-vFpxHrsZlQDeEnx7KY_XmRFRlEKg3AHBsezx7O2DAEhDjMCF7M9CrxaCPEMNszI_oi7eXOcjyDn1LpEI7xb8JpeLUJSJ2DtLTut3hBgIpsAMfR0NDfesXLAeyA0NdpL75k_5pKpDCVsqUZJAITyBWqxSthPaalhboLFQAEPMOYNKLPeZpgFvPDeQdEIqLGbH0ob6nJzJbJz2fyYGEUXPgNBzq_3fJpxIThXNOluSmhBIVjuJwzK1M4nbzs_9EIUwq8YRndfm76yBUHz-YG1mQIJwYX-1gnMZEM4v72QZfI2pCo4igGfFWhSvdbW17ni8SRt46_LpxzM9uXoLTOEeMf2VXDqZxRMar1CxdORcz2gHfZHkBLfTkRHB9QkrtcE9ckU3B3srHtBKwhYxL-FT5gKix5u9o4dTvQGQzHnUmBX_5AvoWayATE3BPVXoAIBYFVX3oMwkRElyKyZmwSzmoi1LvoDnzkF7ZuWtTF6GaCPvv6S1aUsTLh6yd0Izu3T_-N7TeblmcHnE3OgIO8IWOFr3jQutrhIiY4D9X637Pr5-GubYtX-sptVPzEhvRM_epIrsBYZWFSvA7HN4JUcZaB7Gn0_ibcuNhDBBwYiai35AslEOSfLrwrDwoygAC2asMILGn8vXEeZNybZKmmyQY2jcBynuHBXh-Jnx18of0Xk2sQ-Q_sCDiA3m3CQCmn2_zF_hdi1AWiC0oPANKlqPhXMuaToEqBSIqtny1PdFyZgK1Q-p2_Gz-Tz5BD9CHaEu6iHRpt5uFwbQFCHa-iumAyJq5NFIPlQ0Gb56P7JLt6Ks0o6VCIFGP2itDRR1kWcaRIFwdeK9v67QTuYjomFszM0P73ZhdZp_AeaWlCF0lovY32cLIxjzpKQ3Z6VIa10s2abc2Gj6fdgnh6OQDHcFaBqHO1jAA-lTmEQ4CSVma7JLfl00LcqS5bwfV2y7eM6Gfck0uF6JUXmfjHyV5qDpLcok-n-sOHLbkmF551_NwpRTTvXUBxaEYzDJ3U8QF-yFQOD5KPREyZfEitATZikay78XT67tyrCPMQ4TmkmZSf5evKlb542x4H1KHT2QW0b_gU7c6jBzZyeR9ki0Fnop3-VwqP0GKDG1_FzexDN1T8dNjCosJXjBcvt1X6u5pcf_Uoe0w",
|
||||
"refresh_token": "8-fiK0Io8Z8AAAAAAAAAAXn1QIGTwFTVWKF47COXY2bjqYlWyd4aRnFtQJ7usZ0y",
|
||||
"expires_at": 1772727518350,
|
||||
"expires_at": 1772717208506,
|
||||
"app_key": "sjwprgka82p8tpv",
|
||||
"app_secret": "g3vuczqo0rx3crz"
|
||||
}
|
||||
+528
-528
File diff suppressed because it is too large
Load Diff
@@ -86,6 +86,19 @@ export async function registerRoutes(
|
||||
});
|
||||
|
||||
app.get("/api/articles/category/:category", async (req, res) => {
|
||||
const page = parseInt(req.query.page as string);
|
||||
const limit = parseInt(req.query.limit as string) || 12;
|
||||
if (page && page > 0) {
|
||||
const result = await storage.getArticlesByCategoryPaginated(req.params.category, page, limit);
|
||||
const totalPages = Math.ceil(result.total / limit);
|
||||
return res.json({
|
||||
articles: result.articles,
|
||||
total: result.total,
|
||||
page,
|
||||
totalPages,
|
||||
hasMore: page < totalPages,
|
||||
});
|
||||
}
|
||||
const articles = await storage.getArticlesByCategory(req.params.category);
|
||||
res.json(articles);
|
||||
});
|
||||
|
||||
@@ -193,9 +193,18 @@ const seedArticles = [
|
||||
];
|
||||
|
||||
export async function seedDatabase() {
|
||||
const validSlugs = new Set(seedArticles.map((a) => a.slug));
|
||||
const existing = await storage.getArticles();
|
||||
const existingSlugs = new Set(existing.map((a) => a.slug));
|
||||
|
||||
const toDelete = existing.filter((a) => !validSlugs.has(a.slug));
|
||||
if (toDelete.length > 0) {
|
||||
for (const article of toDelete) {
|
||||
await db.execute(sql`DELETE FROM articles WHERE id = ${article.id}`);
|
||||
}
|
||||
console.log("Cleanup: removed " + toDelete.length + " articles not in seed list.");
|
||||
}
|
||||
|
||||
let added = 0;
|
||||
for (const article of seedArticles) {
|
||||
if (existingSlugs.has(article.slug)) continue;
|
||||
@@ -246,4 +255,12 @@ export async function seedDatabase() {
|
||||
content = ${"<p>Die beliebte S\u00e4ngerin <strong>Melanie Payer</strong> stellt ihren Titel \u201eEndlich wieder Gipfelstammtisch\u201c vor, der eigens als Titelsong f\u00fcr die neue Staffel der Sendung <strong>\u201eGipfelstammtisch\u201c</strong> auf Folx TV geschrieben wurde. Im Zuge der Ver\u00f6ffentlichung wurde auch ein offizielles Musikvideo produziert, das die musikalische Idee und die beteiligten K\u00fcnstler in den Mittelpunkt stellt.</p>\n<p>Die Produktion entstand im renommierten Tonstudio FD-Musics in Gmunden. Komponiert wurde der Titel von <strong>Flo Daxner</strong> und <strong>Hanneliese Krei\u00dfl Wurth</strong>, die auch den Text verfasste. Produktion und Arrangement \u00fcbernahm Flo Daxner. Ver\u00f6ffentlicht wurde der Song unter dem Label mymusic.media und ist auf allen g\u00e4ngigen Plattformen als Download und Stream verf\u00fcgbar.</p>\n<p><strong>Song & Musikvideo:</strong><br><a href=\"https://music.imusician.pro/a/K2P_nWeA\" target=\"_blank\" rel=\"noopener noreferrer\">https://music.imusician.pro/a/K2P_nWeA</a></p>\n<div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;max-width:100%;margin:1.5rem 0;\"><iframe style=\"position:absolute;top:0;left:0;width:100%;height:100%;border:0;border-radius:8px;\" src=\"https://www.youtube.com/embed/38HygQCVFoo\" title=\"Melanie Payer - Endlich wieder Gipfelstammtisch\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>\n<p>Der Titel wurde speziell f\u00fcr die Sendung \u201eGipfelstammtisch\u201c geschrieben, um deren Charakter musikalisch widerzuspiegeln \u2013 Geselligkeit, Tradition und die authentische Volksmusik aus der Region Wilder Kaiser. Im Fokus stehen die Musik und die K\u00fcnstler, die die neue Staffel begleiten.</p>\n<p>Im Musikvideo wirken mit: <strong>Natascha</strong>, <strong>Meli Stein</strong>, <strong>Linda Feller</strong>, <strong>Mark Ed</strong>, <strong>Da Wadltreiber von Amadeus</strong>, <strong>Tauern Echo</strong>, <strong>Melanie Payer</strong>, <strong>Hansi Berger</strong>, <strong>Sanny \u2013 Die Stimme der Berge</strong>, <strong>Julia Raich</strong>, <strong>Spitzbua Markus</strong>, <strong>Meissnitzer Band</strong>, <strong>Brennholz</strong>, <strong>Charly Kaiser</strong>, <strong>Franz Nolf</strong>, <strong>Die Grubertaler</strong>, <strong>Franz Steiner</strong>, <strong>Marlena Martinelli</strong>, <strong>Die 3 Z'widern</strong>, <strong>SUSAL</strong> und <strong>Pfundskerle</strong>. Wijbrand van der Sande ist im Hintergrund zu sehen, w\u00e4hrend der Fokus klar auf dem Titel und den beteiligten K\u00fcnstlern liegt.</p>\n<p>\u201eEndlich wieder Gipfelstammtisch\u201c verbindet die neue Staffel der Sendung mit der Klangwelt der alpenl\u00e4ndischen Volksmusik und unterstreicht die musikalische Identit\u00e4t des Formats.</p>"}
|
||||
WHERE slug = 'melanie-payer-endlich-wieder-gipfelstammtisch'
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
UPDATE articles SET
|
||||
title = REPLACE(REPLACE(title, '„', '\u201e'), '“', '\u201c'),
|
||||
excerpt = REPLACE(REPLACE(excerpt, '„', '\u201e'), '“', '\u201c')
|
||||
WHERE title LIKE '%„%' OR title LIKE '%“%'
|
||||
OR excerpt LIKE '%„%' OR excerpt LIKE '%“%'
|
||||
`);
|
||||
}
|
||||
|
||||
+14
-4
@@ -7,6 +7,15 @@ function escapeHtml(str: string): string {
|
||||
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function ogImageUrl(coverImage: string, baseUrl: string): string {
|
||||
if (!coverImage) return "";
|
||||
let imgPath = coverImage;
|
||||
if (imgPath.endsWith(".webp")) {
|
||||
imgPath = imgPath.replace(/\.webp$/, ".jpg");
|
||||
}
|
||||
return imgPath.startsWith("http") ? imgPath : `${baseUrl}${imgPath}`;
|
||||
}
|
||||
|
||||
export function serveStatic(app: Express) {
|
||||
const distPath = path.resolve(__dirname, "public");
|
||||
if (!fs.existsSync(distPath)) {
|
||||
@@ -31,7 +40,7 @@ export function serveStatic(app: Express) {
|
||||
const protocol = req.get("x-forwarded-proto") || "https";
|
||||
const baseUrl = `${protocol}://${host}`;
|
||||
const articleUrl = `${baseUrl}/article/${article.slug}`;
|
||||
const imageUrl = article.coverImage ? (article.coverImage.startsWith("http") ? article.coverImage : `${baseUrl}${article.coverImage}`) : "";
|
||||
const imageUrl = ogImageUrl(article.coverImage || "", baseUrl);
|
||||
|
||||
let template = await fs.promises.readFile(indexPath, "utf-8");
|
||||
|
||||
@@ -40,13 +49,14 @@ export function serveStatic(app: Express) {
|
||||
`<meta property="og:description" content="${escapeHtml(article.excerpt)}" />`,
|
||||
`<meta property="og:type" content="article" />`,
|
||||
`<meta property="og:url" content="${escapeHtml(articleUrl)}" />`,
|
||||
imageUrl ? `<meta property="og:image" content="${escapeHtml(imageUrl)}" />` : "",
|
||||
`<meta property="og:image" content="${escapeHtml(imageUrl || `${baseUrl}/og-image.jpg`)}" />`,
|
||||
`<meta property="og:site_name" content="Folx Music Television" />`,
|
||||
`<meta name="twitter:card" content="summary_large_image" />`,
|
||||
`<meta name="twitter:title" content="${escapeHtml(article.title)}" />`,
|
||||
`<meta name="twitter:description" content="${escapeHtml(article.excerpt)}" />`,
|
||||
imageUrl ? `<meta name="twitter:image" content="${escapeHtml(imageUrl)}" />` : "",
|
||||
`<title>${escapeHtml(article.title)} - Folx Music Television</title>`,
|
||||
`<meta name="twitter:image" content="${escapeHtml(imageUrl || `${baseUrl}/og-image.jpg`)}" />`,
|
||||
`<meta name="description" content="${escapeHtml(article.excerpt)}" />`,
|
||||
`<title>${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television</title>`,
|
||||
].filter(Boolean).join("\n ");
|
||||
|
||||
template = template.replace(/<meta property="og:[^"]*"[^>]*\/>\s*/g, "");
|
||||
|
||||
+10
-1
@@ -1,6 +1,6 @@
|
||||
import { type Article, type InsertArticle, articles } from "@shared/schema";
|
||||
import { db } from "./db";
|
||||
import { eq, desc, sql } from "drizzle-orm";
|
||||
import { eq, desc, sql, count } from "drizzle-orm";
|
||||
|
||||
export interface IStorage {
|
||||
getArticles(): Promise<Article[]>;
|
||||
@@ -9,6 +9,7 @@ export interface IStorage {
|
||||
getFeaturedArticles(): Promise<Article[]>;
|
||||
getPopularArticles(limit: number): Promise<Article[]>;
|
||||
getArticlesByCategory(category: string): Promise<Article[]>;
|
||||
getArticlesByCategoryPaginated(category: string, page: number, limit: number): Promise<{ articles: Article[]; total: number }>;
|
||||
createArticle(article: InsertArticle): Promise<Article>;
|
||||
updateArticle(id: number, article: Partial<InsertArticle>): Promise<Article | undefined>;
|
||||
incrementViews(id: number): Promise<void>;
|
||||
@@ -42,6 +43,14 @@ export class DatabaseStorage implements IStorage {
|
||||
return db.select().from(articles).where(eq(articles.category, category)).orderBy(desc(articles.publishedAt));
|
||||
}
|
||||
|
||||
async getArticlesByCategoryPaginated(category: string, page: number, limit: number): Promise<{ articles: Article[]; total: number }> {
|
||||
const offset = (page - 1) * limit;
|
||||
const [totalResult] = await db.select({ count: count() }).from(articles).where(eq(articles.category, category));
|
||||
const total = totalResult.count;
|
||||
const items = await db.select().from(articles).where(eq(articles.category, category)).orderBy(desc(articles.publishedAt)).limit(limit).offset(offset);
|
||||
return { articles: items, total };
|
||||
}
|
||||
|
||||
async createArticle(article: InsertArticle): Promise<Article> {
|
||||
const [created] = await db.insert(articles).values(article).returning();
|
||||
return created;
|
||||
|
||||
+10
-1
@@ -13,6 +13,15 @@ function escapeHtml(str: string): string {
|
||||
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function ogImageUrl(coverImage: string, baseUrl: string): string {
|
||||
if (!coverImage) return "";
|
||||
let imgPath = coverImage;
|
||||
if (imgPath.endsWith(".webp")) {
|
||||
imgPath = imgPath.replace(/\.webp$/, ".jpg");
|
||||
}
|
||||
return imgPath.startsWith("http") ? imgPath : `${baseUrl}${imgPath}`;
|
||||
}
|
||||
|
||||
export async function setupVite(server: Server, app: Express) {
|
||||
const serverOptions = {
|
||||
middlewareMode: true,
|
||||
@@ -63,7 +72,7 @@ export async function setupVite(server: Server, app: Express) {
|
||||
const protocol = req.get("x-forwarded-proto") || "https";
|
||||
const baseUrl = `${protocol}://${host}`;
|
||||
const articleUrl = `${baseUrl}/article/${article.slug}`;
|
||||
const imageUrl = article.coverImage ? (article.coverImage.startsWith("http") ? article.coverImage : `${baseUrl}${article.coverImage}`) : "";
|
||||
const imageUrl = ogImageUrl(article.coverImage || "", baseUrl);
|
||||
|
||||
const ogTags = [
|
||||
`<meta property="og:title" content="${escapeHtml(article.title)}" />`,
|
||||
|
||||
Reference in New Issue
Block a user