257 lines
9.9 KiB
Rust
257 lines
9.9 KiB
Rust
#[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<i32>,
|
|
name: String,
|
|
slug: String,
|
|
description: String,
|
|
tags: Vec<String>,
|
|
published_at: NaiveDate,
|
|
edited_at: Option<NaiveDate>,
|
|
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::<PostFrontmatter>(&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::<Result<Vec<(Vec<String>, Post)>>>()?;
|
|
|
|
let tags: Vec<String> = x.iter().flat_map(|y| y.0.to_owned()).unique().collect();
|
|
let posts: Vec<Post> = 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::<i32>(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::<i32>(db_conn)?,
|
|
},
|
|
)
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
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<i32, anyhow::Error> = 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::<i32>(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::<i32>(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::<i32>(db_conn)?;
|
|
|
|
let post_tags = db::schema::post_tags::table
|
|
.filter(db::schema::post_tags::post_id.eq(id))
|
|
.load::<db::models::PostTag>(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::<Vec<_>>(),
|
|
)
|
|
.execute(db_conn)?;
|
|
|
|
Ok(id)
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
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(())
|
|
}
|
|
}
|
|
}
|