use std::fs; use std::io::Read; use crate::utils::task_log; use serde::{Deserialize, Deserializer}; pub fn register(pool: &sqlx::Pool) { let p = pool.clone(); tokio::spawn(async move { let _ = import_posts("app/", &p).await; }); } async fn import_posts( dir_path: &str, pool: &sqlx::Pool, ) -> Result<(), Box> { println!("Beginning post import process"); // Start task logging let task = task_log::start(1, pool).await?; // Setup markdown options let options = MarkdownOptions { options: markdown::Constructs::gfm(), }; // Read directory contents let entries = fs::read_dir(dir_path)?; // Process each file for entry_result in entries { let file = entry_result?; let file_path = file.path(); // Skip non-file entries if !file_path.is_file() { continue; } let file_name = file.file_name(); let file_name_str = match file_name.to_str() { Some(name) => name, None => { eprintln!("Skipping file with non-UTF8 filename: {:?}", file_path); continue; } }; println!("Processing file: {}", file_name_str); // Check if file already exists in database let exists_query = sqlx::query_as!( FilenameExists, "SELECT EXISTS(SELECT 1 FROM posts p WHERE p.filename = $1) as filename", file_name_str ) .fetch_one(pool) .await?; // Skip if file already exists in database if !exists_query.filename.unwrap_or(false) { println!("Importing new file: {}", file_name_str); // Process file contents let file_md_contents = process_read_file(&file_path)?; // println!("{:?}", file_md_contents); // Extract metadata let document = crate::utils::front_matter::YamlFrontMatter::parse::( &file_md_contents, )?; let content = markdown::to_html_with_options(&document.content, &markdown::Options::default()); println!("{:?}", content); // println!("{:?}", document); let title = document.metadata.title; let content_final = content.unwrap(); // println!("{:?}", title); // Insert into database let results = sqlx::query_as::<_, InsertPosts>( "INSERT INTO posts (title, body, filename, author_id) VALUES ($1, $2, $3, $4) RETURNING title, body, filename, author_id" ) .bind(title) .bind(content_final) .bind(file_name_str) .bind(1) // Consider making author_id a parameter .fetch_one(pool) .await?; println!("{:?}", results); println!("Successfully imported: {}", file_name_str); } else { println!("Skipping existing file: {}", file_name_str); } } // Mark task as completed task_log::update(task.task_id, String::from("Completed"), pool).await?; Ok(()) } fn process_read_file(file_path: &std::path::Path) -> Result { let mut file = std::fs::read_to_string(file_path)?; Ok(file) } #[derive(Debug, sqlx::FromRow)] struct FilenameExists { filename: Option, } #[derive(Debug, sqlx::FromRow)] struct InsertPosts { title: String, body: String, filename: String, author_id: i32, } struct MarkdownOptions { options: markdown::Constructs, } #[derive(Deserialize, Debug)] struct MarkdownMetadata { layout: String, title: String, #[serde(deserialize_with = "deserialize_datetime")] date: chrono::DateTime, published: bool, } fn deserialize_datetime<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; chrono::DateTime::parse_from_rfc3339(&s) .map(|dt| dt.with_timezone(&chrono::Utc)) .map_err(serde::de::Error::custom) }