Rust beta fixes (#514)

* rust beta that breaks stuff

* flake up

* replace with macros, replace miette

* progress

* progress

* finish

* up

* Update typos config

* format toml

* revert back to stable
This commit is contained in:
Aumetra Weisman 2024-04-01 21:47:16 +02:00 committed by GitHub
parent 5b814bf674
commit 0a92aebfd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
132 changed files with 2511 additions and 3400 deletions

351
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -117,3 +117,4 @@ install-updater = true
[patch.crates-io]
diesel-async = { git = "https://github.com/weiznich/diesel_async.git", rev = "017ebe2fb7a2709ab5db92148dea5ce812a35e09" }
tokio-postgres-rustls = { git = "https://github.com/jbg/tokio-postgres-rustls.git", rev = "b3b59ac2fa1b5823f2426fef78a0fb74c004ec38" }

View File

@ -1,3 +1,6 @@
[default]
extend-ignore-words-re = ["guid"]
[files]
extend-exclude = [
"crates/kitsune-language/examples/basic.rs",
@ -10,4 +13,7 @@ extend-exclude = [
"lib/http-signatures/tests/data.rs",
"lib/post-process/tests/input/*",
# Exclude all snapshot files
"*.snap",
]

View File

@ -11,6 +11,7 @@ autometrics = { version = "1.0.1", default-features = false }
base64-simd = "0.8.0"
diesel = "2.1.5"
diesel-async = "0.4.1"
eyre = "0.6.12"
futures-util = "0.3.30"
headers = "0.4.0"
http = "1.1.0"
@ -32,7 +33,6 @@ kitsune-wasm-mrf = { path = "../kitsune-wasm-mrf" }
mime = "0.3.17"
mime_guess = { version = "2.0.4", default-features = false }
rsa = "0.9.6"
scoped-futures = "0.1.3"
serde = "1.0.197"
sha2 = "0.10.8"
simd-json = "0.13.9"
@ -52,7 +52,7 @@ kitsune-config = { path = "../kitsune-config" }
kitsune-test = { path = "../kitsune-test" }
kitsune-webfinger = { path = "../kitsune-webfinger" }
pretty_assertions = "1.4.0"
tokio = { version = "1.36.0", features = ["macros"] }
tokio = { version = "1.37.0", features = ["macros"] }
tower = { version = "0.4.13", default-features = false, features = ["util"] }
[lints]

View File

@ -11,20 +11,16 @@ use diesel::{
use diesel_async::RunQueryDsl;
use futures_util::TryStreamExt;
use iso8601_timestamp::Timestamp;
use kitsune_core::{
error::BoxError,
traits::{deliverer::Action, Deliverer as DelivererTrait},
};
use kitsune_core::traits::{deliverer::Action, Deliverer as DelivererTrait};
use kitsune_db::{
model::{account::Account, favourite::Favourite, follower::Follow, post::Post, user::User},
schema::{accounts, posts, users},
PgPool,
with_connection, PgPool,
};
use kitsune_service::attachment::AttachmentService;
use kitsune_type::ap::{ap_context, Activity, ActivityType, ObjectField};
use kitsune_url::UrlService;
use kitsune_util::try_join;
use scoped_futures::ScopedFutureExt;
use std::sync::Arc;
use typed_builder::TypedBuilder;
@ -61,26 +57,21 @@ impl Deliverer {
}
async fn accept_follow(&self, follow: Follow) -> Result<()> {
let (follower_inbox_url, (followed_account, followed_user)): (String, _) = self
.db_pool
.with_connection(|db_conn| {
async move {
let follower_inbox_url_fut = accounts::table
.find(follow.follower_id)
.select(accounts::inbox_url.assume_not_null())
.get_result::<String>(db_conn);
let (follower_inbox_url, (followed_account, followed_user)): (String, _) =
with_connection!(self.db_pool, |db_conn| {
let follower_inbox_url_fut = accounts::table
.find(follow.follower_id)
.select(accounts::inbox_url.assume_not_null())
.get_result::<String>(db_conn);
let followed_info_fut = accounts::table
.find(follow.account_id)
.inner_join(users::table.on(accounts::id.eq(users::account_id)))
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let followed_info_fut = accounts::table
.find(follow.account_id)
.inner_join(users::table.on(accounts::id.eq(users::account_id)))
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
try_join!(follower_inbox_url_fut, followed_info_fut)
}
.scoped()
})
.await?;
try_join!(follower_inbox_url_fut, followed_info_fut)
})?;
let followed_account_url = self.service.url.user_url(followed_account.id);
@ -111,17 +102,14 @@ impl Deliverer {
}
async fn create_or_repost(&self, post: Post) -> Result<()> {
let (account, user) = self
.db_pool
.with_connection(|db_conn| {
accounts::table
.find(post.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn)
.scoped()
})
.await?;
let (account, user) = with_connection!(self.db_pool, |db_conn| {
accounts::table
.find(post.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn)
.await
})?;
let inbox_stream = self
.inbox_resolver
@ -141,21 +129,15 @@ impl Deliverer {
}
async fn delete_or_unrepost(&self, post: Post) -> Result<()> {
let account_user_data = self
.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.find(post.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let account_user_data = with_connection!(self.db_pool, |db_conn| {
accounts::table
.find(post.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn)
.await
.optional()
})?;
let Some((account, user)) = account_user_data else {
return Ok(());
@ -179,27 +161,21 @@ impl Deliverer {
}
async fn favourite(&self, favourite: Favourite) -> Result<()> {
let ((account, user), inbox_url) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account_user_fut = accounts::table
.find(favourite.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result(db_conn);
let ((account, user), inbox_url) = with_connection!(self.db_pool, |db_conn| {
let account_user_fut = accounts::table
.find(favourite.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result(db_conn);
let inbox_url_fut = posts::table
.find(favourite.post_id)
.inner_join(accounts::table)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
let inbox_url_fut = posts::table
.find(favourite.post_id)
.inner_join(accounts::table)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
try_join!(account_user_fut, inbox_url_fut)
}
.scoped()
})
.await?;
try_join!(account_user_fut, inbox_url_fut)
})?;
if let Some(ref inbox_url) = inbox_url {
let activity = favourite.into_activity(self.mapping_state()).await?;
@ -213,26 +189,21 @@ impl Deliverer {
}
async fn follow(&self, follow: Follow) -> Result<()> {
let ((follower, follower_user), followed_inbox) = self
.db_pool
.with_connection(|db_conn| {
async move {
let follower_info_fut = accounts::table
.find(follow.follower_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let ((follower, follower_user), followed_inbox) =
with_connection!(self.db_pool, |db_conn| {
let follower_info_fut = accounts::table
.find(follow.follower_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let followed_inbox_fut = accounts::table
.find(follow.account_id)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
let followed_inbox_fut = accounts::table
.find(follow.account_id)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
try_join!(follower_info_fut, followed_inbox_fut)
}
.scoped()
})
.await?;
try_join!(follower_info_fut, followed_inbox_fut)
})?;
if let Some(followed_inbox) = followed_inbox {
let follow_activity = follow.into_activity(self.mapping_state()).await?;
@ -246,28 +217,23 @@ impl Deliverer {
}
async fn reject_follow(&self, follow: Follow) -> Result<()> {
let (follower_inbox_url, (followed_account, followed_user), _delete_result) = self
.db_pool
.with_connection(|db_conn| {
async {
let follower_inbox_url_fut = accounts::table
.find(follow.follower_id)
.select(accounts::inbox_url.assume_not_null())
.get_result::<String>(db_conn);
let (follower_inbox_url, (followed_account, followed_user), _delete_result) =
with_connection!(self.db_pool, |db_conn| {
let follower_inbox_url_fut = accounts::table
.find(follow.follower_id)
.select(accounts::inbox_url.assume_not_null())
.get_result::<String>(db_conn);
let followed_info_fut = accounts::table
.find(follow.account_id)
.inner_join(users::table.on(accounts::id.eq(users::account_id)))
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let followed_info_fut = accounts::table
.find(follow.account_id)
.inner_join(users::table.on(accounts::id.eq(users::account_id)))
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let delete_fut = diesel::delete(&follow).execute(db_conn);
let delete_fut = diesel::delete(&follow).execute(db_conn);
try_join!(follower_inbox_url_fut, followed_info_fut, delete_fut)
}
.scoped()
})
.await?;
try_join!(follower_inbox_url_fut, followed_info_fut, delete_fut)
})?;
let followed_account_url = self.service.url.user_url(followed_account.id);
@ -298,27 +264,21 @@ impl Deliverer {
}
async fn unfavourite(&self, favourite: Favourite) -> Result<()> {
let ((account, user), inbox_url) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account_user_fut = accounts::table
.find(favourite.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result(db_conn);
let ((account, user), inbox_url) = with_connection!(self.db_pool, |db_conn| {
let account_user_fut = accounts::table
.find(favourite.account_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result(db_conn);
let inbox_url_fut = posts::table
.find(favourite.post_id)
.inner_join(accounts::table)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
let inbox_url_fut = posts::table
.find(favourite.post_id)
.inner_join(accounts::table)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
try_join!(account_user_fut, inbox_url_fut)
}
.scoped()
})
.await?;
try_join!(account_user_fut, inbox_url_fut)
})?;
if let Some(ref inbox_url) = inbox_url {
let activity = favourite.into_negate_activity(self.mapping_state()).await?;
@ -331,26 +291,21 @@ impl Deliverer {
}
async fn unfollow(&self, follow: Follow) -> Result<()> {
let ((follower, follower_user), followed_account_inbox_url) = self
.db_pool
.with_connection(|db_conn| {
async {
let follower_info_fut = accounts::table
.find(follow.follower_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let ((follower, follower_user), followed_account_inbox_url) =
with_connection!(self.db_pool, |db_conn| {
let follower_info_fut = accounts::table
.find(follow.follower_id)
.inner_join(users::table)
.select(<(Account, User)>::as_select())
.get_result::<(Account, User)>(db_conn);
let followed_account_inbox_url_fut = accounts::table
.find(follow.account_id)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
let followed_account_inbox_url_fut = accounts::table
.find(follow.account_id)
.select(accounts::inbox_url)
.get_result::<Option<String>>(db_conn);
try_join!(follower_info_fut, followed_account_inbox_url_fut)
}
.scoped()
})
.await?;
try_join!(follower_info_fut, followed_account_inbox_url_fut)
})?;
if let Some(ref followed_account_inbox_url) = followed_account_inbox_url {
let follow_activity = follow.into_negate_activity(self.mapping_state()).await?;
@ -369,20 +324,14 @@ impl Deliverer {
}
async fn update_account(&self, account: Account) -> Result<()> {
let user = self
.db_pool
.with_connection(|db_conn| {
async move {
users::table
.filter(users::account_id.eq(account.id))
.select(User::as_select())
.get_result(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let user = with_connection!(self.db_pool, |db_conn| {
users::table
.filter(users::account_id.eq(account.id))
.select(User::as_select())
.get_result(db_conn)
.await
.optional()
})?;
let Some(user) = user else {
return Ok(());
@ -404,22 +353,16 @@ impl Deliverer {
}
async fn update_post(&self, post: Post) -> Result<()> {
let post_account_user_data = self
.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.find(post.id)
.inner_join(accounts::table)
.inner_join(users::table.on(accounts::id.eq(users::account_id)))
.select(<(Account, User)>::as_select())
.get_result(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let post_account_user_data = with_connection!(self.db_pool, |db_conn| {
posts::table
.find(post.id)
.inner_join(accounts::table)
.inner_join(users::table.on(accounts::id.eq(users::account_id)))
.select(<(Account, User)>::as_select())
.get_result(db_conn)
.await
.optional()
})?;
let Some((account, user)) = post_account_user_data else {
return Ok(());
@ -447,7 +390,7 @@ impl Deliverer {
#[async_trait]
impl DelivererTrait for Deliverer {
async fn deliver(&self, action: Action) -> Result<(), BoxError> {
async fn deliver(&self, action: Action) -> eyre::Result<()> {
match action {
Action::AcceptFollow(follow) => self.accept_follow(follow).await,
Action::Create(post) | Action::Repost(post) => self.create_or_repost(post).await,

View File

@ -1,10 +1,6 @@
use diesel_async::pooled_connection::bb8;
use kitsune_core::error::BoxError;
use rsa::pkcs8::der;
use std::{
convert::Infallible,
fmt::{Debug, Display},
};
use std::{convert::Infallible, fmt::Debug};
use thiserror::Error;
pub type Result<T, E = Error> = std::result::Result<T, E>;
@ -33,13 +29,13 @@ pub enum Error {
FederationFilter(#[from] kitsune_federation_filter::error::Error),
#[error(transparent)]
FetchAccount(BoxError),
FetchAccount(eyre::Report),
#[error(transparent)]
FetchEmoji(BoxError),
FetchEmoji(eyre::Report),
#[error(transparent)]
FetchPost(BoxError),
FetchPost(eyre::Report),
#[error(transparent)]
Http(#[from] http::Error),
@ -66,7 +62,7 @@ pub enum Error {
NotFound,
#[error(transparent)]
Resolver(BoxError),
Resolver(eyre::Report),
#[error(transparent)]
Search(#[from] kitsune_search::Error),
@ -89,15 +85,3 @@ impl From<Infallible> for Error {
match err {}
}
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}

View File

@ -11,11 +11,11 @@ use kitsune_core::traits::fetcher::AccountFetchOptions;
use kitsune_db::{
model::account::{Account, AccountConflictChangeset, NewAccount, UpdateAccountMedia},
schema::accounts,
with_connection, with_transaction,
};
use kitsune_search::SearchBackend;
use kitsune_type::ap::actor::Actor;
use kitsune_util::{convert::timestamp_to_uuid, sanitize::CleanHtmlExt};
use scoped_futures::ScopedFutureExt;
use url::Url;
impl Fetcher {
@ -36,20 +36,14 @@ impl Fetcher {
return Ok(Some(user));
}
let user_data = self
.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.filter(accounts::url.eq(opts.url))
.select(Account::as_select())
.first(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let user_data = with_connection!(self.db_pool, |db_conn| {
accounts::table
.filter(accounts::url.eq(opts.url))
.select(Account::as_select())
.first(db_conn)
.await
.optional()
})?;
if let Some(user) = user_data {
return Ok(Some(user));
@ -96,93 +90,87 @@ impl Fetcher {
actor.clean_html();
let account: Account = self
.db_pool
.with_transaction(|tx| {
async move {
let account = diesel::insert_into(accounts::table)
.values(NewAccount {
id: timestamp_to_uuid(actor.published),
display_name: actor.name.as_deref(),
note: actor.subject.as_deref(),
username: actor.preferred_username.as_str(),
locked: actor.manually_approves_followers,
local: false,
domain,
actor_type: actor.r#type.into(),
url: actor.id.as_str(),
featured_collection_url: actor.featured.as_deref(),
followers_url: actor.followers.as_deref(),
following_url: actor.following.as_deref(),
inbox_url: Some(actor.inbox.as_str()),
outbox_url: actor.outbox.as_deref(),
shared_inbox_url: actor
.endpoints
.and_then(|endpoints| endpoints.shared_inbox)
.as_deref(),
public_key_id: actor.public_key.id.as_str(),
public_key: actor.public_key.public_key_pem.as_str(),
created_at: Some(actor.published),
})
.on_conflict(accounts::url)
.do_update()
.set(AccountConflictChangeset {
display_name: actor.name.as_deref(),
note: actor.subject.as_deref(),
locked: actor.manually_approves_followers,
public_key_id: actor.public_key.id.as_str(),
public_key: actor.public_key.public_key_pem.as_str(),
})
let account: Account = with_transaction!(self.db_pool, |tx| {
let account = diesel::insert_into(accounts::table)
.values(NewAccount {
id: timestamp_to_uuid(actor.published),
display_name: actor.name.as_deref(),
note: actor.subject.as_deref(),
username: actor.preferred_username.as_str(),
locked: actor.manually_approves_followers,
local: false,
domain,
actor_type: actor.r#type.into(),
url: actor.id.as_str(),
featured_collection_url: actor.featured.as_deref(),
followers_url: actor.followers.as_deref(),
following_url: actor.following.as_deref(),
inbox_url: Some(actor.inbox.as_str()),
outbox_url: actor.outbox.as_deref(),
shared_inbox_url: actor
.endpoints
.and_then(|endpoints| endpoints.shared_inbox)
.as_deref(),
public_key_id: actor.public_key.id.as_str(),
public_key: actor.public_key.public_key_pem.as_str(),
created_at: Some(actor.published),
})
.on_conflict(accounts::url)
.do_update()
.set(AccountConflictChangeset {
display_name: actor.name.as_deref(),
note: actor.subject.as_deref(),
locked: actor.manually_approves_followers,
public_key_id: actor.public_key.id.as_str(),
public_key: actor.public_key.public_key_pem.as_str(),
})
.returning(Account::as_returning())
.get_result::<Account>(tx)
.await?;
let avatar_id = if let Some(icon) = actor.icon {
process_attachments(tx, &account, &[icon]).await?.pop()
} else {
None
};
let header_id = if let Some(image) = actor.image {
process_attachments(tx, &account, &[image]).await?.pop()
} else {
None
};
let mut update_changeset = UpdateAccountMedia::default();
if let Some(avatar_id) = avatar_id {
update_changeset = UpdateAccountMedia {
avatar_id: Some(avatar_id),
..update_changeset
};
}
if let Some(header_id) = header_id {
update_changeset = UpdateAccountMedia {
header_id: Some(header_id),
..update_changeset
};
}
let account = match update_changeset {
UpdateAccountMedia {
avatar_id: None,
header_id: None,
} => account,
_ => {
diesel::update(&account)
.set(update_changeset)
.returning(Account::as_returning())
.get_result::<Account>(tx)
.await?;
let avatar_id = if let Some(icon) = actor.icon {
process_attachments(tx, &account, &[icon]).await?.pop()
} else {
None
};
let header_id = if let Some(image) = actor.image {
process_attachments(tx, &account, &[image]).await?.pop()
} else {
None
};
let mut update_changeset = UpdateAccountMedia::default();
if let Some(avatar_id) = avatar_id {
update_changeset = UpdateAccountMedia {
avatar_id: Some(avatar_id),
..update_changeset
};
}
if let Some(header_id) = header_id {
update_changeset = UpdateAccountMedia {
header_id: Some(header_id),
..update_changeset
};
}
let account = match update_changeset {
UpdateAccountMedia {
avatar_id: None,
header_id: None,
} => account,
_ => {
diesel::update(&account)
.set(update_changeset)
.returning(Account::as_returning())
.get_result(tx)
.await?
}
};
Ok::<_, Error>(account)
.get_result(tx)
.await?
}
.scoped()
})
.await?;
};
Ok::<_, Error>(account)
})?;
self.search_backend
.add_to_index(account.clone().into())

View File

@ -9,28 +9,22 @@ use kitsune_db::{
media_attachment::{MediaAttachment, NewMediaAttachment},
},
schema::{custom_emojis, media_attachments},
with_connection, with_transaction,
};
use kitsune_type::ap::emoji::Emoji;
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use url::Url;
impl Fetcher {
pub(crate) async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>> {
let existing_emoji = self
.db_pool
.with_connection(|db_conn| {
async move {
custom_emojis::table
.filter(custom_emojis::remote_id.eq(url))
.select(CustomEmoji::as_select())
.first(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let existing_emoji = with_connection!(self.db_pool, |db_conn| {
custom_emojis::table
.filter(custom_emojis::remote_id.eq(url))
.select(CustomEmoji::as_select())
.first(db_conn)
.await
.optional()
})?;
if let Some(emoji) = existing_emoji {
return Ok(Some(emoji));
@ -57,42 +51,36 @@ impl Fetcher {
let name_pure = emoji.name.replace(':', "");
let emoji: CustomEmoji = self
.db_pool
.with_transaction(|tx| {
async move {
let media_attachment = diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: Uuid::now_v7(),
account_id: None,
content_type,
description: None,
blurhash: None,
file_path: None,
remote_url: Some(&emoji.icon.url),
})
.returning(MediaAttachment::as_returning())
.get_result::<MediaAttachment>(tx)
.await?;
let emoji = diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id: Uuid::now_v7(),
remote_id: emoji.id,
shortcode: name_pure.to_string(),
domain: Some(domain.to_string()),
media_attachment_id: media_attachment.id,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc(),
})
.returning(CustomEmoji::as_returning())
.get_result::<CustomEmoji>(tx)
.await?;
Ok::<_, Error>(emoji)
}
.scoped()
})
.await?;
let emoji: CustomEmoji = with_transaction!(self.db_pool, |tx| {
let media_attachment = diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: Uuid::now_v7(),
account_id: None,
content_type,
description: None,
blurhash: None,
file_path: None,
remote_url: Some(&emoji.icon.url),
})
.returning(MediaAttachment::as_returning())
.get_result::<MediaAttachment>(tx)
.await?;
let emoji = diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id: Uuid::now_v7(),
remote_id: emoji.id,
shortcode: name_pure.to_string(),
domain: Some(domain.to_string()),
media_attachment_id: media_attachment.id,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc(),
})
.returning(CustomEmoji::as_returning())
.get_result::<CustomEmoji>(tx)
.await?;
Ok::<_, Error>(emoji)
})?;
Ok(Some(emoji))
}

View File

@ -6,7 +6,6 @@ use kitsune_cache::ArcCache;
use kitsune_config::language_detection::Configuration as LanguageDetectionConfig;
use kitsune_core::{
consts::USER_AGENT,
error::BoxError,
traits::{
fetcher::{AccountFetchOptions, PostFetchOptions},
Fetcher as FetcherTrait, Resolver,
@ -124,18 +123,15 @@ impl FetcherTrait for Fetcher {
Arc::new(self.resolver.clone())
}
async fn fetch_account(
&self,
opts: AccountFetchOptions<'_>,
) -> Result<Option<Account>, BoxError> {
async fn fetch_account(&self, opts: AccountFetchOptions<'_>) -> eyre::Result<Option<Account>> {
Ok(self.fetch_actor(opts).await?)
}
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>, BoxError> {
async fn fetch_emoji(&self, url: &str) -> eyre::Result<Option<CustomEmoji>> {
Ok(self.fetch_emoji(url).await?)
}
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>, BoxError> {
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> eyre::Result<Option<Post>> {
Ok(self.fetch_object(opts.url, opts.call_depth).await?)
}
}

View File

@ -4,8 +4,7 @@ use autometrics::autometrics;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use kitsune_cache::CacheBackend;
use kitsune_db::{model::post::Post, schema::posts};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::post::Post, schema::posts, with_connection};
// Maximum call depth of fetching new posts. Prevents unbounded recursion.
// Setting this to >=40 would cause the `fetch_infinitely_long_reply_chain` test to run into stack overflow
@ -23,20 +22,14 @@ impl Fetcher {
return Ok(Some(post));
}
let post = self
.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.filter(posts::url.eq(url))
.select(Post::as_select())
.first(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let post = with_connection!(self.db_pool, |db_conn| {
posts::table
.filter(posts::url.eq(url))
.select(Post::as_select())
.first(db_conn)
.await
.optional()
})?;
if let Some(post) = post {
self.post_cache.set(url, &post).await?;

View File

@ -13,9 +13,8 @@ use kitsune_db::{
post::{Post, Visibility},
},
schema::{accounts, accounts_follows},
PgPool,
with_connection, PgPool,
};
use scoped_futures::ScopedFutureExt;
pub struct InboxResolver {
db_pool: PgPool,
@ -32,27 +31,25 @@ impl InboxResolver {
&self,
account: &Account,
) -> Result<impl Stream<Item = Result<String, DieselError>> + Send + '_> {
self.db_pool
.with_connection(|db_conn| {
accounts_follows::table
.filter(accounts_follows::account_id.eq(account.id))
.inner_join(
accounts::table.on(accounts::id.eq(accounts_follows::follower_id).and(
accounts::inbox_url
.is_not_null()
.or(accounts::shared_inbox_url.is_not_null()),
)),
)
.distinct()
.select(coalesce_nullable(
accounts::shared_inbox_url,
accounts::inbox_url,
))
.load_stream(db_conn)
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
accounts_follows::table
.filter(accounts_follows::account_id.eq(account.id))
.inner_join(
accounts::table.on(accounts::id.eq(accounts_follows::follower_id).and(
accounts::inbox_url
.is_not_null()
.or(accounts::shared_inbox_url.is_not_null()),
)),
)
.distinct()
.select(coalesce_nullable(
accounts::shared_inbox_url,
accounts::inbox_url,
))
.load_stream(db_conn)
.await
})
.map_err(Error::from)
}
#[instrument(skip_all, fields(post_id = %post.id))]
@ -60,35 +57,29 @@ impl InboxResolver {
&self,
post: &Post,
) -> Result<impl Stream<Item = Result<String, DieselError>> + Send + '_> {
let (account, mentioned_inbox_stream) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account = accounts::table
.find(post.account_id)
.select(Account::as_select())
.first(db_conn)
.await?;
let (account, mentioned_inbox_stream) = with_connection!(self.db_pool, |db_conn| {
let account = accounts::table
.find(post.account_id)
.select(Account::as_select())
.first(db_conn)
.await?;
let mentioned_inbox_stream = Mention::belonging_to(post)
.inner_join(accounts::table)
.filter(
accounts::shared_inbox_url
.is_not_null()
.or(accounts::inbox_url.is_not_null()),
)
.select(coalesce_nullable(
accounts::shared_inbox_url,
accounts::inbox_url,
))
.load_stream(db_conn)
.await?;
let mentioned_inbox_stream = Mention::belonging_to(post)
.inner_join(accounts::table)
.filter(
accounts::shared_inbox_url
.is_not_null()
.or(accounts::inbox_url.is_not_null()),
)
.select(coalesce_nullable(
accounts::shared_inbox_url,
accounts::inbox_url,
))
.load_stream(db_conn)
.await?;
Ok::<_, Error>((account, mentioned_inbox_stream))
}
.scoped()
})
.await?;
Ok::<_, Error>((account, mentioned_inbox_stream))
})?;
let stream = if post.visibility == Visibility::MentionOnly {
Either::Left(mentioned_inbox_stream)

View File

@ -20,14 +20,13 @@ use kitsune_db::{
schema::{
media_attachments, posts, posts_custom_emojis, posts_media_attachments, posts_mentions,
},
PgPool,
with_transaction, PgPool,
};
use kitsune_embed::Client as EmbedClient;
use kitsune_language::Language;
use kitsune_search::{AnySearchBackend, SearchBackend};
use kitsune_type::ap::{object::MediaAttachment, Object, Tag, TagType};
use kitsune_util::{convert::timestamp_to_uuid, process, sanitize::CleanHtmlExt, CowBox};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -288,58 +287,53 @@ pub async fn process_new_object(process_data: ProcessNewObject<'_>) -> Result<Po
search_backend,
} = preprocess_object(process_data).await?;
let post = db_pool
.with_transaction(|tx| {
async move {
let new_post = diesel::insert_into(posts::table)
.values(NewPost {
id: timestamp_to_uuid(object.published),
account_id: user.id,
in_reply_to_id,
reposted_post_id: None,
subject: object.summary.as_deref(),
content: object.content.as_str(),
content_source: "",
content_lang: content_lang.into(),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: object.sensitive,
visibility,
is_local: false,
url: object.id.as_str(),
created_at: Some(object.published),
let post = with_transaction!(db_pool, |tx| {
let new_post = diesel::insert_into(posts::table)
.values(NewPost {
id: timestamp_to_uuid(object.published),
account_id: user.id,
in_reply_to_id,
reposted_post_id: None,
subject: object.summary.as_deref(),
content: object.content.as_str(),
content_source: "",
content_lang: content_lang.into(),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: object.sensitive,
visibility,
is_local: false,
url: object.id.as_str(),
created_at: Some(object.published),
})
.on_conflict(posts::url)
.do_update()
.set(PostConflictChangeset {
subject: object.summary.as_deref(),
content: object.content.as_str(),
})
.returning(Post::as_returning())
.get_result::<Post>(tx)
.await?;
let attachment_ids = process_attachments(tx, &user, &object.attachment).await?;
diesel::insert_into(posts_media_attachments::table)
.values(
attachment_ids
.into_iter()
.map(|attachment_id| NewPostMediaAttachment {
post_id: new_post.id,
media_attachment_id: attachment_id,
})
.on_conflict(posts::url)
.do_update()
.set(PostConflictChangeset {
subject: object.summary.as_deref(),
content: object.content.as_str(),
})
.returning(Post::as_returning())
.get_result::<Post>(tx)
.await?;
.collect::<Vec<NewPostMediaAttachment>>(),
)
.execute(tx)
.await?;
let attachment_ids = process_attachments(tx, &user, &object.attachment).await?;
diesel::insert_into(posts_media_attachments::table)
.values(
attachment_ids
.into_iter()
.map(|attachment_id| NewPostMediaAttachment {
post_id: new_post.id,
media_attachment_id: attachment_id,
})
.collect::<Vec<NewPostMediaAttachment>>(),
)
.execute(tx)
.await?;
handle_mentions(tx, &user, new_post.id, &object.tag).await?;
handle_custom_emojis(tx, new_post.id, fetcher, &object.tag).await?;
handle_mentions(tx, &user, new_post.id, &object.tag).await?;
handle_custom_emojis(tx, new_post.id, fetcher, &object.tag).await?;
Ok::<_, Error>(new_post)
}
.scoped()
})
.await?;
Ok::<_, Error>(new_post)
})?;
if post.visibility == Visibility::Public || post.visibility == Visibility::Unlisted {
search_backend.add_to_index(post.clone().into()).await?;
@ -362,51 +356,46 @@ pub async fn update_object(process_data: ProcessNewObject<'_>) -> Result<Post> {
search_backend,
} = preprocess_object(process_data).await?;
let post = db_pool
.with_transaction(|tx| {
async move {
let updated_post = diesel::update(posts::table)
.filter(posts::url.eq(object.id.as_str()))
.set(FullPostChangeset {
account_id: user.id,
in_reply_to_id,
reposted_post_id: None,
subject: object.summary.as_deref(),
content: object.content.as_str(),
content_source: "",
content_lang: content_lang.into(),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: object.sensitive,
visibility,
is_local: false,
updated_at: Timestamp::now_utc(),
let post = with_transaction!(db_pool, |tx| {
let updated_post = diesel::update(posts::table)
.filter(posts::url.eq(object.id.as_str()))
.set(FullPostChangeset {
account_id: user.id,
in_reply_to_id,
reposted_post_id: None,
subject: object.summary.as_deref(),
content: object.content.as_str(),
content_source: "",
content_lang: content_lang.into(),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: object.sensitive,
visibility,
is_local: false,
updated_at: Timestamp::now_utc(),
})
.returning(Post::as_returning())
.get_result::<Post>(tx)
.await?;
let attachment_ids = process_attachments(tx, &user, &object.attachment).await?;
diesel::insert_into(posts_media_attachments::table)
.values(
attachment_ids
.into_iter()
.map(|attachment_id| NewPostMediaAttachment {
post_id: updated_post.id,
media_attachment_id: attachment_id,
})
.returning(Post::as_returning())
.get_result::<Post>(tx)
.await?;
.collect::<Vec<NewPostMediaAttachment>>(),
)
.on_conflict_do_nothing()
.execute(tx)
.await?;
let attachment_ids = process_attachments(tx, &user, &object.attachment).await?;
diesel::insert_into(posts_media_attachments::table)
.values(
attachment_ids
.into_iter()
.map(|attachment_id| NewPostMediaAttachment {
post_id: updated_post.id,
media_attachment_id: attachment_id,
})
.collect::<Vec<NewPostMediaAttachment>>(),
)
.on_conflict_do_nothing()
.execute(tx)
.await?;
handle_mentions(tx, &user, updated_post.id, &object.tag).await?;
handle_mentions(tx, &user, updated_post.id, &object.tag).await?;
Ok::<_, Error>(updated_post)
}
.scoped()
})
.await?;
Ok::<_, Error>(updated_post)
})?;
if post.visibility == Visibility::Public || post.visibility == Visibility::Unlisted {
search_backend.update_in_index(post.clone().into()).await?;

View File

@ -6,10 +6,10 @@ use iso8601_timestamp::Timestamp;
use kitsune_db::{
model::{account::Account, favourite::Favourite, follower::Follow, post::Post},
schema::{accounts, posts},
with_connection,
};
use kitsune_type::ap::{ap_context, Activity, ActivityType, ObjectField};
use kitsune_util::try_join;
use scoped_futures::ScopedFutureExt;
use std::future::Future;
pub trait IntoActivity {
@ -50,25 +50,19 @@ impl IntoActivity for Favourite {
type NegateOutput = Activity;
async fn into_activity(self, state: State<'_>) -> Result<Self::Output> {
let (account_url, post_url) = state
.db_pool
.with_connection(|db_conn| {
async move {
let account_url_fut = accounts::table
.find(self.account_id)
.select(accounts::url)
.get_result::<String>(db_conn);
let (account_url, post_url) = with_connection!(state.db_pool, |db_conn| {
let account_url_fut = accounts::table
.find(self.account_id)
.select(accounts::url)
.get_result::<String>(db_conn);
let post_url_fut = posts::table
.find(self.post_id)
.select(posts::url)
.get_result(db_conn);
let post_url_fut = posts::table
.find(self.post_id)
.select(posts::url)
.get_result(db_conn);
try_join!(account_url_fut, post_url_fut)
}
.scoped()
})
.await?;
try_join!(account_url_fut, post_url_fut)
})?;
Ok(Activity {
context: ap_context(),
@ -81,16 +75,13 @@ impl IntoActivity for Favourite {
}
async fn into_negate_activity(self, state: State<'_>) -> Result<Self::NegateOutput> {
let account_url = state
.db_pool
.with_connection(|db_conn| {
accounts::table
.find(self.account_id)
.select(accounts::url)
.get_result::<String>(db_conn)
.scoped()
})
.await?;
let account_url = with_connection!(state.db_pool, |db_conn| {
accounts::table
.find(self.account_id)
.select(accounts::url)
.get_result::<String>(db_conn)
.await
})?;
Ok(Activity {
context: ap_context(),
@ -108,25 +99,19 @@ impl IntoActivity for Follow {
type NegateOutput = Activity;
async fn into_activity(self, state: State<'_>) -> Result<Self::Output> {
let (attributed_to, object) = state
.db_pool
.with_connection(|db_conn| {
async move {
let attributed_to_fut = accounts::table
.find(self.follower_id)
.select(accounts::url)
.get_result::<String>(db_conn);
let (attributed_to, object) = with_connection!(state.db_pool, |db_conn| {
let attributed_to_fut = accounts::table
.find(self.follower_id)
.select(accounts::url)
.get_result::<String>(db_conn);
let object_fut = accounts::table
.find(self.account_id)
.select(accounts::url)
.get_result::<String>(db_conn);
let object_fut = accounts::table
.find(self.account_id)
.select(accounts::url)
.get_result::<String>(db_conn);
try_join!(attributed_to_fut, object_fut)
}
.scoped()
})
.await?;
try_join!(attributed_to_fut, object_fut)
})?;
Ok(Activity {
context: ap_context(),
@ -139,16 +124,13 @@ impl IntoActivity for Follow {
}
async fn into_negate_activity(self, state: State<'_>) -> Result<Self::NegateOutput> {
let attributed_to = state
.db_pool
.with_connection(|db_conn| {
accounts::table
.find(self.follower_id)
.select(accounts::url)
.get_result::<String>(db_conn)
.scoped()
})
.await?;
let attributed_to = with_connection!(state.db_pool, |db_conn| {
accounts::table
.find(self.follower_id)
.select(accounts::url)
.get_result::<String>(db_conn)
.await
})?;
Ok(Activity {
context: ap_context(),
@ -169,16 +151,13 @@ impl IntoActivity for Post {
let account_url = state.service.url.user_url(self.account_id);
if let Some(reposted_post_id) = self.reposted_post_id {
let reposted_post_url = state
.db_pool
.with_connection(|db_conn| {
posts::table
.find(reposted_post_id)
.select(posts::url)
.get_result(db_conn)
.scoped()
})
.await?;
let reposted_post_url = with_connection!(state.db_pool, |db_conn| {
posts::table
.find(reposted_post_id)
.select(posts::url)
.get_result(db_conn)
.await
})?;
Ok(Activity {
context: ap_context(),

View File

@ -12,6 +12,7 @@ use kitsune_db::{
post::Post,
},
schema::{accounts, custom_emojis, media_attachments, posts, posts_custom_emojis},
with_connection,
};
use kitsune_type::ap::{
actor::{Actor, PublicKey},
@ -22,7 +23,6 @@ use kitsune_type::ap::{
};
use kitsune_util::try_join;
use mime::Mime;
use scoped_futures::ScopedFutureExt;
use std::{future::Future, str::FromStr};
pub trait IntoObject {
@ -101,56 +101,51 @@ impl IntoObject for Post {
return Err(Error::NotFound);
}
let (account, in_reply_to, mentions, emojis, attachment_stream) = state
.db_pool
.with_connection(|db_conn| {
async {
let account_fut = accounts::table
.find(self.account_id)
.select(Account::as_select())
.get_result(db_conn);
let (account, in_reply_to, mentions, emojis, attachment_stream) =
with_connection!(state.db_pool, |db_conn| {
let account_fut = accounts::table
.find(self.account_id)
.select(Account::as_select())
.get_result(db_conn);
let in_reply_to_fut =
OptionFuture::from(self.in_reply_to_id.map(|in_reply_to_id| {
posts::table
.find(in_reply_to_id)
.select(posts::url)
.get_result(db_conn)
}))
.map(Option::transpose);
let in_reply_to_fut =
OptionFuture::from(self.in_reply_to_id.map(|in_reply_to_id| {
posts::table
.find(in_reply_to_id)
.select(posts::url)
.get_result(db_conn)
}))
.map(Option::transpose);
let mentions_fut = Mention::belonging_to(&self)
.inner_join(accounts::table)
.select((Mention::as_select(), Account::as_select()))
.load::<(Mention, Account)>(db_conn);
let mentions_fut = Mention::belonging_to(&self)
.inner_join(accounts::table)
.select((Mention::as_select(), Account::as_select()))
.load::<(Mention, Account)>(db_conn);
let custom_emojis_fut = custom_emojis::table
.inner_join(posts_custom_emojis::table)
.inner_join(media_attachments::table)
.filter(posts_custom_emojis::post_id.eq(self.id))
.select((
CustomEmoji::as_select(),
PostCustomEmoji::as_select(),
DbMediaAttachment::as_select(),
))
.load::<(CustomEmoji, PostCustomEmoji, DbMediaAttachment)>(db_conn);
let custom_emojis_fut = custom_emojis::table
.inner_join(posts_custom_emojis::table)
.inner_join(media_attachments::table)
.filter(posts_custom_emojis::post_id.eq(self.id))
.select((
CustomEmoji::as_select(),
PostCustomEmoji::as_select(),
DbMediaAttachment::as_select(),
))
.load::<(CustomEmoji, PostCustomEmoji, DbMediaAttachment)>(db_conn);
let attachment_stream_fut = PostMediaAttachment::belonging_to(&self)
.inner_join(media_attachments::table)
.select(DbMediaAttachment::as_select())
.load_stream::<DbMediaAttachment>(db_conn);
let attachment_stream_fut = PostMediaAttachment::belonging_to(&self)
.inner_join(media_attachments::table)
.select(DbMediaAttachment::as_select())
.load_stream::<DbMediaAttachment>(db_conn);
try_join!(
account_fut,
in_reply_to_fut,
mentions_fut,
custom_emojis_fut,
attachment_stream_fut
)
}
.scoped()
})
.await?;
try_join!(
account_fut,
in_reply_to_fut,
mentions_fut,
custom_emojis_fut,
attachment_stream_fut
)
})?;
let attachment = attachment_stream
.map_err(Error::from)
@ -197,34 +192,28 @@ impl IntoObject for Account {
type Output = Actor;
async fn into_object(self, state: State<'_>) -> Result<Self::Output> {
let (icon, image) = state
.db_pool
.with_connection(|db_conn| {
async move {
// These calls also probably allocate two cocnnections. ugh.
let icon_fut = OptionFuture::from(self.avatar_id.map(|avatar_id| {
media_attachments::table
.find(avatar_id)
.get_result::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|media_attachment| media_attachment.into_object(state))
}))
.map(Option::transpose);
let (icon, image) = with_connection!(state.db_pool, |db_conn| {
// These calls also probably allocate two cocnnections. ugh.
let icon_fut = OptionFuture::from(self.avatar_id.map(|avatar_id| {
media_attachments::table
.find(avatar_id)
.get_result::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|media_attachment| media_attachment.into_object(state))
}))
.map(Option::transpose);
let image_fut = OptionFuture::from(self.header_id.map(|header_id| {
media_attachments::table
.find(header_id)
.get_result::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|media_attachment| media_attachment.into_object(state))
}))
.map(Option::transpose);
let image_fut = OptionFuture::from(self.header_id.map(|header_id| {
media_attachments::table
.find(header_id)
.get_result::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|media_attachment| media_attachment.into_object(state))
}))
.map(Option::transpose);
try_join!(icon_fut, image_fut)
}
.scoped()
})
.await?;
try_join!(icon_fut, image_fut)
})?;
let user_url = state.service.url.user_url(self.id);
let inbox = state.service.url.inbox_url(self.id);
@ -269,17 +258,14 @@ impl IntoObject for CustomEmoji {
Some(_) => Err(Error::NotFound),
}?;
let icon = state
.db_pool
.with_connection(|db_conn| {
media_attachments::table
.find(self.media_attachment_id)
.get_result::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|media_attachment| media_attachment.into_object(state))
.scoped()
})
.await?;
let icon = with_connection!(state.db_pool, |db_conn| {
media_attachments::table
.find(self.media_attachment_id)
.get_result::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|media_attachment| media_attachment.into_object(state))
.await
})?;
Ok(Emoji {
context: ap_context(),

View File

@ -8,6 +8,7 @@ use kitsune_core::traits::Fetcher as _;
use kitsune_db::{
model::{account::Account, media_attachment::MediaAttachment},
schema::{accounts, media_attachments},
with_connection_panicky,
};
use kitsune_federation_filter::FederationFilter;
use kitsune_http_client::Client;
@ -15,7 +16,6 @@ use kitsune_search::NoopSearchService;
use kitsune_test::{database_test, language_detection_config};
use kitsune_webfinger::Webfinger;
use pretty_assertions::assert_eq;
use scoped_futures::ScopedFutureExt;
use std::sync::Arc;
use tower::service_fn;
@ -92,15 +92,14 @@ async fn fetch_emoji() {
assert_eq!(emoji.shortcode, "Blobhaj");
assert_eq!(emoji.domain, Some(String::from("corteximplant.com")));
let media_attachment = db_pool
.with_connection(|db_conn| {
let media_attachment =
with_connection_panicky!(db_pool, |db_conn| {
media_attachments::table
.find(emoji.media_attachment_id)
.select(MediaAttachment::as_select())
.get_result::<MediaAttachment>(db_conn)
.scoped()
.await
})
.await
.expect("Get media attachment");
assert_eq!(
@ -149,16 +148,14 @@ async fn fetch_note() {
"https://corteximplant.com/users/0x0/statuses/109501674056556919"
);
let author = db_pool
.with_connection(|db_conn| {
accounts::table
.find(note.account_id)
.select(Account::as_select())
.get_result::<Account>(db_conn)
.scoped()
})
.await
.expect("Get author");
let author = with_connection_panicky!(db_pool, |db_conn| {
accounts::table
.find(note.account_id)
.select(Account::as_select())
.get_result::<Account>(db_conn)
.await
})
.expect("Get author");
assert_eq!(author.username, "0x0");
assert_eq!(author.url, "https://corteximplant.com/users/0x0");

View File

@ -6,7 +6,7 @@ version.workspace = true
license.workspace = true
[dependencies]
enum_dispatch = "0.3.12"
enum_dispatch = "0.3.13"
moka = { version = "0.12.5", features = ["future"] }
multiplex-pool = { path = "../../lib/multiplex-pool" }
redis = { version = "0.25.2", default-features = false, features = [
@ -20,7 +20,7 @@ tracing = "0.1.40"
typed-builder = "0.18.1"
[dev-dependencies]
tokio = { version = "1.36.0", features = ["macros", "rt"] }
tokio = { version = "1.37.0", features = ["macros", "rt"] }
[lints]
workspace = true

View File

@ -6,7 +6,7 @@ edition.workspace = true
license.workspace = true
[dependencies]
enum_dispatch = "0.3.12"
enum_dispatch = "0.3.13"
http = "1.1.0"
kitsune-http-client = { path = "../kitsune-http-client" }
serde = { version = "1.0.197", features = ["derive"] }

View File

@ -6,11 +6,11 @@ version.workspace = true
license.workspace = true
[dependencies]
eyre = "0.6.12"
isolang = { version = "2.4.0", features = ["serde"] }
miette = "7.2.0"
serde = { version = "1.0.197", features = ["derive"] }
smol_str = { version = "0.2.1", features = ["serde"] }
tokio = { version = "1.36.0", features = ["fs"] }
tokio = { version = "1.37.0", features = ["fs"] }
toml = { version = "0.8.12", default-features = false, features = ["parse"] }
[lints]

View File

@ -15,7 +15,7 @@ pub mod server;
pub mod storage;
pub mod url;
use miette::{Context, IntoDiagnostic};
use eyre::{Result, WrapErr};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tokio::fs;
@ -41,17 +41,14 @@ pub struct Configuration {
}
impl Configuration {
pub async fn load<P>(path: P) -> miette::Result<Self>
pub async fn load<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
let content = fs::read_to_string(path)
.await
.into_diagnostic()
.wrap_err("Couldn't read configuration file")?;
toml::from_str(&content)
.into_diagnostic()
.wrap_err("Failed to parse configuration file")
toml::from_str(&content).wrap_err("Failed to parse configuration file")
}
}

View File

@ -9,6 +9,7 @@ build = "build.rs"
[dependencies]
async-trait = "0.1.79"
const_format = "0.2.32"
eyre = "0.6.12"
http = "1.1.0"
kitsune-db = { path = "../kitsune-db" }
kitsune-messaging = { path = "../kitsune-messaging" }

View File

@ -1,10 +1,7 @@
use http::StatusCode;
use std::borrow::Cow;
use std::error::Error as StdError;
use thiserror::Error;
pub type BoxError = Box<dyn StdError + Send + Sync>;
macro_rules! http_error {
($($variant_name:ident => $status_code:path),*$(,)?) => {
#[derive(Debug, Error)]

View File

@ -1,5 +1,5 @@
use crate::error::BoxError;
use async_trait::async_trait;
use eyre::Result;
use kitsune_db::model::{account::Account, favourite::Favourite, follower::Follow, post::Post};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@ -22,12 +22,12 @@ pub enum Action {
#[async_trait]
pub trait Deliverer: Send + Sync + 'static {
async fn deliver(&self, action: Action) -> Result<(), BoxError>;
async fn deliver(&self, action: Action) -> Result<()>;
}
#[async_trait]
impl Deliverer for Arc<dyn Deliverer> {
async fn deliver(&self, action: Action) -> Result<(), BoxError> {
async fn deliver(&self, action: Action) -> Result<()> {
(**self).deliver(action).await
}
}
@ -37,7 +37,7 @@ impl<T> Deliverer for Vec<T>
where
T: Deliverer,
{
async fn deliver(&self, action: Action) -> Result<(), BoxError> {
async fn deliver(&self, action: Action) -> Result<()> {
for deliverer in self {
deliverer.deliver(action.clone()).await?;
}

View File

@ -1,6 +1,6 @@
use super::Resolver;
use crate::error::BoxError;
use async_trait::async_trait;
use eyre::Result;
use kitsune_db::model::{account::Account, custom_emoji::CustomEmoji, post::Post};
use std::sync::Arc;
use typed_builder::TypedBuilder;
@ -8,7 +8,7 @@ use typed_builder::TypedBuilder;
#[derive(Clone, Copy, Debug, TypedBuilder)]
/// Options passed to the fetcher
pub struct AccountFetchOptions<'a> {
/// Prefetched WebFinger `acct` URI
/// Prefetched Webfinger `acct` URI
#[builder(default, setter(strip_option))]
pub acct: Option<(&'a str, &'a str)>,
@ -50,14 +50,11 @@ impl<'a> From<&'a str> for PostFetchOptions<'a> {
pub trait Fetcher: Send + Sync + 'static {
fn resolver(&self) -> Arc<dyn Resolver>;
async fn fetch_account(
&self,
opts: AccountFetchOptions<'_>,
) -> Result<Option<Account>, BoxError>;
async fn fetch_account(&self, opts: AccountFetchOptions<'_>) -> Result<Option<Account>>;
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>, BoxError>;
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>>;
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>, BoxError>;
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>>;
}
#[async_trait]
@ -66,18 +63,15 @@ impl Fetcher for Arc<dyn Fetcher> {
(**self).resolver()
}
async fn fetch_account(
&self,
opts: AccountFetchOptions<'_>,
) -> Result<Option<Account>, BoxError> {
async fn fetch_account(&self, opts: AccountFetchOptions<'_>) -> Result<Option<Account>> {
(**self).fetch_account(opts).await
}
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>, BoxError> {
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>> {
(**self).fetch_emoji(url).await
}
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>, BoxError> {
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>> {
(**self).fetch_post(opts).await
}
}
@ -91,10 +85,7 @@ where
Arc::new(self.iter().map(Fetcher::resolver).collect::<Vec<_>>())
}
async fn fetch_account(
&self,
opts: AccountFetchOptions<'_>,
) -> Result<Option<Account>, BoxError> {
async fn fetch_account(&self, opts: AccountFetchOptions<'_>) -> Result<Option<Account>> {
for fetcher in self {
if let Some(account) = fetcher.fetch_account(opts).await? {
return Ok(Some(account));
@ -104,7 +95,7 @@ where
Ok(None)
}
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>, BoxError> {
async fn fetch_emoji(&self, url: &str) -> Result<Option<CustomEmoji>> {
for fetcher in self {
if let Some(emoji) = fetcher.fetch_emoji(url).await? {
return Ok(Some(emoji));
@ -114,7 +105,7 @@ where
Ok(None)
}
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>, BoxError> {
async fn fetch_post(&self, opts: PostFetchOptions<'_>) -> Result<Option<Post>> {
for fetcher in self {
if let Some(post) = fetcher.fetch_post(opts).await? {
return Ok(Some(post));

View File

@ -1,5 +1,5 @@
use crate::error::BoxError;
use async_trait::async_trait;
use eyre::Result;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@ -20,7 +20,7 @@ pub trait Resolver: Send + Sync + 'static {
&self,
username: &str,
domain: &str,
) -> Result<Option<AccountResource>, BoxError>;
) -> Result<Option<AccountResource>>;
}
#[async_trait]
@ -29,7 +29,7 @@ impl Resolver for Arc<dyn Resolver> {
&self,
username: &str,
domain: &str,
) -> Result<Option<AccountResource>, BoxError> {
) -> Result<Option<AccountResource>> {
(**self).resolve_account(username, domain).await
}
}
@ -43,7 +43,7 @@ where
&self,
username: &str,
domain: &str,
) -> Result<Option<AccountResource>, BoxError> {
) -> Result<Option<AccountResource>> {
for resolver in self {
if let Some(resource) = resolver.resolve_account(username, domain).await? {
return Ok(Some(resource));

View File

@ -24,16 +24,20 @@ iso8601-timestamp = { version = "0.2.17", features = ["diesel-pg"] }
kitsune-config = { path = "../kitsune-config" }
kitsune-language = { path = "../kitsune-language" }
kitsune-type = { path = "../kitsune-type" }
miette = "7.2.0"
num-derive = "0.4.2"
num-traits = "0.2.18"
rustls = "=0.22.2"
rustls = { version = "0.23.4", default-features = false, features = [
"logging",
"ring",
"std",
"tls12",
] }
rustls-native-certs = "0.7.0"
serde = { version = "1.0.197", features = ["derive"] }
simd-json = "0.13.9"
speedy-uuid = { path = "../../lib/speedy-uuid", features = ["diesel"] }
thiserror = "1.0.58"
tokio = { version = "1.36.0", features = ["rt"] }
tokio = { version = "1.37.0", features = ["rt"] }
tokio-postgres = "0.7.10"
tokio-postgres-rustls = "0.11.1"
tracing = "0.1.40"
@ -42,7 +46,7 @@ typed-builder = "0.18.1"
[dev-dependencies]
kitsune-test = { path = "../kitsune-test" }
tokio = { version = "1.36.0", features = ["macros"] }
tokio = { version = "1.37.0", features = ["macros"] }
[lints]
workspace = true

View File

@ -1,6 +1,5 @@
use core::fmt;
use diesel_async::pooled_connection::bb8;
use miette::Diagnostic;
use std::error::Error as StdError;
use thiserror::Error;
@ -37,7 +36,7 @@ impl fmt::Display for IsoCodeConversionError {
impl StdError for IsoCodeConversionError {}
#[derive(Debug, Diagnostic, Error)]
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Blocking(#[from] blowocking::Error),

View File

@ -11,10 +11,11 @@ use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use kitsune_config::database::Configuration as DatabaseConfig;
use tracing_log::LogTracer;
pub use crate::{
error::{Error, Result},
pool::{PgPool, PoolError},
};
pub type PgPool = Pool<AsyncPgConnection>;
pub use crate::error::{Error, Result};
#[doc(hidden)]
pub use diesel_async;
mod error;
mod pool;
@ -75,5 +76,5 @@ pub async fn connect(config: &DatabaseConfig) -> Result<PgPool> {
.await?;
}
Ok(pool.into())
Ok(pool)
}

View File

@ -1,70 +1,41 @@
use diesel_async::{
pooled_connection::bb8::{self, Pool},
scoped_futures::{ScopedFutureExt, ScopedFutureWrapper},
AsyncConnection, AsyncPgConnection,
};
use miette::Diagnostic;
use std::{
fmt::{Debug, Display},
future::Future,
};
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
pub enum PoolError<E>
where
E: Display + Debug,
{
#[error(transparent)]
Pool(#[from] bb8::RunError),
#[error("{0}")]
User(E),
#[macro_export]
macro_rules! with_connection {
($pool:expr, |$conn_name:ident| $code:block) => {{
let mut conn = $pool.get().await?;
let $conn_name = &mut *conn;
async { $code }.await
}};
}
/// Small wrapper around [`Pool<AsyncPgConnection>`]
///
/// The intent of this API is to encourage and make short-lived ownership of connections easier.
/// With the traditional RAII guard based approach, it is rather hard (and/or ugly) to define clear scopes for connections
/// (especially when they are used *a lot* throughout the code).
///
/// The API of this wrapper is based on closures, meaning you have no choice but to be aware of the scope.
/// And the extra level of indentation this forces is supposed to coerce users to keep the scope as small as possible.
#[derive(Clone)]
pub struct PgPool {
inner: Pool<AsyncPgConnection>,
#[macro_export]
macro_rules! catch_error {
($($tt:tt)*) => {{
let result: ::std::result::Result<_, ::diesel_async::pooled_connection::bb8::RunError> = async {
Ok({ $($tt)* })
}.await;
result
}};
}
impl PgPool {
/// Run the code inside a context with a database connection
pub async fn with_connection<'a, F, Fut, T, E>(&self, func: F) -> Result<T, PoolError<E>>
where
for<'conn> F: FnOnce(&'conn mut AsyncPgConnection) -> ScopedFutureWrapper<'conn, 'a, Fut>,
Fut: Future<Output = Result<T, E>>,
E: Display + Debug,
{
let mut conn = self.inner.get().await?;
func(&mut conn).await.map_err(PoolError::User)
}
/// Run the code inside a context with a database transaction
pub async fn with_transaction<'a, F, Fut, T, E>(&self, func: F) -> Result<T, PoolError<E>>
where
for<'conn> F:
FnOnce(&'conn mut AsyncPgConnection) -> ScopedFutureWrapper<'conn, 'a, Fut> + Send,
Fut: Future<Output = Result<T, E>> + Send,
T: Send,
E: From<diesel::result::Error> + Debug + Display + Send,
{
let mut conn = self.inner.get().await?;
conn.transaction(|conn| func(conn).scope_boxed())
.await
.map_err(PoolError::User)
}
#[macro_export]
macro_rules! with_connection_panicky {
($pool:expr, $($other:tt)*) => {{
$crate::catch_error!($crate::with_connection!($pool, $($other)*)).unwrap()
}};
}
impl From<Pool<AsyncPgConnection>> for PgPool {
fn from(value: Pool<AsyncPgConnection>) -> Self {
Self { inner: value }
}
#[macro_export]
macro_rules! with_transaction {
($pool:expr, |$conn_name:ident| $code:block) => {{
use $crate::diesel_async::AsyncConnection;
let mut conn = $pool.get().await?;
conn.transaction(|conn| {
Box::pin(async move {
let $conn_name = conn;
$code
})
})
.await
}};
}

View File

@ -1,11 +1,12 @@
use diesel::SelectableHelper;
use diesel_async::{scoped_futures::ScopedFutureExt, AsyncPgConnection, RunQueryDsl};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use kitsune_db::{
model::{
account::{Account, ActorType, NewAccount},
user::{NewUser, User},
},
schema::{accounts, users},
with_connection_panicky,
};
use kitsune_test::database_test;
use speedy_uuid::Uuid;
@ -65,30 +66,22 @@ async fn create_user(conn: &mut AsyncPgConnection, username: &str) -> Result<Use
#[tokio::test]
async fn accounts_username() {
database_test(|db_pool| async move {
db_pool
.with_connection(|conn| {
async move {
let initial_insert = create_account(conn, "aumetra").await;
assert!(initial_insert.is_ok());
with_connection_panicky!(db_pool, |conn| {
let initial_insert = create_account(conn, "aumetra").await;
assert!(initial_insert.is_ok());
let case_mutation = create_account(conn, "AuMeTrA").await;
assert!(case_mutation.is_err());
let case_mutation = create_account(conn, "AuMeTrA").await;
assert!(case_mutation.is_err());
let unicode_mutation_1 = create_account(conn, "äumeträ").await;
assert!(unicode_mutation_1.is_err());
let unicode_mutation_1 = create_account(conn, "äumeträ").await;
assert!(unicode_mutation_1.is_err());
let unicode_mutation_2 = create_account(conn, "🅰umetr🅰").await;
assert!(unicode_mutation_2.is_err());
let unicode_mutation_2 = create_account(conn, "🅰umetr🅰").await;
assert!(unicode_mutation_2.is_err());
let unicode_case_mutation = create_account(conn, "🅰UMETR🅰").await;
assert!(unicode_case_mutation.is_err());
Result::Ok(())
}
.scoped()
})
.await
.unwrap();
let unicode_case_mutation = create_account(conn, "🅰UMETR🅰").await;
assert!(unicode_case_mutation.is_err());
});
})
.await;
}
@ -96,30 +89,22 @@ async fn accounts_username() {
#[tokio::test]
async fn users_username() {
database_test(|db_pool| async move {
db_pool
.with_connection(|conn| {
async move {
let initial_insert = create_user(conn, "aumetra").await;
assert!(initial_insert.is_ok());
with_connection_panicky!(db_pool, |conn| {
let initial_insert = create_user(conn, "aumetra").await;
assert!(initial_insert.is_ok());
let case_mutation = create_user(conn, "AuMeTrA").await;
assert!(case_mutation.is_err());
let case_mutation = create_user(conn, "AuMeTrA").await;
assert!(case_mutation.is_err());
let unicode_mutation_1 = create_user(conn, "äumeträ").await;
assert!(unicode_mutation_1.is_err());
let unicode_mutation_1 = create_user(conn, "äumeträ").await;
assert!(unicode_mutation_1.is_err());
let unicode_mutation_2 = create_user(conn, "🅰umetr🅰").await;
assert!(unicode_mutation_2.is_err());
let unicode_mutation_2 = create_user(conn, "🅰umetr🅰").await;
assert!(unicode_mutation_2.is_err());
let unicode_case_mutation = create_user(conn, "🅰UMETR🅰").await;
assert!(unicode_case_mutation.is_err());
Result::Ok(())
}
.scoped()
})
.await
.unwrap();
let unicode_case_mutation = create_user(conn, "🅰UMETR🅰").await;
assert!(unicode_case_mutation.is_err());
});
})
.await;
}

View File

@ -15,7 +15,7 @@ diesel = "2.1.5"
diesel-async = "0.4.1"
kitsune-db = { path = "../kitsune-db" }
kitsune-url = { path = "../kitsune-url" }
lettre = { version = "0.11.5", default-features = false, features = [
lettre = { version = "0.11.6", default-features = false, features = [
"builder",
"hostname",
"pool",
@ -24,13 +24,11 @@ lettre = { version = "0.11.5", default-features = false, features = [
"tokio1-rustls-tls",
"tracing",
] }
miette = "7.2.0"
mrml = { version = "3.1.3", default-features = false, features = [
"orderedmap",
"parse",
"render",
] }
scoped-futures = "0.1.3"
speedy-uuid = { path = "../../lib/speedy-uuid" }
thiserror = "1.0.58"
typed-builder = "0.18.1"

View File

@ -1,15 +1,11 @@
use diesel_async::pooled_connection::bb8::RunError as DatabasePoolError;
use miette::Diagnostic;
use std::{
error::Error as StdError,
fmt::{Debug, Display},
};
use std::{error::Error as StdError, fmt::Debug};
use thiserror::Error;
pub type BoxError = Box<dyn StdError + Send + Sync>;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Diagnostic, Error)]
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Address(#[from] lettre::address::AddressError),
@ -35,15 +31,3 @@ pub enum Error {
#[error(transparent)]
Rendering(#[from] mrml::prelude::render::Error),
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}

View File

@ -1,10 +1,9 @@
use crate::{error::Result, mails::confirm_account::ConfirmAccount, MailSender};
use diesel::{ExpressionMethods, NullableExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_db::{function::now, model::user::User, schema::users, PgPool};
use kitsune_db::{function::now, model::user::User, schema::users, with_connection, PgPool};
use kitsune_url::UrlService;
use lettre::{AsyncSmtpTransport, Tokio1Executor};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -27,31 +26,27 @@ impl Mailing {
}
pub async fn mark_as_confirmed(&self, user_id: Uuid) -> Result<()> {
self.db_pool
.with_connection(|db_conn| {
diesel::update(users::table.find(user_id))
.set(users::confirmed_at.eq(now().nullable()))
.execute(db_conn)
.scoped()
})
.await?;
with_connection!(self.db_pool, |db_conn| {
diesel::update(users::table.find(user_id))
.set(users::confirmed_at.eq(now().nullable()))
.execute(db_conn)
.await
})?;
Ok(())
}
pub async fn mark_as_confirmed_by_token(&self, confirmation_token: &str) -> Result<()> {
self.db_pool
.with_connection(|db_conn| {
diesel::update(
users::table
.filter(users::confirmation_token.eq(confirmation_token))
.filter(users::confirmed_at.is_null()),
)
.set(users::confirmed_at.eq(now().nullable()))
.execute(db_conn)
.scoped()
})
.await?;
with_connection!(self.db_pool, |db_conn| {
diesel::update(
users::table
.filter(users::confirmation_token.eq(confirmation_token))
.filter(users::confirmed_at.is_null()),
)
.set(users::confirmed_at.eq(now().nullable()))
.execute(db_conn)
.await
})?;
Ok(())
}

View File

@ -1,5 +1,5 @@
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::{pooled_connection::bb8, scoped_futures::ScopedFutureExt, RunQueryDsl};
use diesel_async::{pooled_connection::bb8, RunQueryDsl};
use embed_sdk::EmbedWithExpire;
use http::{Method, Request};
use iso8601_timestamp::Timestamp;
@ -7,13 +7,13 @@ use kitsune_db::{
json::Json,
model::link_preview::{ConflictLinkPreviewChangeset, LinkPreview, NewLinkPreview},
schema::link_previews,
PgPool,
with_connection, PgPool,
};
use kitsune_http_client::Client as HttpClient;
use once_cell::sync::Lazy;
use scraper::{Html, Selector};
use smol_str::SmolStr;
use std::fmt::{Debug, Display};
use std::fmt::Debug;
use typed_builder::TypedBuilder;
pub use embed_sdk;
@ -46,18 +46,6 @@ pub enum Error {
Pool(#[from] bb8::RunError),
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}
#[derive(Clone, TypedBuilder)]
pub struct Client {
db_pool: PgPool,
@ -83,19 +71,13 @@ impl Client {
}
pub async fn fetch_embed(&self, url: &str) -> Result<LinkPreview<Embed>> {
let embed_data = self
.db_pool
.with_connection(|db_conn| {
async move {
link_previews::table
.find(url)
.get_result::<LinkPreview<Embed>>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let embed_data = with_connection!(self.db_pool, |db_conn| {
link_previews::table
.find(url)
.get_result::<LinkPreview<Embed>>(db_conn)
.await
.optional()
})?;
if let Some(data) = embed_data {
if data.expires_at > Timestamp::now_utc() {
@ -112,25 +94,22 @@ impl Client {
let response = HttpClient::execute(&self.http_client, request).await?;
let (expires_at, embed_data): EmbedWithExpire = response.json().await?;
let embed_data = self
.db_pool
.with_connection(|db_conn| {
diesel::insert_into(link_previews::table)
.values(NewLinkPreview {
url,
embed_data: Json(&embed_data),
expires_at,
})
.on_conflict(link_previews::url)
.do_update()
.set(ConflictLinkPreviewChangeset {
embed_data: Json(&embed_data),
expires_at,
})
.get_result(db_conn)
.scoped()
})
.await?;
let embed_data = with_connection!(self.db_pool, |db_conn| {
diesel::insert_into(link_previews::table)
.values(NewLinkPreview {
url,
embed_data: Json(&embed_data),
expires_at,
})
.on_conflict(link_previews::url)
.do_update()
.set(ConflictLinkPreviewChangeset {
embed_data: Json(&embed_data),
expires_at,
})
.get_result(db_conn)
.await
})?;
Ok(embed_data)
}

View File

@ -9,7 +9,6 @@ license.workspace = true
globset = "0.4.14"
kitsune-config = { path = "../kitsune-config" }
kitsune-type = { path = "../kitsune-type" }
miette = "7.2.0"
thiserror = "1.0.58"
url = "2.5.0"

View File

@ -1,9 +1,8 @@
use miette::Diagnostic;
use thiserror::Error;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Diagnostic, Error)]
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Glob(#[from] globset::Error),

View File

@ -48,7 +48,7 @@ tower-http = { version = "0.5.2", features = [
] }
[dev-dependencies]
tokio = { version = "1.36.0", features = ["macros", "rt"] }
tokio = { version = "1.37.0", features = ["macros", "rt"] }
[lints]
workspace = true

View File

@ -10,12 +10,11 @@ athena = { path = "../../lib/athena" }
derive_more = { version = "1.0.0-beta.6", features = ["from"] }
diesel = "2.1.5"
diesel-async = "0.4.1"
eyre = "0.6.12"
futures-util = "0.3.30"
kitsune-core = { path = "../kitsune-core" }
kitsune-db = { path = "../kitsune-db" }
kitsune-email = { path = "../kitsune-email" }
miette = "7.2.0"
scoped-futures = "0.1.3"
serde = { version = "1.0.197", features = ["derive"] }
speedy-uuid = { path = "../../lib/speedy-uuid" }
tracing = "0.1.40"

View File

@ -2,8 +2,7 @@ use crate::{JobRunnerContext, Runnable};
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::follower::Follow, schema::accounts_follows};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::follower::Follow, schema::accounts_follows, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -14,32 +13,23 @@ pub struct DeliverAccept {
impl Runnable for DeliverAccept {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(follow_id = %self.follow_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let follow = ctx
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(ctx.db_pool, |db_conn| {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
})?;
let Some(follow) = follow else {
return Ok(());
};
ctx.deliverer
.deliver(Action::AcceptFollow(follow))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::AcceptFollow(follow)).await?;
Ok(())
}

View File

@ -3,8 +3,7 @@ use athena::Runnable;
use diesel::{OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::post::Post, schema::posts};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::post::Post, schema::posts, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -15,33 +14,24 @@ pub struct DeliverCreate {
impl Runnable for DeliverCreate {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(post_id = %self.post_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let post = ctx
.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.find(self.post_id)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let post = with_connection!(ctx.db_pool, |db_conn| {
posts::table
.find(self.post_id)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.await
.optional()
})?;
let Some(post) = post else {
return Ok(());
};
ctx.deliverer
.deliver(Action::Create(post))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::Create(post)).await?;
Ok(())
}

View File

@ -3,8 +3,7 @@ use athena::Runnable;
use diesel::{OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::post::Post, schema::posts};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::post::Post, schema::posts, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -15,41 +14,30 @@ pub struct DeliverDelete {
impl Runnable for DeliverDelete {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(post_id = %self.post_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let post = ctx
.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.find(self.post_id)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let post = with_connection!(ctx.db_pool, |db_conn| {
posts::table
.find(self.post_id)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.await
.optional()
})?;
let Some(post) = post else {
return Ok(());
};
ctx.deliverer
.deliver(Action::Delete(post))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::Delete(post)).await?;
ctx.db_pool
.with_connection(|db_conn| {
diesel::delete(posts::table.find(self.post_id))
.execute(db_conn)
.scoped()
})
.await?;
with_connection!(ctx.db_pool, |db_conn| {
diesel::delete(posts::table.find(self.post_id))
.execute(db_conn)
.await
})?;
Ok(())
}

View File

@ -3,8 +3,7 @@ use athena::Runnable;
use diesel::QueryDsl;
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::favourite::Favourite, schema::posts_favourites};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::favourite::Favourite, schema::posts_favourites, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -15,24 +14,18 @@ pub struct DeliverFavourite {
impl Runnable for DeliverFavourite {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(favourite_id = %self.favourite_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let favourite = ctx
.db_pool
.with_connection(|db_conn| {
posts_favourites::table
.find(self.favourite_id)
.get_result::<Favourite>(db_conn)
.scoped()
})
.await?;
let favourite = with_connection!(ctx.db_pool, |db_conn| {
posts_favourites::table
.find(self.favourite_id)
.get_result::<Favourite>(db_conn)
.await
})?;
ctx.deliverer
.deliver(Action::Favourite(favourite))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::Favourite(favourite)).await?;
Ok(())
}

View File

@ -3,8 +3,7 @@ use athena::Runnable;
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::follower::Follow, schema::accounts_follows};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::follower::Follow, schema::accounts_follows, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -15,32 +14,23 @@ pub struct DeliverFollow {
impl Runnable for DeliverFollow {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(follow_id = %self.follow_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let follow = ctx
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(ctx.db_pool, |db_conn| {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
})?;
let Some(follow) = follow else {
return Ok(());
};
ctx.deliverer
.deliver(Action::Follow(follow))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::Follow(follow)).await?;
Ok(())
}

View File

@ -2,8 +2,7 @@ use crate::{JobRunnerContext, Runnable};
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::follower::Follow, schema::accounts_follows};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::follower::Follow, schema::accounts_follows, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -14,40 +13,29 @@ pub struct DeliverReject {
impl Runnable for DeliverReject {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(follow_id = %self.follow_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let follow = ctx
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(ctx.db_pool, |db_conn| {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
})?;
let Some(follow) = follow else {
return Ok(());
};
ctx.deliverer
.deliver(Action::RejectFollow(follow))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::RejectFollow(follow)).await?;
ctx.db_pool
.with_connection(|db_conn| {
diesel::delete(accounts_follows::table.find(self.follow_id))
.execute(db_conn)
.scoped()
})
.await?;
with_connection!(ctx.db_pool, |db_conn| {
diesel::delete(accounts_follows::table.find(self.follow_id))
.execute(db_conn)
.await
})?;
Ok(())
}

View File

@ -3,8 +3,7 @@ use athena::Runnable;
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::favourite::Favourite, schema::posts_favourites};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::favourite::Favourite, schema::posts_favourites, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -15,23 +14,17 @@ pub struct DeliverUnfavourite {
impl Runnable for DeliverUnfavourite {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
#[instrument(skip_all, fields(favourite_id = %self.favourite_id))]
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let favourite = ctx
.db_pool
.with_connection(|db_conn| {
async move {
posts_favourites::table
.find(self.favourite_id)
.get_result::<Favourite>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let favourite = with_connection!(ctx.db_pool, |db_conn| {
posts_favourites::table
.find(self.favourite_id)
.get_result::<Favourite>(db_conn)
.await
.optional()
})?;
let Some(favourite) = favourite else {
return Ok(());
@ -39,17 +32,14 @@ impl Runnable for DeliverUnfavourite {
ctx.deliverer
.deliver(Action::Unfavourite(favourite))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.db_pool
.with_connection(|db_conn| {
diesel::delete(posts_favourites::table.find(self.favourite_id))
.execute(db_conn)
.scoped()
})
.await?;
with_connection!(ctx.db_pool, |db_conn| {
diesel::delete(posts_favourites::table.find(self.favourite_id))
.execute(db_conn)
.await
})?;
Ok(())
}
}

View File

@ -3,8 +3,7 @@ use athena::Runnable;
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_core::traits::deliverer::Action;
use kitsune_db::{model::follower::Follow, schema::accounts_follows};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::follower::Follow, schema::accounts_follows, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -15,39 +14,28 @@ pub struct DeliverUnfollow {
impl Runnable for DeliverUnfollow {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let follow = ctx
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(ctx.db_pool, |db_conn| {
accounts_follows::table
.find(self.follow_id)
.get_result::<Follow>(db_conn)
.await
.optional()
})?;
let Some(follow) = follow else {
return Ok(());
};
ctx.deliverer
.deliver(Action::Unfollow(follow))
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(Action::Unfollow(follow)).await?;
ctx.db_pool
.with_connection(|db_conn| {
diesel::delete(accounts_follows::table.find(self.follow_id))
.execute(db_conn)
.scoped()
})
.await?;
with_connection!(ctx.db_pool, |db_conn| {
diesel::delete(accounts_follows::table.find(self.follow_id))
.execute(db_conn)
.await
})?;
Ok(())
}

View File

@ -6,8 +6,8 @@ use kitsune_core::traits::deliverer::Action;
use kitsune_db::{
model::{account::Account, post::Post},
schema::{accounts, posts},
with_connection,
};
use scoped_futures::ScopedFutureExt;
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -25,25 +25,19 @@ pub struct DeliverUpdate {
impl Runnable for DeliverUpdate {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let action = match self.entity {
UpdateEntity::Account => {
let account = ctx
.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.find(self.id)
.select(Account::as_select())
.get_result(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let account = with_connection!(ctx.db_pool, |db_conn| {
accounts::table
.find(self.id)
.select(Account::as_select())
.get_result(db_conn)
.await
.optional()
})?;
let Some(account) = account else {
return Ok(());
@ -52,20 +46,14 @@ impl Runnable for DeliverUpdate {
Action::UpdateAccount(account)
}
UpdateEntity::Status => {
let post = ctx
.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.find(self.id)
.select(Post::as_select())
.get_result(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let post = with_connection!(ctx.db_pool, |db_conn| {
posts::table
.find(self.id)
.select(Post::as_select())
.get_result(db_conn)
.await
.optional()
})?;
let Some(post) = post else {
return Ok(());
@ -75,10 +63,7 @@ impl Runnable for DeliverUpdate {
}
};
ctx.deliverer
.deliver(action)
.await
.map_err(|err| miette::Report::new_boxed(err.into()))?;
ctx.deliverer.deliver(action).await?;
Ok(())
}

View File

@ -19,11 +19,9 @@ use kitsune_db::{
json::Json,
model::job_context::{JobContext, NewJobContext},
schema::job_context,
PgPool,
with_connection, PgPool,
};
use kitsune_email::MailingService;
use miette::IntoDiagnostic;
use scoped_futures::ScopedFutureExt;
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -57,7 +55,7 @@ pub enum Job {
impl Runnable for Job {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
match self {
@ -89,37 +87,32 @@ impl KitsuneContextRepo {
impl JobContextRepository for KitsuneContextRepo {
type JobContext = Job;
type Error = miette::Report;
type Error = eyre::Report;
type Stream = BoxStream<'static, Result<(Uuid, Self::JobContext), Self::Error>>;
async fn fetch_context<I>(&self, job_ids: I) -> Result<Self::Stream, Self::Error>
where
I: Iterator<Item = Uuid> + Send + 'static,
{
let stream = self
.db_pool
.with_connection(|conn| {
job_context::table
.filter(job_context::id.eq_any(job_ids))
.load_stream::<JobContext<Job>>(conn)
.scoped()
})
.await?;
let stream = with_connection!(self.db_pool, |conn| {
job_context::table
.filter(job_context::id.eq_any(job_ids))
.load_stream::<JobContext<Job>>(conn)
.await
})?;
Ok(stream
.map_ok(|ctx| (ctx.id, ctx.context.0))
.map(IntoDiagnostic::into_diagnostic)
.map_err(eyre::Report::from)
.boxed())
}
async fn remove_context(&self, job_id: Uuid) -> Result<(), Self::Error> {
self.db_pool
.with_connection(|conn| {
diesel::delete(job_context::table.find(job_id))
.execute(conn)
.scoped()
})
.await?;
with_connection!(self.db_pool, |conn| {
diesel::delete(job_context::table.find(job_id))
.execute(conn)
.await
})?;
Ok(())
}
@ -129,17 +122,15 @@ impl JobContextRepository for KitsuneContextRepo {
job_id: Uuid,
context: Self::JobContext,
) -> Result<(), Self::Error> {
self.db_pool
.with_connection(|conn| {
diesel::insert_into(job_context::table)
.values(NewJobContext {
id: job_id,
context: Json(context),
})
.execute(conn)
.scoped()
})
.await?;
with_connection!(self.db_pool, |conn| {
diesel::insert_into(job_context::table)
.values(NewJobContext {
id: job_id,
context: Json(context),
})
.execute(conn)
.await
})?;
Ok(())
}

View File

@ -2,8 +2,7 @@ use crate::JobRunnerContext;
use athena::Runnable;
use diesel::{QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use kitsune_db::{model::user::User, schema::users};
use scoped_futures::ScopedFutureExt;
use kitsune_db::{model::user::User, schema::users, with_connection};
use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
@ -14,7 +13,7 @@ pub struct SendConfirmationMail {
impl Runnable for SendConfirmationMail {
type Context = JobRunnerContext;
type Error = miette::Report;
type Error = eyre::Report;
async fn run(&self, ctx: &Self::Context) -> Result<(), Self::Error> {
let mailing_service = &ctx.service.mailing;
@ -24,16 +23,13 @@ impl Runnable for SendConfirmationMail {
mailing_service.mark_as_confirmed(self.user_id).await?;
}
let user = ctx
.db_pool
.with_connection(|db_conn| {
users::table
.find(self.user_id)
.select(User::as_select())
.get_result(db_conn)
.scoped()
})
.await?;
let user = with_connection!(ctx.db_pool, |db_conn| {
users::table
.find(self.user_id)
.select(User::as_select())
.get_result(db_conn)
.await
})?;
mailing_service.send_confirmation_email(&user).await?;

View File

@ -20,13 +20,12 @@ kitsune-type = { path = "../kitsune-type" }
kitsune-url = { path = "../kitsune-url" }
kitsune-util = { path = "../kitsune-util" }
mime = "0.3.17"
scoped-futures = "0.1.3"
serde = "1.0.197"
simd-json = "0.13.9"
smol_str = "0.2.1"
speedy-uuid = { path = "../../lib/speedy-uuid" }
thiserror = "1.0.58"
tokio = { version = "1.36.0", features = ["rt"] }
tokio = { version = "1.37.0", features = ["rt"] }
tracing = "0.1.40"
typed-builder = "0.18.1"

View File

@ -1,6 +1,5 @@
use std::fmt::{Debug, Display};
use diesel_async::pooled_connection::bb8;
use std::fmt::Debug;
pub type Result<T, E = Error> = std::result::Result<T, E>;
@ -21,15 +20,3 @@ pub enum Error {
#[error(transparent)]
Service(#[from] kitsune_service::error::Error),
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}

View File

@ -24,7 +24,7 @@ use kitsune_db::{
accounts, accounts_follows, custom_emojis, media_attachments, notifications, posts,
posts_favourites,
},
PgPool,
with_connection, PgPool,
};
use kitsune_embed::Client as EmbedClient;
use kitsune_embed::{embed_sdk::EmbedType, Embed};
@ -40,7 +40,6 @@ use kitsune_type::mastodon::{
use kitsune_url::UrlService;
use kitsune_util::try_join;
use mime::Mime;
use scoped_futures::ScopedFutureExt;
use serde::{de::DeserializeOwned, Serialize};
use smol_str::SmolStr;
use speedy_uuid::Uuid;
@ -78,30 +77,25 @@ impl IntoMastodon for DbAccount {
}
async fn into_mastodon(self, state: MapperState<'_>) -> Result<Self::Output> {
let (statuses_count, followers_count, following_count) = state
.db_pool
.with_connection(|db_conn| {
async {
let statuses_count_fut = posts::table
.filter(posts::account_id.eq(self.id))
.count()
.get_result::<i64>(db_conn);
let (statuses_count, followers_count, following_count) =
with_connection!(state.db_pool, |db_conn| {
let statuses_count_fut = posts::table
.filter(posts::account_id.eq(self.id))
.count()
.get_result::<i64>(db_conn);
let followers_count_fut = accounts_follows::table
.filter(accounts_follows::account_id.eq(self.id))
.count()
.get_result::<i64>(db_conn);
let followers_count_fut = accounts_follows::table
.filter(accounts_follows::account_id.eq(self.id))
.count()
.get_result::<i64>(db_conn);
let following_count_fut = accounts_follows::table
.filter(accounts_follows::follower_id.eq(self.id))
.count()
.get_result::<i64>(db_conn);
let following_count_fut = accounts_follows::table
.filter(accounts_follows::follower_id.eq(self.id))
.count()
.get_result::<i64>(db_conn);
try_join!(statuses_count_fut, followers_count_fut, following_count_fut)
}
.scoped()
})
.await?;
try_join!(statuses_count_fut, followers_count_fut, following_count_fut)
})?;
let mut acct = self.username.clone();
if !self.local {
@ -163,39 +157,34 @@ impl IntoMastodon for (&DbAccount, &DbAccount) {
async fn into_mastodon(self, state: MapperState<'_>) -> Result<Self::Output> {
let (requester, target) = self;
let ((following, follow_requested), followed_by) = state
.db_pool
.with_connection(|db_conn| {
async move {
let following_requested_fut = accounts_follows::table
.filter(
accounts_follows::account_id
.eq(target.id)
.and(accounts_follows::follower_id.eq(requester.id)),
)
.get_result::<Follow>(db_conn)
.map(OptionalExtension::optional)
.map_ok(|optional_follow| {
optional_follow.map_or((false, false), |follow| {
(follow.approved_at.is_some(), follow.approved_at.is_none())
})
});
let ((following, follow_requested), followed_by) =
with_connection!(state.db_pool, |db_conn| {
let following_requested_fut = accounts_follows::table
.filter(
accounts_follows::account_id
.eq(target.id)
.and(accounts_follows::follower_id.eq(requester.id)),
)
.get_result::<Follow>(db_conn)
.map(OptionalExtension::optional)
.map_ok(|optional_follow| {
optional_follow.map_or((false, false), |follow| {
(follow.approved_at.is_some(), follow.approved_at.is_none())
})
});
let followed_by_fut = accounts_follows::table
.filter(
accounts_follows::account_id
.eq(requester.id)
.and(accounts_follows::follower_id.eq(target.id)),
)
.count()
.get_result::<i64>(db_conn)
.map_ok(|count| count != 0);
let followed_by_fut = accounts_follows::table
.filter(
accounts_follows::account_id
.eq(requester.id)
.and(accounts_follows::follower_id.eq(target.id)),
)
.count()
.get_result::<i64>(db_conn)
.map_ok(|count| count != 0);
try_join!(following_requested_fut, followed_by_fut)
}
.scoped()
})
.await?;
try_join!(following_requested_fut, followed_by_fut)
})?;
Ok(Relationship {
id: target.id,
@ -223,16 +212,13 @@ impl IntoMastodon for DbMention {
}
async fn into_mastodon(self, state: MapperState<'_>) -> Result<Self::Output> {
let account: DbAccount = state
.db_pool
.with_connection(|db_conn| {
accounts::table
.find(self.account_id)
.select(DbAccount::as_select())
.get_result(db_conn)
.scoped()
})
.await?;
let account: DbAccount = with_connection!(state.db_pool, |db_conn| {
accounts::table
.find(self.account_id)
.select(DbAccount::as_select())
.get_result(db_conn)
.await
})?;
let mut acct = account.username.clone();
if !account.local {
@ -289,29 +275,23 @@ impl IntoMastodon for (&DbAccount, DbPost) {
async fn into_mastodon(self, state: MapperState<'_>) -> Result<Self::Output> {
let (account, post) = self;
let (favourited, reblogged) = state
.db_pool
.with_connection(|db_conn| {
async move {
let favourited_fut = posts_favourites::table
.filter(posts_favourites::account_id.eq(account.id))
.filter(posts_favourites::post_id.eq(post.id))
.count()
.get_result::<i64>(db_conn)
.map_ok(|count| count != 0);
let (favourited, reblogged) = with_connection!(state.db_pool, |db_conn| {
let favourited_fut = posts_favourites::table
.filter(posts_favourites::account_id.eq(account.id))
.filter(posts_favourites::post_id.eq(post.id))
.count()
.get_result::<i64>(db_conn)
.map_ok(|count| count != 0);
let reblogged_fut = posts::table
.filter(posts::account_id.eq(account.id))
.filter(posts::reposted_post_id.eq(post.id))
.count()
.get_result::<i64>(db_conn)
.map_ok(|count| count != 0);
let reblogged_fut = posts::table
.filter(posts::account_id.eq(account.id))
.filter(posts::reposted_post_id.eq(post.id))
.count()
.get_result::<i64>(db_conn)
.map_ok(|count| count != 0);
try_join!(favourited_fut, reblogged_fut)
}
.scoped()
})
.await?;
try_join!(favourited_fut, reblogged_fut)
})?;
let mut status = post.into_mastodon(state).await?;
status.favourited = favourited;
@ -341,62 +321,56 @@ impl IntoMastodon for DbPost {
media_attachments,
mentions_stream,
custom_emojis_stream,
) = state
.db_pool
.with_connection(|db_conn| {
async {
let account_fut = accounts::table
.find(self.account_id)
.select(DbAccount::as_select())
.get_result::<DbAccount>(db_conn)
) = with_connection!(state.db_pool, |db_conn| {
let account_fut = accounts::table
.find(self.account_id)
.select(DbAccount::as_select())
.get_result::<DbAccount>(db_conn)
.map_err(Error::from)
.and_then(|db_account| db_account.into_mastodon(state));
let reblog_count_fut = posts::table
.filter(posts::reposted_post_id.eq(self.id))
.count()
.get_result::<i64>(db_conn)
.map_err(Error::from);
let favourites_count_fut = DbFavourite::belonging_to(&self)
.count()
.get_result::<i64>(db_conn)
.map_err(Error::from);
let media_attachments_fut = DbPostMediaAttachment::belonging_to(&self)
.inner_join(media_attachments::table)
.select(DbMediaAttachment::as_select())
.load_stream::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|attachment_stream| {
attachment_stream
.map_err(Error::from)
.and_then(|db_account| db_account.into_mastodon(state));
.and_then(|attachment| attachment.into_mastodon(state))
.try_collect()
});
let reblog_count_fut = posts::table
.filter(posts::reposted_post_id.eq(self.id))
.count()
.get_result::<i64>(db_conn)
.map_err(Error::from);
let mentions_stream_fut = DbMention::belonging_to(&self)
.load_stream::<DbMention>(db_conn)
.map_err(Error::from);
let favourites_count_fut = DbFavourite::belonging_to(&self)
.count()
.get_result::<i64>(db_conn)
.map_err(Error::from);
let custom_emojis_stream_fut = DbPostCustomEmoji::belonging_to(&self)
.inner_join(custom_emojis::table.inner_join(media_attachments::table))
.select((DbCustomEmoji::as_select(), DbMediaAttachment::as_select()))
.load_stream::<(DbCustomEmoji, DbMediaAttachment)>(db_conn)
.map_err(Error::from);
let media_attachments_fut = DbPostMediaAttachment::belonging_to(&self)
.inner_join(media_attachments::table)
.select(DbMediaAttachment::as_select())
.load_stream::<DbMediaAttachment>(db_conn)
.map_err(Error::from)
.and_then(|attachment_stream| {
attachment_stream
.map_err(Error::from)
.and_then(|attachment| attachment.into_mastodon(state))
.try_collect()
});
let mentions_stream_fut = DbMention::belonging_to(&self)
.load_stream::<DbMention>(db_conn)
.map_err(Error::from);
let custom_emojis_stream_fut = DbPostCustomEmoji::belonging_to(&self)
.inner_join(custom_emojis::table.inner_join(media_attachments::table))
.select((DbCustomEmoji::as_select(), DbMediaAttachment::as_select()))
.load_stream::<(DbCustomEmoji, DbMediaAttachment)>(db_conn)
.map_err(Error::from);
try_join!(
account_fut,
reblog_count_fut,
favourites_count_fut,
media_attachments_fut,
mentions_stream_fut,
custom_emojis_stream_fut
)
}
.scoped()
})
.await?;
try_join!(
account_fut,
reblog_count_fut,
favourites_count_fut,
media_attachments_fut,
mentions_stream_fut,
custom_emojis_stream_fut
)
})?;
let link_preview = OptionFuture::from(
self.link_preview_url
@ -423,30 +397,24 @@ impl IntoMastodon for DbPost {
.try_collect()
.await?;
let reblog = state
.db_pool
.with_connection(|db_conn| {
async {
OptionFuture::from(
OptionFuture::from(self.reposted_post_id.map(|id| {
posts::table
.find(id)
.select(DbPost::as_select())
.get_result::<DbPost>(db_conn)
.map(OptionalExtension::optional)
}))
.await
.transpose()?
.flatten()
.map(|post| post.into_mastodon(state)), // This will allocate two database connections. Fuck.
)
.await
.transpose()
}
.scoped()
})
.await?
.map(Box::new);
let reblog = with_connection!(state.db_pool, |db_conn| {
OptionFuture::from(
OptionFuture::from(self.reposted_post_id.map(|id| {
posts::table
.find(id)
.select(DbPost::as_select())
.get_result::<DbPost>(db_conn)
.map(OptionalExtension::optional)
}))
.await
.transpose()?
.flatten()
.map(|post| post.into_mastodon(state)), // This will allocate two database connections. Fuck.
)
.await
.transpose()
})?
.map(Box::new);
let language = self.content_lang.to_639_1().map(str::to_string);
@ -581,9 +549,8 @@ impl IntoMastodon for DbNotification {
}
async fn into_mastodon(self, state: MapperState<'_>) -> Result<Self::Output> {
let (notification, account, status): (DbNotification, DbAccount, Option<DbPost>) = state
.db_pool
.with_connection(|mut db_conn| {
let (notification, account, status): (DbNotification, DbAccount, Option<DbPost>) =
with_connection!(state.db_pool, |db_conn| {
notifications::table
.filter(notifications::receiving_account_id.eq(self.receiving_account_id))
.inner_join(
@ -592,10 +559,9 @@ impl IntoMastodon for DbNotification {
)
.left_outer_join(posts::table)
.select(<(DbNotification, DbAccount, Option<DbPost>)>::as_select())
.get_result(&mut db_conn)
.scoped()
})
.await?;
.get_result(db_conn)
.await
})?;
let status = OptionFuture::from(status.map(|status| status.into_mastodon(state)))
.await

View File

@ -10,11 +10,11 @@ ahash = "0.8.11"
derive_more = { version = "1.0.0-beta.6", features = ["from"] }
futures-util = "0.3.30"
just-retry = { path = "../../lib/just-retry" }
pin-project-lite = "0.2.13"
pin-project-lite = "0.2.14"
redis = { version = "0.25.2", features = ["connection-manager", "tokio-comp"] }
serde = "1.0.197"
simd-json = "0.13.9"
tokio = { version = "1.36.0", features = ["macros", "rt", "sync"] }
tokio = { version = "1.37.0", features = ["macros", "rt", "sync"] }
tokio-stream = { version = "0.1.15", features = ["sync"] }
tracing = "0.1.40"

View File

@ -7,6 +7,7 @@ license.workspace = true
[dependencies]
async-trait = "0.1.79"
eyre = "0.6.12"
http-body-util = "0.1.1"
http-compat = { path = "../../lib/http-compat" }
hyper = { version = "1.2.0", default-features = false }
@ -16,11 +17,10 @@ metrics = "=0.22.0"
metrics-opentelemetry = { git = "https://github.com/aumetra/metrics-opentelemetry.git", rev = "95537b16370e595981e195be52f98ea5983a7a8e" }
metrics-tracing-context = "0.15.0"
metrics-util = "0.16.3"
miette = "7.2.0"
opentelemetry = { version = "0.22.0", default-features = false, features = [
"trace",
] }
opentelemetry-http = "0.11.0"
opentelemetry-http = "0.11.1"
opentelemetry-otlp = { version = "0.15.0", default-features = false, features = [
"grpc-tonic",
"http-proto",

View File

@ -1,11 +1,11 @@
use async_trait::async_trait;
use eyre::WrapErr;
use http_body_util::BodyExt;
use http_compat::Compat;
use kitsune_config::{open_telemetry::Transport, Configuration};
use metrics_opentelemetry::OpenTelemetryRecorder;
use metrics_tracing_context::{MetricsLayer, TracingContextLayer};
use metrics_util::layers::Layer as _;
use miette::{Context, IntoDiagnostic};
use opentelemetry::{
metrics::{noop::NoopMeterProvider, Meter, MeterProvider},
trace::{noop::NoopTracer, Tracer},
@ -67,18 +67,13 @@ macro_rules! build_exporter {
}};
}
fn initialise_logging<T>(tracer: T) -> miette::Result<()>
fn initialise_logging<T>(tracer: T) -> eyre::Result<()>
where
T: Tracer + PreSampledTracer + Send + Sync + 'static,
{
let env_filter = env::var("RUST_LOG")
.into_diagnostic()
.and_then(|targets| {
targets
.parse()
.into_diagnostic()
.wrap_err("Failed to parse RUST_LOG value")
})
.map_err(eyre::Report::from)
.and_then(|targets| targets.parse().wrap_err("Failed to parse RUST_LOG value"))
.unwrap_or_else(|_| Targets::default().with_default(LevelFilter::INFO));
let subscriber = Registry::default()
@ -89,22 +84,20 @@ where
let subscriber = subscriber.with(MetricsLayer::new());
tracing::subscriber::set_global_default(subscriber)
.into_diagnostic()
.wrap_err("Couldn't install the global tracing subscriber")?;
Ok(())
}
fn initialise_metrics(meter: Meter) -> miette::Result<()> {
fn initialise_metrics(meter: Meter) -> eyre::Result<()> {
let recorder = TracingContextLayer::all().layer(OpenTelemetryRecorder::new(meter));
metrics::set_global_recorder(recorder)
.into_diagnostic()
.wrap_err("Couldn't install the global metrics recorder")?;
Ok(())
}
pub fn initialise(app_name: &'static str, config: &Configuration) -> miette::Result<()> {
pub fn initialise(app_name: &'static str, config: &Configuration) -> eyre::Result<()> {
if let Some(ref opentelemetry_config) = config.opentelemetry {
let http_client = HttpClientAdapter {
inner: kitsune_http_client::Client::default(),
@ -120,8 +113,7 @@ pub fn initialise(app_name: &'static str, config: &Configuration) -> miette::Res
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(trace_exporter)
.install_batch(Tokio)
.into_diagnostic()?;
.install_batch(Tokio)?;
initialise_logging(tracer)?;
@ -135,8 +127,7 @@ pub fn initialise(app_name: &'static str, config: &Configuration) -> miette::Res
let meter_provider = opentelemetry_otlp::new_pipeline()
.metrics(Tokio)
.with_exporter(metrics_exporter)
.build()
.into_diagnostic()?;
.build()?;
initialise_metrics(meter_provider.meter(app_name))?;
} else {

View File

@ -6,12 +6,11 @@ version.workspace = true
license.workspace = true
[dependencies]
enum_dispatch = "0.3.12"
enum_dispatch = "0.3.13"
http = "1.1.0"
http-compat = { path = "../../lib/http-compat" }
kitsune-config = { path = "../kitsune-config" }
kitsune-http-client = { path = "../kitsune-http-client" }
miette = "7.2.0"
moka = { version = "0.12.5", features = ["future"] }
multiplex-pool = { path = "../../lib/multiplex-pool" }
once_cell = "1.19.0"

View File

@ -1,4 +1,3 @@
use miette::Diagnostic;
use openidconnect::{
core::CoreErrorResponseType, ClaimsVerificationError, DiscoveryError, RequestTokenError,
SigningError, StandardErrorResponse,
@ -7,7 +6,7 @@ use thiserror::Error;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Diagnostic, Error)]
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
ClaimsVerification(#[from] ClaimsVerificationError),

View File

@ -17,7 +17,7 @@ typed-builder = "0.18.1"
[dev-dependencies]
kitsune-test = { path = "../kitsune-test" }
tokio = { version = "1.36.0", features = ["macros", "rt"] }
tokio = { version = "1.37.0", features = ["macros", "rt"] }
[lints]
workspace = true

View File

@ -33,9 +33,9 @@ const fn s3_method_to_http(method: rusty_s3::Method) -> http::Method {
}
#[inline]
const fn http_method_by_value<'a, T: ?Sized>(_: &T) -> http::Method
const fn http_method_by_value<'a, T>(_: &T) -> http::Method
where
T: S3Action<'a>,
T: S3Action<'a> + ?Sized,
{
s3_method_to_http(T::METHOD)
}

View File

@ -12,12 +12,11 @@ ignored = ["isahc"] # To make `meilisearch` builds static
diesel = "2.1.5"
diesel-async = "0.4.1"
diesel_full_text_search = { version = "2.1.1", default-features = false }
enum_dispatch = "0.3.12"
enum_dispatch = "0.3.13"
futures-util = "0.3.30"
kitsune-config = { path = "../kitsune-config" }
kitsune-db = { path = "../kitsune-db" }
kitsune-language = { path = "../kitsune-language" }
miette = "7.2.0"
serde = { version = "1.0.197", features = ["derive"] }
speedy-uuid = { path = "../../lib/speedy-uuid" }
strum = { version = "0.26.2", features = ["derive"] }

View File

@ -1,9 +1,8 @@
use diesel_async::pooled_connection::bb8;
use miette::Diagnostic;
use std::fmt::{Debug, Display};
use std::fmt::Debug;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Database(#[from] diesel::result::Error),
@ -15,15 +14,3 @@ pub enum Error {
#[error(transparent)]
Meilisearch(#[from] meilisearch_sdk::errors::Error),
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}

View File

@ -1,6 +1,6 @@
use super::{Result, SearchBackend, SearchIndex, SearchItem, SearchResultReference};
use diesel::{ExpressionMethods, QueryDsl};
use diesel_async::{scoped_futures::ScopedFutureExt, RunQueryDsl};
use diesel_async::RunQueryDsl;
use diesel_full_text_search::{websearch_to_tsquery_with_search_config, TsVectorExtensions};
use futures_util::TryStreamExt;
use kitsune_config::language_detection::Configuration as LanguageDetectionConfig;
@ -9,7 +9,7 @@ use kitsune_db::{
lang::LanguageIsoCode,
model::post::Visibility,
schema::{accounts, posts},
PgPool,
with_connection, PgPool,
};
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -62,23 +62,17 @@ impl SearchBackend for SearchService {
query = query.filter(accounts::id.lt(max_id));
}
let results = self
.db_pool
.with_connection(move |db_conn| {
async move {
query
.limit(max_results as i64)
.offset(offset as i64)
.select(accounts::id)
.load_stream(db_conn)
.await?
.map_ok(|id| SearchResultReference { index, id })
.try_collect()
.await
}
.scoped()
})
.await?;
let results = with_connection!(self.db_pool, |db_conn| {
query
.limit(max_results as i64)
.offset(offset as i64)
.select(accounts::id)
.load_stream(db_conn)
.await?
.map_ok(|id| SearchResultReference { index, id })
.try_collect()
.await
})?;
Ok(results)
}
@ -94,27 +88,20 @@ impl SearchBackend for SearchService {
query = query.filter(posts::id.lt(max_id));
}
let results = self
.db_pool
.with_connection(|db_conn| {
async move {
query
.filter(
posts::visibility
.eq_any([Visibility::Public, Visibility::Unlisted]),
)
.limit(max_results as i64)
.offset(offset as i64)
.select(posts::id)
.load_stream(db_conn)
.await?
.map_ok(|id| SearchResultReference { index, id })
.try_collect()
.await
}
.scoped()
})
.await?;
let results = with_connection!(self.db_pool, |db_conn| {
query
.filter(
posts::visibility.eq_any([Visibility::Public, Visibility::Unlisted]),
)
.limit(max_results as i64)
.offset(offset as i64)
.select(posts::id)
.load_stream(db_conn)
.await?
.map_ok(|id| SearchResultReference { index, id })
.try_collect()
.await
})?;
Ok(results)
}

View File

@ -15,6 +15,7 @@ bytes = "1.6.0"
derive_builder = "0.20.0"
diesel = "2.1.5"
diesel-async = "0.4.1"
eyre = "0.6.12"
futures-util = "0.3.30"
garde = { version = "0.18.0", default-features = false, features = [
"derive",
@ -41,7 +42,6 @@ kitsune-search = { path = "../kitsune-search" }
kitsune-storage = { path = "../kitsune-storage" }
kitsune-url = { path = "../kitsune-url" }
kitsune-util = { path = "../kitsune-util" }
miette = "7.2.0"
mime = "0.3.17"
multiplex-pool = { path = "../../lib/multiplex-pool" }
password-hash = { version = "0.5.0", features = ["std"] }
@ -54,13 +54,12 @@ redis = { version = "0.25.2", default-features = false, features = [
] }
rsa = "0.9.6"
rusty-s3 = { version = "0.5.0", default-features = false }
scoped-futures = "0.1.3"
serde = "1.0.197"
simd-json = "0.13.9"
smol_str = "0.2.1"
speedy-uuid = { path = "../../lib/speedy-uuid" }
thiserror = "1.0.58"
tokio = { version = "1.36.0", features = ["macros", "sync"] }
tokio = { version = "1.37.0", features = ["macros", "sync"] }
tracing = "0.1.40"
typed-builder = "0.18.1"
url = "2.5.0"

View File

@ -7,8 +7,8 @@ use crate::error::{Error, Result};
use bytes::Bytes;
use derive_builder::Builder;
use diesel::{
BoolExpressionMethods, ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl,
SelectableHelper,
BoolExpressionMethods, ExpressionMethods, JoinOnDsl, NullableExpressionMethods,
OptionalExtension, QueryDsl, SelectableHelper,
};
use diesel_async::RunQueryDsl;
use futures_util::{Stream, TryStreamExt};
@ -16,17 +16,17 @@ use garde::Validate;
use iso8601_timestamp::Timestamp;
use kitsune_core::traits::{fetcher::AccountFetchOptions, Fetcher, Resolver};
use kitsune_db::{
function::now,
model::{
account::{Account, UpdateAccount},
follower::Follow as DbFollow,
follower::NewFollow,
follower::{Follow as DbFollow, NewFollow},
notification::NewNotification,
post::Post,
preference::Preferences,
},
post_permission_check::{PermissionCheck, PostPermissionCheckExt},
schema::{accounts, accounts_follows, accounts_preferences, notifications, posts},
PgPool,
with_connection, PgPool,
};
use kitsune_jobs::deliver::{
accept::DeliverAccept,
@ -37,7 +37,6 @@ use kitsune_jobs::deliver::{
};
use kitsune_url::UrlService;
use kitsune_util::{sanitize::CleanHtmlExt, try_join};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use std::sync::Arc;
use typed_builder::TypedBuilder;
@ -196,25 +195,19 @@ impl AccountService {
///
/// Tuple of two account models. First model is the account the followee account, the second model is the followed account
pub async fn follow(&self, follow: Follow, notify: bool) -> Result<(Account, Account)> {
let (account, follower) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account_fut = accounts::table
.find(follow.account_id)
.select(Account::as_select())
.get_result(db_conn);
let (account, follower) = with_connection!(self.db_pool, |db_conn| {
let account_fut = accounts::table
.find(follow.account_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(follow.follower_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(follow.follower_id)
.select(Account::as_select())
.get_result(db_conn);
try_join!(account_fut, follower_fut)
}
.scoped()
})
.await?;
try_join!(account_fut, follower_fut)
})?;
let id = Uuid::now_v7();
let url = self.url_service.follow_url(id);
@ -232,28 +225,22 @@ impl AccountService {
follow_model.approved_at = Some(Timestamp::now_utc());
}
let follow_id = self
.db_pool
.with_connection(|db_conn| {
diesel::insert_into(accounts_follows::table)
.values(follow_model)
.returning(accounts_follows::id)
.get_result(db_conn)
.scoped()
})
.await?;
let follow_id = with_connection!(self.db_pool, |db_conn| {
diesel::insert_into(accounts_follows::table)
.values(follow_model)
.returning(accounts_follows::id)
.get_result(db_conn)
.await
})?;
if account.local {
let preferences = self
.db_pool
.with_connection(|db_conn| {
accounts_preferences::table
.find(follow.account_id)
.select(Preferences::as_select())
.get_result(db_conn)
.scoped()
})
.await?;
let preferences = with_connection!(self.db_pool, |db_conn| {
accounts_preferences::table
.find(follow.account_id)
.select(Preferences::as_select())
.get_result(db_conn)
.await
})?;
if (preferences.notify_on_follow && !account.locked)
|| (preferences.notify_on_follow_request && account.locked)
@ -268,15 +255,13 @@ impl AccountService {
.follow(follower.id)
};
self.db_pool
.with_connection(|mut db_conn| {
diesel::insert_into(notifications::table)
.values(notification)
.on_conflict_do_nothing()
.execute(&mut db_conn)
.scoped()
})
.await?;
with_connection!(self.db_pool, |db_conn| {
diesel::insert_into(notifications::table)
.values(notification)
.on_conflict_do_nothing()
.execute(db_conn)
.await
})?;
}
}
@ -292,24 +277,18 @@ impl AccountService {
/// Get an account by its username and domain
pub async fn get(&self, get_user: GetUser<'_>) -> Result<Option<Account>> {
if let Some(domain) = get_user.domain {
let account = self
.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.filter(
accounts::username
.eq(get_user.username)
.and(accounts::domain.eq(domain)),
)
.select(Account::as_select())
.get_result(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let account = with_connection!(self.db_pool, |db_conn| {
accounts::table
.filter(
accounts::username
.eq(get_user.username)
.and(accounts::domain.eq(domain)),
)
.select(Account::as_select())
.get_result(db_conn)
.await
.optional()
})?;
if let Some(account) = account {
return Ok(Some(account));
@ -336,43 +315,33 @@ impl AccountService {
.await
.map_err(Error::Fetcher)
} else {
self.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.filter(
accounts::username
.eq(get_user.username)
.and(accounts::local.eq(true)),
)
.select(Account::as_select())
.first(db_conn)
.await
.optional()
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
accounts::table
.filter(
accounts::username
.eq(get_user.username)
.and(accounts::local.eq(true)),
)
.select(Account::as_select())
.first(db_conn)
.await
.optional()
})
.map_err(Error::from)
}
}
/// Get an account by its ID
pub async fn get_by_id(&self, account_id: Uuid) -> Result<Option<Account>> {
self.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.find(account_id)
.select(Account::as_select())
.get_result(db_conn)
.await
.optional()
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
accounts::table
.find(account_id)
.select(Account::as_select())
.get_result(db_conn)
.await
.optional()
})
.map_err(Error::from)
}
/// Get a stream of posts owned by the user
@ -408,14 +377,9 @@ impl AccountService {
query = query.filter(posts::id.gt(min_id)).order(posts::id.asc());
}
self.db_pool
.with_connection(|db_conn| {
async move {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
}.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
}
/// Undo the follow of an account
@ -424,43 +388,31 @@ impl AccountService {
///
/// Tuple of two account models. First account is the account that was being followed, second account is the account that was following
pub async fn unfollow(&self, unfollow: Unfollow) -> Result<(Account, Account)> {
let (account, follower) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account_fut = accounts::table
.find(unfollow.account_id)
.select(Account::as_select())
.get_result(db_conn);
let (account, follower) = with_connection!(self.db_pool, |db_conn| {
let account_fut = accounts::table
.find(unfollow.account_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(unfollow.follower_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(unfollow.follower_id)
.select(Account::as_select())
.get_result(db_conn);
try_join!(account_fut, follower_fut)
}
.scoped()
})
.await?;
try_join!(account_fut, follower_fut)
})?;
let follow = self
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.filter(
accounts_follows::account_id
.eq(account.id)
.and(accounts_follows::follower_id.eq(follower.id)),
)
.get_result::<DbFollow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(self.db_pool, |db_conn| {
accounts_follows::table
.filter(
accounts_follows::account_id
.eq(account.id)
.and(accounts_follows::follower_id.eq(follower.id)),
)
.get_result::<DbFollow>(db_conn)
.await
.optional()
})?;
if let Some(follow) = follow {
if !account.local {
@ -505,73 +457,52 @@ impl AccountService {
query = query.filter(accounts::id.lt(max_id));
}
self.db_pool
.with_connection(|db_conn| {
async move {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
.map_err(Error::from)
}
pub async fn accept_follow_request(
&self,
follow_request: FollowRequest,
) -> Result<Option<(Account, Account)>> {
let (account, follower) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account_fut = accounts::table
.find(follow_request.account_id)
.select(Account::as_select())
.get_result(db_conn);
let (account, follower) = with_connection!(self.db_pool, |db_conn| {
let account_fut = accounts::table
.find(follow_request.account_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(follow_request.follower_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(follow_request.follower_id)
.select(Account::as_select())
.get_result(db_conn);
try_join!(account_fut, follower_fut)
}
.scoped()
})
.await?;
try_join!(account_fut, follower_fut)
})?;
let follow = self
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.filter(
accounts_follows::account_id
.eq(account.id)
.and(accounts_follows::follower_id.eq(follower.id)),
)
.get_result::<DbFollow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(self.db_pool, |db_conn| {
accounts_follows::table
.filter(
accounts_follows::account_id
.eq(account.id)
.and(accounts_follows::follower_id.eq(follower.id)),
)
.get_result::<DbFollow>(db_conn)
.await
.optional()
})?;
if let Some(follow) = follow {
let now = Timestamp::now_utc();
self.db_pool
.with_connection(|db_conn| {
diesel::update(&follow)
.set((
accounts_follows::approved_at.eq(now),
accounts_follows::updated_at.eq(now),
))
.execute(db_conn)
.scoped()
})
.await?;
if let Some(ref follow) = follow {
with_connection!(self.db_pool, |db_conn| {
diesel::update(follow)
.set((
accounts_follows::approved_at.eq(now().nullable()),
accounts_follows::updated_at.eq(now()),
))
.execute(db_conn)
.await
})?;
if !account.local {
self.job_service
@ -595,49 +526,37 @@ impl AccountService {
&self,
follow_request: FollowRequest,
) -> Result<Option<(Account, Account)>> {
let (account, follower) = self
.db_pool
.with_connection(|db_conn| {
async move {
let account_fut = accounts::table
.find(follow_request.account_id)
.select(Account::as_select())
.get_result(db_conn);
let (account, follower) = with_connection!(self.db_pool, |db_conn| {
let account_fut = accounts::table
.find(follow_request.account_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(follow_request.follower_id)
.select(Account::as_select())
.get_result(db_conn);
let follower_fut = accounts::table
.find(follow_request.follower_id)
.select(Account::as_select())
.get_result(db_conn);
try_join!(account_fut, follower_fut)
}
.scoped()
})
.await?;
try_join!(account_fut, follower_fut)
})?;
let follow = self
.db_pool
.with_connection(|db_conn| {
async move {
accounts_follows::table
.filter(
accounts_follows::account_id
.eq(account.id)
.and(accounts_follows::follower_id.eq(follower.id)),
)
.get_result::<DbFollow>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let follow = with_connection!(self.db_pool, |db_conn| {
accounts_follows::table
.filter(
accounts_follows::account_id
.eq(account.id)
.and(accounts_follows::follower_id.eq(follower.id)),
)
.get_result::<DbFollow>(db_conn)
.await
.optional()
})?;
if let Some(follow) = follow {
if account.local {
self.db_pool
.with_connection(|db_conn| diesel::delete(&follow).execute(db_conn).scoped())
.await?;
with_connection!(self.db_pool, |db_conn| {
diesel::delete(&follow).execute(db_conn).await
})?;
} else {
self.job_service
.enqueue(
@ -704,16 +623,13 @@ impl AccountService {
};
}
let updated_account: Account = self
.db_pool
.with_connection(|db_conn| {
diesel::update(accounts::table.find(update.account_id))
.set(changeset)
.returning(Account::as_returning())
.get_result(db_conn)
.scoped()
})
.await?;
let updated_account: Account = with_connection!(self.db_pool, |db_conn| {
diesel::update(accounts::table.find(update.account_id))
.set(changeset)
.returning(Account::as_returning())
.get_result(db_conn)
.await
})?;
self.job_service
.enqueue(

View File

@ -10,12 +10,11 @@ use kitsune_core::consts::{MAX_MEDIA_DESCRIPTION_LENGTH, USER_AGENT};
use kitsune_db::{
model::media_attachment::{MediaAttachment, NewMediaAttachment, UpdateMediaAttachment},
schema::media_attachments,
PgPool,
with_connection, PgPool,
};
use kitsune_http_client::Client;
use kitsune_storage::{AnyStorageBackend, BoxError, StorageBackend};
use kitsune_url::UrlService;
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -93,15 +92,10 @@ pub struct AttachmentService {
impl AttachmentService {
pub async fn get_by_id(&self, id: Uuid) -> Result<MediaAttachment> {
self.db_pool
.with_connection(|db_conn| {
media_attachments::table
.find(id)
.get_result(db_conn)
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
media_attachments::table.find(id).get_result(db_conn).await
})
.map_err(Error::from)
}
/// Get the URL to an attachment
@ -161,21 +155,19 @@ impl AttachmentService {
};
}
self.db_pool
.with_connection(|db_conn| {
diesel::update(
media_attachments::table.filter(
media_attachments::id
.eq(update.attachment_id)
.and(media_attachments::account_id.eq(update.account_id)),
),
)
.set(changeset)
.get_result(db_conn)
.scoped()
})
with_connection!(self.db_pool, |db_conn| {
diesel::update(
media_attachments::table.filter(
media_attachments::id
.eq(update.attachment_id)
.and(media_attachments::account_id.eq(update.account_id)),
),
)
.set(changeset)
.get_result(db_conn)
.await
.map_err(Error::from)
})
.map_err(Error::from)
}
pub async fn upload<S>(&self, upload: Upload<S>) -> Result<MediaAttachment>
@ -217,23 +209,20 @@ impl AttachmentService {
}
.map_err(Error::Storage)?;
let media_attachment = self
.db_pool
.with_connection(|db_conn| {
diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: Uuid::now_v7(),
content_type: upload.content_type.as_str(),
account_id: upload.account_id,
description: upload.description.as_deref(),
blurhash: None,
file_path: Some(upload.path.as_str()),
remote_url: None,
})
.get_result(db_conn)
.scoped()
})
.await?;
let media_attachment = with_connection!(self.db_pool, |db_conn| {
diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: Uuid::now_v7(),
content_type: upload.content_type.as_str(),
account_id: upload.account_id,
description: upload.description.as_deref(),
blurhash: None,
file_path: Some(upload.path.as_str()),
remote_url: None,
})
.get_result(db_conn)
.await
})?;
Ok(media_attachment)
}
@ -241,12 +230,9 @@ impl AttachmentService {
#[cfg(test)]
mod test {
use crate::{
attachment::{AttachmentService, Upload},
error::Error,
};
use crate::attachment::{AttachmentService, Upload};
use bytes::{Bytes, BytesMut};
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use futures_util::{future, pin_mut, stream, StreamExt};
use http::{Request, Response};
use http_body_util::Empty;
@ -261,12 +247,12 @@ mod test {
media_attachment::MediaAttachment,
},
schema::accounts,
with_connection_panicky,
};
use kitsune_http_client::Client;
use kitsune_storage::fs::Storage;
use kitsune_test::database_test;
use kitsune_url::UrlService;
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use std::convert::Infallible;
use tempfile::TempDir;
@ -277,12 +263,10 @@ mod test {
database_test(|db_pool| async move {
let client = Client::builder().service(service_fn(handle));
let account_id = db_pool
.with_connection(|db_conn| {
async move { Ok::<_, miette::Report>(prepare_db(db_conn).await) }.scoped()
})
.await
.unwrap();
let account_id = with_connection_panicky!(db_pool, |db_conn| {
Ok::<_, eyre::Report>(prepare_db(db_conn).await)
})
.unwrap();
let temp_dir = TempDir::new().unwrap();
let storage = Storage::new(temp_dir.path().to_owned());
@ -350,38 +334,32 @@ mod test {
async fn prepare_db(db_conn: &mut AsyncPgConnection) -> Uuid {
// Create a local user `@alice`
db_conn
.transaction(|tx| {
async move {
let account_id = Uuid::now_v7();
diesel::insert_into(accounts::table)
.values(NewAccount {
id: account_id,
display_name: None,
username: "alice",
locked: false,
note: None,
local: true,
domain: "example.com",
actor_type: ActorType::Person,
url: "https://example.com/users/alice",
featured_collection_url: None,
followers_url: None,
following_url: None,
inbox_url: None,
outbox_url: None,
shared_inbox_url: None,
public_key_id: "https://example.com/users/alice#main-key",
public_key: "",
created_at: None,
})
.execute(tx)
.await?;
Ok::<_, Error>(account_id)
}
.scope_boxed()
let account_id = Uuid::now_v7();
diesel::insert_into(accounts::table)
.values(NewAccount {
id: account_id,
display_name: None,
username: "alice",
locked: false,
note: None,
local: true,
domain: "example.com",
actor_type: ActorType::Person,
url: "https://example.com/users/alice",
featured_collection_url: None,
followers_url: None,
following_url: None,
inbox_url: None,
outbox_url: None,
shared_inbox_url: None,
public_key_id: "https://example.com/users/alice#main-key",
public_key: "",
created_at: None,
})
.execute(db_conn)
.await
.unwrap()
.unwrap();
account_id
}
}

View File

@ -13,10 +13,9 @@ use kitsune_core::consts::MAX_EMOJI_SHORTCODE_LENGTH;
use kitsune_db::{
model::{custom_emoji::CustomEmoji, media_attachment::MediaAttachment},
schema::{custom_emojis, media_attachments, posts, posts_custom_emojis},
PgPool,
with_connection, PgPool,
};
use kitsune_url::UrlService;
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -80,12 +79,10 @@ impl CustomEmojiService {
query = query.filter(custom_emojis::domain.eq(domain));
}
self.db_pool
.with_connection(|db_conn| {
async move { query.first(db_conn).await.optional() }.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
query.first(db_conn).await.optional()
})
.map_err(Error::from)
}
pub async fn get_by_id(&self, id: Uuid) -> Result<CustomEmoji> {
@ -93,9 +90,7 @@ impl CustomEmojiService {
.find(id)
.select(CustomEmoji::as_select());
self.db_pool
.with_connection(|db_conn| async move { query.get_result(db_conn).await }.scoped())
.await
with_connection!(self.db_pool, |db_conn| { query.get_result(db_conn).await })
.map_err(Error::from)
}
@ -132,13 +127,9 @@ impl CustomEmojiService {
))
.limit(get_emoji_list.limit);
self.db_pool
.with_connection(|db_conn| {
async move { Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from)) }
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
}
pub async fn add_emoji<S>(&self, emoji_upload: EmojiUpload<S>) -> Result<CustomEmoji>
@ -158,24 +149,21 @@ impl CustomEmojiService {
let id = Uuid::now_v7();
let remote_id = self.url_service.custom_emoji_url(id);
let custom_emoji = self
.db_pool
.with_connection(|db_conn| {
diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id,
remote_id,
shortcode: emoji_upload.shortcode,
domain: None,
media_attachment_id: attachment.id,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc(),
})
.get_result(db_conn)
.scoped()
})
.await?;
let custom_emoji = with_connection!(self.db_pool, |db_conn| {
diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id,
remote_id,
shortcode: emoji_upload.shortcode,
domain: None,
media_attachment_id: attachment.id,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc(),
})
.get_result(db_conn)
.await
})?;
Ok(custom_emoji)
}

View File

@ -1,8 +1,5 @@
use diesel_async::pooled_connection::bb8;
use std::{
error::Error as StdError,
fmt::{Debug, Display},
};
use std::{error::Error as StdError, fmt::Debug};
use thiserror::Error;
pub type BoxError = Box<dyn StdError + Send + Sync>;
@ -71,7 +68,7 @@ pub enum Error {
Event(kitsune_messaging::BoxError),
#[error(transparent)]
Fetcher(BoxError),
Fetcher(eyre::Report),
#[error(transparent)]
Http(#[from] http::Error),
@ -101,7 +98,7 @@ pub enum Error {
PostProcessing(post_process::BoxError),
#[error(transparent)]
Resolver(BoxError),
Resolver(eyre::Report),
#[error(transparent)]
Rsa(#[from] rsa::Error),
@ -130,15 +127,3 @@ pub enum Error {
#[error(transparent)]
Validate(#[from] garde::Report),
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}

View File

@ -3,9 +3,8 @@ use diesel::{ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use kitsune_db::{
schema::{accounts, posts, users},
PgPool,
with_connection, PgPool,
};
use scoped_futures::ScopedFutureExt;
use smol_str::SmolStr;
use typed_builder::TypedBuilder;
@ -37,39 +36,29 @@ impl InstanceService {
}
pub async fn known_instances(&self) -> Result<u64> {
self.db_pool
.with_connection(|db_conn| {
async move {
accounts::table
.filter(accounts::local.eq(false))
.select(accounts::domain)
.distinct()
.count()
.get_result::<i64>(db_conn)
.await
.map(|count| count as u64)
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
accounts::table
.filter(accounts::local.eq(false))
.select(accounts::domain)
.distinct()
.count()
.get_result::<i64>(db_conn)
.await
.map(|count| count as u64)
})
.map_err(Error::from)
}
pub async fn local_post_count(&self) -> Result<u64> {
self.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.filter(posts::is_local.eq(true))
.count()
.get_result::<i64>(db_conn)
.await
.map(|count| count as u64)
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
posts::table
.filter(posts::is_local.eq(true))
.count()
.get_result::<i64>(db_conn)
.await
.map(|count| count as u64)
})
.map_err(Error::from)
}
#[must_use]
@ -78,18 +67,13 @@ impl InstanceService {
}
pub async fn user_count(&self) -> Result<u64> {
self.db_pool
.with_connection(|db_conn| {
async move {
users::table
.count()
.get_result::<i64>(db_conn)
.await
.map(|count| count as u64)
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
users::table
.count()
.get_result::<i64>(db_conn)
.await
.map(|count| count as u64)
})
.map_err(Error::from)
}
}

View File

@ -10,9 +10,8 @@ use garde::Validate;
use kitsune_db::{
model::notification::{NewNotification, Notification, NotificationType},
schema::{accounts, accounts_follows, accounts_preferences, notifications, posts},
PgPool,
with_connection, PgPool,
};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -104,15 +103,9 @@ impl NotificationService {
.order(notifications::id.asc());
}
self.db_pool
.with_connection(|mut db_conn| {
async move {
Ok::<_, Error>(query.load_stream(&mut db_conn).await?.map_err(Error::from))
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
}
pub async fn get_notification_by_id(
@ -120,61 +113,45 @@ impl NotificationService {
id: Uuid,
account_id: Uuid,
) -> Result<Option<Notification>> {
self.db_pool
.with_connection(|mut db_conn| {
async move {
notifications::table
.filter(
notifications::id
.eq(id)
.and(notifications::receiving_account_id.eq(account_id)),
)
.select(Notification::as_select())
.first(&mut db_conn)
.await
.optional()
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
notifications::table
.filter(
notifications::id
.eq(id)
.and(notifications::receiving_account_id.eq(account_id)),
)
.select(Notification::as_select())
.first(db_conn)
.await
.optional()
})
.map_err(Error::from)
}
pub async fn dismiss(&self, id: Uuid, account_id: Uuid) -> Result<()> {
self.db_pool
.with_connection(|mut db_conn| {
async move {
diesel::delete(
notifications::table.filter(
notifications::id
.eq(id)
.and(notifications::receiving_account_id.eq(account_id)),
),
)
.execute(&mut db_conn)
.await
}
.scoped()
})
.await?;
with_connection!(self.db_pool, |db_conn| {
diesel::delete(
notifications::table.filter(
notifications::id
.eq(id)
.and(notifications::receiving_account_id.eq(account_id)),
),
)
.execute(db_conn)
.await
})?;
Ok(())
}
pub async fn clear_all(&self, account_id: Uuid) -> Result<()> {
self.db_pool
.with_connection(|mut db_conn| {
async move {
diesel::delete(
notifications::table
.filter(notifications::receiving_account_id.eq(account_id)),
)
.execute(&mut db_conn)
.await
}
.scoped()
})
.await?;
with_connection!(self.db_pool, |db_conn| {
diesel::delete(
notifications::table.filter(notifications::receiving_account_id.eq(account_id)),
)
.execute(db_conn)
.await
})?;
Ok(())
}

View File

@ -33,7 +33,7 @@ use kitsune_db::{
posts_custom_emojis, posts_favourites, posts_media_attachments, posts_mentions,
users_roles,
},
PgPool,
with_connection, with_transaction, PgPool,
};
use kitsune_embed::Client as EmbedClient;
use kitsune_jobs::deliver::{
@ -47,7 +47,6 @@ use kitsune_language::Language;
use kitsune_search::SearchBackend;
use kitsune_url::UrlService;
use kitsune_util::{process, sanitize::CleanHtmlExt};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -478,59 +477,48 @@ impl PostService {
let id = Uuid::now_v7();
let url = self.url_service.post_url(id);
let post = self
.db_pool
.with_transaction(move |tx| {
async move {
let in_reply_to_id = if let Some(in_reply_to_id) = create_post.in_reply_to_id {
(posts::table
.find(in_reply_to_id)
.count()
.get_result::<i64>(tx)
.await?
!= 0)
.then_some(in_reply_to_id)
} else {
None
};
let post = with_transaction!(self.db_pool, |tx| {
let in_reply_to_id = if let Some(in_reply_to_id) = create_post.in_reply_to_id {
(posts::table
.find(in_reply_to_id)
.count()
.get_result::<i64>(tx)
.await?
!= 0)
.then_some(in_reply_to_id)
} else {
None
};
let post: Post = diesel::insert_into(posts::table)
.values(NewPost {
id,
account_id: create_post.author_id,
in_reply_to_id,
reposted_post_id: None,
subject: subject.as_deref(),
content: resolved.content.as_str(),
content_source: content_source.as_str(),
content_lang: content_lang.into(),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: create_post.sensitive,
visibility: create_post.visibility,
is_local: true,
url: url.as_str(),
created_at: None,
})
.returning(Post::as_returning())
.get_result(tx)
.await?;
let post: Post = diesel::insert_into(posts::table)
.values(NewPost {
id,
account_id: create_post.author_id,
in_reply_to_id,
reposted_post_id: None,
subject: subject.as_deref(),
content: resolved.content.as_str(),
content_source: content_source.as_str(),
content_lang: content_lang.into(),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: create_post.sensitive,
visibility: create_post.visibility,
is_local: true,
url: url.as_str(),
created_at: None,
})
.returning(Post::as_returning())
.get_result(tx)
.await?;
Self::process_mentions(
tx,
post.account_id,
post.id,
resolved.mentioned_accounts,
)
.await?;
Self::process_custom_emojis(tx, post.id, resolved.custom_emojis).await?;
Self::process_media_attachments(tx, post.id, &create_post.media_ids).await?;
NotificationService::notify_on_new_post(tx, post.account_id, post.id).await?;
Self::process_mentions(tx, post.account_id, post.id, resolved.mentioned_accounts)
.await?;
Self::process_custom_emojis(tx, post.id, resolved.custom_emojis).await?;
Self::process_media_attachments(tx, post.id, &create_post.media_ids).await?;
NotificationService::notify_on_new_post(tx, post.account_id, post.id).await?;
Ok::<_, Error>(post)
}
.scoped()
})
.await?;
Ok::<_, Error>(post)
})?;
self.job_service
.enqueue(
@ -653,37 +641,29 @@ impl PostService {
None
};
let post = self
.db_pool
.with_transaction(move |tx| {
async move {
let post: Post = diesel::update(posts::table)
.set(PartialPostChangeset {
id: update_post.post_id,
subject: subject.as_deref(),
content: content.as_deref(),
content_source: update_post.content.as_deref(),
content_lang: content_lang.map(Into::into),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: update_post.sensitive,
updated_at: Timestamp::now_utc(),
})
.returning(Post::as_returning())
.get_result(tx)
.await?;
let post = with_transaction!(self.db_pool, |tx| {
let post: Post = diesel::update(posts::table)
.set(PartialPostChangeset {
id: update_post.post_id,
subject: subject.as_deref(),
content: content.as_deref(),
content_source: update_post.content.as_deref(),
content_lang: content_lang.map(Into::into),
link_preview_url: link_preview_url.as_deref(),
is_sensitive: update_post.sensitive,
updated_at: Timestamp::now_utc(),
})
.returning(Post::as_returning())
.get_result(tx)
.await?;
Self::process_mentions(tx, post.account_id, post.id, mentioned_account_ids)
.await?;
Self::process_custom_emojis(tx, post.id, custom_emojis).await?;
Self::process_media_attachments(tx, post.id, &update_post.media_ids).await?;
NotificationService::notify_on_update_post(tx, post.account_id, post.id)
.await?;
Self::process_mentions(tx, post.account_id, post.id, mentioned_account_ids).await?;
Self::process_custom_emojis(tx, post.id, custom_emojis).await?;
Self::process_media_attachments(tx, post.id, &update_post.media_ids).await?;
NotificationService::notify_on_update_post(tx, post.account_id, post.id).await?;
Ok::<_, Error>(post)
}
.scoped()
})
.await?;
Ok::<_, Error>(post)
})?;
self.job_service
.enqueue(
@ -723,84 +703,69 @@ impl PostService {
.fetching_account_id(Some(repost_post.account_id))
.build();
let existing_repost: Option<Post> = self
.db_pool
.with_connection(|db_conn| {
async move {
posts::table
.filter(
posts::reposted_post_id
.eq(repost_post.post_id)
.and(posts::account_id.eq(repost_post.account_id)),
)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.first(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let existing_repost: Option<Post> = with_connection!(self.db_pool, |db_conn| {
posts::table
.filter(
posts::reposted_post_id
.eq(repost_post.post_id)
.and(posts::account_id.eq(repost_post.account_id)),
)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.first(db_conn)
.await
.optional()
})?;
if let Some(repost) = existing_repost {
return Ok(repost);
}
let post: Post = self
.db_pool
.with_connection(|db_conn| {
posts::table
.find(repost_post.post_id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result(db_conn)
.scoped()
})
.await?;
let post: Post = with_connection!(self.db_pool, |db_conn| {
posts::table
.find(repost_post.post_id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result(db_conn)
.await
})?;
let id = Uuid::now_v7();
let url = self.url_service.post_url(id);
let repost = self
.db_pool
.with_transaction(|tx| {
async move {
let new_repost = diesel::insert_into(posts::table)
.values(NewPost {
id,
account_id: repost_post.account_id,
in_reply_to_id: None,
reposted_post_id: Some(post.id),
subject: None,
content: "",
content_source: "",
content_lang: post.content_lang,
link_preview_url: None,
is_sensitive: post.is_sensitive,
visibility: repost_post.visibility,
is_local: true,
url: url.as_str(),
created_at: Some(Timestamp::now_utc()),
})
.returning(Post::as_returning())
.get_result(tx)
.await?;
let repost = with_transaction!(self.db_pool, |tx| {
let new_repost = diesel::insert_into(posts::table)
.values(NewPost {
id,
account_id: repost_post.account_id,
in_reply_to_id: None,
reposted_post_id: Some(post.id),
subject: None,
content: "",
content_source: "",
content_lang: post.content_lang,
link_preview_url: None,
is_sensitive: post.is_sensitive,
visibility: repost_post.visibility,
is_local: true,
url: url.as_str(),
created_at: Some(Timestamp::now_utc()),
})
.returning(Post::as_returning())
.get_result(tx)
.await?;
NotificationService::notify_on_repost(
tx,
post.account_id,
new_repost.account_id,
post.id,
)
.await?;
Ok::<_, Error>(new_repost)
}
.scoped()
})
NotificationService::notify_on_repost(
tx,
post.account_id,
new_repost.account_id,
post.id,
)
.await?;
Ok::<_, Error>(new_repost)
})?;
self.job_service
.enqueue(
Enqueue::builder()
@ -830,21 +795,18 @@ impl PostService {
.fetching_account_id(Some(unrepost_post.account_id))
.build();
let post: Post = self
.db_pool
.with_connection(|db_conn| {
posts::table
.filter(
posts::account_id
.eq(unrepost_post.account_id)
.and(posts::reposted_post_id.eq(unrepost_post.post_id)),
)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.first(db_conn)
.scoped()
})
.await?;
let post: Post = with_connection!(self.db_pool, |db_conn| {
posts::table
.filter(
posts::account_id
.eq(unrepost_post.account_id)
.and(posts::reposted_post_id.eq(unrepost_post.post_id)),
)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.first(db_conn)
.await
})?;
self.job_service
.enqueue(
@ -875,65 +837,57 @@ impl PostService {
.fetching_account_id(Some(favouriting_account_id))
.build();
let post: Post = self
.db_pool
.with_connection(|db_conn| {
posts::table
.find(post_id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result(db_conn)
.scoped()
})
.await?;
let post: Post = with_connection!(self.db_pool, |db_conn| {
posts::table
.find(post_id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result(db_conn)
.await
})?;
let id = Uuid::now_v7();
let url = self.url_service.favourite_url(id);
let favourite_id = self
.db_pool
.with_transaction(|tx| {
async move {
let favourite = diesel::insert_into(posts_favourites::table)
.values(NewFavourite {
id,
account_id: favouriting_account_id,
post_id: post.id,
url,
created_at: None,
})
.returning(posts_favourites::id)
.get_result(tx)
.await?;
let account_id = accounts::table
.inner_join(accounts_preferences::table)
.filter(
accounts::id
.eq(post.account_id)
.and(accounts_preferences::notify_on_favourite.eq(true)),
)
.select(accounts::id)
.get_result::<Uuid>(tx)
.await
.optional()?;
let favourite_id = with_transaction!(self.db_pool, |tx| {
let favourite = diesel::insert_into(posts_favourites::table)
.values(NewFavourite {
id,
account_id: favouriting_account_id,
post_id: post.id,
url,
created_at: None,
})
.returning(posts_favourites::id)
.get_result(tx)
.await?;
if let Some(account_id) = account_id {
diesel::insert_into(notifications::table)
.values(
NewNotification::builder()
.receiving_account_id(account_id)
.favourite(favouriting_account_id, post.id),
)
.on_conflict_do_nothing()
.execute(tx)
.await?;
}
let account_id = accounts::table
.inner_join(accounts_preferences::table)
.filter(
accounts::id
.eq(post.account_id)
.and(accounts_preferences::notify_on_favourite.eq(true)),
)
.select(accounts::id)
.get_result::<Uuid>(tx)
.await
.optional()?;
Ok::<_, Error>(favourite)
}
.scoped()
})
.await?;
if let Some(account_id) = account_id {
diesel::insert_into(notifications::table)
.values(
NewNotification::builder()
.receiving_account_id(account_id)
.favourite(favouriting_account_id, post.id),
)
.on_conflict_do_nothing()
.execute(tx)
.await?;
}
Ok::<_, Error>(favourite)
})?;
self.job_service
.enqueue(
@ -956,19 +910,13 @@ impl PostService {
.get_by_id(post_id, Some(favouriting_account_id))
.await?;
let favourite = self
.db_pool
.with_connection(|db_conn| {
async {
Favourite::belonging_to(&post)
.filter(posts_favourites::account_id.eq(favouriting_account_id))
.get_result::<Favourite>(db_conn)
.await
.optional()
}
.scoped()
})
.await?;
let favourite = with_connection!(self.db_pool, |db_conn| {
Favourite::belonging_to(&post)
.filter(posts_favourites::account_id.eq(favouriting_account_id))
.get_result::<Favourite>(db_conn)
.await
.optional()
})?;
if let Some(favourite) = favourite {
self.job_service
@ -1018,15 +966,9 @@ impl PostService {
.order(posts_favourites::id.asc());
}
self.db_pool
.with_connection(|db_conn| {
async move {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
}
/// Get accounts that reblogged a post
@ -1067,15 +1009,10 @@ impl PostService {
.order(accounts::id.desc())
.limit(get_reblogs.limit as i64);
self.db_pool
.with_connection(|db_conn| {
async move {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
.map_err(Error::from)
}
/// Get a post by its ID
@ -1090,17 +1027,15 @@ impl PostService {
.fetching_account_id(fetching_account_id)
.build();
self.db_pool
.with_connection(|db_conn| {
posts::table
.find(id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result(db_conn)
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
posts::table
.find(id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result(db_conn)
.await
})
.map_err(Error::from)
}
/// Get a post's source by its ID
@ -1116,17 +1051,15 @@ impl PostService {
.fetching_account_id(fetching_account_id)
.build();
self.db_pool
.with_connection(|db_conn| {
posts::table
.find(id)
.add_post_permission_check(permission_check)
.select(PostSource::as_select())
.get_result(db_conn)
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
posts::table
.find(id)
.add_post_permission_check(permission_check)
.select(PostSource::as_select())
.get_result(db_conn)
.await
})
.map_err(Error::from)
}
/// Get the ancestors of the post
@ -1139,6 +1072,19 @@ impl PostService {
id: Uuid,
fetching_account_id: Option<Uuid>,
) -> impl Stream<Item = Result<Post>> + '_ {
let load_post = move |in_reply_to_id, permission_check| async move {
let post = with_connection!(self.db_pool, |db_conn| {
posts::table
.find(in_reply_to_id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.await
})?;
Ok::<_, Error>(post)
};
try_stream! {
let mut last_post = self.get_by_id(id, fetching_account_id).await?;
let permission_check = PermissionCheck::builder()
@ -1146,16 +1092,7 @@ impl PostService {
.build();
while let Some(in_reply_to_id) = last_post.in_reply_to_id {
let post = self.db_pool
.with_connection(|db_conn| {
posts::table
.find(in_reply_to_id)
.add_post_permission_check(permission_check)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.scoped()
})
.await?;
let post = load_post(in_reply_to_id, permission_check).await?;
yield post.clone();
@ -1175,22 +1112,25 @@ impl PostService {
id: Uuid,
fetching_account_id: Option<Uuid>,
) -> BoxStream<'_, Result<Post>> {
let load_post = move |id, permission_check| async move {
let post = with_connection!(self.db_pool, |db_conn| {
posts::table
.filter(posts::in_reply_to_id.eq(id))
.add_post_permission_check(permission_check)
.select(Post::as_select())
.load_stream::<Post>(db_conn)
.await
})?;
Ok::<_, Error>(post)
};
try_stream! {
let permission_check = PermissionCheck::builder()
.fetching_account_id(fetching_account_id)
.build();
let descendant_stream = self.db_pool
.with_connection(|db_conn| {
posts::table
.filter(posts::in_reply_to_id.eq(id))
.add_post_permission_check(permission_check)
.select(Post::as_select())
.load_stream::<Post>(db_conn)
.scoped()
})
.await?;
let descendant_stream = load_post(id, permission_check).await?;
for await descendant in descendant_stream {
let descendant = descendant?;
let descendant_id = descendant.id;
@ -1212,33 +1152,27 @@ impl PostService {
account_id: Uuid,
user_id: Option<Uuid>,
) -> Result<Post> {
let post: Post = self
.db_pool
.with_connection(|db_conn| {
posts::table
.find(post_id)
.select(Post::as_select())
.first(db_conn)
.scoped()
})
.await?;
let post: Post = with_connection!(self.db_pool, |db_conn| {
posts::table
.find(post_id)
.select(Post::as_select())
.first(db_conn)
.await
})?;
if post.account_id != account_id {
if let Some(user_id) = user_id {
let admin_role_count = self
.db_pool
.with_connection(|db_conn| {
users_roles::table
.filter(
users_roles::user_id
.eq(user_id)
.and(users_roles::role.eq(Role::Administrator)),
)
.count()
.get_result::<i64>(db_conn)
.scoped()
})
.await?;
let admin_role_count = with_connection!(self.db_pool, |db_conn| {
users_roles::table
.filter(
users_roles::user_id
.eq(user_id)
.and(users_roles::role.eq(Role::Administrator)),
)
.count()
.get_result::<i64>(db_conn)
.await
})?;
if admin_role_count == 0 {
return Err(PostError::Unauthorised.into());

View File

@ -126,6 +126,7 @@ mod test {
account::Account, custom_emoji::CustomEmoji, media_attachment::NewMediaAttachment,
},
schema::{accounts, custom_emojis, media_attachments},
with_connection_panicky,
};
use kitsune_federation_filter::FederationFilter;
use kitsune_http_client::Client;
@ -137,7 +138,6 @@ mod test {
use kitsune_util::try_join;
use kitsune_webfinger::Webfinger;
use pretty_assertions::assert_eq;
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use std::sync::Arc;
use tower::service_fn;
@ -221,69 +221,63 @@ mod test {
let emoji_ids = (Uuid::now_v7(), Uuid::now_v7());
let media_attachment_ids = (Uuid::now_v7(), Uuid::now_v7());
db_pool
.with_connection(|db_conn| {
async {
let media_fut = diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: media_attachment_ids.0,
content_type: "image/jpeg",
account_id: None,
description: None,
blurhash: None,
file_path: None,
remote_url: None,
})
.execute(db_conn);
let emoji_fut = diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id: emoji_ids.0,
shortcode: String::from("blobhaj_happy"),
domain: None,
remote_id: String::from("https://local.domain/emoji/blobhaj_happy"),
media_attachment_id: media_attachment_ids.0,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc()
})
.execute(db_conn);
try_join!(media_fut, emoji_fut)
}.scoped()
})
.await
.expect("Failed to insert the local emoji");
db_pool
.with_connection(|db_conn| {
async {
let media_fut = diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: media_attachment_ids.1,
content_type: "image/jpeg",
account_id: None,
description: None,
blurhash: None,
file_path: None,
remote_url: Some("https://media.example.com/emojis/blobhaj.jpeg"),
})
.execute(db_conn);
let emoji_fut = diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id: emoji_ids.1,
shortcode: String::from("blobhaj_sad"),
domain: Some(String::from("example.com")),
remote_id: String::from("https://example.com/emojis/1"),
media_attachment_id: media_attachment_ids.1,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc(),
})
.execute(db_conn);
try_join!(media_fut, emoji_fut)
}.scoped()
})
.await
.expect("Failed to insert the remote emoji");
with_connection_panicky!(db_pool, |db_conn| {
let media_fut = diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: media_attachment_ids.0,
content_type: "image/jpeg",
account_id: None,
description: None,
blurhash: None,
file_path: None,
remote_url: None,
})
.execute(db_conn);
let emoji_fut = diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id: emoji_ids.0,
shortcode: String::from("blobhaj_happy"),
domain: None,
remote_id: String::from("https://local.domain/emoji/blobhaj_happy"),
media_attachment_id: media_attachment_ids.0,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc()
})
.execute(db_conn);
try_join!(media_fut, emoji_fut)
})
.expect("Failed to insert the local emoji");
with_connection_panicky!(db_pool, |db_conn| {
let media_fut = diesel::insert_into(media_attachments::table)
.values(NewMediaAttachment {
id: media_attachment_ids.1,
content_type: "image/jpeg",
account_id: None,
description: None,
blurhash: None,
file_path: None,
remote_url: Some("https://media.example.com/emojis/blobhaj.jpeg"),
})
.execute(db_conn);
let emoji_fut = diesel::insert_into(custom_emojis::table)
.values(CustomEmoji {
id: emoji_ids.1,
shortcode: String::from("blobhaj_sad"),
domain: Some(String::from("example.com")),
remote_id: String::from("https://example.com/emojis/1"),
media_attachment_id: media_attachment_ids.1,
endorsed: false,
created_at: Timestamp::now_utc(),
updated_at: Timestamp::now_utc(),
})
.execute(db_conn);
try_join!(media_fut, emoji_fut)
})
.expect("Failed to insert the remote emoji");
let post_resolver = PostResolver::builder()
.account(account_service)
@ -300,16 +294,14 @@ mod test {
assert_eq!(resolved.custom_emojis.len(), 2);
let (account_id, _mention_text) = &resolved.mentioned_accounts[0];
let mentioned_account = db_pool
.with_connection(|db_conn| {
accounts::table
.find(account_id)
.select(Account::as_select())
.get_result::<Account>(db_conn)
.scoped()
})
.await
.expect("Failed to fetch account");
let mentioned_account = with_connection_panicky!(db_pool, |db_conn| {
accounts::table
.find(account_id)
.select(Account::as_select())
.get_result::<Account>(db_conn)
.await
})
.expect("Failed to fetch account");
assert_eq!(mentioned_account.username, "0x0");
assert_eq!(mentioned_account.domain, "corteximplant.com");

View File

@ -1,3 +1,4 @@
use eyre::WrapErr;
use kitsune_cache::{ArcCache, InMemoryCache, NoopCache, RedisCache};
use kitsune_captcha::AnyCaptcha;
use kitsune_captcha::{hcaptcha::Captcha as HCaptcha, mcaptcha::Captcha as MCaptcha};
@ -12,7 +13,6 @@ use kitsune_messaging::{
};
use kitsune_search::{AnySearchBackend, NoopSearchService, SqlSearchService};
use kitsune_storage::{fs::Storage as FsStorage, s3::Storage as S3Storage, AnyStorageBackend};
use miette::{Context, IntoDiagnostic};
use multiplex_pool::RoundRobinStrategy;
use redis::aio::ConnectionManager;
use serde::{de::DeserializeOwned, Serialize};
@ -22,7 +22,7 @@ use tokio::sync::OnceCell;
pub async fn cache<K, V>(
config: &cache::Configuration,
cache_name: &str,
) -> miette::Result<ArcCache<K, V>>
) -> eyre::Result<ArcCache<K, V>>
where
K: Display + Send + Sync + ?Sized + 'static,
V: Clone + DeserializeOwned + Serialize + Send + Sync + 'static,
@ -45,8 +45,7 @@ where
)
.await
})
.await
.into_diagnostic()?;
.await?;
RedisCache::builder()
.prefix(cache_name)
@ -79,7 +78,7 @@ pub fn captcha(config: &captcha::Configuration) -> AnyCaptcha {
}
}
pub fn storage(config: &storage::Configuration) -> miette::Result<AnyStorageBackend> {
pub fn storage(config: &storage::Configuration) -> eyre::Result<AnyStorageBackend> {
let storage = match config {
storage::Configuration::Fs(ref fs_config) => {
FsStorage::new(fs_config.upload_dir.as_str().into()).into()
@ -96,12 +95,11 @@ pub fn storage(config: &storage::Configuration) -> miette::Result<AnyStorageBack
s3_config.secret_access_key.as_str(),
);
let s3_bucket = rusty_s3::Bucket::new(
s3_config.endpoint_url.parse().into_diagnostic()?,
s3_config.endpoint_url.parse()?,
path_style,
s3_config.bucket_name.to_string(),
s3_config.region.to_string(),
)
.into_diagnostic()?;
)?;
S3Storage::new(s3_bucket, s3_credentials).into()
}
@ -112,13 +110,12 @@ pub fn storage(config: &storage::Configuration) -> miette::Result<AnyStorageBack
pub fn mail_sender(
config: &email::Configuration,
) -> miette::Result<MailSender<AsyncSmtpTransport<Tokio1Executor>>> {
) -> eyre::Result<MailSender<AsyncSmtpTransport<Tokio1Executor>>> {
let transport_builder = if config.starttls {
AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(config.host.as_str())
} else {
AsyncSmtpTransport::<Tokio1Executor>::relay(config.host.as_str())
}
.into_diagnostic()?;
}?;
let transport = transport_builder
.credentials((config.username.as_str(), config.password.as_str()).into())
@ -126,11 +123,11 @@ pub fn mail_sender(
Ok(MailSender::builder()
.backend(transport)
.from_mailbox(Mailbox::from_str(config.from_address.as_str()).into_diagnostic()?)
.from_mailbox(Mailbox::from_str(config.from_address.as_str())?)
.build())
}
pub async fn messaging(config: &messaging::Configuration) -> miette::Result<MessagingHub> {
pub async fn messaging(config: &messaging::Configuration) -> eyre::Result<MessagingHub> {
let backend = match config {
messaging::Configuration::InProcess => {
MessagingHub::new(TokioBroadcastMessagingBackend::default())
@ -138,7 +135,6 @@ pub async fn messaging(config: &messaging::Configuration) -> miette::Result<Mess
messaging::Configuration::Redis(ref redis_config) => {
let redis_messaging_backend = RedisMessagingBackend::new(&redis_config.url)
.await
.into_diagnostic()
.wrap_err("Failed to initialise Redis messaging backend")?;
MessagingHub::new(redis_messaging_backend)
@ -153,7 +149,7 @@ pub async fn search(
search_config: &search::Configuration,
language_detection_config: language_detection::Configuration,
db_pool: &PgPool,
) -> miette::Result<AnySearchBackend> {
) -> eyre::Result<AnySearchBackend> {
let service = match search_config {
search::Configuration::Meilisearch(_config) => {
#[cfg(not(feature = "meilisearch"))]

View File

@ -8,10 +8,9 @@ use kitsune_core::{consts::API_MAX_LIMIT, traits::Fetcher};
use kitsune_db::{
model::{account::Account, post::Post},
schema::{accounts, posts},
PgPool,
with_connection, PgPool,
};
use kitsune_search::{SearchBackend, SearchIndex};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use std::sync::Arc;
use typed_builder::TypedBuilder;
@ -97,30 +96,27 @@ impl SearchService {
.try_concat()
.await?;
let search_backend_results = self
.db_pool
.with_connection(|db_conn| {
result_references
.iter()
.map(|result| match result.index {
SearchIndex::Account => accounts::table
.find(result.id)
.select(Account::as_select())
.get_result::<Account>(db_conn)
.map_ok(SearchResult::Account)
.left_future(),
SearchIndex::Post => posts::table
.find(result.id)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.map_ok(SearchResult::Post)
.right_future(),
})
.collect::<FuturesUnordered<_>>()
.try_collect::<Vec<SearchResult>>()
.scoped()
})
.await?;
let search_backend_results = with_connection!(self.db_pool, |db_conn| {
result_references
.iter()
.map(|result| match result.index {
SearchIndex::Account => accounts::table
.find(result.id)
.select(Account::as_select())
.get_result::<Account>(db_conn)
.map_ok(SearchResult::Account)
.left_future(),
SearchIndex::Post => posts::table
.find(result.id)
.select(Post::as_select())
.get_result::<Post>(db_conn)
.map_ok(SearchResult::Post)
.right_future(),
})
.collect::<FuturesUnordered<_>>()
.try_collect::<Vec<SearchResult>>()
.await
})?;
results.extend(search_backend_results);

View File

@ -8,9 +8,8 @@ use kitsune_db::{
model::post::{Post, Visibility},
post_permission_check::{PermissionCheck, PostPermissionCheckExt},
schema::{accounts_follows, posts, posts_mentions},
PgPool,
with_connection, PgPool,
};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use typed_builder::TypedBuilder;
@ -121,15 +120,9 @@ impl TimelineService {
query = query.filter(posts::id.gt(min_id)).order(posts::id.asc());
}
self.db_pool
.with_connection(|db_conn| {
async move {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
}
/// Get a stream of public posts
@ -169,14 +162,8 @@ impl TimelineService {
query = query.filter(posts::is_local.eq(false));
}
self.db_pool
.with_connection(|db_conn| {
async move {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
}
.scoped()
})
.await
.map_err(Error::from)
with_connection!(self.db_pool, |db_conn| {
Ok::<_, Error>(query.load_stream(db_conn).await?.map_err(Error::from))
})
}
}

View File

@ -15,7 +15,7 @@ use kitsune_db::{
user::{NewUser, User},
},
schema::{accounts, accounts_preferences, users},
PgPool,
with_transaction, PgPool,
};
use kitsune_jobs::mailing::confirmation::SendConfirmationMail;
use kitsune_url::UrlService;
@ -24,7 +24,6 @@ use rsa::{
pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding},
RsaPrivateKey,
};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use std::fmt::Write;
use typed_builder::TypedBuilder;
@ -167,67 +166,61 @@ impl UserService {
let url = self.url_service.user_url(account_id);
let public_key_id = self.url_service.public_key_id(account_id);
let new_user = self
.db_pool
.with_transaction(|tx| {
async move {
let account_fut = diesel::insert_into(accounts::table)
.values(NewAccount {
id: account_id,
display_name: None,
username: register.username.as_str(),
locked: false,
note: None,
local: true,
domain: domain.as_str(),
actor_type: ActorType::Person,
url: url.as_str(),
featured_collection_url: None,
followers_url: None,
following_url: None,
inbox_url: None,
outbox_url: None,
shared_inbox_url: None,
public_key_id: public_key_id.as_str(),
public_key: public_key_str.as_str(),
created_at: None,
})
.execute(tx);
let new_user = with_transaction!(self.db_pool, |tx| {
let account_fut = diesel::insert_into(accounts::table)
.values(NewAccount {
id: account_id,
display_name: None,
username: register.username.as_str(),
locked: false,
note: None,
local: true,
domain: domain.as_str(),
actor_type: ActorType::Person,
url: url.as_str(),
featured_collection_url: None,
followers_url: None,
following_url: None,
inbox_url: None,
outbox_url: None,
shared_inbox_url: None,
public_key_id: public_key_id.as_str(),
public_key: public_key_str.as_str(),
created_at: None,
})
.execute(tx);
let confirmation_token = generate_secret();
let user_fut = diesel::insert_into(users::table)
.values(NewUser {
id: Uuid::now_v7(),
account_id,
username: register.username.as_str(),
oidc_id: register.oidc_id.as_deref(),
email: register.email.as_str(),
password: hashed_password.as_deref(),
domain: domain.as_str(),
private_key: private_key_str.as_str(),
confirmation_token: confirmation_token.as_str(),
})
.get_result::<User>(tx);
let confirmation_token = generate_secret();
let user_fut = diesel::insert_into(users::table)
.values(NewUser {
id: Uuid::now_v7(),
account_id,
username: register.username.as_str(),
oidc_id: register.oidc_id.as_deref(),
email: register.email.as_str(),
password: hashed_password.as_deref(),
domain: domain.as_str(),
private_key: private_key_str.as_str(),
confirmation_token: confirmation_token.as_str(),
})
.get_result::<User>(tx);
let preferences_fut = diesel::insert_into(accounts_preferences::table)
.values(Preferences {
account_id,
notify_on_follow: true,
notify_on_follow_request: true,
notify_on_repost: false,
notify_on_favourite: false,
notify_on_mention: true,
notify_on_post_update: true,
})
.execute(tx);
let preferences_fut = diesel::insert_into(accounts_preferences::table)
.values(Preferences {
account_id,
notify_on_follow: true,
notify_on_follow_request: true,
notify_on_repost: false,
notify_on_favourite: false,
notify_on_mention: true,
notify_on_post_update: true,
})
.execute(tx);
let (_, user, _) = try_join!(account_fut, user_fut, preferences_fut)?;
let (_, user, _) = try_join!(account_fut, user_fut, preferences_fut)?;
Ok::<_, Error>(user)
}
.scoped()
})
.await?;
Ok::<_, Error>(user)
})?;
self.job_service
.enqueue(

View File

@ -11,12 +11,12 @@ derive_more = { version = "1.0.0-beta.6", features = ["from"] }
futures-util = "0.3.30"
kitsune-s3 = { path = "../kitsune-s3" }
rusty-s3 = { version = "0.5.0", default-features = false }
tokio = { version = "1.36.0", features = ["fs", "io-util"] }
tokio = { version = "1.37.0", features = ["fs", "io-util"] }
tokio-util = { version = "0.7.10", features = ["io"] }
[dev-dependencies]
tempfile = "3.10.1"
tokio = { version = "1.36.0", features = ["macros", "rt"] }
tokio = { version = "1.37.0", features = ["macros", "rt"] }
[lints]
workspace = true

View File

@ -16,7 +16,7 @@ kitsune-config = { path = "../kitsune-config" }
kitsune-db = { path = "../kitsune-db" }
kitsune-s3 = { path = "../kitsune-s3" }
multiplex-pool = { path = "../../lib/multiplex-pool" }
pin-project-lite = "0.2.13"
pin-project-lite = "0.2.14"
rand = "0.8.5"
redis = { version = "0.25.2", default-features = false, features = [
"connection-manager",
@ -29,7 +29,7 @@ testcontainers-modules = { version = "0.3.6", features = [
"postgres",
"redis",
] }
tokio = { version = "1.36.0", features = ["time"] }
tokio = { version = "1.37.0", features = ["time"] }
url = "2.5.0"
uuid = { version = "1.8.0", features = ["fast-rng", "v4"] }

View File

@ -16,7 +16,7 @@ pulldown-cmark = { version = "0.10.0", default-features = false, features = [
] }
rand = "0.8.5"
speedy-uuid = { path = "../../lib/speedy-uuid" }
tokio = { version = "1.36.0", features = ["macros"] }
tokio = { version = "1.37.0", features = ["macros"] }
[lints]
workspace = true

View File

@ -8,14 +8,14 @@ build = "build.rs"
[dependencies]
async-trait = "0.1.79"
color-eyre = "0.6.3"
derive_more = { version = "1.0.0-beta.6", features = ["from"] }
enum_dispatch = "0.3.12"
enum_dispatch = "0.3.13"
futures-util = { version = "0.3.30", default-features = false, features = [
"alloc",
] }
kitsune-config = { path = "../kitsune-config" }
kitsune-type = { path = "../kitsune-type" }
miette = "7.2.0"
mrf-manifest = { path = "../../lib/mrf-manifest", features = ["decode"] }
multiplex-pool = { path = "../../lib/multiplex-pool" }
redis = { version = "0.25.2", default-features = false, features = [
@ -27,7 +27,7 @@ slab = "0.4.9"
sled = "0.34.7"
smol_str = "0.2.1"
thiserror = "1.0.58"
tokio = { version = "1.36.0", features = ["fs"] }
tokio = { version = "1.37.0", features = ["fs"] }
tracing = "0.1.40"
typed-builder = "0.18.1"
walkdir = "2.5.0"
@ -44,7 +44,7 @@ wasmtime-wasi = { version = "19.0.0", default-features = false }
[dev-dependencies]
tempfile = "3.10.1"
tokio = { version = "1.36.0", features = ["macros", "rt"] }
tokio = { version = "1.37.0", features = ["macros", "rt"] }
tracing-subscriber = "0.3.18"
[lints]

View File

@ -11,7 +11,7 @@ crate-type = ["cdylib"]
[dependencies]
rand = "0.8.5"
wit-bindgen = "0.22.0"
wit-bindgen = "0.23.0"
[lints]
workspace = true

View File

@ -1,7 +1,6 @@
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Json(#[from] simd_json::Error),

View File

@ -1,5 +1,5 @@
use super::{Backend, BoxError, BucketBackend};
use miette::IntoDiagnostic;
use color_eyre::eyre;
use std::path::Path;
pub struct FsBackend {
@ -7,12 +7,12 @@ pub struct FsBackend {
}
impl FsBackend {
pub fn from_path<P>(path: P) -> miette::Result<Self>
pub fn from_path<P>(path: P) -> eyre::Result<Self>
where
P: AsRef<Path>,
{
Ok(Self {
inner: sled::open(path).into_diagnostic()?,
inner: sled::open(path)?,
})
}
}

View File

@ -1,5 +1,5 @@
use super::BoxError;
use miette::IntoDiagnostic;
use color_eyre::eyre;
use redis::{aio::ConnectionManager, AsyncCommands};
const REDIS_NAMESPACE: &str = "MRF-KV-STORE";
@ -9,14 +9,13 @@ pub struct RedisBackend {
}
impl RedisBackend {
pub async fn from_client(client: redis::Client, pool_size: usize) -> miette::Result<Self> {
pub async fn from_client(client: redis::Client, pool_size: usize) -> eyre::Result<Self> {
let pool = multiplex_pool::Pool::from_producer(
|| client.get_connection_manager(),
pool_size,
multiplex_pool::RoundRobinStrategy::default(),
)
.await
.into_diagnostic()?;
.await?;
Ok(Self { pool })
}

View File

@ -5,12 +5,12 @@ use self::{
ctx::{construct_store, Context},
mrf_wit::v1::fep::mrf::types::{Direction, Error as MrfError},
};
use futures_util::{stream::FuturesUnordered, Stream, StreamExt, TryFutureExt, TryStreamExt};
use color_eyre::{eyre, Section};
use futures_util::{stream::FuturesUnordered, Stream, TryFutureExt, TryStreamExt};
use kitsune_config::mrf::{
Configuration as MrfConfiguration, FsKvStorage, KvStorage, RedisKvStorage,
};
use kitsune_type::ap::Activity;
use miette::{Diagnostic, IntoDiagnostic};
use mrf_manifest::{Manifest, ManifestV1};
use smol_str::SmolStr;
use std::{
@ -20,7 +20,6 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use thiserror::Error;
use tokio::fs;
use typed_builder::TypedBuilder;
use walkdir::WalkDir;
@ -64,14 +63,11 @@ fn load_mrf_module(
engine: &Engine,
module_path: &Path,
bytes: &[u8],
) -> miette::Result<Option<(ManifestV1<'static>, Component)>> {
let component = Component::new(engine, bytes).map_err(|err| {
miette::Report::new(ComponentParseError {
path_help: format!("path to the module: {}", module_path.display()),
advice: "Did you make the WASM file a component via `wasm-tools`?",
})
.wrap_err(err)
})?;
) -> eyre::Result<Option<(ManifestV1<'static>, Component)>> {
let component = Component::new(engine, bytes)
.map_err(eyre::Report::msg)
.with_note(|| format!("path to the module: {}", module_path.display()))
.suggestion("Did you make the WASM file a component via `wasm-tools`?")?;
let Some((manifest, _section_range)) = mrf_manifest::decode(bytes)? else {
error!("missing manifest. skipping load.");
@ -93,14 +89,6 @@ pub enum Outcome<'a> {
Reject,
}
#[derive(Debug, Diagnostic, Error)]
#[error("{path_help}")]
struct ComponentParseError {
path_help: String,
#[help]
advice: &'static str,
}
pub struct MrfModule {
pub component: Component,
pub config: SmolStr,
@ -121,11 +109,11 @@ impl MrfService {
engine: Engine,
modules: Vec<MrfModule>,
storage: kv_storage::BackendDispatch,
) -> miette::Result<Self> {
) -> eyre::Result<Self> {
let mut linker = Linker::<Context>::new(&engine);
mrf_wit::v1::Mrf::add_to_linker(&mut linker, |ctx| ctx).map_err(miette::Report::msg)?;
wasmtime_wasi::command::add_to_linker(&mut linker).map_err(miette::Report::msg)?;
mrf_wit::v1::Mrf::add_to_linker(&mut linker, |ctx| ctx).map_err(eyre::Report::msg)?;
wasmtime_wasi::command::add_to_linker(&mut linker).map_err(eyre::Report::msg)?;
Ok(Self {
engine,
@ -136,13 +124,13 @@ impl MrfService {
}
#[instrument(skip_all, fields(module_dir = %config.module_dir))]
pub async fn from_config(config: &MrfConfiguration) -> miette::Result<Self> {
pub async fn from_config(config: &MrfConfiguration) -> eyre::Result<Self> {
let storage = match config.storage {
KvStorage::Fs(FsKvStorage { ref path }) => {
kv_storage::FsBackend::from_path(path.as_str())?.into()
}
KvStorage::Redis(RedisKvStorage { ref url, pool_size }) => {
let client = redis::Client::open(url.as_str()).into_diagnostic()?;
let client = redis::Client::open(url.as_str())?;
kv_storage::RedisBackend::from_client(client, pool_size.get())
.await?
.into()
@ -155,9 +143,9 @@ impl MrfService {
.async_support(true)
.wasm_component_model(true);
let engine = Engine::new(&engine_config).map_err(miette::Report::msg)?;
let engine = Engine::new(&engine_config).map_err(eyre::Report::msg)?;
let wasm_data_stream = find_mrf_modules(config.module_dir.as_str())
.map(IntoDiagnostic::into_diagnostic)
.map_err(eyre::Report::from)
.and_then(|(module_path, wasm_data)| {
let engine = &engine;

View File

@ -8,6 +8,7 @@ license.workspace = true
[dependencies]
async-trait = "0.1.79"
autometrics = { version = "1.0.1", default-features = false }
eyre = "0.6.12"
futures-util = "0.3.30"
http = "1.1.0"
kitsune-cache = { path = "../kitsune-cache" }
@ -28,7 +29,7 @@ http-body-util = "0.1.1"
hyper = "1.2.0"
pretty_assertions = "1.4.0"
simd-json = "0.13.9"
tokio = { version = "1.36.0", features = ["macros"] }
tokio = { version = "1.37.0", features = ["macros"] }
tower = { version = "0.4.13", default-features = false, features = ["util"] }
[lints]

View File

@ -6,9 +6,8 @@ use autometrics::autometrics;
use futures_util::future::{FutureExt, OptionFuture};
use http::{HeaderValue, StatusCode};
use kitsune_cache::{ArcCache, CacheBackend, RedisCache};
use kitsune_core::consts::USER_AGENT;
use kitsune_core::{
error::BoxError,
consts::USER_AGENT,
traits::{resolver::AccountResource, Resolver},
};
use kitsune_http_client::Client;
@ -73,7 +72,7 @@ impl Resolver for Webfinger {
&self,
username: &str,
domain: &str,
) -> Result<Option<AccountResource>, BoxError> {
) -> eyre::Result<Option<AccountResource>> {
// XXX: Assigning the arguments to local bindings because the `#[instrument]` attribute
// desugars to an `async move {}` block, inside which mutating the function arguments would
// upset the borrowck

View File

@ -71,11 +71,11 @@
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
"lastModified": 1711122363,
"narHash": "sha256-Yw/t9HCY9U/EpkXlzW+5o/WEpZKUNgrSbcuHOZFpAXU=",
"lastModified": 1711676035,
"narHash": "sha256-m8AHpQR0WVVEoWWOpXVNvqfB5cXiMRnHfcYYm2zg7tk=",
"owner": "cachix",
"repo": "devenv",
"rev": "63c7109f20b5ded0bc07f95ece9518bbb7fdea5b",
"rev": "19098329b4e79f60705d7fb241a3617266658f98",
"type": "github"
},
"original": {
@ -468,11 +468,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1711001935,
"narHash": "sha256-URtGpHue7HHZK0mrHnSf8wJ6OmMKYSsoLmJybrOLFSQ=",
"lastModified": 1711523803,
"narHash": "sha256-UKcYiHWHQynzj6CN/vTcix4yd1eCu1uFdsuarupdCQQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "20f77aa09916374aa3141cbc605c955626762c9a",
"rev": "2726f127c15a4cc9810843b96cad73c7eb39e443",
"type": "github"
},
"original": {
@ -581,11 +581,11 @@
]
},
"locked": {
"lastModified": 1711073443,
"narHash": "sha256-PpNb4xq7U5Q/DdX40qe7CijUsqhVVM3VZrhN0+c6Lcw=",
"lastModified": 1711678273,
"narHash": "sha256-7lIB0hMRnfzx/9oSIwTnwXmVnbvVGRoadOCW+1HI5zY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "eec55ba9fcde6be4c63942827247e42afef7fafe",
"rev": "42a168449605950935f15ea546f6f770e5f7f629",
"type": "github"
},
"original": {

View File

@ -14,16 +14,16 @@ eula = false
[dependencies]
clap = { version = "4.5.4", features = ["derive", "wrap_help"] }
color-eyre = "0.6.3"
diesel = "2.1.5"
diesel-async = "0.4.1"
dotenvy = "0.15.7"
envy = "0.4.2"
kitsune-config = { path = "../crates/kitsune-config" }
kitsune-db = { path = "../crates/kitsune-db" }
miette = { version = "7.2.0", features = ["fancy"] }
serde = { version = "1.0.197", features = ["derive"] }
speedy-uuid = { path = "../lib/speedy-uuid" }
tokio = { version = "1.36.0", features = ["full"] }
tokio = { version = "1.37.0", features = ["full"] }
tracing-subscriber = "0.3.18"
[build-dependencies]

View File

@ -1,8 +1,7 @@
use self::{config::Configuration, role::RoleSubcommand};
use clap::{Parser, Subcommand};
use diesel_async::scoped_futures::ScopedFutureExt;
use color_eyre::eyre::Result;
use kitsune_config::database::Configuration as DatabaseConfig;
use miette::{IntoDiagnostic, Result};
mod config;
mod role;
@ -24,11 +23,11 @@ struct App {
#[tokio::main]
async fn main() -> Result<()> {
miette::set_panic_hook();
color_eyre::install()?;
dotenvy::dotenv().ok();
tracing_subscriber::fmt::init();
let config: Configuration = envy::from_env().into_diagnostic()?;
let config: Configuration = envy::from_env()?;
let db_conn = kitsune_db::connect(&DatabaseConfig {
url: config.database_url.into(),
max_connections: 1,
@ -38,18 +37,10 @@ async fn main() -> Result<()> {
let cmd = App::parse();
db_conn
.with_connection(|db_conn| {
async move {
match cmd.subcommand {
AppSubcommand::Role(cmd) => self::role::handle(cmd, db_conn).await?,
}
Ok::<_, miette::Report>(())
}
.scoped()
})
.await?;
let mut db_conn = db_conn.get().await?;
match cmd.subcommand {
AppSubcommand::Role(cmd) => self::role::handle(cmd, &mut db_conn).await?,
}
Ok(())
}

View File

@ -1,11 +1,11 @@
use clap::{Args, Subcommand, ValueEnum};
use color_eyre::eyre::Result;
use diesel::{BelongingToDsl, BoolExpressionMethods, ExpressionMethods, QueryDsl};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use kitsune_db::model::{
user::User,
user_role::{NewUserRole, Role as DbRole, UserRole},
};
use miette::{IntoDiagnostic, Result};
use speedy_uuid::Uuid;
#[derive(Subcommand)]
@ -57,8 +57,7 @@ async fn add_role(db_conn: &mut AsyncPgConnection, username_str: &str, role: Rol
let user = users
.filter(username.eq(username_str))
.first::<User>(db_conn)
.await
.into_diagnostic()?;
.await?;
let new_role = NewUserRole {
id: Uuid::now_v7(),
@ -69,8 +68,7 @@ async fn add_role(db_conn: &mut AsyncPgConnection, username_str: &str, role: Rol
diesel::insert_into(users_roles::table)
.values(&new_role)
.execute(db_conn)
.await
.into_diagnostic()?;
.await?;
Ok(())
}
@ -81,13 +79,11 @@ async fn list_roles(db_conn: &mut AsyncPgConnection, username_str: &str) -> Resu
let user: User = users::table
.filter(users::username.eq(username_str))
.first(db_conn)
.await
.into_diagnostic()?;
.await?;
let roles = UserRole::belonging_to(&user)
.load::<UserRole>(db_conn)
.await
.into_diagnostic()?;
.await?;
println!("User \"{username_str}\" has the following roles:");
for role in roles {
@ -107,8 +103,7 @@ async fn remove_role(
let user = users::table
.filter(users::username.eq(username_str))
.first::<User>(db_conn)
.await
.into_diagnostic()?;
.await?;
diesel::delete(
users_roles::table.filter(
@ -118,8 +113,7 @@ async fn remove_role(
),
)
.execute(db_conn)
.await
.into_diagnostic()?;
.await?;
Ok(())
}

View File

@ -14,6 +14,7 @@ eula = false
[dependencies]
athena = { path = "../lib/athena" }
clap = { version = "4.5.4", features = ["derive", "wrap_help"] }
color-eyre = "0.6.3"
just-retry = { path = "../lib/just-retry" }
kitsune-config = { path = "../crates/kitsune-config" }
kitsune-core = { path = "../crates/kitsune-core" }
@ -26,7 +27,6 @@ kitsune-observability = { path = "../crates/kitsune-observability" }
kitsune-service = { path = "../crates/kitsune-service" }
kitsune-url = { path = "../crates/kitsune-url" }
kitsune-wasm-mrf = { path = "../crates/kitsune-wasm-mrf" }
miette = { version = "7.2.0", features = ["fancy"] }
mimalloc = "0.1.39"
multiplex-pool = { path = "../lib/multiplex-pool" }
redis = { version = "0.25.2", default-features = false, features = [
@ -34,7 +34,7 @@ redis = { version = "0.25.2", default-features = false, features = [
"connection-manager",
"tokio-rustls-comp",
] }
tokio = { version = "1.36.0", features = ["full"] }
tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
typed-builder = "0.18.1"

View File

@ -1,4 +1,5 @@
use clap::Parser;
use color_eyre::eyre;
use kitsune_config::Configuration;
use kitsune_core::consts::VERSION;
use kitsune_federation_filter::FederationFilter;
@ -6,7 +7,6 @@ use kitsune_job_runner::JobDispatcherState;
use kitsune_service::{attachment::AttachmentService, prepare};
use kitsune_url::UrlService;
use kitsune_wasm_mrf::MrfService;
use miette::IntoDiagnostic;
use std::path::PathBuf;
#[global_allocator]
@ -22,8 +22,8 @@ struct Args {
}
#[tokio::main]
async fn main() -> miette::Result<()> {
miette::set_panic_hook();
async fn main() -> eyre::Result<()> {
color_eyre::install()?;
let args = Args::parse();
let config = Configuration::load(args.config).await?;
@ -31,9 +31,8 @@ async fn main() -> miette::Result<()> {
kitsune_observability::initialise(env!("CARGO_PKG_NAME"), &config)?;
let db_pool = kitsune_db::connect(&config.database).await?;
let job_queue = kitsune_job_runner::prepare_job_queue(db_pool.clone(), &config.job_queue)
.await
.into_diagnostic()?;
let job_queue =
kitsune_job_runner::prepare_job_queue(db_pool.clone(), &config.job_queue).await?;
let mrf_service = MrfService::from_config(&config.mrf).await?;
let url_service = UrlService::builder()

View File

@ -69,7 +69,6 @@ kitsune-util = { path = "../crates/kitsune-util" }
kitsune-wasm-mrf = { path = "../crates/kitsune-wasm-mrf" }
kitsune-webfinger = { path = "../crates/kitsune-webfinger" }
metrics = "=0.22.0"
miette = { version = "7.2.0", features = ["fancy"] }
mimalloc = "0.1.39"
mime = "0.3.17"
mime_guess = { version = "2.0.4", default-features = false }
@ -90,7 +89,7 @@ strum = { version = "0.26.2", features = ["derive", "phf"] }
tempfile = "3.10.1"
thiserror = "1.0.58"
time = "0.3.34"
tokio = { version = "1.36.0", features = ["full"] }
tokio = { version = "1.37.0", features = ["full"] }
tokio-util = { version = "0.7.10", features = ["compat"] }
tower-stop-using-brave = { path = "../lib/tower-stop-using-brave" }
tower-x-clacks-overhead = { path = "../lib/tower-x-clacks-overhead" }
@ -126,6 +125,7 @@ kitsune-mastodon = { path = "../crates/kitsune-mastodon", optional = true }
# "oidc" feature
kitsune-oidc = { path = "../crates/kitsune-oidc", optional = true }
color-eyre = "0.6.3"
[build-dependencies]
camino = "1.1.6"

View File

@ -4,14 +4,12 @@ use axum::{
extract::multipart::MultipartError,
response::{IntoResponse, Response},
};
use color_eyre::eyre;
use diesel_async::pooled_connection::bb8;
use http::StatusCode;
use kitsune_core::error::{BoxError, HttpError};
use kitsune_core::error::HttpError;
use kitsune_service::error::{Error as ServiceError, PostError};
use std::{
fmt::{Debug, Display},
str::ParseBoolError,
};
use std::{fmt::Debug, str::ParseBoolError};
use thiserror::Error;
pub type Result<T, E = Error> = std::result::Result<T, E>;
@ -41,7 +39,7 @@ pub enum Error {
Der(#[from] der::Error),
#[error(transparent)]
Fetcher(BoxError),
Fetcher(eyre::Report),
#[error(transparent)]
Http(#[from] http::Error),
@ -115,18 +113,6 @@ pub enum OAuth2Error {
Web(#[from] oxide_auth_axum::WebError),
}
impl<E> From<kitsune_db::PoolError<E>> for Error
where
E: Into<Error> + Debug + Display,
{
fn from(value: kitsune_db::PoolError<E>) -> Self {
match value {
kitsune_db::PoolError::Pool(err) => err.into(),
kitsune_db::PoolError::User(err) => err.into(),
}
}
}
impl From<Error> for Response {
fn from(err: Error) -> Response {
err.into_response()

View File

@ -11,10 +11,11 @@ use diesel_async::RunQueryDsl;
use headers::{authorization::Bearer, Authorization};
use http::request::Parts;
use kitsune_db::{
catch_error,
model::{account::Account, user::User},
schema::{accounts, oauth2_access_tokens, users},
with_connection,
};
use scoped_futures::ScopedFutureExt;
use time::OffsetDateTime;
/// Mastodon-specific auth extractor alias
@ -62,16 +63,14 @@ impl<const ENFORCE_EXPIRATION: bool> FromRequestParts<Zustand>
.filter(oauth2_access_tokens::expires_at.gt(OffsetDateTime::now_utc()));
}
let (user, account) = state
.db_pool
.with_connection(|db_conn| {
user_account_query
.select(<(User, Account)>::as_select())
.get_result(db_conn)
.scoped()
})
.await
.map_err(Error::from)?;
let (user, account) = catch_error!(with_connection!(state.db_pool, |db_conn| {
user_account_query
.select(<(User, Account)>::as_select())
.get_result(db_conn)
.await
.map_err(Error::from)
}))
.map_err(Error::from)??;
Ok(Self(UserData { account, user }))
}

View File

@ -15,7 +15,7 @@ use diesel_async::RunQueryDsl;
use http::StatusCode;
use http_body_util::BodyExt;
use kitsune_core::{error::HttpError, traits::fetcher::AccountFetchOptions};
use kitsune_db::{model::account::Account, schema::accounts, PgPool};
use kitsune_db::{model::account::Account, schema::accounts, with_connection, PgPool};
use kitsune_type::ap::Activity;
use kitsune_wasm_mrf::Outcome;
use scoped_futures::ScopedFutureExt;
@ -116,20 +116,18 @@ impl FromRequest<Zustand, Body> for SignedActivity {
async fn verify_signature(
req: &http::Request<()>,
db_conn: &PgPool,
db_pool: &PgPool,
expected_account: Option<&Account>,
) -> Result<bool> {
let is_valid = http_signatures::cavage::easy::verify(req, |key_id| {
async move {
let remote_user: Account = db_conn
.with_connection(|db_conn| {
accounts::table
.filter(accounts::public_key_id.eq(key_id))
.select(Account::as_select())
.first(db_conn)
.scoped()
})
.await?;
let remote_user: Account = with_connection!(db_pool, |db_conn| {
accounts::table
.filter(accounts::public_key_id.eq(key_id))
.select(Account::as_select())
.first(db_conn)
.await
})?;
// If we have an expected account, which we have in the case of an incoming new activity,
// then we do this comparison.

View File

@ -1,7 +1,6 @@
use crate::http::graphql::{types::Instance, ContextExt};
use async_graphql::{Context, Object, Result};
use kitsune_core::consts::VERSION;
use std::convert::Into;
#[derive(Default)]
pub struct InstanceQuery;

View File

@ -12,9 +12,9 @@ use kitsune_db::{
account::Account as DbAccount, media_attachment::MediaAttachment as DbMediaAttachment,
},
schema::media_attachments,
with_connection,
};
use kitsune_service::account::GetPosts;
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use time::OffsetDateTime;
@ -41,20 +41,15 @@ impl Account {
let db_pool = &ctx.state().db_pool;
if let Some(avatar_id) = self.avatar_id {
db_pool
.with_connection(|db_conn| {
async move {
media_attachments::table
.find(avatar_id)
.get_result::<DbMediaAttachment>(db_conn)
.await
.optional()
.map(|attachment| attachment.map(Into::into))
}
.scoped()
})
.await
.map_err(Into::into)
with_connection!(db_pool, |db_conn| {
media_attachments::table
.find(avatar_id)
.get_result::<DbMediaAttachment>(db_conn)
.await
.optional()
.map(|attachment| attachment.map(Into::into))
})
.map_err(Into::into)
} else {
Ok(None)
}
@ -64,20 +59,15 @@ impl Account {
let db_pool = &ctx.state().db_pool;
if let Some(header_id) = self.header_id {
db_pool
.with_connection(|db_conn| {
async move {
media_attachments::table
.find(header_id)
.get_result::<DbMediaAttachment>(db_conn)
.await
.optional()
.map(|attachment| attachment.map(Into::into))
}
.scoped()
})
.await
.map_err(Into::into)
with_connection!(db_pool, |db_conn| {
media_attachments::table
.find(header_id)
.get_result::<DbMediaAttachment>(db_conn)
.await
.optional()
.map(|attachment| attachment.map(Into::into))
})
.map_err(Into::into)
} else {
Ok(None)
}

View File

@ -7,8 +7,8 @@ use futures_util::TryStreamExt;
use kitsune_db::{
model::{media_attachment::MediaAttachment as DbMediaAttachment, post::Post as DbPost},
schema::{media_attachments, posts_media_attachments},
with_connection,
};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use time::OffsetDateTime;
@ -44,22 +44,17 @@ impl Post {
pub async fn attachments(&self, ctx: &Context<'_>) -> Result<Vec<MediaAttachment>> {
let db_pool = &ctx.state().db_pool;
let attachments = db_pool
.with_connection(|db_conn| {
async move {
media_attachments::table
.inner_join(posts_media_attachments::table)
.filter(posts_media_attachments::post_id.eq(self.id))
.select(DbMediaAttachment::as_select())
.load_stream(db_conn)
.await?
.map_ok(Into::into)
.try_collect()
.await
}
.scoped()
})
.await?;
let attachments = with_connection!(db_pool, |db_conn| {
media_attachments::table
.inner_join(posts_media_attachments::table)
.filter(posts_media_attachments::post_id.eq(self.id))
.select(DbMediaAttachment::as_select())
.load_stream(db_conn)
.await?
.map_ok(Into::into)
.try_collect()
.await
})?;
Ok(attachments)
}

View File

@ -6,8 +6,8 @@ use diesel_async::RunQueryDsl;
use kitsune_db::{
model::{account::Account as DbAccount, user::User as DbUser},
schema::{accounts, users},
with_connection,
};
use scoped_futures::ScopedFutureExt;
use speedy_uuid::Uuid;
use time::OffsetDateTime;
@ -27,21 +27,16 @@ pub struct User {
impl User {
pub async fn account(&self, ctx: &Context<'_>) -> Result<Account> {
let db_pool = &ctx.state().db_pool;
db_pool
.with_connection(|db_conn| {
async move {
users::table
.find(self.id)
.inner_join(accounts::table)
.select(DbAccount::as_select())
.get_result::<DbAccount>(db_conn)
.await
.map(Into::into)
}
.scoped()
})
.await
.map_err(Into::into)
with_connection!(db_pool, |db_conn| {
users::table
.find(self.id)
.inner_join(accounts::table)
.select(DbAccount::as_select())
.get_result::<DbAccount>(db_conn)
.await
.map(Into::into)
})
.map_err(Into::into)
}
}

View File

@ -7,10 +7,9 @@ use axum_extra::extract::Query;
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use futures_util::StreamExt;
use kitsune_db::{model::account::Account, schema::accounts, PgPool};
use kitsune_db::{model::account::Account, schema::accounts, with_connection, PgPool};
use kitsune_mastodon::MastodonMapper;
use kitsune_type::mastodon::relationship::Relationship;
use scoped_futures::ScopedFutureExt;
use serde::Deserialize;
use speedy_uuid::Uuid;
use utoipa::IntoParams;
@ -39,15 +38,13 @@ pub async fn get(
State(mastodon_mapper): State<MastodonMapper>,
Query(query): Query<RelationshipQuery>,
) -> Result<Json<Vec<Relationship>>> {
let mut account_stream = db_pool
.with_connection(|db_conn| {
accounts::table
.filter(accounts::id.eq_any(&query.id))
.select(Account::as_select())
.load_stream::<Account>(db_conn)
.scoped()
})
.await?;
let mut account_stream = with_connection!(db_pool, |db_conn| {
accounts::table
.filter(accounts::id.eq_any(&query.id))
.select(Account::as_select())
.load_stream::<Account>(db_conn)
.await
})?;
let mut relationships = Vec::with_capacity(query.id.len());
while let Some(account) = account_stream.next().await.transpose()? {

Some files were not shown because too many files have changed in this diff Show More