#[cfg(not(target_env = "msvc"))] use tikv_jemallocator::Jemalloc; #[cfg(not(target_env = "msvc"))] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; use anyhow::{bail, Result}; use chrono::prelude::*; use clap::{Parser, Subcommand}; use diesel::prelude::*; use itertools::Itertools; use scan_dir::ScanDir; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; use backend::*; #[derive(Debug, Parser)] #[clap(author, version, about, long_about = None)] struct Cli { #[clap(subcommand)] commands: Commands, } #[derive(Debug, Subcommand)] enum Commands { #[clap(about = "Imports new posts")] Import, #[clap(about = "Clears redis cache")] Clear, } #[derive(Deserialize, Serialize, Clone, Debug)] struct PostFrontmatter { id: Option, name: String, slug: String, description: String, tags: Vec, published_at: NaiveDate, edited_at: Option, active: bool, } #[derive(Debug)] struct Post { path: PathBuf, frontmatter: PostFrontmatter, content: String, } fn main() -> Result<()> { match Cli::parse().commands { Commands::Import => { let db_conn = &mut db::establish_connection()?; let redis_conn = &mut cache::establish_connection()?; let (tags_raw, posts_raw) = ScanDir::dirs().read("/blog", |iter| -> Result<_> { let x = iter .map(|(entry, _)| { let path = entry.path().join("post.md"); let src = fs::read_to_string(&path)?; let frontmatter = match fronma::parser::parse::(&src) { Ok(x) => x, Err(x) => bail!("Error parsing frontmatter: {:?}", x), }; Ok(( frontmatter.headers.tags.to_owned(), Post { path, frontmatter: frontmatter.headers, content: frontmatter.body.to_string(), }, )) }) .collect::, Post)>>>()?; let tags: Vec = x.iter().flat_map(|y| y.0.to_owned()).unique().collect(); let posts: Vec = x.into_iter().map(|y| y.1).collect(); Ok((tags, posts)) })??; let tags = tags_raw .into_iter() .map(|tag| { Ok( match db::schema::tags::table .select(db::schema::tags::id) .filter(db::schema::tags::name.eq(&tag)) .first::(db_conn) .optional()? { Some(x) => x, None => diesel::insert_into(db::schema::tags::table) .values(db::models::NewTag { name: &tag }) .returning(db::schema::tags::id) .get_result::(db_conn)?, }, ) }) .collect::>>()?; diesel::delete( db::schema::tags::table.filter(diesel::dsl::not(db::schema::tags::id.eq_any(tags))), ) .execute(db_conn)?; let posts = posts_raw .into_iter() .map(|post| { let trimmed = PostFrontmatter { id: post.frontmatter.id, name: post.frontmatter.name.trim().to_string(), slug: post.frontmatter.slug.trim().to_string(), description: post.frontmatter.description.trim().to_string(), tags: post .frontmatter .tags .iter() .map(|x| x.trim().to_string()) .collect(), published_at: post.frontmatter.published_at, edited_at: post.frontmatter.edited_at, active: post.frontmatter.active, }; let content = post.content.trim(); let id = { let res: Result = if let Some(id) = trimmed.id { diesel::update(db::schema::posts::table) .filter(db::schema::posts::id.eq(id)) .set(db::models::UpdatePost { name: Some(&trimmed.name), slug: Some(&trimmed.slug), description: Some(&trimmed.description), content: Some(content), published_at: Some(trimmed.published_at), edited_at: Some(trimmed.edited_at), active: Some(trimmed.active), }) .execute(db_conn)?; Ok(id) } else { let id = if let Some(id) = db::schema::posts::table .select(db::schema::posts::id) .filter(db::schema::posts::slug.eq(&trimmed.slug)) .first::(db_conn) .optional()? { diesel::update(db::schema::posts::table) .filter(db::schema::posts::id.eq(id)) .set(db::models::UpdatePost { name: Some(&trimmed.name), slug: None, description: Some(&trimmed.description), content: Some(content), published_at: Some(trimmed.published_at), edited_at: Some(trimmed.edited_at), active: Some(trimmed.active), }) .execute(db_conn)?; id } else { diesel::insert_into(db::schema::posts::table) .values(db::models::NewPost { name: &trimmed.name, slug: &trimmed.slug, description: &trimmed.description, content: content, published_at: trimmed.published_at, edited_at: trimmed.edited_at, active: trimmed.active, }) .returning(db::schema::posts::id) .get_result::(db_conn)? }; fs::write( post.path, format!( "---\n{}---\n\n{}\n", serde_yaml::to_string(&PostFrontmatter { id: Some(id), ..trimmed.to_owned() })?, content ), )?; Ok(id) }; res }?; let tag_ids = db::schema::tags::table .select(db::schema::tags::id) .filter(db::schema::tags::name.eq_any(trimmed.tags)) .load::(db_conn)?; let post_tags = db::schema::post_tags::table .filter(db::schema::post_tags::post_id.eq(id)) .load::(db_conn)?; diesel::delete( db::schema::post_tags::table .filter(db::schema::post_tags::post_id.eq(id)) .filter(diesel::dsl::not( db::schema::post_tags::tag_id.eq_any(&tag_ids), )), ) .execute(db_conn)?; let post_tag_tag_ids: Vec<_> = post_tags.iter().map(|x| x.tag_id).collect(); diesel::insert_into(db::schema::post_tags::table) .values( tag_ids .into_iter() .filter(|x| !post_tag_tag_ids.contains(x)) .map(|x| db::models::NewPostTag { post_id: id, tag_id: x, }) .collect::>(), ) .execute(db_conn)?; Ok(id) }) .collect::>>()?; diesel::delete( db::schema::posts::table .filter(diesel::dsl::not(db::schema::posts::id.eq_any(posts))), ) .execute(db_conn)?; cache::clear(redis_conn)?; Ok(()) } Commands::Clear => { let redis_conn = &mut cache::establish_connection()?; cache::clear(redis_conn)?; Ok(()) } } }