mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-10-01 01:36:12 -04:00
Merge remote-tracking branch 'origin/main' into vote_display_mode
This commit is contained in:
commit
b2add170b3
@ -60,7 +60,7 @@ steps:
|
||||
# store cargo data in repo folder so that it gets cached between steps
|
||||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
# need make existing toolchain available
|
||||
- rustup component add rustfmt
|
||||
- cargo +nightly fmt -- --check
|
||||
|
||||
cargo_machete:
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2875,6 +2875,7 @@ version = "0.19.3"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
"deser-hjson",
|
||||
"diesel",
|
||||
"doku",
|
||||
|
@ -89,7 +89,7 @@ unwrap_used = "deny"
|
||||
lemmy_api = { version = "=0.19.3", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.3", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.3", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.3", path = "./crates/utils" }
|
||||
lemmy_utils = { version = "=0.19.3", path = "./crates/utils", default-features = false }
|
||||
lemmy_db_schema = { version = "=0.19.3", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.3", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.3", path = "./crates/routes" }
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LE
|
||||
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
|
||||
|
||||
# pictrs setup
|
||||
if [ ! -f "pict-rs" ]; then
|
||||
if [ ! -f "api_tests/pict-rs" ]; then
|
||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||
chmod +x api_tests/pict-rs
|
||||
fi
|
||||
|
@ -37,9 +37,11 @@ import {
|
||||
waitForPost,
|
||||
alphaUrl,
|
||||
loginUser,
|
||||
createCommunity,
|
||||
getPosts,
|
||||
} from "./shared";
|
||||
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
||||
import { EditSite, ResolveObject } from "lemmy-js-client";
|
||||
import { EditSite, ListingType, ResolveObject } from "lemmy-js-client";
|
||||
|
||||
let betaCommunity: CommunityView | undefined;
|
||||
|
||||
@ -225,6 +227,26 @@ test("Sticky a post", async () => {
|
||||
expect(betaPost3?.post.featured_community).toBe(false);
|
||||
});
|
||||
|
||||
test("Collection of featured posts gets federated", async () => {
|
||||
// create a new community and feature a post
|
||||
let community = await createCommunity(alpha);
|
||||
let post = await createPost(alpha, community.community_view.community.id);
|
||||
let featuredPost = await featurePost(alpha, true, post.post_view.post);
|
||||
expect(featuredPost.post_view.post.featured_community).toBe(true);
|
||||
|
||||
// fetch the community, ensure that post is also fetched and marked as featured
|
||||
let betaCommunity = await resolveCommunity(
|
||||
beta,
|
||||
community.community_view.community.actor_id,
|
||||
);
|
||||
|
||||
const betaPost = await waitForPost(
|
||||
beta,
|
||||
post.post_view.post,
|
||||
post => post?.post.featured_community === true,
|
||||
);
|
||||
});
|
||||
|
||||
test("Lock a post", async () => {
|
||||
if (!betaCommunity) {
|
||||
throw "Missing beta community";
|
||||
|
@ -18,7 +18,7 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lemmy_utils = { workspace = true }
|
||||
lemmy_utils = { workspace = true, features = ["default"] }
|
||||
lemmy_db_schema = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_moderator = { workspace = true, features = ["full"] }
|
||||
|
@ -20,10 +20,10 @@ workspace = true
|
||||
full = [
|
||||
"tracing",
|
||||
"rosetta-i18n",
|
||||
"lemmy_utils",
|
||||
"lemmy_db_views/full",
|
||||
"lemmy_db_views_actor/full",
|
||||
"lemmy_db_views_moderator/full",
|
||||
"lemmy_utils/default",
|
||||
"activitypub_federation",
|
||||
"encoding",
|
||||
"reqwest-middleware",
|
||||
@ -44,7 +44,7 @@ lemmy_db_views = { workspace = true }
|
||||
lemmy_db_views_moderator = { workspace = true }
|
||||
lemmy_db_views_actor = { workspace = true }
|
||||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
lemmy_utils = { workspace = true, features = ["error-type"] }
|
||||
activitypub_federation = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
|
@ -23,7 +23,9 @@ pub extern crate lemmy_db_schema;
|
||||
pub extern crate lemmy_db_views;
|
||||
pub extern crate lemmy_db_views_actor;
|
||||
pub extern crate lemmy_db_views_moderator;
|
||||
pub extern crate lemmy_utils;
|
||||
|
||||
pub use lemmy_utils::LemmyErrorType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -53,7 +53,9 @@ pub async fn fetch_link_metadata(
|
||||
// https://github.com/LemmyNet/lemmy/issues/1964
|
||||
let html_bytes = response.bytes().await.map_err(LemmyError::from)?.to_vec();
|
||||
|
||||
let opengraph_data = extract_opengraph_data(&html_bytes, url).unwrap_or_default();
|
||||
let opengraph_data = extract_opengraph_data(&html_bytes, url)
|
||||
.map_err(|e| info!("{e}"))
|
||||
.unwrap_or_default();
|
||||
let thumbnail = extract_thumbnail_from_opengraph_data(
|
||||
url,
|
||||
&opengraph_data,
|
||||
@ -96,7 +98,7 @@ fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result<OpenGraphData,
|
||||
.ok_or(LemmyErrorType::NoLinesInHtml)?
|
||||
.to_lowercase();
|
||||
|
||||
if !first_line.starts_with("<!doctype html>") {
|
||||
if !first_line.starts_with("<!doctype html") {
|
||||
Err(LemmyErrorType::SiteMetadataPageIsNotDoctypeHtml)?
|
||||
}
|
||||
|
||||
|
@ -967,8 +967,6 @@ mod tests {
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::utils::{honeypot_check, limit_expire_time, password_length_check};
|
||||
use chrono::{Days, Utc};
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
|
@ -13,7 +13,7 @@ repository.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lemmy_utils = { workspace = true }
|
||||
lemmy_utils = { workspace = true, features = ["default"] }
|
||||
lemmy_db_schema = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_actor = { workspace = true, features = ["full"] }
|
||||
|
@ -18,7 +18,7 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lemmy_utils = { workspace = true }
|
||||
lemmy_utils = { workspace = true, features = ["default"] }
|
||||
lemmy_db_schema = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_actor = { workspace = true, features = ["full"] }
|
||||
|
@ -6,11 +6,14 @@ use activitypub_federation::{
|
||||
config::Data,
|
||||
kinds::collection::OrderedCollectionType,
|
||||
protocol::verification::verify_domains_match,
|
||||
traits::{ActivityHandler, Collection, Object},
|
||||
traits::{Collection, Object},
|
||||
};
|
||||
use futures::future::{join_all, try_join_all};
|
||||
use lemmy_api_common::{context::LemmyContext, utils::generate_featured_url};
|
||||
use lemmy_db_schema::{source::post::Post, utils::FETCH_LIMIT_MAX};
|
||||
use lemmy_db_schema::{
|
||||
source::{community::Community, post::Post},
|
||||
utils::FETCH_LIMIT_MAX,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
@ -55,35 +58,36 @@ impl Collection for ApubCommunityFeatured {
|
||||
|
||||
async fn from_json(
|
||||
apub: Self::Kind,
|
||||
_owner: &Self::Owner,
|
||||
data: &Data<Self::DataType>,
|
||||
owner: &Self::Owner,
|
||||
context: &Data<Self::DataType>,
|
||||
) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut posts = apub.ordered_items;
|
||||
if posts.len() as i64 > FETCH_LIMIT_MAX {
|
||||
posts = posts
|
||||
let mut pages = apub.ordered_items;
|
||||
if pages.len() as i64 > FETCH_LIMIT_MAX {
|
||||
pages = pages
|
||||
.get(0..(FETCH_LIMIT_MAX as usize))
|
||||
.unwrap_or_default()
|
||||
.to_vec();
|
||||
}
|
||||
|
||||
// We intentionally ignore errors here. This is because the outbox might contain posts from old
|
||||
// Lemmy versions, or from other software which we cant parse. In that case, we simply skip the
|
||||
// item and only parse the ones that work.
|
||||
// process items in parallel, to avoid long delay from fetch_site_metadata() and other processing
|
||||
join_all(posts.into_iter().map(|post| {
|
||||
let stickied_posts: Vec<Post> = join_all(pages.into_iter().map(|page| {
|
||||
async {
|
||||
// use separate request counter for each item, otherwise there will be problems with
|
||||
// parallel processing
|
||||
let verify = post.verify(data).await;
|
||||
if verify.is_ok() {
|
||||
post.receive(data).await.ok();
|
||||
}
|
||||
ApubPost::verify(&page, &apub.id, context).await?;
|
||||
ApubPost::from_json(page, context).await
|
||||
}
|
||||
}))
|
||||
.await;
|
||||
.await
|
||||
// ignore any failed or unparseable items
|
||||
.into_iter()
|
||||
.filter_map(|p| p.ok().map(|p| p.0))
|
||||
.collect();
|
||||
|
||||
Community::set_featured_posts(owner.id, stickied_posts, &mut context.pool()).await?;
|
||||
|
||||
// This return value is unused, so just set an empty vec
|
||||
Ok(ApubCommunityFeatured(()))
|
||||
|
@ -123,10 +123,7 @@ pub(crate) mod tests {
|
||||
use crate::protocol::objects::{group::Group, tombstone::Tombstone};
|
||||
use actix_web::body::to_bytes;
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
},
|
||||
source::{community::CommunityInsertForm, instance::Instance},
|
||||
traits::Crud,
|
||||
CommunityVisibility,
|
||||
};
|
||||
|
@ -166,7 +166,7 @@ impl Object for ApubCommunity {
|
||||
moderators_url: group.attributed_to.clone().map(Into::into),
|
||||
posting_restricted_to_mods: group.posting_restricted_to_mods,
|
||||
instance_id,
|
||||
featured_url: group.featured.map(Into::into),
|
||||
featured_url: group.featured.clone().map(Into::into),
|
||||
..Default::default()
|
||||
};
|
||||
let languages =
|
||||
@ -184,6 +184,9 @@ impl Object for ApubCommunity {
|
||||
spawn_try_task(async move {
|
||||
group.outbox.dereference(&community_, &context_).await?;
|
||||
group.followers.dereference(&community_, &context_).await?;
|
||||
if let Some(featured) = group.featured {
|
||||
featured.dereference(&community_, &context_).await?;
|
||||
}
|
||||
if let Some(moderators) = group.attributed_to {
|
||||
moderators.dereference(&community_, &context_).await?;
|
||||
}
|
||||
@ -254,7 +257,7 @@ pub(crate) mod tests {
|
||||
protocol::tests::file_to_json_object,
|
||||
};
|
||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
||||
use lemmy_db_schema::{source::site::Site, traits::Crud};
|
||||
use lemmy_db_schema::source::site::Site;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
@ -218,7 +218,6 @@ pub(in crate::objects) async fn fetch_instance_actor_for_object<T: Into<Url> + C
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::protocol::tests::file_to_json_object;
|
||||
use lemmy_db_schema::traits::Crud;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
@ -225,7 +225,7 @@ pub(crate) mod tests {
|
||||
protocol::{objects::instance::Instance, tests::file_to_json_object},
|
||||
};
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use lemmy_db_schema::{source::site::Site, traits::Crud};
|
||||
use lemmy_db_schema::source::site::Site;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
@ -34,7 +34,6 @@ use lemmy_api_common::{
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
self,
|
||||
source::{
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
@ -297,7 +296,6 @@ mod tests {
|
||||
community::{tests::parse_lemmy_community, ApubCommunity},
|
||||
instance::ApubSite,
|
||||
person::{tests::parse_lemmy_person, ApubPerson},
|
||||
post::ApubPost,
|
||||
},
|
||||
protocol::tests::file_to_json_object,
|
||||
};
|
||||
|
@ -8,7 +8,6 @@ use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use strum_macros::Display;
|
||||
use url::Url;
|
||||
|
||||
|
@ -19,6 +19,6 @@ diesel = { workspace = true }
|
||||
diesel-async = { workspace = true }
|
||||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_db_views = { workspace = true, features = ["full"] }
|
||||
lemmy_utils = { workspace = true }
|
||||
lemmy_utils = { workspace = true, features = ["default"] }
|
||||
tokio = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
@ -48,7 +48,7 @@ strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
activitypub_federation = { workspace = true, optional = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
lemmy_utils = { workspace = true, optional = true, features = ["default"] }
|
||||
bcrypt = { workspace = true, optional = true }
|
||||
diesel = { workspace = true, features = [
|
||||
"postgres",
|
||||
|
@ -306,9 +306,9 @@ impl CommunityLanguage {
|
||||
// tracing::warn!("unique error: {_info:#?}");
|
||||
// _info.constraint_name() should be = "community_language_community_id_language_id_key"
|
||||
return Ok(());
|
||||
} else {
|
||||
insert_res?;
|
||||
}
|
||||
insert_res?;
|
||||
|
||||
Ok(())
|
||||
}) as _
|
||||
})
|
||||
@ -391,27 +391,13 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
impls::actor_language::{
|
||||
convert_read_languages,
|
||||
convert_update_languages,
|
||||
default_post_language,
|
||||
get_conn,
|
||||
CommunityLanguage,
|
||||
DbPool,
|
||||
Language,
|
||||
LanguageId,
|
||||
LocalUserLanguage,
|
||||
QueryDsl,
|
||||
RunQueryDsl,
|
||||
SiteLanguage,
|
||||
},
|
||||
source::{
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
local_site::{LocalSite, LocalSiteInsertForm},
|
||||
local_user::{LocalUser, LocalUserInsertForm},
|
||||
person::{Person, PersonInsertForm},
|
||||
site::{Site, SiteInsertForm},
|
||||
site::SiteInsertForm,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::build_db_pool_for_tests,
|
||||
|
@ -14,6 +14,7 @@ use crate::{
|
||||
CommunityPersonBanForm,
|
||||
CommunityUpdateForm,
|
||||
},
|
||||
post::Post,
|
||||
},
|
||||
traits::{ApubActor, Bannable, Crud, Followable, Joinable},
|
||||
utils::{functions::lower, get_conn, DbPool},
|
||||
@ -27,6 +28,7 @@ use diesel::{
|
||||
result::Error,
|
||||
select,
|
||||
sql_types,
|
||||
update,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
@ -137,6 +139,42 @@ impl Community {
|
||||
}
|
||||
Err(diesel::NotFound)
|
||||
}
|
||||
|
||||
pub async fn set_featured_posts(
|
||||
community_id: CommunityId,
|
||||
posts: Vec<Post>,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), Error> {
|
||||
use crate::schema::post;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
for p in &posts {
|
||||
debug_assert!(p.community_id == community_id);
|
||||
}
|
||||
conn
|
||||
.build_transaction()
|
||||
.run(|conn| {
|
||||
Box::pin(async move {
|
||||
update(
|
||||
// first remove all existing featured posts
|
||||
post::table,
|
||||
)
|
||||
.filter(post::dsl::community_id.eq(community_id))
|
||||
.set(post::dsl::featured_community.eq(false))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
// then mark the given posts as featured
|
||||
let post_ids: Vec<_> = posts.iter().map(|p| p.id).collect();
|
||||
update(post::table)
|
||||
.filter(post::dsl::id.eq_any(post_ids))
|
||||
.set(post::dsl::featured_community.eq(true))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}) as _
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl CommunityModerator {
|
||||
|
@ -248,9 +248,8 @@ pub fn limit_and_offset(
|
||||
Some(page) => {
|
||||
if page < 1 {
|
||||
return Err(QueryBuilderError("Page is < 1".into()));
|
||||
} else {
|
||||
page
|
||||
}
|
||||
page
|
||||
}
|
||||
None => 1,
|
||||
};
|
||||
@ -260,9 +259,8 @@ pub fn limit_and_offset(
|
||||
return Err(QueryBuilderError(
|
||||
format!("Fetch limit is > {FETCH_LIMIT_MAX}").into(),
|
||||
));
|
||||
} else {
|
||||
limit
|
||||
}
|
||||
limit
|
||||
}
|
||||
None => FETCH_LIMIT_DEFAULT,
|
||||
};
|
||||
@ -542,8 +540,7 @@ mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::{fuzzy_search, *};
|
||||
use crate::utils::is_email_regex;
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
|
@ -29,7 +29,7 @@ full = [
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
lemmy_utils = { workspace = true, optional = true, features = ["default"] }
|
||||
diesel = { workspace = true, optional = true }
|
||||
diesel-async = { workspace = true, optional = true }
|
||||
diesel_ltree = { workspace = true, optional = true }
|
||||
|
@ -16,7 +16,7 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lemmy_utils = { workspace = true }
|
||||
lemmy_utils = { workspace = true, features = ["default"] }
|
||||
lemmy_db_views = { workspace = true }
|
||||
lemmy_db_views_actor = { workspace = true }
|
||||
lemmy_db_schema = { workspace = true }
|
||||
|
@ -13,42 +13,82 @@ name = "lemmy_utils"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[[bin]]
|
||||
name = "lemmy_util_bin"
|
||||
path = "src/main.rs"
|
||||
required-features = ["default"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
full = ["ts-rs"]
|
||||
default = [
|
||||
"error-type",
|
||||
"dep:serde_json",
|
||||
"dep:anyhow",
|
||||
"dep:tracing-error",
|
||||
"dep:diesel",
|
||||
"dep:http",
|
||||
"dep:actix-web",
|
||||
"dep:reqwest-middleware",
|
||||
"dep:tracing",
|
||||
"dep:actix-web",
|
||||
"dep:deser-hjson",
|
||||
"dep:regex",
|
||||
"dep:urlencoding",
|
||||
"dep:doku",
|
||||
"dep:url",
|
||||
"dep:once_cell",
|
||||
"dep:smart-default",
|
||||
"dep:enum-map",
|
||||
"dep:futures",
|
||||
"dep:tokio",
|
||||
"dep:openssl",
|
||||
"dep:html2text",
|
||||
"dep:lettre",
|
||||
"dep:uuid",
|
||||
"dep:rosetta-i18n",
|
||||
"dep:itertools",
|
||||
"dep:markdown-it",
|
||||
|
||||
]
|
||||
full = ["default", "dep:ts-rs"]
|
||||
error-type = ["dep:serde", "dep:strum"]
|
||||
|
||||
[dependencies]
|
||||
regex = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
url = { workspace = true }
|
||||
actix-web = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
reqwest-middleware = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
regex = { workspace = true, optional = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
tracing-error = { workspace = true, optional = true }
|
||||
itertools = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
once_cell = { workspace = true, optional = true }
|
||||
url = { workspace = true, optional = true }
|
||||
actix-web = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true, optional = true }
|
||||
reqwest-middleware = { workspace = true, optional = true }
|
||||
strum = { workspace = true, optional = true }
|
||||
strum_macros = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
diesel = { workspace = true, features = ["chrono"] }
|
||||
http = { workspace = true }
|
||||
doku = { workspace = true, features = ["url-2"] }
|
||||
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||
rosetta-i18n = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
openssl = "0.10.63"
|
||||
html2text = "0.6.0"
|
||||
deser-hjson = "2.2.4"
|
||||
smart-default = "0.7.1"
|
||||
lettre = { version = "0.11.3", features = ["tokio1", "tokio1-native-tls"] }
|
||||
markdown-it = "0.6.0"
|
||||
futures = { workspace = true, optional = true }
|
||||
diesel = { workspace = true, features = ["chrono"], optional = true }
|
||||
http = { workspace = true, optional = true }
|
||||
doku = { workspace = true, features = ["url-2"], optional = true }
|
||||
uuid = { workspace = true, features = ["serde", "v4"], optional = true }
|
||||
rosetta-i18n = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
urlencoding = { workspace = true, optional = true }
|
||||
openssl = { version = "0.10.63", optional = true }
|
||||
html2text = { version = "0.6.0", optional = true }
|
||||
deser-hjson = { version = "2.2.4", optional = true }
|
||||
smart-default = { version = "0.7.1", optional = true }
|
||||
lettre = { version = "0.11.3", features = [
|
||||
"tokio1",
|
||||
"tokio1-native-tls",
|
||||
], optional = true }
|
||||
markdown-it = { version = "0.6.0", optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
enum-map = { workspace = true }
|
||||
enum-map = { workspace = true, optional = true }
|
||||
cfg-if = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
reqwest = { workspace = true }
|
||||
|
@ -1,79 +1,15 @@
|
||||
use cfg_if::cfg_if;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
use tracing_error::SpanTrace;
|
||||
#[cfg(feature = "full")]
|
||||
use std::fmt::Debug;
|
||||
use strum_macros::{Display, EnumIter};
|
||||
#[cfg(feature = "ts-rs")]
|
||||
use ts_rs::TS;
|
||||
|
||||
pub type LemmyResult<T> = Result<T, LemmyError>;
|
||||
|
||||
pub struct LemmyError {
|
||||
pub error_type: LemmyErrorType,
|
||||
pub inner: anyhow::Error,
|
||||
pub context: SpanTrace,
|
||||
}
|
||||
|
||||
/// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]]
|
||||
pub const MAX_API_PARAM_ELEMENTS: usize = 10_000;
|
||||
|
||||
impl<T> From<T> for LemmyError
|
||||
where
|
||||
T: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(t: T) -> Self {
|
||||
let cause = t.into();
|
||||
LemmyError {
|
||||
error_type: LemmyErrorType::Unknown(format!("{}", &cause)),
|
||||
inner: cause,
|
||||
context: SpanTrace::capture(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LemmyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("LemmyError")
|
||||
.field("message", &self.error_type)
|
||||
.field("inner", &self.inner)
|
||||
.field("context", &self.context)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LemmyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: ", &self.error_type)?;
|
||||
// print anyhow including trace
|
||||
// https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
|
||||
// this will print the anyhow trace (only if it exists)
|
||||
// and if RUST_BACKTRACE=1, also a full backtrace
|
||||
writeln!(f, "{:?}", self.inner)?;
|
||||
fmt::Display::fmt(&self.context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for LemmyError {
|
||||
fn status_code(&self) -> http::StatusCode {
|
||||
if self.error_type == LemmyErrorType::IncorrectLogin {
|
||||
return http::StatusCode::UNAUTHORIZED;
|
||||
}
|
||||
match self.inner.downcast_ref::<diesel::result::Error>() {
|
||||
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
|
||||
_ => http::StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> actix_web::HttpResponse {
|
||||
actix_web::HttpResponse::build(self.status_code()).json(&self.error_type)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, EnumIter)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, EnumIter, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
#[serde(tag = "error", content = "message", rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
// TODO: order these based on the crate they belong to (utils, federation, db, api)
|
||||
pub enum LemmyErrorType {
|
||||
ReportReasonRequired,
|
||||
@ -231,45 +167,115 @@ pub enum LemmyErrorType {
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl From<LemmyErrorType> for LemmyError {
|
||||
fn from(error_type: LemmyErrorType) -> Self {
|
||||
let inner = anyhow::anyhow!("{}", error_type);
|
||||
LemmyError {
|
||||
error_type,
|
||||
inner,
|
||||
context: SpanTrace::capture(),
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "default")] {
|
||||
|
||||
use tracing_error::SpanTrace;
|
||||
use std::fmt;
|
||||
pub type LemmyResult<T> = Result<T, LemmyError>;
|
||||
|
||||
pub struct LemmyError {
|
||||
pub error_type: LemmyErrorType,
|
||||
pub inner: anyhow::Error,
|
||||
pub context: SpanTrace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LemmyErrorExt<T, E: Into<anyhow::Error>> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
|
||||
}
|
||||
/// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]]
|
||||
pub const MAX_API_PARAM_ELEMENTS: usize = 10_000;
|
||||
|
||||
impl<T, E: Into<anyhow::Error>> LemmyErrorExt<T, E> for Result<T, E> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError> {
|
||||
self.map_err(|error| LemmyError {
|
||||
error_type,
|
||||
inner: error.into(),
|
||||
context: SpanTrace::capture(),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub trait LemmyErrorExt2<T> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
|
||||
fn into_anyhow(self) -> Result<T, anyhow::Error>;
|
||||
}
|
||||
impl<T> From<T> for LemmyError
|
||||
where
|
||||
T: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(t: T) -> Self {
|
||||
let cause = t.into();
|
||||
LemmyError {
|
||||
error_type: LemmyErrorType::Unknown(format!("{}", &cause)),
|
||||
inner: cause,
|
||||
context: SpanTrace::capture(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> LemmyErrorExt2<T> for Result<T, LemmyError> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError> {
|
||||
self.map_err(|mut e| {
|
||||
e.error_type = error_type;
|
||||
e
|
||||
})
|
||||
}
|
||||
// this function can't be an impl From or similar because it would conflict with one of the other broad Into<> implementations
|
||||
fn into_anyhow(self) -> Result<T, anyhow::Error> {
|
||||
self.map_err(|e| e.inner)
|
||||
impl Debug for LemmyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("LemmyError")
|
||||
.field("message", &self.error_type)
|
||||
.field("inner", &self.inner)
|
||||
.field("context", &self.context)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LemmyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: ", &self.error_type)?;
|
||||
// print anyhow including trace
|
||||
// https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
|
||||
// this will print the anyhow trace (only if it exists)
|
||||
// and if RUST_BACKTRACE=1, also a full backtrace
|
||||
writeln!(f, "{:?}", self.inner)?;
|
||||
fmt::Display::fmt(&self.context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for LemmyError {
|
||||
fn status_code(&self) -> http::StatusCode {
|
||||
if self.error_type == LemmyErrorType::IncorrectLogin {
|
||||
return http::StatusCode::UNAUTHORIZED;
|
||||
}
|
||||
match self.inner.downcast_ref::<diesel::result::Error>() {
|
||||
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
|
||||
_ => http::StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> actix_web::HttpResponse {
|
||||
actix_web::HttpResponse::build(self.status_code()).json(&self.error_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LemmyErrorType> for LemmyError {
|
||||
fn from(error_type: LemmyErrorType) -> Self {
|
||||
let inner = anyhow::anyhow!("{}", error_type);
|
||||
LemmyError {
|
||||
error_type,
|
||||
inner,
|
||||
context: SpanTrace::capture(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LemmyErrorExt<T, E: Into<anyhow::Error>> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
|
||||
}
|
||||
|
||||
impl<T, E: Into<anyhow::Error>> LemmyErrorExt<T, E> for Result<T, E> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError> {
|
||||
self.map_err(|error| LemmyError {
|
||||
error_type,
|
||||
inner: error.into(),
|
||||
context: SpanTrace::capture(),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub trait LemmyErrorExt2<T> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError>;
|
||||
fn into_anyhow(self) -> Result<T, anyhow::Error>;
|
||||
}
|
||||
|
||||
impl<T> LemmyErrorExt2<T> for Result<T, LemmyError> {
|
||||
fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result<T, LemmyError> {
|
||||
self.map_err(|mut e| {
|
||||
e.error_type = error_type;
|
||||
e
|
||||
})
|
||||
}
|
||||
// this function can't be an impl From or similar because it would conflict with one of the other broad Into<> implementations
|
||||
fn into_anyhow(self) -> Result<T, anyhow::Error> {
|
||||
self.map_err(|e| e.inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,29 @@
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate smart_default;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
pub mod apub;
|
||||
pub mod cache_header;
|
||||
pub mod email;
|
||||
pub mod error;
|
||||
pub mod rate_limit;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod settings;
|
||||
pub mod utils;
|
||||
pub mod version;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "default")] {
|
||||
pub mod apub;
|
||||
pub mod cache_header;
|
||||
pub mod email;
|
||||
pub mod error;
|
||||
pub mod rate_limit;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod settings;
|
||||
pub mod utils;
|
||||
pub mod version;
|
||||
} else {
|
||||
mod error;
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "error-type")] {
|
||||
pub use error::LemmyErrorType;
|
||||
}
|
||||
}
|
||||
|
||||
use error::LemmyError;
|
||||
use futures::Future;
|
||||
use std::time::Duration;
|
||||
use tracing::Instrument;
|
||||
|
||||
pub type ConnectionId = usize;
|
||||
|
||||
@ -35,10 +41,14 @@ macro_rules! location_info {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
/// tokio::spawn, but accepts a future that may fail and also
|
||||
/// * logs errors
|
||||
/// * attaches the spawned task to the tracing span of the caller for better logging
|
||||
pub fn spawn_try_task(task: impl Future<Output = Result<(), LemmyError>> + Send + 'static) {
|
||||
pub fn spawn_try_task(
|
||||
task: impl futures::Future<Output = Result<(), error::LemmyError>> + Send + 'static,
|
||||
) {
|
||||
use tracing::Instrument;
|
||||
tokio::spawn(
|
||||
async {
|
||||
if let Err(e) = task.await {
|
||||
|
@ -6,6 +6,7 @@ use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
time::Instant,
|
||||
};
|
||||
use strum_macros::AsRefStr;
|
||||
use tracing::debug;
|
||||
|
||||
static START_TIME: Lazy<Instant> = Lazy::new(Instant::now);
|
||||
|
@ -1,8 +1,4 @@
|
||||
use crate::{
|
||||
error::LemmyError,
|
||||
location_info,
|
||||
settings::structs::{PictrsConfig, Settings},
|
||||
};
|
||||
use crate::{error::LemmyError, location_info};
|
||||
use anyhow::{anyhow, Context};
|
||||
use deser_hjson::from_str;
|
||||
use once_cell::sync::Lazy;
|
||||
@ -12,8 +8,7 @@ use urlencoding::encode;
|
||||
|
||||
pub mod structs;
|
||||
|
||||
use crate::settings::structs::PictrsImageMode;
|
||||
use structs::DatabaseConnection;
|
||||
use structs::{DatabaseConnection, PictrsConfig, PictrsImageMode, Settings};
|
||||
|
||||
static DEFAULT_CONFIG_FILE: &str = "config/config.hjson";
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use doku::Document;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smart_default::SmartDefault;
|
||||
use std::{
|
||||
env,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
|
Loading…
Reference in New Issue
Block a user