mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-30 08:20:20 +00:00
Federated private messages.
This commit is contained in:
parent
21407260a4
commit
15f1920b25
13 changed files with 1081 additions and 271 deletions
21
server/migrations/2020-05-05-210233_add_activitypub_for_private_messages/down.sql
vendored
Normal file
21
server/migrations/2020-05-05-210233_add_activitypub_for_private_messages/down.sql
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
drop materialized view private_message_mview;
|
||||||
|
drop view private_message_view;
|
||||||
|
|
||||||
|
alter table private_message
|
||||||
|
drop column ap_id,
|
||||||
|
drop column local;
|
||||||
|
|
||||||
|
create view private_message_view as
|
||||||
|
select
|
||||||
|
pm.*,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
u2.name as recipient_name,
|
||||||
|
u2.avatar as recipient_avatar
|
||||||
|
from private_message pm
|
||||||
|
inner join user_ u on u.id = pm.creator_id
|
||||||
|
inner join user_ u2 on u2.id = pm.recipient_id;
|
||||||
|
|
||||||
|
create materialized view private_message_mview as select * from private_message_view;
|
||||||
|
|
||||||
|
create unique index idx_private_message_mview_id on private_message_mview (id);
|
25
server/migrations/2020-05-05-210233_add_activitypub_for_private_messages/up.sql
vendored
Normal file
25
server/migrations/2020-05-05-210233_add_activitypub_for_private_messages/up.sql
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
alter table private_message
|
||||||
|
add column ap_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
|
||||||
|
add column local boolean not null default true
|
||||||
|
;
|
||||||
|
|
||||||
|
drop materialized view private_message_mview;
|
||||||
|
drop view private_message_view;
|
||||||
|
create view private_message_view as
|
||||||
|
select
|
||||||
|
pm.*,
|
||||||
|
u.name as creator_name,
|
||||||
|
u.avatar as creator_avatar,
|
||||||
|
u.actor_id as creator_actor_id,
|
||||||
|
u.local as creator_local,
|
||||||
|
u2.name as recipient_name,
|
||||||
|
u2.avatar as recipient_avatar,
|
||||||
|
u2.actor_id as recipient_actor_id,
|
||||||
|
u2.local as recipient_local
|
||||||
|
from private_message pm
|
||||||
|
inner join user_ u on u.id = pm.creator_id
|
||||||
|
inner join user_ u2 on u2.id = pm.recipient_id;
|
||||||
|
|
||||||
|
create materialized view private_message_mview as select * from private_message_view;
|
||||||
|
|
||||||
|
create unique index idx_private_message_mview_id on private_message_mview (id);
|
|
@ -186,7 +186,7 @@ pub struct PrivateMessagesResponse {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct PrivateMessageResponse {
|
pub struct PrivateMessageResponse {
|
||||||
message: PrivateMessageView,
|
pub message: PrivateMessageView,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -861,12 +861,15 @@ impl Perform for Oper<MarkAllAsRead> {
|
||||||
|
|
||||||
for message in &messages {
|
for message in &messages {
|
||||||
let private_message_form = PrivateMessageForm {
|
let private_message_form = PrivateMessageForm {
|
||||||
content: None,
|
content: message.to_owned().content,
|
||||||
creator_id: message.to_owned().creator_id,
|
creator_id: message.to_owned().creator_id,
|
||||||
recipient_id: message.to_owned().recipient_id,
|
recipient_id: message.to_owned().recipient_id,
|
||||||
deleted: None,
|
deleted: None,
|
||||||
read: Some(true),
|
read: Some(true),
|
||||||
updated: None,
|
updated: None,
|
||||||
|
ap_id: message.to_owned().ap_id,
|
||||||
|
local: message.local,
|
||||||
|
published: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form)
|
let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form)
|
||||||
|
@ -1034,19 +1037,23 @@ impl Perform for Oper<CreatePrivateMessage> {
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
|
|
||||||
// Check for a site ban
|
// 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());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||||
|
|
||||||
let private_message_form = PrivateMessageForm {
|
let private_message_form = PrivateMessageForm {
|
||||||
content: Some(content_slurs_removed.to_owned()),
|
content: content_slurs_removed.to_owned(),
|
||||||
creator_id: user_id,
|
creator_id: user_id,
|
||||||
recipient_id: data.recipient_id,
|
recipient_id: data.recipient_id,
|
||||||
deleted: None,
|
deleted: None,
|
||||||
read: None,
|
read: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
|
ap_id: "changeme".into(),
|
||||||
|
local: true,
|
||||||
|
published: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) {
|
let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) {
|
||||||
|
@ -1056,6 +1063,14 @@ impl Perform for Oper<CreatePrivateMessage> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let updated_private_message =
|
||||||
|
match PrivateMessage::update_ap_id(&conn, inserted_private_message.id) {
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
updated_private_message.send_create(&user, &conn)?;
|
||||||
|
|
||||||
// Send notifications to the recipient
|
// Send notifications to the recipient
|
||||||
let recipient_user = User_::read(&conn, data.recipient_id)?;
|
let recipient_user = User_::read(&conn, data.recipient_id)?;
|
||||||
if recipient_user.send_notifications_to_email {
|
if recipient_user.send_notifications_to_email {
|
||||||
|
@ -1099,7 +1114,7 @@ impl Perform for Oper<EditPrivateMessage> {
|
||||||
fn perform(
|
fn perform(
|
||||||
&self,
|
&self,
|
||||||
pool: Pool<ConnectionManager<PgConnection>>,
|
pool: Pool<ConnectionManager<PgConnection>>,
|
||||||
_websocket_info: Option<WebsocketInfo>,
|
websocket_info: Option<WebsocketInfo>,
|
||||||
) -> Result<PrivateMessageResponse, Error> {
|
) -> Result<PrivateMessageResponse, Error> {
|
||||||
let data: &EditPrivateMessage = &self.data;
|
let data: &EditPrivateMessage = &self.data;
|
||||||
|
|
||||||
|
@ -1115,7 +1130,8 @@ impl Perform for Oper<EditPrivateMessage> {
|
||||||
let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?;
|
let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?;
|
||||||
|
|
||||||
// Check for a site ban
|
// 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());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1127,8 +1143,8 @@ impl Perform for Oper<EditPrivateMessage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_slurs_removed = match &data.content {
|
let content_slurs_removed = match &data.content {
|
||||||
Some(content) => Some(remove_slurs(content)),
|
Some(content) => remove_slurs(content),
|
||||||
None => None,
|
None => orig_private_message.content,
|
||||||
};
|
};
|
||||||
|
|
||||||
let private_message_form = PrivateMessageForm {
|
let private_message_form = PrivateMessageForm {
|
||||||
|
@ -1142,17 +1158,41 @@ impl Perform for Oper<EditPrivateMessage> {
|
||||||
} else {
|
} else {
|
||||||
Some(naive_now())
|
Some(naive_now())
|
||||||
},
|
},
|
||||||
|
ap_id: orig_private_message.ap_id,
|
||||||
|
local: orig_private_message.local,
|
||||||
|
published: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _updated_private_message =
|
let updated_private_message =
|
||||||
match PrivateMessage::update(&conn, data.edit_id, &private_message_form) {
|
match PrivateMessage::update(&conn, data.edit_id, &private_message_form) {
|
||||||
Ok(private_message) => private_message,
|
Ok(private_message) => private_message,
|
||||||
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
|
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(deleted) = data.deleted.to_owned() {
|
||||||
|
if deleted {
|
||||||
|
updated_private_message.send_delete(&user, &conn)?;
|
||||||
|
} else {
|
||||||
|
updated_private_message.send_undo_delete(&user, &conn)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updated_private_message.send_update(&user, &conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
let message = PrivateMessageView::read(&conn, data.edit_id)?;
|
let message = PrivateMessageView::read(&conn, data.edit_id)?;
|
||||||
|
|
||||||
Ok(PrivateMessageResponse { message })
|
let res = PrivateMessageResponse { message };
|
||||||
|
|
||||||
|
if let Some(ws) = websocket_info {
|
||||||
|
ws.chatserver.do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::EditPrivateMessage,
|
||||||
|
response: res.clone(),
|
||||||
|
recipient_id: orig_private_message.recipient_id,
|
||||||
|
my_id: ws.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod community_inbox;
|
||||||
pub mod fetcher;
|
pub mod fetcher;
|
||||||
pub mod page_extension;
|
pub mod page_extension;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
|
pub mod private_message;
|
||||||
pub mod shared_inbox;
|
pub mod shared_inbox;
|
||||||
pub mod signatures;
|
pub mod signatures;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
@ -46,6 +47,7 @@ use url::Url;
|
||||||
use crate::api::comment::CommentResponse;
|
use crate::api::comment::CommentResponse;
|
||||||
use crate::api::post::PostResponse;
|
use crate::api::post::PostResponse;
|
||||||
use crate::api::site::SearchResponse;
|
use crate::api::site::SearchResponse;
|
||||||
|
use crate::api::user::PrivateMessageResponse;
|
||||||
use crate::db::comment::{Comment, CommentForm, CommentLike, CommentLikeForm};
|
use crate::db::comment::{Comment, CommentForm, CommentLike, CommentLikeForm};
|
||||||
use crate::db::comment_view::CommentView;
|
use crate::db::comment_view::CommentView;
|
||||||
use crate::db::community::{
|
use crate::db::community::{
|
||||||
|
@ -55,13 +57,15 @@ use crate::db::community::{
|
||||||
use crate::db::community_view::{CommunityFollowerView, CommunityModeratorView, CommunityView};
|
use crate::db::community_view::{CommunityFollowerView, CommunityModeratorView, CommunityView};
|
||||||
use crate::db::post::{Post, PostForm, PostLike, PostLikeForm};
|
use crate::db::post::{Post, PostForm, PostLike, PostLikeForm};
|
||||||
use crate::db::post_view::PostView;
|
use crate::db::post_view::PostView;
|
||||||
|
use crate::db::private_message::{PrivateMessage, PrivateMessageForm};
|
||||||
|
use crate::db::private_message_view::PrivateMessageView;
|
||||||
use crate::db::user::{UserForm, User_};
|
use crate::db::user::{UserForm, User_};
|
||||||
use crate::db::user_view::UserView;
|
use crate::db::user_view::UserView;
|
||||||
use crate::db::{activity, Crud, Followable, Joinable, Likeable, SearchType};
|
use crate::db::{activity, Crud, Followable, Joinable, Likeable, SearchType};
|
||||||
use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
|
use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
|
||||||
use crate::routes::{ChatServerParam, DbPoolParam};
|
use crate::routes::{ChatServerParam, DbPoolParam};
|
||||||
use crate::websocket::{
|
use crate::websocket::{
|
||||||
server::{SendComment, SendPost},
|
server::{SendComment, SendPost, SendUserRoomMessage},
|
||||||
UserOperation,
|
UserOperation,
|
||||||
};
|
};
|
||||||
use crate::{convert_datetime, naive_now, Settings};
|
use crate::{convert_datetime, naive_now, Settings};
|
||||||
|
@ -85,6 +89,7 @@ pub enum EndpointType {
|
||||||
User,
|
User,
|
||||||
Post,
|
Post,
|
||||||
Comment,
|
Comment,
|
||||||
|
PrivateMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
||||||
|
@ -120,6 +125,7 @@ pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
|
||||||
// TODO I have to change this else my update advanced_migrations crashes the
|
// TODO I have to change this else my update advanced_migrations crashes the
|
||||||
// server if a comment exists.
|
// server if a comment exists.
|
||||||
EndpointType::Comment => "comment",
|
EndpointType::Comment => "comment",
|
||||||
|
EndpointType::PrivateMessage => "private_message",
|
||||||
};
|
};
|
||||||
|
|
||||||
Url::parse(&format!(
|
Url::parse(&format!(
|
||||||
|
|
234
server/src/apub/private_message.rs
Normal file
234
server/src/apub/private_message.rs
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl ToApub for PrivateMessage {
|
||||||
|
type Response = Note;
|
||||||
|
|
||||||
|
fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> {
|
||||||
|
let mut private_message = Note::default();
|
||||||
|
let oprops: &mut ObjectProperties = private_message.as_mut();
|
||||||
|
let creator = User_::read(&conn, self.creator_id)?;
|
||||||
|
let recipient = User_::read(&conn, self.recipient_id)?;
|
||||||
|
|
||||||
|
oprops
|
||||||
|
.set_context_xsd_any_uri(context())?
|
||||||
|
.set_id(self.ap_id.to_owned())?
|
||||||
|
.set_published(convert_datetime(self.published))?
|
||||||
|
.set_content_xsd_string(self.content.to_owned())?
|
||||||
|
.set_to_xsd_any_uri(recipient.actor_id)?
|
||||||
|
.set_attributed_to_xsd_any_uri(creator.actor_id)?;
|
||||||
|
|
||||||
|
if let Some(u) = self.updated {
|
||||||
|
oprops.set_updated(convert_datetime(u))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(private_message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_tombstone(&self) -> Result<Tombstone, Error> {
|
||||||
|
create_tombstone(
|
||||||
|
self.deleted,
|
||||||
|
&self.ap_id,
|
||||||
|
self.updated,
|
||||||
|
NoteType.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromApub for PrivateMessageForm {
|
||||||
|
type ApubType = Note;
|
||||||
|
|
||||||
|
/// Parse an ActivityPub note received from another instance into a Lemmy Private message
|
||||||
|
fn from_apub(note: &Note, conn: &PgConnection) -> Result<PrivateMessageForm, 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 recipient_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
|
||||||
|
let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, &conn)?;
|
||||||
|
|
||||||
|
Ok(PrivateMessageForm {
|
||||||
|
creator_id: creator.id,
|
||||||
|
recipient_id: recipient.id,
|
||||||
|
content: oprops
|
||||||
|
.get_content_xsd_string()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.unwrap(),
|
||||||
|
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,
|
||||||
|
read: None,
|
||||||
|
ap_id: oprops.get_id().unwrap().to_string(),
|
||||||
|
local: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApubObjectType for PrivateMessage {
|
||||||
|
/// Send out information about a newly created private message
|
||||||
|
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
let note = self.to_apub(conn)?;
|
||||||
|
let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||||
|
let recipient = User_::read(&conn, self.recipient_id)?;
|
||||||
|
|
||||||
|
let mut create = Create::new();
|
||||||
|
create
|
||||||
|
.object_props
|
||||||
|
.set_context_xsd_any_uri(context())?
|
||||||
|
.set_id(id)?;
|
||||||
|
let to = format!("{}/inbox", recipient.actor_id);
|
||||||
|
|
||||||
|
create
|
||||||
|
.create_props
|
||||||
|
.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 {
|
||||||
|
user_id: creator.id,
|
||||||
|
data: serde_json::to_value(&create)?,
|
||||||
|
local: true,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
activity::Activity::create(&conn, &activity_form)?;
|
||||||
|
|
||||||
|
send_activity(
|
||||||
|
&create,
|
||||||
|
&creator.private_key.as_ref().unwrap(),
|
||||||
|
&creator.actor_id,
|
||||||
|
vec![to],
|
||||||
|
)?;
|
||||||
|
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 id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||||
|
let recipient = User_::read(&conn, self.recipient_id)?;
|
||||||
|
|
||||||
|
let mut update = Update::new();
|
||||||
|
update
|
||||||
|
.object_props
|
||||||
|
.set_context_xsd_any_uri(context())?
|
||||||
|
.set_id(id)?;
|
||||||
|
let to = format!("{}/inbox", recipient.actor_id);
|
||||||
|
|
||||||
|
update
|
||||||
|
.update_props
|
||||||
|
.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 {
|
||||||
|
user_id: creator.id,
|
||||||
|
data: serde_json::to_value(&update)?,
|
||||||
|
local: true,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
activity::Activity::create(&conn, &activity_form)?;
|
||||||
|
|
||||||
|
send_activity(
|
||||||
|
&update,
|
||||||
|
&creator.private_key.as_ref().unwrap(),
|
||||||
|
&creator.actor_id,
|
||||||
|
vec![to],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
let note = self.to_apub(conn)?;
|
||||||
|
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||||
|
let recipient = User_::read(&conn, self.recipient_id)?;
|
||||||
|
|
||||||
|
let mut delete = Delete::new();
|
||||||
|
delete
|
||||||
|
.object_props
|
||||||
|
.set_context_xsd_any_uri(context())?
|
||||||
|
.set_id(id)?;
|
||||||
|
let to = format!("{}/inbox", recipient.actor_id);
|
||||||
|
|
||||||
|
delete
|
||||||
|
.delete_props
|
||||||
|
.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 {
|
||||||
|
user_id: creator.id,
|
||||||
|
data: serde_json::to_value(&delete)?,
|
||||||
|
local: true,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
activity::Activity::create(&conn, &activity_form)?;
|
||||||
|
|
||||||
|
send_activity(
|
||||||
|
&delete,
|
||||||
|
&creator.private_key.as_ref().unwrap(),
|
||||||
|
&creator.actor_id,
|
||||||
|
vec![to],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
let note = self.to_apub(conn)?;
|
||||||
|
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||||
|
let recipient = User_::read(&conn, self.recipient_id)?;
|
||||||
|
|
||||||
|
let mut delete = Delete::new();
|
||||||
|
delete
|
||||||
|
.object_props
|
||||||
|
.set_context_xsd_any_uri(context())?
|
||||||
|
.set_id(id)?;
|
||||||
|
let to = format!("{}/inbox", recipient.actor_id);
|
||||||
|
|
||||||
|
delete
|
||||||
|
.delete_props
|
||||||
|
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||||
|
.set_object_base_box(note)?;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Undo that fake activity
|
||||||
|
let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
|
||||||
|
let mut undo = Undo::default();
|
||||||
|
|
||||||
|
undo
|
||||||
|
.object_props
|
||||||
|
.set_context_xsd_any_uri(context())?
|
||||||
|
.set_id(undo_id)?;
|
||||||
|
|
||||||
|
undo
|
||||||
|
.undo_props
|
||||||
|
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||||
|
.set_object_base_box(delete)?;
|
||||||
|
|
||||||
|
// Insert the sent activity into the activity table
|
||||||
|
let activity_form = activity::ActivityForm {
|
||||||
|
user_id: creator.id,
|
||||||
|
data: serde_json::to_value(&undo)?,
|
||||||
|
local: true,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
activity::Activity::create(&conn, &activity_form)?;
|
||||||
|
|
||||||
|
send_activity(
|
||||||
|
&undo,
|
||||||
|
&creator.private_key.as_ref().unwrap(),
|
||||||
|
&creator.actor_id,
|
||||||
|
vec![to],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_undo_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,11 @@ use super::*;
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub enum UserAcceptedObjects {
|
pub enum UserAcceptedObjects {
|
||||||
Accept(Accept),
|
Accept(Box<Accept>),
|
||||||
|
Create(Box<Create>),
|
||||||
|
Update(Box<Update>),
|
||||||
|
Delete(Box<Delete>),
|
||||||
|
Undo(Box<Undo>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for all incoming activities to user inboxes.
|
/// Handler for all incoming activities to user inboxes.
|
||||||
|
@ -12,7 +16,7 @@ pub async fn user_inbox(
|
||||||
input: web::Json<UserAcceptedObjects>,
|
input: web::Json<UserAcceptedObjects>,
|
||||||
path: web::Path<String>,
|
path: web::Path<String>,
|
||||||
db: DbPoolParam,
|
db: DbPoolParam,
|
||||||
_chat_server: ChatServerParam,
|
chat_server: ChatServerParam,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
// TODO: would be nice if we could do the signature check here, but we cant access the actor property
|
// 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 input = input.into_inner();
|
||||||
|
@ -21,12 +25,24 @@ pub async fn user_inbox(
|
||||||
debug!("User {} received activity: {:?}", &username, &input);
|
debug!("User {} received activity: {:?}", &username, &input);
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
UserAcceptedObjects::Accept(a) => handle_accept(&a, &request, &username, &conn),
|
UserAcceptedObjects::Accept(a) => receive_accept(&a, &request, &username, &conn),
|
||||||
|
UserAcceptedObjects::Create(c) => {
|
||||||
|
receive_create_private_message(&c, &request, &conn, chat_server)
|
||||||
|
}
|
||||||
|
UserAcceptedObjects::Update(u) => {
|
||||||
|
receive_update_private_message(&u, &request, &conn, chat_server)
|
||||||
|
}
|
||||||
|
UserAcceptedObjects::Delete(d) => {
|
||||||
|
receive_delete_private_message(&d, &request, &conn, chat_server)
|
||||||
|
}
|
||||||
|
UserAcceptedObjects::Undo(u) => {
|
||||||
|
receive_undo_delete_private_message(&u, &request, &conn, chat_server)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle accepted follows.
|
/// Handle accepted follows.
|
||||||
fn handle_accept(
|
fn receive_accept(
|
||||||
accept: &Accept,
|
accept: &Accept,
|
||||||
request: &HttpRequest,
|
request: &HttpRequest,
|
||||||
username: &str,
|
username: &str,
|
||||||
|
@ -65,3 +81,240 @@ fn handle_accept(
|
||||||
// TODO: at this point, indicate to the user that they are following the community
|
// TODO: at this point, indicate to the user that they are following the community
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn receive_create_private_message(
|
||||||
|
create: &Create,
|
||||||
|
request: &HttpRequest,
|
||||||
|
conn: &PgConnection,
|
||||||
|
chat_server: ChatServerParam,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let note = create
|
||||||
|
.create_props
|
||||||
|
.get_object_base_box()
|
||||||
|
.to_owned()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.into_concrete::<Note>()?;
|
||||||
|
|
||||||
|
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())?;
|
||||||
|
|
||||||
|
// Insert the received activity into the activity table
|
||||||
|
let activity_form = activity::ActivityForm {
|
||||||
|
user_id: user.id,
|
||||||
|
data: serde_json::to_value(&create)?,
|
||||||
|
local: false,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
activity::Activity::create(&conn, &activity_form)?;
|
||||||
|
|
||||||
|
let private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
|
||||||
|
let inserted_private_message = PrivateMessage::create(&conn, &private_message)?;
|
||||||
|
|
||||||
|
let message = PrivateMessageView::read(&conn, inserted_private_message.id)?;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
message: message.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
chat_server.do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::CreatePrivateMessage,
|
||||||
|
response: res,
|
||||||
|
recipient_id: message.recipient_id,
|
||||||
|
my_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_update_private_message(
|
||||||
|
update: &Update,
|
||||||
|
request: &HttpRequest,
|
||||||
|
conn: &PgConnection,
|
||||||
|
chat_server: ChatServerParam,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let note = update
|
||||||
|
.update_props
|
||||||
|
.get_object_base_box()
|
||||||
|
.to_owned()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.into_concrete::<Note>()?;
|
||||||
|
|
||||||
|
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())?;
|
||||||
|
|
||||||
|
// Insert the received activity into the activity table
|
||||||
|
let activity_form = activity::ActivityForm {
|
||||||
|
user_id: user.id,
|
||||||
|
data: serde_json::to_value(&update)?,
|
||||||
|
local: false,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
activity::Activity::create(&conn, &activity_form)?;
|
||||||
|
|
||||||
|
let private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
|
||||||
|
let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
|
||||||
|
PrivateMessage::update(conn, private_message_id, &private_message)?;
|
||||||
|
|
||||||
|
let message = PrivateMessageView::read(&conn, private_message_id)?;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
message: message.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
chat_server.do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::EditPrivateMessage,
|
||||||
|
response: res,
|
||||||
|
recipient_id: message.recipient_id,
|
||||||
|
my_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_delete_private_message(
|
||||||
|
delete: &Delete,
|
||||||
|
request: &HttpRequest,
|
||||||
|
conn: &PgConnection,
|
||||||
|
chat_server: ChatServerParam,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let note = delete
|
||||||
|
.delete_props
|
||||||
|
.get_object_base_box()
|
||||||
|
.to_owned()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.into_concrete::<Note>()?;
|
||||||
|
|
||||||
|
let user_uri = delete
|
||||||
|
.delete_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())?;
|
||||||
|
|
||||||
|
// 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 private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
|
||||||
|
let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
|
||||||
|
let private_message_form = PrivateMessageForm {
|
||||||
|
content: private_message.content,
|
||||||
|
recipient_id: private_message.recipient_id,
|
||||||
|
creator_id: private_message.creator_id,
|
||||||
|
deleted: Some(true),
|
||||||
|
read: None,
|
||||||
|
ap_id: private_message.ap_id,
|
||||||
|
local: private_message.local,
|
||||||
|
published: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
};
|
||||||
|
PrivateMessage::update(conn, private_message_id, &private_message_form)?;
|
||||||
|
|
||||||
|
let message = PrivateMessageView::read(&conn, private_message_id)?;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
message: message.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
chat_server.do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::EditPrivateMessage,
|
||||||
|
response: res,
|
||||||
|
recipient_id: message.recipient_id,
|
||||||
|
my_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_undo_delete_private_message(
|
||||||
|
undo: &Undo,
|
||||||
|
request: &HttpRequest,
|
||||||
|
conn: &PgConnection,
|
||||||
|
chat_server: ChatServerParam,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let delete = undo
|
||||||
|
.undo_props
|
||||||
|
.get_object_base_box()
|
||||||
|
.to_owned()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.into_concrete::<Delete>()?;
|
||||||
|
|
||||||
|
let note = delete
|
||||||
|
.delete_props
|
||||||
|
.get_object_base_box()
|
||||||
|
.to_owned()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
.into_concrete::<Note>()?;
|
||||||
|
|
||||||
|
let user_uri = delete
|
||||||
|
.delete_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())?;
|
||||||
|
|
||||||
|
// 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 private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
|
||||||
|
let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
|
||||||
|
let private_message_form = PrivateMessageForm {
|
||||||
|
content: private_message.content,
|
||||||
|
recipient_id: private_message.recipient_id,
|
||||||
|
creator_id: private_message.creator_id,
|
||||||
|
deleted: Some(false),
|
||||||
|
read: None,
|
||||||
|
ap_id: private_message.ap_id,
|
||||||
|
local: private_message.local,
|
||||||
|
published: None,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
};
|
||||||
|
PrivateMessage::update(conn, private_message_id, &private_message_form)?;
|
||||||
|
|
||||||
|
let message = PrivateMessageView::read(&conn, private_message_id)?;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
message: message.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
chat_server.do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::EditPrivateMessage,
|
||||||
|
response: res,
|
||||||
|
recipient_id: message.recipient_id,
|
||||||
|
my_id: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use super::comment::Comment;
|
use super::comment::Comment;
|
||||||
use super::community::{Community, CommunityForm};
|
use super::community::{Community, CommunityForm};
|
||||||
use super::post::Post;
|
use super::post::Post;
|
||||||
|
use super::private_message::PrivateMessage;
|
||||||
use super::user::{UserForm, User_};
|
use super::user::{UserForm, User_};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::apub::signatures::generate_actor_keypair;
|
use crate::apub::signatures::generate_actor_keypair;
|
||||||
|
@ -15,6 +16,7 @@ pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), Error> {
|
||||||
community_updates_2020_04_02(conn)?;
|
community_updates_2020_04_02(conn)?;
|
||||||
post_updates_2020_04_03(conn)?;
|
post_updates_2020_04_03(conn)?;
|
||||||
comment_updates_2020_04_03(conn)?;
|
comment_updates_2020_04_03(conn)?;
|
||||||
|
private_message_updates_2020_05_05(conn)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -145,3 +147,23 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
|
||||||
|
info!("Running private_message_updates_2020_05_05");
|
||||||
|
|
||||||
|
// Update the ap_id
|
||||||
|
let incorrect_pms = private_message
|
||||||
|
.filter(ap_id.eq("changeme"))
|
||||||
|
.filter(local.eq(true))
|
||||||
|
.load::<PrivateMessage>(conn)?;
|
||||||
|
|
||||||
|
for cpm in &incorrect_pms {
|
||||||
|
PrivateMessage::update_ap_id(&conn, cpm.id)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("{} private message rows updated.", incorrect_pms.len());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::apub::{make_apub_endpoint, EndpointType};
|
||||||
use crate::schema::private_message;
|
use crate::schema::private_message;
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
@ -12,6 +13,8 @@ pub struct PrivateMessage {
|
||||||
pub read: bool,
|
pub read: bool,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
pub ap_id: String,
|
||||||
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, AsChangeset, Clone)]
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
|
@ -19,10 +22,13 @@ pub struct PrivateMessage {
|
||||||
pub struct PrivateMessageForm {
|
pub struct PrivateMessageForm {
|
||||||
pub creator_id: i32,
|
pub creator_id: i32,
|
||||||
pub recipient_id: i32,
|
pub recipient_id: i32,
|
||||||
pub content: Option<String>,
|
pub content: String,
|
||||||
pub deleted: Option<bool>,
|
pub deleted: Option<bool>,
|
||||||
pub read: Option<bool>,
|
pub read: Option<bool>,
|
||||||
|
pub published: Option<chrono::NaiveDateTime>,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
pub ap_id: String,
|
||||||
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Crud<PrivateMessageForm> for PrivateMessage {
|
impl Crud<PrivateMessageForm> for PrivateMessage {
|
||||||
|
@ -55,6 +61,28 @@ impl Crud<PrivateMessageForm> for PrivateMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PrivateMessage {
|
||||||
|
pub fn update_ap_id(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
|
||||||
|
let apid = make_apub_endpoint(
|
||||||
|
EndpointType::PrivateMessage,
|
||||||
|
&private_message_id.to_string(),
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
|
diesel::update(private_message.find(private_message_id))
|
||||||
|
.set(ap_id.eq(apid))
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
|
||||||
|
use crate::schema::private_message::dsl::*;
|
||||||
|
private_message
|
||||||
|
.filter(ap_id.eq(object_id))
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::super::user::*;
|
use super::super::user::*;
|
||||||
|
@ -118,12 +146,15 @@ mod tests {
|
||||||
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
|
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
|
||||||
|
|
||||||
let private_message_form = PrivateMessageForm {
|
let private_message_form = PrivateMessageForm {
|
||||||
content: Some("A test private message".into()),
|
content: "A test private message".into(),
|
||||||
creator_id: inserted_creator.id,
|
creator_id: inserted_creator.id,
|
||||||
recipient_id: inserted_recipient.id,
|
recipient_id: inserted_recipient.id,
|
||||||
deleted: None,
|
deleted: None,
|
||||||
read: None,
|
read: None,
|
||||||
|
published: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
|
ap_id: "changeme".into(),
|
||||||
|
local: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_private_message = PrivateMessage::create(&conn, &private_message_form).unwrap();
|
let inserted_private_message = PrivateMessage::create(&conn, &private_message_form).unwrap();
|
||||||
|
@ -137,6 +168,8 @@ mod tests {
|
||||||
read: false,
|
read: false,
|
||||||
updated: None,
|
updated: None,
|
||||||
published: inserted_private_message.published,
|
published: inserted_private_message.published,
|
||||||
|
ap_id: "changeme".into(),
|
||||||
|
local: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let read_private_message = PrivateMessage::read(&conn, inserted_private_message.id).unwrap();
|
let read_private_message = PrivateMessage::read(&conn, inserted_private_message.id).unwrap();
|
||||||
|
|
|
@ -12,10 +12,16 @@ table! {
|
||||||
read -> Bool,
|
read -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
|
ap_id -> Text,
|
||||||
|
local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
|
creator_actor_id -> Text,
|
||||||
|
creator_local -> Bool,
|
||||||
recipient_name -> Varchar,
|
recipient_name -> Varchar,
|
||||||
recipient_avatar -> Nullable<Text>,
|
recipient_avatar -> Nullable<Text>,
|
||||||
|
recipient_actor_id -> Text,
|
||||||
|
recipient_local -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,10 +35,16 @@ table! {
|
||||||
read -> Bool,
|
read -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
|
ap_id -> Text,
|
||||||
|
local -> Bool,
|
||||||
creator_name -> Varchar,
|
creator_name -> Varchar,
|
||||||
creator_avatar -> Nullable<Text>,
|
creator_avatar -> Nullable<Text>,
|
||||||
|
creator_actor_id -> Text,
|
||||||
|
creator_local -> Bool,
|
||||||
recipient_name -> Varchar,
|
recipient_name -> Varchar,
|
||||||
recipient_avatar -> Nullable<Text>,
|
recipient_avatar -> Nullable<Text>,
|
||||||
|
recipient_actor_id -> Text,
|
||||||
|
recipient_local -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,10 +61,16 @@ pub struct PrivateMessageView {
|
||||||
pub read: bool,
|
pub read: bool,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
pub ap_id: String,
|
||||||
|
pub local: bool,
|
||||||
pub creator_name: String,
|
pub creator_name: String,
|
||||||
pub creator_avatar: Option<String>,
|
pub creator_avatar: Option<String>,
|
||||||
|
pub creator_actor_id: String,
|
||||||
|
pub creator_local: bool,
|
||||||
pub recipient_name: String,
|
pub recipient_name: String,
|
||||||
pub recipient_avatar: Option<String>,
|
pub recipient_avatar: Option<String>,
|
||||||
|
pub recipient_actor_id: String,
|
||||||
|
pub recipient_local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrivateMessageQueryBuilder<'a> {
|
pub struct PrivateMessageQueryBuilder<'a> {
|
||||||
|
|
|
@ -83,6 +83,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
||||||
.route("/save", web::put().to(route_post::<SaveComment>)),
|
.route("/save", web::put().to(route_post::<SaveComment>)),
|
||||||
)
|
)
|
||||||
|
// Private Message
|
||||||
|
.service(
|
||||||
|
web::scope("/private_message")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("/list", web::get().to(route_get::<GetPrivateMessages>))
|
||||||
|
.route("", web::post().to(route_post::<CreatePrivateMessage>))
|
||||||
|
.route("", web::put().to(route_post::<EditPrivateMessage>)),
|
||||||
|
)
|
||||||
// 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
|
||||||
|
|
|
@ -272,6 +272,8 @@ table! {
|
||||||
read -> Bool,
|
read -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
|
ap_id -> Varchar,
|
||||||
|
local -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
142
ui/src/api_tests/api.spec.ts
vendored
142
ui/src/api_tests/api.spec.ts
vendored
|
@ -18,6 +18,10 @@ import {
|
||||||
GetCommunityResponse,
|
GetCommunityResponse,
|
||||||
CommentLikeForm,
|
CommentLikeForm,
|
||||||
CreatePostLikeForm,
|
CreatePostLikeForm,
|
||||||
|
PrivateMessageForm,
|
||||||
|
EditPrivateMessageForm,
|
||||||
|
PrivateMessageResponse,
|
||||||
|
PrivateMessagesResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
|
|
||||||
let lemmyAlphaUrl = 'http://localhost:8540';
|
let lemmyAlphaUrl = 'http://localhost:8540';
|
||||||
|
@ -158,6 +162,7 @@ describe('main', () => {
|
||||||
body: wrapper(unfollowForm),
|
body: wrapper(unfollowForm),
|
||||||
}
|
}
|
||||||
).then(d => d.json());
|
).then(d => d.json());
|
||||||
|
expect(unfollowRes.community.local).toBe(false);
|
||||||
|
|
||||||
// Check that you are unsubscribed to it locally
|
// Check that you are unsubscribed to it locally
|
||||||
let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
|
let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
|
||||||
|
@ -965,6 +970,143 @@ describe('main', () => {
|
||||||
expect(getCommunityResAgain.community.removed).toBe(false);
|
expect(getCommunityResAgain.community.removed).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('private message', () => {
|
||||||
|
test('/u/lemmy_alpha creates/updates/deletes/undeletes a private_message to /u/lemmy_beta, its on both instances', async () => {
|
||||||
|
let content = 'A jest test federated private message';
|
||||||
|
let privateMessageForm: PrivateMessageForm = {
|
||||||
|
content,
|
||||||
|
recipient_id: 3,
|
||||||
|
auth: lemmyAlphaAuth,
|
||||||
|
};
|
||||||
|
|
||||||
|
let createRes: PrivateMessageResponse = await fetch(
|
||||||
|
`${lemmyAlphaApiUrl}/private_message`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: wrapper(privateMessageForm),
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
expect(createRes.message.content).toBe(content);
|
||||||
|
expect(createRes.message.local).toBe(true);
|
||||||
|
expect(createRes.message.creator_local).toBe(true);
|
||||||
|
expect(createRes.message.recipient_local).toBe(false);
|
||||||
|
|
||||||
|
// Get it from beta
|
||||||
|
let getPrivateMessagesUrl = `${lemmyBetaApiUrl}/private_message/list?auth=${lemmyBetaAuth}&unread_only=false`;
|
||||||
|
|
||||||
|
let getPrivateMessagesRes: PrivateMessagesResponse = await fetch(
|
||||||
|
getPrivateMessagesUrl,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
expect(getPrivateMessagesRes.messages[0].content).toBe(content);
|
||||||
|
expect(getPrivateMessagesRes.messages[0].local).toBe(false);
|
||||||
|
expect(getPrivateMessagesRes.messages[0].creator_local).toBe(false);
|
||||||
|
expect(getPrivateMessagesRes.messages[0].recipient_local).toBe(true);
|
||||||
|
|
||||||
|
// lemmy alpha updates the private message
|
||||||
|
let updatedContent = 'A jest test federated private message edited';
|
||||||
|
let updatePrivateMessageForm: EditPrivateMessageForm = {
|
||||||
|
content: updatedContent,
|
||||||
|
edit_id: createRes.message.id,
|
||||||
|
auth: lemmyAlphaAuth,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updateRes: PrivateMessageResponse = await fetch(
|
||||||
|
`${lemmyAlphaApiUrl}/private_message`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: wrapper(updatePrivateMessageForm),
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
expect(updateRes.message.content).toBe(updatedContent);
|
||||||
|
|
||||||
|
// Fetch from beta again
|
||||||
|
let getPrivateMessagesUpdatedRes: PrivateMessagesResponse = await fetch(
|
||||||
|
getPrivateMessagesUrl,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
expect(getPrivateMessagesUpdatedRes.messages[0].content).toBe(
|
||||||
|
updatedContent
|
||||||
|
);
|
||||||
|
|
||||||
|
// lemmy alpha deletes the private message
|
||||||
|
let deletePrivateMessageForm: EditPrivateMessageForm = {
|
||||||
|
deleted: true,
|
||||||
|
edit_id: createRes.message.id,
|
||||||
|
auth: lemmyAlphaAuth,
|
||||||
|
};
|
||||||
|
|
||||||
|
let deleteRes: PrivateMessageResponse = await fetch(
|
||||||
|
`${lemmyAlphaApiUrl}/private_message`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: wrapper(deletePrivateMessageForm),
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
expect(deleteRes.message.deleted).toBe(true);
|
||||||
|
|
||||||
|
// Fetch from beta again
|
||||||
|
let getPrivateMessagesDeletedRes: PrivateMessagesResponse = await fetch(
|
||||||
|
getPrivateMessagesUrl,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
// The GetPrivateMessages filters out deleted,
|
||||||
|
// even though they are in the actual database.
|
||||||
|
// no reason to show them
|
||||||
|
expect(getPrivateMessagesDeletedRes.messages.length).toBe(0);
|
||||||
|
|
||||||
|
// lemmy alpha undeletes the private message
|
||||||
|
let undeletePrivateMessageForm: EditPrivateMessageForm = {
|
||||||
|
deleted: false,
|
||||||
|
edit_id: createRes.message.id,
|
||||||
|
auth: lemmyAlphaAuth,
|
||||||
|
};
|
||||||
|
|
||||||
|
let undeleteRes: PrivateMessageResponse = await fetch(
|
||||||
|
`${lemmyAlphaApiUrl}/private_message`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: wrapper(undeletePrivateMessageForm),
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
expect(undeleteRes.message.deleted).toBe(false);
|
||||||
|
|
||||||
|
// Fetch from beta again
|
||||||
|
let getPrivateMessagesUnDeletedRes: PrivateMessagesResponse = await fetch(
|
||||||
|
getPrivateMessagesUrl,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
expect(getPrivateMessagesUnDeletedRes.messages[0].deleted).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function wrapper(form: any): string {
|
function wrapper(form: any): string {
|
||||||
|
|
6
ui/src/interfaces.ts
vendored
6
ui/src/interfaces.ts
vendored
|
@ -273,10 +273,16 @@ export interface PrivateMessage {
|
||||||
read: boolean;
|
read: boolean;
|
||||||
published: string;
|
published: string;
|
||||||
updated?: string;
|
updated?: string;
|
||||||
|
ap_id: string;
|
||||||
|
local: boolean;
|
||||||
creator_name: string;
|
creator_name: string;
|
||||||
creator_avatar?: string;
|
creator_avatar?: string;
|
||||||
|
creator_actor_id: string;
|
||||||
|
creator_local: boolean;
|
||||||
recipient_name: string;
|
recipient_name: string;
|
||||||
recipient_avatar?: string;
|
recipient_avatar?: string;
|
||||||
|
recipient_actor_id: string;
|
||||||
|
recipient_local: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BanType {
|
export enum BanType {
|
||||||
|
|
Loading…
Reference in a new issue