332 lines
11 KiB
Rust
332 lines
11 KiB
Rust
use anyhow::Result;
|
|
use chrono::prelude::*;
|
|
use diesel::prelude::*;
|
|
use lazy_static::lazy_static;
|
|
use r2d2_redis::{r2d2, redis, RedisConnectionManager};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{config, db, markdown, web};
|
|
|
|
pub type RedisPool = r2d2::Pool<RedisConnectionManager>;
|
|
pub type ConnectionPool = r2d2::PooledConnection<RedisConnectionManager>;
|
|
pub type Connection = redis::Connection;
|
|
|
|
pub fn establish_connection() -> Result<redis::Connection> {
|
|
Ok(redis::Client::open(config::CONFIG.redis_url.as_str())?.get_connection()?)
|
|
}
|
|
|
|
pub fn pool() -> Result<RedisPool> {
|
|
Ok(r2d2::Pool::builder().build(RedisConnectionManager::new(
|
|
config::CONFIG.redis_url.as_str(),
|
|
)?)?)
|
|
}
|
|
|
|
lazy_static! {
|
|
pub static ref POOL: RedisPool = pool().unwrap();
|
|
}
|
|
|
|
pub mod keys {
|
|
pub const POSTS: &str = "posts";
|
|
|
|
pub const POST: &str = "post:";
|
|
|
|
pub fn post(id: i32) -> String {
|
|
format!("{}{}", POST, id)
|
|
}
|
|
|
|
pub const TAGS: &str = "tags";
|
|
|
|
pub const TAG_POSTS: &str = "tag_posts:";
|
|
|
|
pub fn tag_posts(id: i32) -> String {
|
|
format!("{}{}", TAG_POSTS, id)
|
|
}
|
|
}
|
|
|
|
pub fn clear(redis_conn: &mut Connection) -> Result<()> {
|
|
redis::cmd("UNLINK").arg(keys::POSTS).query(redis_conn)?;
|
|
|
|
for key in redis::cmd("KEYS")
|
|
.arg(format!("{}*", keys::POST))
|
|
.query::<Vec<String>>(redis_conn)?
|
|
{
|
|
redis::cmd("UNLINK").arg(key).query(redis_conn)?;
|
|
}
|
|
|
|
redis::cmd("UNLINK").arg(keys::TAGS).query(redis_conn)?;
|
|
|
|
for key in redis::cmd("KEYS")
|
|
.arg(format!("{}*", keys::TAG_POSTS))
|
|
.query::<Vec<String>>(redis_conn)?
|
|
{
|
|
redis::cmd("UNLINK").arg(key).query(redis_conn)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cache_posts(
|
|
db_conn: &mut db::Connection,
|
|
redis_conn: &mut Connection,
|
|
) -> Result<Vec<web::templates::PostIndexPost>> {
|
|
if let Some(s) = redis::cmd("GET")
|
|
.arg(keys::POSTS)
|
|
.query::<Option<String>>(redis_conn)?
|
|
{
|
|
Ok(serde_json::from_str(&s)?)
|
|
} else {
|
|
let posts_cache: Vec<web::templates::PostIndexPost> = db::schema::posts::table
|
|
.select((
|
|
db::schema::posts::id,
|
|
db::schema::posts::name,
|
|
db::schema::posts::slug,
|
|
db::schema::posts::description,
|
|
db::schema::posts::published_at,
|
|
db::schema::posts::edited_at,
|
|
))
|
|
.filter(db::schema::posts::active)
|
|
.order(db::schema::posts::published_at.desc())
|
|
.load::<(i32, String, String, String, NaiveDate, Option<NaiveDate>)>(db_conn)?
|
|
.into_iter()
|
|
.map(
|
|
|p: (i32, String, String, String, NaiveDate, Option<NaiveDate>)| -> Result<_> {
|
|
let (id, name, slug, description, published_at, edited_at) = p;
|
|
let tags = web::get_tags_by_post(id, db_conn)?;
|
|
|
|
Ok(web::templates::PostIndexPost {
|
|
name,
|
|
slug,
|
|
description,
|
|
published_at,
|
|
edited_at,
|
|
tags,
|
|
})
|
|
},
|
|
)
|
|
.collect::<Result<_>>()?;
|
|
|
|
redis::cmd("SET")
|
|
.arg(keys::POSTS)
|
|
.arg(serde_json::to_string(&posts_cache)?)
|
|
.query(redis_conn)?;
|
|
redis::cmd("EXPIRE")
|
|
.arg(keys::POSTS)
|
|
.arg(config::CONFIG.cache_ttl)
|
|
.query(redis_conn)?;
|
|
|
|
Ok(posts_cache)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct PostWithoutDescription {
|
|
pub name: String,
|
|
pub slug: String,
|
|
pub published_at: NaiveDate,
|
|
pub edited_at: Option<NaiveDate>,
|
|
pub tags: Vec<String>,
|
|
pub content: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct PostWithoutContent {
|
|
pub name: String,
|
|
pub slug: String,
|
|
pub description: String,
|
|
pub published_at: NaiveDate,
|
|
pub edited_at: Option<NaiveDate>,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
pub fn cache_post(
|
|
id: i32,
|
|
db_conn: &mut db::Connection,
|
|
redis_conn: &mut Connection,
|
|
) -> Result<PostWithoutDescription> {
|
|
let key = keys::post(id);
|
|
|
|
if let Some(s) = redis::cmd("GET")
|
|
.arg(&key)
|
|
.query::<Option<String>>(redis_conn)?
|
|
{
|
|
Ok(serde_json::from_str(&s)?)
|
|
} else {
|
|
let post = db::schema::posts::table
|
|
.filter(db::schema::posts::id.eq(id))
|
|
.first::<db::models::Post>(db_conn)?;
|
|
|
|
let post_cache = PostWithoutDescription {
|
|
name: post.name,
|
|
slug: post.slug,
|
|
published_at: post.published_at,
|
|
edited_at: post.edited_at,
|
|
tags: web::get_tags_by_post(id, db_conn)?,
|
|
content: markdown::to_html(&post.content),
|
|
};
|
|
|
|
redis::cmd("SET")
|
|
.arg(&key)
|
|
.arg(serde_json::to_string(&post_cache)?)
|
|
.query(redis_conn)?;
|
|
redis::cmd("EXPIRE")
|
|
.arg(key)
|
|
.arg(config::CONFIG.cache_ttl)
|
|
.query(redis_conn)?;
|
|
|
|
Ok(post_cache)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct PostsByTag {
|
|
pub name: String,
|
|
pub posts: Vec<PostWithoutContent>,
|
|
}
|
|
|
|
pub fn cache_tags(
|
|
db_conn: &mut db::Connection,
|
|
redis_conn: &mut Connection,
|
|
) -> Result<Vec<PostsByTag>> {
|
|
if let Some(s) = redis::cmd("GET")
|
|
.arg(keys::TAGS)
|
|
.query::<Option<String>>(redis_conn)?
|
|
{
|
|
Ok(serde_json::from_str(&s)?)
|
|
} else {
|
|
let tags = db::schema::tags::table
|
|
.select((db::schema::tags::id, db::schema::tags::name))
|
|
.order(db::schema::tags::name)
|
|
.load::<(i32, String)>(db_conn)?;
|
|
|
|
let posts_by_tag = tags
|
|
.into_iter()
|
|
.map(|(id, name)| -> Result<_> {
|
|
let post_tag_post_ids = db::schema::post_tags::table
|
|
.select(db::schema::post_tags::post_id)
|
|
.filter(db::schema::post_tags::tag_id.eq(id))
|
|
.load::<i32>(db_conn)?;
|
|
|
|
let posts = db::schema::posts::table
|
|
.select((
|
|
db::schema::posts::id,
|
|
db::schema::posts::name,
|
|
db::schema::posts::slug,
|
|
db::schema::posts::description,
|
|
db::schema::posts::published_at,
|
|
db::schema::posts::edited_at,
|
|
))
|
|
.filter(db::schema::posts::active)
|
|
.filter(db::schema::posts::id.eq_any(post_tag_post_ids))
|
|
.order(db::schema::posts::published_at.desc())
|
|
.load::<(i32, String, String, String, NaiveDate, Option<NaiveDate>)>(db_conn)?;
|
|
|
|
Ok(
|
|
PostsByTag {
|
|
name,
|
|
posts:
|
|
posts
|
|
.into_iter()
|
|
.map(
|
|
|p: (
|
|
i32,
|
|
String,
|
|
String,
|
|
String,
|
|
NaiveDate,
|
|
Option<NaiveDate>,
|
|
)|
|
|
-> Result<_> {
|
|
let (id, name, slug, description, published_at, edited_at) =
|
|
p;
|
|
let tags = web::get_tags_by_post(id, db_conn)?;
|
|
|
|
Ok(PostWithoutContent {
|
|
name,
|
|
slug,
|
|
description,
|
|
published_at,
|
|
edited_at,
|
|
tags,
|
|
})
|
|
},
|
|
)
|
|
.collect::<Result<Vec<_>>>()?,
|
|
},
|
|
)
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
redis::cmd("SET")
|
|
.arg(keys::TAGS)
|
|
.arg(serde_json::to_string(&posts_by_tag)?)
|
|
.query(redis_conn)?;
|
|
redis::cmd("EXPIRE")
|
|
.arg(keys::TAGS)
|
|
.arg(config::CONFIG.cache_ttl)
|
|
.query(redis_conn)?;
|
|
|
|
Ok(posts_by_tag)
|
|
}
|
|
}
|
|
|
|
pub fn cache_tag_posts(
|
|
id: i32,
|
|
db_conn: &mut db::Connection,
|
|
redis_conn: &mut Connection,
|
|
) -> Result<Vec<web::templates::PostIndexPost>> {
|
|
let key = keys::tag_posts(id);
|
|
|
|
if let Some(s) = redis::cmd("GET")
|
|
.arg(&key)
|
|
.query::<Option<String>>(redis_conn)?
|
|
{
|
|
Ok(serde_json::from_str(&s)?)
|
|
} else {
|
|
let post_tag_post_ids = db::schema::post_tags::table
|
|
.select(db::schema::post_tags::post_id)
|
|
.filter(db::schema::post_tags::tag_id.eq(id))
|
|
.load::<i32>(db_conn)?;
|
|
|
|
let posts: Vec<web::templates::PostIndexPost> = db::schema::posts::table
|
|
.select((
|
|
db::schema::posts::id,
|
|
db::schema::posts::name,
|
|
db::schema::posts::slug,
|
|
db::schema::posts::description,
|
|
db::schema::posts::published_at,
|
|
db::schema::posts::edited_at,
|
|
))
|
|
.filter(db::schema::posts::id.eq_any(post_tag_post_ids))
|
|
.filter(db::schema::posts::active)
|
|
.order(db::schema::posts::published_at.desc())
|
|
.load::<(i32, String, String, String, NaiveDate, Option<NaiveDate>)>(db_conn)?
|
|
.into_iter()
|
|
.map(
|
|
|p: (i32, String, String, String, NaiveDate, Option<NaiveDate>)| -> Result<_> {
|
|
let (id, name, slug, description, published_at, edited_at) = p;
|
|
let tags = web::get_tags_by_post(id, db_conn)?;
|
|
|
|
Ok(web::templates::PostIndexPost {
|
|
name,
|
|
slug,
|
|
description,
|
|
published_at,
|
|
edited_at,
|
|
tags,
|
|
})
|
|
},
|
|
)
|
|
.collect::<Result<_>>()?;
|
|
|
|
redis::cmd("SET")
|
|
.arg(&key)
|
|
.arg(serde_json::to_string(&posts)?)
|
|
.query(redis_conn)?;
|
|
redis::cmd("EXPIRE")
|
|
.arg(key)
|
|
.arg(config::CONFIG.cache_ttl)
|
|
.query(redis_conn)?;
|
|
|
|
Ok(posts)
|
|
}
|
|
}
|