Making the chat server an actor. (#2793)
* Making the chat server an actor. - Fixes #2778 - #2787 * Forgot to add handlers folder. * Some cleanup. * Forgot to remove a comment. * Address PR comments. * Using ToString for enum operations.
This commit is contained in:
parent
844cde9db3
commit
63f54a3103
66 changed files with 1478 additions and 1237 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -58,6 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5"
|
checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
|
"actix_derive",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
@ -324,16 +325,14 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-ws"
|
name = "actix_derive"
|
||||||
version = "0.2.5"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "535aec173810be3ca6f25dd5b4d431ae7125d62000aa3cbae1ec739921b02cf3"
|
checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-codec",
|
"proc-macro2 1.0.47",
|
||||||
"actix-http",
|
"quote 1.0.21",
|
||||||
"actix-web",
|
"syn 1.0.103",
|
||||||
"futures-core",
|
|
||||||
"tokio",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2409,9 +2408,9 @@ dependencies = [
|
||||||
name = "lemmy_api_common"
|
name = "lemmy_api_common"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-ws",
|
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"encoding",
|
"encoding",
|
||||||
|
@ -2461,6 +2460,7 @@ name = "lemmy_apub"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -2589,10 +2589,10 @@ name = "lemmy_server"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"actix-ws",
|
|
||||||
"clokwerk",
|
"clokwerk",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
"diesel",
|
"diesel",
|
||||||
|
|
|
@ -104,7 +104,7 @@ rosetta-i18n = "0.1.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
opentelemetry = { version = "0.17.0", features = ["rt-tokio"] }
|
opentelemetry = { version = "0.17.0", features = ["rt-tokio"] }
|
||||||
tracing-opentelemetry = { version = "0.17.2" }
|
tracing-opentelemetry = { version = "0.17.2" }
|
||||||
actix-ws = "0.2.0"
|
actix = "0.13"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
@ -133,7 +133,7 @@ doku = { workspace = true }
|
||||||
reqwest-retry = { workspace = true }
|
reqwest-retry = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
actix-ws = { workspace = true }
|
actix = { workspace = true }
|
||||||
tracing-opentelemetry = { workspace = true, optional = true }
|
tracing-opentelemetry = { workspace = true, optional = true }
|
||||||
opentelemetry = { workspace = true, optional = true }
|
opentelemetry = { workspace = true, optional = true }
|
||||||
actix-web-actors = { version = "4.1.0", default-features = false }
|
actix-web-actors = { version = "4.1.0", default-features = false }
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, CreateCommentLike},
|
comment::{CommentResponse, CreateCommentLike},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_community_ban, check_downvotes_enabled, get_local_user_view_from_jwt},
|
utils::{check_community_ban, check_downvotes_enabled, get_local_user_view_from_jwt},
|
||||||
websocket::{send::send_comment_ws_message, UserOperation},
|
websocket::UserOperation,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
|
@ -78,15 +78,15 @@ impl Perform for CreateCommentLike {
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_like_comment"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_like_comment"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_comment_ws_message(
|
context
|
||||||
data.comment_id,
|
.send_comment_ws_message(
|
||||||
UserOperation::CreateCommentLike,
|
&UserOperation::CreateCommentLike,
|
||||||
websocket_id,
|
data.comment_id,
|
||||||
None,
|
websocket_id,
|
||||||
Some(local_user_view.person.id),
|
None,
|
||||||
recipient_ids,
|
Some(local_user_view.person.id),
|
||||||
context,
|
recipient_ids,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,15 +69,12 @@ impl Perform for CreateCommentReport {
|
||||||
comment_report_view,
|
comment_report_view,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::CreateCommentReport,
|
||||||
.send_mod_room_message(
|
&res,
|
||||||
UserOperation::CreateCommentReport,
|
comment_view.community.id,
|
||||||
&res,
|
websocket_id,
|
||||||
comment_view.community.id,
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,15 +49,12 @@ impl Perform for ResolveCommentReport {
|
||||||
comment_report_view,
|
comment_report_view,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::ResolveCommentReport,
|
||||||
.send_mod_room_message(
|
&res,
|
||||||
UserOperation::ResolveCommentReport,
|
report.community.id,
|
||||||
&res,
|
websocket_id,
|
||||||
report.community.id,
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,15 +70,13 @@ impl Perform for AddModToCommunity {
|
||||||
let moderators = CommunityModeratorView::for_community(context.pool(), community_id).await?;
|
let moderators = CommunityModeratorView::for_community(context.pool(), community_id).await?;
|
||||||
|
|
||||||
let res = AddModToCommunityResponse { moderators };
|
let res = AddModToCommunityResponse { moderators };
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::AddModToCommunity,
|
||||||
.send_community_room_message(
|
&res,
|
||||||
&UserOperation::AddModToCommunity,
|
community_id,
|
||||||
&res,
|
websocket_id,
|
||||||
community_id,
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ use lemmy_api_common::{
|
||||||
community::{BanFromCommunity, BanFromCommunityResponse},
|
community::{BanFromCommunity, BanFromCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{get_local_user_view_from_jwt, is_mod_or_admin, remove_user_data_in_community},
|
utils::{get_local_user_view_from_jwt, is_mod_or_admin, remove_user_data_in_community},
|
||||||
websocket::UserOperation,
|
websocket::{
|
||||||
|
handlers::messages::SendCommunityRoomMessage,
|
||||||
|
serialize_websocket_message,
|
||||||
|
UserOperation,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -95,15 +99,13 @@ impl Perform for BanFromCommunity {
|
||||||
banned: data.ban,
|
banned: data.ban,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
// A custom ban message
|
||||||
.chat_server()
|
let message = serialize_websocket_message(&UserOperation::BanFromCommunity, &res)?;
|
||||||
.send_community_room_message(
|
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
&UserOperation::BanFromCommunity,
|
community_id,
|
||||||
&res,
|
message,
|
||||||
community_id,
|
websocket_id,
|
||||||
websocket_id,
|
});
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, HideCommunity},
|
community::{CommunityResponse, HideCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{get_local_user_view_from_jwt, is_admin},
|
utils::{get_local_user_view_from_jwt, is_admin},
|
||||||
websocket::{send::send_community_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -50,7 +50,13 @@ impl Perform for HideCommunity {
|
||||||
|
|
||||||
ModHideCommunity::create(context.pool(), &mod_hide_community_form).await?;
|
ModHideCommunity::create(context.pool(), &mod_hide_community_form).await?;
|
||||||
|
|
||||||
let op = UserOperationCrud::EditCommunity;
|
context
|
||||||
send_community_ws_message(data.community_id, op, websocket_id, None, context).await
|
.send_community_ws_message(
|
||||||
|
&UserOperationCrud::EditCommunity,
|
||||||
|
data.community_id,
|
||||||
|
websocket_id,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,10 +56,7 @@ impl Perform for AddAdmin {
|
||||||
|
|
||||||
let res = AddAdminResponse { admins };
|
let res = AddAdminResponse { admins };
|
||||||
|
|
||||||
context
|
context.send_all_ws_message(&UserOperation::AddAdmin, &res, websocket_id)?;
|
||||||
.chat_server()
|
|
||||||
.send_all_message(UserOperation::AddAdmin, &res, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,10 +79,7 @@ impl Perform for BanPerson {
|
||||||
banned: data.ban,
|
banned: data.ban,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
context.send_all_ws_message(&UserOperation::BanPerson, &res, websocket_id)?;
|
||||||
.chat_server()
|
|
||||||
.send_all_message(UserOperation::BanPerson, &res, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use chrono::Duration;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{CaptchaResponse, GetCaptcha, GetCaptchaResponse},
|
person::{CaptchaResponse, GetCaptcha, GetCaptchaResponse},
|
||||||
websocket::structs::CaptchaItem,
|
websocket::{handlers::captcha::AddCaptcha, structs::CaptchaItem},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::local_site::LocalSite, utils::naive_now};
|
use lemmy_db_schema::{source::local_site::LocalSite, utils::naive_now};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
|
@ -47,7 +47,9 @@ impl Perform for GetCaptcha {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stores the captcha item on the queue
|
// Stores the captcha item on the queue
|
||||||
context.chat_server().add_captcha(captcha_item)?;
|
context.chat_server().do_send(AddCaptcha {
|
||||||
|
captcha: captcha_item,
|
||||||
|
});
|
||||||
|
|
||||||
Ok(GetCaptchaResponse {
|
Ok(GetCaptchaResponse {
|
||||||
ok: Some(CaptchaResponse { png, wav, uuid }),
|
ok: Some(CaptchaResponse { png, wav, uuid }),
|
||||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_api_common::{
|
||||||
is_admin,
|
is_admin,
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
},
|
},
|
||||||
websocket::{send::send_post_ws_message, UserOperation},
|
websocket::UserOperation,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -82,13 +82,13 @@ impl Perform for FeaturePost {
|
||||||
|
|
||||||
ModFeaturePost::create(context.pool(), &form).await?;
|
ModFeaturePost::create(context.pool(), &form).await?;
|
||||||
|
|
||||||
send_post_ws_message(
|
context
|
||||||
data.post_id,
|
.send_post_ws_message(
|
||||||
UserOperation::FeaturePost,
|
&UserOperation::FeaturePost,
|
||||||
websocket_id,
|
data.post_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_api_common::{
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
},
|
},
|
||||||
websocket::{send::send_post_ws_message, UserOperation},
|
websocket::UserOperation,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -69,13 +69,13 @@ impl Perform for CreatePostLike {
|
||||||
// Mark the post as read
|
// Mark the post as read
|
||||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||||
|
|
||||||
send_post_ws_message(
|
context
|
||||||
data.post_id,
|
.send_post_ws_message(
|
||||||
UserOperation::CreatePostLike,
|
&UserOperation::CreatePostLike,
|
||||||
websocket_id,
|
data.post_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use lemmy_api_common::{
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
},
|
},
|
||||||
websocket::{send::send_post_ws_message, UserOperation},
|
websocket::UserOperation,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -71,13 +71,13 @@ impl Perform for LockPost {
|
||||||
};
|
};
|
||||||
ModLockPost::create(context.pool(), &form).await?;
|
ModLockPost::create(context.pool(), &form).await?;
|
||||||
|
|
||||||
send_post_ws_message(
|
context
|
||||||
data.post_id,
|
.send_post_ws_message(
|
||||||
UserOperation::LockPost,
|
&UserOperation::LockPost,
|
||||||
websocket_id,
|
data.post_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,15 +69,12 @@ impl Perform for CreatePostReport {
|
||||||
|
|
||||||
let res = PostReportResponse { post_report_view };
|
let res = PostReportResponse { post_report_view };
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::CreatePostReport,
|
||||||
.send_mod_room_message(
|
&res,
|
||||||
UserOperation::CreatePostReport,
|
post_view.community.id,
|
||||||
&res,
|
websocket_id,
|
||||||
post_view.community.id,
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,15 +46,12 @@ impl Perform for ResolvePostReport {
|
||||||
|
|
||||||
let res = PostReportResponse { post_report_view };
|
let res = PostReportResponse { post_report_view };
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::ResolvePostReport,
|
||||||
.send_mod_room_message(
|
&res,
|
||||||
UserOperation::ResolvePostReport,
|
report.community.id,
|
||||||
&res,
|
websocket_id,
|
||||||
report.community.id,
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
private_message::{MarkPrivateMessageAsRead, PrivateMessageResponse},
|
private_message::{MarkPrivateMessageAsRead, PrivateMessageResponse},
|
||||||
utils::get_local_user_view_from_jwt,
|
utils::get_local_user_view_from_jwt,
|
||||||
websocket::{send::send_pm_ws_message, UserOperation},
|
websocket::UserOperation,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
||||||
|
@ -45,7 +45,12 @@ impl Perform for MarkPrivateMessageAsRead {
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||||
|
|
||||||
// No need to send an apub update
|
// No need to send an apub update
|
||||||
let op = UserOperation::MarkPrivateMessageAsRead;
|
context
|
||||||
send_pm_ws_message(data.private_message_id, op, websocket_id, context).await
|
.send_pm_ws_message(
|
||||||
|
&UserOperation::MarkPrivateMessageAsRead,
|
||||||
|
data.private_message_id,
|
||||||
|
websocket_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,15 +68,12 @@ impl Perform for CreatePrivateMessageReport {
|
||||||
private_message_report_view,
|
private_message_report_view,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::CreatePrivateMessageReport,
|
||||||
.send_mod_room_message(
|
&res,
|
||||||
UserOperation::CreatePrivateMessageReport,
|
CommunityId(0),
|
||||||
&res,
|
websocket_id,
|
||||||
CommunityId(0),
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// TODO: consider federating this
|
// TODO: consider federating this
|
||||||
|
|
||||||
|
|
|
@ -48,15 +48,12 @@ impl Perform for ResolvePrivateMessageReport {
|
||||||
private_message_report_view,
|
private_message_report_view,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::ResolvePrivateMessageReport,
|
||||||
.send_mod_room_message(
|
&res,
|
||||||
UserOperation::ResolvePrivateMessageReport,
|
CommunityId(0),
|
||||||
&res,
|
websocket_id,
|
||||||
CommunityId(0),
|
)?;
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,18 @@ use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::get_local_user_view_from_jwt,
|
utils::get_local_user_view_from_jwt,
|
||||||
websocket::structs::{
|
websocket::{
|
||||||
CommunityJoin,
|
handlers::join_rooms::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
|
||||||
CommunityJoinResponse,
|
structs::{
|
||||||
ModJoin,
|
CommunityJoin,
|
||||||
ModJoinResponse,
|
CommunityJoinResponse,
|
||||||
PostJoin,
|
ModJoin,
|
||||||
PostJoinResponse,
|
ModJoinResponse,
|
||||||
UserJoin,
|
PostJoin,
|
||||||
UserJoinResponse,
|
PostJoinResponse,
|
||||||
|
UserJoin,
|
||||||
|
UserJoinResponse,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
|
@ -30,10 +33,11 @@ impl Perform for UserJoin {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
if let Some(ws_id) = websocket_id {
|
if let Some(id) = websocket_id {
|
||||||
context
|
context.chat_server().do_send(JoinUserRoom {
|
||||||
.chat_server()
|
user_id: local_user_view.local_user.id,
|
||||||
.join_user_room(local_user_view.local_user.id, ws_id)?;
|
id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(UserJoinResponse { joined: true })
|
Ok(UserJoinResponse { joined: true })
|
||||||
|
@ -52,10 +56,11 @@ impl Perform for CommunityJoin {
|
||||||
) -> Result<CommunityJoinResponse, LemmyError> {
|
) -> Result<CommunityJoinResponse, LemmyError> {
|
||||||
let data: &CommunityJoin = self;
|
let data: &CommunityJoin = self;
|
||||||
|
|
||||||
if let Some(ws_id) = websocket_id {
|
if let Some(id) = websocket_id {
|
||||||
context
|
context.chat_server().do_send(JoinCommunityRoom {
|
||||||
.chat_server()
|
community_id: data.community_id,
|
||||||
.join_community_room(data.community_id, ws_id)?;
|
id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CommunityJoinResponse { joined: true })
|
Ok(CommunityJoinResponse { joined: true })
|
||||||
|
@ -74,10 +79,11 @@ impl Perform for ModJoin {
|
||||||
) -> Result<ModJoinResponse, LemmyError> {
|
) -> Result<ModJoinResponse, LemmyError> {
|
||||||
let data: &ModJoin = self;
|
let data: &ModJoin = self;
|
||||||
|
|
||||||
if let Some(ws_id) = websocket_id {
|
if let Some(id) = websocket_id {
|
||||||
context
|
context.chat_server().do_send(JoinModRoom {
|
||||||
.chat_server()
|
community_id: data.community_id,
|
||||||
.join_mod_room(data.community_id, ws_id)?;
|
id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ModJoinResponse { joined: true })
|
Ok(ModJoinResponse { joined: true })
|
||||||
|
@ -96,8 +102,11 @@ impl Perform for PostJoin {
|
||||||
) -> Result<PostJoinResponse, LemmyError> {
|
) -> Result<PostJoinResponse, LemmyError> {
|
||||||
let data: &PostJoin = self;
|
let data: &PostJoin = self;
|
||||||
|
|
||||||
if let Some(ws_id) = websocket_id {
|
if let Some(id) = websocket_id {
|
||||||
context.chat_server().join_post_room(data.post_id, ws_id)?;
|
context.chat_server().do_send(JoinPostRoom {
|
||||||
|
post_id: data.post_id,
|
||||||
|
id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PostJoinResponse { joined: true })
|
Ok(PostJoinResponse { joined: true })
|
||||||
|
|
|
@ -40,7 +40,7 @@ serde_json = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
actix-ws = { workspace = true }
|
actix = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
actix-rt = { workspace = true }
|
actix-rt = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::websocket::chat_server::ChatServer;
|
use crate::websocket::chat_server::ChatServer;
|
||||||
|
use actix::Addr;
|
||||||
use lemmy_db_schema::{source::secret::Secret, utils::DbPool};
|
use lemmy_db_schema::{source::secret::Secret, utils::DbPool};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
rate_limit::RateLimitCell,
|
rate_limit::RateLimitCell,
|
||||||
|
@ -10,7 +11,7 @@ use std::sync::Arc;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LemmyContext {
|
pub struct LemmyContext {
|
||||||
pool: DbPool,
|
pool: DbPool,
|
||||||
chat_server: Arc<ChatServer>,
|
chat_server: Addr<ChatServer>,
|
||||||
client: Arc<ClientWithMiddleware>,
|
client: Arc<ClientWithMiddleware>,
|
||||||
secret: Arc<Secret>,
|
secret: Arc<Secret>,
|
||||||
rate_limit_cell: RateLimitCell,
|
rate_limit_cell: RateLimitCell,
|
||||||
|
@ -19,7 +20,7 @@ pub struct LemmyContext {
|
||||||
impl LemmyContext {
|
impl LemmyContext {
|
||||||
pub fn create(
|
pub fn create(
|
||||||
pool: DbPool,
|
pool: DbPool,
|
||||||
chat_server: Arc<ChatServer>,
|
chat_server: Addr<ChatServer>,
|
||||||
client: ClientWithMiddleware,
|
client: ClientWithMiddleware,
|
||||||
secret: Secret,
|
secret: Secret,
|
||||||
rate_limit_cell: RateLimitCell,
|
rate_limit_cell: RateLimitCell,
|
||||||
|
@ -35,7 +36,7 @@ impl LemmyContext {
|
||||||
pub fn pool(&self) -> &DbPool {
|
pub fn pool(&self) -> &DbPool {
|
||||||
&self.pool
|
&self.pool
|
||||||
}
|
}
|
||||||
pub fn chat_server(&self) -> &Arc<ChatServer> {
|
pub fn chat_server(&self) -> &Addr<ChatServer> {
|
||||||
&self.chat_server
|
&self.chat_server
|
||||||
}
|
}
|
||||||
pub fn client(&self) -> &ClientWithMiddleware {
|
pub fn client(&self) -> &ClientWithMiddleware {
|
||||||
|
|
|
@ -1,30 +1,16 @@
|
||||||
use crate::{
|
use crate::websocket::{
|
||||||
comment::CommentResponse,
|
handlers::{SessionInfo, WsMessage},
|
||||||
post::PostResponse,
|
structs::CaptchaItem,
|
||||||
websocket::{serialize_websocket_message, structs::CaptchaItem, OperationType},
|
|
||||||
};
|
};
|
||||||
use actix_ws::Session;
|
use actix::{Actor, Context};
|
||||||
use anyhow::Context as acontext;
|
|
||||||
use futures::future::join_all;
|
|
||||||
use lemmy_db_schema::newtypes::{CommunityId, LocalUserId, PostId};
|
use lemmy_db_schema::newtypes::{CommunityId, LocalUserId, PostId};
|
||||||
use lemmy_utils::{error::LemmyError, location_info, ConnectionId};
|
use lemmy_utils::ConnectionId;
|
||||||
use rand::{rngs::StdRng, SeedableRng};
|
use rand::{rngs::StdRng, SeedableRng};
|
||||||
use serde::Serialize;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::{
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
sync::{Mutex, MutexGuard},
|
|
||||||
};
|
|
||||||
use tracing::log::warn;
|
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
|
||||||
/// session.
|
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
inner: Mutex<ChatServerInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChatServerInner {
|
|
||||||
/// A map from generated random ID to session addr
|
/// A map from generated random ID to session addr
|
||||||
pub sessions: HashMap<ConnectionId, Session>,
|
pub sessions: HashMap<ConnectionId, SessionInfo>,
|
||||||
|
|
||||||
/// A map from post_id to set of connectionIDs
|
/// A map from post_id to set of connectionIDs
|
||||||
pub post_rooms: HashMap<PostId, HashSet<ConnectionId>>,
|
pub post_rooms: HashMap<PostId, HashSet<ConnectionId>>,
|
||||||
|
@ -48,347 +34,44 @@ pub struct ChatServerInner {
|
||||||
/// And manages available rooms. Peers send messages to other peers in same
|
/// And manages available rooms. Peers send messages to other peers in same
|
||||||
/// room through `ChatServer`.
|
/// room through `ChatServer`.
|
||||||
impl ChatServer {
|
impl ChatServer {
|
||||||
pub fn startup() -> ChatServer {
|
pub fn new() -> ChatServer {
|
||||||
ChatServer {
|
ChatServer {
|
||||||
inner: Mutex::new(ChatServerInner {
|
sessions: Default::default(),
|
||||||
sessions: Default::default(),
|
post_rooms: Default::default(),
|
||||||
post_rooms: Default::default(),
|
community_rooms: Default::default(),
|
||||||
community_rooms: Default::default(),
|
mod_rooms: Default::default(),
|
||||||
mod_rooms: Default::default(),
|
user_rooms: Default::default(),
|
||||||
user_rooms: Default::default(),
|
rng: StdRng::from_entropy(),
|
||||||
rng: StdRng::from_entropy(),
|
captchas: vec![],
|
||||||
captchas: vec![],
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn join_community_room(
|
pub fn send_message(
|
||||||
&self,
|
|
||||||
community_id: CommunityId,
|
|
||||||
id: ConnectionId,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// remove session from all rooms
|
|
||||||
for sessions in inner.community_rooms.values_mut() {
|
|
||||||
sessions.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also leave all post rooms
|
|
||||||
// This avoids double messages
|
|
||||||
for sessions in inner.post_rooms.values_mut() {
|
|
||||||
sessions.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the room doesn't exist yet
|
|
||||||
if inner.community_rooms.get_mut(&community_id).is_none() {
|
|
||||||
inner.community_rooms.insert(community_id, HashSet::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
inner
|
|
||||||
.community_rooms
|
|
||||||
.get_mut(&community_id)
|
|
||||||
.context(location_info!())?
|
|
||||||
.insert(id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn join_mod_room(
|
|
||||||
&self,
|
|
||||||
community_id: CommunityId,
|
|
||||||
id: ConnectionId,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// remove session from all rooms
|
|
||||||
for sessions in inner.mod_rooms.values_mut() {
|
|
||||||
sessions.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the room doesn't exist yet
|
|
||||||
if inner.mod_rooms.get_mut(&community_id).is_none() {
|
|
||||||
inner.mod_rooms.insert(community_id, HashSet::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
inner
|
|
||||||
.mod_rooms
|
|
||||||
.get_mut(&community_id)
|
|
||||||
.context(location_info!())?
|
|
||||||
.insert(id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn join_post_room(&self, post_id: PostId, id: ConnectionId) -> Result<(), LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// remove session from all rooms
|
|
||||||
for sessions in inner.post_rooms.values_mut() {
|
|
||||||
sessions.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also leave all communities
|
|
||||||
// This avoids double messages
|
|
||||||
// TODO found a bug, whereby community messages like
|
|
||||||
// delete and remove aren't sent, because
|
|
||||||
// you left the community room
|
|
||||||
for sessions in inner.community_rooms.values_mut() {
|
|
||||||
sessions.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the room doesn't exist yet
|
|
||||||
if inner.post_rooms.get_mut(&post_id).is_none() {
|
|
||||||
inner.post_rooms.insert(post_id, HashSet::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
inner
|
|
||||||
.post_rooms
|
|
||||||
.get_mut(&post_id)
|
|
||||||
.context(location_info!())?
|
|
||||||
.insert(id);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn join_user_room(&self, user_id: LocalUserId, id: ConnectionId) -> Result<(), LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// remove session from all rooms
|
|
||||||
for sessions in inner.user_rooms.values_mut() {
|
|
||||||
sessions.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the room doesn't exist yet
|
|
||||||
if inner.user_rooms.get_mut(&user_id).is_none() {
|
|
||||||
inner.user_rooms.insert(user_id, HashSet::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
inner
|
|
||||||
.user_rooms
|
|
||||||
.get_mut(&user_id)
|
|
||||||
.context(location_info!())?
|
|
||||||
.insert(id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_post_room_message<OP, Response>(
|
|
||||||
&self,
|
|
||||||
op: &OP,
|
|
||||||
response: &Response,
|
|
||||||
post_id: PostId,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
Response: Serialize,
|
|
||||||
{
|
|
||||||
let msg = serialize_websocket_message(op, response)?;
|
|
||||||
let room = self.inner()?.post_rooms.get(&post_id).cloned();
|
|
||||||
self.send_message_in_room(&msg, room, websocket_id).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to all users viewing the given community.
|
|
||||||
pub async fn send_community_room_message<OP, Response>(
|
|
||||||
&self,
|
|
||||||
op: &OP,
|
|
||||||
response: &Response,
|
|
||||||
community_id: CommunityId,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
Response: Serialize,
|
|
||||||
{
|
|
||||||
let msg = serialize_websocket_message(op, response)?;
|
|
||||||
let room = self.inner()?.community_rooms.get(&community_id).cloned();
|
|
||||||
self.send_message_in_room(&msg, room, websocket_id).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to mods of a given community. Set community_id = 0 to send to site admins.
|
|
||||||
pub async fn send_mod_room_message<OP, Response>(
|
|
||||||
&self,
|
|
||||||
op: OP,
|
|
||||||
response: &Response,
|
|
||||||
community_id: CommunityId,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
Response: Serialize,
|
|
||||||
{
|
|
||||||
let msg = serialize_websocket_message(&op, response)?;
|
|
||||||
let room = self.inner()?.mod_rooms.get(&community_id).cloned();
|
|
||||||
self.send_message_in_room(&msg, room, websocket_id).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_all_message<OP, Response>(
|
|
||||||
&self,
|
|
||||||
op: OP,
|
|
||||||
response: &Response,
|
|
||||||
exclude_connection: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
Response: Serialize,
|
|
||||||
{
|
|
||||||
let msg = &serialize_websocket_message(&op, response)?;
|
|
||||||
let sessions = self.inner()?.sessions.clone();
|
|
||||||
// Note, this will ignore any errors, such as closed connections
|
|
||||||
join_all(
|
|
||||||
sessions
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(id, _)| Some(id) != exclude_connection.as_ref())
|
|
||||||
.map(|(_, mut s): (_, Session)| async move { s.text(msg).await }),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_user_room_message<OP, Response>(
|
|
||||||
&self,
|
|
||||||
op: &OP,
|
|
||||||
response: &Response,
|
|
||||||
recipient_id: LocalUserId,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
Response: Serialize,
|
|
||||||
{
|
|
||||||
let msg = serialize_websocket_message(op, response)?;
|
|
||||||
let room = self.inner()?.user_rooms.get(&recipient_id).cloned();
|
|
||||||
self.send_message_in_room(&msg, room, websocket_id).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_comment<OP>(
|
|
||||||
&self,
|
|
||||||
user_operation: &OP,
|
|
||||||
comment: &CommentResponse,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
{
|
|
||||||
let mut comment_reply_sent = comment.clone();
|
|
||||||
|
|
||||||
// Strip out my specific user info
|
|
||||||
comment_reply_sent.comment_view.my_vote = None;
|
|
||||||
|
|
||||||
// Send it to the post room
|
|
||||||
let mut comment_post_sent = comment_reply_sent.clone();
|
|
||||||
// Remove the recipients here to separate mentions / user messages from post or community comments
|
|
||||||
comment_post_sent.recipient_ids = Vec::new();
|
|
||||||
self
|
|
||||||
.send_post_room_message(
|
|
||||||
user_operation,
|
|
||||||
&comment_post_sent,
|
|
||||||
comment_post_sent.comment_view.post.id,
|
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Send it to the community too
|
|
||||||
self
|
|
||||||
.send_community_room_message(
|
|
||||||
user_operation,
|
|
||||||
&comment_post_sent,
|
|
||||||
CommunityId(0),
|
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
self
|
|
||||||
.send_community_room_message(
|
|
||||||
user_operation,
|
|
||||||
&comment_post_sent,
|
|
||||||
comment.comment_view.community.id,
|
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Send it to the recipient(s) including the mentioned users
|
|
||||||
for recipient_id in &comment_reply_sent.recipient_ids {
|
|
||||||
self
|
|
||||||
.send_user_room_message(
|
|
||||||
user_operation,
|
|
||||||
&comment_reply_sent,
|
|
||||||
*recipient_id,
|
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_post<OP>(
|
|
||||||
&self,
|
|
||||||
user_operation: &OP,
|
|
||||||
post_res: &PostResponse,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<(), LemmyError>
|
|
||||||
where
|
|
||||||
OP: OperationType + ToString,
|
|
||||||
{
|
|
||||||
let community_id = post_res.post_view.community.id;
|
|
||||||
|
|
||||||
// Don't send my data with it
|
|
||||||
let mut post_sent = post_res.clone();
|
|
||||||
post_sent.post_view.my_vote = None;
|
|
||||||
|
|
||||||
// Send it to /c/all and that community
|
|
||||||
self
|
|
||||||
.send_community_room_message(user_operation, &post_sent, CommunityId(0), websocket_id)
|
|
||||||
.await?;
|
|
||||||
self
|
|
||||||
.send_community_room_message(user_operation, &post_sent, community_id, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Send it to the post room
|
|
||||||
self
|
|
||||||
.send_post_room_message(
|
|
||||||
user_operation,
|
|
||||||
&post_sent,
|
|
||||||
post_res.post_view.post.id,
|
|
||||||
websocket_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send websocket message in all sessions which joined a specific room.
|
|
||||||
///
|
|
||||||
/// `message` - The json message body to send
|
|
||||||
/// `room` - Connection IDs which should receive the message
|
|
||||||
/// `exclude_connection` - Dont send to user who initiated the api call, as that
|
|
||||||
/// would result in duplicate notification
|
|
||||||
async fn send_message_in_room(
|
|
||||||
&self,
|
&self,
|
||||||
|
connections: &HashSet<ConnectionId>,
|
||||||
message: &str,
|
message: &str,
|
||||||
room: Option<HashSet<ConnectionId>>,
|
|
||||||
exclude_connection: Option<ConnectionId>,
|
exclude_connection: Option<ConnectionId>,
|
||||||
) -> Result<(), LemmyError> {
|
) {
|
||||||
let mut session = self.inner()?.sessions.clone();
|
for id in connections
|
||||||
if let Some(room) = room {
|
.iter()
|
||||||
// Note, this will ignore any errors, such as closed connections
|
.filter(|c| Some(*c) != exclude_connection.as_ref())
|
||||||
join_all(
|
{
|
||||||
room
|
if let Some(session) = self.sessions.get(id) {
|
||||||
.into_iter()
|
session.addr.do_send(WsMessage(message.to_owned()));
|
||||||
.filter(|c| Some(c) != exclude_connection.as_ref())
|
|
||||||
.filter_map(|c| session.remove(&c))
|
|
||||||
.map(|mut s: Session| async move { s.text(message).await }),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::websocket) fn inner(&self) -> Result<MutexGuard<'_, ChatServerInner>, LemmyError> {
|
|
||||||
match self.inner.lock() {
|
|
||||||
Ok(g) => Ok(g),
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to lock chatserver mutex: {}", e);
|
|
||||||
Err(LemmyError::from_message("Failed to lock chatserver mutex"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ChatServer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make actor from `ChatServer`
|
||||||
|
impl Actor for ChatServer {
|
||||||
|
/// We are going to use simple Context, we just need ability to communicate
|
||||||
|
/// with other actors.
|
||||||
|
type Context = Context<Self>;
|
||||||
|
}
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
use crate::websocket::{chat_server::ChatServer, structs::CaptchaItem};
|
|
||||||
use actix_ws::Session;
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
newtypes::{CommunityId, PostId},
|
|
||||||
utils::naive_now,
|
|
||||||
};
|
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
impl ChatServer {
|
|
||||||
/// Handler for Connect message.
|
|
||||||
///
|
|
||||||
/// Register new session and assign unique id to this session
|
|
||||||
pub fn handle_connect(&self, session: Session) -> Result<ConnectionId, LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// register session with random id
|
|
||||||
let id = inner.rng.gen::<usize>();
|
|
||||||
|
|
||||||
inner.sessions.insert(id, session);
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Disconnect message.
|
|
||||||
pub fn handle_disconnect(&self, connection_id: &ConnectionId) -> Result<(), LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// Remove connections from sessions and all 3 scopes
|
|
||||||
if inner.sessions.remove(connection_id).is_some() {
|
|
||||||
for sessions in inner.user_rooms.values_mut() {
|
|
||||||
sessions.remove(connection_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for sessions in inner.post_rooms.values_mut() {
|
|
||||||
sessions.remove(connection_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for sessions in inner.community_rooms.values_mut() {
|
|
||||||
sessions.remove(connection_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_users_online(&self) -> Result<usize, LemmyError> {
|
|
||||||
Ok(self.inner()?.sessions.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_post_users_online(&self, post_id: PostId) -> Result<usize, LemmyError> {
|
|
||||||
if let Some(users) = self.inner()?.post_rooms.get(&post_id) {
|
|
||||||
Ok(users.len())
|
|
||||||
} else {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_community_users_online(&self, community_id: CommunityId) -> Result<usize, LemmyError> {
|
|
||||||
if let Some(users) = self.inner()?.community_rooms.get(&community_id) {
|
|
||||||
Ok(users.len())
|
|
||||||
} else {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_captcha(&self, captcha: CaptchaItem) -> Result<(), LemmyError> {
|
|
||||||
self.inner()?.captchas.push(captcha);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_captcha(&self, uuid: String, answer: String) -> Result<bool, LemmyError> {
|
|
||||||
let mut inner = self.inner()?;
|
|
||||||
// Remove all the ones that are past the expire time
|
|
||||||
inner.captchas.retain(|x| x.expires.gt(&naive_now()));
|
|
||||||
|
|
||||||
let check = inner
|
|
||||||
.captchas
|
|
||||||
.iter()
|
|
||||||
.any(|r| r.uuid == uuid && r.answer.to_lowercase() == answer.to_lowercase());
|
|
||||||
|
|
||||||
// Remove this uuid so it can't be re-checked (Checks only work once)
|
|
||||||
inner.captchas.retain(|x| x.uuid != uuid);
|
|
||||||
|
|
||||||
Ok(check)
|
|
||||||
}
|
|
||||||
}
|
|
45
crates/api_common/src/websocket/handlers/captcha.rs
Normal file
45
crates/api_common/src/websocket/handlers/captcha.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::websocket::{chat_server::ChatServer, structs::CaptchaItem};
|
||||||
|
use actix::{Context, Handler, Message};
|
||||||
|
use lemmy_db_schema::utils::naive_now;
|
||||||
|
|
||||||
|
/// Adding a Captcha
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct AddCaptcha {
|
||||||
|
pub captcha: CaptchaItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<AddCaptcha> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: AddCaptcha, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
self.captchas.push(msg.captcha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checking a Captcha
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(bool)]
|
||||||
|
pub struct CheckCaptcha {
|
||||||
|
pub uuid: String,
|
||||||
|
pub answer: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CheckCaptcha> for ChatServer {
|
||||||
|
type Result = bool;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CheckCaptcha, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// Remove all the ones that are past the expire time
|
||||||
|
self.captchas.retain(|x| x.expires.gt(&naive_now()));
|
||||||
|
|
||||||
|
let check = self
|
||||||
|
.captchas
|
||||||
|
.iter()
|
||||||
|
.any(|r| r.uuid == msg.uuid && r.answer.to_lowercase() == msg.answer.to_lowercase());
|
||||||
|
|
||||||
|
// Remove this uuid so it can't be re-checked (Checks only work once)
|
||||||
|
self.captchas.retain(|x| x.uuid != msg.uuid);
|
||||||
|
|
||||||
|
check
|
||||||
|
}
|
||||||
|
}
|
62
crates/api_common/src/websocket/handlers/connect.rs
Normal file
62
crates/api_common/src/websocket/handlers/connect.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use crate::websocket::{
|
||||||
|
chat_server::ChatServer,
|
||||||
|
handlers::{SessionInfo, WsMessage},
|
||||||
|
};
|
||||||
|
use actix::{Context, Handler, Message, Recipient};
|
||||||
|
use lemmy_utils::ConnectionId;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
/// New chat session is created
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(ConnectionId)]
|
||||||
|
pub struct Connect {
|
||||||
|
pub addr: Recipient<WsMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for Connect message.
|
||||||
|
///
|
||||||
|
/// Register new session and assign unique id to this session
|
||||||
|
impl Handler<Connect> for ChatServer {
|
||||||
|
type Result = ConnectionId;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// register session with random id
|
||||||
|
let id = self.rng.gen::<usize>();
|
||||||
|
let session = SessionInfo { addr: msg.addr };
|
||||||
|
self.sessions.insert(id, session);
|
||||||
|
|
||||||
|
// send id back
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Session is disconnected
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct Disconnect {
|
||||||
|
pub id: ConnectionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for Disconnect message.
|
||||||
|
impl Handler<Disconnect> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// remove address
|
||||||
|
if self.sessions.remove(&msg.id).is_some() {
|
||||||
|
// remove session from all rooms
|
||||||
|
for sessions in self.user_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
for sessions in self.post_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
for sessions in self.community_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
for sessions in self.mod_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
crates/api_common/src/websocket/handlers/join_rooms.rs
Normal file
120
crates/api_common/src/websocket/handlers/join_rooms.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
use crate::websocket::chat_server::ChatServer;
|
||||||
|
use actix::{Context, Handler, Message};
|
||||||
|
use lemmy_db_schema::newtypes::{CommunityId, LocalUserId, PostId};
|
||||||
|
use lemmy_utils::ConnectionId;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// Joining a Post room
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct JoinPostRoom {
|
||||||
|
pub post_id: PostId,
|
||||||
|
pub id: ConnectionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<JoinPostRoom> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: JoinPostRoom, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// remove session from all rooms
|
||||||
|
for sessions in self.post_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also leave all communities
|
||||||
|
// This avoids double messages
|
||||||
|
// TODO found a bug, whereby community messages like
|
||||||
|
// delete and remove aren't sent, because
|
||||||
|
// you left the community room
|
||||||
|
for sessions in self.community_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
.post_rooms
|
||||||
|
.entry(msg.post_id)
|
||||||
|
.or_insert_with(HashSet::new)
|
||||||
|
.insert(msg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Joining a Community Room
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct JoinCommunityRoom {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub id: ConnectionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<JoinCommunityRoom> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: JoinCommunityRoom, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// remove session from all rooms
|
||||||
|
for sessions in self.community_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also leave all post rooms
|
||||||
|
// This avoids double messages
|
||||||
|
for sessions in self.post_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
.community_rooms
|
||||||
|
.entry(msg.community_id)
|
||||||
|
.or_insert_with(HashSet::new)
|
||||||
|
.insert(msg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Joining a Mod room
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct JoinModRoom {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub id: ConnectionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<JoinModRoom> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: JoinModRoom, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// remove session from all rooms
|
||||||
|
for sessions in self.mod_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
.mod_rooms
|
||||||
|
.entry(msg.community_id)
|
||||||
|
.or_insert_with(HashSet::new)
|
||||||
|
.insert(msg.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Joining a User room
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct JoinUserRoom {
|
||||||
|
pub user_id: LocalUserId,
|
||||||
|
pub id: ConnectionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<JoinUserRoom> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: JoinUserRoom, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// remove session from all rooms
|
||||||
|
for sessions in self.user_rooms.values_mut() {
|
||||||
|
sessions.remove(&msg.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
.user_rooms
|
||||||
|
.entry(msg.user_id)
|
||||||
|
.or_insert_with(HashSet::new)
|
||||||
|
.insert(msg.id);
|
||||||
|
}
|
||||||
|
}
|
130
crates/api_common/src/websocket/handlers/messages.rs
Normal file
130
crates/api_common/src/websocket/handlers/messages.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use crate::websocket::chat_server::ChatServer;
|
||||||
|
use actix::{Context, Handler, Message};
|
||||||
|
use lemmy_db_schema::newtypes::{CommunityId, LocalUserId, PostId};
|
||||||
|
use lemmy_utils::ConnectionId;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// Sending a post room message
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct SendPostRoomMessage {
|
||||||
|
pub post_id: PostId,
|
||||||
|
pub message: String,
|
||||||
|
pub websocket_id: Option<ConnectionId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<SendPostRoomMessage> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: SendPostRoomMessage, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
let room_connections = self.post_rooms.get(&msg.post_id);
|
||||||
|
if let Some(connections) = room_connections {
|
||||||
|
self.send_message(connections, &msg.message, msg.websocket_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sending a community room message
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct SendCommunityRoomMessage {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub message: String,
|
||||||
|
pub websocket_id: Option<ConnectionId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<SendCommunityRoomMessage> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: SendCommunityRoomMessage, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
let room_connections = self.community_rooms.get(&msg.community_id);
|
||||||
|
if let Some(connections) = room_connections {
|
||||||
|
self.send_message(connections, &msg.message, msg.websocket_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sending a mod room message
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct SendModRoomMessage {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub message: String,
|
||||||
|
pub websocket_id: Option<ConnectionId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<SendModRoomMessage> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: SendModRoomMessage, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
let room_connections = self.community_rooms.get(&msg.community_id);
|
||||||
|
if let Some(connections) = room_connections {
|
||||||
|
self.send_message(connections, &msg.message, msg.websocket_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sending a user room message
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct SendUserRoomMessage {
|
||||||
|
pub recipient_id: LocalUserId,
|
||||||
|
pub message: String,
|
||||||
|
pub websocket_id: Option<ConnectionId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<SendUserRoomMessage> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: SendUserRoomMessage, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
let room_connections = self.user_rooms.get(&msg.recipient_id);
|
||||||
|
if let Some(connections) = room_connections {
|
||||||
|
self.send_message(connections, &msg.message, msg.websocket_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sending a message to every session
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct SendAllMessage {
|
||||||
|
pub message: String,
|
||||||
|
pub websocket_id: Option<ConnectionId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<SendAllMessage> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: SendAllMessage, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
let connections: HashSet<ConnectionId> = self.sessions.keys().cloned().collect();
|
||||||
|
self.send_message(&connections, &msg.message, msg.websocket_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///// Send websocket message in all sessions which joined a specific room.
|
||||||
|
/////
|
||||||
|
///// `message` - The json message body to send
|
||||||
|
///// `room` - Connection IDs which should receive the message
|
||||||
|
///// `exclude_connection` - Dont send to user who initiated the api call, as that
|
||||||
|
///// would result in duplicate notification
|
||||||
|
//async fn send_message_in_room(
|
||||||
|
// &self,
|
||||||
|
// message: &str,
|
||||||
|
// room: Option<HashSet<ConnectionId>>,
|
||||||
|
// exclude_connection: Option<ConnectionId>,
|
||||||
|
//) -> Result<(), LemmyError> {
|
||||||
|
// let mut session = self.inner()?.sessions.clone();
|
||||||
|
// if let Some(room) = room {
|
||||||
|
// // Note, this will ignore any errors, such as closed connections
|
||||||
|
// join_all(
|
||||||
|
// room
|
||||||
|
// .into_iter()
|
||||||
|
// .filter(|c| Some(c) != exclude_connection.as_ref())
|
||||||
|
// .filter_map(|c| session.remove(&c))
|
||||||
|
// .map(|mut s: Session| async move { s.text(message).await }),
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Ok(())
|
||||||
|
//}
|
||||||
|
//}
|
18
crates/api_common/src/websocket/handlers/mod.rs
Normal file
18
crates/api_common/src/websocket/handlers/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use actix::{Message, Recipient};
|
||||||
|
|
||||||
|
pub mod captcha;
|
||||||
|
pub mod connect;
|
||||||
|
pub mod join_rooms;
|
||||||
|
pub mod messages;
|
||||||
|
pub mod online_users;
|
||||||
|
|
||||||
|
/// A string message sent to a websocket session
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct WsMessage(pub String);
|
||||||
|
|
||||||
|
// TODO move this?
|
||||||
|
pub struct SessionInfo {
|
||||||
|
pub addr: Recipient<WsMessage>,
|
||||||
|
// pub ip: IpAddr
|
||||||
|
}
|
55
crates/api_common/src/websocket/handlers/online_users.rs
Normal file
55
crates/api_common/src/websocket/handlers/online_users.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::websocket::chat_server::ChatServer;
|
||||||
|
use actix::{Context, Handler, Message};
|
||||||
|
use lemmy_db_schema::newtypes::{CommunityId, PostId};
|
||||||
|
|
||||||
|
/// Getting the number of online connections
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(usize)]
|
||||||
|
pub struct GetUsersOnline;
|
||||||
|
|
||||||
|
/// Handler for Disconnect message.
|
||||||
|
impl Handler<GetUsersOnline> for ChatServer {
|
||||||
|
type Result = usize;
|
||||||
|
|
||||||
|
fn handle(&mut self, _msg: GetUsersOnline, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
self.sessions.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getting the number of post users online
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(usize)]
|
||||||
|
pub struct GetPostUsersOnline {
|
||||||
|
pub post_id: PostId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for Disconnect message.
|
||||||
|
impl Handler<GetPostUsersOnline> for ChatServer {
|
||||||
|
type Result = usize;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: GetPostUsersOnline, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
self
|
||||||
|
.post_rooms
|
||||||
|
.get(&msg.post_id)
|
||||||
|
.map_or(1, std::collections::HashSet::len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getting the number of post users online
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(usize)]
|
||||||
|
pub struct GetCommunityUsersOnline {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for Disconnect message.
|
||||||
|
impl Handler<GetCommunityUsersOnline> for ChatServer {
|
||||||
|
type Result = usize;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: GetCommunityUsersOnline, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
self
|
||||||
|
.community_rooms
|
||||||
|
.get(&msg.community_id)
|
||||||
|
.map_or(1, std::collections::HashSet::len)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use actix::{Message, Recipient};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
@ -6,13 +7,22 @@ pub mod handlers;
|
||||||
pub mod send;
|
pub mod send;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
||||||
|
/// A string message sent to a websocket session
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct WsMessage(pub String);
|
||||||
|
|
||||||
|
pub struct SessionInfo {
|
||||||
|
pub addr: Recipient<WsMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct WebsocketResponse<T> {
|
struct WebsocketResponse<T> {
|
||||||
op: String,
|
op: String,
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_websocket_message<OP, Response>(
|
pub fn serialize_websocket_message<Response, OP>(
|
||||||
op: &OP,
|
op: &OP,
|
||||||
data: &Response,
|
data: &Response,
|
||||||
) -> Result<String, LemmyError>
|
) -> Result<String, LemmyError>
|
||||||
|
@ -133,11 +143,3 @@ pub enum UserOperationApub {
|
||||||
Search,
|
Search,
|
||||||
ResolveObject,
|
ResolveObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait OperationType {}
|
|
||||||
|
|
||||||
impl OperationType for UserOperationCrud {}
|
|
||||||
|
|
||||||
impl OperationType for UserOperation {}
|
|
||||||
|
|
||||||
impl OperationType for UserOperationApub {}
|
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
use super::{
|
||||||
|
handlers::messages::{
|
||||||
|
SendAllMessage,
|
||||||
|
SendCommunityRoomMessage,
|
||||||
|
SendModRoomMessage,
|
||||||
|
SendPostRoomMessage,
|
||||||
|
SendUserRoomMessage,
|
||||||
|
},
|
||||||
|
serialize_websocket_message,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
comment::CommentResponse,
|
comment::CommentResponse,
|
||||||
community::CommunityResponse,
|
community::CommunityResponse,
|
||||||
|
@ -5,7 +15,6 @@ use crate::{
|
||||||
post::PostResponse,
|
post::PostResponse,
|
||||||
private_message::PrivateMessageResponse,
|
private_message::PrivateMessageResponse,
|
||||||
utils::{check_person_block, get_interface_language, send_email_to_user},
|
utils::{check_person_block, get_interface_language, send_email_to_user},
|
||||||
websocket::OperationType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
||||||
|
@ -23,253 +32,374 @@ use lemmy_db_schema::{
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView, PrivateMessageView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView, PrivateMessageView};
|
||||||
use lemmy_db_views_actor::structs::CommunityView;
|
use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::{error::LemmyError, utils::mention::MentionData, ConnectionId};
|
use lemmy_utils::{error::LemmyError, utils::mention::MentionData, ConnectionId};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
impl LemmyContext {
|
||||||
pub async fn send_post_ws_message<OP: ToString + Send + OperationType + 'static>(
|
#[tracing::instrument(skip_all)]
|
||||||
post_id: PostId,
|
pub async fn send_post_ws_message<OP>(
|
||||||
op: OP,
|
&self,
|
||||||
websocket_id: Option<ConnectionId>,
|
op: &OP,
|
||||||
person_id: Option<PersonId>,
|
post_id: PostId,
|
||||||
context: &LemmyContext,
|
websocket_id: Option<ConnectionId>,
|
||||||
) -> Result<PostResponse, LemmyError> {
|
person_id: Option<PersonId>,
|
||||||
let post_view = PostView::read(context.pool(), post_id, person_id, Some(true)).await?;
|
) -> Result<PostResponse, LemmyError>
|
||||||
|
where
|
||||||
let res = PostResponse { post_view };
|
OP: ToString,
|
||||||
|
|
||||||
context
|
|
||||||
.chat_server()
|
|
||||||
.send_post(&op, &res, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: in many call sites in apub crate, we are setting an empty vec for recipient_ids,
|
|
||||||
// we should get the actual recipient actors from somewhere
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn send_comment_ws_message_simple<OP: ToString + Send + OperationType + 'static>(
|
|
||||||
comment_id: CommentId,
|
|
||||||
op: OP,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
|
||||||
send_comment_ws_message(comment_id, op, None, None, None, vec![], context).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn send_comment_ws_message<OP: ToString + Send + OperationType + 'static>(
|
|
||||||
comment_id: CommentId,
|
|
||||||
op: OP,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
form_id: Option<String>,
|
|
||||||
person_id: Option<PersonId>,
|
|
||||||
recipient_ids: Vec<LocalUserId>,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
|
||||||
let view = CommentView::read(context.pool(), comment_id, person_id).await?;
|
|
||||||
|
|
||||||
let mut res = CommentResponse {
|
|
||||||
comment_view: view,
|
|
||||||
recipient_ids,
|
|
||||||
// The sent out form id should be null
|
|
||||||
form_id: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
context
|
|
||||||
.chat_server()
|
|
||||||
.send_comment(&op, &res, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// The recipient_ids should be empty for returns
|
|
||||||
res.recipient_ids = Vec::new();
|
|
||||||
res.form_id = form_id;
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn send_community_ws_message<OP: ToString + Send + OperationType + 'static>(
|
|
||||||
community_id: CommunityId,
|
|
||||||
op: OP,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
person_id: Option<PersonId>,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<CommunityResponse, LemmyError> {
|
|
||||||
let community_view =
|
|
||||||
CommunityView::read(context.pool(), community_id, person_id, Some(true)).await?;
|
|
||||||
let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?;
|
|
||||||
|
|
||||||
let mut res = CommunityResponse {
|
|
||||||
community_view,
|
|
||||||
discussion_languages,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Strip out the person id and subscribed when sending to others
|
|
||||||
res.community_view.subscribed = SubscribedType::NotSubscribed;
|
|
||||||
|
|
||||||
context
|
|
||||||
.chat_server()
|
|
||||||
.send_community_room_message(&op, &res, res.community_view.community.id, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn send_pm_ws_message<OP: ToString + Send + OperationType + 'static>(
|
|
||||||
private_message_id: PrivateMessageId,
|
|
||||||
op: OP,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<PrivateMessageResponse, LemmyError> {
|
|
||||||
let view = PrivateMessageView::read(context.pool(), private_message_id).await?;
|
|
||||||
|
|
||||||
let res = PrivateMessageResponse {
|
|
||||||
private_message_view: view,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send notifications to the local recipient, if one exists
|
|
||||||
if res.private_message_view.recipient.local {
|
|
||||||
let recipient_id = res.private_message_view.recipient.id;
|
|
||||||
let local_recipient = LocalUserView::read_person(context.pool(), recipient_id).await?;
|
|
||||||
|
|
||||||
context
|
|
||||||
.chat_server()
|
|
||||||
.send_user_room_message(&op, &res, local_recipient.local_user.id, websocket_id)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn send_local_notifs(
|
|
||||||
mentions: Vec<MentionData>,
|
|
||||||
comment: &Comment,
|
|
||||||
person: &Person,
|
|
||||||
post: &Post,
|
|
||||||
do_send_email: bool,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<Vec<LocalUserId>, LemmyError> {
|
|
||||||
let mut recipient_ids = Vec::new();
|
|
||||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
|
||||||
|
|
||||||
// Send the local mentions
|
|
||||||
for mention in mentions
|
|
||||||
.iter()
|
|
||||||
.filter(|m| m.is_local(&context.settings().hostname) && m.name.ne(&person.name))
|
|
||||||
.collect::<Vec<&MentionData>>()
|
|
||||||
{
|
{
|
||||||
let mention_name = mention.name.clone();
|
let post_view = PostView::read(self.pool(), post_id, person_id, Some(true)).await?;
|
||||||
let user_view = LocalUserView::read_from_name(context.pool(), &mention_name).await;
|
|
||||||
if let Ok(mention_user_view) = user_view {
|
|
||||||
// TODO
|
|
||||||
// At some point, make it so you can't tag the parent creator either
|
|
||||||
// This can cause two notifications, one for reply and the other for mention
|
|
||||||
recipient_ids.push(mention_user_view.local_user.id);
|
|
||||||
|
|
||||||
let user_mention_form = PersonMentionInsertForm {
|
let res = PostResponse { post_view };
|
||||||
recipient_id: mention_user_view.person.id,
|
|
||||||
comment_id: comment.id,
|
|
||||||
read: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
// Send it to the post room
|
||||||
// Let the uniqueness handle this fail
|
// Don't send my data with it
|
||||||
PersonMention::create(context.pool(), &user_mention_form)
|
let mut post_sent = res.clone();
|
||||||
|
post_sent.post_view.my_vote = None;
|
||||||
|
let message = serialize_websocket_message(op, &post_sent)?;
|
||||||
|
|
||||||
|
self.chat_server().do_send(SendPostRoomMessage {
|
||||||
|
post_id,
|
||||||
|
message: message.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send it to /c/all and that community
|
||||||
|
self.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
community_id: CommunityId(0),
|
||||||
|
message: message.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
community_id: post_sent.post_view.community.id,
|
||||||
|
message,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: in many call sites in apub crate, we are setting an empty vec for recipient_ids,
|
||||||
|
// we should get the actual recipient actors from somewhere
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn send_comment_ws_message_simple<OP>(
|
||||||
|
&self,
|
||||||
|
op: &OP,
|
||||||
|
comment_id: CommentId,
|
||||||
|
) -> Result<CommentResponse, LemmyError>
|
||||||
|
where
|
||||||
|
OP: ToString,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
.send_comment_ws_message(op, comment_id, None, None, None, vec![])
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn send_comment_ws_message<OP>(
|
||||||
|
&self,
|
||||||
|
op: &OP,
|
||||||
|
comment_id: CommentId,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
form_id: Option<String>,
|
||||||
|
person_id: Option<PersonId>,
|
||||||
|
recipient_ids: Vec<LocalUserId>,
|
||||||
|
) -> Result<CommentResponse, LemmyError>
|
||||||
|
where
|
||||||
|
OP: ToString,
|
||||||
|
{
|
||||||
|
let view = CommentView::read(self.pool(), comment_id, person_id).await?;
|
||||||
|
|
||||||
|
let mut res = CommentResponse {
|
||||||
|
comment_view: view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Strip out my specific user info
|
||||||
|
let mut sent_recipient_comment = res.clone();
|
||||||
|
sent_recipient_comment.form_id = None;
|
||||||
|
sent_recipient_comment.comment_view.my_vote = None;
|
||||||
|
let recipient_message = serialize_websocket_message(op, &sent_recipient_comment)?;
|
||||||
|
|
||||||
|
// Send it to the recipient(s) including the mentioned users
|
||||||
|
for recipient_id in &sent_recipient_comment.recipient_ids {
|
||||||
|
self.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
recipient_id: *recipient_id,
|
||||||
|
message: recipient_message.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the recipients here to separate mentions / user messages from post or community comments
|
||||||
|
let mut sent_post_comment = sent_recipient_comment;
|
||||||
|
sent_post_comment.recipient_ids = Vec::new();
|
||||||
|
let post_message = serialize_websocket_message(op, &sent_post_comment)?;
|
||||||
|
|
||||||
|
// Send it to the post room
|
||||||
|
self.chat_server().do_send(SendPostRoomMessage {
|
||||||
|
post_id: sent_post_comment.comment_view.post.id,
|
||||||
|
message: post_message.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send it to the community too
|
||||||
|
self.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
community_id: sent_post_comment.comment_view.community.id,
|
||||||
|
message: post_message,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
// TODO should I send it to all? Seems excessive
|
||||||
|
// self
|
||||||
|
// .send_community_room_message(
|
||||||
|
// user_operation,
|
||||||
|
// &comment_post_sent,
|
||||||
|
// CommunityId(0),
|
||||||
|
// websocket_id,
|
||||||
|
// )
|
||||||
|
// .await?;
|
||||||
|
|
||||||
|
// No need to return recipients
|
||||||
|
res.recipient_ids = Vec::new();
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn send_community_ws_message<OP>(
|
||||||
|
&self,
|
||||||
|
op: &OP,
|
||||||
|
community_id: CommunityId,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
person_id: Option<PersonId>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError>
|
||||||
|
where
|
||||||
|
OP: ToString,
|
||||||
|
{
|
||||||
|
let community_view =
|
||||||
|
CommunityView::read(self.pool(), community_id, person_id, Some(true)).await?;
|
||||||
|
let discussion_languages = CommunityLanguage::read(self.pool(), community_id).await?;
|
||||||
|
|
||||||
|
let mut res = CommunityResponse {
|
||||||
|
community_view,
|
||||||
|
discussion_languages,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Strip out the person id and subscribed when sending to others
|
||||||
|
res.community_view.subscribed = SubscribedType::NotSubscribed;
|
||||||
|
let message = serialize_websocket_message(op, &res)?;
|
||||||
|
|
||||||
|
self.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
community_id: res.community_view.community.id,
|
||||||
|
message,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn send_pm_ws_message<OP>(
|
||||||
|
&self,
|
||||||
|
op: &OP,
|
||||||
|
private_message_id: PrivateMessageId,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PrivateMessageResponse, LemmyError>
|
||||||
|
where
|
||||||
|
OP: ToString,
|
||||||
|
{
|
||||||
|
let view = PrivateMessageView::read(self.pool(), private_message_id).await?;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
private_message_view: view,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notifications to the local recipient, if one exists
|
||||||
|
if res.private_message_view.recipient.local {
|
||||||
|
let recipient_id = res.private_message_view.recipient.id;
|
||||||
|
let local_recipient = LocalUserView::read_person(self.pool(), recipient_id).await?;
|
||||||
|
|
||||||
|
let message = serialize_websocket_message(op, &res)?;
|
||||||
|
|
||||||
|
self.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
recipient_id: local_recipient.local_user.id,
|
||||||
|
message,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn send_local_notifs(
|
||||||
|
&self,
|
||||||
|
mentions: Vec<MentionData>,
|
||||||
|
comment: &Comment,
|
||||||
|
person: &Person,
|
||||||
|
post: &Post,
|
||||||
|
do_send_email: bool,
|
||||||
|
) -> Result<Vec<LocalUserId>, LemmyError> {
|
||||||
|
let mut recipient_ids = Vec::new();
|
||||||
|
let inbox_link = format!("{}/inbox", self.settings().get_protocol_and_hostname());
|
||||||
|
|
||||||
|
// Send the local mentions
|
||||||
|
for mention in mentions
|
||||||
|
.iter()
|
||||||
|
.filter(|m| m.is_local(&self.settings().hostname) && m.name.ne(&person.name))
|
||||||
|
.collect::<Vec<&MentionData>>()
|
||||||
|
{
|
||||||
|
let mention_name = mention.name.clone();
|
||||||
|
let user_view = LocalUserView::read_from_name(self.pool(), &mention_name).await;
|
||||||
|
if let Ok(mention_user_view) = user_view {
|
||||||
|
// TODO
|
||||||
|
// At some point, make it so you can't tag the parent creator either
|
||||||
|
// This can cause two notifications, one for reply and the other for mention
|
||||||
|
recipient_ids.push(mention_user_view.local_user.id);
|
||||||
|
|
||||||
|
let user_mention_form = PersonMentionInsertForm {
|
||||||
|
recipient_id: mention_user_view.person.id,
|
||||||
|
comment_id: comment.id,
|
||||||
|
read: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||||
|
// Let the uniqueness handle this fail
|
||||||
|
PersonMention::create(self.pool(), &user_mention_form)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
// Send an email to those local users that have notifications on
|
||||||
|
if do_send_email {
|
||||||
|
let lang = get_interface_language(&mention_user_view);
|
||||||
|
send_email_to_user(
|
||||||
|
&mention_user_view,
|
||||||
|
&lang.notification_mentioned_by_subject(&person.name),
|
||||||
|
&lang.notification_mentioned_by_body(&comment.content, &inbox_link, &person.name),
|
||||||
|
self.settings(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send comment_reply to the parent commenter / poster
|
||||||
|
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||||
|
let parent_comment = Comment::read(self.pool(), parent_comment_id).await?;
|
||||||
|
|
||||||
|
// Get the parent commenter local_user
|
||||||
|
let parent_creator_id = parent_comment.creator_id;
|
||||||
|
|
||||||
|
// Only add to recipients if that person isn't blocked
|
||||||
|
let creator_blocked = check_person_block(person.id, parent_creator_id, self.pool())
|
||||||
.await
|
.await
|
||||||
.ok();
|
.is_err();
|
||||||
|
|
||||||
// Send an email to those local users that have notifications on
|
// Don't send a notif to yourself
|
||||||
if do_send_email {
|
if parent_comment.creator_id != person.id && !creator_blocked {
|
||||||
let lang = get_interface_language(&mention_user_view);
|
let user_view = LocalUserView::read_person(self.pool(), parent_creator_id).await;
|
||||||
send_email_to_user(
|
if let Ok(parent_user_view) = user_view {
|
||||||
&mention_user_view,
|
recipient_ids.push(parent_user_view.local_user.id);
|
||||||
&lang.notification_mentioned_by_subject(&person.name),
|
|
||||||
&lang.notification_mentioned_by_body(&comment.content, &inbox_link, &person.name),
|
let comment_reply_form = CommentReplyInsertForm {
|
||||||
context.settings(),
|
recipient_id: parent_user_view.person.id,
|
||||||
)
|
comment_id: comment.id,
|
||||||
|
read: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||||
|
// Let the uniqueness handle this fail
|
||||||
|
CommentReply::create(self.pool(), &comment_reply_form)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
if do_send_email {
|
||||||
|
let lang = get_interface_language(&parent_user_view);
|
||||||
|
send_email_to_user(
|
||||||
|
&parent_user_view,
|
||||||
|
&lang.notification_comment_reply_subject(&person.name),
|
||||||
|
&lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
|
||||||
|
self.settings(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
// If there's no parent, its the post creator
|
||||||
|
// Only add to recipients if that person isn't blocked
|
||||||
|
let creator_blocked = check_person_block(person.id, post.creator_id, self.pool())
|
||||||
|
.await
|
||||||
|
.is_err();
|
||||||
|
|
||||||
// Send comment_reply to the parent commenter / poster
|
if post.creator_id != person.id && !creator_blocked {
|
||||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
let creator_id = post.creator_id;
|
||||||
let parent_comment = Comment::read(context.pool(), parent_comment_id).await?;
|
let parent_user = LocalUserView::read_person(self.pool(), creator_id).await;
|
||||||
|
if let Ok(parent_user_view) = parent_user {
|
||||||
|
recipient_ids.push(parent_user_view.local_user.id);
|
||||||
|
|
||||||
// Get the parent commenter local_user
|
let comment_reply_form = CommentReplyInsertForm {
|
||||||
let parent_creator_id = parent_comment.creator_id;
|
recipient_id: parent_user_view.person.id,
|
||||||
|
comment_id: comment.id,
|
||||||
|
read: None,
|
||||||
|
};
|
||||||
|
|
||||||
// Only add to recipients if that person isn't blocked
|
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||||
let creator_blocked = check_person_block(person.id, parent_creator_id, context.pool())
|
// Let the uniqueness handle this fail
|
||||||
.await
|
CommentReply::create(self.pool(), &comment_reply_form)
|
||||||
.is_err();
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Don't send a notif to yourself
|
if do_send_email {
|
||||||
if parent_comment.creator_id != person.id && !creator_blocked {
|
let lang = get_interface_language(&parent_user_view);
|
||||||
let user_view = LocalUserView::read_person(context.pool(), parent_creator_id).await;
|
send_email_to_user(
|
||||||
if let Ok(parent_user_view) = user_view {
|
&parent_user_view,
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
&lang.notification_post_reply_subject(&person.name),
|
||||||
|
&lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
|
||||||
let comment_reply_form = CommentReplyInsertForm {
|
self.settings(),
|
||||||
recipient_id: parent_user_view.person.id,
|
)
|
||||||
comment_id: comment.id,
|
}
|
||||||
read: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
|
||||||
// Let the uniqueness handle this fail
|
|
||||||
CommentReply::create(context.pool(), &comment_reply_form)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if do_send_email {
|
|
||||||
let lang = get_interface_language(&parent_user_view);
|
|
||||||
send_email_to_user(
|
|
||||||
&parent_user_view,
|
|
||||||
&lang.notification_comment_reply_subject(&person.name),
|
|
||||||
&lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
|
|
||||||
context.settings(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// If there's no parent, its the post creator
|
|
||||||
// Only add to recipients if that person isn't blocked
|
|
||||||
let creator_blocked = check_person_block(person.id, post.creator_id, context.pool())
|
|
||||||
.await
|
|
||||||
.is_err();
|
|
||||||
|
|
||||||
if post.creator_id != person.id && !creator_blocked {
|
Ok(recipient_ids)
|
||||||
let creator_id = post.creator_id;
|
|
||||||
let parent_user = LocalUserView::read_person(context.pool(), creator_id).await;
|
|
||||||
if let Ok(parent_user_view) = parent_user {
|
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
|
||||||
|
|
||||||
let comment_reply_form = CommentReplyInsertForm {
|
|
||||||
recipient_id: parent_user_view.person.id,
|
|
||||||
comment_id: comment.id,
|
|
||||||
read: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
|
||||||
// Let the uniqueness handle this fail
|
|
||||||
CommentReply::create(context.pool(), &comment_reply_form)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if do_send_email {
|
|
||||||
let lang = get_interface_language(&parent_user_view);
|
|
||||||
send_email_to_user(
|
|
||||||
&parent_user_view,
|
|
||||||
&lang.notification_post_reply_subject(&person.name),
|
|
||||||
&lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
|
|
||||||
context.settings(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(recipient_ids)
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn send_all_ws_message<Data, OP>(
|
||||||
|
&self,
|
||||||
|
op: &OP,
|
||||||
|
data: Data,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<(), LemmyError>
|
||||||
|
where
|
||||||
|
Data: Serialize,
|
||||||
|
OP: ToString,
|
||||||
|
{
|
||||||
|
let message = serialize_websocket_message(op, &data)?;
|
||||||
|
self.chat_server().do_send(SendAllMessage {
|
||||||
|
message,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn send_mod_ws_message<Data, OP>(
|
||||||
|
&self,
|
||||||
|
op: &OP,
|
||||||
|
data: Data,
|
||||||
|
community_id: CommunityId,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<(), LemmyError>
|
||||||
|
where
|
||||||
|
Data: Serialize,
|
||||||
|
OP: ToString,
|
||||||
|
{
|
||||||
|
let message = serialize_websocket_message(op, &data)?;
|
||||||
|
self.chat_server().do_send(SendModRoomMessage {
|
||||||
|
community_id,
|
||||||
|
message,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,7 @@ use lemmy_api_common::{
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
websocket::{
|
websocket::UserOperationCrud,
|
||||||
send::{send_comment_ws_message, send_local_notifs},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -131,15 +128,15 @@ impl PerformCrud for CreateComment {
|
||||||
// Scan the comment for user mentions, add those rows
|
// Scan the comment for user mentions, add those rows
|
||||||
let post_id = post.id;
|
let post_id = post.id;
|
||||||
let mentions = scrape_text_for_mentions(&content_slurs_removed);
|
let mentions = scrape_text_for_mentions(&content_slurs_removed);
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = context
|
||||||
mentions,
|
.send_local_notifs(
|
||||||
&updated_comment,
|
mentions,
|
||||||
&local_user_view.person,
|
&updated_comment,
|
||||||
&post,
|
&local_user_view.person,
|
||||||
true,
|
&post,
|
||||||
context,
|
true,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// You like your own comment by default
|
// You like your own comment by default
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
|
@ -182,15 +179,15 @@ impl PerformCrud for CreateComment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_comment_ws_message(
|
context
|
||||||
inserted_comment.id,
|
.send_comment_ws_message(
|
||||||
UserOperationCrud::CreateComment,
|
&UserOperationCrud::CreateComment,
|
||||||
websocket_id,
|
inserted_comment.id,
|
||||||
data.form_id.clone(),
|
websocket_id,
|
||||||
Some(local_user_view.person.id),
|
data.form_id.clone(),
|
||||||
recipient_ids,
|
Some(local_user_view.person.id),
|
||||||
context,
|
recipient_ids,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, DeleteComment},
|
comment::{CommentResponse, DeleteComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_community_ban, get_local_user_view_from_jwt},
|
utils::{check_community_ban, get_local_user_view_from_jwt},
|
||||||
websocket::{
|
websocket::UserOperationCrud,
|
||||||
send::{send_comment_ws_message, send_local_notifs},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -65,26 +62,26 @@ impl PerformCrud for DeleteComment {
|
||||||
|
|
||||||
let post_id = updated_comment.post_id;
|
let post_id = updated_comment.post_id;
|
||||||
let post = Post::read(context.pool(), post_id).await?;
|
let post = Post::read(context.pool(), post_id).await?;
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = context
|
||||||
vec![],
|
.send_local_notifs(
|
||||||
&updated_comment,
|
vec![],
|
||||||
&local_user_view.person,
|
&updated_comment,
|
||||||
&post,
|
&local_user_view.person,
|
||||||
false,
|
&post,
|
||||||
context,
|
false,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let res = send_comment_ws_message(
|
let res = context
|
||||||
data.comment_id,
|
.send_comment_ws_message(
|
||||||
UserOperationCrud::DeleteComment,
|
&UserOperationCrud::DeleteComment,
|
||||||
websocket_id,
|
data.comment_id,
|
||||||
None, // TODO a comment delete might clear forms?
|
websocket_id,
|
||||||
Some(local_user_view.person.id),
|
None,
|
||||||
recipient_ids,
|
Some(local_user_view.person.id),
|
||||||
context,
|
recipient_ids,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, RemoveComment},
|
comment::{CommentResponse, RemoveComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_community_ban, get_local_user_view_from_jwt, is_mod_or_admin},
|
utils::{check_community_ban, get_local_user_view_from_jwt, is_mod_or_admin},
|
||||||
websocket::{
|
websocket::UserOperationCrud,
|
||||||
send::{send_comment_ws_message, send_local_notifs},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -73,26 +70,26 @@ impl PerformCrud for RemoveComment {
|
||||||
|
|
||||||
let post_id = updated_comment.post_id;
|
let post_id = updated_comment.post_id;
|
||||||
let post = Post::read(context.pool(), post_id).await?;
|
let post = Post::read(context.pool(), post_id).await?;
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = context
|
||||||
vec![],
|
.send_local_notifs(
|
||||||
&updated_comment,
|
vec![],
|
||||||
&local_user_view.person.clone(),
|
&updated_comment,
|
||||||
&post,
|
&local_user_view.person.clone(),
|
||||||
false,
|
&post,
|
||||||
context,
|
false,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let res = send_comment_ws_message(
|
let res = context
|
||||||
data.comment_id,
|
.send_comment_ws_message(
|
||||||
UserOperationCrud::RemoveComment,
|
&UserOperationCrud::RemoveComment,
|
||||||
websocket_id,
|
data.comment_id,
|
||||||
None, // TODO maybe this might clear other forms
|
websocket_id,
|
||||||
Some(local_user_view.person.id),
|
None, // TODO maybe this might clear other forms
|
||||||
recipient_ids,
|
Some(local_user_view.person.id),
|
||||||
context,
|
recipient_ids,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, EditComment},
|
comment::{CommentResponse, EditComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_community_ban, get_local_user_view_from_jwt, local_site_to_slur_regex},
|
utils::{check_community_ban, get_local_user_view_from_jwt, local_site_to_slur_regex},
|
||||||
websocket::{
|
websocket::UserOperationCrud,
|
||||||
send::{send_comment_ws_message, send_local_notifs},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -81,25 +78,25 @@ impl PerformCrud for EditComment {
|
||||||
// Do the mentions / recipients
|
// Do the mentions / recipients
|
||||||
let updated_comment_content = updated_comment.content.clone();
|
let updated_comment_content = updated_comment.content.clone();
|
||||||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = context
|
||||||
mentions,
|
.send_local_notifs(
|
||||||
&updated_comment,
|
mentions,
|
||||||
&local_user_view.person,
|
&updated_comment,
|
||||||
&orig_comment.post,
|
&local_user_view.person,
|
||||||
false,
|
&orig_comment.post,
|
||||||
context,
|
false,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
send_comment_ws_message(
|
context
|
||||||
data.comment_id,
|
.send_comment_ws_message(
|
||||||
UserOperationCrud::EditComment,
|
&UserOperationCrud::EditComment,
|
||||||
websocket_id,
|
data.comment_id,
|
||||||
data.form_id.clone(),
|
websocket_id,
|
||||||
None,
|
data.form_id.clone(),
|
||||||
recipient_ids,
|
None,
|
||||||
context,
|
recipient_ids,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, DeleteCommunity},
|
community::{CommunityResponse, DeleteCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{get_local_user_view_from_jwt, is_top_mod},
|
utils::{get_local_user_view_from_jwt, is_top_mod},
|
||||||
websocket::{send::send_community_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::community::{Community, CommunityUpdateForm},
|
source::community::{Community, CommunityUpdateForm},
|
||||||
|
@ -48,14 +48,14 @@ impl PerformCrud for DeleteCommunity {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?;
|
||||||
|
|
||||||
let res = send_community_ws_message(
|
let res = context
|
||||||
data.community_id,
|
.send_community_ws_message(
|
||||||
UserOperationCrud::DeleteCommunity,
|
&UserOperationCrud::DeleteCommunity,
|
||||||
websocket_id,
|
data.community_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, RemoveCommunity},
|
community::{CommunityResponse, RemoveCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{get_local_user_view_from_jwt, is_admin},
|
utils::{get_local_user_view_from_jwt, is_admin},
|
||||||
websocket::{send::send_community_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -56,14 +56,14 @@ impl PerformCrud for RemoveCommunity {
|
||||||
};
|
};
|
||||||
ModRemoveCommunity::create(context.pool(), &form).await?;
|
ModRemoveCommunity::create(context.pool(), &form).await?;
|
||||||
|
|
||||||
let res = send_community_ws_message(
|
let res = context
|
||||||
data.community_id,
|
.send_community_ws_message(
|
||||||
UserOperationCrud::RemoveCommunity,
|
&UserOperationCrud::RemoveCommunity,
|
||||||
websocket_id,
|
data.community_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, EditCommunity},
|
community::{CommunityResponse, EditCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{get_local_user_view_from_jwt, local_site_to_slur_regex},
|
utils::{get_local_user_view_from_jwt, local_site_to_slur_regex},
|
||||||
websocket::{send::send_community_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PersonId,
|
newtypes::PersonId,
|
||||||
|
@ -78,7 +78,13 @@ impl PerformCrud for EditCommunity {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?;
|
||||||
|
|
||||||
let op = UserOperationCrud::EditCommunity;
|
context
|
||||||
send_community_ws_message(data.community_id, op, websocket_id, None, context).await
|
.send_community_ws_message(
|
||||||
|
&UserOperationCrud::EditCommunity,
|
||||||
|
data.community_id,
|
||||||
|
websocket_id,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use lemmy_api_common::{
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::default_post_language,
|
impls::actor_language::default_post_language,
|
||||||
|
@ -173,13 +173,13 @@ impl PerformCrud for CreatePost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_post_ws_message(
|
context
|
||||||
inserted_post.id,
|
.send_post_ws_message(
|
||||||
UserOperationCrud::CreatePost,
|
&UserOperationCrud::CreatePost,
|
||||||
websocket_id,
|
inserted_post.id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{DeletePost, PostResponse},
|
post::{DeletePost, PostResponse},
|
||||||
utils::{check_community_ban, check_community_deleted_or_removed, get_local_user_view_from_jwt},
|
utils::{check_community_ban, check_community_deleted_or_removed, get_local_user_view_from_jwt},
|
||||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::post::{Post, PostUpdateForm},
|
source::post::{Post, PostUpdateForm},
|
||||||
|
@ -57,14 +57,14 @@ impl PerformCrud for DeletePost {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let res = send_post_ws_message(
|
let res = context
|
||||||
data.post_id,
|
.send_post_ws_message(
|
||||||
UserOperationCrud::DeletePost,
|
&UserOperationCrud::DeletePost,
|
||||||
websocket_id,
|
data.post_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_api_common::{
|
||||||
is_mod_or_admin_opt,
|
is_mod_or_admin_opt,
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
},
|
},
|
||||||
|
websocket::handlers::online_users::GetPostUsersOnline,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||||
|
@ -95,7 +96,10 @@ impl PerformCrud for GetPost {
|
||||||
|
|
||||||
let moderators = CommunityModeratorView::for_community(context.pool(), community_id).await?;
|
let moderators = CommunityModeratorView::for_community(context.pool(), community_id).await?;
|
||||||
|
|
||||||
let online = context.chat_server().get_post_users_online(post_id)?;
|
let online = context
|
||||||
|
.chat_server()
|
||||||
|
.send(GetPostUsersOnline { post_id })
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(GetPostResponse {
|
Ok(GetPostResponse {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{PostResponse, RemovePost},
|
post::{PostResponse, RemovePost},
|
||||||
utils::{check_community_ban, get_local_user_view_from_jwt, is_mod_or_admin},
|
utils::{check_community_ban, get_local_user_view_from_jwt, is_mod_or_admin},
|
||||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -66,14 +66,14 @@ impl PerformCrud for RemovePost {
|
||||||
};
|
};
|
||||||
ModRemovePost::create(context.pool(), &form).await?;
|
ModRemovePost::create(context.pool(), &form).await?;
|
||||||
|
|
||||||
let res = send_post_ws_message(
|
let res = context
|
||||||
data.post_id,
|
.send_post_ws_message(
|
||||||
UserOperationCrud::RemovePost,
|
&UserOperationCrud::RemovePost,
|
||||||
websocket_id,
|
data.post_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
post::{EditPost, PostResponse},
|
post::{EditPost, PostResponse},
|
||||||
request::fetch_site_data,
|
request::fetch_site_data,
|
||||||
utils::{check_community_ban, get_local_user_view_from_jwt, local_site_to_slur_regex},
|
utils::{check_community_ban, get_local_user_view_from_jwt, local_site_to_slur_regex},
|
||||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -113,13 +113,13 @@ impl PerformCrud for EditPost {
|
||||||
return Err(LemmyError::from_error_message(e, err_type));
|
return Err(LemmyError::from_error_message(e, err_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
send_post_ws_message(
|
context
|
||||||
data.post_id,
|
.send_post_ws_message(
|
||||||
UserOperationCrud::EditPost,
|
&UserOperationCrud::EditPost,
|
||||||
websocket_id,
|
data.post_id,
|
||||||
Some(local_user_view.person.id),
|
websocket_id,
|
||||||
context,
|
Some(local_user_view.person.id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use lemmy_api_common::{
|
||||||
send_email_to_user,
|
send_email_to_user,
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
websocket::{send::send_pm_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -80,13 +80,13 @@ impl PerformCrud for CreatePrivateMessage {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_private_message"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_private_message"))?;
|
||||||
|
|
||||||
let res = send_pm_ws_message(
|
let res = context
|
||||||
inserted_private_message.id,
|
.send_pm_ws_message(
|
||||||
UserOperationCrud::CreatePrivateMessage,
|
&UserOperationCrud::CreatePrivateMessage,
|
||||||
websocket_id,
|
inserted_private_message.id,
|
||||||
context,
|
websocket_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Send email to the local recipient, if one exists
|
// Send email to the local recipient, if one exists
|
||||||
if res.private_message_view.recipient.local {
|
if res.private_message_view.recipient.local {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
private_message::{DeletePrivateMessage, PrivateMessageResponse},
|
private_message::{DeletePrivateMessage, PrivateMessageResponse},
|
||||||
utils::get_local_user_view_from_jwt,
|
utils::get_local_user_view_from_jwt,
|
||||||
websocket::{send::send_pm_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
||||||
|
@ -46,7 +46,12 @@ impl PerformCrud for DeletePrivateMessage {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||||
|
|
||||||
let op = UserOperationCrud::DeletePrivateMessage;
|
context
|
||||||
send_pm_ws_message(data.private_message_id, op, websocket_id, context).await
|
.send_pm_ws_message(
|
||||||
|
&UserOperationCrud::DeletePrivateMessage,
|
||||||
|
data.private_message_id,
|
||||||
|
websocket_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
private_message::{EditPrivateMessage, PrivateMessageResponse},
|
private_message::{EditPrivateMessage, PrivateMessageResponse},
|
||||||
utils::{get_local_user_view_from_jwt, local_site_to_slur_regex},
|
utils::{get_local_user_view_from_jwt, local_site_to_slur_regex},
|
||||||
websocket::{send::send_pm_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -52,7 +52,12 @@ impl PerformCrud for EditPrivateMessage {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||||
|
|
||||||
let op = UserOperationCrud::EditPrivateMessage;
|
context
|
||||||
send_pm_ws_message(data.private_message_id, op, websocket_id, context).await
|
.send_pm_ws_message(
|
||||||
|
&UserOperationCrud::EditPrivateMessage,
|
||||||
|
data.private_message_id,
|
||||||
|
websocket_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{GetSite, GetSiteResponse, MyUserInfo},
|
site::{GetSite, GetSiteResponse, MyUserInfo},
|
||||||
utils::{build_federated_instances, get_local_user_settings_view_from_jwt_opt},
|
utils::{build_federated_instances, get_local_user_settings_view_from_jwt_opt},
|
||||||
|
websocket::handlers::online_users::GetUsersOnline,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
actor_language::{LocalUserLanguage, SiteLanguage},
|
actor_language::{LocalUserLanguage, SiteLanguage},
|
||||||
|
@ -36,7 +37,7 @@ impl PerformCrud for GetSite {
|
||||||
|
|
||||||
let admins = PersonView::admins(context.pool()).await?;
|
let admins = PersonView::admins(context.pool()).await?;
|
||||||
|
|
||||||
let online = context.chat_server().get_users_online()?;
|
let online = context.chat_server().send(GetUsersOnline).await?;
|
||||||
|
|
||||||
// Build the local user
|
// Build the local user
|
||||||
let my_user = if let Some(local_user_view) = get_local_user_settings_view_from_jwt_opt(
|
let my_user = if let Some(local_user_view) = get_local_user_settings_view_from_jwt_opt(
|
||||||
|
|
|
@ -192,10 +192,7 @@ impl PerformCrud for EditSite {
|
||||||
|
|
||||||
let res = SiteResponse { site_view };
|
let res = SiteResponse { site_view };
|
||||||
|
|
||||||
context
|
context.send_all_ws_message(&UserOperationCrud::EditSite, &res, websocket_id)?;
|
||||||
.chat_server()
|
|
||||||
.send_all_message(UserOperationCrud::EditSite, &res, websocket_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use lemmy_api_common::{
|
||||||
send_verification_email,
|
send_verification_email,
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
|
websocket::handlers::captcha::CheckCaptcha,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::PersonAggregates,
|
aggregates::structs::PersonAggregates,
|
||||||
|
@ -78,10 +79,13 @@ impl PerformCrud for Register {
|
||||||
|
|
||||||
// If the site is set up, check the captcha
|
// If the site is set up, check the captcha
|
||||||
if local_site.site_setup && local_site.captcha_enabled {
|
if local_site.site_setup && local_site.captcha_enabled {
|
||||||
let check = context.chat_server().check_captcha(
|
let check = context
|
||||||
data.captcha_uuid.clone().unwrap_or_default(),
|
.chat_server()
|
||||||
data.captcha_answer.clone().unwrap_or_default(),
|
.send(CheckCaptcha {
|
||||||
)?;
|
uuid: data.captcha_uuid.clone().unwrap_or_default(),
|
||||||
|
answer: data.captcha_answer.clone().unwrap_or_default(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
if !check {
|
if !check {
|
||||||
return Err(LemmyError::from_message("captcha_incorrect"));
|
return Err(LemmyError::from_message("captcha_incorrect"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ serde_json = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
actix-web = { workspace = true }
|
actix-web = { workspace = true }
|
||||||
actix-rt = { workspace = true }
|
actix-rt = { workspace = true }
|
||||||
|
actix = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
|
|
@ -143,15 +143,12 @@ impl ActivityHandler for Report {
|
||||||
|
|
||||||
let post_report_view = PostReportView::read(context.pool(), report.id, actor.id).await?;
|
let post_report_view = PostReportView::read(context.pool(), report.id, actor.id).await?;
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::CreateCommentReport,
|
||||||
.send_mod_room_message(
|
&PostReportResponse { post_report_view },
|
||||||
UserOperation::CreateCommentReport,
|
post.community_id,
|
||||||
&PostReportResponse { post_report_view },
|
None,
|
||||||
post.community_id,
|
)?;
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
PostOrComment::Comment(comment) => {
|
PostOrComment::Comment(comment) => {
|
||||||
let report_form = CommentReportForm {
|
let report_form = CommentReportForm {
|
||||||
|
@ -167,17 +164,14 @@ impl ActivityHandler for Report {
|
||||||
CommentReportView::read(context.pool(), report.id, actor.id).await?;
|
CommentReportView::read(context.pool(), report.id, actor.id).await?;
|
||||||
let community_id = comment_report_view.community.id;
|
let community_id = comment_report_view.community.id;
|
||||||
|
|
||||||
context
|
context.send_mod_ws_message(
|
||||||
.chat_server()
|
&UserOperation::CreateCommentReport,
|
||||||
.send_mod_room_message(
|
&CommentReportResponse {
|
||||||
UserOperation::CreateCommentReport,
|
comment_report_view,
|
||||||
&CommentReportResponse {
|
},
|
||||||
comment_report_view,
|
community_id,
|
||||||
},
|
None,
|
||||||
community_id,
|
)?;
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, EditCommunity, HideCommunity},
|
community::{CommunityResponse, EditCommunity, HideCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::get_local_user_view_from_jwt,
|
utils::get_local_user_view_from_jwt,
|
||||||
websocket::{send::send_community_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::community::Community, traits::Crud};
|
use lemmy_db_schema::{source::community::Community, traits::Crud};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
|
@ -102,14 +102,14 @@ impl ActivityHandler for UpdateCommunity {
|
||||||
let updated_community =
|
let updated_community =
|
||||||
Community::update(context.pool(), community.id, &community_update_form).await?;
|
Community::update(context.pool(), community.id, &community_update_form).await?;
|
||||||
|
|
||||||
send_community_ws_message(
|
context
|
||||||
updated_community.id,
|
.send_community_ws_message(
|
||||||
UserOperationCrud::EditCommunity,
|
&UserOperationCrud::EditCommunity,
|
||||||
None,
|
updated_community.id,
|
||||||
None,
|
None,
|
||||||
context,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, CreateComment, EditComment},
|
comment::{CommentResponse, CreateComment, EditComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_post_deleted_or_removed, is_mod_or_admin},
|
utils::{check_post_deleted_or_removed, is_mod_or_admin},
|
||||||
websocket::{send::send_comment_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PersonId,
|
newtypes::PersonId,
|
||||||
|
@ -199,10 +199,9 @@ impl ActivityHandler for CreateOrUpdateNote {
|
||||||
CreateOrUpdateType::Create => UserOperationCrud::CreateComment,
|
CreateOrUpdateType::Create => UserOperationCrud::CreateComment,
|
||||||
CreateOrUpdateType::Update => UserOperationCrud::EditComment,
|
CreateOrUpdateType::Update => UserOperationCrud::EditComment,
|
||||||
};
|
};
|
||||||
send_comment_ws_message(
|
context
|
||||||
comment.id, notif_type, None, None, None, recipients, context,
|
.send_comment_ws_message(¬if_type, comment.id, None, None, None, recipients)
|
||||||
)
|
.await?;
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::objects::person::ApubPerson;
|
use crate::objects::person::ApubPerson;
|
||||||
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||||
use lemmy_api_common::{context::LemmyContext, websocket::send::send_local_notifs};
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
source::{comment::Comment, post::Post},
|
source::{comment::Comment, post::Post},
|
||||||
|
@ -29,5 +29,7 @@ async fn get_comment_notif_recipients(
|
||||||
// anyway.
|
// anyway.
|
||||||
// TODO: for compatibility with other projects, it would be much better to read this from cc or tags
|
// TODO: for compatibility with other projects, it would be much better to read this from cc or tags
|
||||||
let mentions = scrape_text_for_mentions(&comment.content);
|
let mentions = scrape_text_for_mentions(&comment.content);
|
||||||
send_local_notifs(mentions, comment, &actor, &post, do_send_email, context).await
|
context
|
||||||
|
.send_local_notifs(mentions, comment, &actor, &post, do_send_email)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ use activitypub_federation::{
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{CreatePost, EditPost, PostResponse},
|
post::{CreatePost, EditPost, PostResponse},
|
||||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PersonId,
|
newtypes::PersonId,
|
||||||
|
@ -196,7 +196,9 @@ impl ActivityHandler for CreateOrUpdatePage {
|
||||||
CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
|
CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
|
||||||
CreateOrUpdateType::Update => UserOperationCrud::EditPost,
|
CreateOrUpdateType::Update => UserOperationCrud::EditPost,
|
||||||
};
|
};
|
||||||
send_post_ws_message(post.id, notif_type, None, None, context).await?;
|
context
|
||||||
|
.send_post_ws_message(¬if_type, post.id, None, None)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use activitypub_federation::{
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
private_message::{CreatePrivateMessage, EditPrivateMessage, PrivateMessageResponse},
|
private_message::{CreatePrivateMessage, EditPrivateMessage, PrivateMessageResponse},
|
||||||
websocket::{send::send_pm_ws_message, UserOperationCrud},
|
websocket::UserOperationCrud,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PersonId,
|
newtypes::PersonId,
|
||||||
|
@ -124,7 +124,9 @@ impl ActivityHandler for CreateOrUpdateChatMessage {
|
||||||
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,
|
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,
|
||||||
CreateOrUpdateType::Update => UserOperationCrud::EditPrivateMessage,
|
CreateOrUpdateType::Update => UserOperationCrud::EditPrivateMessage,
|
||||||
};
|
};
|
||||||
send_pm_ws_message(private_message.id, notif_type, None, context).await?;
|
context
|
||||||
|
.send_pm_ws_message(¬if_type, private_message.id, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,7 @@ use crate::{
|
||||||
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
|
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
|
||||||
};
|
};
|
||||||
use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler};
|
use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, websocket::UserOperationCrud};
|
||||||
context::LemmyContext,
|
|
||||||
websocket::{
|
|
||||||
send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
|
@ -134,7 +128,9 @@ pub(in crate::activities) async fn receive_remove_action(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
send_community_ws_message(deleted_community.id, RemoveCommunity, None, None, context).await?;
|
context
|
||||||
|
.send_community_ws_message(&RemoveCommunity, deleted_community.id, None, None)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::Post(post) => {
|
DeletableObjects::Post(post) => {
|
||||||
let form = ModRemovePostForm {
|
let form = ModRemovePostForm {
|
||||||
|
@ -151,7 +147,9 @@ pub(in crate::activities) async fn receive_remove_action(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
send_post_ws_message(removed_post.id, RemovePost, None, None, context).await?;
|
context
|
||||||
|
.send_post_ws_message(&RemovePost, removed_post.id, None, None)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::Comment(comment) => {
|
DeletableObjects::Comment(comment) => {
|
||||||
let form = ModRemoveCommentForm {
|
let form = ModRemoveCommentForm {
|
||||||
|
@ -168,7 +166,9 @@ pub(in crate::activities) async fn receive_remove_action(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
send_comment_ws_message_simple(removed_comment.id, RemoveComment, context).await?;
|
context
|
||||||
|
.send_comment_ws_message_simple(&RemoveComment, removed_comment.id)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::PrivateMessage(_) => unimplemented!(),
|
DeletableObjects::PrivateMessage(_) => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,15 +35,7 @@ use lemmy_api_common::{
|
||||||
post::{DeletePost, PostResponse, RemovePost},
|
post::{DeletePost, PostResponse, RemovePost},
|
||||||
private_message::{DeletePrivateMessage, PrivateMessageResponse},
|
private_message::{DeletePrivateMessage, PrivateMessageResponse},
|
||||||
utils::get_local_user_view_from_jwt,
|
utils::get_local_user_view_from_jwt,
|
||||||
websocket::{
|
websocket::UserOperationCrud,
|
||||||
send::{
|
|
||||||
send_comment_ws_message_simple,
|
|
||||||
send_community_ws_message,
|
|
||||||
send_pm_ws_message,
|
|
||||||
send_post_ws_message,
|
|
||||||
},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -410,14 +402,14 @@ async fn receive_delete_action(
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_community_ws_message(
|
context
|
||||||
community.id,
|
.send_community_ws_message(
|
||||||
UserOperationCrud::DeleteCommunity,
|
&UserOperationCrud::DeleteCommunity,
|
||||||
None,
|
community.id,
|
||||||
None,
|
None,
|
||||||
context,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::Post(post) => {
|
DeletableObjects::Post(post) => {
|
||||||
if deleted != post.deleted {
|
if deleted != post.deleted {
|
||||||
|
@ -427,14 +419,9 @@ async fn receive_delete_action(
|
||||||
&PostUpdateForm::builder().deleted(Some(deleted)).build(),
|
&PostUpdateForm::builder().deleted(Some(deleted)).build(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_post_ws_message(
|
context
|
||||||
deleted_post.id,
|
.send_post_ws_message(&UserOperationCrud::DeletePost, deleted_post.id, None, None)
|
||||||
UserOperationCrud::DeletePost,
|
.await?;
|
||||||
None,
|
|
||||||
None,
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeletableObjects::Comment(comment) => {
|
DeletableObjects::Comment(comment) => {
|
||||||
|
@ -445,12 +432,9 @@ async fn receive_delete_action(
|
||||||
&CommentUpdateForm::builder().deleted(Some(deleted)).build(),
|
&CommentUpdateForm::builder().deleted(Some(deleted)).build(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_comment_ws_message_simple(
|
context
|
||||||
deleted_comment.id,
|
.send_comment_ws_message_simple(&UserOperationCrud::DeleteComment, deleted_comment.id)
|
||||||
UserOperationCrud::DeleteComment,
|
.await?;
|
||||||
context,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeletableObjects::PrivateMessage(pm) => {
|
DeletableObjects::PrivateMessage(pm) => {
|
||||||
|
@ -463,13 +447,13 @@ async fn receive_delete_action(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
send_pm_ws_message(
|
context
|
||||||
deleted_private_message.id,
|
.send_pm_ws_message(
|
||||||
UserOperationCrud::DeletePrivateMessage,
|
&UserOperationCrud::DeletePrivateMessage,
|
||||||
None,
|
deleted_private_message.id,
|
||||||
context,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,13 +8,7 @@ use crate::{
|
||||||
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
|
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
|
||||||
};
|
};
|
||||||
use activitypub_federation::{config::Data, kinds::activity::UndoType, traits::ActivityHandler};
|
use activitypub_federation::{config::Data, kinds::activity::UndoType, traits::ActivityHandler};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, websocket::UserOperationCrud};
|
||||||
context::LemmyContext,
|
|
||||||
websocket::{
|
|
||||||
send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
|
|
||||||
UserOperationCrud,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
|
@ -125,7 +119,9 @@ impl UndoDelete {
|
||||||
&CommunityUpdateForm::builder().removed(Some(false)).build(),
|
&CommunityUpdateForm::builder().removed(Some(false)).build(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_community_ws_message(deleted_community.id, EditCommunity, None, None, context).await?;
|
context
|
||||||
|
.send_community_ws_message(&EditCommunity, deleted_community.id, None, None)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::Post(post) => {
|
DeletableObjects::Post(post) => {
|
||||||
let form = ModRemovePostForm {
|
let form = ModRemovePostForm {
|
||||||
|
@ -141,7 +137,9 @@ impl UndoDelete {
|
||||||
&PostUpdateForm::builder().removed(Some(false)).build(),
|
&PostUpdateForm::builder().removed(Some(false)).build(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_post_ws_message(removed_post.id, EditPost, None, None, context).await?;
|
context
|
||||||
|
.send_post_ws_message(&EditPost, removed_post.id, None, None)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::Comment(comment) => {
|
DeletableObjects::Comment(comment) => {
|
||||||
let form = ModRemoveCommentForm {
|
let form = ModRemoveCommentForm {
|
||||||
|
@ -157,7 +155,9 @@ impl UndoDelete {
|
||||||
&CommentUpdateForm::builder().removed(Some(false)).build(),
|
&CommentUpdateForm::builder().removed(Some(false)).build(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
send_comment_ws_message_simple(removed_comment.id, EditComment, context).await?;
|
context
|
||||||
|
.send_comment_ws_message_simple(&EditComment, removed_comment.id)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
DeletableObjects::PrivateMessage(_) => unimplemented!(),
|
DeletableObjects::PrivateMessage(_) => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,11 @@ use activitypub_federation::{
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
community::CommunityResponse,
|
community::CommunityResponse,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
websocket::UserOperation,
|
websocket::{
|
||||||
|
handlers::messages::SendUserRoomMessage,
|
||||||
|
serialize_websocket_message,
|
||||||
|
UserOperation,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{actor_language::CommunityLanguage, community::CommunityFollower},
|
source::{actor_language::CommunityLanguage, community::CommunityFollower},
|
||||||
|
@ -85,20 +89,18 @@ impl ActivityHandler for AcceptFollow {
|
||||||
.id;
|
.id;
|
||||||
let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?;
|
let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?;
|
||||||
|
|
||||||
let response = CommunityResponse {
|
let res = CommunityResponse {
|
||||||
community_view,
|
community_view,
|
||||||
discussion_languages,
|
discussion_languages,
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
let message = serialize_websocket_message(&UserOperation::FollowCommunity, &res)?;
|
||||||
.chat_server()
|
|
||||||
.send_user_room_message(
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
&UserOperation::FollowCommunity,
|
recipient_id: local_recipient_id,
|
||||||
&response,
|
message,
|
||||||
local_recipient_id,
|
websocket_id: None,
|
||||||
None,
|
});
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,7 @@ use lemmy_api_common::{
|
||||||
post::{CreatePostLike, PostResponse},
|
post::{CreatePostLike, PostResponse},
|
||||||
sensitive::Sensitive,
|
sensitive::Sensitive,
|
||||||
utils::get_local_user_view_from_jwt,
|
utils::get_local_user_view_from_jwt,
|
||||||
websocket::{
|
websocket::UserOperation,
|
||||||
send::{send_comment_ws_message_simple, send_post_ws_message},
|
|
||||||
UserOperation,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::CommunityId,
|
newtypes::CommunityId,
|
||||||
|
@ -125,7 +122,9 @@ async fn vote_comment(
|
||||||
CommentLike::remove(context.pool(), person_id, comment_id).await?;
|
CommentLike::remove(context.pool(), person_id, comment_id).await?;
|
||||||
CommentLike::like(context.pool(), &like_form).await?;
|
CommentLike::like(context.pool(), &like_form).await?;
|
||||||
|
|
||||||
send_comment_ws_message_simple(comment_id, UserOperation::CreateCommentLike, context).await?;
|
context
|
||||||
|
.send_comment_ws_message_simple(&UserOperation::CreateCommentLike, comment_id)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +145,9 @@ async fn vote_post(
|
||||||
PostLike::remove(context.pool(), person_id, post_id).await?;
|
PostLike::remove(context.pool(), person_id, post_id).await?;
|
||||||
PostLike::like(context.pool(), &like_form).await?;
|
PostLike::like(context.pool(), &like_form).await?;
|
||||||
|
|
||||||
send_post_ws_message(post.id, UserOperation::CreatePostLike, None, None, context).await?;
|
context
|
||||||
|
.send_post_ws_message(&UserOperation::CreatePostLike, post.id, None, None)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +161,9 @@ async fn undo_vote_comment(
|
||||||
let person_id = actor.id;
|
let person_id = actor.id;
|
||||||
CommentLike::remove(context.pool(), person_id, comment_id).await?;
|
CommentLike::remove(context.pool(), person_id, comment_id).await?;
|
||||||
|
|
||||||
send_comment_ws_message_simple(comment_id, UserOperation::CreateCommentLike, context).await?;
|
context
|
||||||
|
.send_comment_ws_message_simple(&UserOperation::CreateCommentLike, comment_id)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +177,8 @@ async fn undo_vote_post(
|
||||||
let person_id = actor.id;
|
let person_id = actor.id;
|
||||||
PostLike::remove(context.pool(), person_id, post_id).await?;
|
PostLike::remove(context.pool(), person_id, post_id).await?;
|
||||||
|
|
||||||
send_post_ws_message(post_id, UserOperation::CreatePostLike, None, None, context).await?;
|
context
|
||||||
|
.send_post_ws_message(&UserOperation::CreatePostLike, post_id, None, None)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use lemmy_api_common::{
|
||||||
community::{GetCommunity, GetCommunityResponse},
|
community::{GetCommunity, GetCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_private_instance, get_local_user_view_from_jwt_opt, is_mod_or_admin_opt},
|
utils::{check_private_instance, get_local_user_view_from_jwt_opt, is_mod_or_admin_opt},
|
||||||
|
websocket::handlers::online_users::GetCommunityUsersOnline,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::default_post_language,
|
impls::actor_language::default_post_language,
|
||||||
|
@ -76,7 +77,8 @@ impl PerformApub for GetCommunity {
|
||||||
|
|
||||||
let online = context
|
let online = context
|
||||||
.chat_server()
|
.chat_server()
|
||||||
.get_community_users_online(community_id)?;
|
.send(GetCommunityUsersOnline { community_id })
|
||||||
|
.await?;
|
||||||
|
|
||||||
let site_id =
|
let site_id =
|
||||||
Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into());
|
Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into());
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub(crate) fn verify_is_remote_object(id: &Url, settings: &Settings) -> Result<(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use activitypub_federation::config::{Data, FederationConfig};
|
use activitypub_federation::config::{Data, FederationConfig};
|
||||||
|
use actix::Actor;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
@ -69,7 +70,6 @@ pub(crate) mod tests {
|
||||||
};
|
};
|
||||||
use reqwest::{Client, Request, Response};
|
use reqwest::{Client, Request, Response};
|
||||||
use reqwest_middleware::{ClientBuilder, Middleware, Next};
|
use reqwest_middleware::{ClientBuilder, Middleware, Next};
|
||||||
use std::sync::Arc;
|
|
||||||
use task_local_extensions::Extensions;
|
use task_local_extensions::Extensions;
|
||||||
|
|
||||||
struct BlockedMiddleware;
|
struct BlockedMiddleware;
|
||||||
|
@ -110,7 +110,7 @@ pub(crate) mod tests {
|
||||||
let rate_limit_config = RateLimitConfig::builder().build();
|
let rate_limit_config = RateLimitConfig::builder().build();
|
||||||
let rate_limit_cell = RateLimitCell::new(rate_limit_config).await;
|
let rate_limit_cell = RateLimitCell::new(rate_limit_config).await;
|
||||||
|
|
||||||
let chat_server = Arc::new(ChatServer::startup());
|
let chat_server = ChatServer::default().start();
|
||||||
let context = LemmyContext::create(pool, chat_server, client, secret, rate_limit_cell.clone());
|
let context = LemmyContext::create(pool, chat_server, client, secret, rate_limit_cell.clone());
|
||||||
let config = FederationConfig::builder()
|
let config = FederationConfig::builder()
|
||||||
.domain("example.com")
|
.domain("example.com")
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
use activitypub_federation::config::Data as ContextData;
|
use activitypub_federation::config::Data as ContextData;
|
||||||
|
use actix::{
|
||||||
|
fut,
|
||||||
|
Actor,
|
||||||
|
ActorContext,
|
||||||
|
ActorFutureExt,
|
||||||
|
AsyncContext,
|
||||||
|
ContextFutureSpawner,
|
||||||
|
Handler,
|
||||||
|
Running,
|
||||||
|
StreamHandler,
|
||||||
|
WrapFuture,
|
||||||
|
};
|
||||||
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
use actix_ws::{MessageStream, Session};
|
|
||||||
use futures::stream::StreamExt;
|
|
||||||
use lemmy_api::Perform;
|
use lemmy_api::Perform;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
comment::{
|
comment::{
|
||||||
|
@ -101,6 +111,10 @@ use lemmy_api_common::{
|
||||||
Search,
|
Search,
|
||||||
},
|
},
|
||||||
websocket::{
|
websocket::{
|
||||||
|
handlers::{
|
||||||
|
connect::{Connect, Disconnect},
|
||||||
|
WsMessage,
|
||||||
|
},
|
||||||
serialize_websocket_message,
|
serialize_websocket_message,
|
||||||
structs::{CommunityJoin, ModJoin, PostJoin, UserJoin},
|
structs::{CommunityJoin, ModJoin, PostJoin, UserJoin},
|
||||||
UserOperation,
|
UserOperation,
|
||||||
|
@ -117,21 +131,36 @@ use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
result,
|
result,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, Mutex},
|
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
/// How often heartbeat pings are sent
|
||||||
|
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(25);
|
||||||
|
|
||||||
|
/// How long before lack of client response causes a timeout
|
||||||
|
const CLIENT_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
|
pub struct WsChatSession {
|
||||||
|
/// unique session id
|
||||||
|
pub id: ConnectionId,
|
||||||
|
|
||||||
|
pub ip: IpAddr,
|
||||||
|
|
||||||
|
/// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
|
||||||
|
/// otherwise we drop connection.
|
||||||
|
pub hb: Instant,
|
||||||
|
|
||||||
|
/// The context data
|
||||||
|
apub_data: ContextData<LemmyContext>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Entry point for our route
|
|
||||||
pub async fn websocket(
|
pub async fn websocket(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
body: web::Payload,
|
body: web::Payload,
|
||||||
context: web::Data<LemmyContext>,
|
|
||||||
rate_limiter: web::Data<RateLimitCell>,
|
rate_limiter: web::Data<RateLimitCell>,
|
||||||
apub_data: ContextData<LemmyContext>,
|
apub_data: ContextData<LemmyContext>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let (response, session, stream) = actix_ws::handle(&req, body)?;
|
|
||||||
|
|
||||||
let client_ip = IpAddr(
|
let client_ip = IpAddr(
|
||||||
req
|
req
|
||||||
.connection_info()
|
.connection_info()
|
||||||
|
@ -146,111 +175,158 @@ pub async fn websocket(
|
||||||
"Websocket join with IP: {} has been rate limited.",
|
"Websocket join with IP: {} has been rate limited.",
|
||||||
&client_ip
|
&client_ip
|
||||||
);
|
);
|
||||||
session.close(None).await.map_err(LemmyError::from)?;
|
return Ok(HttpResponse::TooManyRequests().finish());
|
||||||
return Ok(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection_id = context.chat_server().handle_connect(session.clone())?;
|
ws::start(
|
||||||
info!("{} joined", &client_ip);
|
WsChatSession {
|
||||||
|
id: 0,
|
||||||
let alive = Arc::new(Mutex::new(Instant::now()));
|
ip: client_ip,
|
||||||
heartbeat(session.clone(), alive.clone());
|
hb: Instant::now(),
|
||||||
|
apub_data,
|
||||||
actix_rt::spawn(handle_messages(
|
},
|
||||||
stream,
|
&req,
|
||||||
client_ip,
|
body,
|
||||||
session,
|
)
|
||||||
connection_id,
|
|
||||||
alive,
|
|
||||||
rate_limiter,
|
|
||||||
apub_data,
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_messages(
|
/// helper method that sends ping to client every few seconds (HEARTBEAT_INTERVAL).
|
||||||
mut stream: MessageStream,
|
///
|
||||||
client_ip: IpAddr,
|
/// also this method checks heartbeats from client
|
||||||
mut session: Session,
|
fn hb(ctx: &mut ws::WebsocketContext<WsChatSession>) {
|
||||||
connection_id: ConnectionId,
|
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||||
alive: Arc<Mutex<Instant>>,
|
// check client heartbeats
|
||||||
rate_limiter: web::Data<RateLimitCell>,
|
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
|
||||||
context: ContextData<LemmyContext>,
|
// heartbeat timed out
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
while let Some(Ok(msg)) = stream.next().await {
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(bytes) => {
|
|
||||||
if session.pong(&bytes).await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws::Message::Pong(_) => {
|
|
||||||
let mut lock = alive
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire websocket heartbeat alive lock");
|
|
||||||
*lock = Instant::now();
|
|
||||||
}
|
|
||||||
ws::Message::Text(text) => {
|
|
||||||
let msg = text.trim().to_string();
|
|
||||||
let executed = parse_json_message(
|
|
||||||
msg,
|
|
||||||
client_ip.clone(),
|
|
||||||
connection_id,
|
|
||||||
rate_limiter.get_ref(),
|
|
||||||
context.reset_request_count(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let res = executed.unwrap_or_else(|e| {
|
// notify chat server
|
||||||
error!("Error during message handling {}", e);
|
act
|
||||||
e.to_json()
|
.apub_data
|
||||||
.unwrap_or_else(|_| String::from(r#"{"error":"failed to serialize json"}"#))
|
.chat_server()
|
||||||
});
|
.do_send(Disconnect { id: act.id });
|
||||||
session.text(res).await?;
|
|
||||||
}
|
// stop actor
|
||||||
ws::Message::Close(_) => {
|
ctx.stop();
|
||||||
session.close(None).await?;
|
|
||||||
context.chat_server().handle_disconnect(&connection_id)?;
|
// don't try to send a ping
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
ws::Message::Binary(_) => info!("Unexpected binary"),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn heartbeat(mut session: Session, alive: Arc<Mutex<Instant>>) {
|
ctx.ping(b"");
|
||||||
actix_rt::spawn(async move {
|
|
||||||
let mut interval = actix_rt::time::interval(Duration::from_secs(5));
|
|
||||||
loop {
|
|
||||||
if session.ping(b"").await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let duration_since = {
|
|
||||||
let alive_lock = alive
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire websocket heartbeat alive lock");
|
|
||||||
Instant::now().duration_since(*alive_lock)
|
|
||||||
};
|
|
||||||
if duration_since > Duration::from_secs(10) {
|
|
||||||
let _ = session.close(None).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
interval.tick().await;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Actor for WsChatSession {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
|
||||||
|
/// Method is called on actor start.
|
||||||
|
/// We register ws session with ChatServer
|
||||||
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
|
// we'll start heartbeat process on session start.
|
||||||
|
hb(ctx);
|
||||||
|
|
||||||
|
// register self in chat server. `AsyncContext::wait` register
|
||||||
|
// future within context, but context waits until this future resolves
|
||||||
|
// before processing any other events.
|
||||||
|
// HttpContext::state() is instance of WsChatSessionState, state is shared
|
||||||
|
// across all routes within application
|
||||||
|
let addr = ctx.address();
|
||||||
|
self
|
||||||
|
.apub_data
|
||||||
|
.chat_server()
|
||||||
|
.send(Connect {
|
||||||
|
addr: addr.recipient(),
|
||||||
|
})
|
||||||
|
.into_actor(self)
|
||||||
|
.then(|res, act, ctx| {
|
||||||
|
match res {
|
||||||
|
Ok(res) => act.id = res,
|
||||||
|
// something is wrong with chat server
|
||||||
|
_ => ctx.stop(),
|
||||||
|
}
|
||||||
|
fut::ready(())
|
||||||
|
})
|
||||||
|
.wait(ctx);
|
||||||
|
}
|
||||||
|
fn stopping(&mut self, _: &mut Self::Context) -> Running {
|
||||||
|
// notify chat server
|
||||||
|
self
|
||||||
|
.apub_data
|
||||||
|
.chat_server()
|
||||||
|
.do_send(Disconnect { id: self.id });
|
||||||
|
Running::Stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle messages from chat server, we simply send it to peer websocket
|
||||||
|
impl Handler<WsMessage> for WsChatSession {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: WsMessage, ctx: &mut Self::Context) {
|
||||||
|
ctx.text(msg.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WebSocket message handler
|
||||||
|
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsChatSession {
|
||||||
|
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
||||||
|
let msg = match msg {
|
||||||
|
Err(_) => {
|
||||||
|
ctx.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(msg) => msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => {
|
||||||
|
self.hb = Instant::now();
|
||||||
|
ctx.pong(&msg);
|
||||||
|
}
|
||||||
|
ws::Message::Pong(_) => {
|
||||||
|
self.hb = Instant::now();
|
||||||
|
}
|
||||||
|
ws::Message::Text(text) => {
|
||||||
|
let ip_clone = self.ip.clone();
|
||||||
|
let id_clone = self.id.to_owned();
|
||||||
|
let context_clone = self.apub_data.reset_request_count();
|
||||||
|
|
||||||
|
let fut = Box::pin(async move {
|
||||||
|
let msg = text.trim().to_string();
|
||||||
|
parse_json_message(msg, ip_clone, id_clone, context_clone).await
|
||||||
|
});
|
||||||
|
fut
|
||||||
|
.into_actor(self)
|
||||||
|
.then(|res, _, ctx| {
|
||||||
|
match res {
|
||||||
|
Ok(res) => ctx.text(res),
|
||||||
|
Err(e) => error!("{}", &e),
|
||||||
|
}
|
||||||
|
actix::fut::ready(())
|
||||||
|
})
|
||||||
|
.spawn(ctx);
|
||||||
|
}
|
||||||
|
ws::Message::Binary(_) => println!("Unexpected binary"),
|
||||||
|
ws::Message::Close(reason) => {
|
||||||
|
ctx.close(reason);
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
ws::Message::Continuation(_) => {
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
ws::Message::Nop => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry point for our websocket route
|
||||||
async fn parse_json_message(
|
async fn parse_json_message(
|
||||||
msg: String,
|
msg: String,
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
rate_limiter: &RateLimitCell,
|
|
||||||
context: ContextData<LemmyContext>,
|
context: ContextData<LemmyContext>,
|
||||||
) -> Result<String, LemmyError> {
|
) -> Result<String, LemmyError> {
|
||||||
|
let rate_limiter = context.settings_updated_channel();
|
||||||
let json: Value = serde_json::from_str(&msg)?;
|
let json: Value = serde_json::from_str(&msg)?;
|
||||||
let data = json
|
let data = json
|
||||||
.get("data")
|
.get("data")
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub mod telemetry;
|
||||||
|
|
||||||
use crate::{code_migrations::run_advanced_migrations, root_span_builder::QuieterRootSpanBuilder};
|
use crate::{code_migrations::run_advanced_migrations, root_span_builder::QuieterRootSpanBuilder};
|
||||||
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
||||||
|
use actix::Actor;
|
||||||
use actix_web::{middleware, web::Data, App, HttpServer, Result};
|
use actix_web::{middleware, web::Data, App, HttpServer, Result};
|
||||||
use doku::json::{AutoComments, CommentsStyle, Formatting, ObjectsStyle};
|
use doku::json::{AutoComments, CommentsStyle, Formatting, ObjectsStyle};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
|
@ -35,7 +36,7 @@ use reqwest::Client;
|
||||||
use reqwest_middleware::ClientBuilder;
|
use reqwest_middleware::ClientBuilder;
|
||||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||||
use reqwest_tracing::TracingMiddleware;
|
use reqwest_tracing::TracingMiddleware;
|
||||||
use std::{env, sync::Arc, thread, time::Duration};
|
use std::{env, thread, time::Duration};
|
||||||
use tracing::subscriber::set_global_default;
|
use tracing::subscriber::set_global_default;
|
||||||
use tracing_actix_web::TracingLogger;
|
use tracing_actix_web::TracingLogger;
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
|
@ -133,7 +134,7 @@ pub async fn start_lemmy_server() -> Result<(), LemmyError> {
|
||||||
scheduled_tasks::setup(db_url, user_agent).expect("Couldn't set up scheduled_tasks");
|
scheduled_tasks::setup(db_url, user_agent).expect("Couldn't set up scheduled_tasks");
|
||||||
});
|
});
|
||||||
|
|
||||||
let chat_server = Arc::new(ChatServer::startup());
|
let chat_server = ChatServer::default().start();
|
||||||
|
|
||||||
// Create Http server with websocket support
|
// Create Http server with websocket support
|
||||||
let settings_bind = settings.clone();
|
let settings_bind = settings.clone();
|
||||||
|
|
Loading…
Reference in a new issue