Merge remote-tracking branch 'origin/main' into thumbnail_sizes

This commit is contained in:
Dessalines 2024-05-14 23:02:09 -04:00
commit 1129b0b036
11 changed files with 421 additions and 346 deletions

575
Cargo.lock generated

File diff suppressed because it is too large Load diff

1
api_tests/.npmrc Normal file
View file

@ -0,0 +1 @@
package-manager-strict=false

View file

@ -657,40 +657,60 @@ test("A and G subscribe to B (center) A posts, it gets announced to G", async ()
}); });
test("Report a post", async () => { test("Report a post", async () => {
// Note, this is a different one from the setup // Create post from alpha
let betaCommunity = (await resolveBetaCommunity(beta)).community; let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
if (!betaCommunity) {
throw "Missing beta community";
}
await followBeta(alpha); await followBeta(alpha);
let postRes = await createPost(beta, betaCommunity.community.id); let postRes = await createPost(alpha, alphaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined(); expect(postRes.post_view.post).toBeDefined();
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post; let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
if (!alphaPost) { if (!alphaPost) {
throw "Missing alpha post"; throw "Missing alpha post";
} }
let alphaReport = (
await reportPost(alpha, alphaPost.post.id, randomString(10))
).post_report_view.post_report;
// Send report from gamma
let gammaPost = (await resolvePost(gamma, alphaPost.post)).post!;
let gammaReport = (
await reportPost(gamma, gammaPost.post.id, randomString(10))
).post_report_view.post_report;
expect(gammaReport).toBeDefined();
// Report was federated to community instance
let betaReport = (await waitUntil( let betaReport = (await waitUntil(
() => () =>
listPostReports(beta).then(p => listPostReports(beta).then(p =>
p.post_reports.find( p.post_reports.find(
r => r =>
r.post_report.original_post_name === alphaReport.original_post_name, r.post_report.original_post_name === gammaReport.original_post_name,
), ),
), ),
res => !!res, res => !!res,
))!.post_report; ))!.post_report;
expect(betaReport).toBeDefined(); expect(betaReport).toBeDefined();
expect(betaReport.resolved).toBe(false); expect(betaReport.resolved).toBe(false);
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name); expect(betaReport.original_post_name).toBe(gammaReport.original_post_name);
expect(betaReport.original_post_url).toBe(alphaReport.original_post_url); //expect(betaReport.original_post_url).toBe(gammaReport.original_post_url);
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body); expect(betaReport.original_post_body).toBe(gammaReport.original_post_body);
expect(betaReport.reason).toBe(alphaReport.reason); expect(betaReport.reason).toBe(gammaReport.reason);
await unfollowRemotes(alpha); await unfollowRemotes(alpha);
// Report was federated to poster's instance
let alphaReport = (await waitUntil(
() =>
listPostReports(alpha).then(p =>
p.post_reports.find(
r =>
r.post_report.original_post_name === gammaReport.original_post_name,
),
),
res => !!res,
))!.post_report;
expect(alphaReport).toBeDefined();
expect(alphaReport.resolved).toBe(false);
expect(alphaReport.original_post_name).toBe(gammaReport.original_post_name);
//expect(alphaReport.original_post_url).toBe(gammaReport.original_post_url);
expect(alphaReport.original_post_body).toBe(gammaReport.original_post_body);
expect(alphaReport.reason).toBe(gammaReport.reason);
}); });
test("Fetch post via redirect", async () => { test("Fetch post via redirect", async () => {

View file

@ -29,7 +29,7 @@ pub async fn add_admin(
.await? .await?
.ok_or(LemmyErrorType::ObjectNotLocal)?; .ok_or(LemmyErrorType::ObjectNotLocal)?;
let added_admin = LocalUser::update( LocalUser::update(
&mut context.pool(), &mut context.pool(),
added_local_user.local_user.id, added_local_user.local_user.id,
&LocalUserUpdateForm { &LocalUserUpdateForm {
@ -43,7 +43,7 @@ pub async fn add_admin(
// Mod tables // Mod tables
let form = ModAddForm { let form = ModAddForm {
mod_person_id: local_user_view.person.id, mod_person_id: local_user_view.person.id,
other_person_id: added_admin.person_id, other_person_id: added_local_user.person.id,
removed: Some(!data.added), removed: Some(!data.added),
}; };

View file

@ -141,11 +141,7 @@ pub async fn save_user_settings(
..Default::default() ..Default::default()
}; };
// Ignore errors, because 'no fields updated' will return an error. LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
// https://github.com/LemmyNet/lemmy/issues/4076
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form)
.await
.ok();
// Update the vote display modes // Update the vote display modes
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm { let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {

View file

@ -9,12 +9,10 @@ use lemmy_db_schema::{
source::{ source::{
email_verification::EmailVerification, email_verification::EmailVerification,
local_user::{LocalUser, LocalUserUpdateForm}, local_user::{LocalUser, LocalUserUpdateForm},
person::Person,
}, },
traits::Crud,
RegistrationMode, RegistrationMode,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorType, LemmyResult};
pub async fn verify_email( pub async fn verify_email(
@ -38,7 +36,7 @@ pub async fn verify_email(
}; };
let local_user_id = verification.local_user_id; let local_user_id = verification.local_user_id;
let local_user = LocalUser::update(&mut context.pool(), local_user_id, &form).await?; LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?; EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
@ -46,12 +44,16 @@ pub async fn verify_email(
if site_view.local_site.registration_mode == RegistrationMode::RequireApplication if site_view.local_site.registration_mode == RegistrationMode::RequireApplication
&& site_view.local_site.application_email_admins && site_view.local_site.application_email_admins
{ {
let person = Person::read(&mut context.pool(), local_user.person_id) let local_user = LocalUserView::read(&mut context.pool(), local_user_id)
.await? .await?
.ok_or(LemmyErrorType::CouldntFindPerson)?; .ok_or(LemmyErrorType::CouldntFindPerson)?;
send_new_applicant_email_to_admins(&person.name, &mut context.pool(), context.settings()) send_new_applicant_email_to_admins(
.await?; &local_user.person.name,
&mut context.pool(),
context.settings(),
)
.await?;
} }
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))

View file

@ -40,7 +40,7 @@ use tracing::info;
/// ///
/// Be careful with any changes to this struct, to avoid breaking changes which could prevent /// Be careful with any changes to this struct, to avoid breaking changes which could prevent
/// importing older backups. /// importing older backups.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct UserSettingsBackup { pub struct UserSettingsBackup {
pub display_name: Option<String>, pub display_name: Option<String>,
pub bio: Option<String>, pub bio: Option<String>,
@ -322,7 +322,7 @@ pub async fn import_settings(
#[allow(clippy::indexing_slicing)] #[allow(clippy::indexing_slicing)]
mod tests { mod tests {
use crate::api::user_settings_backup::{export_settings, import_settings}; use crate::api::user_settings_backup::{export_settings, import_settings, UserSettingsBackup};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -420,6 +420,44 @@ mod tests {
Ok(()) Ok(())
} }
#[tokio::test]
#[serial]
async fn test_settings_partial_import() -> LemmyResult<()> {
let context = LemmyContext::init_test_context().await;
let export_user =
create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?;
let community_form = CommunityInsertForm::builder()
.name("testcom".to_string())
.title("testcom".to_string())
.instance_id(export_user.person.instance_id)
.build();
let community = Community::create(&mut context.pool(), &community_form).await?;
let follower_form = CommunityFollowerForm {
community_id: community.id,
person_id: export_user.person.id,
pending: false,
};
CommunityFollower::follow(&mut context.pool(), &follower_form).await?;
let backup = export_settings(export_user.clone(), context.reset_request_count()).await?;
let import_user = create_user("charles".to_string(), None, &context).await?;
let backup2 = UserSettingsBackup {
followed_communities: backup.followed_communities.clone(),
..Default::default()
};
import_settings(
actix_web::web::Json(backup2),
import_user.clone(),
context.reset_request_count(),
)
.await?;
Ok(())
}
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn disallow_large_backup() -> LemmyResult<()> { async fn disallow_large_backup() -> LemmyResult<()> {

View file

@ -55,12 +55,17 @@ impl LocalUser {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
local_user_id: LocalUserId, local_user_id: LocalUserId,
form: &LocalUserUpdateForm, form: &LocalUserUpdateForm,
) -> Result<LocalUser, Error> { ) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::update(local_user::table.find(local_user_id)) let res = diesel::update(local_user::table.find(local_user_id))
.set(form) .set(form)
.get_result::<Self>(conn) .execute(conn)
.await .await;
// Diesel will throw an error if the query is all Nones (not updating anything), ignore this.
match res {
Err(Error::QueryBuilderError(_)) => Ok(0),
other => other,
}
} }
pub async fn delete(pool: &mut DbPool<'_>, id: LocalUserId) -> Result<usize, Error> { pub async fn delete(pool: &mut DbPool<'_>, id: LocalUserId) -> Result<usize, Error> {

View file

@ -953,9 +953,8 @@ mod tests {
show_bot_accounts: Some(false), show_bot_accounts: Some(false),
..Default::default() ..Default::default()
}; };
let inserted_local_user = LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?; data.local_user_view.local_user.show_bot_accounts = false;
data.local_user_view.local_user = inserted_local_user;
let read_post_listing = PostQuery { let read_post_listing = PostQuery {
community_id: Some(data.inserted_community.id), community_id: Some(data.inserted_community.id),
@ -989,9 +988,8 @@ mod tests {
show_bot_accounts: Some(true), show_bot_accounts: Some(true),
..Default::default() ..Default::default()
}; };
let inserted_local_user = LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?; data.local_user_view.local_user.show_bot_accounts = true;
data.local_user_view.local_user = inserted_local_user;
let post_listings_with_bots = PostQuery { let post_listings_with_bots = PostQuery {
community_id: Some(data.inserted_community.id), community_id: Some(data.inserted_community.id),
@ -1113,9 +1111,8 @@ mod tests {
show_bot_accounts: Some(false), show_bot_accounts: Some(false),
..Default::default() ..Default::default()
}; };
let inserted_local_user = LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?; data.local_user_view.local_user.show_bot_accounts = false;
data.local_user_view.local_user = inserted_local_user;
let read_post_listing = PostQuery { let read_post_listing = PostQuery {
community_id: Some(data.inserted_community.id), community_id: Some(data.inserted_community.id),
@ -1536,9 +1533,8 @@ mod tests {
show_read_posts: Some(false), show_read_posts: Some(false),
..Default::default() ..Default::default()
}; };
let inserted_local_user = LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?; data.local_user_view.local_user.show_read_posts = false;
data.local_user_view.local_user = inserted_local_user;
// Mark a post as read // Mark a post as read
PostRead::mark_as_read( PostRead::mark_as_read(

View file

@ -43,21 +43,18 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))? .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
.ok_or(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?; .ok_or(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
let protocols = if site_view.local_site.federation_enabled {
Some(vec!["activitypub".to_string()])
} else {
None
};
// Since there are 3 registration options, // Since there are 3 registration options,
// we need to set open_registrations as true if RegistrationMode is not Closed. // we need to set open_registrations as true if RegistrationMode is not Closed.
let open_registrations = Some(site_view.local_site.registration_mode != RegistrationMode::Closed); let open_registrations = Some(site_view.local_site.registration_mode != RegistrationMode::Closed);
let json = NodeInfo { let json = NodeInfo {
version: Some("2.0".to_string()), version: Some("2.1".to_string()),
software: Some(NodeInfoSoftware { software: Some(NodeInfoSoftware {
name: Some("lemmy".to_string()), name: Some("lemmy".to_string()),
version: Some(VERSION.to_string()), version: Some(VERSION.to_string()),
repository: Some("https://github.com/LemmyNet/lemmy".to_string()),
homepage: Some("https://join-lemmy.org/".to_string()),
}), }),
protocols, protocols: Some(vec!["activitypub".to_string()]),
usage: Some(NodeInfoUsage { usage: Some(NodeInfoUsage {
users: Some(NodeInfoUsers { users: Some(NodeInfoUsers {
total: Some(site_view.counts.users), total: Some(site_view.counts.users),
@ -68,6 +65,11 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
local_comments: Some(site_view.counts.comments), local_comments: Some(site_view.counts.comments),
}), }),
open_registrations, open_registrations,
services: Some(NodeInfoServices {
inbound: Some(vec![]),
outbound: Some(vec![]),
}),
metadata: Some(vec![]),
}; };
Ok(HttpResponse::Ok().json(json)) Ok(HttpResponse::Ok().json(json))
@ -84,6 +86,7 @@ struct NodeInfoWellKnownLinks {
pub href: Url, pub href: Url,
} }
/// Nodeinfo spec: http://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1
#[derive(Serialize, Deserialize, Debug, Default)] #[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase", default)] #[serde(rename_all = "camelCase", default)]
pub struct NodeInfo { pub struct NodeInfo {
@ -92,6 +95,9 @@ pub struct NodeInfo {
pub protocols: Option<Vec<String>>, pub protocols: Option<Vec<String>>,
pub usage: Option<NodeInfoUsage>, pub usage: Option<NodeInfoUsage>,
pub open_registrations: Option<bool>, pub open_registrations: Option<bool>,
/// These fields are required by the spec for no reason
pub services: Option<NodeInfoServices>,
pub metadata: Option<Vec<String>>,
} }
#[derive(Serialize, Deserialize, Debug, Default)] #[derive(Serialize, Deserialize, Debug, Default)]
@ -99,6 +105,8 @@ pub struct NodeInfo {
pub struct NodeInfoSoftware { pub struct NodeInfoSoftware {
pub name: Option<String>, pub name: Option<String>,
pub version: Option<String>, pub version: Option<String>,
pub repository: Option<String>,
pub homepage: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, Default)] #[derive(Serialize, Deserialize, Debug, Default)]
@ -116,3 +124,10 @@ pub struct NodeInfoUsers {
pub active_halfyear: Option<i64>, pub active_halfyear: Option<i64>,
pub active_month: Option<i64>, pub active_month: Option<i64>,
} }
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct NodeInfoServices {
pub inbound: Option<Vec<String>>,
pub outbound: Option<Vec<String>>,
}

View file

@ -262,12 +262,22 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
// User // User
.service( .service(
// Account action, I don't like that it's in /user maybe /accounts // Account action, I don't like that it's in /user maybe /accounts
// Handle /user/register separately to add the register() rate limitter // Handle /user/register separately to add the register() rate limiter
web::resource("/user/register") web::resource("/user/register")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(register)), .route(web::post().to(register)),
) )
// User
.service(
// Handle /user/login separately to add the register() rate limiter
// TODO: pretty annoying way to apply rate limits for register and login, we should
// group them under a common path so that rate limit is only applied once (eg under /account).
web::resource("/user/login")
.guard(guard::Post())
.wrap(rate_limit.register())
.route(web::post().to(login)),
)
.service( .service(
// Handle captcha separately // Handle captcha separately
web::resource("/user/get_captcha") web::resource("/user/get_captcha")
@ -306,7 +316,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.route("/banned", web::get().to(list_banned_users)) .route("/banned", web::get().to(list_banned_users))
.route("/block", web::post().to(block_person)) .route("/block", web::post().to(block_person))
// TODO Account actions. I don't like that they're in /user maybe /accounts // TODO Account actions. I don't like that they're in /user maybe /accounts
.route("/login", web::post().to(login))
.route("/logout", web::post().to(logout)) .route("/logout", web::post().to(logout))
.route("/delete_account", web::post().to(delete_account)) .route("/delete_account", web::post().to(delete_account))
.route("/password_reset", web::post().to(reset_password)) .route("/password_reset", web::post().to(reset_password))