This commit is contained in:
Dominic Grimm 2023-02-12 09:18:56 +01:00
parent 501b9d3093
commit 964534d0d9
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
21 changed files with 762 additions and 207 deletions

View file

@ -9,7 +9,7 @@ use anyhow::{bail, Result};
use chrono::prelude::*;
use clap::{Parser, Subcommand};
use diesel::prelude::*;
use r2d2_redis::redis;
use itertools::Itertools;
use scan_dir::ScanDir;
use serde::{Deserialize, Serialize};
use std::fs;
@ -33,12 +33,13 @@ enum Commands {
Clear,
}
#[derive(Deserialize, Serialize, Debug)]
#[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,
@ -57,9 +58,9 @@ fn main() -> Result<()> {
let db_conn = &mut db::establish_connection()?;
let redis_conn = &mut cache::establish_connection()?;
let posts = ScanDir::dirs()
.read("/blog", |iter| {
iter.map(|(entry, _)| {
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) {
@ -67,54 +68,74 @@ fn main() -> Result<()> {
Err(x) => bail!("Error parsing frontmatter: {:?}", x),
};
Ok(Post {
path,
frontmatter: frontmatter.headers,
content: frontmatter.body.to_string(),
})
Ok((
frontmatter.headers.tags.to_owned(),
Post {
path,
frontmatter: frontmatter.headers,
content: frontmatter.body.to_string(),
},
))
})
.collect::<Result<Vec<_>>>()
})??
.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(|post| -> Result<_> {
.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();
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()?
{
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: None,
slug: Some(&trimmed.slug),
description: Some(&trimmed.description),
content: Some(content),
published_at: Some(trimmed.published_at),
@ -123,69 +144,111 @@ fn main() -> Result<()> {
})
.execute(db_conn)?;
id
Ok(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)?
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)
};
fs::write(
post.path,
format!(
"---\n{}---\n\n{}\n",
serde_yaml::to_string(&PostFrontmatter {
id: Some(id),
..trimmed
})?,
content
),
)?;
res
}?;
Ok(id)
}
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<_>>>()?;
let ids = db::schema::posts::table
.select(db::schema::posts::id)
.load::<i32>(db_conn)?;
diesel::delete(
db::schema::posts::table
.filter(diesel::dsl::not(db::schema::posts::id.eq_any(posts))),
)
.execute(db_conn)?;
for id in ids {
redis::cmd("DEL")
.arg(cache::keys::post_content(id))
.query::<()>(redis_conn)?;
}
cache::clear(redis_conn)?;
Ok(())
}
Commands::Clear => {
let db_conn = &mut db::establish_connection()?;
let redis_conn = &mut cache::establish_connection()?;
for id in db::schema::posts::table
.select(db::schema::posts::id)
.load::<i32>(db_conn)?
{
redis::cmd("DEL")
.arg(cache::keys::post_content(id))
.query::<()>(redis_conn)?;
}
cache::clear(redis_conn)?;
Ok(())
}