import fs from 'fs'; import path from 'path'; import { cache } from '@/utils/cache'; export interface BlogPost { slug: string; title: string; date: string; excerpt: string; coverImage: string; category?: string; tags?: string[]; content?: string; } const BLOG_POSTS_DIR = path.join(process.cwd(), 'blog-posts'); // Category mapping based on filename patterns const categoryMap: Record = { 'agentic': 'KI-Agenten', 'ai-agent': 'KI-Agenten', 'deepseek': 'KI-Entwicklung', 'openai': 'KI-Entwicklung', 'gpt': 'KI-Entwicklung', 'claude': 'KI-Entwicklung', 'langchain': 'KI-Entwicklung', 'langgraph': 'KI-Entwicklung', 'rag': 'KI-Entwicklung', 'llm': 'KI-Entwicklung', 'voice': 'Voice AI', 'vapi': 'Voice AI', 'speech': 'Voice AI', 'elevenlabs': 'Voice AI', 'whisper': 'Voice AI', 'n8n': 'Automatisierung', 'make': 'Automatisierung', 'zapier': 'Automatisierung', 'automation': 'Automatisierung', 'workflow': 'Automatisierung', 'scraping': 'Web Scraping', 'playwright': 'Web Scraping', 'puppeteer': 'Web Scraping', 'nextjs': 'Web Development', 'react': 'Web Development', 'typescript': 'Web Development', 'api': 'Backend', 'database': 'Backend', 'postgres': 'Backend', 'prisma': 'Backend', 'supabase': 'Backend', 'saas': 'SaaS', 'stripe': 'SaaS', 'subscription': 'SaaS', 'seo': 'SEO', 'performance': 'Performance', 'iot': 'IoT', 'raspberry': 'IoT', 'mqtt': 'IoT', 'home-assistant': 'Smart Home', }; function detectCategory(filename: string, content: string): string { const lowerFilename = filename.toLowerCase(); const lowerContent = content.toLowerCase().slice(0, 500); for (const [keyword, category] of Object.entries(categoryMap)) { if (lowerFilename.includes(keyword) || lowerContent.includes(keyword)) { return category; } } return 'Technologie'; } function parseMarkdownPost(filename: string, content: string): BlogPost | null { try { // Extract title from first H1 const titleMatch = content.match(/^#\s+(.+)$/m); const title = titleMatch ? titleMatch[1].trim() : filename.replace(/\.md$/, ''); // Extract meta description const metaMatch = content.match(/\*\*Meta-Description:\*\*\s*(.+)/i); const metaDescription = metaMatch ? metaMatch[1].trim() : ''; // Extract keywords const keywordsMatch = content.match(/\*\*Keywords?:\*\*\s*(.+)/i); const keywordsStr = keywordsMatch ? keywordsMatch[1].trim() : ''; const tags = keywordsStr ? keywordsStr.split(',').map(k => k.trim()).slice(0, 6) : []; // Extract excerpt from introduction or meta description let excerpt = metaDescription; if (!excerpt) { const introMatch = content.match(/## Einführung\s*\n\n([\s\S]+?)(?:\n\n|$)/); if (introMatch) { excerpt = introMatch[1].trim().slice(0, 200) + '...'; } } // Generate slug from filename (remove number prefix) const slug = filename .replace(/\.md$/, '') .replace(/^\d+-/, ''); // Extract date from filename number (approximate) const numMatch = filename.match(/^(\d+)-/); const postNum = numMatch ? parseInt(numMatch[1], 10) : 1; // Generate dates starting from 2026-01-19 going backwards const baseDate = new Date('2026-01-19'); baseDate.setDate(baseDate.getDate() - (postNum - 1)); const date = baseDate.toISOString().split('T')[0]; // Detect category const category = detectCategory(filename, content); // Cover image path const coverImage = `/images/posts/${slug}/cover.jpg`; return { slug, title, date, excerpt: excerpt || title, coverImage, category, tags, content, }; } catch (error) { console.error(`Error parsing ${filename}:`, error); return null; } } export function getAllBlogPosts(): BlogPost[] { const cacheKey = 'blog:all-posts'; // Check cache first const cachedPosts = cache.get(cacheKey); if (cachedPosts) { return cachedPosts; } if (!fs.existsSync(BLOG_POSTS_DIR)) { console.warn('Blog posts directory not found:', BLOG_POSTS_DIR); return []; } const files = fs.readdirSync(BLOG_POSTS_DIR) .filter(file => file.endsWith('.md')) .sort(); const posts: BlogPost[] = []; for (const file of files) { try { const filePath = path.join(BLOG_POSTS_DIR, file); const content = fs.readFileSync(filePath, 'utf-8'); const post = parseMarkdownPost(file, content); if (post) { posts.push(post); } } catch (error) { // Skip files that fail to parse console.warn(`Failed to parse blog post: ${file}`, error); continue; } } // Sort by date descending (newest first) const sortedPosts = posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); // Cache the results cache.set(cacheKey, sortedPosts); return sortedPosts; } export function getBlogPostBySlug(slug: string): BlogPost | null { const posts = getAllBlogPosts(); return posts.find(post => post.slug === slug) || null; } export function getBlogPostSlugs(): string[] { return getAllBlogPosts().map(post => post.slug); }