add: redis cache support

This commit is contained in:
trisua 2025-03-23 21:19:16 -04:00
parent 1d9a96ae69
commit 38dbf10130
13 changed files with 541 additions and 17 deletions

View file

@ -1,4 +1,5 @@
use super::*;
use crate::cache::Cache;
use crate::model::{
Error, Result,
auth::{Token, User},
@ -31,8 +32,8 @@ impl DataManager {
}
}
auto_method!(get_user_by_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User);
auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User);
auto_method!(get_user_by_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
/// Get a user given just their auth token.
///
@ -130,8 +131,11 @@ impl DataManager {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!("atto.user:{}", id)).await;
self.2.remove(format!("atto.user:{}", user.username)).await;
Ok(())
}
auto_method!(update_user_tokens(Vec<Token>) -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde);
auto_method!(update_user_tokens(Vec<Token>) -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.user:{}");
}

View file

@ -39,6 +39,31 @@ macro_rules! auto_method {
}
};
($name:ident()@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, id: usize) -> Result<$returns_> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_row!(&conn, $query, &[&id], |x| { Ok(Self::$select_fn(x)) });
if res.is_err() {
return Err(Error::GeneralNotFound($name_.to_string()));
}
let x = res.unwrap();
self.2
.set(
format!($cache_key_tmpl, id),
serde_json::to_string(&x).unwrap(),
)
.await;
Ok(x)
}
};
($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => {
pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
let conn = match self.connect().await {
@ -56,6 +81,31 @@ macro_rules! auto_method {
}
};
($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_row!(&conn, $query, &[&selector], |x| { Ok(Self::$select_fn(x)) });
if res.is_err() {
return Err(Error::GeneralNotFound($name_.to_string()));
}
let x = res.unwrap();
self.2
.set(
format!($cache_key_tmpl, selector),
serde_json::to_string(&x).unwrap(),
)
.await;
Ok(x)
}
};
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal) => {
pub async fn $name(&self, id: usize, user: User) -> Result<()> {
let page = self.$select_fn(id).await?;
@ -81,6 +131,33 @@ macro_rules! auto_method {
}
};
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, id: usize, user: User) -> Result<()> {
let page = self.$select_fn(id).await?;
if user.id != page.owner {
if !user.permissions.check(FinePermission::$permission) {
return Err(Error::NotAllowed);
}
}
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(&conn, $query, &[&id.to_string()]);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!($cache_key_tmpl, id)).await;
Ok(())
}
};
($name:ident($x:ty)@$select_fn:ident:$permission:ident -> $query:literal) => {
pub async fn $name(&self, id: usize, user: User, x: $x) -> Result<()> {
let y = self.$select_fn(id).await?;
@ -106,6 +183,33 @@ macro_rules! auto_method {
}
};
($name:ident($x:ty)@$select_fn:ident:$permission:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, id: usize, user: User, x: $x) -> Result<()> {
let y = self.$select_fn(id).await?;
if user.id != y.owner {
if !user.permissions.check(FinePermission::$permission) {
return Err(Error::NotAllowed);
}
}
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(&conn, $query, &[&x, &id.to_string()]);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!($cache_key_tmpl, id)).await;
Ok(())
}
};
($name:ident($x:ty)@$select_fn:ident:$permission:ident -> $query:literal --serde) => {
pub async fn $name(&self, id: usize, user: User, x: $x) -> Result<()> {
let y = self.$select_fn(id).await?;
@ -135,6 +239,37 @@ macro_rules! auto_method {
}
};
($name:ident($x:ty)@$select_fn:ident:$permission:ident -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, id: usize, user: User, x: $x) -> Result<()> {
let y = self.$select_fn(id).await?;
if user.id != y.owner {
if !user.permissions.check(FinePermission::$permission) {
return Err(Error::NotAllowed);
}
}
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(
&conn,
$query,
&[&serde_json::to_string(&x).unwrap(), &id.to_string()]
);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!($cache_key_tmpl, id)).await;
Ok(())
}
};
($name:ident($x:ty) -> $query:literal) => {
pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
let conn = match self.connect().await {
@ -152,6 +287,25 @@ macro_rules! auto_method {
}
};
($name:ident($x:ty) -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(&conn, $query, &[&x, &id.to_string()]);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!($cache_key_tmpl, id)).await;
Ok(())
}
};
($name:ident($x:ty) -> $query:literal --serde) => {
pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
let conn = match self.connect().await {
@ -172,4 +326,27 @@ macro_rules! auto_method {
Ok(())
}
};
($name:ident($x:ty) -> $query:literal --serde --cache-key-tmpl=$cache_key_tmpl:literal) => {
pub async fn $name(&self, id: usize, x: $x) -> Result<()> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(
&conn,
$query,
&[&serde_json::to_string(&x).unwrap(), &id.to_string()]
);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!($cache_key_tmpl, id)).await;
Ok(())
}
};
}

View file

@ -1,3 +1,10 @@
#[cfg(not(feature = "redis"))]
use crate::cache::no_cache::NoCache;
#[cfg(feature = "redis")]
use crate::cache::redis::RedisCache;
use crate::cache::Cache;
use crate::config::Config;
use bb8_postgres::{
PostgresConnectionManager,
@ -13,13 +20,15 @@ pub type Connection<'a> = PooledConnection<'a, PostgresConnectionManager<NoTls>>
pub struct DataManager(
pub Config,
pub HashMap<String, LangFile>,
#[cfg(feature = "redis")] pub RedisCache,
#[cfg(not(feature = "redis"))] pub NoCache,
pub Pool<PostgresConnectionManager<NoTls>>,
);
impl DataManager {
/// Obtain a connection to the staging database.
pub(crate) async fn connect(&self) -> Result<Connection> {
Ok(self.2.get().await.unwrap())
Ok(self.3.get().await.unwrap())
}
/// Create a new [`DataManager`] (and init database).
@ -36,7 +45,15 @@ impl DataManager {
);
let pool = Pool::builder().max_size(15).build(manager).await.unwrap();
Ok(Self(config.clone(), read_langs(), pool))
Ok(Self(
config.clone(),
read_langs(),
#[cfg(feature = "redis")]
RedisCache::new().await,
#[cfg(not(feature = "redis"))]
NoCache::new().await,
pool,
))
}
}

View file

@ -1,10 +1,22 @@
#[cfg(not(feature = "redis"))]
use crate::cache::no_cache::NoCache;
#[cfg(feature = "redis")]
use crate::cache::redis::RedisCache;
use crate::cache::Cache;
use crate::config::Config;
use rusqlite::{Connection, Result};
use std::collections::HashMap;
use tetratto_l10n::{LangFile, read_langs};
#[derive(Clone)]
pub struct DataManager(pub Config, pub HashMap<String, LangFile>);
pub struct DataManager(
pub Config,
pub HashMap<String, LangFile>,
#[cfg(feature = "redis")] pub RedisCache,
#[cfg(not(feature = "redis"))] pub NoCache,
);
impl DataManager {
/// Obtain a connection to the staging database.
@ -14,7 +26,14 @@ impl DataManager {
/// Create a new [`DataManager`] (and init database).
pub async fn new(config: Config) -> Result<Self> {
let this = Self(config.clone(), read_langs());
let this = Self(
config.clone(),
read_langs(),
#[cfg(feature = "redis")]
RedisCache::new().await,
#[cfg(not(feature = "redis"))]
NoCache::new().await,
);
let conn = this.connect().await?;
conn.pragma_update(None, "journal_mode", "WAL").unwrap();

View file

@ -1,4 +1,5 @@
use super::*;
use crate::cache::Cache;
use crate::model::auth::User;
use crate::model::{Error, Result, journal::JournalPage, permissions::FinePermission};
use crate::{auto_method, execute, get, query_row};
@ -26,7 +27,7 @@ impl DataManager {
}
}
auto_method!(get_page_by_id()@get_page_from_row -> "SELECT * FROM pages WHERE id = $1" --name="journal page" --returns=JournalPage);
auto_method!(get_page_by_id()@get_page_from_row -> "SELECT * FROM pages WHERE id = $1" --name="journal page" --returns=JournalPage --cache-key-tmpl="atto.page:{}");
/// Create a new journal page in the database.
///
@ -77,9 +78,9 @@ impl DataManager {
Ok(())
}
auto_method!(delete_page()@get_page_by_id:MANAGE_JOURNAL_PAGES -> "DELETE FROM pages WHERE id = $1");
auto_method!(update_page_title(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET title = $1 WHERE id = $2");
auto_method!(update_page_prompt(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET prompt = $1 WHERE id = $2");
auto_method!(update_page_read_access(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET read_access = $1 WHERE id = $2" --serde);
auto_method!(update_page_write_access(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET write_access = $1 WHERE id = $2" --serde);
auto_method!(delete_page()@get_page_by_id:MANAGE_JOURNAL_PAGES -> "DELETE FROM pages WHERE id = $1" --cache-key-tmpl="atto.page:{}");
auto_method!(update_page_title(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.page:{}");
auto_method!(update_page_prompt(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET prompt = $1 WHERE id = $2" --cache-key-tmpl="atto.page:{}");
auto_method!(update_page_read_access(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.page:{}");
auto_method!(update_page_write_access(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.page:{}");
}