mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-10-01 01:36:12 -04:00
Allow admins to resolve removed or deleted objects via API
This commit is contained in:
parent
f7d881ac78
commit
4c30e6673a
@ -158,16 +158,16 @@ test("Delete a comment", async () => {
|
|||||||
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
||||||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
||||||
|
|
||||||
// Make sure that comment is undefined on beta
|
// Make sure that comment is deleted on beta
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(beta, commentRes.comment_view.comment),
|
||||||
e => e.message == "not_found",
|
c => c.comment?.comment.deleted === true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure that comment is undefined on gamma after delete
|
// Make sure that comment is deleted on gamma after delete
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(gamma, commentRes.comment_view.comment),
|
||||||
e => e.message === "not_found",
|
c => c.comment?.comment.deleted === true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test undeleting the comment
|
// Test undeleting the comment
|
||||||
@ -181,11 +181,10 @@ test("Delete a comment", async () => {
|
|||||||
// Make sure that comment is undeleted on beta
|
// Make sure that comment is undeleted on beta
|
||||||
let betaComment2 = (
|
let betaComment2 = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(beta, commentRes.comment_view.comment),
|
||||||
e => e.message !== "not_found",
|
c => c.comment?.comment.deleted === false,
|
||||||
)
|
)
|
||||||
).comment;
|
).comment;
|
||||||
expect(betaComment2?.comment.deleted).toBe(false);
|
|
||||||
assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
|
assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ pub mod read_community;
|
|||||||
pub mod read_person;
|
pub mod read_person;
|
||||||
pub mod resolve_object;
|
pub mod resolve_object;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
pub mod user_settings_backup;
|
pub mod user_settings_backup;
|
||||||
|
|
||||||
/// Returns default listing type, depending if the query is for frontpage or community.
|
/// Returns default listing type, depending if the query is for frontpage or community.
|
||||||
|
@ -4,7 +4,6 @@ use crate::fetcher::{
|
|||||||
};
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::{Json, Query};
|
use actix_web::web::{Json, Query};
|
||||||
use diesel::NotFound;
|
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{ResolveObject, ResolveObjectResponse},
|
site::{ResolveObject, ResolveObjectResponse},
|
||||||
@ -47,34 +46,152 @@ async fn convert_response(
|
|||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> LemmyResult<Json<ResolveObjectResponse>> {
|
) -> LemmyResult<Json<ResolveObjectResponse>> {
|
||||||
use SearchableObjects::*;
|
use SearchableObjects::*;
|
||||||
let removed_or_deleted;
|
|
||||||
let mut res = ResolveObjectResponse::default();
|
let mut res = ResolveObjectResponse::default();
|
||||||
let local_user = local_user_view.map(|l| l.local_user);
|
let local_user = local_user_view.map(|l| l.local_user);
|
||||||
|
let is_admin = local_user.clone().map(|l| l.admin).unwrap_or_default();
|
||||||
|
|
||||||
match object {
|
match object {
|
||||||
Post(p) => {
|
Post(p) => res.post = Some(PostView::read(pool, p.id, local_user.as_ref(), is_admin).await?),
|
||||||
removed_or_deleted = p.deleted || p.removed;
|
Comment(c) => res.comment = Some(CommentView::read(pool, c.id, local_user.as_ref()).await?),
|
||||||
res.post = Some(PostView::read(pool, p.id, local_user.as_ref(), false).await?)
|
|
||||||
}
|
|
||||||
Comment(c) => {
|
|
||||||
removed_or_deleted = c.deleted || c.removed;
|
|
||||||
res.comment = Some(CommentView::read(pool, c.id, local_user.as_ref()).await?)
|
|
||||||
}
|
|
||||||
PersonOrCommunity(p) => match *p {
|
PersonOrCommunity(p) => match *p {
|
||||||
UserOrCommunity::User(u) => {
|
UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id).await?),
|
||||||
removed_or_deleted = u.deleted;
|
|
||||||
res.person = Some(PersonView::read(pool, u.id).await?)
|
|
||||||
}
|
|
||||||
UserOrCommunity::Community(c) => {
|
UserOrCommunity::Community(c) => {
|
||||||
removed_or_deleted = c.deleted || c.removed;
|
res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?)
|
||||||
res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), false).await?)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// if the object was deleted from database, dont return it
|
|
||||||
if removed_or_deleted {
|
Ok(Json(res))
|
||||||
Err(NotFound {}.into())
|
}
|
||||||
} else {
|
|
||||||
Ok(Json(res))
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::api::{resolve_object::resolve_object, test::TestUser};
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Query;
|
||||||
|
use lemmy_api_common::{context::LemmyContext, site::ResolveObject};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
community::{Community, CommunityInsertForm},
|
||||||
|
instance::Instance,
|
||||||
|
local_site::{LocalSite, LocalSiteInsertForm},
|
||||||
|
post::{Post, PostInsertForm, PostUpdateForm},
|
||||||
|
site::{Site, SiteInsertForm},
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
async fn create_test_user(
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
name: &str,
|
||||||
|
admin: bool,
|
||||||
|
) -> LemmyResult<LocalUserView> {
|
||||||
|
TestUser::create(
|
||||||
|
context,
|
||||||
|
TestUser {
|
||||||
|
name: name.to_string(),
|
||||||
|
admin,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
#[expect(clippy::unwrap_used)]
|
||||||
|
async fn test_object_visibility() -> LemmyResult<()> {
|
||||||
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
|
||||||
|
let creator = create_test_user(&context, "creator", false).await?;
|
||||||
|
let regular_user = create_test_user(&context, "user", false).await?;
|
||||||
|
let admin_user = create_test_user(&context, "admin", true).await?;
|
||||||
|
|
||||||
|
let instance_id = creator.person.instance_id;
|
||||||
|
let site_form = SiteInsertForm::new("test site".to_string(), instance_id);
|
||||||
|
let site = Site::create(&mut context.pool(), &site_form).await?;
|
||||||
|
|
||||||
|
let local_site_form = LocalSiteInsertForm {
|
||||||
|
site_setup: Some(true),
|
||||||
|
private_instance: Some(false),
|
||||||
|
..LocalSiteInsertForm::new(site.id)
|
||||||
|
};
|
||||||
|
LocalSite::create(&mut context.pool(), &local_site_form).await?;
|
||||||
|
|
||||||
|
let community = Community::create(
|
||||||
|
&mut context.pool(),
|
||||||
|
&CommunityInsertForm::new(
|
||||||
|
instance_id,
|
||||||
|
"test".to_string(),
|
||||||
|
"test".to_string(),
|
||||||
|
"pubkey".to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let post_insert_form = PostInsertForm::new("Test".to_string(), creator.person.id, community.id);
|
||||||
|
let post = Post::create(&mut context.pool(), &post_insert_form).await?;
|
||||||
|
|
||||||
|
let query = format!("q={}", post.ap_id).to_string();
|
||||||
|
let query: Query<ResolveObject> = Query::from_query(&query)?;
|
||||||
|
|
||||||
|
// Objects should be resolvable without authentication
|
||||||
|
let res = resolve_object(query.clone(), context.reset_request_count(), None).await?;
|
||||||
|
assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id);
|
||||||
|
// Objects should be resolvable by regular users
|
||||||
|
let res = resolve_object(
|
||||||
|
query.clone(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
Some(regular_user.clone()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id);
|
||||||
|
// Objects should be resolvable by admins
|
||||||
|
let res = resolve_object(
|
||||||
|
query.clone(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
Some(admin_user.clone()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id);
|
||||||
|
|
||||||
|
Post::update(
|
||||||
|
&mut context.pool(),
|
||||||
|
post.id,
|
||||||
|
&PostUpdateForm {
|
||||||
|
deleted: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Deleted objects should not be resolvable without authentication
|
||||||
|
let res = resolve_object(query.clone(), context.reset_request_count(), None).await;
|
||||||
|
assert!(res.is_err_and(|e| e.error_type == LemmyErrorType::NotFound));
|
||||||
|
// Deleted objects should not be resolvable by regular users
|
||||||
|
let res = resolve_object(
|
||||||
|
query.clone(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
Some(regular_user.clone()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(res.is_err_and(|e| e.error_type == LemmyErrorType::NotFound));
|
||||||
|
// Deleted objects should be resolvable by admins
|
||||||
|
let res = resolve_object(
|
||||||
|
query.clone(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
Some(admin_user.clone()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id);
|
||||||
|
|
||||||
|
LocalSite::delete(&mut context.pool()).await?;
|
||||||
|
Site::delete(&mut context.pool(), site.id).await?;
|
||||||
|
Instance::delete(&mut context.pool(), instance_id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
crates/apub/src/api/test.rs
Normal file
42
crates/apub/src/api/test.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
instance::Instance,
|
||||||
|
local_user::{LocalUser, LocalUserInsertForm},
|
||||||
|
person::{Person, PersonInsertForm},
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TestUser {
|
||||||
|
pub name: String,
|
||||||
|
pub bio: Option<String>,
|
||||||
|
pub admin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestUser {
|
||||||
|
pub async fn create(context: &Data<LemmyContext>, form: TestUser) -> LemmyResult<LocalUserView> {
|
||||||
|
let instance_id = Instance::read_or_create(&mut context.pool(), "example.com".to_string())
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let person_form = PersonInsertForm {
|
||||||
|
display_name: Some(form.name.clone()),
|
||||||
|
bio: form.bio,
|
||||||
|
..PersonInsertForm::test_form(instance_id, &form.name)
|
||||||
|
};
|
||||||
|
let person = Person::create(&mut context.pool(), &person_form).await?;
|
||||||
|
|
||||||
|
let user_form = match form.admin {
|
||||||
|
true => LocalUserInsertForm::test_form_admin(person.id),
|
||||||
|
false => LocalUserInsertForm::test_form(person.id),
|
||||||
|
};
|
||||||
|
let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?;
|
||||||
|
|
||||||
|
Ok(LocalUserView::read(&mut context.pool(), local_user.id).await?)
|
||||||
|
}
|
||||||
|
}
|
@ -311,16 +311,16 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[expect(clippy::indexing_slicing)]
|
#[expect(clippy::indexing_slicing)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::api::{
|
||||||
use crate::api::user_settings_backup::{export_settings, import_settings, UserSettingsBackup};
|
test::TestUser,
|
||||||
|
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::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm},
|
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm},
|
||||||
instance::Instance,
|
local_user::LocalUser,
|
||||||
local_user::{LocalUser, LocalUserInsertForm},
|
|
||||||
person::{Person, PersonInsertForm},
|
|
||||||
},
|
},
|
||||||
traits::{Crud, Followable},
|
traits::{Crud, Followable},
|
||||||
};
|
};
|
||||||
@ -332,23 +332,22 @@ mod tests {
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
async fn create_user(
|
async fn create_test_user(
|
||||||
name: String,
|
|
||||||
bio: Option<String>,
|
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
|
name: &str,
|
||||||
|
bio: Option<&str>,
|
||||||
) -> LemmyResult<LocalUserView> {
|
) -> LemmyResult<LocalUserView> {
|
||||||
let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?;
|
let bio = bio.map(ToString::to_string);
|
||||||
let person_form = PersonInsertForm {
|
|
||||||
display_name: Some(name.clone()),
|
|
||||||
bio,
|
|
||||||
..PersonInsertForm::test_form(instance.id, &name)
|
|
||||||
};
|
|
||||||
let person = Person::create(&mut context.pool(), &person_form).await?;
|
|
||||||
|
|
||||||
let user_form = LocalUserInsertForm::test_form(person.id);
|
TestUser::create(
|
||||||
let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?;
|
context,
|
||||||
|
TestUser {
|
||||||
Ok(LocalUserView::read(&mut context.pool(), local_user.id).await?)
|
name: name.to_string(),
|
||||||
|
bio,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -356,8 +355,7 @@ mod tests {
|
|||||||
async fn test_settings_export_import() -> LemmyResult<()> {
|
async fn test_settings_export_import() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
|
||||||
let export_user =
|
let export_user = create_test_user(&context, "hanna", Some("my bio")).await?;
|
||||||
create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?;
|
|
||||||
|
|
||||||
let community_form = CommunityInsertForm::new(
|
let community_form = CommunityInsertForm::new(
|
||||||
export_user.person.instance_id,
|
export_user.person.instance_id,
|
||||||
@ -375,7 +373,7 @@ mod tests {
|
|||||||
|
|
||||||
let backup = export_settings(export_user.clone(), context.reset_request_count()).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 import_user = create_test_user(&context, "charles", None).await?;
|
||||||
|
|
||||||
import_settings(backup, import_user.clone(), context.reset_request_count()).await?;
|
import_settings(backup, import_user.clone(), context.reset_request_count()).await?;
|
||||||
|
|
||||||
@ -406,8 +404,7 @@ mod tests {
|
|||||||
async fn test_settings_partial_import() -> LemmyResult<()> {
|
async fn test_settings_partial_import() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
|
||||||
let export_user =
|
let export_user = create_test_user(&context, "hanna", Some("my bio")).await?;
|
||||||
create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?;
|
|
||||||
|
|
||||||
let community_form = CommunityInsertForm::new(
|
let community_form = CommunityInsertForm::new(
|
||||||
export_user.person.instance_id,
|
export_user.person.instance_id,
|
||||||
@ -425,7 +422,7 @@ mod tests {
|
|||||||
|
|
||||||
let backup = export_settings(export_user.clone(), context.reset_request_count()).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 import_user = create_test_user(&context, "charles", None).await?;
|
||||||
|
|
||||||
let backup2 = UserSettingsBackup {
|
let backup2 = UserSettingsBackup {
|
||||||
followed_communities: backup.followed_communities.clone(),
|
followed_communities: backup.followed_communities.clone(),
|
||||||
@ -445,8 +442,7 @@ mod tests {
|
|||||||
async fn disallow_large_backup() -> LemmyResult<()> {
|
async fn disallow_large_backup() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
|
||||||
let export_user =
|
let export_user = create_test_user(&context, "hanna", Some("my bio")).await?;
|
||||||
create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?;
|
|
||||||
|
|
||||||
let mut backup = export_settings(export_user.clone(), context.reset_request_count()).await?;
|
let mut backup = export_settings(export_user.clone(), context.reset_request_count()).await?;
|
||||||
|
|
||||||
@ -461,7 +457,7 @@ mod tests {
|
|||||||
backup.saved_comments.push("http://example4.com".parse()?);
|
backup.saved_comments.push("http://example4.com".parse()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let import_user = create_user("charles".to_string(), None, &context).await?;
|
let import_user = create_test_user(&context, "charles", None).await?;
|
||||||
|
|
||||||
let imported =
|
let imported =
|
||||||
import_settings(backup, import_user.clone(), context.reset_request_count()).await;
|
import_settings(backup, import_user.clone(), context.reset_request_count()).await;
|
||||||
|
Loading…
Reference in New Issue
Block a user