mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-22 12:21:18 +00:00
Simplify config using macros (#1686)
Co-authored-by: Felix Ableitner <me@nutomic.com>
This commit is contained in:
parent
b8d7f00d58
commit
7b8cbbba85
29 changed files with 230 additions and 451 deletions
69
Cargo.lock
generated
69
Cargo.lock
generated
|
@ -1001,15 +1001,6 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "envy"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
|
@ -1882,7 +1873,6 @@ dependencies = [
|
||||||
"comrak",
|
"comrak",
|
||||||
"deser-hjson",
|
"deser-hjson",
|
||||||
"diesel",
|
"diesel",
|
||||||
"envy",
|
|
||||||
"futures",
|
"futures",
|
||||||
"http",
|
"http",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
@ -1890,7 +1880,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lettre",
|
"lettre",
|
||||||
"log",
|
"log",
|
||||||
"merge",
|
|
||||||
"openssl",
|
"openssl",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
|
@ -1898,6 +1887,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"smart-default",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -2037,28 +2027,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "merge"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
|
|
||||||
dependencies = [
|
|
||||||
"merge_derive",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "merge_derive"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2 1.0.27",
|
|
||||||
"quote 1.0.9",
|
|
||||||
"syn 1.0.73",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "migrations_internals"
|
name = "migrations_internals"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -2444,30 +2412,6 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2 1.0.27",
|
|
||||||
"quote 1.0.9",
|
|
||||||
"syn 1.0.73",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2 1.0.27",
|
|
||||||
"quote 1.0.9",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
version = "0.5.19"
|
version = "0.5.19"
|
||||||
|
@ -3021,6 +2965,17 @@ version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smart-default"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 1.0.27",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
|
@ -115,7 +115,7 @@ impl Perform for GetCaptcha {
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
_websocket_id: Option<ConnectionId>,
|
_websocket_id: Option<ConnectionId>,
|
||||||
) -> Result<Self::Response, LemmyError> {
|
) -> Result<Self::Response, LemmyError> {
|
||||||
let captcha_settings = Settings::get().captcha();
|
let captcha_settings = Settings::get().captcha;
|
||||||
|
|
||||||
if !captcha_settings.enabled {
|
if !captcha_settings.enabled {
|
||||||
return Ok(GetCaptchaResponse { ok: None });
|
return Ok(GetCaptchaResponse { ok: None });
|
||||||
|
|
|
@ -198,7 +198,7 @@ pub fn send_email_to_user(
|
||||||
let subject = &format!(
|
let subject = &format!(
|
||||||
"{} - {} {}",
|
"{} - {} {}",
|
||||||
subject_text,
|
subject_text,
|
||||||
Settings::get().hostname(),
|
Settings::get().hostname,
|
||||||
local_user_view.person.name,
|
local_user_view.person.name,
|
||||||
);
|
);
|
||||||
let html = &format!(
|
let html = &format!(
|
||||||
|
@ -386,14 +386,14 @@ pub async fn collect_moderated_communities(
|
||||||
pub async fn build_federated_instances(
|
pub async fn build_federated_instances(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<Option<FederatedInstances>, LemmyError> {
|
) -> Result<Option<FederatedInstances>, LemmyError> {
|
||||||
if Settings::get().federation().enabled {
|
if Settings::get().federation.enabled {
|
||||||
let distinct_communities = blocking(pool, move |conn| {
|
let distinct_communities = blocking(pool, move |conn| {
|
||||||
Community::distinct_federated_communities(conn)
|
Community::distinct_federated_communities(conn)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let allowed = Settings::get().get_allowed_instances();
|
let allowed = Settings::get().federation.allowed_instances;
|
||||||
let blocked = Settings::get().get_blocked_instances();
|
let blocked = Settings::get().federation.blocked_instances;
|
||||||
|
|
||||||
let mut linked = distinct_communities
|
let mut linked = distinct_communities
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -405,7 +405,7 @@ pub async fn build_federated_instances(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(blocked) = blocked.as_ref() {
|
if let Some(blocked) = blocked.as_ref() {
|
||||||
linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
|
linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort and remove dupes
|
// Sort and remove dupes
|
||||||
|
|
|
@ -52,8 +52,11 @@ impl PerformCrud for CreatePost {
|
||||||
|
|
||||||
// Fetch Iframely and pictrs cached image
|
// Fetch Iframely and pictrs cached image
|
||||||
let data_url = data.url.as_ref();
|
let data_url = data.url.as_ref();
|
||||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
let (iframely_response, pictrs_thumbnail) =
|
||||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
|
||||||
|
let (embed_title, embed_description, embed_html) = iframely_response
|
||||||
|
.map(|u| (u.title, u.description, u.html))
|
||||||
|
.unwrap_or((None, None, None));
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.trim().to_owned(),
|
name: data.name.trim().to_owned(),
|
||||||
|
@ -62,9 +65,9 @@ impl PerformCrud for CreatePost {
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
creator_id: local_user_view.person.id,
|
creator_id: local_user_view.person.id,
|
||||||
nsfw: data.nsfw,
|
nsfw: data.nsfw,
|
||||||
embed_title: iframely_title,
|
embed_title,
|
||||||
embed_description: iframely_description,
|
embed_description,
|
||||||
embed_html: iframely_html,
|
embed_html,
|
||||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||||
..PostForm::default()
|
..PostForm::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,8 +52,11 @@ impl PerformCrud for EditPost {
|
||||||
|
|
||||||
// Fetch Iframely and Pictrs cached image
|
// Fetch Iframely and Pictrs cached image
|
||||||
let data_url = data.url.as_ref();
|
let data_url = data.url.as_ref();
|
||||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
let (iframely_response, pictrs_thumbnail) =
|
||||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
|
||||||
|
let (embed_title, embed_description, embed_html) = iframely_response
|
||||||
|
.map(|u| (u.title, u.description, u.html))
|
||||||
|
.unwrap_or((None, None, None));
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
creator_id: orig_post.creator_id.to_owned(),
|
creator_id: orig_post.creator_id.to_owned(),
|
||||||
|
@ -63,9 +66,9 @@ impl PerformCrud for EditPost {
|
||||||
body: data.body.to_owned(),
|
body: data.body.to_owned(),
|
||||||
nsfw: data.nsfw,
|
nsfw: data.nsfw,
|
||||||
updated: Some(naive_now()),
|
updated: Some(naive_now()),
|
||||||
embed_title: iframely_title,
|
embed_title,
|
||||||
embed_description: iframely_description,
|
embed_description,
|
||||||
embed_html: iframely_html,
|
embed_html,
|
||||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||||
..PostForm::default()
|
..PostForm::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@ impl PerformCrud for GetSite {
|
||||||
Ok(site_view) => Some(site_view),
|
Ok(site_view) => Some(site_view),
|
||||||
// If the site isn't created yet, check the setup
|
// If the site isn't created yet, check the setup
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
if let Some(setup) = Settings::get().setup().as_ref() {
|
if let Some(setup) = Settings::get().setup.as_ref() {
|
||||||
let register = Register {
|
let register = Register {
|
||||||
username: setup.admin_username.to_owned(),
|
username: setup.admin_username.to_owned(),
|
||||||
email: setup.admin_email.to_owned(),
|
email: setup.admin_email.to_owned(),
|
||||||
|
|
|
@ -69,7 +69,7 @@ impl PerformCrud for Register {
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
// If its not the admin, check the captcha
|
// If its not the admin, check the captcha
|
||||||
if !no_admins && Settings::get().captcha().enabled {
|
if !no_admins && Settings::get().captcha.enabled {
|
||||||
let check = context
|
let check = context
|
||||||
.chat_server()
|
.chat_server()
|
||||||
.send(CheckCaptcha {
|
.send(CheckCaptcha {
|
||||||
|
|
|
@ -50,7 +50,7 @@ async fn list_community_follower_inboxes(
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.unique()
|
.unique()
|
||||||
.filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
|
.filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
|
||||||
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
|
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
|
||||||
.map(|inbox| inbox.to_owned())
|
.map(|inbox| inbox.to_owned())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
|
@ -83,7 +83,7 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.unique()
|
.unique()
|
||||||
.filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
|
.filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
|
||||||
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
|
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
|
||||||
.map(|inbox| inbox.to_owned())
|
.map(|inbox| inbox.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -170,7 +170,7 @@ pub(crate) async fn send_activity_new<T>(
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
if !Settings::get().federation().enabled || inboxes.is_empty() {
|
if !Settings::get().federation.enabled || inboxes.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ where
|
||||||
Kind: Serialize,
|
Kind: Serialize,
|
||||||
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
|
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
if !Settings::get().federation().enabled || inboxes.is_empty() {
|
if !Settings::get().federation.enabled || inboxes.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ fn assert_activity_not_local<T: Debug + ActivityHandler>(activity: &T) -> Result
|
||||||
.domain()
|
.domain()
|
||||||
.context(location_info!())?;
|
.context(location_info!())?;
|
||||||
|
|
||||||
if activity_domain == Settings::get().hostname() {
|
if activity_domain == Settings::get().hostname {
|
||||||
return Err(
|
return Err(
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Error: received activity which was sent by local instance: {:?}",
|
"Error: received activity which was sent by local instance: {:?}",
|
||||||
|
|
|
@ -25,8 +25,8 @@ static APUB_JSON_CONTENT_TYPE_LONG: &str =
|
||||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
|
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
if Settings::get().federation().enabled {
|
if Settings::get().federation.enabled {
|
||||||
println!("federation enabled, host is {}", Settings::get().hostname());
|
println!("federation enabled, host is {}", Settings::get().hostname);
|
||||||
let digest_verifier = VerifyDigest::new(Sha256::new());
|
let digest_verifier = VerifyDigest::new(Sha256::new());
|
||||||
|
|
||||||
let header_guard_accept = guard::Any(guard::Header("Accept", APUB_JSON_CONTENT_TYPE))
|
let header_guard_accept = guard::Any(guard::Header("Accept", APUB_JSON_CONTENT_TYPE))
|
||||||
|
|
|
@ -75,7 +75,7 @@ pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Resu
|
||||||
let domain = apub_id.domain().context(location_info!())?.to_string();
|
let domain = apub_id.domain().context(location_info!())?.to_string();
|
||||||
let local_instance = settings.get_hostname_without_port()?;
|
let local_instance = settings.get_hostname_without_port()?;
|
||||||
|
|
||||||
if !settings.federation().enabled {
|
if !settings.federation.enabled {
|
||||||
return if domain == local_instance {
|
return if domain == local_instance {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
@ -102,18 +102,15 @@ pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Resu
|
||||||
// TODO: might be good to put the part above in one method, and below in another
|
// TODO: might be good to put the part above in one method, and below in another
|
||||||
// (which only gets called in apub::objects)
|
// (which only gets called in apub::objects)
|
||||||
// -> no that doesnt make sense, we still need the code below for blocklist and strict allowlist
|
// -> no that doesnt make sense, we still need the code below for blocklist and strict allowlist
|
||||||
if let Some(blocked) = Settings::get().get_blocked_instances() {
|
if let Some(blocked) = Settings::get().federation.blocked_instances {
|
||||||
if blocked.contains(&domain) {
|
if blocked.contains(&domain) {
|
||||||
return Err(anyhow!("{} is in federation blocklist", domain).into());
|
return Err(anyhow!("{} is in federation blocklist", domain).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut allowed) = Settings::get().get_allowed_instances() {
|
if let Some(mut allowed) = Settings::get().federation.allowed_instances {
|
||||||
// Only check allowlist if this is a community, or strict allowlist is enabled.
|
// Only check allowlist if this is a community, or strict allowlist is enabled.
|
||||||
let strict_allowlist = Settings::get()
|
let strict_allowlist = Settings::get().federation.strict_allowlist;
|
||||||
.federation()
|
|
||||||
.strict_allowlist
|
|
||||||
.unwrap_or(true);
|
|
||||||
if use_strict_allowlist || strict_allowlist {
|
if use_strict_allowlist || strict_allowlist {
|
||||||
// need to allow this explicitly because apub receive might contain objects from our local
|
// need to allow this explicitly because apub receive might contain objects from our local
|
||||||
// instance.
|
// instance.
|
||||||
|
|
|
@ -193,7 +193,7 @@ where
|
||||||
let domain = object_id.domain().context(location_info!())?;
|
let domain = object_id.domain().context(location_info!())?;
|
||||||
|
|
||||||
// if its a local object, return it directly from the database
|
// if its a local object, return it directly from the database
|
||||||
if Settings::get().hostname() == domain {
|
if Settings::get().hostname == domain {
|
||||||
let object = blocking(context.pool(), move |conn| {
|
let object = blocking(context.pool(), move |conn| {
|
||||||
To::read_from_apub_id(conn, &object_id.into())
|
To::read_from_apub_id(conn, &object_id.into())
|
||||||
})
|
})
|
||||||
|
|
|
@ -109,7 +109,7 @@ impl FromApub for DbPerson {
|
||||||
) -> Result<DbPerson, LemmyError> {
|
) -> Result<DbPerson, LemmyError> {
|
||||||
let person_id = person.id_unchecked().context(location_info!())?.to_owned();
|
let person_id = person.id_unchecked().context(location_info!())?.to_owned();
|
||||||
let domain = person_id.domain().context(location_info!())?;
|
let domain = person_id.domain().context(location_info!())?;
|
||||||
if domain == Settings::get().hostname() {
|
if domain == Settings::get().hostname {
|
||||||
let person = blocking(context.pool(), move |conn| {
|
let person = blocking(context.pool(), move |conn| {
|
||||||
DbPerson::read_from_apub_id(conn, &person_id.into())
|
DbPerson::read_from_apub_id(conn, &person_id.into())
|
||||||
})
|
})
|
||||||
|
|
|
@ -178,12 +178,14 @@ impl FromApub for Post {
|
||||||
let community = extract_community(&page.to, context, request_counter).await?;
|
let community = extract_community(&page.to, context, request_counter).await?;
|
||||||
|
|
||||||
let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
|
let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
|
||||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
let (iframely_response, pictrs_thumbnail) = if let Some(url) = &page.url {
|
||||||
if let Some(url) = &page.url {
|
fetch_iframely_and_pictrs_data(context.client(), Some(url)).await?
|
||||||
fetch_iframely_and_pictrs_data(context.client(), Some(url)).await
|
} else {
|
||||||
} else {
|
(None, thumbnail_url)
|
||||||
(None, None, None, thumbnail_url)
|
};
|
||||||
};
|
let (embed_title, embed_description, embed_html) = iframely_response
|
||||||
|
.map(|u| (u.title, u.description, u.html))
|
||||||
|
.unwrap_or((None, None, None));
|
||||||
|
|
||||||
let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content));
|
let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content));
|
||||||
let form = PostForm {
|
let form = PostForm {
|
||||||
|
@ -199,9 +201,9 @@ impl FromApub for Post {
|
||||||
deleted: None,
|
deleted: None,
|
||||||
nsfw: page.sensitive,
|
nsfw: page.sensitive,
|
||||||
stickied: page.stickied,
|
stickied: page.stickied,
|
||||||
embed_title: iframely_title,
|
embed_title,
|
||||||
embed_description: iframely_description,
|
embed_description,
|
||||||
embed_html: iframely_html,
|
embed_html,
|
||||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||||
ap_id: Some(page.id.clone().into()),
|
ap_id: Some(page.id.clone().into()),
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use actix_web::{body::BodyStream, http::StatusCode, web::Data, *};
|
use actix_web::{body::BodyStream, http::StatusCode, web::Data, *};
|
||||||
|
use anyhow::anyhow;
|
||||||
use awc::Client;
|
use awc::Client;
|
||||||
use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings};
|
use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings, LemmyError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -54,10 +55,7 @@ async fn upload(
|
||||||
return Ok(HttpResponse::Unauthorized().finish());
|
return Ok(HttpResponse::Unauthorized().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut client_req = client.request_from(
|
let mut client_req = client.request_from(format!("{}/image", pictrs_url()?), req.head());
|
||||||
format!("{}/image", Settings::get().pictrs_url()),
|
|
||||||
req.head(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(addr) = req.head().peer_addr {
|
if let Some(addr) = req.head().peer_addr {
|
||||||
client_req = client_req.insert_header(("X-Forwarded-For", addr.to_string()))
|
client_req = client_req.insert_header(("X-Forwarded-For", addr.to_string()))
|
||||||
|
@ -83,17 +81,12 @@ async fn full_res(
|
||||||
|
|
||||||
// If there are no query params, the URL is original
|
// If there are no query params, the URL is original
|
||||||
let url = if params.format.is_none() && params.thumbnail.is_none() {
|
let url = if params.format.is_none() && params.thumbnail.is_none() {
|
||||||
format!("{}/image/original/{}", Settings::get().pictrs_url(), name,)
|
format!("{}/image/original/{}", pictrs_url()?, name,)
|
||||||
} else {
|
} else {
|
||||||
// Use jpg as a default when none is given
|
// Use jpg as a default when none is given
|
||||||
let format = params.format.unwrap_or_else(|| "jpg".to_string());
|
let format = params.format.unwrap_or_else(|| "jpg".to_string());
|
||||||
|
|
||||||
let mut url = format!(
|
let mut url = format!("{}/image/process.{}?src={}", pictrs_url()?, format, name,);
|
||||||
"{}/image/process.{}?src={}",
|
|
||||||
Settings::get().pictrs_url(),
|
|
||||||
format,
|
|
||||||
name,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(size) = params.thumbnail {
|
if let Some(size) = params.thumbnail {
|
||||||
url = format!("{}&thumbnail={}", url, size,);
|
url = format!("{}&thumbnail={}", url, size,);
|
||||||
|
@ -141,12 +134,7 @@ async fn delete(
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let (token, file) = components.into_inner();
|
let (token, file) = components.into_inner();
|
||||||
|
|
||||||
let url = format!(
|
let url = format!("{}/image/delete/{}/{}", pictrs_url()?, &token, &file);
|
||||||
"{}/image/delete/{}/{}",
|
|
||||||
Settings::get().pictrs_url(),
|
|
||||||
&token,
|
|
||||||
&file
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut client_req = client.request_from(url, req.head());
|
let mut client_req = client.request_from(url, req.head());
|
||||||
|
|
||||||
|
@ -162,3 +150,9 @@ async fn delete(
|
||||||
|
|
||||||
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
|
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pictrs_url() -> Result<String, LemmyError> {
|
||||||
|
Settings::get()
|
||||||
|
.pictrs_url
|
||||||
|
.ok_or_else(|| anyhow!("images_disabled").into())
|
||||||
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
|
||||||
.await?
|
.await?
|
||||||
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
|
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
|
||||||
|
|
||||||
let protocols = if Settings::get().federation().enabled {
|
let protocols = if Settings::get().federation.enabled {
|
||||||
vec!["activitypub".to_string()]
|
vec!["activitypub".to_string()]
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct Params {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
if Settings::get().federation().enabled {
|
if Settings::get().federation.enabled {
|
||||||
cfg.route(
|
cfg.route(
|
||||||
".well-known/webfinger",
|
".well-known/webfinger",
|
||||||
web::get().to(get_webfinger_response),
|
web::get().to(get_webfinger_response),
|
||||||
|
|
|
@ -35,5 +35,4 @@ diesel = "1.4.7"
|
||||||
http = "0.2.4"
|
http = "0.2.4"
|
||||||
jsonwebtoken = "7.2.0"
|
jsonwebtoken = "7.2.0"
|
||||||
deser-hjson = "1.0.1"
|
deser-hjson = "1.0.1"
|
||||||
merge = "0.1.0"
|
smart-default = "0.6.0"
|
||||||
envy = "0.4.2"
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ impl Claims {
|
||||||
};
|
};
|
||||||
decode::<Claims>(
|
decode::<Claims>(
|
||||||
jwt,
|
jwt,
|
||||||
&DecodingKey::from_secret(Settings::get().jwt_secret().as_ref()),
|
&DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
|
||||||
&v,
|
&v,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,13 @@ impl Claims {
|
||||||
pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> {
|
pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> {
|
||||||
let my_claims = Claims {
|
let my_claims = Claims {
|
||||||
sub: local_user_id,
|
sub: local_user_id,
|
||||||
iss: Settings::get().hostname(),
|
iss: Settings::get().hostname,
|
||||||
iat: Utc::now().timestamp(),
|
iat: Utc::now().timestamp(),
|
||||||
};
|
};
|
||||||
encode(
|
encode(
|
||||||
&Header::default(),
|
&Header::default(),
|
||||||
&my_claims,
|
&my_claims,
|
||||||
&EncodingKey::from_secret(Settings::get().jwt_secret().as_ref()),
|
&EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ pub fn send_email(
|
||||||
to_username: &str,
|
to_username: &str,
|
||||||
html: &str,
|
html: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let email_config = Settings::get().email().ok_or("no_email_setup")?;
|
let email_config = Settings::get().email.ok_or("no_email_setup")?;
|
||||||
let domain = Settings::get().hostname();
|
let domain = Settings::get().hostname;
|
||||||
|
|
||||||
let (smtp_server, smtp_port) = {
|
let (smtp_server, smtp_port) = {
|
||||||
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
|
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate strum_macros;
|
extern crate strum_macros;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate smart_default;
|
||||||
|
|
||||||
pub mod apub;
|
pub mod apub;
|
||||||
pub mod claims;
|
pub mod claims;
|
||||||
|
@ -90,12 +92,12 @@ impl actix_web::error::ResponseError for LemmyError {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
|
pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!(
|
||||||
"^group:([a-z0-9_]{{3,}})@{}$",
|
"^group:([a-z0-9_]{{3,}})@{}$",
|
||||||
Settings::get().hostname()
|
Settings::get().hostname
|
||||||
))
|
))
|
||||||
.expect("compile webfinger regex");
|
.expect("compile webfinger regex");
|
||||||
pub static ref WEBFINGER_USERNAME_REGEX: Regex = Regex::new(&format!(
|
pub static ref WEBFINGER_USERNAME_REGEX: Regex = Regex::new(&format!(
|
||||||
"^acct:([a-z0-9_]{{3,}})@{}$",
|
"^acct:([a-z0-9_]{{3,}})@{}$",
|
||||||
Settings::get().hostname()
|
Settings::get().hostname
|
||||||
))
|
))
|
||||||
.expect("compile webfinger regex");
|
.expect("compile webfinger regex");
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,9 @@ impl RateLimited {
|
||||||
{
|
{
|
||||||
// Does not need to be blocking because the RwLock in settings never held across await points,
|
// Does not need to be blocking because the RwLock in settings never held across await points,
|
||||||
// and the operation here locks only long enough to clone
|
// and the operation here locks only long enough to clone
|
||||||
let rate_limit: RateLimitConfig = Settings::get().rate_limit();
|
let rate_limit: RateLimitConfig = Settings::get()
|
||||||
|
.rate_limit
|
||||||
|
.unwrap_or_else(RateLimitConfig::default);
|
||||||
|
|
||||||
// before
|
// before
|
||||||
{
|
{
|
||||||
|
|
|
@ -48,26 +48,30 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct IframelyResponse {
|
pub struct IframelyResponse {
|
||||||
title: Option<String>,
|
pub title: Option<String>,
|
||||||
description: Option<String>,
|
pub description: Option<String>,
|
||||||
thumbnail_url: Option<Url>,
|
thumbnail_url: Option<Url>,
|
||||||
html: Option<String>,
|
pub html: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn fetch_iframely(
|
pub(crate) async fn fetch_iframely(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
) -> Result<IframelyResponse, LemmyError> {
|
) -> Result<IframelyResponse, LemmyError> {
|
||||||
let fetch_url = format!("{}/oembed?url={}", Settings::get().iframely_url(), url);
|
if let Some(iframely_url) = Settings::get().iframely_url {
|
||||||
|
let fetch_url = format!("{}/oembed?url={}", iframely_url, url);
|
||||||
|
|
||||||
let response = retry(|| client.get(&fetch_url).send()).await?;
|
let response = retry(|| client.get(&fetch_url).send()).await?;
|
||||||
|
|
||||||
let res: IframelyResponse = response
|
let res: IframelyResponse = response
|
||||||
.json()
|
.json()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| RecvError(e.to_string()))?;
|
.map_err(|e| RecvError(e.to_string()))?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Missing Iframely URL in config.").into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
@ -85,91 +89,73 @@ pub(crate) struct PictrsFile {
|
||||||
pub(crate) async fn fetch_pictrs(
|
pub(crate) async fn fetch_pictrs(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
image_url: &Url,
|
image_url: &Url,
|
||||||
) -> Result<PictrsResponse, LemmyError> {
|
) -> Result<Option<PictrsResponse>, LemmyError> {
|
||||||
is_image_content_type(client, image_url).await?;
|
if let Some(pictrs_url) = Settings::get().pictrs_url {
|
||||||
|
is_image_content_type(client, image_url).await?;
|
||||||
|
|
||||||
let fetch_url = format!(
|
let fetch_url = format!(
|
||||||
"{}/image/download?url={}",
|
"{}/image/download?url={}",
|
||||||
Settings::get().pictrs_url(),
|
pictrs_url,
|
||||||
utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
|
utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = retry(|| client.get(&fetch_url).send()).await?;
|
let response = retry(|| client.get(&fetch_url).send()).await?;
|
||||||
|
|
||||||
let response: PictrsResponse = response
|
let response: PictrsResponse = response
|
||||||
.json()
|
.json()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| RecvError(e.to_string()))?;
|
.map_err(|e| RecvError(e.to_string()))?;
|
||||||
|
|
||||||
if response.msg == "ok" {
|
if response.msg == "ok" {
|
||||||
Ok(response)
|
Ok(Some(response))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("{}", &response.msg).into())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("{}", &response.msg).into())
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_iframely_and_pictrs_data(
|
pub async fn fetch_iframely_and_pictrs_data(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
url: Option<&Url>,
|
url: Option<&Url>,
|
||||||
) -> (Option<String>, Option<String>, Option<String>, Option<Url>) {
|
) -> Result<(Option<IframelyResponse>, Option<Url>), LemmyError> {
|
||||||
match &url {
|
match &url {
|
||||||
Some(url) => {
|
Some(url) => {
|
||||||
// Fetch iframely data
|
// Fetch iframely data
|
||||||
let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
|
let iframely_res_option = fetch_iframely(client, url).await.ok();
|
||||||
match fetch_iframely(client, url).await {
|
|
||||||
Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
|
|
||||||
Err(e) => {
|
|
||||||
error!("iframely err: {}", e);
|
|
||||||
(None, None, None, None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch pictrs thumbnail
|
// Fetch pictrs thumbnail
|
||||||
let pictrs_hash = match iframely_thumbnail_url {
|
let pictrs_hash = match &iframely_res_option {
|
||||||
Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
|
Some(iframely_res) => match &iframely_res.thumbnail_url {
|
||||||
Ok(res) => Some(res.files[0].file.to_owned()),
|
Some(iframely_thumbnail_url) => fetch_pictrs(client, iframely_thumbnail_url)
|
||||||
Err(e) => {
|
.await?
|
||||||
error!("pictrs err: {}", e);
|
.map(|r| r.files[0].file.to_owned()),
|
||||||
None
|
// Try to generate a small thumbnail if iframely is not supported
|
||||||
}
|
None => fetch_pictrs(client, url)
|
||||||
},
|
.await?
|
||||||
// Try to generate a small thumbnail if iframely is not supported
|
.map(|r| r.files[0].file.to_owned()),
|
||||||
None => match fetch_pictrs(client, url).await {
|
|
||||||
Ok(res) => Some(res.files[0].file.to_owned()),
|
|
||||||
Err(e) => {
|
|
||||||
error!("pictrs err: {}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
None => fetch_pictrs(client, url)
|
||||||
|
.await?
|
||||||
|
.map(|r| r.files[0].file.to_owned()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// The full urls are necessary for federation
|
// The full urls are necessary for federation
|
||||||
let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
|
let pictrs_thumbnail = pictrs_hash
|
||||||
let url = Url::parse(&format!(
|
.map(|p| {
|
||||||
"{}/pictrs/image/{}",
|
Url::parse(&format!(
|
||||||
Settings::get().get_protocol_and_hostname(),
|
"{}/pictrs/image/{}",
|
||||||
pictrs_hash
|
Settings::get().get_protocol_and_hostname(),
|
||||||
));
|
p
|
||||||
match url {
|
))
|
||||||
Ok(parsed_url) => Some(parsed_url),
|
.ok()
|
||||||
Err(e) => {
|
})
|
||||||
// This really shouldn't happen unless the settings or hash are malformed
|
.flatten();
|
||||||
error!("Unexpected error constructing pictrs thumbnail URL: {}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
Ok((iframely_res_option, pictrs_thumbnail))
|
||||||
iframely_title,
|
|
||||||
iframely_description,
|
|
||||||
iframely_html,
|
|
||||||
pictrs_thumbnail,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
None => (None, None, None, None),
|
None => Ok((None, None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
use crate::settings::{CaptchaConfig, DatabaseConfig, FederationConfig, RateLimitConfig, Settings};
|
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
|
||||||
|
|
||||||
impl Default for Settings {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
database: Some(DatabaseConfig::default()),
|
|
||||||
rate_limit: Some(RateLimitConfig::default()),
|
|
||||||
federation: Some(FederationConfig::default()),
|
|
||||||
captcha: Some(CaptchaConfig::default()),
|
|
||||||
email: None,
|
|
||||||
setup: None,
|
|
||||||
hostname: None,
|
|
||||||
bind: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
|
|
||||||
port: Some(8536),
|
|
||||||
tls_enabled: Some(true),
|
|
||||||
jwt_secret: Some("changeme".into()),
|
|
||||||
pictrs_url: Some("http://pictrs:8080".into()),
|
|
||||||
iframely_url: Some("http://iframely".into()),
|
|
||||||
additional_slurs: None,
|
|
||||||
actor_name_max_length: Some(20),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DatabaseConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
user: Some("lemmy".to_string()),
|
|
||||||
password: "password".into(),
|
|
||||||
host: "localhost".into(),
|
|
||||||
port: Some(5432),
|
|
||||||
database: Some("lemmy".to_string()),
|
|
||||||
pool_size: Some(5),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CaptchaConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enabled: true,
|
|
||||||
difficulty: "medium".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FederationConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enabled: false,
|
|
||||||
allowed_instances: None,
|
|
||||||
blocked_instances: None,
|
|
||||||
strict_allowlist: Some(true),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RateLimitConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
message: 180,
|
|
||||||
message_per_second: 60,
|
|
||||||
post: 6,
|
|
||||||
post_per_second: 600,
|
|
||||||
register: 3,
|
|
||||||
register_per_second: 3600,
|
|
||||||
image: 6,
|
|
||||||
image_per_second: 3600,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,8 @@
|
||||||
use crate::{
|
use crate::{location_info, settings::structs::Settings, LemmyError};
|
||||||
location_info,
|
|
||||||
settings::structs::{
|
|
||||||
CaptchaConfig,
|
|
||||||
DatabaseConfig,
|
|
||||||
EmailConfig,
|
|
||||||
FederationConfig,
|
|
||||||
RateLimitConfig,
|
|
||||||
Settings,
|
|
||||||
SetupConfig,
|
|
||||||
},
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use deser_hjson::from_str;
|
use deser_hjson::from_str;
|
||||||
use merge::Merge;
|
use std::{env, fs, io::Error, sync::RwLock};
|
||||||
use std::{env, fs, io::Error, net::IpAddr, sync::RwLock};
|
|
||||||
|
|
||||||
pub mod defaults;
|
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
||||||
static CONFIG_FILE: &str = "config/config.hjson";
|
static CONFIG_FILE: &str = "config/config.hjson";
|
||||||
|
@ -28,27 +14,18 @@ lazy_static! {
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
/// Reads config from configuration file.
|
/// Reads config from configuration file.
|
||||||
/// Then values from the environment (with prefix LEMMY) are added to the config.
|
|
||||||
/// And then default values are merged into config.
|
|
||||||
/// Defaults are controlled by Default trait implemntation for Settings structs.
|
|
||||||
///
|
///
|
||||||
/// Note: The env var `LEMMY_DATABASE_URL` is parsed in
|
/// Note: The env var `LEMMY_DATABASE_URL` is parsed in
|
||||||
/// `lemmy_db_queries/src/lib.rs::get_database_url_from_env()`
|
/// `lemmy_db_queries/src/lib.rs::get_database_url_from_env()`
|
||||||
fn init() -> Result<Self, LemmyError> {
|
fn init() -> Result<Self, LemmyError> {
|
||||||
// Read the config file
|
// Read the config file
|
||||||
let mut custom_config = from_str::<Settings>(&Self::read_config_file()?)?;
|
let config = from_str::<Settings>(&Self::read_config_file()?)?;
|
||||||
|
|
||||||
// Merge with env vars
|
if config.hostname == "unset" {
|
||||||
custom_config.merge(envy::prefixed("LEMMY_").from_env::<Settings>()?);
|
|
||||||
|
|
||||||
// Merge with default
|
|
||||||
custom_config.merge(Settings::default());
|
|
||||||
|
|
||||||
if custom_config.hostname == Settings::default().hostname {
|
|
||||||
return Err(anyhow!("Hostname variable is not set!").into());
|
return Err(anyhow!("Hostname variable is not set!").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(custom_config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the config as a struct.
|
/// Returns the config as a struct.
|
||||||
|
@ -57,14 +34,10 @@ impl Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_database_url(&self) -> String {
|
pub fn get_database_url(&self) -> String {
|
||||||
let conf = self.database();
|
let conf = &self.database;
|
||||||
format!(
|
format!(
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
conf.user(),
|
conf.user, conf.password, conf.host, conf.port, conf.database,
|
||||||
conf.password,
|
|
||||||
conf.host,
|
|
||||||
conf.port(),
|
|
||||||
conf.database(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,22 +49,10 @@ impl Settings {
|
||||||
fs::read_to_string(Self::get_config_location())
|
fs::read_to_string(Self::get_config_location())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_allowed_instances(&self) -> Option<Vec<String>> {
|
|
||||||
self.federation().allowed_instances
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_blocked_instances(&self) -> Option<Vec<String>> {
|
|
||||||
self.federation().blocked_instances
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns either "http" or "https", depending on tls_enabled setting
|
/// Returns either "http" or "https", depending on tls_enabled setting
|
||||||
pub fn get_protocol_string(&self) -> &'static str {
|
pub fn get_protocol_string(&self) -> &'static str {
|
||||||
if let Some(tls_enabled) = self.tls_enabled {
|
if self.tls_enabled {
|
||||||
if tls_enabled {
|
"https"
|
||||||
"https"
|
|
||||||
} else {
|
|
||||||
"http"
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
"http"
|
"http"
|
||||||
}
|
}
|
||||||
|
@ -100,7 +61,7 @@ impl Settings {
|
||||||
/// Returns something like `http://localhost` or `https://lemmy.ml`,
|
/// Returns something like `http://localhost` or `https://lemmy.ml`,
|
||||||
/// with the correct protocol and hostname.
|
/// with the correct protocol and hostname.
|
||||||
pub fn get_protocol_and_hostname(&self) -> String {
|
pub fn get_protocol_and_hostname(&self) -> String {
|
||||||
format!("{}://{}", self.get_protocol_string(), self.hostname())
|
format!("{}://{}", self.get_protocol_string(), self.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When running the federation test setup in `api_tests/` or `docker/federation`, the `hostname`
|
/// When running the federation test setup in `api_tests/` or `docker/federation`, the `hostname`
|
||||||
|
@ -109,7 +70,7 @@ impl Settings {
|
||||||
pub fn get_hostname_without_port(&self) -> Result<String, anyhow::Error> {
|
pub fn get_hostname_without_port(&self) -> Result<String, anyhow::Error> {
|
||||||
Ok(
|
Ok(
|
||||||
self
|
self
|
||||||
.hostname()
|
.hostname
|
||||||
.split(':')
|
.split(':')
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.first()
|
.first()
|
||||||
|
@ -131,67 +92,4 @@ impl Settings {
|
||||||
|
|
||||||
Ok(Self::read_config_file()?)
|
Ok(Self::read_config_file()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hostname(&self) -> String {
|
|
||||||
self.hostname.to_owned().expect("No hostname given")
|
|
||||||
}
|
|
||||||
pub fn bind(&self) -> IpAddr {
|
|
||||||
self.bind.expect("return bind address")
|
|
||||||
}
|
|
||||||
pub fn port(&self) -> u16 {
|
|
||||||
self
|
|
||||||
.port
|
|
||||||
.unwrap_or_else(|| Settings::default().port.expect("missing port"))
|
|
||||||
}
|
|
||||||
pub fn tls_enabled(&self) -> bool {
|
|
||||||
self.tls_enabled.unwrap_or_else(|| {
|
|
||||||
Settings::default()
|
|
||||||
.tls_enabled
|
|
||||||
.expect("missing tls_enabled")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn jwt_secret(&self) -> String {
|
|
||||||
self
|
|
||||||
.jwt_secret
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| Settings::default().jwt_secret.expect("missing jwt_secret"))
|
|
||||||
}
|
|
||||||
pub fn pictrs_url(&self) -> String {
|
|
||||||
self
|
|
||||||
.pictrs_url
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| Settings::default().pictrs_url.expect("missing pictrs_url"))
|
|
||||||
}
|
|
||||||
pub fn iframely_url(&self) -> String {
|
|
||||||
self.iframely_url.to_owned().unwrap_or_else(|| {
|
|
||||||
Settings::default()
|
|
||||||
.iframely_url
|
|
||||||
.expect("missing iframely_url")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn actor_name_max_length(&self) -> usize {
|
|
||||||
self.actor_name_max_length.unwrap_or_else(|| {
|
|
||||||
Settings::default()
|
|
||||||
.actor_name_max_length
|
|
||||||
.expect("missing actor name length")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn database(&self) -> DatabaseConfig {
|
|
||||||
self.database.to_owned().unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn rate_limit(&self) -> RateLimitConfig {
|
|
||||||
self.rate_limit.to_owned().unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn federation(&self) -> FederationConfig {
|
|
||||||
self.federation.to_owned().unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn captcha(&self) -> CaptchaConfig {
|
|
||||||
self.captcha.to_owned().unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn email(&self) -> Option<EmailConfig> {
|
|
||||||
self.email.to_owned()
|
|
||||||
}
|
|
||||||
pub fn setup(&self) -> Option<SetupConfig> {
|
|
||||||
self.setup.to_owned()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +1,65 @@
|
||||||
use merge::Merge;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::net::IpAddr;
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Merge)]
|
#[derive(Debug, Deserialize, Clone, SmartDefault)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub(crate) database: Option<DatabaseConfig>,
|
#[serde(default)]
|
||||||
pub(crate) rate_limit: Option<RateLimitConfig>,
|
pub database: DatabaseConfig,
|
||||||
pub(crate) federation: Option<FederationConfig>,
|
#[default(Some(RateLimitConfig::default()))]
|
||||||
pub(crate) hostname: Option<String>,
|
pub rate_limit: Option<RateLimitConfig>,
|
||||||
pub(crate) bind: Option<IpAddr>,
|
#[default(FederationConfig::default())]
|
||||||
pub(crate) port: Option<u16>,
|
pub federation: FederationConfig,
|
||||||
pub(crate) tls_enabled: Option<bool>,
|
#[default(CaptchaConfig::default())]
|
||||||
pub(crate) jwt_secret: Option<String>,
|
pub captcha: CaptchaConfig,
|
||||||
pub(crate) pictrs_url: Option<String>,
|
#[default(None)]
|
||||||
pub(crate) iframely_url: Option<String>,
|
pub email: Option<EmailConfig>,
|
||||||
pub(crate) captcha: Option<CaptchaConfig>,
|
#[default(None)]
|
||||||
pub(crate) email: Option<EmailConfig>,
|
pub setup: Option<SetupConfig>,
|
||||||
pub(crate) setup: Option<SetupConfig>,
|
#[default("unset")]
|
||||||
pub(crate) additional_slurs: Option<String>,
|
pub hostname: String,
|
||||||
pub(crate) actor_name_max_length: Option<usize>,
|
#[default(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))]
|
||||||
|
pub bind: IpAddr,
|
||||||
|
#[default(8536)]
|
||||||
|
pub port: u16,
|
||||||
|
#[default(true)]
|
||||||
|
pub tls_enabled: bool,
|
||||||
|
#[default("changeme")]
|
||||||
|
pub jwt_secret: String,
|
||||||
|
#[default(None)]
|
||||||
|
pub pictrs_url: Option<String>,
|
||||||
|
#[default(None)]
|
||||||
|
pub iframely_url: Option<String>,
|
||||||
|
#[default(None)]
|
||||||
|
pub additional_slurs: Option<String>,
|
||||||
|
#[default(20)]
|
||||||
|
pub actor_name_max_length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, SmartDefault)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct CaptchaConfig {
|
pub struct CaptchaConfig {
|
||||||
|
#[default(false)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
#[default("medium")]
|
||||||
pub difficulty: String,
|
pub difficulty: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, SmartDefault)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct DatabaseConfig {
|
pub struct DatabaseConfig {
|
||||||
pub(super) user: Option<String>,
|
#[default("lemmy")]
|
||||||
|
pub(super) user: String,
|
||||||
|
#[default("password")]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
#[default("localhost")]
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub(super) port: Option<i32>,
|
#[default(5432)]
|
||||||
pub(super) database: Option<String>,
|
pub(super) port: i32,
|
||||||
pub(super) pool_size: Option<u32>,
|
#[default("lemmy")]
|
||||||
}
|
pub(super) database: String,
|
||||||
|
#[default(5)]
|
||||||
impl DatabaseConfig {
|
pub pool_size: u32,
|
||||||
pub fn user(&self) -> String {
|
|
||||||
self
|
|
||||||
.user
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| DatabaseConfig::default().user.expect("missing user"))
|
|
||||||
}
|
|
||||||
pub fn port(&self) -> i32 {
|
|
||||||
self
|
|
||||||
.port
|
|
||||||
.unwrap_or_else(|| DatabaseConfig::default().port.expect("missing port"))
|
|
||||||
}
|
|
||||||
pub fn database(&self) -> String {
|
|
||||||
self.database.to_owned().unwrap_or_else(|| {
|
|
||||||
DatabaseConfig::default()
|
|
||||||
.database
|
|
||||||
.expect("missing database")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn pool_size(&self) -> u32 {
|
|
||||||
self.pool_size.unwrap_or_else(|| {
|
|
||||||
DatabaseConfig::default()
|
|
||||||
.pool_size
|
|
||||||
.expect("missing pool_size")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
@ -74,23 +71,37 @@ pub struct EmailConfig {
|
||||||
pub use_tls: bool,
|
pub use_tls: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, SmartDefault)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct FederationConfig {
|
pub struct FederationConfig {
|
||||||
|
#[default(false)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
#[default(None)]
|
||||||
pub allowed_instances: Option<Vec<String>>,
|
pub allowed_instances: Option<Vec<String>>,
|
||||||
|
#[default(None)]
|
||||||
pub blocked_instances: Option<Vec<String>>,
|
pub blocked_instances: Option<Vec<String>>,
|
||||||
pub strict_allowlist: Option<bool>,
|
#[default(true)]
|
||||||
|
pub strict_allowlist: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, SmartDefault)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct RateLimitConfig {
|
pub struct RateLimitConfig {
|
||||||
|
#[default(180)]
|
||||||
pub message: i32,
|
pub message: i32,
|
||||||
|
#[default(60)]
|
||||||
pub message_per_second: i32,
|
pub message_per_second: i32,
|
||||||
|
#[default(6)]
|
||||||
pub post: i32,
|
pub post: i32,
|
||||||
|
#[default(600)]
|
||||||
pub post_per_second: i32,
|
pub post_per_second: i32,
|
||||||
|
#[default(3)]
|
||||||
pub register: i32,
|
pub register: i32,
|
||||||
|
#[default(3600)]
|
||||||
pub register_per_second: i32,
|
pub register_per_second: i32,
|
||||||
|
#[default(6)]
|
||||||
pub image: i32,
|
pub image: i32,
|
||||||
|
#[default(3600)]
|
||||||
pub image_per_second: i32,
|
pub image_per_second: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub struct MentionData {
|
||||||
|
|
||||||
impl MentionData {
|
impl MentionData {
|
||||||
pub fn is_local(&self) -> bool {
|
pub fn is_local(&self) -> bool {
|
||||||
Settings::get().hostname().eq(&self.domain)
|
Settings::get().hostname.eq(&self.domain)
|
||||||
}
|
}
|
||||||
pub fn full_name(&self) -> String {
|
pub fn full_name(&self) -> String {
|
||||||
format!("@{}@{}", &self.name, &self.domain)
|
format!("@{}@{}", &self.name, &self.domain)
|
||||||
|
@ -116,7 +116,7 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_actor_name(name: &str) -> bool {
|
pub fn is_valid_actor_name(name: &str) -> bool {
|
||||||
name.chars().count() <= Settings::get().actor_name_max_length()
|
name.chars().count() <= Settings::get().actor_name_max_length
|
||||||
&& VALID_ACTOR_NAME_REGEX.is_match(name)
|
&& VALID_ACTOR_NAME_REGEX.is_match(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ pub fn is_valid_display_name(name: &str) -> bool {
|
||||||
!name.starts_with('@')
|
!name.starts_with('@')
|
||||||
&& !name.starts_with('\u{200b}')
|
&& !name.starts_with('\u{200b}')
|
||||||
&& name.chars().count() >= 3
|
&& name.chars().count() >= 3
|
||||||
&& name.chars().count() <= Settings::get().actor_name_max_length()
|
&& name.chars().count() <= Settings::get().actor_name_max_length
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_matrix_id(matrix_id: &str) -> bool {
|
pub fn is_valid_matrix_id(matrix_id: &str) -> bool {
|
||||||
|
|
|
@ -38,7 +38,7 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
};
|
};
|
||||||
let manager = ConnectionManager::<PgConnection>::new(&db_url);
|
let manager = ConnectionManager::<PgConnection>::new(&db_url);
|
||||||
let pool = Pool::builder()
|
let pool = Pool::builder()
|
||||||
.max_size(settings.database().pool_size())
|
.max_size(settings.database.pool_size)
|
||||||
.build(manager)
|
.build(manager)
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
|
.unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
|
||||||
|
|
||||||
|
@ -62,8 +62,7 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Starting http server at {}:{}",
|
"Starting http server at {}:{}",
|
||||||
settings.bind(),
|
settings.bind, settings.port
|
||||||
settings.port()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let activity_queue = create_activity_queue();
|
let activity_queue = create_activity_queue();
|
||||||
|
@ -97,7 +96,7 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
.configure(nodeinfo::config)
|
.configure(nodeinfo::config)
|
||||||
.configure(webfinger::config)
|
.configure(webfinger::config)
|
||||||
})
|
})
|
||||||
.bind((settings.bind(), settings.port()))?
|
.bind((settings.bind, settings.port))?
|
||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue