import fs from 'fs' import path from 'path' import matter from 'gray-matter' import { BlogPost, BlogMeta } from '@/types/blog' const postsDirectory = path.join(process.cwd(), 'content/posts') const scheduledDirectory = path.join(process.cwd(), 'content/scheduled') // Helper function to check if a post should be published function shouldPublishPost(post: BlogMeta): boolean { // Don't publish draft posts if (post.draft === true) return false // Require at least one valid date field const publishDate = post.publishDate || post.date if (!publishDate) return false const publishDateTime = new Date(publishDate) if (isNaN(publishDateTime.getTime())) return false const now = new Date() return publishDateTime <= now } // Helper function to recursively read markdown files from a directory function readMarkdownFiles(directory: string, basePath: string = ''): BlogPost[] { if (!fs.existsSync(directory)) { return [] } const items = fs.readdirSync(directory) const posts: BlogPost[] = [] items.forEach((item) => { const fullPath = path.join(directory, item) const stat = fs.statSync(fullPath) if (stat.isDirectory()) { // Recursively read subdirectories const subPosts = readMarkdownFiles(fullPath, path.join(basePath, item)) posts.push(...subPosts) } else if (item.endsWith('.md')) { try { // Read markdown file as string const fileContents = fs.readFileSync(fullPath, 'utf8') // Use gray-matter to parse the post metadata section const matterResult = matter(fileContents) const meta = matterResult.data as Partial // Create slug from filename (remove .md extension) const slug = item.replace(/\.md$/, '') // Calculate reading time (average 200 words per minute) const wordCount = matterResult.content.split(/\s+/).length const readingTime = Math.ceil(wordCount / 200) // Basic validation and normalization const rawDate = meta.date || meta.publishDate if (!rawDate) { console.warn(`Skipping '${fullPath}' - missing date/publishDate in frontmatter`) return } const dateObj = new Date(rawDate as string) if (isNaN(dateObj.getTime())) { console.warn(`Skipping '${fullPath}' - invalid date value: ${rawDate}`) return } if (!meta.title || !meta.description) { console.warn(`Skipping '${fullPath}' - missing required title/description in frontmatter`) return } const normalizedDate = meta.date ? (meta.date as string) : dateObj.toISOString().split('T')[0] const normalizedPublishDate = meta.publishDate ? new Date(meta.publishDate as string).toISOString() : undefined const post: BlogPost = { slug, title: meta.title, description: meta.description, content: matterResult.content, date: normalizedDate, publishDate: normalizedPublishDate, draft: meta.draft === true, author: meta.author || { name: 'CHORUS Team' }, tags: Array.isArray(meta.tags) ? (meta.tags as string[]) : [], featured: meta.featured === true, coverImage: meta.coverImage, readingTime, } posts.push(post) } catch (error) { console.error(`Error reading markdown file ${fullPath}:`, error) } } }) return posts } export function getSortedPostsData(): BlogPost[] { // Read posts from both published and scheduled directories const publishedPosts = readMarkdownFiles(postsDirectory) const scheduledPosts = readMarkdownFiles(scheduledDirectory) // Combine all posts const allPosts = [...publishedPosts, ...scheduledPosts] // Filter posts that should be published const publishablePosts = allPosts.filter(post => shouldPublishPost(post)) // Sort posts by date (newest first) return publishablePosts.sort((a, b) => { const dateA = new Date(a.date) const dateB = new Date(b.date) return dateB.getTime() - dateA.getTime() }) } export function getAllPostSlugs() { // Get all publishable posts and extract their slugs const posts = getSortedPostsData() return posts.map((post) => { return { params: { slug: post.slug, }, } }) } export function getPostData(slug: string): BlogPost | null { // Find the post in our sorted posts data const posts = getSortedPostsData() const post = posts.find(p => p.slug === slug) if (!post) { console.error(`Post with slug '${slug}' not found or not yet publishable`) return null } return post } export function getFeaturedPost(): BlogPost | null { const allPosts = getSortedPostsData() // Return the most recent post as the featured post return allPosts.length > 0 ? allPosts[0] : null } export function getPostsByTag(tag: string): BlogPost[] { const allPosts = getSortedPostsData() return allPosts.filter(post => post.tags.some(t => t.toLowerCase() === tag.toLowerCase()) ) } export function getAllTags(): string[] { const allPosts = getSortedPostsData() const tags = new Set() allPosts.forEach(post => { post.tags.forEach(tag => tags.add(tag)) }) return Array.from(tags).sort() }