mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-22 19:01:32 +00:00
Lots of additions to federation.
- Added a shared inbox. - Added federated comments, comment updates, and tests. - Abstracted ap object sends into a common trait.
This commit is contained in:
parent
3ce0618362
commit
22abbebd41
18 changed files with 811 additions and 175 deletions
3
docker/federation-test/run-tests.sh
vendored
3
docker/federation-test/run-tests.sh
vendored
|
@ -12,7 +12,8 @@ sudo docker-compose --file ../federation/docker-compose.yml --project-directory
|
|||
pushd ../../ui
|
||||
yarn
|
||||
echo "Waiting for Lemmy to start..."
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 5; done
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||
yarn api-test || true
|
||||
popd
|
||||
|
||||
|
|
|
@ -87,7 +87,8 @@ impl Perform for Oper<CreateComment> {
|
|||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
|
@ -101,6 +102,7 @@ impl Perform for Oper<CreateComment> {
|
|||
removed: None,
|
||||
deleted: None,
|
||||
read: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
ap_id: "changeme".into(),
|
||||
local: true,
|
||||
|
@ -111,11 +113,13 @@ impl Perform for Oper<CreateComment> {
|
|||
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
|
||||
};
|
||||
|
||||
match Comment::update_ap_id(&conn, inserted_comment.id) {
|
||||
let updated_comment = match Comment::update_ap_id(&conn, inserted_comment.id) {
|
||||
Ok(comment) => comment,
|
||||
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
|
||||
};
|
||||
|
||||
updated_comment.send_create(&user, &conn)?;
|
||||
|
||||
let mut recipient_ids = Vec::new();
|
||||
|
||||
// Scan the comment for user mentions, add those rows
|
||||
|
@ -273,6 +277,8 @@ impl Perform for Oper<EditComment> {
|
|||
|
||||
let conn = pool.get()?;
|
||||
|
||||
let user = User_::read(&conn, user_id)?;
|
||||
|
||||
let orig_comment = CommentView::read(&conn, data.edit_id, None)?;
|
||||
|
||||
// You are allowed to mark the comment as read even if you're banned.
|
||||
|
@ -297,7 +303,7 @@ impl Perform for Oper<EditComment> {
|
|||
}
|
||||
|
||||
// Check for a site ban
|
||||
if UserView::read(&conn, user_id)?.banned {
|
||||
if user.banned {
|
||||
return Err(APIError::err("site_ban").into());
|
||||
}
|
||||
}
|
||||
|
@ -314,6 +320,7 @@ impl Perform for Oper<EditComment> {
|
|||
removed: data.removed.to_owned(),
|
||||
deleted: data.deleted.to_owned(),
|
||||
read: data.read.to_owned(),
|
||||
published: None,
|
||||
updated: if data.read.is_some() {
|
||||
orig_comment.updated
|
||||
} else {
|
||||
|
@ -323,11 +330,13 @@ impl Perform for Oper<EditComment> {
|
|||
local: read_comment.local,
|
||||
};
|
||||
|
||||
let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
|
||||
let updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
|
||||
Ok(comment) => comment,
|
||||
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
|
||||
};
|
||||
|
||||
updated_comment.send_update(&user, &conn)?;
|
||||
|
||||
let mut recipient_ids = Vec::new();
|
||||
|
||||
// Scan the comment for user mentions, add those rows
|
||||
|
|
|
@ -23,19 +23,17 @@ use crate::{
|
|||
};
|
||||
|
||||
use crate::apub::{
|
||||
activities::{send_post_create, send_post_update},
|
||||
fetcher::search_by_apub_id,
|
||||
signatures::generate_actor_keypair,
|
||||
{make_apub_endpoint, ActorType, EndpointType},
|
||||
{make_apub_endpoint, ActorType, ApubObjectType, EndpointType},
|
||||
};
|
||||
use crate::settings::Settings;
|
||||
use crate::websocket::UserOperation;
|
||||
use crate::websocket::{
|
||||
server::{
|
||||
JoinCommunityRoom, JoinPostRoom, JoinUserRoom, SendAllMessage, SendComment,
|
||||
SendCommunityRoomMessage, SendPost, SendUserRoomMessage,
|
||||
},
|
||||
WebsocketInfo,
|
||||
UserOperation, WebsocketInfo,
|
||||
};
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use diesel::PgConnection;
|
||||
|
|
|
@ -160,7 +160,7 @@ impl Perform for Oper<CreatePost> {
|
|||
Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
|
||||
};
|
||||
|
||||
send_post_create(&updated_post, &user, &conn)?;
|
||||
updated_post.send_create(&user, &conn)?;
|
||||
|
||||
// They like their own post by default
|
||||
let like_form = PostLikeForm {
|
||||
|
@ -531,7 +531,7 @@ impl Perform for Oper<EditPost> {
|
|||
ModStickyPost::create(&conn, &form)?;
|
||||
}
|
||||
|
||||
send_post_update(&updated_post, &user, &conn)?;
|
||||
updated_post.send_update(&user, &conn)?;
|
||||
|
||||
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
fn populate_object_props(
|
||||
pub fn populate_object_props(
|
||||
props: &mut ObjectProperties,
|
||||
addressed_to: &str,
|
||||
object_id: &str,
|
||||
|
@ -47,63 +47,3 @@ where
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// For a given community, returns the inboxes of all followers.
|
||||
fn get_follower_inboxes(conn: &PgConnection, community: &Community) -> Result<Vec<String>, Error> {
|
||||
Ok(
|
||||
CommunityFollowerView::for_community(conn, community.id)?
|
||||
.into_iter()
|
||||
.filter(|c| !c.user_local)
|
||||
// TODO eventually this will have to use the inbox or shared_inbox column, meaning that view
|
||||
// will have to change
|
||||
.map(|c| format!("{}/inbox", c.user_actor_id.to_owned()))
|
||||
.unique()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Send out information about a newly created post, to the followers of the community.
|
||||
pub fn send_post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = post.to_apub(conn)?;
|
||||
let community = Community::read(conn, post.community_id)?;
|
||||
let mut create = Create::new();
|
||||
populate_object_props(
|
||||
&mut create.object_props,
|
||||
&community.get_followers_url(),
|
||||
&post.ap_id,
|
||||
)?;
|
||||
create
|
||||
.create_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
send_activity(
|
||||
&create,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
get_follower_inboxes(conn, &community)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community.
|
||||
pub fn send_post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = post.to_apub(conn)?;
|
||||
let community = Community::read(conn, post.community_id)?;
|
||||
let mut update = Update::new();
|
||||
populate_object_props(
|
||||
&mut update.object_props,
|
||||
&community.get_followers_url(),
|
||||
&post.ap_id,
|
||||
)?;
|
||||
update
|
||||
.update_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
send_activity(
|
||||
&update,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
get_follower_inboxes(conn, &community)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
139
server/src/apub/comment.rs
Normal file
139
server/src/apub/comment.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use super::*;
|
||||
|
||||
impl ToApub for Comment {
|
||||
type Response = Note;
|
||||
|
||||
fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> {
|
||||
let mut comment = Note::default();
|
||||
let oprops: &mut ObjectProperties = comment.as_mut();
|
||||
let creator = User_::read(&conn, self.creator_id)?;
|
||||
let post = Post::read(&conn, self.post_id)?;
|
||||
let community = Community::read(&conn, post.community_id)?;
|
||||
|
||||
// Add a vector containing some important info to the "in_reply_to" field
|
||||
// [post_ap_id, Option(parent_comment_ap_id)]
|
||||
let mut in_reply_to_vec = vec![post.ap_id];
|
||||
|
||||
if let Some(parent_id) = self.parent_id {
|
||||
let parent_comment = Comment::read(&conn, parent_id)?;
|
||||
in_reply_to_vec.push(parent_comment.ap_id);
|
||||
}
|
||||
|
||||
oprops
|
||||
// Not needed when the Post is embedded in a collection (like for community outbox)
|
||||
.set_context_xsd_any_uri(context())?
|
||||
.set_id(self.ap_id.to_owned())?
|
||||
// Use summary field to be consistent with mastodon content warning.
|
||||
// https://mastodon.xyz/@Louisa/103987265222901387.json
|
||||
// .set_summary_xsd_string(self.name.to_owned())?
|
||||
.set_published(convert_datetime(self.published))?
|
||||
.set_to_xsd_any_uri(community.actor_id)?
|
||||
.set_many_in_reply_to_xsd_any_uris(in_reply_to_vec)?
|
||||
.set_content_xsd_string(self.content.to_owned())?
|
||||
.set_attributed_to_xsd_any_uri(creator.actor_id)?;
|
||||
|
||||
if let Some(u) = self.updated {
|
||||
oprops.set_updated(convert_datetime(u))?;
|
||||
}
|
||||
|
||||
Ok(comment)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromApub for CommentForm {
|
||||
type ApubType = Note;
|
||||
|
||||
/// Parse an ActivityPub note received from another instance into a Lemmy comment
|
||||
fn from_apub(note: &Note, conn: &PgConnection) -> Result<CommentForm, Error> {
|
||||
let oprops = ¬e.object_props;
|
||||
let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
|
||||
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
|
||||
|
||||
let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap();
|
||||
let post_ap_id = in_reply_tos.next().unwrap().to_string();
|
||||
|
||||
// The 2nd item, if it exists, is the parent comment apub_id
|
||||
let parent_id: Option<i32> = match in_reply_tos.next() {
|
||||
Some(parent_comment_uri) => {
|
||||
let parent_comment_uri_str = &parent_comment_uri.to_string();
|
||||
let parent_comment = Comment::read_from_apub_id(&conn, &parent_comment_uri_str)?;
|
||||
|
||||
Some(parent_comment.id)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let post = Post::read_from_apub_id(&conn, &post_ap_id)?;
|
||||
|
||||
Ok(CommentForm {
|
||||
creator_id: creator.id,
|
||||
post_id: post.id,
|
||||
parent_id,
|
||||
content: oprops
|
||||
.get_content_xsd_string()
|
||||
.map(|c| c.to_string())
|
||||
.unwrap(),
|
||||
removed: None,
|
||||
read: None,
|
||||
published: oprops
|
||||
.get_published()
|
||||
.map(|u| u.as_ref().to_owned().naive_local()),
|
||||
updated: oprops
|
||||
.get_updated()
|
||||
.map(|u| u.as_ref().to_owned().naive_local()),
|
||||
deleted: None,
|
||||
ap_id: oprops.get_id().unwrap().to_string(),
|
||||
local: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ApubObjectType for Comment {
|
||||
/// Send out information about a newly created comment, to the followers of the community.
|
||||
fn send_create(&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 mut create = Create::new();
|
||||
populate_object_props(
|
||||
&mut create.object_props,
|
||||
&community.get_followers_url(),
|
||||
&self.ap_id,
|
||||
)?;
|
||||
create
|
||||
.create_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(note)?;
|
||||
send_activity(
|
||||
&create,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
community.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community.
|
||||
fn send_update(&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 mut update = Update::new();
|
||||
populate_object_props(
|
||||
&mut update.object_props,
|
||||
&community.get_followers_url(),
|
||||
&self.ap_id,
|
||||
)?;
|
||||
update
|
||||
.update_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(note)?;
|
||||
send_activity(
|
||||
&update,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
community.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -89,6 +89,32 @@ impl ActorType for Community {
|
|||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// For a given community, returns the inboxes of all followers.
|
||||
fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> {
|
||||
debug!("got here.");
|
||||
|
||||
Ok(
|
||||
CommunityFollowerView::for_community(conn, self.id)?
|
||||
.into_iter()
|
||||
// TODO eventually this will have to use the inbox or shared_inbox column, meaning that view
|
||||
// will have to change
|
||||
.map(|c| {
|
||||
// If the user is local, but the community isn't, get the community shared inbox
|
||||
// and vice versa
|
||||
if c.user_local && !c.community_local {
|
||||
get_shared_inbox(&c.community_actor_id)
|
||||
} else if !c.user_local && c.community_local {
|
||||
get_shared_inbox(&c.user_actor_id)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
})
|
||||
.filter(|s| !s.is_empty())
|
||||
.unique()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromApub for CommunityForm {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod activities;
|
||||
pub mod comment;
|
||||
pub mod community;
|
||||
pub mod community_inbox;
|
||||
pub mod fetcher;
|
||||
|
@ -15,7 +16,11 @@ use activitystreams::{
|
|||
context,
|
||||
endpoint::EndpointProperties,
|
||||
ext::{Ext, Extensible, Extension},
|
||||
object::{properties::ObjectProperties, Page},
|
||||
object::{
|
||||
kind::{NoteType, PageType},
|
||||
properties::ObjectProperties,
|
||||
Note, Page,
|
||||
},
|
||||
public, BaseBox,
|
||||
};
|
||||
use actix_web::body::Body;
|
||||
|
@ -38,7 +43,11 @@ use std::collections::BTreeMap;
|
|||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use crate::api::comment::CommentResponse;
|
||||
use crate::api::post::PostResponse;
|
||||
use crate::api::site::SearchResponse;
|
||||
use crate::db::comment::{Comment, CommentForm};
|
||||
use crate::db::comment_view::CommentView;
|
||||
use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm};
|
||||
use crate::db::community_view::{CommunityFollowerView, CommunityView};
|
||||
use crate::db::post::{Post, PostForm};
|
||||
|
@ -48,9 +57,13 @@ use crate::db::user_view::UserView;
|
|||
use crate::db::{Crud, Followable, SearchType};
|
||||
use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
|
||||
use crate::routes::{ChatServerParam, DbPoolParam};
|
||||
use crate::websocket::{
|
||||
server::{SendComment, SendPost},
|
||||
UserOperation,
|
||||
};
|
||||
use crate::{convert_datetime, naive_now, Settings};
|
||||
|
||||
use activities::send_activity;
|
||||
use activities::{populate_object_props, send_activity};
|
||||
use fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user};
|
||||
use signatures::verify;
|
||||
use signatures::{sign, PublicKey, PublicKeyExtension};
|
||||
|
@ -142,6 +155,25 @@ pub trait FromApub {
|
|||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait ApubObjectType {
|
||||
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub fn get_shared_inbox(actor_id: &str) -> String {
|
||||
let url = Url::parse(actor_id).unwrap();
|
||||
format!(
|
||||
"{}://{}{}/inbox",
|
||||
&url.scheme(),
|
||||
&url.host_str().unwrap(),
|
||||
if let Some(port) = url.port() {
|
||||
format!(":{}", port)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub trait ActorType {
|
||||
fn actor_id(&self) -> String;
|
||||
|
||||
|
@ -159,24 +191,20 @@ pub trait ActorType {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO default because there is no user following yet.
|
||||
#[allow(unused_variables)]
|
||||
/// For a given community, returns the inboxes of all followers.
|
||||
fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
// TODO move these to the db rows
|
||||
fn get_inbox_url(&self) -> String {
|
||||
format!("{}/inbox", &self.actor_id())
|
||||
}
|
||||
|
||||
fn get_shared_inbox_url(&self) -> String {
|
||||
let url = Url::parse(&self.actor_id()).unwrap();
|
||||
let url_str = format!(
|
||||
"{}://{}{}/inbox",
|
||||
&url.scheme(),
|
||||
&url.host_str().unwrap(),
|
||||
if let Some(port) = url.port() {
|
||||
format!(":{}", port)
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
);
|
||||
format!("{}/inbox", &url_str)
|
||||
get_shared_inbox(&self.actor_id())
|
||||
}
|
||||
|
||||
fn get_outbox_url(&self) -> String {
|
||||
|
|
|
@ -92,3 +92,51 @@ impl FromApub for PostForm {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ApubObjectType for Post {
|
||||
/// Send out information about a newly created post, to the followers of the community.
|
||||
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = self.to_apub(conn)?;
|
||||
let community = Community::read(conn, self.community_id)?;
|
||||
let mut create = Create::new();
|
||||
populate_object_props(
|
||||
&mut create.object_props,
|
||||
&community.get_followers_url(),
|
||||
&self.ap_id,
|
||||
)?;
|
||||
create
|
||||
.create_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
send_activity(
|
||||
&create,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
community.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community.
|
||||
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = self.to_apub(conn)?;
|
||||
let community = Community::read(conn, self.community_id)?;
|
||||
let mut update = Update::new();
|
||||
populate_object_props(
|
||||
&mut update.object_props,
|
||||
&community.get_followers_url(),
|
||||
&self.ap_id,
|
||||
)?;
|
||||
update
|
||||
.update_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
send_activity(
|
||||
&update,
|
||||
&creator.private_key.as_ref().unwrap(),
|
||||
&creator.actor_id,
|
||||
community.get_follower_inboxes(&conn)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,241 @@
|
|||
// use super::*;
|
||||
use super::*;
|
||||
|
||||
#[serde(untagged)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum SharedAcceptedObjects {
|
||||
Create(Create),
|
||||
Update(Update),
|
||||
}
|
||||
|
||||
/// Handler for all incoming activities to user inboxes.
|
||||
pub async fn shared_inbox(
|
||||
request: HttpRequest,
|
||||
input: web::Json<SharedAcceptedObjects>,
|
||||
db: DbPoolParam,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// TODO: would be nice if we could do the signature check here, but we cant access the actor property
|
||||
let input = input.into_inner();
|
||||
let conn = &db.get().unwrap();
|
||||
|
||||
let json = serde_json::to_string(&input)?;
|
||||
debug!("Shared inbox received activity: {:?}", &json);
|
||||
|
||||
match input {
|
||||
SharedAcceptedObjects::Create(c) => handle_create(&c, &request, &conn, chat_server),
|
||||
SharedAcceptedObjects::Update(u) => handle_update(&u, &request, &conn, chat_server),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle create activities and insert them in the database.
|
||||
fn handle_create(
|
||||
create: &Create,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let base_box = create.create_props.get_object_base_box().unwrap();
|
||||
|
||||
if base_box.is_kind(PageType) {
|
||||
let page = create
|
||||
.create_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.to_concrete::<Page>()?;
|
||||
receive_create_post(&create, &page, &request, &conn, chat_server)?;
|
||||
} else if base_box.is_kind(NoteType) {
|
||||
let note = create
|
||||
.create_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.to_concrete::<Note>()?;
|
||||
receive_create_comment(&create, ¬e, &request, &conn, chat_server)?;
|
||||
} else {
|
||||
return Err(format_err!("Unknown base box type"));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
fn receive_create_post(
|
||||
create: &Create,
|
||||
page: &Page,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<(), Error> {
|
||||
let user_uri = create
|
||||
.create_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
let post = PostForm::from_apub(&page, &conn)?;
|
||||
let inserted_post = Post::create(conn, &post)?;
|
||||
|
||||
// Refetch the view
|
||||
let post_view = PostView::read(&conn, inserted_post.id, None)?;
|
||||
|
||||
let res = PostResponse { post: post_view };
|
||||
|
||||
chat_server.do_send(SendPost {
|
||||
op: UserOperation::CreatePost,
|
||||
post: res,
|
||||
my_id: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_create_comment(
|
||||
create: &Create,
|
||||
note: &Note,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<(), Error> {
|
||||
let user_uri = create
|
||||
.create_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
let comment = CommentForm::from_apub(¬e, &conn)?;
|
||||
let inserted_comment = Comment::create(conn, &comment)?;
|
||||
|
||||
// Refetch the view
|
||||
let comment_view = CommentView::read(&conn, inserted_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::CreateComment,
|
||||
comment: res,
|
||||
my_id: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle create activities and insert them in the database.
|
||||
fn handle_update(
|
||||
update: &Update,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let base_box = update.update_props.get_object_base_box().unwrap();
|
||||
|
||||
if base_box.is_kind(PageType) {
|
||||
let page = update
|
||||
.update_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.to_concrete::<Page>()?;
|
||||
|
||||
receive_update_post(&update, &page, &request, &conn, chat_server)?;
|
||||
} else if base_box.is_kind(NoteType) {
|
||||
let note = update
|
||||
.update_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.to_concrete::<Note>()?;
|
||||
receive_update_comment(&update, ¬e, &request, &conn, chat_server)?;
|
||||
} else {
|
||||
return Err(format_err!("Unknown base box type"));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
fn receive_update_post(
|
||||
update: &Update,
|
||||
page: &Page,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<(), Error> {
|
||||
let user_uri = update
|
||||
.update_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
let post = PostForm::from_apub(&page, conn)?;
|
||||
let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
|
||||
Post::update(conn, post_id, &post)?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
fn receive_update_comment(
|
||||
update: &Update,
|
||||
note: &Note,
|
||||
request: &HttpRequest,
|
||||
conn: &PgConnection,
|
||||
chat_server: ChatServerParam,
|
||||
) -> Result<(), Error> {
|
||||
let user_uri = update
|
||||
.update_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
let comment = CommentForm::from_apub(¬e, &conn)?;
|
||||
let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
|
||||
Comment::update(conn, comment_id, &comment)?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ use super::*;
|
|||
#[serde(untagged)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub enum UserAcceptedObjects {
|
||||
Create(Create),
|
||||
Update(Update),
|
||||
Accept(Accept),
|
||||
}
|
||||
|
||||
|
@ -23,73 +21,10 @@ pub async fn user_inbox(
|
|||
debug!("User {} received activity: {:?}", &username, &input);
|
||||
|
||||
match input {
|
||||
UserAcceptedObjects::Create(c) => handle_create(&c, &request, &username, &conn),
|
||||
UserAcceptedObjects::Update(u) => handle_update(&u, &request, &username, &conn),
|
||||
UserAcceptedObjects::Accept(a) => handle_accept(&a, &request, &username, &conn),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle create activities and insert them in the database.
|
||||
fn handle_create(
|
||||
create: &Create,
|
||||
request: &HttpRequest,
|
||||
_username: &str,
|
||||
conn: &PgConnection,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// TODO before this even gets named, because we don't know what type of object it is, we need
|
||||
// to parse this out
|
||||
let user_uri = create
|
||||
.create_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
let page = create
|
||||
.create_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.to_concrete::<Page>()?;
|
||||
let post = PostForm::from_apub(&page, conn)?;
|
||||
Post::create(conn, &post)?;
|
||||
// TODO: send the new post out via websocket
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Handle update activities and insert them in the database.
|
||||
fn handle_update(
|
||||
update: &Update,
|
||||
request: &HttpRequest,
|
||||
_username: &str,
|
||||
conn: &PgConnection,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let user_uri = update
|
||||
.update_props
|
||||
.get_actor_xsd_any_uri()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
||||
verify(request, &user.public_key.unwrap())?;
|
||||
|
||||
let page = update
|
||||
.update_props
|
||||
.get_object_base_box()
|
||||
.to_owned()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
.to_concrete::<Page>()?;
|
||||
let post = PostForm::from_apub(&page, conn)?;
|
||||
let id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
|
||||
Post::update(conn, id, &post)?;
|
||||
// TODO: send the new post out via websocket
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Handle accepted follows.
|
||||
fn handle_accept(
|
||||
accept: &Accept,
|
||||
|
|
|
@ -38,6 +38,7 @@ pub struct CommentForm {
|
|||
pub content: String,
|
||||
pub removed: Option<bool>,
|
||||
pub read: Option<bool>,
|
||||
pub published: Option<chrono::NaiveDateTime>,
|
||||
pub updated: Option<chrono::NaiveDateTime>,
|
||||
pub deleted: Option<bool>,
|
||||
pub ap_id: String,
|
||||
|
@ -84,6 +85,11 @@ impl Comment {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn mark_as_read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
|
||||
|
@ -283,6 +289,7 @@ mod tests {
|
|||
deleted: None,
|
||||
read: None,
|
||||
parent_id: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
ap_id: "changeme".into(),
|
||||
local: true,
|
||||
|
@ -313,6 +320,7 @@ mod tests {
|
|||
removed: None,
|
||||
deleted: None,
|
||||
read: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
ap_id: "changeme".into(),
|
||||
local: true,
|
||||
|
|
|
@ -540,6 +540,7 @@ mod tests {
|
|||
removed: None,
|
||||
deleted: None,
|
||||
read: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
ap_id: "changeme".into(),
|
||||
local: true,
|
||||
|
|
|
@ -541,6 +541,7 @@ mod tests {
|
|||
deleted: None,
|
||||
read: None,
|
||||
parent_id: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
ap_id: "changeme".into(),
|
||||
local: true,
|
||||
|
|
|
@ -167,6 +167,7 @@ mod tests {
|
|||
deleted: None,
|
||||
read: None,
|
||||
parent_id: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
ap_id: "changeme".into(),
|
||||
local: true,
|
||||
|
|
|
@ -2,6 +2,7 @@ use super::*;
|
|||
use crate::apub::community::*;
|
||||
use crate::apub::community_inbox::community_inbox;
|
||||
use crate::apub::post::get_apub_post;
|
||||
use crate::apub::shared_inbox::shared_inbox;
|
||||
use crate::apub::user::*;
|
||||
use crate::apub::user_inbox::user_inbox;
|
||||
use crate::apub::APUB_JSON_CONTENT_TYPE;
|
||||
|
@ -31,6 +32,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
)
|
||||
// Inboxes dont work with the header guard for some reason.
|
||||
.route("/c/{community_name}/inbox", web::post().to(community_inbox))
|
||||
.route("/u/{user_name}/inbox", web::post().to(user_inbox));
|
||||
.route("/u/{user_name}/inbox", web::post().to(user_inbox))
|
||||
.route("/inbox", web::post().to(shared_inbox));
|
||||
}
|
||||
}
|
||||
|
|
1
ui/jest.config.js
vendored
1
ui/jest.config.js
vendored
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testTimeout: 30000,
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
diagnostics: false,
|
||||
|
|
266
ui/src/api_tests/api.spec.ts
vendored
266
ui/src/api_tests/api.spec.ts
vendored
|
@ -6,6 +6,13 @@ import {
|
|||
PostForm,
|
||||
PostResponse,
|
||||
SearchResponse,
|
||||
FollowCommunityForm,
|
||||
CommunityResponse,
|
||||
GetFollowedCommunitiesResponse,
|
||||
GetPostForm,
|
||||
GetPostResponse,
|
||||
CommentForm,
|
||||
CommentResponse,
|
||||
} from '../interfaces';
|
||||
|
||||
let lemmyAlphaUrl = 'http://localhost:8540';
|
||||
|
@ -13,6 +20,7 @@ let lemmyBetaUrl = 'http://localhost:8550';
|
|||
let lemmyAlphaApiUrl = `${lemmyAlphaUrl}/api/v1`;
|
||||
let lemmyBetaApiUrl = `${lemmyBetaUrl}/api/v1`;
|
||||
let lemmyAlphaAuth: string;
|
||||
let lemmyBetaAuth: string;
|
||||
|
||||
// Workaround for tests being run before beforeAll() is finished
|
||||
// https://github.com/facebook/jest/issues/9527#issuecomment-592406108
|
||||
|
@ -33,8 +41,25 @@ describe('main', () => {
|
|||
}).then(d => d.json());
|
||||
|
||||
lemmyAlphaAuth = res.jwt;
|
||||
|
||||
console.log('Logging in as lemmy_beta');
|
||||
let formB = {
|
||||
username_or_email: 'lemmy_beta',
|
||||
password: 'lemmy',
|
||||
};
|
||||
|
||||
let resB: LoginResponse = await fetch(`${lemmyBetaApiUrl}/user/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(formB),
|
||||
}).then(d => d.json());
|
||||
|
||||
lemmyBetaAuth = resB.jwt;
|
||||
});
|
||||
|
||||
describe('beta_fetch', () => {
|
||||
test('Create test post on alpha and fetch it on beta', async () => {
|
||||
let name = 'A jest test post';
|
||||
let postForm: PostForm = {
|
||||
|
@ -45,13 +70,16 @@ describe('main', () => {
|
|||
nsfw: false,
|
||||
};
|
||||
|
||||
let createResponse: PostResponse = await fetch(`${lemmyAlphaApiUrl}/post`, {
|
||||
let createResponse: PostResponse = await fetch(
|
||||
`${lemmyAlphaApiUrl}/post`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(postForm),
|
||||
}).then(d => d.json());
|
||||
}
|
||||
).then(d => d.json());
|
||||
expect(createResponse.post.name).toBe(name);
|
||||
|
||||
let searchUrl = `${lemmyBetaApiUrl}/search?q=${createResponse.post.ap_id}&type_=All&sort=TopAll`;
|
||||
|
@ -62,8 +90,238 @@ describe('main', () => {
|
|||
// TODO: check more fields
|
||||
expect(searchResponse.posts[0].name).toBe(name);
|
||||
});
|
||||
});
|
||||
|
||||
function wrapper(form: any): string {
|
||||
return JSON.stringify(form);
|
||||
describe('follow_accept', () => {
|
||||
test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => {
|
||||
// Make sure lemmy_beta/c/main is cached on lemmy_alpha
|
||||
let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/main&type_=All&sort=TopAll`;
|
||||
let searchResponse: SearchResponse = await fetch(searchUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
expect(searchResponse.communities[0].name).toBe('main');
|
||||
|
||||
// TODO
|
||||
// Unfortunately the search is correctly
|
||||
let followForm: FollowCommunityForm = {
|
||||
community_id: searchResponse.communities[0].id,
|
||||
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('main');
|
||||
|
||||
// Check that you are subscribed to it locally
|
||||
let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
|
||||
let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
|
||||
followedCommunitiesUrl,
|
||||
{
|
||||
method: 'GET',
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create test post', () => {
|
||||
test('/u/lemmy_alpha creates a post on /c/lemmy_beta/main, its on both instances', async () => {
|
||||
let name = 'A jest test federated post';
|
||||
let postForm: PostForm = {
|
||||
name,
|
||||
auth: lemmyAlphaAuth,
|
||||
community_id: 3,
|
||||
creator_id: 2,
|
||||
nsfw: false,
|
||||
};
|
||||
|
||||
let createResponse: PostResponse = await fetch(
|
||||
`${lemmyAlphaApiUrl}/post`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(postForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(createResponse.post.name).toBe(name);
|
||||
expect(createResponse.post.community_local).toBe(false);
|
||||
expect(createResponse.post.creator_local).toBe(true);
|
||||
|
||||
let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
|
||||
let getPostRes: GetPostResponse = await fetch(getPostUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
expect(getPostRes.post.name).toBe(name);
|
||||
expect(getPostRes.post.community_local).toBe(true);
|
||||
expect(getPostRes.post.creator_local).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update test post', () => {
|
||||
test('/u/lemmy_alpha updates a post on /c/lemmy_beta/main, the update is on both', async () => {
|
||||
let name = 'A jest test federated post, updated';
|
||||
let postForm: PostForm = {
|
||||
name,
|
||||
edit_id: 2,
|
||||
auth: lemmyAlphaAuth,
|
||||
community_id: 3,
|
||||
creator_id: 2,
|
||||
nsfw: false,
|
||||
};
|
||||
|
||||
let updateResponse: PostResponse = await fetch(
|
||||
`${lemmyAlphaApiUrl}/post`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(postForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(updateResponse.post.name).toBe(name);
|
||||
expect(updateResponse.post.community_local).toBe(false);
|
||||
expect(updateResponse.post.creator_local).toBe(true);
|
||||
|
||||
let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
|
||||
let getPostRes: GetPostResponse = await fetch(getPostUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
expect(getPostRes.post.name).toBe(name);
|
||||
expect(getPostRes.post.community_local).toBe(true);
|
||||
expect(getPostRes.post.creator_local).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create test comment', () => {
|
||||
test('/u/lemmy_alpha creates a comment on /c/lemmy_beta/main, its on both instances', async () => {
|
||||
let content = 'A jest test federated comment';
|
||||
let commentForm: CommentForm = {
|
||||
content,
|
||||
post_id: 2,
|
||||
auth: lemmyAlphaAuth,
|
||||
};
|
||||
|
||||
let createResponse: CommentResponse = await fetch(
|
||||
`${lemmyAlphaApiUrl}/comment`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(commentForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(createResponse.comment.content).toBe(content);
|
||||
expect(createResponse.comment.community_local).toBe(false);
|
||||
expect(createResponse.comment.creator_local).toBe(true);
|
||||
|
||||
let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
|
||||
let getPostRes: GetPostResponse = await fetch(getPostUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
expect(getPostRes.comments[0].content).toBe(content);
|
||||
expect(getPostRes.comments[0].community_local).toBe(true);
|
||||
expect(getPostRes.comments[0].creator_local).toBe(false);
|
||||
|
||||
// Now do beta replying to that comment, as a child comment
|
||||
let contentBeta = 'A child federated comment from beta';
|
||||
let commentFormBeta: CommentForm = {
|
||||
content: contentBeta,
|
||||
post_id: getPostRes.post.id,
|
||||
parent_id: getPostRes.comments[0].id,
|
||||
auth: lemmyBetaAuth,
|
||||
};
|
||||
|
||||
let createResponseBeta: CommentResponse = await fetch(
|
||||
`${lemmyBetaApiUrl}/comment`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(commentFormBeta),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(createResponseBeta.comment.content).toBe(contentBeta);
|
||||
expect(createResponseBeta.comment.community_local).toBe(true);
|
||||
expect(createResponseBeta.comment.creator_local).toBe(true);
|
||||
expect(createResponseBeta.comment.parent_id).toBe(1);
|
||||
|
||||
// Make sure lemmy alpha sees that new child comment from beta
|
||||
let getPostUrlAlpha = `${lemmyAlphaApiUrl}/post?id=2`;
|
||||
let getPostResAlpha: GetPostResponse = await fetch(getPostUrlAlpha, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
// The newest show up first
|
||||
expect(getPostResAlpha.comments[0].content).toBe(contentBeta);
|
||||
expect(getPostResAlpha.comments[0].community_local).toBe(false);
|
||||
expect(getPostResAlpha.comments[0].creator_local).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update test comment', () => {
|
||||
test('/u/lemmy_alpha updates a comment on /c/lemmy_beta/main, its on both instances', async () => {
|
||||
let content = 'A jest test federated comment update';
|
||||
let commentForm: CommentForm = {
|
||||
content,
|
||||
post_id: 2,
|
||||
edit_id: 1,
|
||||
auth: lemmyAlphaAuth,
|
||||
creator_id: 2,
|
||||
};
|
||||
|
||||
let updateResponse: CommentResponse = await fetch(
|
||||
`${lemmyAlphaApiUrl}/comment`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: wrapper(commentForm),
|
||||
}
|
||||
).then(d => d.json());
|
||||
|
||||
expect(updateResponse.comment.content).toBe(content);
|
||||
expect(updateResponse.comment.community_local).toBe(false);
|
||||
expect(updateResponse.comment.creator_local).toBe(true);
|
||||
|
||||
let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
|
||||
let getPostRes: GetPostResponse = await fetch(getPostUrl, {
|
||||
method: 'GET',
|
||||
}).then(d => d.json());
|
||||
|
||||
expect(getPostRes.comments[1].content).toBe(content);
|
||||
expect(getPostRes.comments[1].community_local).toBe(true);
|
||||
expect(getPostRes.comments[1].creator_local).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function wrapper(form: any): string {
|
||||
return JSON.stringify(form);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue