Merge branch 'main' into nested-comments-stack-overflow

This commit is contained in:
Felix Ableitner 2024-09-13 11:26:39 +02:00
commit 62e4a13c5a
45 changed files with 612 additions and 1051 deletions

View file

@ -157,7 +157,7 @@ steps:
CARGO_HOME: .cargo_home
commands:
- rustup component add clippy
- cargo clippy --workspace --tests --all-targets --features console -- -D warnings
- cargo clippy --workspace --tests --all-targets -- -D warnings
when: *slow_check_paths
cargo_build:
@ -240,10 +240,13 @@ steps:
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy
dockerfile: docker/Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
platforms: linux/amd64, linux/arm64
build_args:
- RUST_RELEASE_MODE=release
@ -253,10 +256,13 @@ steps:
nightly_build:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy
dockerfile: docker/Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
platforms: linux/amd64,linux/arm64
build_args:
- RUST_RELEASE_MODE=release

1033
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -37,15 +37,6 @@ debug = 0
[features]
embed-pictrs = ["pict-rs"]
# This feature requires building with `tokio_unstable` flag, see documentation:
# https://docs.rs/tokio/latest/tokio/#unstable-features
console = [
"console-subscriber",
"opentelemetry",
"opentelemetry-otlp",
"tracing-opentelemetry",
"reqwest-tracing/opentelemetry_0_16",
]
json-log = ["tracing-subscriber/json"]
default = []
@ -100,7 +91,7 @@ lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
activitypub_federation = { git = "https://github.com/LemmyNet/activitypub-federation-rust.git", branch = "nested-comments-stack-overflow", default-features = false, features = [
activitypub_federation = { version = "0.6.0-alpha2", default-features = false, features = [
"actix-web",
] }
diesel = "2.1.6"
@ -108,7 +99,7 @@ diesel_migrations = "2.1.0"
diesel-async = "0.4.1"
serde = { version = "1.0.204", features = ["derive"] }
serde_with = "3.9.0"
actix-web = { version = "4.8.0", default-features = false, features = [
actix-web = { version = "4.9.0", default-features = false, features = [
"macros",
"rustls-0_23",
"compress-brotli",
@ -117,19 +108,17 @@ actix-web = { version = "4.8.0", default-features = false, features = [
"cookies",
] }
tracing = "0.1.40"
tracing-actix-web = { version = "0.7.11", default-features = false }
tracing-error = "0.2.0"
tracing-log = "0.2.0"
tracing-actix-web = { version = "0.7.10", default-features = false }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
url = { version = "2.5.2", features = ["serde"] }
reqwest = { version = "0.11.27", default-features = false, features = [
reqwest = { version = "0.12.7", default-features = false, features = [
"json",
"blocking",
"gzip",
"rustls-tls",
] }
reqwest-middleware = "0.2.5"
reqwest-tracing = "0.4.8"
reqwest-middleware = "0.3.3"
reqwest-tracing = "0.5.3"
clokwerk = "0.4.0"
doku = { version = "0.21.1", features = ["url-2"] }
bcrypt = "0.15.1"
@ -152,10 +141,8 @@ diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
strum = { version = "0.26.3", features = ["derive"] }
itertools = "0.13.0"
futures = "0.3.30"
http = "0.2.12"
http = "1.1"
rosetta-i18n = "0.1.3"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" }
ts-rs = { version = "7.1.1", features = [
"serde-compat",
"chrono-impl",
@ -188,8 +175,6 @@ diesel-async = { workspace = true }
actix-web = { workspace = true }
tracing = { workspace = true }
tracing-actix-web = { workspace = true }
tracing-error = { workspace = true }
tracing-log = { workspace = true }
tracing-subscriber = { workspace = true }
url = { workspace = true }
reqwest = { workspace = true }
@ -197,10 +182,6 @@ reqwest-middleware = { workspace = true }
reqwest-tracing = { workspace = true }
clokwerk = { workspace = true }
serde_json = { workspace = true }
tracing-opentelemetry = { workspace = true, optional = true }
opentelemetry = { workspace = true, optional = true }
console-subscriber = { version = "0.4.0", optional = true }
opentelemetry-otlp = { version = "0.12.0", optional = true }
pict-rs = { version = "0.5.16", optional = true }
rustls = { workspace = true }
tokio.workspace = true

View file

@ -1,5 +1,5 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::context::LemmyContext;
use lemmy_api_common::{context::LemmyContext, person::ListLoginsResponse};
use lemmy_db_schema::source::login_token::LoginToken;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
@ -7,8 +7,8 @@ use lemmy_utils::error::LemmyResult;
pub async fn list_logins(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<Vec<LoginToken>>> {
) -> LemmyResult<Json<ListLoginsResponse>> {
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
Ok(Json(logins))
Ok(Json(ListLoginsResponse { logins }))
}

View file

@ -135,7 +135,6 @@ pub async fn save_user_settings(
blur_nsfw: data.blur_nsfw,
auto_expand: data.auto_expand,
show_bot_accounts: data.show_bot_accounts,
show_scores: data.show_scores,
default_sort_type,
default_listing_type,
theme: data.theme.clone(),

View file

@ -1,7 +1,7 @@
use lemmy_db_schema::{
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
sensitive::SensitiveString,
source::site::Site,
source::{login_token::LoginToken, site::Site},
CommentSortType,
ListingType,
PostListingMode,
@ -441,3 +441,10 @@ pub struct ListMedia {
pub struct ListMediaResponse {
pub images: Vec<LocalImageView>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ListLoginsResponse {
pub logins: Vec<LoginToken>,
}

View file

@ -11,10 +11,12 @@ use lemmy_db_schema::{
RegistrationApplicationId,
},
source::{
community::Community,
federation_queue_state::FederationQueueState,
instance::Instance,
language::Language,
local_site_url_blocklist::LocalSiteUrlBlocklist,
person::Person,
tagline::Tagline,
},
ListingType,
@ -33,12 +35,9 @@ use lemmy_db_views::structs::{
SiteView,
};
use lemmy_db_views_actor::structs::{
CommunityBlockView,
CommunityFollowerView,
CommunityModeratorView,
CommunityView,
InstanceBlockView,
PersonBlockView,
PersonView,
};
use lemmy_db_views_moderator::structs::{
@ -337,9 +336,9 @@ pub struct MyUserInfo {
pub local_user_view: LocalUserView,
pub follows: Vec<CommunityFollowerView>,
pub moderates: Vec<CommunityModeratorView>,
pub community_blocks: Vec<CommunityBlockView>,
pub instance_blocks: Vec<InstanceBlockView>,
pub person_blocks: Vec<PersonBlockView>,
pub community_blocks: Vec<Community>,
pub instance_blocks: Vec<Instance>,
pub person_blocks: Vec<Person>,
pub discussion_languages: Vec<LanguageId>,
}

View file

@ -5,19 +5,15 @@ use lemmy_api_common::{
};
use lemmy_db_schema::source::{
actor_language::{LocalUserLanguage, SiteLanguage},
community_block::CommunityBlock,
instance_block::InstanceBlock,
language::Language,
local_site_url_blocklist::LocalSiteUrlBlocklist,
person_block::PersonBlock,
tagline::Tagline,
};
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
use lemmy_db_views_actor::structs::{
CommunityBlockView,
CommunityFollowerView,
CommunityModeratorView,
InstanceBlockView,
PersonBlockView,
PersonView,
};
use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView};
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
CACHE_DURATION_API,
@ -81,9 +77,9 @@ pub async fn get_site(
discussion_languages,
) = lemmy_db_schema::try_join_with_pool!(pool => (
|pool| CommunityFollowerView::for_person(pool, person_id),
|pool| CommunityBlockView::for_person(pool, person_id),
|pool| InstanceBlockView::for_person(pool, person_id),
|pool| PersonBlockView::for_person(pool, person_id),
|pool| CommunityBlock::for_person(pool, person_id),
|pool| InstanceBlock::for_person(pool, person_id),
|pool| PersonBlock::for_person(pool, person_id),
|pool| CommunityModeratorView::for_person(pool, person_id, Some(&local_user_view.local_user)),
|pool| LocalUserLanguage::read(pool, local_user_id)
))

View file

@ -8,6 +8,6 @@
"type": "Block",
"removeData": true,
"summary": "spam post",
"expires": "2021-11-01T12:23:50.151874Z",
"endTime": "2021-11-01T12:23:50.151874Z",
"id": "http://enterprise.lemmy.ml/activities/block/5d42fffb-0903-4625-86d4-0b39bb344fc2"
}

View file

@ -11,7 +11,7 @@
"type": "Block",
"removeData": true,
"summary": "spam post",
"expires": "2021-11-01T12:23:50.151874Z",
"endTime": "2021-11-01T12:23:50.151874Z",
"id": "http://enterprise.lemmy.ml/activities/block/726f43ab-bd0e-4ab3-89c8-627e976f553c"
},
"cc": ["http://enterprise.lemmy.ml/c/main"],

View file

@ -74,7 +74,6 @@ impl BlockUser {
&context.settings().get_protocol_and_hostname(),
)?,
audience,
expires,
end_time: expires,
})
}
@ -157,7 +156,7 @@ impl ActivityHandler for BlockUser {
#[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
insert_received_activity(&self.id, context).await?;
let expires = self.expires.or(self.end_time).map(Into::into);
let expires = self.end_time.map(Into::into);
let mod_person = self.actor.dereference(context).await?;
let blocked_person = self.object.dereference(context).await?;
let target = self.target.dereference(context).await?;

View file

@ -98,7 +98,7 @@ impl ActivityHandler for UndoBlockUser {
#[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
insert_received_activity(&self.id, context).await?;
let expires = self.object.expires.or(self.object.end_time).map(Into::into);
let expires = self.object.end_time.map(Into::into);
let mod_person = self.actor.dereference(context).await?;
let blocked_person = self.object.object.dereference(context).await?;
match self.object.target.dereference(context).await? {

View file

@ -27,7 +27,7 @@ pub async fn resolve_object(
// if there's no personId then the JWT was missing or invalid.
let is_authenticated = local_user_view.is_some();
let res = if is_authenticated {
let res = if is_authenticated || cfg!(debug_assertions) {
// user is fully authenticated; allow remote lookups as well.
search_query_to_object_id(data.q.clone(), &context).await
} else {

View file

@ -122,7 +122,6 @@ pub async fn import_settings(
.settings
.as_ref()
.map(|s| s.send_notifications_to_email),
show_scores: data.settings.as_ref().map(|s| s.show_scores),
show_bot_accounts: data.settings.as_ref().map(|s| s.show_bot_accounts),
show_read_posts: data.settings.as_ref().map(|s| s.show_read_posts),
open_links_in_new_tab: data.settings.as_ref().map(|s| s.open_links_in_new_tab),

View file

@ -11,7 +11,6 @@ use activitypub_federation::{
FEDERATION_CONTENT_TYPE,
};
use actix_web::{web, web::Bytes, HttpRequest, HttpResponse};
use http::{header::LOCATION, StatusCode};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
newtypes::DbUrl,
@ -76,14 +75,14 @@ fn create_apub_tombstone_response<T: Into<Url>>(id: T) -> LemmyResult<HttpRespon
Ok(
HttpResponse::Gone()
.content_type(FEDERATION_CONTENT_TYPE)
.status(StatusCode::GONE)
.status(actix_web::http::StatusCode::GONE)
.body(json),
)
}
fn redirect_remote_object(url: &DbUrl) -> HttpResponse {
let mut res = HttpResponse::PermanentRedirect();
res.insert_header((LOCATION, url.as_str()));
res.insert_header((actix_web::http::header::LOCATION, url.as_str()));
res.finish()
}

View file

@ -38,8 +38,6 @@ pub struct BlockUser {
pub(crate) remove_data: Option<bool>,
/// block reason, written to mod log
pub(crate) summary: Option<String>,
/// TODO: deprecated
pub(crate) expires: Option<DateTime<Utc>>,
pub(crate) end_time: Option<DateTime<Utc>>,
}

View file

@ -1,7 +1,10 @@
use crate::{
newtypes::{CommunityId, PersonId},
schema::community_block::dsl::{community_block, community_id, person_id},
source::community_block::{CommunityBlock, CommunityBlockForm},
schema::{community, community_block},
source::{
community::Community,
community_block::{CommunityBlock, CommunityBlockForm},
},
traits::Blockable,
utils::{get_conn, DbPool},
};
@ -9,6 +12,7 @@ use diesel::{
dsl::{exists, insert_into},
result::Error,
select,
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
@ -21,11 +25,27 @@ impl CommunityBlock {
) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?;
select(exists(
community_block.find((for_person_id, for_community_id)),
community_block::table.find((for_person_id, for_community_id)),
))
.get_result(conn)
.await
}
pub async fn for_person(
pool: &mut DbPool<'_>,
person_id: PersonId,
) -> Result<Vec<Community>, Error> {
let conn = &mut get_conn(pool).await?;
community_block::table
.inner_join(community::table)
.select(community::all_columns)
.filter(community_block::person_id.eq(person_id))
.filter(community::deleted.eq(false))
.filter(community::removed.eq(false))
.order_by(community_block::published)
.load::<Community>(conn)
.await
}
}
#[async_trait]
@ -33,9 +53,9 @@ impl Blockable for CommunityBlock {
type Form = CommunityBlockForm;
async fn block(pool: &mut DbPool<'_>, community_block_form: &Self::Form) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(community_block)
insert_into(community_block::table)
.values(community_block_form)
.on_conflict((person_id, community_id))
.on_conflict((community_block::person_id, community_block::community_id))
.do_update()
.set(community_block_form)
.get_result::<Self>(conn)
@ -46,7 +66,7 @@ impl Blockable for CommunityBlock {
community_block_form: &Self::Form,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(community_block.find((
diesel::delete(community_block::table.find((
community_block_form.person_id,
community_block_form.community_id,
)))

View file

@ -1,7 +1,10 @@
use crate::{
newtypes::{InstanceId, PersonId},
schema::instance_block::dsl::{instance_block, instance_id, person_id},
source::instance_block::{InstanceBlock, InstanceBlockForm},
schema::{instance, instance_block},
source::{
instance::Instance,
instance_block::{InstanceBlock, InstanceBlockForm},
},
traits::Blockable,
utils::{get_conn, DbPool},
};
@ -9,6 +12,7 @@ use diesel::{
dsl::{exists, insert_into},
result::Error,
select,
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
@ -21,11 +25,25 @@ impl InstanceBlock {
) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?;
select(exists(
instance_block.find((for_person_id, for_instance_id)),
instance_block::table.find((for_person_id, for_instance_id)),
))
.get_result(conn)
.await
}
pub async fn for_person(
pool: &mut DbPool<'_>,
person_id: PersonId,
) -> Result<Vec<Instance>, Error> {
let conn = &mut get_conn(pool).await?;
instance_block::table
.inner_join(instance::table)
.select(instance::all_columns)
.filter(instance_block::person_id.eq(person_id))
.order_by(instance_block::published)
.load::<Instance>(conn)
.await
}
}
#[async_trait]
@ -33,9 +51,9 @@ impl Blockable for InstanceBlock {
type Form = InstanceBlockForm;
async fn block(pool: &mut DbPool<'_>, instance_block_form: &Self::Form) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(instance_block)
insert_into(instance_block::table)
.values(instance_block_form)
.on_conflict((person_id, instance_id))
.on_conflict((instance_block::person_id, instance_block::instance_id))
.do_update()
.set(instance_block_form)
.get_result::<Self>(conn)
@ -46,7 +64,7 @@ impl Blockable for InstanceBlock {
instance_block_form: &Self::Form,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(instance_block.find((
diesel::delete(instance_block::table.find((
instance_block_form.person_id,
instance_block_form.instance_id,
)))

View file

@ -1,7 +1,10 @@
use crate::{
newtypes::PersonId,
schema::person_block::dsl::{person_block, person_id, target_id},
source::person_block::{PersonBlock, PersonBlockForm},
schema::{person, person_block},
source::{
person::Person,
person_block::{PersonBlock, PersonBlockForm},
},
traits::Blockable,
utils::{get_conn, DbPool},
};
@ -9,6 +12,8 @@ use diesel::{
dsl::{exists, insert_into},
result::Error,
select,
ExpressionMethods,
JoinOnDsl,
QueryDsl,
};
use diesel_async::RunQueryDsl;
@ -20,8 +25,30 @@ impl PersonBlock {
for_recipient_id: PersonId,
) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?;
select(exists(person_block.find((for_person_id, for_recipient_id))))
.get_result(conn)
select(exists(
person_block::table.find((for_person_id, for_recipient_id)),
))
.get_result(conn)
.await
}
pub async fn for_person(
pool: &mut DbPool<'_>,
person_id: PersonId,
) -> Result<Vec<Person>, Error> {
let conn = &mut get_conn(pool).await?;
let target_person_alias = diesel::alias!(person as person1);
person_block::table
.inner_join(person::table.on(person_block::person_id.eq(person::id)))
.inner_join(
target_person_alias.on(person_block::target_id.eq(target_person_alias.field(person::id))),
)
.select(target_person_alias.fields(person::all_columns))
.filter(person_block::person_id.eq(person_id))
.filter(target_person_alias.field(person::deleted).eq(false))
.order_by(person_block::published)
.load::<Person>(conn)
.await
}
}
@ -34,9 +61,9 @@ impl Blockable for PersonBlock {
person_block_form: &PersonBlockForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(person_block)
insert_into(person_block::table)
.values(person_block_form)
.on_conflict((person_id, target_id))
.on_conflict((person_block::person_id, person_block::target_id))
.do_update()
.set(person_block_form)
.get_result::<Self>(conn)
@ -44,8 +71,10 @@ impl Blockable for PersonBlock {
}
async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(person_block.find((person_block_form.person_id, person_block_form.target_id)))
.execute(conn)
.await
diesel::delete(
person_block::table.find((person_block_form.person_id, person_block_form.target_id)),
)
.execute(conn)
.await
}
}

View file

@ -446,7 +446,6 @@ diesel::table! {
interface_language -> Varchar,
show_avatars -> Bool,
send_notifications_to_email -> Bool,
show_scores -> Bool,
show_bot_accounts -> Bool,
show_read_posts -> Bool,
email_verified -> Bool,

View file

@ -2,7 +2,6 @@ use anyhow::Context;
use diesel::{connection::SimpleConnection, Connection, PgConnection};
use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
use lemmy_utils::error::LemmyError;
use tracing::info;
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
@ -34,7 +33,7 @@ pub fn run(db_url: &str) -> Result<(), LemmyError> {
// transaction as `REPLACEABLE_SCHEMA`. This code will be becone less hacky when the conditional
// setup of things in `REPLACEABLE_SCHEMA` is done without using the number of pending
// migrations.
info!("Running Database migrations (This may take a long time)...");
println!("Running Database migrations (This may take a long time)...");
let migrations = conn
.pending_migrations(MIGRATIONS)
.map_err(|e| anyhow::anyhow!("Couldn't determine pending migrations: {e}"))?;
@ -60,7 +59,7 @@ pub fn run(db_url: &str) -> Result<(), LemmyError> {
Ok(())
})?;
info!("Database migrations complete.");
println!("Database migrations complete.");
Ok(())
}

View file

@ -35,9 +35,6 @@ pub struct LocalUser {
/// Whether to show avatars.
pub show_avatars: bool,
pub send_notifications_to_email: bool,
/// Whether to show comment / post scores.
// TODO now that there is a vote_display_mode, this can be gotten rid of in future releases.
pub show_scores: bool,
/// Whether to show bot accounts.
pub show_bot_accounts: bool,
/// Whether to show read posts.
@ -93,8 +90,6 @@ pub struct LocalUserInsertForm {
#[new(default)]
pub show_bot_accounts: Option<bool>,
#[new(default)]
pub show_scores: Option<bool>,
#[new(default)]
pub show_read_posts: Option<bool>,
#[new(default)]
pub email_verified: Option<bool>,
@ -138,7 +133,6 @@ pub struct LocalUserUpdateForm {
pub show_avatars: Option<bool>,
pub send_notifications_to_email: Option<bool>,
pub show_bot_accounts: Option<bool>,
pub show_scores: Option<bool>,
pub show_read_posts: Option<bool>,
pub email_verified: Option<bool>,
pub accepted_application: Option<bool>,

View file

@ -20,6 +20,7 @@ use typed_builder::TypedBuilder;
#[cfg_attr(feature = "full", ts(export))]
/// The vote display settings for your user.
pub struct LocalUserVoteDisplayMode {
#[serde(skip)]
pub local_user_id: LocalUserId,
pub score: bool,
pub upvotes: bool,

View file

@ -252,7 +252,6 @@ mod tests {
show_avatars: inserted_sara_local_user.show_avatars,
send_notifications_to_email: inserted_sara_local_user.send_notifications_to_email,
show_bot_accounts: inserted_sara_local_user.show_bot_accounts,
show_scores: inserted_sara_local_user.show_scores,
show_read_posts: inserted_sara_local_user.show_read_posts,
email_verified: inserted_sara_local_user.email_verified,
accepted_application: inserted_sara_local_user.accepted_application,

View file

@ -1,24 +0,0 @@
use crate::structs::CommunityBlockView;
use diesel::{result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::PersonId,
schema::{community, community_block, person},
utils::{get_conn, DbPool},
};
impl CommunityBlockView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?;
community_block::table
.inner_join(person::table)
.inner_join(community::table)
.select((person::all_columns, community::all_columns))
.filter(community_block::person_id.eq(person_id))
.filter(community::deleted.eq(false))
.filter(community::removed.eq(false))
.order_by(community_block::published)
.load::<CommunityBlockView>(conn)
.await
}
}

View file

@ -1,27 +0,0 @@
use crate::structs::InstanceBlockView;
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::PersonId,
schema::{instance, instance_block, person, site},
utils::{get_conn, DbPool},
};
impl InstanceBlockView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?;
instance_block::table
.inner_join(person::table)
.inner_join(instance::table)
.left_join(site::table.on(site::instance_id.eq(instance::id)))
.select((
person::all_columns,
instance::all_columns,
site::all_columns.nullable(),
))
.filter(instance_block::person_id.eq(person_id))
.order_by(instance_block::published)
.load::<InstanceBlockView>(conn)
.await
}
}

View file

@ -1,8 +1,6 @@
#[cfg(feature = "full")]
pub mod comment_reply_view;
#[cfg(feature = "full")]
pub mod community_block_view;
#[cfg(feature = "full")]
pub mod community_follower_view;
#[cfg(feature = "full")]
pub mod community_moderator_view;
@ -11,10 +9,6 @@ pub mod community_person_ban_view;
#[cfg(feature = "full")]
pub mod community_view;
#[cfg(feature = "full")]
pub mod instance_block_view;
#[cfg(feature = "full")]
pub mod person_block_view;
#[cfg(feature = "full")]
pub mod person_mention_view;
#[cfg(feature = "full")]
pub mod person_view;

View file

@ -1,30 +0,0 @@
use crate::structs::PersonBlockView;
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::PersonId,
schema::{person, person_block},
utils::{get_conn, DbPool},
};
impl PersonBlockView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?;
let target_person_alias = diesel::alias!(person as person1);
person_block::table
.inner_join(person::table.on(person_block::person_id.eq(person::id)))
.inner_join(
target_person_alias.on(person_block::target_id.eq(target_person_alias.field(person::id))),
)
.select((
person::all_columns,
target_person_alias.fields(person::all_columns),
))
.filter(person_block::person_id.eq(person_id))
.filter(target_person_alias.field(person::deleted).eq(false))
.order_by(person_block::published)
.load::<PersonBlockView>(conn)
.await
}
}

View file

@ -6,11 +6,9 @@ use lemmy_db_schema::{
comment::Comment,
comment_reply::CommentReply,
community::Community,
instance::Instance,
person::Person,
person_mention::PersonMention,
post::Post,
site::Site,
},
SubscribedType,
};
@ -19,28 +17,6 @@ use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// A community block.
pub struct CommunityBlockView {
pub person: Person,
pub community: Community,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// An instance block by a user.
pub struct InstanceBlockView {
pub person: Person,
pub instance: Instance,
pub site: Option<Site>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -83,16 +59,6 @@ pub struct CommunityView {
pub banned_from_community: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// A person block.
pub struct PersonBlockView {
pub person: Person,
pub target: Person,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]

View file

@ -32,7 +32,7 @@ serde_json.workspace = true
tokio = { workspace = true, features = ["full"] }
tracing.workspace = true
moka.workspace = true
tokio-util = "0.7.11"
tokio-util = "0.7.12"
async-trait.workspace = true
[dev-dependencies]

View file

@ -459,7 +459,6 @@ mod test {
traits::Crud,
};
use lemmy_utils::error::LemmyResult;
use reqwest::StatusCode;
use serde_json::{json, Value};
use serial_test::serial;
use test_context::{test_context, AsyncTestContext};
@ -688,7 +687,7 @@ mod test {
|inbox_sender: actix_web::web::Data<UnboundedSender<String>>, body: String| async move {
tracing::debug!("received activity: {:?}", body);
inbox_sender.send(body.clone()).unwrap();
HttpResponse::new(StatusCode::OK)
HttpResponse::new(actix_web::http::StatusCode::OK)
},
),
)

View file

@ -32,5 +32,5 @@ serde = { workspace = true }
url = { workspace = true }
tracing = { workspace = true }
tokio = { workspace = true }
urlencoding = { workspace = true }
rss = "2.0.8"
http.workspace = true
rss = "2.0.9"

View file

@ -2,6 +2,7 @@ use actix_web::{
body::BodyStream,
http::{
header::{HeaderName, ACCEPT_ENCODING, HOST},
Method,
StatusCode,
},
web,
@ -10,6 +11,7 @@ use actix_web::{
HttpResponse,
};
use futures::stream::{Stream, StreamExt};
use http::HeaderValue;
use lemmy_api_common::{context::LemmyContext, request::PictrsResponse};
use lemmy_db_schema::source::{
images::{LocalImage, LocalImageForm, RemoteImage},
@ -22,7 +24,6 @@ use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
use serde::Deserialize;
use std::time::Duration;
use url::Url;
use urlencoding::decode;
pub fn config(
cfg: &mut web::ServiceConfig,
@ -110,7 +111,7 @@ fn adapt_request(
const INVALID_HEADERS: &[HeaderName] = &[ACCEPT_ENCODING, HOST];
let client_request = client
.request(request.method().clone(), url)
.request(convert_method(request.method()), url)
.timeout(REQWEST_TIMEOUT);
request
@ -120,7 +121,8 @@ fn adapt_request(
if INVALID_HEADERS.contains(key) {
client_req
} else {
client_req.header(key, value)
// TODO: remove as_str and as_bytes conversions after actix-web upgrades to http 1.0
client_req.header(key.as_str(), value.as_bytes())
}
})
}
@ -167,7 +169,7 @@ async fn upload(
}
}
Ok(HttpResponse::build(status).json(images))
Ok(HttpResponse::build(convert_status(status)).json(images))
}
async fn full_res(
@ -210,14 +212,14 @@ async fn image(
let res = client_req.send().await?;
if res.status() == StatusCode::NOT_FOUND {
if res.status() == http::StatusCode::NOT_FOUND {
return Ok(HttpResponse::NotFound().finish());
}
let mut client_res = HttpResponse::build(res.status());
let mut client_res = HttpResponse::build(StatusCode::from_u16(res.status().as_u16())?);
for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
client_res.insert_header((name.clone(), value.clone()));
client_res.insert_header(convert_header(name, value));
}
Ok(client_res.body(BodyStream::new(res.bytes_stream())))
@ -246,7 +248,7 @@ async fn delete(
LocalImage::delete_by_alias(&mut context.pool(), &file).await?;
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res.bytes_stream())))
Ok(HttpResponse::build(convert_status(res.status())).body(BodyStream::new(res.bytes_stream())))
}
pub async fn image_proxy(
@ -255,7 +257,7 @@ pub async fn image_proxy(
client: web::Data<ClientWithMiddleware>,
context: web::Data<LemmyContext>,
) -> LemmyResult<HttpResponse> {
let url = Url::parse(&decode(&params.url)?)?;
let url = Url::parse(&params.url)?;
// Check that url corresponds to a federated image so that this can't be abused as a proxy
// for arbitrary purposes.
@ -309,3 +311,14 @@ where
std::pin::Pin::new(&mut self.rx).poll_recv(cx)
}
}
// TODO: remove these conversions after actix-web upgrades to http 1.0
fn convert_status(status: http::StatusCode) -> StatusCode {
StatusCode::from_u16(status.as_u16()).expect("status can be converted")
}
fn convert_method(method: &Method) -> http::Method {
http::Method::from_bytes(method.as_str().as_bytes()).expect("method can be converted")
}
fn convert_header<'a>(name: &'a http::HeaderName, value: &'a HeaderValue) -> (&'a str, &'a [u8]) {
(name.as_str(), value.as_bytes())
}

View file

@ -84,7 +84,7 @@ async fn get_webfinger_response(
Ok(
HttpResponse::Ok()
.content_type(&WEBFINGER_CONTENT_TYPE)
.content_type(WEBFINGER_CONTENT_TYPE.as_bytes())
.json(json),
)
}

View file

@ -32,7 +32,6 @@ full = [
"dep:actix-web",
"dep:serde_json",
"dep:anyhow",
"dep:tracing-error",
"dep:http",
"dep:deser-hjson",
"dep:regex",
@ -53,7 +52,6 @@ full = [
[dependencies]
regex = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }
tracing-error = { workspace = true, optional = true }
itertools = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true, optional = true }
@ -73,7 +71,7 @@ urlencoding = { workspace = true, optional = true }
html2text = { version = "0.12.5", optional = true }
deser-hjson = { version = "2.2.4", optional = true }
smart-default = { version = "0.7.1", optional = true }
lettre = { version = "0.11.7", default-features = false, features = [
lettre = { version = "0.11.8", default-features = false, features = [
"builder",
"tokio1",
"tokio1-rustls-tls",

View file

@ -1,6 +1,6 @@
use cfg_if::cfg_if;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::{backtrace::Backtrace, fmt::Debug};
use strum::{Display, EnumIter};
#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, EnumIter, Hash)]
@ -186,14 +186,13 @@ pub enum LemmyErrorType {
cfg_if! {
if #[cfg(feature = "full")] {
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 context: Backtrace,
}
/// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]]
@ -208,7 +207,7 @@ cfg_if! {
LemmyError {
error_type: LemmyErrorType::Unknown(format!("{}", &cause)),
inner: cause,
context: SpanTrace::capture(),
context: Backtrace::capture(),
}
}
}
@ -232,13 +231,13 @@ cfg_if! {
}
impl actix_web::error::ResponseError for LemmyError {
fn status_code(&self) -> http::StatusCode {
fn status_code(&self) -> actix_web::http::StatusCode {
if self.error_type == LemmyErrorType::IncorrectLogin {
return http::StatusCode::UNAUTHORIZED;
return actix_web::http::StatusCode::UNAUTHORIZED;
}
match self.inner.downcast_ref::<diesel::result::Error>() {
Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND,
_ => http::StatusCode::BAD_REQUEST,
Some(diesel::result::Error::NotFound) => actix_web::http::StatusCode::NOT_FOUND,
_ => actix_web::http::StatusCode::BAD_REQUEST,
}
}
@ -253,7 +252,7 @@ cfg_if! {
LemmyError {
error_type,
inner,
context: SpanTrace::capture(),
context: Backtrace::capture(),
}
}
}
@ -267,7 +266,7 @@ cfg_if! {
self.map_err(|error| LemmyError {
error_type,
inner: error.into(),
context: SpanTrace::capture(),
context: Backtrace::capture(),
})
}
}

View file

@ -37,6 +37,7 @@ mod tests {
use crate::error::{LemmyError, LemmyErrorType};
use actix_web::{
error::ErrorInternalServerError,
http::StatusCode,
middleware::ErrorHandlers,
test,
web,
@ -45,7 +46,6 @@ mod tests {
Handler,
Responder,
};
use http::StatusCode;
use pretty_assertions::assert_eq;
#[actix_web::test]

View file

@ -20,7 +20,7 @@ const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"];
const BODY_MAX_LENGTH: usize = 10000;
const POST_BODY_MAX_LENGTH: usize = 50000;
const BIO_MAX_LENGTH: usize = 300;
const BIO_MAX_LENGTH: usize = 1000;
const URL_MAX_LENGTH: usize = 2000;
const ALT_TEXT_MAX_LENGTH: usize = 1500;
const SITE_NAME_MAX_LENGTH: usize = 20;

View file

@ -0,0 +1,3 @@
ALTER TABLE local_user
ADD COLUMN show_scores boolean NOT NULL DEFAULT TRUE;

View file

@ -0,0 +1,3 @@
ALTER TABLE local_user
DROP COLUMN show_scores;

View file

@ -1,17 +1,10 @@
pub mod api_routes_http;
pub mod code_migrations;
pub mod prometheus_metrics;
pub mod root_span_builder;
pub mod scheduled_tasks;
pub mod session_middleware;
#[cfg(feature = "console")]
pub mod telemetry;
use crate::{
code_migrations::run_advanced_migrations,
root_span_builder::QuieterRootSpanBuilder,
session_middleware::SessionMiddleware,
};
use crate::{code_migrations::run_advanced_migrations, session_middleware::SessionMiddleware};
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
use actix_cors::Cors;
use actix_web::{
@ -55,14 +48,9 @@ use prometheus_metrics::serve_prometheus;
use reqwest_middleware::ClientBuilder;
use reqwest_tracing::TracingMiddleware;
use serde_json::json;
use std::{env, ops::Deref, time::Duration};
use std::{ops::Deref, time::Duration};
use tokio::signal::unix::SignalKind;
use tracing::subscriber::set_global_default;
use tracing_actix_web::TracingLogger;
use tracing_error::ErrorLayer;
use tracing_log::LogTracer;
use tracing_subscriber::{filter::Targets, layer::SubscriberExt, Layer, Registry};
use url::Url;
use tracing_actix_web::{DefaultRootSpanBuilder, TracingLogger};
/// Timeout for HTTP requests while sending activities. A longer timeout provides better
/// compatibility with other ActivityPub software that might allocate more time for synchronous
@ -119,7 +107,7 @@ pub struct CmdArgs {
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> {
// Print version number to log
println!("Lemmy v{VERSION}");
println!("Starting Lemmy v{VERSION}");
// return error 503 while running db migrations and startup tasks
let mut startup_server_handle = None;
@ -318,7 +306,7 @@ fn create_http_server(
))
.wrap(middleware::Compress::default())
.wrap(cors_config)
.wrap(TracingLogger::<QuieterRootSpanBuilder>::new())
.wrap(TracingLogger::<DefaultRootSpanBuilder>::new())
.wrap(ErrorHandlers::new().default_handler(jsonify_plain_text_errors))
.app_data(Data::new(context.clone()))
.app_data(Data::new(rate_limit_cell.clone()))
@ -373,38 +361,3 @@ fn cors_config(settings: &Settings) -> Cors {
.max_age(3600),
}
}
pub fn init_logging(opentelemetry_url: &Option<Url>) -> LemmyResult<()> {
LogTracer::init()?;
let log_description = env::var("RUST_LOG").unwrap_or_else(|_| "info".into());
let targets = log_description
.trim()
.trim_matches('"')
.parse::<Targets>()?;
let format_layer = {
#[cfg(feature = "json-log")]
let layer = tracing_subscriber::fmt::layer().with_ansi(false).json();
#[cfg(not(feature = "json-log"))]
let layer = tracing_subscriber::fmt::layer().with_ansi(false);
layer.with_filter(targets.clone())
};
let subscriber = Registry::default()
.with(format_layer)
.with(ErrorLayer::default());
if let Some(_url) = opentelemetry_url {
#[cfg(feature = "console")]
telemetry::init_tracing(_url.as_ref(), subscriber, targets)?;
#[cfg(not(feature = "console"))]
tracing::error!("Feature `console` must be enabled for opentelemetry tracing");
} else {
set_global_default(subscriber)?;
}
Ok(())
}

View file

@ -1,12 +1,18 @@
use clap::Parser;
use lemmy_server::{init_logging, start_lemmy_server, CmdArgs};
use lemmy_utils::{error::LemmyResult, settings::SETTINGS};
use lemmy_server::{start_lemmy_server, CmdArgs};
use lemmy_utils::error::LemmyResult;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
pub extern crate rustls;
#[tokio::main]
pub async fn main() -> LemmyResult<()> {
init_logging(&SETTINGS.opentelemetry_url)?;
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::fmt().with_env_filter(filter).init();
let args = CmdArgs::parse();
rustls::crypto::ring::default_provider()

View file

@ -1,83 +0,0 @@
use actix_web::{http::StatusCode, ResponseError};
use tracing::Span;
use tracing_actix_web::RootSpanBuilder;
// Code in this module adapted from DefaultRootSpanBuilder
// https://github.com/LukeMathWalker/tracing-actix-web/blob/main/src/root_span_builder.rs
// and root_span!
// https://github.com/LukeMathWalker/tracing-actix-web/blob/main/src/root_span_macro.rs
pub struct QuieterRootSpanBuilder;
impl RootSpanBuilder for QuieterRootSpanBuilder {
fn on_request_start(request: &actix_web::dev::ServiceRequest) -> Span {
let request_id = tracing_actix_web::root_span_macro::private::get_request_id(request);
tracing::info_span!(
"HTTP request",
http.method = %request.method(),
http.scheme = request.connection_info().scheme(),
http.host = %request.connection_info().host(),
http.target = %request.uri().path(),
http.status_code = tracing::field::Empty,
otel.kind = "server",
otel.status_code = tracing::field::Empty,
trace_id = tracing::field::Empty,
request_id = %request_id,
exception.message = tracing::field::Empty,
// Not proper OpenTelemetry, but their terminology is fairly exception-centric
exception.details = tracing::field::Empty,
)
}
fn on_request_end<B>(
span: tracing::Span,
outcome: &Result<actix_web::dev::ServiceResponse<B>, actix_web::Error>,
) {
match &outcome {
Ok(response) => {
if let Some(error) = response.response().error() {
// use the status code already constructed for the outgoing HTTP response
handle_error(span, response.status(), error.as_response_error());
} else {
let code: i32 = response.response().status().as_u16().into();
span.record("http.status_code", code);
span.record("otel.status_code", "OK");
}
}
Err(error) => {
let response_error = error.as_response_error();
handle_error(span, response_error.status_code(), response_error);
}
};
}
}
fn handle_error(span: Span, status_code: StatusCode, response_error: &dyn ResponseError) {
let code: i32 = status_code.as_u16().into();
span.record("http.status_code", code);
if status_code.is_client_error() {
span.record("otel.status_code", "OK");
} else {
span.record("otel.status_code", "ERROR");
}
// pre-formatting errors is a workaround for https://github.com/tokio-rs/tracing/issues/1565
let display_error = format!("{response_error}");
tracing::info_span!(
parent: None,
"Error encountered while processing the incoming HTTP request"
)
.in_scope(|| {
if status_code.is_client_error() {
tracing::warn!("{}", display_error);
} else {
tracing::error!("{}", display_error);
}
});
span.record("exception.message", tracing::field::display(display_error));
}

View file

@ -1,7 +1,7 @@
use actix_web::{
body::MessageBody,
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
http::header::CACHE_CONTROL,
http::header::{HeaderValue, CACHE_CONTROL},
Error,
HttpMessage,
};
@ -9,7 +9,6 @@ use core::future::Ready;
use futures_util::future::LocalBoxFuture;
use lemmy_api::{local_user_view_from_jwt, read_auth_token};
use lemmy_api_common::context::LemmyContext;
use reqwest::header::HeaderValue;
use std::{future::ready, rc::Rc};
#[derive(Clone)]

View file

@ -1,47 +0,0 @@
use console_subscriber::ConsoleLayer;
use lemmy_utils::error::LemmyResult;
use opentelemetry::{
sdk::{propagation::TraceContextPropagator, Resource},
KeyValue,
};
use opentelemetry_otlp::WithExportConfig;
use tracing::{subscriber::set_global_default, Subscriber};
use tracing_subscriber::{filter::Targets, layer::SubscriberExt, registry::LookupSpan, Layer};
pub fn init_tracing<S>(opentelemetry_url: &str, subscriber: S, targets: Targets) -> LemmyResult<()>
where
S: Subscriber + for<'a> LookupSpan<'a> + Send + Sync + 'static,
{
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
let console_layer = ConsoleLayer::builder()
.with_default_env()
.server_addr(([0, 0, 0, 0], 6669))
.event_buffer_capacity(1024 * 1024)
.spawn();
let subscriber = subscriber.with(console_layer);
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(
opentelemetry::sdk::trace::config()
.with_resource(Resource::new(vec![KeyValue::new("service.name", "lemmy")])),
)
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint(opentelemetry_url),
)
.install_batch(opentelemetry::runtime::Tokio)?;
let otel_layer = tracing_opentelemetry::layer()
.with_tracer(tracer)
.with_filter(targets);
let subscriber = subscriber.with(otel_layer);
set_global_default(subscriber)?;
Ok(())
}