147 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use std::fs;
 | 
						|
use std::io::Read;
 | 
						|
 | 
						|
use crate::utils::task_log;
 | 
						|
use serde::{Deserialize, Deserializer};
 | 
						|
 | 
						|
pub fn register(pool: &sqlx::Pool<sqlx::Postgres>) {
 | 
						|
    let p = pool.clone();
 | 
						|
    tokio::spawn(async move {
 | 
						|
        let _ = import_posts("app/", &p).await;
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
async fn import_posts(
 | 
						|
    dir_path: &str,
 | 
						|
    pool: &sqlx::Pool<sqlx::Postgres>,
 | 
						|
) -> Result<(), Box<dyn std::error::Error>> {
 | 
						|
    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::<MarkdownMetadata>(
 | 
						|
                &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<String, std::io::Error> {
 | 
						|
    let mut file = std::fs::read_to_string(file_path)?;
 | 
						|
 | 
						|
    Ok(file)
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Debug, sqlx::FromRow)]
 | 
						|
struct FilenameExists {
 | 
						|
    filename: Option<bool>,
 | 
						|
}
 | 
						|
 | 
						|
#[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<chrono::Utc>,
 | 
						|
    published: bool,
 | 
						|
}
 | 
						|
 | 
						|
fn deserialize_datetime<'de, D>(deserializer: D) -> Result<chrono::DateTime<chrono::Utc>, 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)
 | 
						|
}
 |