import { db } from './db'; import { getOrCreateTag } from './tags'; import { getPostTags } from './tags'; // Load the blog post into a SQLite database // This allows us to index and make queries against the metadata // to support functions like search, filtering, and sorting // as well as return either the post or the full AppShell with the post content export function addToDatabase(filePath: string, data: { [key: string]: any }, content: string) { if (!data) return; // Use a transaction to ensure data consistency const transaction = db.transaction(() => { try { // Extract common fields const { title, date, readingTime, tags, excerpt } = data; // Convert all values to strings or null explicitly (except tags) // Ensure date is stored in ISO format for consistent sorting const values = [ filePath ? String(filePath) : null, title ? String(title) : null, date ? (date instanceof Date ? date.toISOString().split('T')[0] : String(date)) : null, readingTime ? String(readingTime) : null, excerpt ? String(excerpt) : null, content ? String(content) : null ]; // Query to insert or replace metadata (without tags) const insertPost = db.query(` INSERT OR REPLACE INTO posts (path, title, date, reading_time, summary, content) VALUES (?, ?, ?, ?, ?, ?) `); insertPost.run(...values); // Get the post ID const getPostId = db.query('SELECT id FROM posts WHERE path = ?'); const postResult = getPostId.get(filePath) as { id: number } | undefined; if (!postResult) { throw new Error(`Failed to retrieve post ID for ${filePath}`); } // Delete existing tag associations for this post const deleteExistingTags = db.query('DELETE FROM post_tags WHERE post_id = ?'); deleteExistingTags.run(postResult.id); // If tags exist, process them if (tags && Array.isArray(tags)) { // Insert into junction table const insertPostTag = db.query('INSERT OR IGNORE INTO post_tags (post_id, tag_id) VALUES (?, ?)'); for (const tag of tags) { const tagId = getOrCreateTag(String(tag)); insertPostTag.run(postResult.id, tagId); } } } catch (error) { console.error(`Failed to store ${filePath}:`, error); throw error; // Re-throw to make the transaction fail } }); // Execute the transaction try { transaction(); } catch (error) { console.error(`Transaction failed for ${filePath}:`, error); } } // Returns the total number of posts export function getNumOfPosts() { const queryCount = db.query('SELECT COUNT(*) AS count FROM posts'); const numPosts = queryCount.get() as { count: number }; return numPosts.count; } // Helper function to get post data with tags export function getPostWithTags(postPath: string) { const getPost = db.query(` SELECT * FROM posts WHERE path = ? `); const post = getPost.get(postPath) as any; if (!post) return null; // Get tags for this post if (post.id) { post.tags = getPostTags(post.id); } return post; } // Get recent posts export function getRecentPosts(limit: number = 10, offset: number = 0) { const query = db.query(` SELECT * FROM posts WHERE path NOT LIKE '%.md' ORDER BY date DESC LIMIT ? OFFSET ? `); const posts = query.all(limit, offset) as any[]; // Add tags to each post and clean up paths return posts.map(post => ({ ...post, tags: getPostTags(post.id), path: post.path.replace(/^.*\/content\//, '/').replace(/\.md$/, '') })); } // Get all posts export function getAllPosts() { const query = db.query(` SELECT * FROM posts ORDER BY date DESC `); const posts = query.all() as any[]; // Add tags to each post return posts.map(post => ({ ...post, tags: getPostTags(post.id) })); } // Helper function to get a post by its path export function getPostByPath(path: string) { const query = db.query(` SELECT * FROM posts WHERE path = ? `); const post = query.get(path) as any; if (!post) return null; // Get tags for this post post.tags = getPostTags(post.id); return post; } // Helper function to calculate read time export function calculateReadTime(content: string): number { const wordsPerMinute = 200; const words = content.split(/\s+/).length; return Math.max(1, Math.ceil(words / wordsPerMinute)); } // Helper function to format date for display export function formatDate(dateString: string): string { // Parse ISO date string (YYYY-MM-DD) const date = new Date(dateString + 'T00:00:00'); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } // Get adjacent posts (previous and next) chronologically for a given post export function getAdjacentPosts(currentPostPath: string) { const allPostsQuery = db.query(` SELECT path, title, date FROM posts WHERE path NOT LIKE '%.md' ORDER BY date DESC `); const allPosts = allPostsQuery.all() as any[]; // Find the current post index const currentIndex = allPosts.findIndex(post => post.path === currentPostPath); // If not found, return empty navigation if (currentIndex === -1) { return { previousPost: null, nextPost: null }; } // Get previous post (newer post, which comes before current in reverse chronological order) let previousPost = null; if (currentIndex > 0) { const prevPost = allPosts[currentIndex - 1]; // Clean up the path to match the URL structure const cleanPath = prevPost.path.replace(/^.*\/content\//, '/').replace(/\.md$/, ''); previousPost = { title: prevPost.title, path: cleanPath }; } // Get next post (older post, which comes after current in reverse chronological order) let nextPost = null; if (currentIndex < allPosts.length - 1) { const post = allPosts[currentIndex + 1]; // Clean up the path to match the URL structure const cleanPath = post.path.replace(/^.*\/content\//, '/').replace(/\.md$/, ''); nextPost = { title: post.title, path: cleanPath }; } return { previousPost, nextPost }; }