mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 11:21:32 +00:00
Adding federated community, comment, and post deletes.
- Unit tests added too. - No undeletes working yet.
This commit is contained in:
parent
461114c143
commit
2f1cd9976d
11 changed files with 450 additions and 79 deletions
|
@ -337,14 +337,14 @@ impl Perform for Oper<EditComment> {
|
|||
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||
};
|
||||
|
||||
updated_comment.send_update(&user, &conn)?;
|
||||
|
||||
if let Some(deleted) = data.deleted.to_owned() {
|
||||
if deleted {
|
||||
updated_comment.send_delete(&user, &conn)?;
|
||||
} else {
|
||||
// TODO: undo delete
|
||||
}
|
||||
} else {
|
||||
updated_comment.send_update(&user, &conn)?;
|
||||
}
|
||||
|
||||
let mut recipient_ids = Vec::new();
|
||||
|
|
|
@ -321,7 +321,8 @@ impl Perform for Oper<EditCommunity> {
|
|||
let conn = pool.get()?;
|
||||
|
||||
// Check for a site ban
|
||||
if UserView::read(&conn, user_id)?.banned {
|
||||
let user = User_::read(&conn, user_id)?;
|
||||
if user.banned {
|
||||
return Err(APIError::err("site_ban").into());
|
||||
}
|
||||
|
||||
|
@ -381,7 +382,7 @@ impl Perform for Oper<EditCommunity> {
|
|||
|
||||
if let Some(deleted) = data.deleted.to_owned() {
|
||||
if deleted {
|
||||
updated_community.send_delete(&conn)?;
|
||||
updated_community.send_delete(&user, &conn)?;
|
||||
} else {
|
||||
// TODO: undo delete
|
||||
}
|
||||
|
@ -709,7 +710,7 @@ impl Perform for Oper<TransferCommunity> {
|
|||
title: read_community.title,
|
||||
description: read_community.description,
|
||||
category_id: read_community.category_id,
|
||||
creator_id: data.user_id,
|
||||
creator_id: data.user_id, // This makes the new user the community creator
|
||||
removed: None,
|
||||
deleted: None,
|
||||
nsfw: read_community.nsfw,
|
||||
|
|
|
@ -541,14 +541,14 @@ impl Perform for Oper<EditPost> {
|
|||
ModStickyPost::create(&conn, &form)?;
|
||||
}
|
||||
|
||||
updated_post.send_update(&user, &conn)?;
|
||||
|
||||
if let Some(deleted) = data.deleted.to_owned() {
|
||||
if deleted {
|
||||
updated_post.send_delete(&user, &conn)?;
|
||||
} else {
|
||||
// TODO: undo delete
|
||||
}
|
||||
} else {
|
||||
updated_post.send_update(&user, &conn)?;
|
||||
}
|
||||
|
||||
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
|
||||
|
|
|
@ -35,11 +35,14 @@ impl ToApub for Comment {
|
|||
|
||||
Ok(comment)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTombstone for Comment {
|
||||
fn to_tombstone(&self) -> Result<Tombstone, Error> {
|
||||
create_tombstone(self.deleted, &self.ap_id, self.published, self.updated, NoteType.to_string())
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
&self.ap_id,
|
||||
self.updated,
|
||||
NoteType.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,13 +167,23 @@ impl ApubObjectType for Comment {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: this code is literally copied from post.rs
|
||||
fn send_delete(&self, actor: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let note = self.to_apub(&conn)?;
|
||||
let post = Post::read(&conn, self.post_id)?;
|
||||
let community = Community::read(&conn, post.community_id)?;
|
||||
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||
let mut delete = Delete::default();
|
||||
|
||||
populate_object_props(
|
||||
&mut delete.object_props,
|
||||
&community.get_followers_url(),
|
||||
&id,
|
||||
)?;
|
||||
|
||||
delete
|
||||
.delete_props
|
||||
.set_actor_xsd_any_uri(actor.actor_id.to_owned())?
|
||||
.set_object_base_box(BaseBox::from_concrete(self.to_tombstone()?)?)?;
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(note)?;
|
||||
|
||||
// Insert the sent activity into the activity table
|
||||
let activity_form = activity::ActivityForm {
|
||||
|
@ -181,12 +194,10 @@ impl ApubObjectType for Comment {
|
|||
};
|
||||
activity::Activity::create(&conn, &activity_form)?;
|
||||
|
||||
let post = Post::read(conn, self.post_id)?;
|
||||
let community = Community::read(conn, post.community_id)?;
|
||||
send_activity(
|
||||
&delete,
|
||||
&actor.private_key.to_owned().unwrap(),
|
||||
&actor.actor_id,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
community.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
|
|
|
@ -46,11 +46,14 @@ impl ToApub for Community {
|
|||
|
||||
Ok(group.extend(actor_props).extend(self.get_public_key_ext()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTombstone for Community {
|
||||
fn to_tombstone(&self) -> Result<Tombstone, Error> {
|
||||
create_tombstone(self.deleted, &self.actor_id, self.published, self.updated, GroupType.to_string())
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
&self.actor_id,
|
||||
self.updated,
|
||||
GroupType.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,12 +104,17 @@ impl ActorType for Community {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn send_delete(&self, conn: &PgConnection) -> Result<(), Error> {
|
||||
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let group = self.to_apub(conn)?;
|
||||
let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
|
||||
|
||||
let mut delete = Delete::default();
|
||||
populate_object_props(&mut delete.object_props, &self.get_followers_url(), &id)?;
|
||||
|
||||
delete
|
||||
.delete_props
|
||||
.set_actor_xsd_any_uri(self.actor_id.to_owned())?
|
||||
.set_object_base_box(BaseBox::from_concrete(self.to_tombstone()?)?)?;
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(group)?;
|
||||
|
||||
// Insert the sent activity into the activity table
|
||||
let activity_form = activity::ActivityForm {
|
||||
|
@ -117,10 +125,13 @@ impl ActorType for Community {
|
|||
};
|
||||
activity::Activity::create(&conn, &activity_form)?;
|
||||
|
||||
// Note: For an accept, since it was automatic, no one pushed a button,
|
||||
// the community was the actor.
|
||||
// But for delete, the creator is the actor, and does the signing
|
||||
send_activity(
|
||||
&delete,
|
||||
&self.private_key.to_owned().unwrap(),
|
||||
&self.actor_id,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
self.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
|
|
|
@ -9,6 +9,9 @@ pub mod signatures;
|
|||
pub mod user;
|
||||
pub mod user_inbox;
|
||||
|
||||
use crate::api::community::CommunityResponse;
|
||||
use crate::websocket::server::SendCommunityRoomMessage;
|
||||
use activitystreams::object::kind::{NoteType, PageType};
|
||||
use activitystreams::{
|
||||
activity::{Accept, Create, Delete, Dislike, Follow, Like, Update},
|
||||
actor::{properties::ApActorProperties, Actor, Group, Person},
|
||||
|
@ -19,9 +22,6 @@ use activitystreams::{
|
|||
object::{properties::ObjectProperties, Note, Page, Tombstone},
|
||||
public, BaseBox,
|
||||
};
|
||||
use activitystreams::object::kind::{NoteType, PageType};
|
||||
use crate::api::community::CommunityResponse;
|
||||
use crate::websocket::server::SendCommunityRoomMessage;
|
||||
use actix_web::body::Body;
|
||||
use actix_web::web::Path;
|
||||
use actix_web::{web, HttpRequest, HttpResponse, Result};
|
||||
|
@ -155,29 +155,29 @@ fn is_apub_id_valid(apub_id: &Url) -> bool {
|
|||
pub trait ToApub {
|
||||
type Response;
|
||||
fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
|
||||
fn to_tombstone(&self) -> Result<Tombstone, Error>;
|
||||
}
|
||||
|
||||
fn create_tombstone(
|
||||
deleted: bool,
|
||||
object_id: &str,
|
||||
published: NaiveDateTime,
|
||||
updated: Option<NaiveDateTime>,
|
||||
former_type: String,
|
||||
) -> Result<Tombstone, Error> {
|
||||
if deleted {
|
||||
let mut tombstone = Tombstone::default();
|
||||
// TODO: might want to include deleted time as well
|
||||
tombstone
|
||||
.object_props
|
||||
.set_id(object_id)?
|
||||
.set_published(convert_datetime(published))?;
|
||||
if let Some(updated) = updated {
|
||||
let mut tombstone = Tombstone::default();
|
||||
tombstone.object_props.set_id(object_id)?;
|
||||
tombstone
|
||||
.object_props
|
||||
.set_updated(convert_datetime(updated))?;
|
||||
.tombstone_props
|
||||
.set_former_type_xsd_string(former_type)?
|
||||
.set_deleted(convert_datetime(updated))?;
|
||||
Ok(tombstone)
|
||||
} else {
|
||||
Err(format_err!(
|
||||
"Cant convert to tombstone because updated time was None."
|
||||
))
|
||||
}
|
||||
tombstone.tombstone_props.set_former_type_xsd_string(former_type)?;
|
||||
Ok(tombstone)
|
||||
} else {
|
||||
Err(format_err!(
|
||||
"Cant convert object to tombstone if it wasnt deleted"
|
||||
|
@ -185,10 +185,6 @@ fn create_tombstone(
|
|||
}
|
||||
}
|
||||
|
||||
pub trait ToTombstone {
|
||||
fn to_tombstone(&self) -> Result<Tombstone, Error>;
|
||||
}
|
||||
|
||||
pub trait FromApub {
|
||||
type ApubType;
|
||||
fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result<Self, Error>
|
||||
|
@ -199,7 +195,7 @@ pub trait FromApub {
|
|||
pub trait ApubObjectType {
|
||||
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
fn send_delete(&self, actor: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub trait ApubLikeableType {
|
||||
|
@ -238,7 +234,7 @@ pub trait ActorType {
|
|||
Err(format_err!("Accept not implemented."))
|
||||
}
|
||||
|
||||
fn send_delete(&self, conn: &PgConnection) -> Result<(), Error>;
|
||||
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
|
||||
// TODO default because there is no user following yet.
|
||||
#[allow(unused_variables)]
|
||||
|
|
|
@ -57,11 +57,14 @@ impl ToApub for Post {
|
|||
|
||||
Ok(page)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTombstone for Post {
|
||||
fn to_tombstone(&self) -> Result<Tombstone, Error> {
|
||||
create_tombstone(self.deleted, &self.ap_id, self.published, self.updated, PageType.to_string())
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
&self.ap_id,
|
||||
self.updated,
|
||||
PageType.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,12 +177,22 @@ impl ApubObjectType for Post {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn send_delete(&self, actor: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = self.to_apub(conn)?;
|
||||
let community = Community::read(conn, self.community_id)?;
|
||||
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||
let mut delete = Delete::default();
|
||||
|
||||
populate_object_props(
|
||||
&mut delete.object_props,
|
||||
&community.get_followers_url(),
|
||||
&id,
|
||||
)?;
|
||||
|
||||
delete
|
||||
.delete_props
|
||||
.set_actor_xsd_any_uri(actor.actor_id.to_owned())?
|
||||
.set_object_base_box(BaseBox::from_concrete(self.to_tombstone()?)?)?;
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
|
||||
// Insert the sent activity into the activity table
|
||||
let activity_form = activity::ActivityForm {
|
||||
|
@ -193,8 +206,8 @@ impl ApubObjectType for Post {
|
|||
let community = Community::read(conn, self.community_id)?;
|
||||
send_activity(
|
||||
&delete,
|
||||
&actor.private_key.to_owned().unwrap(),
|
||||
&actor.actor_id,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
community.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
|
|
|
@ -51,6 +51,9 @@ pub async fn shared_inbox(
|
|||
(SharedAcceptedObjects::Dislike(d), Some("Page")) => {
|
||||
receive_dislike_post(&d, &request, &conn, chat_server)
|
||||
}
|
||||
(SharedAcceptedObjects::Delete(d), Some("Page")) => {
|
||||
receive_delete_post(&d, &request, &conn, chat_server)
|
||||
}
|
||||
(SharedAcceptedObjects::Create(c), Some("Note")) => {
|
||||
receive_create_comment(&c, &request, &conn, chat_server)
|
||||
}
|
||||
|
@ -63,8 +66,11 @@ pub async fn shared_inbox(
|
|||
(SharedAcceptedObjects::Dislike(d), Some("Note")) => {
|
||||
receive_dislike_comment(&d, &request, &conn, chat_server)
|
||||
}
|
||||
(SharedAcceptedObjects::Delete(d), Some("Tombstone")) => {
|
||||
receive_delete(&d, &request, &conn, chat_server)
|
||||
(SharedAcceptedObjects::Delete(d), Some("Note")) => {
|
||||
receive_delete_comment(&d, &request, &conn, chat_server)
|
||||
}
|
||||
(SharedAcceptedObjects::Delete(d), Some("Group")) => {
|
||||
receive_delete_community(&d, &request, &conn, chat_server)
|
||||
}
|
||||
_ => Err(format_err!("Unknown incoming activity type.")),
|
||||
}
|
||||
|
@ -508,58 +514,60 @@ fn receive_dislike_comment(
|
|||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
fn receive_delete(
|
||||
fn receive_delete_community(
|
||||
delete: &Delete,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let tombstone = delete
|
||||
let user_uri = delete
|
||||
.delete_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let group = delete
|
||||
.delete_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.into_concrete::<Tombstone>()?;
|
||||
let former_type = tombstone.tombstone_props.get_former_type_xsd_string().unwrap().to_string();
|
||||
// TODO: handle these
|
||||
match former_type.as_str() {
|
||||
"Group" => {},
|
||||
d => return Err(format_err!("Delete type {} not supported", d)),
|
||||
}
|
||||
let community_apub_id = tombstone.object_props.get_id().unwrap().to_string();
|
||||
.into_concrete::<GroupExt>()?;
|
||||
|
||||
let community = Community::read_from_actor_id(conn, &community_apub_id)?;
|
||||
verify(request, &community.public_key.clone().unwrap())?;
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
// Insert the received activity into the activity table
|
||||
let activity_form = activity::ActivityForm {
|
||||
user_id: community.creator_id,
|
||||
user_id: user.id,
|
||||
data: serde_json::to_value(&delete)?,
|
||||
local: false,
|
||||
updated: None,
|
||||
};
|
||||
activity::Activity::create(&conn, &activity_form)?;
|
||||
|
||||
let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id;
|
||||
let community = Community::read_from_actor_id(conn, &community_actor_id)?;
|
||||
|
||||
let community_form = CommunityForm {
|
||||
name: "".to_string(),
|
||||
title: "".to_string(),
|
||||
description: None,
|
||||
name: community.name.to_owned(),
|
||||
title: community.title.to_owned(),
|
||||
description: community.description.to_owned(),
|
||||
category_id: community.category_id, // Note: need to keep this due to foreign key constraint
|
||||
creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
|
||||
removed: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
updated: Some(naive_now()),
|
||||
deleted: Some(true),
|
||||
nsfw: false,
|
||||
nsfw: community.nsfw,
|
||||
actor_id: community.actor_id,
|
||||
local: false,
|
||||
private_key: None,
|
||||
local: community.local,
|
||||
private_key: community.private_key,
|
||||
public_key: community.public_key,
|
||||
last_refreshed_at: Some(community.last_refreshed_at),
|
||||
last_refreshed_at: None,
|
||||
};
|
||||
|
||||
Community::update(conn, community.id, &community_form)?;
|
||||
Community::update(&conn, community.id, &community_form)?;
|
||||
|
||||
let res = CommunityResponse {
|
||||
community: CommunityView::read(&conn, community.id, None)?,
|
||||
|
@ -574,3 +582,142 @@ fn receive_delete(
|
|||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
fn receive_delete_post(
|
||||
delete: &Delete,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let user_uri = delete
|
||||
.delete_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let page = delete
|
||||
.delete_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.into_concrete::<Page>()?;
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
// Insert the received activity into the activity table
|
||||
let activity_form = activity::ActivityForm {
|
||||
user_id: user.id,
|
||||
data: serde_json::to_value(&delete)?,
|
||||
local: false,
|
||||
updated: None,
|
||||
};
|
||||
activity::Activity::create(&conn, &activity_form)?;
|
||||
|
||||
let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id;
|
||||
let post = Post::read_from_apub_id(conn, &post_ap_id)?;
|
||||
|
||||
let post_form = PostForm {
|
||||
name: post.name.to_owned(),
|
||||
url: post.url.to_owned(),
|
||||
body: post.body.to_owned(),
|
||||
creator_id: post.creator_id.to_owned(),
|
||||
community_id: post.community_id,
|
||||
removed: None,
|
||||
deleted: Some(true),
|
||||
nsfw: post.nsfw,
|
||||
locked: None,
|
||||
stickied: None,
|
||||
updated: Some(naive_now()),
|
||||
embed_title: post.embed_title,
|
||||
embed_description: post.embed_description,
|
||||
embed_html: post.embed_html,
|
||||
thumbnail_url: post.thumbnail_url,
|
||||
ap_id: post.ap_id,
|
||||
local: post.local,
|
||||
published: None,
|
||||
};
|
||||
Post::update(&conn, post.id, &post_form)?;
|
||||
|
||||
// Refetch the view
|
||||
let post_view = PostView::read(&conn, post.id, None)?;
|
||||
|
||||
let res = PostResponse { post: post_view };
|
||||
|
||||
chat_server.do_send(SendPost {
|
||||
op: UserOperation::EditPost,
|
||||
post: res,
|
||||
my_id: None,
|
||||
});
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
fn receive_delete_comment(
|
||||
delete: &Delete,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let user_uri = delete
|
||||
.delete_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let note = delete
|
||||
.delete_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.into_concrete::<Note>()?;
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
// Insert the received activity into the activity table
|
||||
let activity_form = activity::ActivityForm {
|
||||
user_id: user.id,
|
||||
data: serde_json::to_value(&delete)?,
|
||||
local: false,
|
||||
updated: None,
|
||||
};
|
||||
activity::Activity::create(&conn, &activity_form)?;
|
||||
|
||||
let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id;
|
||||
let comment = Comment::read_from_apub_id(conn, &comment_ap_id)?;
|
||||
let comment_form = CommentForm {
|
||||
content: comment.content.to_owned(),
|
||||
parent_id: comment.parent_id,
|
||||
post_id: comment.post_id,
|
||||
creator_id: comment.creator_id,
|
||||
removed: None,
|
||||
deleted: Some(true),
|
||||
read: None,
|
||||
published: None,
|
||||
updated: Some(naive_now()),
|
||||
ap_id: comment.ap_id,
|
||||
local: comment.local,
|
||||
};
|
||||
Comment::update(&conn, comment.id, &comment_form)?;
|
||||
|
||||
// Refetch the view
|
||||
let comment_view = CommentView::read(&conn, comment.id, None)?;
|
||||
|
||||
// TODO get those recipient actor ids from somewhere
|
||||
let recipient_ids = vec![];
|
||||
let res = CommentResponse {
|
||||
comment: comment_view,
|
||||
recipient_ids,
|
||||
};
|
||||
|
||||
chat_server.do_send(SendComment {
|
||||
op: UserOperation::EditComment,
|
||||
comment: res,
|
||||
my_id: None,
|
||||
});
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
|
|
@ -43,6 +43,9 @@ impl ToApub for User_ {
|
|||
|
||||
Ok(person.extend(actor_props).extend(self.get_public_key_ext()))
|
||||
}
|
||||
fn to_tombstone(&self) -> Result<Tombstone, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActorType for User_ {
|
||||
|
@ -88,7 +91,7 @@ impl ActorType for User_ {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn send_delete(&self, _conn: &PgConnection) -> Result<(), Error> {
|
||||
fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ pub struct Community {
|
|||
pub last_refreshed_at: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
// TODO add better delete, remove, lock actions here.
|
||||
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize, Debug)]
|
||||
#[table_name = "community"]
|
||||
pub struct CommunityForm {
|
||||
|
|
188
ui/src/api_tests/api.spec.ts
vendored
188
ui/src/api_tests/api.spec.ts
vendored
|
@ -13,6 +13,9 @@ import {
|
|||
GetPostResponse,
|
||||
CommentForm,
|
||||
CommentResponse,
|
||||
CommunityForm,
|
||||
GetCommunityForm,
|
||||
GetCommunityResponse,
|
||||
} from '../interfaces';
|
||||
|
||||
let lemmyAlphaUrl = 'http://localhost:8540';
|
||||
|
@ -324,6 +327,191 @@ describe('main', () => {
|
|||
expect(getPostRes.comments[1].creator_local).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete community', () => {
|
||||
test('/u/lemmy_beta deletes a federated comment, post, and community, lemmy_alpha sees its deleted.', async () => {
|
||||
// Create a test community
|
||||
let communityName = 'test_community';
|
||||
let communityForm: CommunityForm = {
|
||||
name: communityName,
|
||||
title: communityName,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
auth: lemmyBetaAuth,
|
||||
};
|
||||
|
||||
let createCommunityRes: CommunityResponse = await fetch(
|
||||
`${lemmyBetaApiUrl}/community`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(communityForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(createCommunityRes.community.name).toBe(communityName);
|
||||
|
||||
// Cache it on lemmy_alpha
|
||||
let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`;
|
||||
let searchResponse: SearchResponse = await fetch(searchUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
let communityOnAlphaId = searchResponse.communities[0].id;
|
||||
|
||||
// Follow it
|
||||
let followForm: FollowCommunityForm = {
|
||||
community_id: communityOnAlphaId,
|
||||
follow: true,
|
||||
auth: lemmyAlphaAuth,
|
||||
};
|
||||
|
||||
let followRes: CommunityResponse = await fetch(
|
||||
`${lemmyAlphaApiUrl}/community/follow`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(followForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
// Make sure the follow response went through
|
||||
expect(followRes.community.local).toBe(false);
|
||||
expect(followRes.community.name).toBe(communityName);
|
||||
|
||||
// Lemmy beta creates a test post
|
||||
let postName = 'A jest test post with delete';
|
||||
let createPostForm: PostForm = {
|
||||
name: postName,
|
||||
auth: lemmyBetaAuth,
|
||||
community_id: createCommunityRes.community.id,
|
||||
creator_id: 2,
|
||||
nsfw: false,
|
||||
};
|
||||
|
||||
let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(createPostForm),
|
||||
}).then(d => d.json());
|
||||
expect(createPostRes.post.name).toBe(postName);
|
||||
|
||||
// Lemmy beta creates a test comment
|
||||
let commentContent = 'A jest test federated comment with delete';
|
||||
let createCommentForm: CommentForm = {
|
||||
content: commentContent,
|
||||
post_id: createPostRes.post.id,
|
||||
auth: lemmyBetaAuth,
|
||||
};
|
||||
|
||||
let createCommentRes: CommentResponse = await fetch(
|
||||
`${lemmyBetaApiUrl}/comment`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(createCommentForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(createCommentRes.comment.content).toBe(commentContent);
|
||||
|
||||
// lemmy_beta deletes the comment
|
||||
let deleteCommentForm: CommentForm = {
|
||||
content: commentContent,
|
||||
edit_id: createCommentRes.comment.id,
|
||||
post_id: createPostRes.post.id,
|
||||
deleted: true,
|
||||
auth: lemmyBetaAuth,
|
||||
creator_id: createCommentRes.comment.creator_id,
|
||||
};
|
||||
|
||||
let deleteCommentRes: CommentResponse = await fetch(
|
||||
`${lemmyBetaApiUrl}/comment`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(deleteCommentForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
expect(deleteCommentRes.comment.deleted).toBe(true);
|
||||
|
||||
// lemmy_alpha sees that the comment is deleted
|
||||
let getPostUrl = `${lemmyAlphaApiUrl}/post?id=3`;
|
||||
let getPostRes: GetPostResponse = await fetch(getPostUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
expect(getPostRes.comments[0].deleted).toBe(true);
|
||||
|
||||
// lemmy_beta deletes the post
|
||||
let deletePostForm: PostForm = {
|
||||
name: postName,
|
||||
edit_id: createPostRes.post.id,
|
||||
auth: lemmyBetaAuth,
|
||||
community_id: createPostRes.post.community_id,
|
||||
creator_id: createPostRes.post.creator_id,
|
||||
nsfw: false,
|
||||
deleted: true,
|
||||
};
|
||||
|
||||
let deletePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(deletePostForm),
|
||||
}).then(d => d.json());
|
||||
expect(deletePostRes.post.deleted).toBe(true);
|
||||
|
||||
// Make sure lemmy_alpha sees the post is deleted
|
||||
let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
expect(getPostResAgain.post.deleted).toBe(true);
|
||||
|
||||
// lemmy_beta deletes the community
|
||||
let deleteCommunityForm: CommunityForm = {
|
||||
name: communityName,
|
||||
title: communityName,
|
||||
category_id: 1,
|
||||
edit_id: createCommunityRes.community.id,
|
||||
nsfw: false,
|
||||
deleted: true,
|
||||
auth: lemmyBetaAuth,
|
||||
};
|
||||
|
||||
let deleteResponse: CommunityResponse = await fetch(
|
||||
`${lemmyBetaApiUrl}/community`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(deleteCommunityForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
// Make sure the delete went through
|
||||
expect(deleteResponse.community.deleted).toBe(true);
|
||||
|
||||
// Re-get it from alpha, make sure its deleted there too
|
||||
let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
|
||||
let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
expect(getCommunityRes.community.deleted).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wrapper(form: any): string {
|
||||
|
|
Loading…
Reference in a new issue