feat: make database access more uniform and stop locking as many pool connections
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful

This commit is contained in:
Radical 2025-07-21 04:15:04 +02:00
parent f5d4211fad
commit fa52412b43
30 changed files with 516 additions and 373 deletions

View file

@ -159,7 +159,7 @@ pub async fn post(
.await?;
if let Some(initial_guild) = app_state.config.instance.initial_guild {
Member::new(&app_state, uuid, initial_guild).await?;
Member::new(&mut conn, &app_state.cache_pool, uuid, initial_guild).await?;
}
let mut response = (

View file

@ -38,11 +38,17 @@ pub async fn get(
State(app_state): State<Arc<AppState>>,
query: Query<QueryParams>,
) -> Result<impl IntoResponse, Error> {
if let Ok(password_reset_token) =
PasswordResetToken::get_with_identifier(&app_state, query.identifier.clone()).await
let mut conn = app_state.pool.get().await?;
if let Ok(password_reset_token) = PasswordResetToken::get_with_identifier(
&mut conn,
&app_state.cache_pool,
query.identifier.clone(),
)
.await
{
if Utc::now().signed_duration_since(password_reset_token.created_at) > Duration::hours(1) {
password_reset_token.delete(&app_state).await?;
password_reset_token.delete(&app_state.cache_pool).await?;
} else {
return Err(Error::TooManyRequests(
"Please allow 1 hour before sending a new email".to_string(),
@ -50,7 +56,7 @@ pub async fn get(
}
}
PasswordResetToken::new(&app_state, query.identifier.clone()).await?;
PasswordResetToken::new(&mut conn, &app_state, query.identifier.clone()).await?;
Ok(StatusCode::OK)
}
@ -87,10 +93,14 @@ pub async fn post(
reset_password: Json<ResetPassword>,
) -> Result<impl IntoResponse, Error> {
let password_reset_token =
PasswordResetToken::get(&app_state, reset_password.token.clone()).await?;
PasswordResetToken::get(&app_state.cache_pool, reset_password.token.clone()).await?;
password_reset_token
.set_password(&app_state, reset_password.password.clone())
.set_password(
&mut app_state.pool.get().await?,
&app_state,
reset_password.password.clone(),
)
.await?;
Ok(StatusCode::OK)

View file

@ -55,7 +55,7 @@ pub async fn get(
return Ok(StatusCode::NO_CONTENT);
}
let email_token = EmailToken::get(&app_state, me.uuid).await?;
let email_token = EmailToken::get(&app_state.cache_pool, me.uuid).await?;
if query.token != email_token.token {
return Ok(StatusCode::UNAUTHORIZED);
@ -63,7 +63,7 @@ pub async fn get(
me.verify_email(&mut conn).await?;
email_token.delete(&app_state).await?;
email_token.delete(&app_state.cache_pool).await?;
Ok(StatusCode::OK)
}
@ -91,9 +91,9 @@ pub async fn post(
return Ok(StatusCode::NO_CONTENT);
}
if let Ok(email_token) = EmailToken::get(&app_state, me.uuid).await {
if let Ok(email_token) = EmailToken::get(&app_state.cache_pool, me.uuid).await {
if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(1) {
email_token.delete(&app_state).await?;
email_token.delete(&app_state.cache_pool).await?;
} else {
return Err(Error::TooManyRequests(
"Please allow 1 hour before sending a new email".to_string(),

View file

@ -60,14 +60,21 @@ pub async fn get(
Query(message_request): Query<MessageRequest>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let channel = Channel::fetch_one(&app_state, channel_uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid).await?;
let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?;
Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?;
let messages = channel
.fetch_messages(&app_state, message_request.amount, message_request.offset)
.fetch_messages(
&mut conn,
&app_state.cache_pool,
message_request.amount,
message_request.offset,
)
.await?;
Ok((StatusCode::OK, Json(messages)))

View file

@ -27,11 +27,13 @@ pub async fn get(
Path(channel_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let channel = Channel::fetch_one(&app_state, channel_uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid).await?;
let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?;
Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?;
Ok((StatusCode::OK, Json(channel)))
}
@ -41,19 +43,19 @@ pub async fn delete(
Path(channel_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let channel = Channel::fetch_one(&app_state, channel_uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let member =
Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid)
.await?;
let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?;
let member = Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?;
member
.check_permission(&app_state, Permissions::ManageChannel)
.check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageChannel)
.await?;
channel.delete(&app_state).await?;
channel.delete(&mut conn, &app_state.cache_pool).await?;
Ok(StatusCode::OK)
}
@ -102,31 +104,37 @@ pub async fn patch(
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
Json(new_info): Json<NewInfo>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let mut channel = Channel::fetch_one(&app_state, channel_uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let member =
Member::check_membership(&mut app_state.pool.get().await?, uuid, channel.guild_uuid)
.await?;
let mut channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?;
let member = Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?;
member
.check_permission(&app_state, Permissions::ManageChannel)
.check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageChannel)
.await?;
if let Some(new_name) = &new_info.name {
channel.set_name(&app_state, new_name.to_string()).await?;
channel
.set_name(&mut conn, &app_state.cache_pool, new_name.to_string())
.await?;
}
if let Some(new_description) = &new_info.description {
channel
.set_description(&app_state, new_description.to_string())
.set_description(
&mut conn,
&app_state.cache_pool,
new_description.to_string(),
)
.await?;
}
if let Some(new_is_above) = &new_info.is_above {
channel
.set_description(&app_state, new_is_above.to_string())
.set_description(&mut conn, &app_state.cache_pool, new_is_above.to_string())
.await?;
}

View file

@ -71,9 +71,9 @@ pub async fn ws(
// Authorize client using auth header
let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&app_state, uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let channel = Channel::fetch_one(&app_state, channel_uuid).await?;
let channel = Channel::fetch_one(&mut conn, &app_state.cache_pool, channel_uuid).await?;
Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?;
@ -103,7 +103,8 @@ pub async fn ws(
let message = channel
.new_message(
&app_state,
&mut conn,
&app_state.cache_pool,
uuid,
message_body.message,
message_body.reply_to,

View file

@ -128,9 +128,11 @@ pub async fn get_guilds(
let start = request_query.start.unwrap_or(0);
let amount = request_query.amount.unwrap_or(10);
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let guilds = Guild::fetch_amount(&app_state.pool, start, amount).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let guilds = Guild::fetch_amount(&mut conn, start, amount).await?;
Ok((StatusCode::OK, Json(guilds)))
}

View file

@ -14,7 +14,7 @@ use crate::{
api::v1::auth::CurrentUser,
error::Error,
objects::{Channel, Member, Permissions},
utils::{global_checks, order_by_is_above},
utils::{CacheFns, global_checks, order_by_is_above},
};
#[derive(Deserialize)]
@ -28,23 +28,26 @@ pub async fn get(
Path(guild_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
Member::check_membership(&mut app_state.pool.get().await?, uuid, guild_uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
if let Ok(cache_hit) = app_state
.get_cache_key(format!("{guild_uuid}_channels"))
.cache_pool
.get_cache_key::<Vec<Channel>>(format!("{guild_uuid}_channels"))
.await
&& let Ok(channels) = serde_json::from_str::<Vec<Channel>>(&cache_hit)
{
return Ok((StatusCode::OK, Json(channels)).into_response());
return Ok((StatusCode::OK, Json(cache_hit)).into_response());
}
let channels = Channel::fetch_all(&app_state.pool, guild_uuid).await?;
let channels = Channel::fetch_all(&mut conn, guild_uuid).await?;
let channels_ordered = order_by_is_above(channels).await?;
app_state
.cache_pool
.set_cache_key(
format!("{guild_uuid}_channels"),
channels_ordered.clone(),
@ -61,17 +64,19 @@ pub async fn create(
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
Json(channel_info): Json<ChannelInfo>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let member =
Member::check_membership(&mut app_state.pool.get().await?, uuid, guild_uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?;
member
.check_permission(&app_state, Permissions::ManageChannel)
.check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageChannel)
.await?;
let channel = Channel::new(
&app_state,
&mut conn,
&app_state.cache_pool,
guild_uuid,
channel_info.name.clone(),
channel_info.description.clone(),

View file

@ -27,10 +27,10 @@ pub async fn get(
Path(guild_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
let guild = Guild::fetch_one(&mut conn, guild_uuid).await?;
@ -46,14 +46,14 @@ pub async fn create(
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
Json(invite_request): Json<InviteRequest>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?;
member
.check_permission(&app_state, Permissions::CreateInvite)
.check_permission(&mut conn, &app_state.cache_pool, Permissions::CreateInvite)
.await?;
let guild = Guild::fetch_one(&mut conn, guild_uuid).await?;

View file

@ -21,15 +21,15 @@ pub async fn get(
Path(guild_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
let me = Me::get(&mut conn, uuid).await?;
let members = Member::fetch_all(&app_state, &me, guild_uuid).await?;
let members = Member::fetch_all(&mut conn, &app_state.cache_pool, &me, guild_uuid).await?;
Ok((StatusCode::OK, Json(members)))
}

View file

@ -82,10 +82,10 @@ pub async fn get_guild(
Path(guild_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
let guild = Guild::fetch_one(&mut conn, guild_uuid).await?;
@ -102,14 +102,14 @@ pub async fn edit(
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
mut multipart: Multipart,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?;
member
.check_permission(&app_state, Permissions::ManageGuild)
.check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageGuild)
.await?;
let mut guild = Guild::fetch_one(&mut conn, guild_uuid).await?;
@ -127,14 +127,7 @@ pub async fn edit(
}
if let Some(icon) = icon {
guild
.set_icon(
&app_state.bunny_storage,
&mut conn,
app_state.config.bunny.cdn_url.clone(),
icon,
)
.await?;
guild.set_icon(&mut conn, &app_state, icon).await?;
}
Ok(StatusCode::OK)

View file

@ -14,7 +14,7 @@ use crate::{
api::v1::auth::CurrentUser,
error::Error,
objects::{Member, Permissions, Role},
utils::{global_checks, order_by_is_above},
utils::{CacheFns, global_checks, order_by_is_above},
};
pub mod uuid;
@ -29,16 +29,18 @@ pub async fn get(
Path(guild_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
if let Ok(cache_hit) = app_state.get_cache_key(format!("{guild_uuid}_roles")).await
&& let Ok(roles) = serde_json::from_str::<Vec<Role>>(&cache_hit)
if let Ok(cache_hit) = app_state
.cache_pool
.get_cache_key::<Vec<Role>>(format!("{guild_uuid}_roles"))
.await
{
return Ok((StatusCode::OK, Json(roles)).into_response());
return Ok((StatusCode::OK, Json(cache_hit)).into_response());
}
let roles = Role::fetch_all(&mut conn, guild_uuid).await?;
@ -46,6 +48,7 @@ pub async fn get(
let roles_ordered = order_by_is_above(roles).await?;
app_state
.cache_pool
.set_cache_key(format!("{guild_uuid}_roles"), roles_ordered.clone(), 1800)
.await?;
@ -58,14 +61,14 @@ pub async fn create(
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
Json(role_info): Json<RoleInfo>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?;
member
.check_permission(&app_state, Permissions::ManageRole)
.check_permission(&mut conn, &app_state.cache_pool, Permissions::ManageRole)
.await?;
let role = Role::new(&mut conn, guild_uuid, role_info.name.clone()).await?;

View file

@ -13,7 +13,7 @@ use crate::{
api::v1::auth::CurrentUser,
error::Error,
objects::{Member, Role},
utils::global_checks,
utils::{CacheFns, global_checks},
};
pub async fn get(
@ -21,21 +21,24 @@ pub async fn get(
Path((guild_uuid, role_uuid)): Path<(Uuid, Uuid)>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
Member::check_membership(&mut conn, uuid, guild_uuid).await?;
if let Ok(cache_hit) = app_state.get_cache_key(format!("{role_uuid}")).await
&& let Ok(role) = serde_json::from_str::<Role>(&cache_hit)
if let Ok(cache_hit) = app_state
.cache_pool
.get_cache_key::<Role>(format!("{role_uuid}"))
.await
{
return Ok((StatusCode::OK, Json(role)).into_response());
return Ok((StatusCode::OK, Json(cache_hit)).into_response());
}
let role = Role::fetch_one(&mut conn, role_uuid).await?;
app_state
.cache_pool
.set_cache_key(format!("{role_uuid}"), role.clone(), 60)
.await?;

View file

@ -34,15 +34,15 @@ pub async fn join(
Path(invite_id): Path<String>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let invite = Invite::fetch_one(&mut conn, invite_id).await?;
let guild = Guild::fetch_one(&mut conn, invite.guild_uuid).await?;
Member::new(&app_state, uuid, guild.uuid).await?;
Member::new(&mut conn, &app_state.cache_pool, uuid, guild.uuid).await?;
Ok((StatusCode::OK, Json(guild)))
}

View file

@ -19,11 +19,13 @@ pub async fn get(
State(app_state): State<Arc<AppState>>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let me = Me::get(&mut app_state.pool.get().await?, uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let friends = me.get_friends(&app_state).await?;
let me = Me::get(&mut conn, uuid).await?;
let friends = me.get_friends(&mut conn, &app_state.cache_pool).await?;
Ok((StatusCode::OK, Json(friends)))
}
@ -57,10 +59,10 @@ pub async fn post(
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
Json(user_request): Json<UserReq>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let me = Me::get(&mut conn, uuid).await?;
let target_uuid = user_uuid_from_username(&mut conn, &user_request.username).await?;

View file

@ -17,10 +17,10 @@ pub async fn delete(
Path(friend_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let me = Me::get(&mut conn, uuid).await?;
me.remove_friend(&mut conn, friend_uuid).await?;

View file

@ -58,10 +58,10 @@ pub async fn get(
State(app_state): State<Arc<AppState>>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let me = Me::get(&mut conn, uuid).await?;
let memberships = me.fetch_memberships(&mut conn).await?;

View file

@ -73,36 +73,41 @@ pub async fn update(
let json = json_raw.unwrap_or_default();
let mut conn = app_state.pool.get().await?;
if avatar.is_some() || json.username.is_some() || json.display_name.is_some() {
global_checks(&app_state, uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
}
let mut me = Me::get(&mut app_state.pool.get().await?, uuid).await?;
let mut me = Me::get(&mut conn, uuid).await?;
if let Some(avatar) = avatar {
me.set_avatar(&app_state, app_state.config.bunny.cdn_url.clone(), avatar)
.await?;
me.set_avatar(&mut conn, &app_state, avatar).await?;
}
if let Some(username) = &json.username {
me.set_username(&app_state, username.clone()).await?;
me.set_username(&mut conn, &app_state.cache_pool, username.clone())
.await?;
}
if let Some(display_name) = &json.display_name {
me.set_display_name(&app_state, display_name.clone())
me.set_display_name(&mut conn, &app_state.cache_pool, display_name.clone())
.await?;
}
if let Some(email) = &json.email {
me.set_email(&app_state, email.clone()).await?;
me.set_email(&mut conn, &app_state.cache_pool, email.clone())
.await?;
}
if let Some(pronouns) = &json.pronouns {
me.set_pronouns(&app_state, pronouns.clone()).await?;
me.set_pronouns(&mut conn, &app_state.cache_pool, pronouns.clone())
.await?;
}
if let Some(about) = &json.about {
me.set_about(&app_state, about.clone()).await?;
me.set_about(&mut conn, &app_state.cache_pool, about.clone())
.await?;
}
Ok(StatusCode::OK)

View file

@ -70,9 +70,11 @@ pub async fn users(
return Ok(StatusCode::BAD_REQUEST.into_response());
}
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let users = User::fetch_amount(&mut app_state.pool.get().await?, start, amount).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let users = User::fetch_amount(&mut conn, start, amount).await?;
Ok((StatusCode::OK, Json(users)).into_response())
}

View file

@ -39,11 +39,14 @@ pub async fn get(
Path(user_uuid): Path<Uuid>,
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
) -> Result<impl IntoResponse, Error> {
global_checks(&app_state, uuid).await?;
let mut conn = app_state.pool.get().await?;
let me = Me::get(&mut app_state.pool.get().await?, uuid).await?;
global_checks(&mut conn, &app_state.config, uuid).await?;
let user = User::fetch_one_with_friendship(&app_state, &me, user_uuid).await?;
let me = Me::get(&mut conn, uuid).await?;
let user =
User::fetch_one_with_friendship(&mut conn, &app_state.cache_pool, &me, user_uuid).await?;
Ok((StatusCode::OK, Json(user)))
}