Split api crate into api_structs and api
This commit is contained in:
parent
af2f84805a
commit
249fcc5066
95 changed files with 3953 additions and 3390 deletions
58
Cargo.lock
generated
58
Cargo.lock
generated
|
@ -1746,7 +1746,7 @@ dependencies = [
|
||||||
"http-signature-normalization-actix",
|
"http-signature-normalization-actix",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lemmy_api_structs",
|
"lemmy_api_common",
|
||||||
"lemmy_apub",
|
"lemmy_apub",
|
||||||
"lemmy_db_queries",
|
"lemmy_db_queries",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
|
@ -1771,7 +1771,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api_structs"
|
name = "lemmy_api_common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -1789,6 +1789,51 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lemmy_api_crud"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"actix",
|
||||||
|
"actix-rt",
|
||||||
|
"actix-web",
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"awc",
|
||||||
|
"background-jobs",
|
||||||
|
"base64 0.13.0",
|
||||||
|
"bcrypt",
|
||||||
|
"captcha",
|
||||||
|
"chrono",
|
||||||
|
"diesel",
|
||||||
|
"futures",
|
||||||
|
"http",
|
||||||
|
"http-signature-normalization-actix",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"lemmy_api_common",
|
||||||
|
"lemmy_apub",
|
||||||
|
"lemmy_db_queries",
|
||||||
|
"lemmy_db_schema",
|
||||||
|
"lemmy_db_views",
|
||||||
|
"lemmy_db_views_actor",
|
||||||
|
"lemmy_db_views_moderator",
|
||||||
|
"lemmy_utils",
|
||||||
|
"lemmy_websocket",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"rand 0.8.3",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
"thiserror",
|
||||||
|
"tokio 0.3.7",
|
||||||
|
"url",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_apub"
|
name = "lemmy_apub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1813,7 +1858,7 @@ dependencies = [
|
||||||
"http-signature-normalization-reqwest",
|
"http-signature-normalization-reqwest",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lemmy_api_structs",
|
"lemmy_api_common",
|
||||||
"lemmy_db_queries",
|
"lemmy_db_queries",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"lemmy_db_views",
|
"lemmy_db_views",
|
||||||
|
@ -1916,7 +1961,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lemmy_api_structs",
|
"lemmy_api_common",
|
||||||
"lemmy_db_queries",
|
"lemmy_db_queries",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"lemmy_db_views",
|
"lemmy_db_views",
|
||||||
|
@ -1948,7 +1993,8 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"http-signature-normalization-actix",
|
"http-signature-normalization-actix",
|
||||||
"lemmy_api",
|
"lemmy_api",
|
||||||
"lemmy_api_structs",
|
"lemmy_api_common",
|
||||||
|
"lemmy_api_crud",
|
||||||
"lemmy_apub",
|
"lemmy_apub",
|
||||||
"lemmy_db_queries",
|
"lemmy_db_queries",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
|
@ -2013,7 +2059,7 @@ dependencies = [
|
||||||
"background-jobs",
|
"background-jobs",
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"lemmy_api_structs",
|
"lemmy_api_common",
|
||||||
"lemmy_db_queries",
|
"lemmy_db_queries",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"lemmy_utils",
|
"lemmy_utils",
|
||||||
|
|
|
@ -12,6 +12,8 @@ debug = 0
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/api",
|
"crates/api",
|
||||||
|
"crates/api_crud",
|
||||||
|
"crates/api_common",
|
||||||
"crates/apub",
|
"crates/apub",
|
||||||
"crates/utils",
|
"crates/utils",
|
||||||
"crates/db_queries",
|
"crates/db_queries",
|
||||||
|
@ -19,13 +21,13 @@ members = [
|
||||||
"crates/db_views",
|
"crates/db_views",
|
||||||
"crates/db_views_actor",
|
"crates/db_views_actor",
|
||||||
"crates/db_views_actor",
|
"crates/db_views_actor",
|
||||||
"crates/api_structs",
|
|
||||||
"crates/websocket",
|
"crates/websocket",
|
||||||
"crates/routes"
|
"crates/routes"
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { path = "./crates/api" }
|
lemmy_api = { path = "./crates/api" }
|
||||||
|
lemmy_api_crud = { path = "./crates/api_crud" }
|
||||||
lemmy_apub = { path = "./crates/apub" }
|
lemmy_apub = { path = "./crates/apub" }
|
||||||
lemmy_utils = { path = "./crates/utils" }
|
lemmy_utils = { path = "./crates/utils" }
|
||||||
lemmy_db_schema = { path = "./crates/db_schema" }
|
lemmy_db_schema = { path = "./crates/db_schema" }
|
||||||
|
@ -33,7 +35,7 @@ lemmy_db_queries = { path = "./crates/db_queries" }
|
||||||
lemmy_db_views = { path = "./crates/db_views" }
|
lemmy_db_views = { path = "./crates/db_views" }
|
||||||
lemmy_db_views_moderator = { path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { path = "./crates/db_views_moderator" }
|
||||||
lemmy_db_views_actor = { path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { path = "./crates/db_views_actor" }
|
||||||
lemmy_api_structs = { path = "crates/api_structs" }
|
lemmy_api_common = { path = "crates/api_common" }
|
||||||
lemmy_websocket = { path = "./crates/websocket" }
|
lemmy_websocket = { path = "./crates/websocket" }
|
||||||
lemmy_routes = { path = "./crates/routes" }
|
lemmy_routes = { path = "./crates/routes" }
|
||||||
diesel = "1.4.5"
|
diesel = "1.4.5"
|
||||||
|
|
|
@ -16,7 +16,7 @@ lemmy_db_schema = { path = "../db_schema" }
|
||||||
lemmy_db_views = { path = "../db_views" }
|
lemmy_db_views = { path = "../db_views" }
|
||||||
lemmy_db_views_moderator = { path = "../db_views_moderator" }
|
lemmy_db_views_moderator = { path = "../db_views_moderator" }
|
||||||
lemmy_db_views_actor = { path = "../db_views_actor" }
|
lemmy_db_views_actor = { path = "../db_views_actor" }
|
||||||
lemmy_api_structs = { path = "../api_structs" }
|
lemmy_api_common = { path = "../api_common" }
|
||||||
lemmy_websocket = { path = "../websocket" }
|
lemmy_websocket = { path = "../websocket" }
|
||||||
diesel = "1.4.5"
|
diesel = "1.4.5"
|
||||||
bcrypt = "0.9.0"
|
bcrypt = "0.9.0"
|
||||||
|
|
|
@ -1,472 +1,18 @@
|
||||||
use crate::{
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
check_community_ban,
|
check_community_ban,
|
||||||
check_downvotes_enabled,
|
check_downvotes_enabled,
|
||||||
collect_moderated_communities,
|
comment::*,
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
get_local_user_view_from_jwt_opt,
|
|
||||||
get_post,
|
|
||||||
is_mod_or_admin,
|
|
||||||
Perform,
|
|
||||||
};
|
};
|
||||||
use actix_web::web::Data;
|
use lemmy_apub::ApubLikeableType;
|
||||||
use lemmy_api_structs::{blocking, comment::*, send_local_notifs};
|
use lemmy_db_queries::{source::comment::Comment_, Likeable, Saveable};
|
||||||
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
use lemmy_db_schema::{source::comment::*, LocalUserId};
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
|
||||||
source::comment::Comment_,
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
Crud,
|
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||||
Likeable,
|
|
||||||
ListingType,
|
|
||||||
Reportable,
|
|
||||||
Saveable,
|
|
||||||
SortType,
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
source::{comment::*, comment_report::*, moderator::*},
|
|
||||||
LocalUserId,
|
|
||||||
};
|
|
||||||
use lemmy_db_views::{
|
|
||||||
comment_report_view::{CommentReportQueryBuilder, CommentReportView},
|
|
||||||
comment_view::{CommentQueryBuilder, CommentView},
|
|
||||||
local_user_view::LocalUserView,
|
|
||||||
};
|
|
||||||
use lemmy_utils::{
|
|
||||||
utils::{remove_slurs, scrape_text_for_mentions},
|
|
||||||
ApiError,
|
|
||||||
ConnectionId,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::{
|
|
||||||
messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
|
|
||||||
LemmyContext,
|
|
||||||
UserOperation,
|
|
||||||
};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreateComment {
|
|
||||||
type Response = CommentResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
|
||||||
let data: &CreateComment = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
|
||||||
|
|
||||||
// Check for a community ban
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let post = get_post(post_id, context.pool()).await?;
|
|
||||||
|
|
||||||
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
|
||||||
|
|
||||||
// Check if post is locked, no new comments
|
|
||||||
if post.locked {
|
|
||||||
return Err(ApiError::err("locked").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's a parent_id, check to make sure that comment is in that post
|
|
||||||
if let Some(parent_id) = data.parent_id {
|
|
||||||
// Make sure the parent comment exists
|
|
||||||
let parent =
|
|
||||||
match blocking(context.pool(), move |conn| Comment::read(&conn, parent_id)).await? {
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
|
|
||||||
};
|
|
||||||
if parent.post_id != post_id {
|
|
||||||
return Err(ApiError::err("couldnt_create_comment").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let comment_form = CommentForm {
|
|
||||||
content: content_slurs_removed,
|
|
||||||
parent_id: data.parent_id.to_owned(),
|
|
||||||
post_id: data.post_id,
|
|
||||||
creator_id: local_user_view.person.id,
|
|
||||||
removed: None,
|
|
||||||
deleted: None,
|
|
||||||
read: None,
|
|
||||||
published: None,
|
|
||||||
updated: None,
|
|
||||||
ap_id: None,
|
|
||||||
local: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the comment
|
|
||||||
let comment_form2 = comment_form.clone();
|
|
||||||
let inserted_comment = match blocking(context.pool(), move |conn| {
|
|
||||||
Comment::create(&conn, &comment_form2)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Necessary to update the ap_id
|
|
||||||
let inserted_comment_id = inserted_comment.id;
|
|
||||||
let updated_comment: Comment =
|
|
||||||
match blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
|
|
||||||
let apub_id =
|
|
||||||
generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?;
|
|
||||||
Ok(Comment::update_ap_id(&conn, inserted_comment_id, apub_id)?)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
updated_comment
|
|
||||||
.send_create(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Scan the comment for user mentions, add those rows
|
|
||||||
let post_id = post.id;
|
|
||||||
let mentions = scrape_text_for_mentions(&comment_form.content);
|
|
||||||
let recipient_ids = send_local_notifs(
|
|
||||||
mentions,
|
|
||||||
updated_comment.clone(),
|
|
||||||
local_user_view.person.clone(),
|
|
||||||
post,
|
|
||||||
context.pool(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// You like your own comment by default
|
|
||||||
let like_form = CommentLikeForm {
|
|
||||||
comment_id: inserted_comment.id,
|
|
||||||
post_id,
|
|
||||||
person_id: local_user_view.person.id,
|
|
||||||
score: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
|
|
||||||
if blocking(context.pool(), like).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_like_comment").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
updated_comment
|
|
||||||
.send_like(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let mut comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(&conn, inserted_comment.id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// If its a comment to yourself, mark it as read
|
|
||||||
let comment_id = comment_view.comment.id;
|
|
||||||
if local_user_view.person.id == comment_view.get_recipient_id() {
|
|
||||||
match blocking(context.pool(), move |conn| {
|
|
||||||
Comment::update_read(conn, comment_id, true)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
|
||||||
};
|
|
||||||
comment_view.comment.read = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res = CommentResponse {
|
|
||||||
comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: data.form_id.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::CreateComment,
|
|
||||||
comment: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.recipient_ids = Vec::new(); // Necessary to avoid doubles
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for EditComment {
|
|
||||||
type Response = CommentResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
|
||||||
let data: &EditComment = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let orig_comment = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(&conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
check_community_ban(
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_comment.community.id,
|
|
||||||
context.pool(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Verify that only the creator can edit
|
|
||||||
if local_user_view.person.id != orig_comment.creator.id {
|
|
||||||
return Err(ApiError::err("no_comment_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the update
|
|
||||||
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let updated_comment = match blocking(context.pool(), move |conn| {
|
|
||||||
Comment::update_content(conn, comment_id, &content_slurs_removed)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the apub update
|
|
||||||
updated_comment
|
|
||||||
.send_update(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Do the mentions / recipients
|
|
||||||
let updated_comment_content = updated_comment.content.to_owned();
|
|
||||||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
|
||||||
let recipient_ids = send_local_notifs(
|
|
||||||
mentions,
|
|
||||||
updated_comment,
|
|
||||||
local_user_view.person.clone(),
|
|
||||||
orig_comment.post,
|
|
||||||
context.pool(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: data.form_id.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::EditComment,
|
|
||||||
comment: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for DeleteComment {
|
|
||||||
type Response = CommentResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
|
||||||
let data: &DeleteComment = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let orig_comment = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(&conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
check_community_ban(
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_comment.community.id,
|
|
||||||
context.pool(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Verify that only the creator can delete
|
|
||||||
if local_user_view.person.id != orig_comment.creator.id {
|
|
||||||
return Err(ApiError::err("no_comment_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the delete
|
|
||||||
let deleted = data.deleted;
|
|
||||||
let updated_comment = match blocking(context.pool(), move |conn| {
|
|
||||||
Comment::update_deleted(conn, comment_id, deleted)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the apub message
|
|
||||||
if deleted {
|
|
||||||
updated_comment
|
|
||||||
.send_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_comment
|
|
||||||
.send_undo_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refetch it
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Build the recipients
|
|
||||||
let comment_view_2 = comment_view.clone();
|
|
||||||
let mentions = vec![];
|
|
||||||
let recipient_ids = send_local_notifs(
|
|
||||||
mentions,
|
|
||||||
updated_comment,
|
|
||||||
local_user_view.person.clone(),
|
|
||||||
comment_view_2.post,
|
|
||||||
context.pool(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: None, // TODO a comment delete might clear forms?
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::DeleteComment,
|
|
||||||
comment: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for RemoveComment {
|
|
||||||
type Response = CommentResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
|
||||||
let data: &RemoveComment = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let orig_comment = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(&conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
check_community_ban(
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_comment.community.id,
|
|
||||||
context.pool(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Verify that only a mod or admin can remove
|
|
||||||
is_mod_or_admin(
|
|
||||||
context.pool(),
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_comment.community.id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Do the remove
|
|
||||||
let removed = data.removed;
|
|
||||||
let updated_comment = match blocking(context.pool(), move |conn| {
|
|
||||||
Comment::update_removed(conn, comment_id, removed)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(comment) => comment,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mod tables
|
|
||||||
let form = ModRemoveCommentForm {
|
|
||||||
mod_person_id: local_user_view.person.id,
|
|
||||||
comment_id: data.comment_id,
|
|
||||||
removed: Some(removed),
|
|
||||||
reason: data.reason.to_owned(),
|
|
||||||
};
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
ModRemoveComment::create(conn, &form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Send the apub message
|
|
||||||
if removed {
|
|
||||||
updated_comment
|
|
||||||
.send_remove(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_comment
|
|
||||||
.send_undo_remove(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refetch it
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(conn, comment_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Build the recipients
|
|
||||||
let comment_view_2 = comment_view.clone();
|
|
||||||
|
|
||||||
let mentions = vec![];
|
|
||||||
let recipient_ids = send_local_notifs(
|
|
||||||
mentions,
|
|
||||||
updated_comment,
|
|
||||||
local_user_view.person.clone(),
|
|
||||||
comment_view_2.post,
|
|
||||||
context.pool(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let res = CommentResponse {
|
|
||||||
comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
form_id: None, // TODO maybe this might clear other forms
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendComment {
|
|
||||||
op: UserOperation::RemoveComment,
|
|
||||||
comment: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for MarkCommentAsRead {
|
impl Perform for MarkCommentAsRead {
|
||||||
|
@ -671,208 +217,3 @@ impl Perform for CreateCommentLike {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetComments {
|
|
||||||
type Response = GetCommentsResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetCommentsResponse, LemmyError> {
|
|
||||||
let data: &GetComments = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
let person_id = local_user_view.map(|u| u.person.id);
|
|
||||||
|
|
||||||
let type_ = ListingType::from_str(&data.type_)?;
|
|
||||||
let sort = SortType::from_str(&data.sort)?;
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let community_name = data.community_name.to_owned();
|
|
||||||
let saved_only = data.saved_only;
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let comments = blocking(context.pool(), move |conn| {
|
|
||||||
CommentQueryBuilder::create(conn)
|
|
||||||
.listing_type(type_)
|
|
||||||
.sort(&sort)
|
|
||||||
.saved_only(saved_only)
|
|
||||||
.community_id(community_id)
|
|
||||||
.community_name(community_name)
|
|
||||||
.my_person_id(person_id)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let comments = match comments {
|
|
||||||
Ok(comments) => comments,
|
|
||||||
Err(_) => return Err(ApiError::err("couldnt_get_comments").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(GetCommentsResponse { comments })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a comment report and notifies the moderators of the community
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreateCommentReport {
|
|
||||||
type Response = CreateCommentReportResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CreateCommentReportResponse, LemmyError> {
|
|
||||||
let data: &CreateCommentReport = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// check size of report and check for whitespace
|
|
||||||
let reason = data.reason.trim();
|
|
||||||
if reason.is_empty() {
|
|
||||||
return Err(ApiError::err("report_reason_required").into());
|
|
||||||
}
|
|
||||||
if reason.chars().count() > 1000 {
|
|
||||||
return Err(ApiError::err("report_too_long").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommentView::read(&conn, comment_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
check_community_ban(person_id, comment_view.community.id, context.pool()).await?;
|
|
||||||
|
|
||||||
let report_form = CommentReportForm {
|
|
||||||
creator_id: person_id,
|
|
||||||
comment_id,
|
|
||||||
original_comment_text: comment_view.comment.content,
|
|
||||||
reason: data.reason.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let report = match blocking(context.pool(), move |conn| {
|
|
||||||
CommentReport::report(conn, &report_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(report) => report,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_report").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = CreateCommentReportResponse { success: true };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::CreateCommentReport,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id: local_user_view.local_user.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendModRoomMessage {
|
|
||||||
op: UserOperation::CreateCommentReport,
|
|
||||||
response: report,
|
|
||||||
community_id: comment_view.community.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves or unresolves a comment report and notifies the moderators of the community
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for ResolveCommentReport {
|
|
||||||
type Response = ResolveCommentReportResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<ResolveCommentReportResponse, LemmyError> {
|
|
||||||
let data: &ResolveCommentReport = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let report_id = data.report_id;
|
|
||||||
let report = blocking(context.pool(), move |conn| {
|
|
||||||
CommentReportView::read(&conn, report_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
|
|
||||||
|
|
||||||
let resolved = data.resolved;
|
|
||||||
let resolve_fun = move |conn: &'_ _| {
|
|
||||||
if resolved {
|
|
||||||
CommentReport::resolve(conn, report_id, person_id)
|
|
||||||
} else {
|
|
||||||
CommentReport::unresolve(conn, report_id, person_id)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if blocking(context.pool(), resolve_fun).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_resolve_report").into());
|
|
||||||
};
|
|
||||||
|
|
||||||
let report_id = data.report_id;
|
|
||||||
let res = ResolveCommentReportResponse {
|
|
||||||
report_id,
|
|
||||||
resolved,
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendModRoomMessage {
|
|
||||||
op: UserOperation::ResolveCommentReport,
|
|
||||||
response: res.clone(),
|
|
||||||
community_id: report.community.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists comment reports for a community if an id is supplied
|
|
||||||
/// or returns all comment reports for communities a user moderates
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for ListCommentReports {
|
|
||||||
type Response = ListCommentReportsResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<ListCommentReportsResponse, LemmyError> {
|
|
||||||
let data: &ListCommentReports = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let community_id = data.community;
|
|
||||||
let community_ids =
|
|
||||||
collect_moderated_communities(person_id, community_id, context.pool()).await?;
|
|
||||||
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let comments = blocking(context.pool(), move |conn| {
|
|
||||||
CommentReportQueryBuilder::create(conn)
|
|
||||||
.community_ids(community_ids)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = ListCommentReportsResponse { comments };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::ListCommentReports,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id: local_user_view.local_user.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
184
crates/api/src/comment_report.rs
Normal file
184
crates/api/src/comment_report.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_community_ban,
|
||||||
|
collect_moderated_communities,
|
||||||
|
comment::*,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
is_mod_or_admin,
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::Reportable;
|
||||||
|
use lemmy_db_schema::source::comment_report::*;
|
||||||
|
use lemmy_db_views::{
|
||||||
|
comment_report_view::{CommentReportQueryBuilder, CommentReportView},
|
||||||
|
comment_view::CommentView,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{
|
||||||
|
messages::{SendModRoomMessage, SendUserRoomMessage},
|
||||||
|
LemmyContext,
|
||||||
|
UserOperation,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a comment report and notifies the moderators of the community
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for CreateCommentReport {
|
||||||
|
type Response = CreateCommentReportResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CreateCommentReportResponse, LemmyError> {
|
||||||
|
let data: &CreateCommentReport = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// check size of report and check for whitespace
|
||||||
|
let reason = data.reason.trim();
|
||||||
|
if reason.is_empty() {
|
||||||
|
return Err(ApiError::err("report_reason_required").into());
|
||||||
|
}
|
||||||
|
if reason.chars().count() > 1000 {
|
||||||
|
return Err(ApiError::err("report_too_long").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(&conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
check_community_ban(person_id, comment_view.community.id, context.pool()).await?;
|
||||||
|
|
||||||
|
let report_form = CommentReportForm {
|
||||||
|
creator_id: person_id,
|
||||||
|
comment_id,
|
||||||
|
original_comment_text: comment_view.comment.content,
|
||||||
|
reason: data.reason.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let report = match blocking(context.pool(), move |conn| {
|
||||||
|
CommentReport::report(conn, &report_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(report) => report,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_report").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = CreateCommentReportResponse { success: true };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::CreateCommentReport,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id: local_user_view.local_user.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendModRoomMessage {
|
||||||
|
op: UserOperation::CreateCommentReport,
|
||||||
|
response: report,
|
||||||
|
community_id: comment_view.community.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves or unresolves a comment report and notifies the moderators of the community
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ResolveCommentReport {
|
||||||
|
type Response = ResolveCommentReportResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<ResolveCommentReportResponse, LemmyError> {
|
||||||
|
let data: &ResolveCommentReport = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let report_id = data.report_id;
|
||||||
|
let report = blocking(context.pool(), move |conn| {
|
||||||
|
CommentReportView::read(&conn, report_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
|
||||||
|
|
||||||
|
let resolved = data.resolved;
|
||||||
|
let resolve_fun = move |conn: &'_ _| {
|
||||||
|
if resolved {
|
||||||
|
CommentReport::resolve(conn, report_id, person_id)
|
||||||
|
} else {
|
||||||
|
CommentReport::unresolve(conn, report_id, person_id)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if blocking(context.pool(), resolve_fun).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_resolve_report").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let report_id = data.report_id;
|
||||||
|
let res = ResolveCommentReportResponse {
|
||||||
|
report_id,
|
||||||
|
resolved,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendModRoomMessage {
|
||||||
|
op: UserOperation::ResolveCommentReport,
|
||||||
|
response: res.clone(),
|
||||||
|
community_id: report.community.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists comment reports for a community if an id is supplied
|
||||||
|
/// or returns all comment reports for communities a user moderates
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ListCommentReports {
|
||||||
|
type Response = ListCommentReportsResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<ListCommentReportsResponse, LemmyError> {
|
||||||
|
let data: &ListCommentReports = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let community_id = data.community;
|
||||||
|
let community_ids =
|
||||||
|
collect_moderated_communities(person_id, community_id, context.pool()).await?;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let comments = blocking(context.pool(), move |conn| {
|
||||||
|
CommentReportQueryBuilder::create(conn)
|
||||||
|
.community_ids(community_ids)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = ListCommentReportsResponse { comments };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::ListCommentReports,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id: local_user_view.local_user.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,483 +1,41 @@
|
||||||
use crate::{
|
use crate::Perform;
|
||||||
check_community_ban,
|
|
||||||
get_local_user_view_from_jwt,
|
|
||||||
get_local_user_view_from_jwt_opt,
|
|
||||||
is_admin,
|
|
||||||
is_mod_or_admin,
|
|
||||||
Perform,
|
|
||||||
};
|
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::{blocking, community::*};
|
use lemmy_api_common::{
|
||||||
use lemmy_apub::{
|
blocking,
|
||||||
generate_apub_endpoint,
|
check_community_ban,
|
||||||
generate_followers_url,
|
community::*,
|
||||||
generate_inbox_url,
|
get_local_user_view_from_jwt,
|
||||||
generate_shared_inbox_url,
|
is_mod_or_admin,
|
||||||
ActorType,
|
|
||||||
CommunityType,
|
|
||||||
EndpointType,
|
|
||||||
UserType,
|
|
||||||
};
|
};
|
||||||
|
use lemmy_apub::{ActorType, CommunityType, UserType};
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
diesel_option_overwrite_to_url,
|
|
||||||
source::{
|
source::{
|
||||||
comment::Comment_,
|
comment::Comment_,
|
||||||
community::{CommunityModerator_, Community_},
|
community::{CommunityModerator_, Community_},
|
||||||
post::Post_,
|
post::Post_,
|
||||||
},
|
},
|
||||||
ApubObject,
|
|
||||||
Bannable,
|
Bannable,
|
||||||
Crud,
|
Crud,
|
||||||
Followable,
|
Followable,
|
||||||
Joinable,
|
Joinable,
|
||||||
ListingType,
|
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::source::{
|
||||||
naive_now,
|
comment::Comment,
|
||||||
source::{comment::Comment, community::*, moderator::*, person::Person, post::Post, site::*},
|
community::*,
|
||||||
PersonId,
|
moderator::*,
|
||||||
|
person::Person,
|
||||||
|
post::Post,
|
||||||
|
site::*,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
community_follower_view::CommunityFollowerView,
|
|
||||||
community_moderator_view::CommunityModeratorView,
|
community_moderator_view::CommunityModeratorView,
|
||||||
community_view::{CommunityQueryBuilder, CommunityView},
|
community_view::CommunityView,
|
||||||
person_view::PersonViewSafe,
|
person_view::PersonViewSafe,
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{location_info, utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
|
||||||
apub::generate_actor_keypair,
|
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||||
location_info,
|
|
||||||
utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix},
|
|
||||||
ApiError,
|
|
||||||
ConnectionId,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::{
|
|
||||||
messages::{GetCommunityUsersOnline, SendCommunityRoomMessage},
|
|
||||||
LemmyContext,
|
|
||||||
UserOperation,
|
|
||||||
};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetCommunity {
|
|
||||||
type Response = GetCommunityResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetCommunityResponse, LemmyError> {
|
|
||||||
let data: &GetCommunity = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
let person_id = local_user_view.map(|u| u.person.id);
|
|
||||||
|
|
||||||
let community_id = match data.id {
|
|
||||||
Some(id) => id,
|
|
||||||
None => {
|
|
||||||
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
|
|
||||||
match blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_name(conn, &name)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
|
||||||
}
|
|
||||||
.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_view = match blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, person_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
|
|
||||||
CommunityModeratorView::for_community(conn, community_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(moderators) => moderators,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let online = context
|
|
||||||
.chat_server()
|
|
||||||
.send(GetCommunityUsersOnline { community_id })
|
|
||||||
.await
|
|
||||||
.unwrap_or(1);
|
|
||||||
|
|
||||||
let res = GetCommunityResponse {
|
|
||||||
community_view,
|
|
||||||
moderators,
|
|
||||||
online,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the jwt
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreateCommunity {
|
|
||||||
type Response = CommunityResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommunityResponse, LemmyError> {
|
|
||||||
let data: &CreateCommunity = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
check_slurs(&data.name)?;
|
|
||||||
check_slurs(&data.title)?;
|
|
||||||
check_slurs_opt(&data.description)?;
|
|
||||||
|
|
||||||
if !is_valid_community_name(&data.name) {
|
|
||||||
return Err(ApiError::err("invalid_community_name").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double check for duplicate community actor_ids
|
|
||||||
let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?;
|
|
||||||
let actor_id_cloned = community_actor_id.to_owned();
|
|
||||||
let community_dupe = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read_from_apub_id(conn, &actor_id_cloned)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
if community_dupe.is_ok() {
|
|
||||||
return Err(ApiError::err("community_already_exists").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to make sure the icon and banners are urls
|
|
||||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
|
||||||
|
|
||||||
// When you create a community, make sure the user becomes a moderator and a follower
|
|
||||||
let keypair = generate_actor_keypair()?;
|
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
|
||||||
name: data.name.to_owned(),
|
|
||||||
title: data.title.to_owned(),
|
|
||||||
description: data.description.to_owned(),
|
|
||||||
icon,
|
|
||||||
banner,
|
|
||||||
creator_id: local_user_view.person.id,
|
|
||||||
removed: None,
|
|
||||||
deleted: None,
|
|
||||||
nsfw: data.nsfw,
|
|
||||||
updated: None,
|
|
||||||
actor_id: Some(community_actor_id.to_owned()),
|
|
||||||
local: true,
|
|
||||||
private_key: Some(keypair.private_key),
|
|
||||||
public_key: Some(keypair.public_key),
|
|
||||||
last_refreshed_at: None,
|
|
||||||
published: None,
|
|
||||||
followers_url: Some(generate_followers_url(&community_actor_id)?),
|
|
||||||
inbox_url: Some(generate_inbox_url(&community_actor_id)?),
|
|
||||||
shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_community = match blocking(context.pool(), move |conn| {
|
|
||||||
Community::create(conn, &community_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("community_already_exists").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// The community creator becomes a moderator
|
|
||||||
let community_moderator_form = CommunityModeratorForm {
|
|
||||||
community_id: inserted_community.id,
|
|
||||||
person_id: local_user_view.person.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
|
||||||
if blocking(context.pool(), join).await?.is_err() {
|
|
||||||
return Err(ApiError::err("community_moderator_already_exists").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow your own community
|
|
||||||
let community_follower_form = CommunityFollowerForm {
|
|
||||||
community_id: inserted_community.id,
|
|
||||||
person_id: local_user_view.person.id,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
|
||||||
if blocking(context.pool(), follow).await?.is_err() {
|
|
||||||
return Err(ApiError::err("community_follower_already_exists").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let community_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, inserted_community.id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(CommunityResponse { community_view })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for EditCommunity {
|
|
||||||
type Response = CommunityResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommunityResponse, LemmyError> {
|
|
||||||
let data: &EditCommunity = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
check_slurs(&data.title)?;
|
|
||||||
check_slurs_opt(&data.description)?;
|
|
||||||
|
|
||||||
// Verify its a mod (only mods can edit it)
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let mods: Vec<PersonId> = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityModeratorView::for_community(conn, community_id)
|
|
||||||
.map(|v| v.into_iter().map(|m| m.moderator.id).collect())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
if !mods.contains(&local_user_view.person.id) {
|
|
||||||
return Err(ApiError::err("not_a_moderator").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let read_community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read(conn, community_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
|
||||||
name: read_community.name,
|
|
||||||
title: data.title.to_owned(),
|
|
||||||
description: data.description.to_owned(),
|
|
||||||
icon,
|
|
||||||
banner,
|
|
||||||
creator_id: read_community.creator_id,
|
|
||||||
removed: Some(read_community.removed),
|
|
||||||
deleted: Some(read_community.deleted),
|
|
||||||
nsfw: data.nsfw,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
actor_id: Some(read_community.actor_id),
|
|
||||||
local: read_community.local,
|
|
||||||
private_key: read_community.private_key,
|
|
||||||
public_key: read_community.public_key,
|
|
||||||
last_refreshed_at: None,
|
|
||||||
published: None,
|
|
||||||
followers_url: None,
|
|
||||||
inbox_url: None,
|
|
||||||
shared_inbox_url: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
|
||||||
match blocking(context.pool(), move |conn| {
|
|
||||||
Community::update(conn, community_id, &community_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO there needs to be some kind of an apub update
|
|
||||||
// process for communities and users
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let community_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = CommunityResponse { community_view };
|
|
||||||
|
|
||||||
send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for DeleteCommunity {
|
|
||||||
type Response = CommunityResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommunityResponse, LemmyError> {
|
|
||||||
let data: &DeleteCommunity = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// Verify its the creator (only a creator can delete the community)
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let read_community = blocking(context.pool(), move |conn| {
|
|
||||||
Community::read(conn, community_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
if read_community.creator_id != local_user_view.person.id {
|
|
||||||
return Err(ApiError::err("no_community_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the delete
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let deleted = data.deleted;
|
|
||||||
let updated_community = match blocking(context.pool(), move |conn| {
|
|
||||||
Community::update_deleted(conn, community_id, deleted)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send apub messages
|
|
||||||
if deleted {
|
|
||||||
updated_community.send_delete(context).await?;
|
|
||||||
} else {
|
|
||||||
updated_community.send_undo_delete(context).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let community_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = CommunityResponse { community_view };
|
|
||||||
|
|
||||||
send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for RemoveCommunity {
|
|
||||||
type Response = CommunityResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CommunityResponse, LemmyError> {
|
|
||||||
let data: &RemoveCommunity = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// Verify its an admin (only an admin can remove a community)
|
|
||||||
is_admin(&local_user_view)?;
|
|
||||||
|
|
||||||
// Do the remove
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let removed = data.removed;
|
|
||||||
let updated_community = match blocking(context.pool(), move |conn| {
|
|
||||||
Community::update_removed(conn, community_id, removed)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mod tables
|
|
||||||
let expires = match data.expires {
|
|
||||||
Some(time) => Some(naive_from_unix(time)),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let form = ModRemoveCommunityForm {
|
|
||||||
mod_person_id: local_user_view.person.id,
|
|
||||||
community_id: data.community_id,
|
|
||||||
removed: Some(removed),
|
|
||||||
reason: data.reason.to_owned(),
|
|
||||||
expires,
|
|
||||||
};
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
ModRemoveCommunity::create(conn, &form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Apub messages
|
|
||||||
if removed {
|
|
||||||
updated_community.send_remove(context).await?;
|
|
||||||
} else {
|
|
||||||
updated_community.send_undo_remove(context).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let community_view = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = CommunityResponse { community_view };
|
|
||||||
|
|
||||||
send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for ListCommunities {
|
|
||||||
type Response = ListCommunitiesResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<ListCommunitiesResponse, LemmyError> {
|
|
||||||
let data: &ListCommunities = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let person_id = match &local_user_view {
|
|
||||||
Some(uv) => Some(uv.person.id),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Don't show NSFW by default
|
|
||||||
let show_nsfw = match &local_user_view {
|
|
||||||
Some(uv) => uv.local_user.show_nsfw,
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let type_ = ListingType::from_str(&data.type_)?;
|
|
||||||
let sort = SortType::from_str(&data.sort)?;
|
|
||||||
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let communities = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityQueryBuilder::create(conn)
|
|
||||||
.listing_type(&type_)
|
|
||||||
.sort(&sort)
|
|
||||||
.show_nsfw(show_nsfw)
|
|
||||||
.my_person_id(person_id)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Return the jwt
|
|
||||||
Ok(ListCommunitiesResponse { communities })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for FollowCommunity {
|
impl Perform for FollowCommunity {
|
||||||
|
@ -553,33 +111,6 @@ impl Perform for FollowCommunity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetFollowedCommunities {
|
|
||||||
type Response = GetFollowedCommunitiesResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
|
|
||||||
let data: &GetFollowedCommunities = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let communities = match blocking(context.pool(), move |conn| {
|
|
||||||
CommunityFollowerView::for_person(conn, person_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(communities) => communities,
|
|
||||||
_ => return Err(ApiError::err("system_err_login").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the jwt
|
|
||||||
Ok(GetFollowedCommunitiesResponse { communities })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for BanFromCommunity {
|
impl Perform for BanFromCommunity {
|
||||||
type Response = BanFromCommunityResponse;
|
type Response = BanFromCommunityResponse;
|
||||||
|
@ -907,21 +438,3 @@ impl Perform for TransferCommunity {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_community_websocket(
|
|
||||||
res: &CommunityResponse,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
op: UserOperation,
|
|
||||||
) {
|
|
||||||
// Strip out the person id and subscribed when sending to others
|
|
||||||
let mut res_sent = res.clone();
|
|
||||||
res_sent.community_view.subscribed = false;
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
|
||||||
op,
|
|
||||||
response: res_sent,
|
|
||||||
community_id: res.community_view.community.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,56 +1,20 @@
|
||||||
use actix_web::{web, web::Data};
|
use actix_web::{web, web::Data};
|
||||||
use lemmy_api_structs::{
|
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
|
||||||
blocking,
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
comment::*,
|
|
||||||
community::*,
|
|
||||||
person::*,
|
|
||||||
post::*,
|
|
||||||
site::*,
|
|
||||||
websocket::*,
|
|
||||||
};
|
|
||||||
use lemmy_db_queries::{
|
|
||||||
source::{
|
|
||||||
community::{CommunityModerator_, Community_},
|
|
||||||
site::Site_,
|
|
||||||
},
|
|
||||||
Crud,
|
|
||||||
DbPool,
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
source::{
|
|
||||||
community::{Community, CommunityModerator},
|
|
||||||
post::Post,
|
|
||||||
site::Site,
|
|
||||||
},
|
|
||||||
CommunityId,
|
|
||||||
LocalUserId,
|
|
||||||
PersonId,
|
|
||||||
PostId,
|
|
||||||
};
|
|
||||||
use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
|
|
||||||
use lemmy_db_views_actor::{
|
|
||||||
community_person_ban_view::CommunityPersonBanView,
|
|
||||||
community_view::CommunityView,
|
|
||||||
};
|
|
||||||
use lemmy_utils::{
|
|
||||||
claims::Claims,
|
|
||||||
settings::structs::Settings,
|
|
||||||
ApiError,
|
|
||||||
ConnectionId,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
|
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{env, process::Command};
|
use std::{env, process::Command};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
pub mod comment;
|
mod comment;
|
||||||
pub mod community;
|
mod comment_report;
|
||||||
pub mod local_user;
|
mod community;
|
||||||
pub mod post;
|
mod local_user;
|
||||||
|
mod post;
|
||||||
|
mod post_report;
|
||||||
|
mod private_message;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod site;
|
mod site;
|
||||||
pub mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
pub trait Perform {
|
pub trait Perform {
|
||||||
|
@ -63,221 +27,35 @@ pub trait Perform {
|
||||||
) -> Result<Self::Response, LemmyError>;
|
) -> Result<Self::Response, LemmyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn is_mod_or_admin(
|
|
||||||
pool: &DbPool,
|
|
||||||
person_id: PersonId,
|
|
||||||
community_id: CommunityId,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let is_mod_or_admin = blocking(pool, move |conn| {
|
|
||||||
CommunityView::is_mod_or_admin(conn, person_id, community_id)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
if !is_mod_or_admin {
|
|
||||||
return Err(ApiError::err("not_a_mod_or_admin").into());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
|
||||||
if !local_user_view.local_user.admin {
|
|
||||||
return Err(ApiError::err("not_an_admin").into());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
|
|
||||||
match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
|
|
||||||
Ok(post) => Ok(post),
|
|
||||||
Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_local_user_view_from_jwt(
|
|
||||||
jwt: &str,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<LocalUserView, LemmyError> {
|
|
||||||
let claims = match Claims::decode(&jwt) {
|
|
||||||
Ok(claims) => claims.claims,
|
|
||||||
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
|
|
||||||
};
|
|
||||||
let local_user_id = LocalUserId(claims.sub);
|
|
||||||
let local_user_view =
|
|
||||||
blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
|
|
||||||
// Check for a site ban
|
|
||||||
if local_user_view.person.banned {
|
|
||||||
return Err(ApiError::err("site_ban").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
|
|
||||||
|
|
||||||
Ok(local_user_view)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if user's token was issued before user's password reset.
|
|
||||||
pub(crate) fn check_validator_time(
|
|
||||||
validator_time: &chrono::NaiveDateTime,
|
|
||||||
claims: &Claims,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let user_validation_time = validator_time.timestamp();
|
|
||||||
if user_validation_time > claims.iat {
|
|
||||||
Err(ApiError::err("not_logged_in").into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_local_user_view_from_jwt_opt(
|
|
||||||
jwt: &Option<String>,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<Option<LocalUserView>, LemmyError> {
|
|
||||||
match jwt {
|
|
||||||
Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_local_user_settings_view_from_jwt(
|
|
||||||
jwt: &str,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<LocalUserSettingsView, LemmyError> {
|
|
||||||
let claims = match Claims::decode(&jwt) {
|
|
||||||
Ok(claims) => claims.claims,
|
|
||||||
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
|
|
||||||
};
|
|
||||||
let local_user_id = LocalUserId(claims.sub);
|
|
||||||
let local_user_view = blocking(pool, move |conn| {
|
|
||||||
LocalUserSettingsView::read(conn, local_user_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
// Check for a site ban
|
|
||||||
if local_user_view.person.banned {
|
|
||||||
return Err(ApiError::err("site_ban").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
|
|
||||||
|
|
||||||
Ok(local_user_view)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_local_user_settings_view_from_jwt_opt(
|
|
||||||
jwt: &Option<String>,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<Option<LocalUserSettingsView>, LemmyError> {
|
|
||||||
match jwt {
|
|
||||||
Some(jwt) => Ok(Some(
|
|
||||||
get_local_user_settings_view_from_jwt(jwt, pool).await?,
|
|
||||||
)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn check_community_ban(
|
|
||||||
person_id: PersonId,
|
|
||||||
community_id: CommunityId,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
let is_banned =
|
|
||||||
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
|
|
||||||
if blocking(pool, is_banned).await? {
|
|
||||||
Err(ApiError::err("community_ban").into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
|
|
||||||
if score == -1 {
|
|
||||||
let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
|
|
||||||
if !site.enable_downvotes {
|
|
||||||
return Err(ApiError::err("downvotes_disabled").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a list of communities that the user moderates
|
|
||||||
/// or if a community_id is supplied validates the user is a moderator
|
|
||||||
/// of that community and returns the community id in a vec
|
|
||||||
///
|
|
||||||
/// * `person_id` - the person id of the moderator
|
|
||||||
/// * `community_id` - optional community id to check for moderator privileges
|
|
||||||
/// * `pool` - the diesel db pool
|
|
||||||
pub(crate) async fn collect_moderated_communities(
|
|
||||||
person_id: PersonId,
|
|
||||||
community_id: Option<CommunityId>,
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<Vec<CommunityId>, LemmyError> {
|
|
||||||
if let Some(community_id) = community_id {
|
|
||||||
// if the user provides a community_id, just check for mod/admin privileges
|
|
||||||
is_mod_or_admin(pool, person_id, community_id).await?;
|
|
||||||
Ok(vec![community_id])
|
|
||||||
} else {
|
|
||||||
let ids = blocking(pool, move |conn: &'_ _| {
|
|
||||||
CommunityModerator::get_person_moderated_communities(conn, person_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(ids)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn build_federated_instances(
|
|
||||||
pool: &DbPool,
|
|
||||||
) -> Result<Option<FederatedInstances>, LemmyError> {
|
|
||||||
if Settings::get().federation().enabled {
|
|
||||||
let distinct_communities = blocking(pool, move |conn| {
|
|
||||||
Community::distinct_federated_communities(conn)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let allowed = Settings::get().get_allowed_instances();
|
|
||||||
let blocked = Settings::get().get_blocked_instances();
|
|
||||||
|
|
||||||
let mut linked = distinct_communities
|
|
||||||
.iter()
|
|
||||||
.map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
|
|
||||||
.collect::<Result<Vec<String>, LemmyError>>()?;
|
|
||||||
|
|
||||||
if let Some(allowed) = allowed.as_ref() {
|
|
||||||
linked.extend_from_slice(allowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(blocked) = blocked.as_ref() {
|
|
||||||
linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort and remove dupes
|
|
||||||
linked.sort_unstable();
|
|
||||||
linked.dedup();
|
|
||||||
|
|
||||||
Ok(Some(FederatedInstances {
|
|
||||||
linked,
|
|
||||||
allowed,
|
|
||||||
blocked,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn match_websocket_operation(
|
pub async fn match_websocket_operation(
|
||||||
context: LemmyContext,
|
context: LemmyContext,
|
||||||
id: ConnectionId,
|
id: ConnectionId,
|
||||||
op: UserOperation,
|
op: UserOperation,
|
||||||
data: &str,
|
data: &str,
|
||||||
) -> Result<String, LemmyError> {
|
) -> Result<String, LemmyError> {
|
||||||
|
//TODO: handle commented out actions in crud crate
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
// User ops
|
// User ops
|
||||||
UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
|
UserOperation::Login => {
|
||||||
UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
|
//do_websocket_operation::<Login>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::Register => {
|
||||||
|
//do_websocket_operation::<Register>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
|
UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
|
||||||
UserOperation::GetPersonDetails => {
|
UserOperation::GetPersonDetails => {
|
||||||
do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
|
//do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
|
UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
|
||||||
UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
|
UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
|
||||||
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
|
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
|
||||||
UserOperation::GetPersonMentions => {
|
UserOperation::GetPersonMentions => {
|
||||||
do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
|
//do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::MarkPersonMentionAsRead => {
|
UserOperation::MarkPersonMentionAsRead => {
|
||||||
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
|
||||||
|
@ -286,7 +64,8 @@ pub async fn match_websocket_operation(
|
||||||
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
UserOperation::DeleteAccount => {
|
UserOperation::DeleteAccount => {
|
||||||
do_websocket_operation::<DeleteAccount>(context, id, op, data).await
|
//do_websocket_operation::<DeleteAccount>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::PasswordReset => {
|
UserOperation::PasswordReset => {
|
||||||
do_websocket_operation::<PasswordReset>(context, id, op, data).await
|
do_websocket_operation::<PasswordReset>(context, id, op, data).await
|
||||||
|
@ -309,26 +88,39 @@ pub async fn match_websocket_operation(
|
||||||
|
|
||||||
// Private Message ops
|
// Private Message ops
|
||||||
UserOperation::CreatePrivateMessage => {
|
UserOperation::CreatePrivateMessage => {
|
||||||
do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
|
//do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::EditPrivateMessage => {
|
UserOperation::EditPrivateMessage => {
|
||||||
do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
|
//do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::DeletePrivateMessage => {
|
UserOperation::DeletePrivateMessage => {
|
||||||
do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
|
//do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::MarkPrivateMessageAsRead => {
|
UserOperation::MarkPrivateMessageAsRead => {
|
||||||
do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
UserOperation::GetPrivateMessages => {
|
UserOperation::GetPrivateMessages => {
|
||||||
do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
|
//do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Site ops
|
// Site ops
|
||||||
UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
|
UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
|
||||||
UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
|
UserOperation::CreateSite => {
|
||||||
UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
|
//do_websocket_operation::<CreateSite>(context, id, op, data).await
|
||||||
UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::EditSite => {
|
||||||
|
//do_websocket_operation::<EditSite>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::GetSite => {
|
||||||
|
//do_websocket_operation::<GetSite>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
UserOperation::GetSiteConfig => {
|
UserOperation::GetSiteConfig => {
|
||||||
do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
|
do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
@ -345,22 +137,28 @@ pub async fn match_websocket_operation(
|
||||||
|
|
||||||
// Community ops
|
// Community ops
|
||||||
UserOperation::GetCommunity => {
|
UserOperation::GetCommunity => {
|
||||||
do_websocket_operation::<GetCommunity>(context, id, op, data).await
|
//do_websocket_operation::<GetCommunity>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::ListCommunities => {
|
UserOperation::ListCommunities => {
|
||||||
do_websocket_operation::<ListCommunities>(context, id, op, data).await
|
//do_websocket_operation::<ListCommunities>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::CreateCommunity => {
|
UserOperation::CreateCommunity => {
|
||||||
do_websocket_operation::<CreateCommunity>(context, id, op, data).await
|
//do_websocket_operation::<CreateCommunity>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::EditCommunity => {
|
UserOperation::EditCommunity => {
|
||||||
do_websocket_operation::<EditCommunity>(context, id, op, data).await
|
//do_websocket_operation::<EditCommunity>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::DeleteCommunity => {
|
UserOperation::DeleteCommunity => {
|
||||||
do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
|
//do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::RemoveCommunity => {
|
UserOperation::RemoveCommunity => {
|
||||||
do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
|
//do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::FollowCommunity => {
|
UserOperation::FollowCommunity => {
|
||||||
do_websocket_operation::<FollowCommunity>(context, id, op, data).await
|
do_websocket_operation::<FollowCommunity>(context, id, op, data).await
|
||||||
|
@ -376,12 +174,30 @@ pub async fn match_websocket_operation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post ops
|
// Post ops
|
||||||
UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
|
UserOperation::CreatePost => {
|
||||||
UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
|
//do_websocket_operation::<CreatePost>(context, id, op, data).await
|
||||||
UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
|
todo!()
|
||||||
UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
|
}
|
||||||
UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
|
UserOperation::GetPost => {
|
||||||
UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
|
//do_websocket_operation::<GetPost>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::GetPosts => {
|
||||||
|
//do_websocket_operation::<GetPosts>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::EditPost => {
|
||||||
|
//do_websocket_operation::<EditPost>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::DeletePost => {
|
||||||
|
//do_websocket_operation::<DeletePost>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
UserOperation::RemovePost => {
|
||||||
|
//do_websocket_operation::<RemovePost>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
|
UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
|
||||||
UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
|
UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
|
||||||
UserOperation::CreatePostLike => {
|
UserOperation::CreatePostLike => {
|
||||||
|
@ -400,16 +216,20 @@ pub async fn match_websocket_operation(
|
||||||
|
|
||||||
// Comment ops
|
// Comment ops
|
||||||
UserOperation::CreateComment => {
|
UserOperation::CreateComment => {
|
||||||
do_websocket_operation::<CreateComment>(context, id, op, data).await
|
//do_websocket_operation::<CreateComment>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::EditComment => {
|
UserOperation::EditComment => {
|
||||||
do_websocket_operation::<EditComment>(context, id, op, data).await
|
//do_websocket_operation::<EditComment>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::DeleteComment => {
|
UserOperation::DeleteComment => {
|
||||||
do_websocket_operation::<DeleteComment>(context, id, op, data).await
|
//do_websocket_operation::<DeleteComment>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::RemoveComment => {
|
UserOperation::RemoveComment => {
|
||||||
do_websocket_operation::<RemoveComment>(context, id, op, data).await
|
//do_websocket_operation::<RemoveComment>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::MarkCommentAsRead => {
|
UserOperation::MarkCommentAsRead => {
|
||||||
do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
|
||||||
|
@ -418,7 +238,8 @@ pub async fn match_websocket_operation(
|
||||||
do_websocket_operation::<SaveComment>(context, id, op, data).await
|
do_websocket_operation::<SaveComment>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
UserOperation::GetComments => {
|
UserOperation::GetComments => {
|
||||||
do_websocket_operation::<GetComments>(context, id, op, data).await
|
//do_websocket_operation::<GetComments>(context, id, op, data).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
UserOperation::CreateCommentLike => {
|
UserOperation::CreateCommentLike => {
|
||||||
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
|
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
|
||||||
|
@ -503,18 +324,10 @@ pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
|
||||||
Ok(base64)
|
Ok(base64)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the password length
|
|
||||||
pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
|
|
||||||
if pass.len() > 60 {
|
|
||||||
Err(ApiError::err("invalid_password").into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{captcha_espeak_wav_base64, check_validator_time};
|
use crate::{captcha_espeak_wav_base64, check_validator_time};
|
||||||
|
use lemmy_api_common::check_validator_time;
|
||||||
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
|
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
local_user::{LocalUser, LocalUserForm},
|
local_user::{LocalUser, LocalUserForm},
|
||||||
|
|
|
@ -1,25 +1,17 @@
|
||||||
use crate::{
|
use crate::{captcha_espeak_wav_base64, Perform};
|
||||||
captcha_espeak_wav_base64,
|
|
||||||
collect_moderated_communities,
|
|
||||||
get_local_user_view_from_jwt,
|
|
||||||
get_local_user_view_from_jwt_opt,
|
|
||||||
is_admin,
|
|
||||||
password_length_check,
|
|
||||||
Perform,
|
|
||||||
};
|
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bcrypt::verify;
|
use bcrypt::verify;
|
||||||
use captcha::{gen, Difficulty};
|
use captcha::{gen, Difficulty};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use lemmy_api_structs::{blocking, person::*, send_email_to_user};
|
use lemmy_api_common::{
|
||||||
use lemmy_apub::{
|
blocking,
|
||||||
generate_apub_endpoint,
|
collect_moderated_communities,
|
||||||
generate_followers_url,
|
community::{GetFollowedCommunities, GetFollowedCommunitiesResponse},
|
||||||
generate_inbox_url,
|
get_local_user_view_from_jwt,
|
||||||
generate_shared_inbox_url,
|
is_admin,
|
||||||
ApubObjectType,
|
password_length_check,
|
||||||
EndpointType,
|
person::*,
|
||||||
};
|
};
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
diesel_option_overwrite,
|
diesel_option_overwrite,
|
||||||
|
@ -33,12 +25,8 @@ use lemmy_db_queries::{
|
||||||
person_mention::PersonMention_,
|
person_mention::PersonMention_,
|
||||||
post::Post_,
|
post::Post_,
|
||||||
private_message::PrivateMessage_,
|
private_message::PrivateMessage_,
|
||||||
site::Site_,
|
|
||||||
},
|
},
|
||||||
Crud,
|
Crud,
|
||||||
Followable,
|
|
||||||
Joinable,
|
|
||||||
ListingType,
|
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -52,45 +40,33 @@ use lemmy_db_schema::{
|
||||||
person::*,
|
person::*,
|
||||||
person_mention::*,
|
person_mention::*,
|
||||||
post::Post,
|
post::Post,
|
||||||
private_message::*,
|
private_message::PrivateMessage,
|
||||||
site::*,
|
site::*,
|
||||||
},
|
},
|
||||||
CommunityId,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
comment_report_view::CommentReportView,
|
comment_report_view::CommentReportView,
|
||||||
comment_view::CommentQueryBuilder,
|
comment_view::CommentQueryBuilder,
|
||||||
local_user_view::LocalUserView,
|
local_user_view::LocalUserView,
|
||||||
post_report_view::PostReportView,
|
post_report_view::PostReportView,
|
||||||
post_view::PostQueryBuilder,
|
|
||||||
private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
community_follower_view::CommunityFollowerView,
|
community_follower_view::CommunityFollowerView,
|
||||||
community_moderator_view::CommunityModeratorView,
|
|
||||||
person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
|
person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
|
||||||
person_view::PersonViewSafe,
|
person_view::PersonViewSafe,
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
apub::generate_actor_keypair,
|
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
email::send_email,
|
email::send_email,
|
||||||
location_info,
|
location_info,
|
||||||
settings::structs::Settings,
|
settings::structs::Settings,
|
||||||
utils::{
|
utils::{generate_random_string, is_valid_preferred_username, naive_from_unix},
|
||||||
check_slurs,
|
|
||||||
generate_random_string,
|
|
||||||
is_valid_preferred_username,
|
|
||||||
is_valid_username,
|
|
||||||
naive_from_unix,
|
|
||||||
remove_slurs,
|
|
||||||
},
|
|
||||||
ApiError,
|
ApiError,
|
||||||
ConnectionId,
|
ConnectionId,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::{
|
||||||
messages::{CaptchaItem, CheckCaptcha, SendAllMessage, SendUserRoomMessage},
|
messages::{CaptchaItem, SendAllMessage, SendUserRoomMessage},
|
||||||
LemmyContext,
|
LemmyContext,
|
||||||
UserOperation,
|
UserOperation,
|
||||||
};
|
};
|
||||||
|
@ -135,212 +111,6 @@ impl Perform for Login {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for Register {
|
|
||||||
type Response = LoginResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<LoginResponse, LemmyError> {
|
|
||||||
let data: &Register = &self;
|
|
||||||
|
|
||||||
// Make sure site has open registration
|
|
||||||
if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
|
|
||||||
if !site.open_registration {
|
|
||||||
return Err(ApiError::err("registration_closed").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
password_length_check(&data.password)?;
|
|
||||||
|
|
||||||
// Make sure passwords match
|
|
||||||
if data.password != data.password_verify {
|
|
||||||
return Err(ApiError::err("passwords_dont_match").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there are admins. False if admins exist
|
|
||||||
let no_admins = blocking(context.pool(), move |conn| {
|
|
||||||
PersonViewSafe::admins(conn).map(|a| a.is_empty())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// If its not the admin, check the captcha
|
|
||||||
if !no_admins && Settings::get().captcha().enabled {
|
|
||||||
let check = context
|
|
||||||
.chat_server()
|
|
||||||
.send(CheckCaptcha {
|
|
||||||
uuid: data
|
|
||||||
.captcha_uuid
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| "".to_string()),
|
|
||||||
answer: data
|
|
||||||
.captcha_answer
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| "".to_string()),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
if !check {
|
|
||||||
return Err(ApiError::err("captcha_incorrect").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check_slurs(&data.username)?;
|
|
||||||
|
|
||||||
let actor_keypair = generate_actor_keypair()?;
|
|
||||||
if !is_valid_username(&data.username) {
|
|
||||||
return Err(ApiError::err("invalid_username").into());
|
|
||||||
}
|
|
||||||
let actor_id = generate_apub_endpoint(EndpointType::Person, &data.username)?;
|
|
||||||
|
|
||||||
// We have to create both a person, and local_user
|
|
||||||
|
|
||||||
// Register the new person
|
|
||||||
let person_form = PersonForm {
|
|
||||||
name: data.username.to_owned(),
|
|
||||||
avatar: None,
|
|
||||||
banner: None,
|
|
||||||
preferred_username: None,
|
|
||||||
published: None,
|
|
||||||
updated: None,
|
|
||||||
banned: None,
|
|
||||||
deleted: None,
|
|
||||||
actor_id: Some(actor_id.clone()),
|
|
||||||
bio: None,
|
|
||||||
local: Some(true),
|
|
||||||
private_key: Some(Some(actor_keypair.private_key)),
|
|
||||||
public_key: Some(Some(actor_keypair.public_key)),
|
|
||||||
last_refreshed_at: None,
|
|
||||||
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
|
||||||
shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// insert the person
|
|
||||||
let inserted_person = match blocking(context.pool(), move |conn| {
|
|
||||||
Person::create(conn, &person_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ApiError::err("user_already_exists").into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the local user
|
|
||||||
let local_user_form = LocalUserForm {
|
|
||||||
person_id: inserted_person.id,
|
|
||||||
email: Some(data.email.to_owned()),
|
|
||||||
matrix_user_id: None,
|
|
||||||
password_encrypted: data.password.to_owned(),
|
|
||||||
admin: Some(no_admins),
|
|
||||||
show_nsfw: Some(data.show_nsfw),
|
|
||||||
theme: Some("browser".into()),
|
|
||||||
default_sort_type: Some(SortType::Active as i16),
|
|
||||||
default_listing_type: Some(ListingType::Subscribed as i16),
|
|
||||||
lang: Some("browser".into()),
|
|
||||||
show_avatars: Some(true),
|
|
||||||
send_notifications_to_email: Some(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_local_user = match blocking(context.pool(), move |conn| {
|
|
||||||
LocalUser::register(conn, &local_user_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(lu) => lu,
|
|
||||||
Err(e) => {
|
|
||||||
let err_type = if e.to_string()
|
|
||||||
== "duplicate key value violates unique constraint \"local_user_email_key\""
|
|
||||||
{
|
|
||||||
"email_already_exists"
|
|
||||||
} else {
|
|
||||||
"user_already_exists"
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the local user creation errored, then delete that person
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Person::delete(&conn, inserted_person.id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
return Err(ApiError::err(err_type).into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_community_keypair = generate_actor_keypair()?;
|
|
||||||
|
|
||||||
// Create the main community if it doesn't exist
|
|
||||||
let main_community = match blocking(context.pool(), move |conn| {
|
|
||||||
Community::read(conn, CommunityId(2))
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(_e) => {
|
|
||||||
let default_community_name = "main";
|
|
||||||
let actor_id = generate_apub_endpoint(EndpointType::Community, default_community_name)?;
|
|
||||||
let community_form = CommunityForm {
|
|
||||||
name: default_community_name.to_string(),
|
|
||||||
title: "The Default Community".to_string(),
|
|
||||||
description: Some("The Default Community".to_string()),
|
|
||||||
nsfw: false,
|
|
||||||
creator_id: inserted_person.id,
|
|
||||||
removed: None,
|
|
||||||
deleted: None,
|
|
||||||
updated: None,
|
|
||||||
actor_id: Some(actor_id.to_owned()),
|
|
||||||
local: true,
|
|
||||||
private_key: Some(main_community_keypair.private_key),
|
|
||||||
public_key: Some(main_community_keypair.public_key),
|
|
||||||
last_refreshed_at: None,
|
|
||||||
published: None,
|
|
||||||
icon: None,
|
|
||||||
banner: None,
|
|
||||||
followers_url: Some(generate_followers_url(&actor_id)?),
|
|
||||||
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
|
||||||
shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
|
|
||||||
};
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Community::create(conn, &community_form)
|
|
||||||
})
|
|
||||||
.await??
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sign them up for main community no matter what
|
|
||||||
let community_follower_form = CommunityFollowerForm {
|
|
||||||
community_id: main_community.id,
|
|
||||||
person_id: inserted_person.id,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
|
||||||
if blocking(context.pool(), follow).await?.is_err() {
|
|
||||||
return Err(ApiError::err("community_follower_already_exists").into());
|
|
||||||
};
|
|
||||||
|
|
||||||
// If its an admin, add them as a mod and follower to main
|
|
||||||
if no_admins {
|
|
||||||
let community_moderator_form = CommunityModeratorForm {
|
|
||||||
community_id: main_community.id,
|
|
||||||
person_id: inserted_person.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
|
||||||
if blocking(context.pool(), join).await?.is_err() {
|
|
||||||
return Err(ApiError::err("community_moderator_already_exists").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the jwt
|
|
||||||
Ok(LoginResponse {
|
|
||||||
jwt: Claims::jwt(inserted_local_user.id.0)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for GetCaptcha {
|
impl Perform for GetCaptcha {
|
||||||
type Response = GetCaptchaResponse;
|
type Response = GetCaptchaResponse;
|
||||||
|
@ -531,114 +301,6 @@ impl Perform for SaveUserSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetPersonDetails {
|
|
||||||
type Response = GetPersonDetailsResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetPersonDetailsResponse, LemmyError> {
|
|
||||||
let data: &GetPersonDetails = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let show_nsfw = match &local_user_view {
|
|
||||||
Some(uv) => uv.local_user.show_nsfw,
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let sort = SortType::from_str(&data.sort)?;
|
|
||||||
|
|
||||||
let username = data
|
|
||||||
.username
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or_else(|| "admin".to_string());
|
|
||||||
let person_details_id = match data.person_id {
|
|
||||||
Some(id) => id,
|
|
||||||
None => {
|
|
||||||
let person = blocking(context.pool(), move |conn| {
|
|
||||||
Person::find_by_name(conn, &username)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
match person {
|
|
||||||
Ok(p) => p.id,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let person_id = local_user_view.map(|uv| uv.person.id);
|
|
||||||
|
|
||||||
// You don't need to return settings for the user, since this comes back with GetSite
|
|
||||||
// `my_user`
|
|
||||||
let person_view = blocking(context.pool(), move |conn| {
|
|
||||||
PersonViewSafe::read(conn, person_details_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let saved_only = data.saved_only;
|
|
||||||
let community_id = data.community_id;
|
|
||||||
|
|
||||||
let (posts, comments) = blocking(context.pool(), move |conn| {
|
|
||||||
let mut posts_query = PostQueryBuilder::create(conn)
|
|
||||||
.sort(&sort)
|
|
||||||
.show_nsfw(show_nsfw)
|
|
||||||
.saved_only(saved_only)
|
|
||||||
.community_id(community_id)
|
|
||||||
.my_person_id(person_id)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit);
|
|
||||||
|
|
||||||
let mut comments_query = CommentQueryBuilder::create(conn)
|
|
||||||
.my_person_id(person_id)
|
|
||||||
.sort(&sort)
|
|
||||||
.saved_only(saved_only)
|
|
||||||
.community_id(community_id)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit);
|
|
||||||
|
|
||||||
// If its saved only, you don't care what creator it was
|
|
||||||
// Or, if its not saved, then you only want it for that specific creator
|
|
||||||
if !saved_only {
|
|
||||||
posts_query = posts_query.creator_id(person_details_id);
|
|
||||||
comments_query = comments_query.creator_id(person_details_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let posts = posts_query.list()?;
|
|
||||||
let comments = comments_query.list()?;
|
|
||||||
|
|
||||||
Ok((posts, comments)) as Result<_, LemmyError>
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let mut follows = vec![];
|
|
||||||
if let Some(pid) = person_id {
|
|
||||||
if pid == person_details_id {
|
|
||||||
follows = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityFollowerView::for_person(conn, person_details_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let moderates = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityModeratorView::for_person(conn, person_details_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Return the jwt
|
|
||||||
Ok(GetPersonDetailsResponse {
|
|
||||||
person_view,
|
|
||||||
follows,
|
|
||||||
moderates,
|
|
||||||
comments,
|
|
||||||
posts,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for AddAdmin {
|
impl Perform for AddAdmin {
|
||||||
type Response = AddAdminResponse;
|
type Response = AddAdminResponse;
|
||||||
|
@ -947,52 +609,6 @@ impl Perform for MarkAllAsRead {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for DeleteAccount {
|
|
||||||
type Response = LoginResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<LoginResponse, LemmyError> {
|
|
||||||
let data: &DeleteAccount = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// Verify the password
|
|
||||||
let valid: bool = verify(
|
|
||||||
&data.password,
|
|
||||||
&local_user_view.local_user.password_encrypted,
|
|
||||||
)
|
|
||||||
.unwrap_or(false);
|
|
||||||
if !valid {
|
|
||||||
return Err(ApiError::err("password_incorrect").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comments
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
|
|
||||||
if blocking(context.pool(), permadelete).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_update_comment").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Posts
|
|
||||||
let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
|
|
||||||
if blocking(context.pool(), permadelete).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_update_post").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
Person::delete_account(conn, person_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(LoginResponse {
|
|
||||||
jwt: data.auth.to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for PasswordReset {
|
impl Perform for PasswordReset {
|
||||||
type Response = PasswordResetResponse;
|
type Response = PasswordResetResponse;
|
||||||
|
@ -1084,344 +700,6 @@ impl Perform for PasswordChange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreatePrivateMessage {
|
|
||||||
type Response = PrivateMessageResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PrivateMessageResponse, LemmyError> {
|
|
||||||
let data: &CreatePrivateMessage = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
|
||||||
|
|
||||||
let private_message_form = PrivateMessageForm {
|
|
||||||
content: content_slurs_removed.to_owned(),
|
|
||||||
creator_id: local_user_view.person.id,
|
|
||||||
recipient_id: data.recipient_id,
|
|
||||||
deleted: None,
|
|
||||||
read: None,
|
|
||||||
updated: None,
|
|
||||||
ap_id: None,
|
|
||||||
local: true,
|
|
||||||
published: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_private_message = match blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::create(conn, &private_message_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(private_message) => private_message,
|
|
||||||
Err(_e) => {
|
|
||||||
return Err(ApiError::err("couldnt_create_private_message").into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_private_message_id = inserted_private_message.id;
|
|
||||||
let updated_private_message = match blocking(
|
|
||||||
context.pool(),
|
|
||||||
move |conn| -> Result<PrivateMessage, LemmyError> {
|
|
||||||
let apub_id = generate_apub_endpoint(
|
|
||||||
EndpointType::PrivateMessage,
|
|
||||||
&inserted_private_message_id.to_string(),
|
|
||||||
)?;
|
|
||||||
Ok(PrivateMessage::update_ap_id(
|
|
||||||
&conn,
|
|
||||||
inserted_private_message_id,
|
|
||||||
apub_id,
|
|
||||||
)?)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(private_message) => private_message,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_private_message").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
updated_private_message
|
|
||||||
.send_create(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let private_message_view = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessageView::read(conn, inserted_private_message.id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PrivateMessageResponse {
|
|
||||||
private_message_view,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send notifications to the local recipient, if one exists
|
|
||||||
let recipient_id = data.recipient_id;
|
|
||||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
|
||||||
LocalUserView::read_person(conn, recipient_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
send_email_to_user(
|
|
||||||
&local_recipient,
|
|
||||||
"Private Message from",
|
|
||||||
"Private Message",
|
|
||||||
&content_slurs_removed,
|
|
||||||
);
|
|
||||||
|
|
||||||
let local_recipient_id = local_recipient.local_user.id;
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::CreatePrivateMessage,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for EditPrivateMessage {
|
|
||||||
type Response = PrivateMessageResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PrivateMessageResponse, LemmyError> {
|
|
||||||
let data: &EditPrivateMessage = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// Checking permissions
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let orig_private_message = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::read(conn, private_message_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
if local_user_view.person.id != orig_private_message.creator_id {
|
|
||||||
return Err(ApiError::err("no_private_message_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doing the update
|
|
||||||
let content_slurs_removed = remove_slurs(&data.content);
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let updated_private_message = match blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(private_message) => private_message,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the apub update
|
|
||||||
updated_private_message
|
|
||||||
.send_update(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let private_message_view = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessageView::read(conn, private_message_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PrivateMessageResponse {
|
|
||||||
private_message_view,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send notifications to the local recipient, if one exists
|
|
||||||
let recipient_id = orig_private_message.recipient_id;
|
|
||||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
|
||||||
LocalUserView::read_person(conn, recipient_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
let local_recipient_id = local_recipient.local_user.id;
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::EditPrivateMessage,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for DeletePrivateMessage {
|
|
||||||
type Response = PrivateMessageResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PrivateMessageResponse, LemmyError> {
|
|
||||||
let data: &DeletePrivateMessage = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// Checking permissions
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let orig_private_message = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::read(conn, private_message_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
if local_user_view.person.id != orig_private_message.creator_id {
|
|
||||||
return Err(ApiError::err("no_private_message_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doing the update
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let deleted = data.deleted;
|
|
||||||
let updated_private_message = match blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::update_deleted(conn, private_message_id, deleted)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(private_message) => private_message,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the apub update
|
|
||||||
if data.deleted {
|
|
||||||
updated_private_message
|
|
||||||
.send_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_private_message
|
|
||||||
.send_undo_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let private_message_view = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessageView::read(conn, private_message_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PrivateMessageResponse {
|
|
||||||
private_message_view,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send notifications to the local recipient, if one exists
|
|
||||||
let recipient_id = orig_private_message.recipient_id;
|
|
||||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
|
||||||
LocalUserView::read_person(conn, recipient_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
let local_recipient_id = local_recipient.local_user.id;
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::DeletePrivateMessage,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for MarkPrivateMessageAsRead {
|
|
||||||
type Response = PrivateMessageResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PrivateMessageResponse, LemmyError> {
|
|
||||||
let data: &MarkPrivateMessageAsRead = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// Checking permissions
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let orig_private_message = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::read(conn, private_message_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
if local_user_view.person.id != orig_private_message.recipient_id {
|
|
||||||
return Err(ApiError::err("couldnt_update_private_message").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doing the update
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let read = data.read;
|
|
||||||
match blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessage::update_read(conn, private_message_id, read)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(private_message) => private_message,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// No need to send an apub update
|
|
||||||
let private_message_id = data.private_message_id;
|
|
||||||
let private_message_view = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessageView::read(conn, private_message_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PrivateMessageResponse {
|
|
||||||
private_message_view,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send notifications to the local recipient, if one exists
|
|
||||||
let recipient_id = orig_private_message.recipient_id;
|
|
||||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
|
||||||
LocalUserView::read_person(conn, recipient_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
let local_recipient_id = local_recipient.local_user.id;
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::MarkPrivateMessageAsRead,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetPrivateMessages {
|
|
||||||
type Response = PrivateMessagesResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PrivateMessagesResponse, LemmyError> {
|
|
||||||
let data: &GetPrivateMessages = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let unread_only = data.unread_only;
|
|
||||||
let messages = blocking(context.pool(), move |conn| {
|
|
||||||
PrivateMessageQueryBuilder::create(&conn, person_id)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit)
|
|
||||||
.unread_only(unread_only)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(PrivateMessagesResponse {
|
|
||||||
private_messages: messages,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for GetReportCount {
|
impl Perform for GetReportCount {
|
||||||
type Response = GetReportCountResponse;
|
type Response = GetReportCountResponse;
|
||||||
|
@ -1477,3 +755,30 @@ impl Perform for GetReportCount {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for GetFollowedCommunities {
|
||||||
|
type Response = GetFollowedCommunitiesResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
|
||||||
|
let data: &GetFollowedCommunities = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let communities = match blocking(context.pool(), move |conn| {
|
||||||
|
CommunityFollowerView::for_person(conn, person_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(communities) => communities,
|
||||||
|
_ => return Err(ApiError::err("system_err_login").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
Ok(GetFollowedCommunitiesResponse { communities })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,289 +1,19 @@
|
||||||
use crate::{
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
check_community_ban,
|
check_community_ban,
|
||||||
check_downvotes_enabled,
|
check_downvotes_enabled,
|
||||||
collect_moderated_communities,
|
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
get_local_user_view_from_jwt_opt,
|
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
Perform,
|
|
||||||
};
|
|
||||||
use actix_web::web::Data;
|
|
||||||
use lemmy_api_structs::{blocking, post::*};
|
|
||||||
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
|
||||||
use lemmy_db_queries::{
|
|
||||||
source::post::Post_,
|
|
||||||
Crud,
|
|
||||||
Likeable,
|
|
||||||
ListingType,
|
|
||||||
Reportable,
|
|
||||||
Saveable,
|
|
||||||
SortType,
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
naive_now,
|
|
||||||
source::{
|
|
||||||
moderator::*,
|
|
||||||
post::*,
|
post::*,
|
||||||
post_report::{PostReport, PostReportForm},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{
|
use lemmy_apub::{ApubLikeableType, ApubObjectType};
|
||||||
comment_view::CommentQueryBuilder,
|
use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable};
|
||||||
post_report_view::{PostReportQueryBuilder, PostReportView},
|
use lemmy_db_schema::source::{moderator::*, post::*};
|
||||||
post_view::{PostQueryBuilder, PostView},
|
use lemmy_db_views::post_view::PostView;
|
||||||
};
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||||
community_moderator_view::CommunityModeratorView,
|
|
||||||
community_view::CommunityView,
|
|
||||||
};
|
|
||||||
use lemmy_utils::{
|
|
||||||
request::fetch_iframely_and_pictrs_data,
|
|
||||||
utils::{check_slurs, check_slurs_opt, is_valid_post_title},
|
|
||||||
ApiError,
|
|
||||||
ConnectionId,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use lemmy_websocket::{
|
|
||||||
messages::{GetPostUsersOnline, SendModRoomMessage, SendPost, SendUserRoomMessage},
|
|
||||||
LemmyContext,
|
|
||||||
UserOperation,
|
|
||||||
};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreatePost {
|
|
||||||
type Response = PostResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PostResponse, LemmyError> {
|
|
||||||
let data: &CreatePost = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
check_slurs(&data.name)?;
|
|
||||||
check_slurs_opt(&data.body)?;
|
|
||||||
|
|
||||||
if !is_valid_post_title(&data.name) {
|
|
||||||
return Err(ApiError::err("invalid_post_title").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?;
|
|
||||||
|
|
||||||
// Fetch Iframely and pictrs cached image
|
|
||||||
let data_url = data.url.as_ref();
|
|
||||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
|
||||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
|
||||||
|
|
||||||
let post_form = PostForm {
|
|
||||||
name: data.name.trim().to_owned(),
|
|
||||||
url: data_url.map(|u| u.to_owned().into()),
|
|
||||||
body: data.body.to_owned(),
|
|
||||||
community_id: data.community_id,
|
|
||||||
creator_id: local_user_view.person.id,
|
|
||||||
removed: None,
|
|
||||||
deleted: None,
|
|
||||||
nsfw: data.nsfw,
|
|
||||||
locked: None,
|
|
||||||
stickied: None,
|
|
||||||
updated: None,
|
|
||||||
embed_title: iframely_title,
|
|
||||||
embed_description: iframely_description,
|
|
||||||
embed_html: iframely_html,
|
|
||||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
|
||||||
ap_id: None,
|
|
||||||
local: true,
|
|
||||||
published: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_post =
|
|
||||||
match blocking(context.pool(), move |conn| Post::create(conn, &post_form)).await? {
|
|
||||||
Ok(post) => post,
|
|
||||||
Err(e) => {
|
|
||||||
let err_type = if e.to_string() == "value too long for type character varying(200)" {
|
|
||||||
"post_title_too_long"
|
|
||||||
} else {
|
|
||||||
"couldnt_create_post"
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(ApiError::err(err_type).into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_post_id = inserted_post.id;
|
|
||||||
let updated_post = match blocking(context.pool(), move |conn| -> Result<Post, LemmyError> {
|
|
||||||
let apub_id = generate_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string())?;
|
|
||||||
Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(post) => post,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_post").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
updated_post
|
|
||||||
.send_create(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// They like their own post by default
|
|
||||||
let like_form = PostLikeForm {
|
|
||||||
post_id: inserted_post.id,
|
|
||||||
person_id: local_user_view.person.id,
|
|
||||||
score: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
|
|
||||||
if blocking(context.pool(), like).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_like_post").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
updated_post
|
|
||||||
.send_like(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Refetch the view
|
|
||||||
let inserted_post_id = inserted_post.id;
|
|
||||||
let post_view = match blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, inserted_post_id, Some(local_user_view.person.id))
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(post) => post,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = PostResponse { post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::CreatePost,
|
|
||||||
post: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetPost {
|
|
||||||
type Response = GetPostResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetPostResponse, LemmyError> {
|
|
||||||
let data: &GetPost = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
let person_id = local_user_view.map(|u| u.person.id);
|
|
||||||
|
|
||||||
let id = data.id;
|
|
||||||
let post_view = match blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, id, person_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(post) => post,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let id = data.id;
|
|
||||||
let comments = blocking(context.pool(), move |conn| {
|
|
||||||
CommentQueryBuilder::create(conn)
|
|
||||||
.my_person_id(person_id)
|
|
||||||
.post_id(id)
|
|
||||||
.limit(9999)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let community_id = post_view.community.id;
|
|
||||||
let moderators = blocking(context.pool(), move |conn| {
|
|
||||||
CommunityModeratorView::for_community(conn, community_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Necessary for the sidebar
|
|
||||||
let community_view = match blocking(context.pool(), move |conn| {
|
|
||||||
CommunityView::read(conn, community_id, person_id)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(community) => community,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let online = context
|
|
||||||
.chat_server()
|
|
||||||
.send(GetPostUsersOnline { post_id: data.id })
|
|
||||||
.await
|
|
||||||
.unwrap_or(1);
|
|
||||||
|
|
||||||
// Return the jwt
|
|
||||||
Ok(GetPostResponse {
|
|
||||||
post_view,
|
|
||||||
community_view,
|
|
||||||
comments,
|
|
||||||
moderators,
|
|
||||||
online,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetPosts {
|
|
||||||
type Response = GetPostsResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetPostsResponse, LemmyError> {
|
|
||||||
let data: &GetPosts = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let person_id = match &local_user_view {
|
|
||||||
Some(uv) => Some(uv.person.id),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let show_nsfw = match &local_user_view {
|
|
||||||
Some(uv) => uv.local_user.show_nsfw,
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let type_ = ListingType::from_str(&data.type_)?;
|
|
||||||
let sort = SortType::from_str(&data.sort)?;
|
|
||||||
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let community_id = data.community_id;
|
|
||||||
let community_name = data.community_name.to_owned();
|
|
||||||
let saved_only = data.saved_only;
|
|
||||||
|
|
||||||
let posts = match blocking(context.pool(), move |conn| {
|
|
||||||
PostQueryBuilder::create(conn)
|
|
||||||
.listing_type(&type_)
|
|
||||||
.sort(&sort)
|
|
||||||
.show_nsfw(show_nsfw)
|
|
||||||
.community_id(community_id)
|
|
||||||
.community_name(community_name)
|
|
||||||
.saved_only(saved_only)
|
|
||||||
.my_person_id(person_id)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(posts) => posts,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_get_posts").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(GetPostsResponse { posts })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for CreatePostLike {
|
impl Perform for CreatePostLike {
|
||||||
|
@ -362,253 +92,6 @@ impl Perform for CreatePostLike {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for EditPost {
|
|
||||||
type Response = PostResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PostResponse, LemmyError> {
|
|
||||||
let data: &EditPost = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
check_slurs(&data.name)?;
|
|
||||||
check_slurs_opt(&data.body)?;
|
|
||||||
|
|
||||||
if !is_valid_post_title(&data.name) {
|
|
||||||
return Err(ApiError::err("invalid_post_title").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
|
||||||
|
|
||||||
check_community_ban(
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
context.pool(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Verify that only the creator can edit
|
|
||||||
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
|
||||||
return Err(ApiError::err("no_post_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch Iframely and Pictrs cached image
|
|
||||||
let data_url = data.url.as_ref();
|
|
||||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
|
||||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
|
||||||
|
|
||||||
let post_form = PostForm {
|
|
||||||
name: data.name.trim().to_owned(),
|
|
||||||
url: data_url.map(|u| u.to_owned().into()),
|
|
||||||
body: data.body.to_owned(),
|
|
||||||
nsfw: data.nsfw,
|
|
||||||
creator_id: orig_post.creator_id.to_owned(),
|
|
||||||
community_id: orig_post.community_id,
|
|
||||||
removed: Some(orig_post.removed),
|
|
||||||
deleted: Some(orig_post.deleted),
|
|
||||||
locked: Some(orig_post.locked),
|
|
||||||
stickied: Some(orig_post.stickied),
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
embed_title: iframely_title,
|
|
||||||
embed_description: iframely_description,
|
|
||||||
embed_html: iframely_html,
|
|
||||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
|
||||||
ap_id: Some(orig_post.ap_id),
|
|
||||||
local: orig_post.local,
|
|
||||||
published: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let res = blocking(context.pool(), move |conn| {
|
|
||||||
Post::update(conn, post_id, &post_form)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let updated_post: Post = match res {
|
|
||||||
Ok(post) => post,
|
|
||||||
Err(e) => {
|
|
||||||
let err_type = if e.to_string() == "value too long for type character varying(200)" {
|
|
||||||
"post_title_too_long"
|
|
||||||
} else {
|
|
||||||
"couldnt_update_post"
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(ApiError::err(err_type).into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send apub update
|
|
||||||
updated_post
|
|
||||||
.send_update(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::EditPost,
|
|
||||||
post: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for DeletePost {
|
|
||||||
type Response = PostResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PostResponse, LemmyError> {
|
|
||||||
let data: &DeletePost = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
|
||||||
|
|
||||||
check_community_ban(
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
context.pool(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Verify that only the creator can delete
|
|
||||||
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
|
||||||
return Err(ApiError::err("no_post_edit_allowed").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the post
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let deleted = data.deleted;
|
|
||||||
let updated_post = blocking(context.pool(), move |conn| {
|
|
||||||
Post::update_deleted(conn, post_id, deleted)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// apub updates
|
|
||||||
if deleted {
|
|
||||||
updated_post
|
|
||||||
.send_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_post
|
|
||||||
.send_undo_delete(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refetch the post
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::DeletePost,
|
|
||||||
post: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for RemovePost {
|
|
||||||
type Response = PostResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<PostResponse, LemmyError> {
|
|
||||||
let data: &RemovePost = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
|
||||||
|
|
||||||
check_community_ban(
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
context.pool(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Verify that only the mods can remove
|
|
||||||
is_mod_or_admin(
|
|
||||||
context.pool(),
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Update the post
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let removed = data.removed;
|
|
||||||
let updated_post = blocking(context.pool(), move |conn| {
|
|
||||||
Post::update_removed(conn, post_id, removed)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Mod tables
|
|
||||||
let form = ModRemovePostForm {
|
|
||||||
mod_person_id: local_user_view.person.id,
|
|
||||||
post_id: data.post_id,
|
|
||||||
removed: Some(removed),
|
|
||||||
reason: data.reason.to_owned(),
|
|
||||||
};
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
ModRemovePost::create(conn, &form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// apub updates
|
|
||||||
if removed {
|
|
||||||
updated_post
|
|
||||||
.send_remove(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
updated_post
|
|
||||||
.send_undo_remove(&local_user_view.person, context)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refetch the post
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(conn, post_id, Some(person_id))
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = PostResponse { post_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendPost {
|
|
||||||
op: UserOperation::RemovePost,
|
|
||||||
post: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for LockPost {
|
impl Perform for LockPost {
|
||||||
type Response = PostResponse;
|
type Response = PostResponse;
|
||||||
|
@ -792,166 +275,3 @@ impl Perform for SavePost {
|
||||||
Ok(PostResponse { post_view })
|
Ok(PostResponse { post_view })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a post report and notifies the moderators of the community
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreatePostReport {
|
|
||||||
type Response = CreatePostReportResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<CreatePostReportResponse, LemmyError> {
|
|
||||||
let data: &CreatePostReport = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
// check size of report and check for whitespace
|
|
||||||
let reason = data.reason.trim();
|
|
||||||
if reason.is_empty() {
|
|
||||||
return Err(ApiError::err("report_reason_required").into());
|
|
||||||
}
|
|
||||||
if reason.chars().count() > 1000 {
|
|
||||||
return Err(ApiError::err("report_too_long").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let post_id = data.post_id;
|
|
||||||
let post_view = blocking(context.pool(), move |conn| {
|
|
||||||
PostView::read(&conn, post_id, None)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
check_community_ban(person_id, post_view.community.id, context.pool()).await?;
|
|
||||||
|
|
||||||
let report_form = PostReportForm {
|
|
||||||
creator_id: person_id,
|
|
||||||
post_id,
|
|
||||||
original_post_name: post_view.post.name,
|
|
||||||
original_post_url: post_view.post.url,
|
|
||||||
original_post_body: post_view.post.body,
|
|
||||||
reason: data.reason.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let report = match blocking(context.pool(), move |conn| {
|
|
||||||
PostReport::report(conn, &report_form)
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
Ok(report) => report,
|
|
||||||
Err(_e) => return Err(ApiError::err("couldnt_create_report").into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = CreatePostReportResponse { success: true };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::CreatePostReport,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id: local_user_view.local_user.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendModRoomMessage {
|
|
||||||
op: UserOperation::CreatePostReport,
|
|
||||||
response: report,
|
|
||||||
community_id: post_view.community.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves or unresolves a post report and notifies the moderators of the community
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for ResolvePostReport {
|
|
||||||
type Response = ResolvePostReportResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<ResolvePostReportResponse, LemmyError> {
|
|
||||||
let data: &ResolvePostReport = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let report_id = data.report_id;
|
|
||||||
let report = blocking(context.pool(), move |conn| {
|
|
||||||
PostReportView::read(&conn, report_id)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
|
|
||||||
|
|
||||||
let resolved = data.resolved;
|
|
||||||
let resolve_fun = move |conn: &'_ _| {
|
|
||||||
if resolved {
|
|
||||||
PostReport::resolve(conn, report_id, person_id)
|
|
||||||
} else {
|
|
||||||
PostReport::unresolve(conn, report_id, person_id)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = ResolvePostReportResponse {
|
|
||||||
report_id,
|
|
||||||
resolved: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if blocking(context.pool(), resolve_fun).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_resolve_report").into());
|
|
||||||
};
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendModRoomMessage {
|
|
||||||
op: UserOperation::ResolvePostReport,
|
|
||||||
response: res.clone(),
|
|
||||||
community_id: report.community.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists post reports for a community if an id is supplied
|
|
||||||
/// or returns all post reports for communities a user moderates
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for ListPostReports {
|
|
||||||
type Response = ListPostReportsResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<ListPostReportsResponse, LemmyError> {
|
|
||||||
let data: &ListPostReports = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let community_id = data.community;
|
|
||||||
let community_ids =
|
|
||||||
collect_moderated_communities(person_id, community_id, context.pool()).await?;
|
|
||||||
|
|
||||||
let page = data.page;
|
|
||||||
let limit = data.limit;
|
|
||||||
let posts = blocking(context.pool(), move |conn| {
|
|
||||||
PostReportQueryBuilder::create(conn)
|
|
||||||
.community_ids(community_ids)
|
|
||||||
.page(page)
|
|
||||||
.limit(limit)
|
|
||||||
.list()
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
let res = ListPostReportsResponse { posts };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendUserRoomMessage {
|
|
||||||
op: UserOperation::ListPostReports,
|
|
||||||
response: res.clone(),
|
|
||||||
local_recipient_id: local_user_view.local_user.id,
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
192
crates/api/src/post_report.rs
Normal file
192
crates/api/src/post_report.rs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_community_ban,
|
||||||
|
collect_moderated_communities,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
is_mod_or_admin,
|
||||||
|
post::{
|
||||||
|
CreatePostReport,
|
||||||
|
CreatePostReportResponse,
|
||||||
|
ListPostReports,
|
||||||
|
ListPostReportsResponse,
|
||||||
|
ResolvePostReport,
|
||||||
|
ResolvePostReportResponse,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::Reportable;
|
||||||
|
use lemmy_db_schema::source::post_report::{PostReport, PostReportForm};
|
||||||
|
use lemmy_db_views::{
|
||||||
|
post_report_view::{PostReportQueryBuilder, PostReportView},
|
||||||
|
post_view::PostView,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{
|
||||||
|
messages::{SendModRoomMessage, SendUserRoomMessage},
|
||||||
|
LemmyContext,
|
||||||
|
UserOperation,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a post report and notifies the moderators of the community
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for CreatePostReport {
|
||||||
|
type Response = CreatePostReportResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CreatePostReportResponse, LemmyError> {
|
||||||
|
let data: &CreatePostReport = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// check size of report and check for whitespace
|
||||||
|
let reason = data.reason.trim();
|
||||||
|
if reason.is_empty() {
|
||||||
|
return Err(ApiError::err("report_reason_required").into());
|
||||||
|
}
|
||||||
|
if reason.chars().count() > 1000 {
|
||||||
|
return Err(ApiError::err("report_too_long").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(&conn, post_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
check_community_ban(person_id, post_view.community.id, context.pool()).await?;
|
||||||
|
|
||||||
|
let report_form = PostReportForm {
|
||||||
|
creator_id: person_id,
|
||||||
|
post_id,
|
||||||
|
original_post_name: post_view.post.name,
|
||||||
|
original_post_url: post_view.post.url,
|
||||||
|
original_post_body: post_view.post.body,
|
||||||
|
reason: data.reason.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let report = match blocking(context.pool(), move |conn| {
|
||||||
|
PostReport::report(conn, &report_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(report) => report,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_report").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = CreatePostReportResponse { success: true };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::CreatePostReport,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id: local_user_view.local_user.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendModRoomMessage {
|
||||||
|
op: UserOperation::CreatePostReport,
|
||||||
|
response: report,
|
||||||
|
community_id: post_view.community.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves or unresolves a post report and notifies the moderators of the community
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ResolvePostReport {
|
||||||
|
type Response = ResolvePostReportResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<ResolvePostReportResponse, LemmyError> {
|
||||||
|
let data: &ResolvePostReport = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let report_id = data.report_id;
|
||||||
|
let report = blocking(context.pool(), move |conn| {
|
||||||
|
PostReportView::read(&conn, report_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
|
||||||
|
|
||||||
|
let resolved = data.resolved;
|
||||||
|
let resolve_fun = move |conn: &'_ _| {
|
||||||
|
if resolved {
|
||||||
|
PostReport::resolve(conn, report_id, person_id)
|
||||||
|
} else {
|
||||||
|
PostReport::unresolve(conn, report_id, person_id)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = ResolvePostReportResponse {
|
||||||
|
report_id,
|
||||||
|
resolved: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if blocking(context.pool(), resolve_fun).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_resolve_report").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendModRoomMessage {
|
||||||
|
op: UserOperation::ResolvePostReport,
|
||||||
|
response: res.clone(),
|
||||||
|
community_id: report.community.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists post reports for a community if an id is supplied
|
||||||
|
/// or returns all post reports for communities a user moderates
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ListPostReports {
|
||||||
|
type Response = ListPostReportsResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<ListPostReportsResponse, LemmyError> {
|
||||||
|
let data: &ListPostReports = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let community_id = data.community;
|
||||||
|
let community_ids =
|
||||||
|
collect_moderated_communities(person_id, community_id, context.pool()).await?;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let posts = blocking(context.pool(), move |conn| {
|
||||||
|
PostReportQueryBuilder::create(conn)
|
||||||
|
.community_ids(community_ids)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = ListPostReportsResponse { posts };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::ListPostReports,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id: local_user_view.local_user.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
77
crates/api/src/private_message.rs
Normal file
77
crates/api/src/private_message.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
person::{MarkPrivateMessageAsRead, PrivateMessageResponse},
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||||
|
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||||
|
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for MarkPrivateMessageAsRead {
|
||||||
|
type Response = PrivateMessageResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PrivateMessageResponse, LemmyError> {
|
||||||
|
let data: &MarkPrivateMessageAsRead = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// Checking permissions
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let orig_private_message = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if local_user_view.person.id != orig_private_message.recipient_id {
|
||||||
|
return Err(ApiError::err("couldnt_update_private_message").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doing the update
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let read = data.read;
|
||||||
|
match blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::update_read(conn, private_message_id, read)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// No need to send an apub update
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let private_message_view = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageView::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
private_message_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notifications to the local recipient, if one exists
|
||||||
|
let recipient_id = orig_private_message.recipient_id;
|
||||||
|
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
||||||
|
LocalUserView::read_person(conn, recipient_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let local_recipient_id = local_recipient.local_user.id;
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::MarkPrivateMessageAsRead,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::Perform;
|
use crate::Perform;
|
||||||
use actix_web::{error::ErrorBadRequest, *};
|
use actix_web::{error::ErrorBadRequest, *};
|
||||||
use lemmy_api_structs::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
|
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
|
||||||
use lemmy_utils::rate_limit::RateLimit;
|
use lemmy_utils::rate_limit::RateLimit;
|
||||||
use lemmy_websocket::{routes::chat_route, LemmyContext};
|
use lemmy_websocket::{routes::chat_route, LemmyContext};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -14,10 +14,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.service(
|
.service(
|
||||||
web::scope("/site")
|
web::scope("/site")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route("", web::get().to(route_get::<GetSite>))
|
|
||||||
// Admin Actions
|
// Admin Actions
|
||||||
.route("", web::post().to(route_post::<CreateSite>))
|
|
||||||
.route("", web::put().to(route_post::<EditSite>))
|
|
||||||
.route("/transfer", web::post().to(route_post::<TransferSite>))
|
.route("/transfer", web::post().to(route_post::<TransferSite>))
|
||||||
.route("/config", web::get().to(route_get::<GetSiteConfig>))
|
.route("/config", web::get().to(route_get::<GetSiteConfig>))
|
||||||
.route("/config", web::put().to(route_post::<SaveSiteConfig>)),
|
.route("/config", web::put().to(route_post::<SaveSiteConfig>)),
|
||||||
|
@ -33,22 +30,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route(web::get().to(route_get::<Search>)),
|
.route(web::get().to(route_get::<Search>)),
|
||||||
)
|
)
|
||||||
// Community
|
// Community
|
||||||
.service(
|
|
||||||
web::resource("/community")
|
|
||||||
.guard(guard::Post())
|
|
||||||
.wrap(rate_limit.register())
|
|
||||||
.route(web::post().to(route_post::<CreateCommunity>)),
|
|
||||||
)
|
|
||||||
.service(
|
.service(
|
||||||
web::scope("/community")
|
web::scope("/community")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route("", web::get().to(route_get::<GetCommunity>))
|
|
||||||
.route("", web::put().to(route_post::<EditCommunity>))
|
|
||||||
.route("/list", web::get().to(route_get::<ListCommunities>))
|
|
||||||
.route("/follow", web::post().to(route_post::<FollowCommunity>))
|
.route("/follow", web::post().to(route_post::<FollowCommunity>))
|
||||||
.route("/delete", web::post().to(route_post::<DeleteCommunity>))
|
|
||||||
// Mod Actions
|
|
||||||
.route("/remove", web::post().to(route_post::<RemoveCommunity>))
|
|
||||||
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
|
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
|
||||||
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
|
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
|
||||||
.route("/mod", web::post().to(route_post::<AddModToCommunity>))
|
.route("/mod", web::post().to(route_post::<AddModToCommunity>))
|
||||||
|
@ -56,23 +41,11 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("/mod/join", web::post().to(route_post::<ModJoin>)),
|
.route("/mod/join", web::post().to(route_post::<ModJoin>)),
|
||||||
)
|
)
|
||||||
// Post
|
// Post
|
||||||
.service(
|
|
||||||
// Handle POST to /post separately to add the post() rate limitter
|
|
||||||
web::resource("/post")
|
|
||||||
.guard(guard::Post())
|
|
||||||
.wrap(rate_limit.post())
|
|
||||||
.route(web::post().to(route_post::<CreatePost>)),
|
|
||||||
)
|
|
||||||
.service(
|
.service(
|
||||||
web::scope("/post")
|
web::scope("/post")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route("", web::get().to(route_get::<GetPost>))
|
|
||||||
.route("", web::put().to(route_post::<EditPost>))
|
|
||||||
.route("/delete", web::post().to(route_post::<DeletePost>))
|
|
||||||
.route("/remove", web::post().to(route_post::<RemovePost>))
|
|
||||||
.route("/lock", web::post().to(route_post::<LockPost>))
|
.route("/lock", web::post().to(route_post::<LockPost>))
|
||||||
.route("/sticky", web::post().to(route_post::<StickyPost>))
|
.route("/sticky", web::post().to(route_post::<StickyPost>))
|
||||||
.route("/list", web::get().to(route_get::<GetPosts>))
|
|
||||||
.route("/like", web::post().to(route_post::<CreatePostLike>))
|
.route("/like", web::post().to(route_post::<CreatePostLike>))
|
||||||
.route("/save", web::put().to(route_post::<SavePost>))
|
.route("/save", web::put().to(route_post::<SavePost>))
|
||||||
.route("/join", web::post().to(route_post::<PostJoin>))
|
.route("/join", web::post().to(route_post::<PostJoin>))
|
||||||
|
@ -87,17 +60,12 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.service(
|
.service(
|
||||||
web::scope("/comment")
|
web::scope("/comment")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route("", web::post().to(route_post::<CreateComment>))
|
|
||||||
.route("", web::put().to(route_post::<EditComment>))
|
|
||||||
.route("/delete", web::post().to(route_post::<DeleteComment>))
|
|
||||||
.route("/remove", web::post().to(route_post::<RemoveComment>))
|
|
||||||
.route(
|
.route(
|
||||||
"/mark_as_read",
|
"/mark_as_read",
|
||||||
web::post().to(route_post::<MarkCommentAsRead>),
|
web::post().to(route_post::<MarkCommentAsRead>),
|
||||||
)
|
)
|
||||||
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
||||||
.route("/save", web::put().to(route_post::<SaveComment>))
|
.route("/save", web::put().to(route_post::<SaveComment>))
|
||||||
.route("/list", web::get().to(route_get::<GetComments>))
|
|
||||||
.route("/report", web::post().to(route_post::<CreateCommentReport>))
|
.route("/report", web::post().to(route_post::<CreateCommentReport>))
|
||||||
.route(
|
.route(
|
||||||
"/report/resolve",
|
"/report/resolve",
|
||||||
|
@ -112,32 +80,15 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.service(
|
.service(
|
||||||
web::scope("/private_message")
|
web::scope("/private_message")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route("/list", web::get().to(route_get::<GetPrivateMessages>))
|
|
||||||
.route("", web::post().to(route_post::<CreatePrivateMessage>))
|
|
||||||
.route("", web::put().to(route_post::<EditPrivateMessage>))
|
|
||||||
.route(
|
|
||||||
"/delete",
|
|
||||||
web::post().to(route_post::<DeletePrivateMessage>),
|
|
||||||
)
|
|
||||||
.route(
|
.route(
|
||||||
"/mark_as_read",
|
"/mark_as_read",
|
||||||
web::post().to(route_post::<MarkPrivateMessageAsRead>),
|
web::post().to(route_post::<MarkPrivateMessageAsRead>),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// User
|
|
||||||
.service(
|
|
||||||
// Account action, I don't like that it's in /user maybe /accounts
|
|
||||||
// Handle /user/register separately to add the register() rate limitter
|
|
||||||
web::resource("/user/register")
|
|
||||||
.guard(guard::Post())
|
|
||||||
.wrap(rate_limit.register())
|
|
||||||
.route(web::post().to(route_post::<Register>)),
|
|
||||||
)
|
|
||||||
// User actions
|
// User actions
|
||||||
.service(
|
.service(
|
||||||
web::scope("/user")
|
web::scope("/user")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route("", web::get().to(route_get::<GetPersonDetails>))
|
|
||||||
.route("/mention", web::get().to(route_get::<GetPersonMentions>))
|
.route("/mention", web::get().to(route_get::<GetPersonMentions>))
|
||||||
.route(
|
.route(
|
||||||
"/mention/mark_as_read",
|
"/mention/mark_as_read",
|
||||||
|
@ -154,10 +105,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
// Account actions. I don't like that they're in /user maybe /accounts
|
// Account actions. I don't like that they're in /user maybe /accounts
|
||||||
.route("/login", web::post().to(route_post::<Login>))
|
.route("/login", web::post().to(route_post::<Login>))
|
||||||
.route("/get_captcha", web::get().to(route_get::<GetCaptcha>))
|
.route("/get_captcha", web::get().to(route_get::<GetCaptcha>))
|
||||||
.route(
|
|
||||||
"/delete_account",
|
|
||||||
web::post().to(route_post::<DeleteAccount>),
|
|
||||||
)
|
|
||||||
.route(
|
.route(
|
||||||
"/password_reset",
|
"/password_reset",
|
||||||
web::post().to(route_post::<PasswordReset>),
|
web::post().to(route_post::<PasswordReset>),
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
use crate::{
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use anyhow::Context;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
build_federated_instances,
|
build_federated_instances,
|
||||||
get_local_user_settings_view_from_jwt,
|
get_local_user_settings_view_from_jwt,
|
||||||
get_local_user_settings_view_from_jwt_opt,
|
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
get_local_user_view_from_jwt_opt,
|
get_local_user_view_from_jwt_opt,
|
||||||
is_admin,
|
is_admin,
|
||||||
Perform,
|
site::*,
|
||||||
};
|
};
|
||||||
use actix_web::web::Data;
|
|
||||||
use anyhow::Context;
|
|
||||||
use lemmy_api_structs::{blocking, person::Register, site::*};
|
|
||||||
use lemmy_apub::fetcher::search::search_by_apub_id;
|
use lemmy_apub::fetcher::search::search_by_apub_id;
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{source::site::Site_, Crud, SearchType, SortType};
|
||||||
diesel_option_overwrite_to_url,
|
use lemmy_db_schema::source::{moderator::*, site::Site};
|
||||||
source::site::Site_,
|
|
||||||
Crud,
|
|
||||||
SearchType,
|
|
||||||
SortType,
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
naive_now,
|
|
||||||
source::{
|
|
||||||
moderator::*,
|
|
||||||
site::{Site, *},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
comment_view::CommentQueryBuilder,
|
comment_view::CommentQueryBuilder,
|
||||||
post_view::PostQueryBuilder,
|
post_view::PostQueryBuilder,
|
||||||
|
@ -48,18 +36,13 @@ use lemmy_db_views_moderator::{
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
location_info,
|
||||||
settings::structs::Settings,
|
settings::structs::Settings,
|
||||||
utils::{check_slurs, check_slurs_opt},
|
|
||||||
version,
|
version,
|
||||||
ApiError,
|
ApiError,
|
||||||
ConnectionId,
|
ConnectionId,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::LemmyContext;
|
||||||
messages::{GetUsersOnline, SendAllMessage},
|
use log::debug;
|
||||||
LemmyContext,
|
|
||||||
UserOperation,
|
|
||||||
};
|
|
||||||
use log::{debug, info};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
|
@ -136,189 +119,6 @@ impl Perform for GetModlog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for CreateSite {
|
|
||||||
type Response = SiteResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
_websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<SiteResponse, LemmyError> {
|
|
||||||
let data: &CreateSite = &self;
|
|
||||||
|
|
||||||
let read_site = move |conn: &'_ _| Site::read_simple(conn);
|
|
||||||
if blocking(context.pool(), read_site).await?.is_ok() {
|
|
||||||
return Err(ApiError::err("site_already_exists").into());
|
|
||||||
};
|
|
||||||
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
check_slurs(&data.name)?;
|
|
||||||
check_slurs_opt(&data.description)?;
|
|
||||||
|
|
||||||
// Make sure user is an admin
|
|
||||||
is_admin(&local_user_view)?;
|
|
||||||
|
|
||||||
let site_form = SiteForm {
|
|
||||||
name: data.name.to_owned(),
|
|
||||||
description: data.description.to_owned(),
|
|
||||||
icon: Some(data.icon.to_owned().map(|url| url.into())),
|
|
||||||
banner: Some(data.banner.to_owned().map(|url| url.into())),
|
|
||||||
creator_id: local_user_view.person.id,
|
|
||||||
enable_downvotes: data.enable_downvotes,
|
|
||||||
open_registration: data.open_registration,
|
|
||||||
enable_nsfw: data.enable_nsfw,
|
|
||||||
updated: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
|
|
||||||
if blocking(context.pool(), create_site).await?.is_err() {
|
|
||||||
return Err(ApiError::err("site_already_exists").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
|
|
||||||
|
|
||||||
Ok(SiteResponse { site_view })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for EditSite {
|
|
||||||
type Response = SiteResponse;
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<SiteResponse, LemmyError> {
|
|
||||||
let data: &EditSite = &self;
|
|
||||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
|
||||||
|
|
||||||
check_slurs(&data.name)?;
|
|
||||||
check_slurs_opt(&data.description)?;
|
|
||||||
|
|
||||||
// Make sure user is an admin
|
|
||||||
is_admin(&local_user_view)?;
|
|
||||||
|
|
||||||
let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
|
|
||||||
|
|
||||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
|
||||||
|
|
||||||
let site_form = SiteForm {
|
|
||||||
name: data.name.to_owned(),
|
|
||||||
description: data.description.to_owned(),
|
|
||||||
icon,
|
|
||||||
banner,
|
|
||||||
creator_id: found_site.creator_id,
|
|
||||||
updated: Some(naive_now()),
|
|
||||||
enable_downvotes: data.enable_downvotes,
|
|
||||||
open_registration: data.open_registration,
|
|
||||||
enable_nsfw: data.enable_nsfw,
|
|
||||||
};
|
|
||||||
|
|
||||||
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
|
|
||||||
if blocking(context.pool(), update_site).await?.is_err() {
|
|
||||||
return Err(ApiError::err("couldnt_update_site").into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
|
|
||||||
|
|
||||||
let res = SiteResponse { site_view };
|
|
||||||
|
|
||||||
context.chat_server().do_send(SendAllMessage {
|
|
||||||
op: UserOperation::EditSite,
|
|
||||||
response: res.clone(),
|
|
||||||
websocket_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Perform for GetSite {
|
|
||||||
type Response = GetSiteResponse;
|
|
||||||
|
|
||||||
async fn perform(
|
|
||||||
&self,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
websocket_id: Option<ConnectionId>,
|
|
||||||
) -> Result<GetSiteResponse, LemmyError> {
|
|
||||||
let data: &GetSite = &self;
|
|
||||||
|
|
||||||
let site_view = match blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
|
|
||||||
Ok(site_view) => Some(site_view),
|
|
||||||
// If the site isn't created yet, check the setup
|
|
||||||
Err(_) => {
|
|
||||||
if let Some(setup) = Settings::get().setup().as_ref() {
|
|
||||||
let register = Register {
|
|
||||||
username: setup.admin_username.to_owned(),
|
|
||||||
email: setup.admin_email.to_owned(),
|
|
||||||
password: setup.admin_password.to_owned(),
|
|
||||||
password_verify: setup.admin_password.to_owned(),
|
|
||||||
show_nsfw: true,
|
|
||||||
captcha_uuid: None,
|
|
||||||
captcha_answer: None,
|
|
||||||
};
|
|
||||||
let login_response = register.perform(context, websocket_id).await?;
|
|
||||||
info!("Admin {} created", setup.admin_username);
|
|
||||||
|
|
||||||
let create_site = CreateSite {
|
|
||||||
name: setup.site_name.to_owned(),
|
|
||||||
description: None,
|
|
||||||
icon: None,
|
|
||||||
banner: None,
|
|
||||||
enable_downvotes: true,
|
|
||||||
open_registration: true,
|
|
||||||
enable_nsfw: true,
|
|
||||||
auth: login_response.jwt,
|
|
||||||
};
|
|
||||||
create_site.perform(context, websocket_id).await?;
|
|
||||||
info!("Site {} created", setup.site_name);
|
|
||||||
Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
|
|
||||||
|
|
||||||
// Make sure the site creator is the top admin
|
|
||||||
if let Some(site_view) = site_view.to_owned() {
|
|
||||||
let site_creator_id = site_view.creator.id;
|
|
||||||
// TODO investigate why this is sometimes coming back null
|
|
||||||
// Maybe user_.admin isn't being set to true?
|
|
||||||
if let Some(creator_index) = admins.iter().position(|r| r.person.id == site_creator_id) {
|
|
||||||
let creator_person = admins.remove(creator_index);
|
|
||||||
admins.insert(0, creator_person);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
|
|
||||||
|
|
||||||
let online = context
|
|
||||||
.chat_server()
|
|
||||||
.send(GetUsersOnline)
|
|
||||||
.await
|
|
||||||
.unwrap_or(1);
|
|
||||||
|
|
||||||
let my_user = get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
|
||||||
let federated_instances = build_federated_instances(context.pool()).await?;
|
|
||||||
|
|
||||||
Ok(GetSiteResponse {
|
|
||||||
site_view,
|
|
||||||
admins,
|
|
||||||
banned,
|
|
||||||
online,
|
|
||||||
version: version::VERSION.to_string(),
|
|
||||||
my_user,
|
|
||||||
federated_instances,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for Search {
|
impl Perform for Search {
|
||||||
type Response = SearchResponse;
|
type Response = SearchResponse;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{get_local_user_view_from_jwt, Perform};
|
use crate::Perform;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_structs::websocket::*;
|
use lemmy_api_common::{get_local_user_view_from_jwt, websocket::*};
|
||||||
use lemmy_utils::{ConnectionId, LemmyError};
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::{
|
||||||
messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
|
messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_api_structs"
|
name = "lemmy_api_common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "lemmy_api_structs"
|
name = "lemmy_api_common"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
420
crates/api_common/src/lib.rs
Normal file
420
crates/api_common/src/lib.rs
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
pub mod comment;
|
||||||
|
pub mod community;
|
||||||
|
pub mod person;
|
||||||
|
pub mod post;
|
||||||
|
pub mod site;
|
||||||
|
pub mod websocket;
|
||||||
|
|
||||||
|
use crate::site::FederatedInstances;
|
||||||
|
use diesel::PgConnection;
|
||||||
|
use lemmy_db_queries::{
|
||||||
|
source::{
|
||||||
|
community::{CommunityModerator_, Community_},
|
||||||
|
site::Site_,
|
||||||
|
},
|
||||||
|
Crud,
|
||||||
|
DbPool,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
comment::Comment,
|
||||||
|
community::{Community, CommunityModerator},
|
||||||
|
person::Person,
|
||||||
|
person_mention::{PersonMention, PersonMentionForm},
|
||||||
|
post::Post,
|
||||||
|
site::Site,
|
||||||
|
},
|
||||||
|
CommunityId,
|
||||||
|
LocalUserId,
|
||||||
|
PersonId,
|
||||||
|
PostId,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
|
||||||
|
use lemmy_db_views_actor::{
|
||||||
|
community_person_ban_view::CommunityPersonBanView,
|
||||||
|
community_view::CommunityView,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{
|
||||||
|
claims::Claims,
|
||||||
|
email::send_email,
|
||||||
|
settings::structs::Settings,
|
||||||
|
utils::MentionData,
|
||||||
|
ApiError,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct WebFingerLink {
|
||||||
|
pub rel: Option<String>,
|
||||||
|
#[serde(rename(serialize = "type", deserialize = "type"))]
|
||||||
|
pub type_: Option<String>,
|
||||||
|
pub href: Option<Url>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub template: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct WebFingerResponse {
|
||||||
|
pub subject: String,
|
||||||
|
pub aliases: Vec<Url>,
|
||||||
|
pub links: Vec<WebFingerLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
|
||||||
|
where
|
||||||
|
F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
let pool = pool.clone();
|
||||||
|
let res = actix_web::web::block(move || {
|
||||||
|
let conn = pool.get()?;
|
||||||
|
let res = (f)(&conn);
|
||||||
|
Ok(res) as Result<_, LemmyError>
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_local_notifs(
|
||||||
|
mentions: Vec<MentionData>,
|
||||||
|
comment: Comment,
|
||||||
|
person: Person,
|
||||||
|
post: Post,
|
||||||
|
pool: &DbPool,
|
||||||
|
do_send_email: bool,
|
||||||
|
) -> Result<Vec<LocalUserId>, LemmyError> {
|
||||||
|
let ids = blocking(pool, move |conn| {
|
||||||
|
do_send_local_notifs(conn, &mentions, &comment, &person, &post, do_send_email)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_send_local_notifs(
|
||||||
|
conn: &PgConnection,
|
||||||
|
mentions: &[MentionData],
|
||||||
|
comment: &Comment,
|
||||||
|
person: &Person,
|
||||||
|
post: &Post,
|
||||||
|
do_send_email: bool,
|
||||||
|
) -> Vec<LocalUserId> {
|
||||||
|
let mut recipient_ids = Vec::new();
|
||||||
|
|
||||||
|
// Send the local mentions
|
||||||
|
for mention in mentions
|
||||||
|
.iter()
|
||||||
|
.filter(|m| m.is_local() && m.name.ne(&person.name))
|
||||||
|
.collect::<Vec<&MentionData>>()
|
||||||
|
{
|
||||||
|
if let Ok(mention_user_view) = LocalUserView::read_from_name(&conn, &mention.name) {
|
||||||
|
// 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 = PersonMentionForm {
|
||||||
|
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(&conn, &user_mention_form).ok();
|
||||||
|
|
||||||
|
// Send an email to those local users that have notifications on
|
||||||
|
if do_send_email {
|
||||||
|
send_email_to_user(
|
||||||
|
&mention_user_view,
|
||||||
|
"Mentioned by",
|
||||||
|
"Person Mention",
|
||||||
|
&comment.content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notifs to the parent commenter / poster
|
||||||
|
match comment.parent_id {
|
||||||
|
Some(parent_id) => {
|
||||||
|
if let Ok(parent_comment) = Comment::read(&conn, parent_id) {
|
||||||
|
// Don't send a notif to yourself
|
||||||
|
if parent_comment.creator_id != person.id {
|
||||||
|
// Get the parent commenter local_user
|
||||||
|
if let Ok(parent_user_view) = LocalUserView::read_person(&conn, parent_comment.creator_id)
|
||||||
|
{
|
||||||
|
recipient_ids.push(parent_user_view.local_user.id);
|
||||||
|
|
||||||
|
if do_send_email {
|
||||||
|
send_email_to_user(
|
||||||
|
&parent_user_view,
|
||||||
|
"Reply from",
|
||||||
|
"Comment Reply",
|
||||||
|
&comment.content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Its a post
|
||||||
|
None => {
|
||||||
|
if post.creator_id != person.id {
|
||||||
|
if let Ok(parent_user_view) = LocalUserView::read_person(&conn, post.creator_id) {
|
||||||
|
recipient_ids.push(parent_user_view.local_user.id);
|
||||||
|
|
||||||
|
if do_send_email {
|
||||||
|
send_email_to_user(
|
||||||
|
&parent_user_view,
|
||||||
|
"Reply from",
|
||||||
|
"Post Reply",
|
||||||
|
&comment.content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
recipient_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_email_to_user(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
subject_text: &str,
|
||||||
|
body_text: &str,
|
||||||
|
comment_content: &str,
|
||||||
|
) {
|
||||||
|
if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user_email) = &local_user_view.local_user.email {
|
||||||
|
let subject = &format!(
|
||||||
|
"{} - {} {}",
|
||||||
|
subject_text,
|
||||||
|
Settings::get().hostname(),
|
||||||
|
local_user_view.person.name,
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||||
|
body_text,
|
||||||
|
local_user_view.person.name,
|
||||||
|
comment_content,
|
||||||
|
Settings::get().get_protocol_and_hostname()
|
||||||
|
);
|
||||||
|
match send_email(subject, &user_email, &local_user_view.person.name, html) {
|
||||||
|
Ok(_o) => _o,
|
||||||
|
Err(e) => error!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_mod_or_admin(
|
||||||
|
pool: &DbPool,
|
||||||
|
person_id: PersonId,
|
||||||
|
community_id: CommunityId,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let is_mod_or_admin = blocking(pool, move |conn| {
|
||||||
|
CommunityView::is_mod_or_admin(conn, person_id, community_id)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
if !is_mod_or_admin {
|
||||||
|
return Err(ApiError::err("not_a_mod_or_admin").into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
||||||
|
if !local_user_view.local_user.admin {
|
||||||
|
return Err(ApiError::err("not_an_admin").into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
|
||||||
|
match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
|
||||||
|
Ok(post) => Ok(post),
|
||||||
|
Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_local_user_view_from_jwt(
|
||||||
|
jwt: &str,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<LocalUserView, LemmyError> {
|
||||||
|
let claims = match Claims::decode(&jwt) {
|
||||||
|
Ok(claims) => claims.claims,
|
||||||
|
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
|
||||||
|
};
|
||||||
|
let local_user_id = LocalUserId(claims.sub);
|
||||||
|
let local_user_view =
|
||||||
|
blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
|
||||||
|
// Check for a site ban
|
||||||
|
if local_user_view.person.banned {
|
||||||
|
return Err(ApiError::err("site_ban").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
|
||||||
|
|
||||||
|
Ok(local_user_view)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if user's token was issued before user's password reset.
|
||||||
|
pub fn check_validator_time(
|
||||||
|
validator_time: &chrono::NaiveDateTime,
|
||||||
|
claims: &Claims,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let user_validation_time = validator_time.timestamp();
|
||||||
|
if user_validation_time > claims.iat {
|
||||||
|
Err(ApiError::err("not_logged_in").into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_local_user_view_from_jwt_opt(
|
||||||
|
jwt: &Option<String>,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<Option<LocalUserView>, LemmyError> {
|
||||||
|
match jwt {
|
||||||
|
Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_local_user_settings_view_from_jwt(
|
||||||
|
jwt: &str,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<LocalUserSettingsView, LemmyError> {
|
||||||
|
let claims = match Claims::decode(&jwt) {
|
||||||
|
Ok(claims) => claims.claims,
|
||||||
|
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
|
||||||
|
};
|
||||||
|
let local_user_id = LocalUserId(claims.sub);
|
||||||
|
let local_user_view = blocking(pool, move |conn| {
|
||||||
|
LocalUserSettingsView::read(conn, local_user_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
// Check for a site ban
|
||||||
|
if local_user_view.person.banned {
|
||||||
|
return Err(ApiError::err("site_ban").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
|
||||||
|
|
||||||
|
Ok(local_user_view)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_local_user_settings_view_from_jwt_opt(
|
||||||
|
jwt: &Option<String>,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<Option<LocalUserSettingsView>, LemmyError> {
|
||||||
|
match jwt {
|
||||||
|
Some(jwt) => Ok(Some(
|
||||||
|
get_local_user_settings_view_from_jwt(jwt, pool).await?,
|
||||||
|
)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_community_ban(
|
||||||
|
person_id: PersonId,
|
||||||
|
community_id: CommunityId,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let is_banned =
|
||||||
|
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
|
||||||
|
if blocking(pool, is_banned).await? {
|
||||||
|
Err(ApiError::err("community_ban").into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
|
||||||
|
if score == -1 {
|
||||||
|
let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
|
||||||
|
if !site.enable_downvotes {
|
||||||
|
return Err(ApiError::err("downvotes_disabled").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of communities that the user moderates
|
||||||
|
/// or if a community_id is supplied validates the user is a moderator
|
||||||
|
/// of that community and returns the community id in a vec
|
||||||
|
///
|
||||||
|
/// * `person_id` - the person id of the moderator
|
||||||
|
/// * `community_id` - optional community id to check for moderator privileges
|
||||||
|
/// * `pool` - the diesel db pool
|
||||||
|
pub async fn collect_moderated_communities(
|
||||||
|
person_id: PersonId,
|
||||||
|
community_id: Option<CommunityId>,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<Vec<CommunityId>, LemmyError> {
|
||||||
|
if let Some(community_id) = community_id {
|
||||||
|
// if the user provides a community_id, just check for mod/admin privileges
|
||||||
|
is_mod_or_admin(pool, person_id, community_id).await?;
|
||||||
|
Ok(vec![community_id])
|
||||||
|
} else {
|
||||||
|
let ids = blocking(pool, move |conn: &'_ _| {
|
||||||
|
CommunityModerator::get_person_moderated_communities(conn, person_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
Ok(ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build_federated_instances(
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<Option<FederatedInstances>, LemmyError> {
|
||||||
|
if Settings::get().federation().enabled {
|
||||||
|
let distinct_communities = blocking(pool, move |conn| {
|
||||||
|
Community::distinct_federated_communities(conn)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let allowed = Settings::get().get_allowed_instances();
|
||||||
|
let blocked = Settings::get().get_blocked_instances();
|
||||||
|
|
||||||
|
let mut linked = distinct_communities
|
||||||
|
.iter()
|
||||||
|
.map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
|
||||||
|
.collect::<Result<Vec<String>, LemmyError>>()?;
|
||||||
|
|
||||||
|
if let Some(allowed) = allowed.as_ref() {
|
||||||
|
linked.extend_from_slice(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(blocked) = blocked.as_ref() {
|
||||||
|
linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort and remove dupes
|
||||||
|
linked.sort_unstable();
|
||||||
|
linked.dedup();
|
||||||
|
|
||||||
|
Ok(Some(FederatedInstances {
|
||||||
|
linked,
|
||||||
|
allowed,
|
||||||
|
blocked,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the password length
|
||||||
|
pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
|
||||||
|
if pass.len() > 60 {
|
||||||
|
Err(ApiError::err("invalid_password").into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
45
crates/api_crud/Cargo.toml
Normal file
45
crates/api_crud/Cargo.toml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
[package]
|
||||||
|
name = "lemmy_api_crud"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lemmy_apub = { path = "../apub" }
|
||||||
|
lemmy_utils = { path = "../utils" }
|
||||||
|
lemmy_db_queries = { path = "../db_queries" }
|
||||||
|
lemmy_db_schema = { path = "../db_schema" }
|
||||||
|
lemmy_db_views = { path = "../db_views" }
|
||||||
|
lemmy_db_views_moderator = { path = "../db_views_moderator" }
|
||||||
|
lemmy_db_views_actor = { path = "../db_views_actor" }
|
||||||
|
lemmy_api_common = { path = "../api_common" }
|
||||||
|
lemmy_websocket = { path = "../websocket" }
|
||||||
|
diesel = "1.4.5"
|
||||||
|
bcrypt = "0.9.0"
|
||||||
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
serde_json = { version = "1.0.61", features = ["preserve_order"] }
|
||||||
|
serde = { version = "1.0.123", features = ["derive"] }
|
||||||
|
actix = "0.10.0"
|
||||||
|
actix-web = { version = "3.3.2", default-features = false }
|
||||||
|
actix-rt = { version = "1.1.1", default-features = false }
|
||||||
|
awc = { version = "2.0.3", default-features = false }
|
||||||
|
log = "0.4.14"
|
||||||
|
rand = "0.8.3"
|
||||||
|
strum = "0.20.0"
|
||||||
|
strum_macros = "0.20.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
url = { version = "2.2.1", features = ["serde"] }
|
||||||
|
openssl = "0.10.32"
|
||||||
|
http = "0.2.3"
|
||||||
|
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
|
||||||
|
base64 = "0.13.0"
|
||||||
|
tokio = "0.3.6"
|
||||||
|
futures = "0.3.12"
|
||||||
|
itertools = "0.10.0"
|
||||||
|
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
||||||
|
sha2 = "0.9.3"
|
||||||
|
async-trait = "0.1.42"
|
||||||
|
captcha = "0.0.8"
|
||||||
|
anyhow = "1.0.38"
|
||||||
|
thiserror = "1.0.23"
|
||||||
|
background-jobs = "0.8.0"
|
||||||
|
reqwest = { version = "0.10.10", features = ["json"] }
|
170
crates/api_crud/src/comment/create.rs
Normal file
170
crates/api_crud/src/comment/create.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_community_ban,
|
||||||
|
comment::*,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
get_post,
|
||||||
|
send_local_notifs,
|
||||||
|
};
|
||||||
|
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
||||||
|
use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
|
||||||
|
use lemmy_db_schema::source::comment::*;
|
||||||
|
use lemmy_db_views::comment_view::CommentView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
utils::{remove_slurs, scrape_text_for_mentions},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for CreateComment {
|
||||||
|
type Response = CommentResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommentResponse, LemmyError> {
|
||||||
|
let data: &CreateComment = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||||
|
|
||||||
|
// Check for a community ban
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let post = get_post(post_id, context.pool()).await?;
|
||||||
|
|
||||||
|
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
||||||
|
|
||||||
|
// Check if post is locked, no new comments
|
||||||
|
if post.locked {
|
||||||
|
return Err(ApiError::err("locked").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a parent_id, check to make sure that comment is in that post
|
||||||
|
if let Some(parent_id) = data.parent_id {
|
||||||
|
// Make sure the parent comment exists
|
||||||
|
let parent =
|
||||||
|
match blocking(context.pool(), move |conn| Comment::read(&conn, parent_id)).await? {
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
|
||||||
|
};
|
||||||
|
if parent.post_id != post_id {
|
||||||
|
return Err(ApiError::err("couldnt_create_comment").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let comment_form = CommentForm {
|
||||||
|
content: content_slurs_removed,
|
||||||
|
parent_id: data.parent_id.to_owned(),
|
||||||
|
post_id: data.post_id,
|
||||||
|
creator_id: local_user_view.person.id,
|
||||||
|
removed: None,
|
||||||
|
deleted: None,
|
||||||
|
read: None,
|
||||||
|
published: None,
|
||||||
|
updated: None,
|
||||||
|
ap_id: None,
|
||||||
|
local: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the comment
|
||||||
|
let comment_form2 = comment_form.clone();
|
||||||
|
let inserted_comment = match blocking(context.pool(), move |conn| {
|
||||||
|
Comment::create(&conn, &comment_form2)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Necessary to update the ap_id
|
||||||
|
let inserted_comment_id = inserted_comment.id;
|
||||||
|
let updated_comment: Comment =
|
||||||
|
match blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
|
||||||
|
let apub_id =
|
||||||
|
generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?;
|
||||||
|
Ok(Comment::update_ap_id(&conn, inserted_comment_id, apub_id)?)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
updated_comment
|
||||||
|
.send_create(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Scan the comment for user mentions, add those rows
|
||||||
|
let post_id = post.id;
|
||||||
|
let mentions = scrape_text_for_mentions(&comment_form.content);
|
||||||
|
let recipient_ids = send_local_notifs(
|
||||||
|
mentions,
|
||||||
|
updated_comment.clone(),
|
||||||
|
local_user_view.person.clone(),
|
||||||
|
post,
|
||||||
|
context.pool(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// You like your own comment by default
|
||||||
|
let like_form = CommentLikeForm {
|
||||||
|
comment_id: inserted_comment.id,
|
||||||
|
post_id,
|
||||||
|
person_id: local_user_view.person.id,
|
||||||
|
score: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
|
||||||
|
if blocking(context.pool(), like).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_like_comment").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
updated_comment
|
||||||
|
.send_like(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let mut comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(&conn, inserted_comment.id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// If its a comment to yourself, mark it as read
|
||||||
|
let comment_id = comment_view.comment.id;
|
||||||
|
if local_user_view.person.id == comment_view.get_recipient_id() {
|
||||||
|
match blocking(context.pool(), move |conn| {
|
||||||
|
Comment::update_read(conn, comment_id, true)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
||||||
|
};
|
||||||
|
comment_view.comment.read = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = CommentResponse {
|
||||||
|
comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: data.form_id.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::CreateComment,
|
||||||
|
comment: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.recipient_ids = Vec::new(); // Necessary to avoid doubles
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
210
crates/api_crud/src/comment/delete.rs
Normal file
210
crates/api_crud/src/comment/delete.rs
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_community_ban,
|
||||||
|
comment::*,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
is_mod_or_admin,
|
||||||
|
send_local_notifs,
|
||||||
|
};
|
||||||
|
use lemmy_apub::ApubObjectType;
|
||||||
|
use lemmy_db_queries::{source::comment::Comment_, Crud};
|
||||||
|
use lemmy_db_schema::source::{comment::*, moderator::*};
|
||||||
|
use lemmy_db_views::comment_view::CommentView;
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for DeleteComment {
|
||||||
|
type Response = CommentResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommentResponse, LemmyError> {
|
||||||
|
let data: &DeleteComment = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let orig_comment = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(&conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
check_community_ban(
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_comment.community.id,
|
||||||
|
context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that only the creator can delete
|
||||||
|
if local_user_view.person.id != orig_comment.creator.id {
|
||||||
|
return Err(ApiError::err("no_comment_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the delete
|
||||||
|
let deleted = data.deleted;
|
||||||
|
let updated_comment = match blocking(context.pool(), move |conn| {
|
||||||
|
Comment::update_deleted(conn, comment_id, deleted)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the apub message
|
||||||
|
if deleted {
|
||||||
|
updated_comment
|
||||||
|
.send_delete(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_comment
|
||||||
|
.send_undo_delete(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch it
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Build the recipients
|
||||||
|
let comment_view_2 = comment_view.clone();
|
||||||
|
let mentions = vec![];
|
||||||
|
let recipient_ids = send_local_notifs(
|
||||||
|
mentions,
|
||||||
|
updated_comment,
|
||||||
|
local_user_view.person.clone(),
|
||||||
|
comment_view_2.post,
|
||||||
|
context.pool(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: None, // TODO a comment delete might clear forms?
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::DeleteComment,
|
||||||
|
comment: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for RemoveComment {
|
||||||
|
type Response = CommentResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommentResponse, LemmyError> {
|
||||||
|
let data: &RemoveComment = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let orig_comment = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(&conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
check_community_ban(
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_comment.community.id,
|
||||||
|
context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that only a mod or admin can remove
|
||||||
|
is_mod_or_admin(
|
||||||
|
context.pool(),
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_comment.community.id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Do the remove
|
||||||
|
let removed = data.removed;
|
||||||
|
let updated_comment = match blocking(context.pool(), move |conn| {
|
||||||
|
Comment::update_removed(conn, comment_id, removed)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mod tables
|
||||||
|
let form = ModRemoveCommentForm {
|
||||||
|
mod_person_id: local_user_view.person.id,
|
||||||
|
comment_id: data.comment_id,
|
||||||
|
removed: Some(removed),
|
||||||
|
reason: data.reason.to_owned(),
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
ModRemoveComment::create(conn, &form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Send the apub message
|
||||||
|
if removed {
|
||||||
|
updated_comment
|
||||||
|
.send_remove(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_comment
|
||||||
|
.send_undo_remove(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch it
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Build the recipients
|
||||||
|
let comment_view_2 = comment_view.clone();
|
||||||
|
|
||||||
|
let mentions = vec![];
|
||||||
|
let recipient_ids = send_local_notifs(
|
||||||
|
mentions,
|
||||||
|
updated_comment,
|
||||||
|
local_user_view.person.clone(),
|
||||||
|
comment_view_2.post,
|
||||||
|
context.pool(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: None, // TODO maybe this might clear other forms
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::RemoveComment,
|
||||||
|
comment: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
4
crates/api_crud/src/comment/mod.rs
Normal file
4
crates/api_crud/src/comment/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod create;
|
||||||
|
mod delete;
|
||||||
|
mod read;
|
||||||
|
mod update;
|
51
crates/api_crud/src/comment/read.rs
Normal file
51
crates/api_crud/src/comment/read.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, comment::*, get_local_user_view_from_jwt_opt};
|
||||||
|
use lemmy_db_queries::{ListingType, SortType};
|
||||||
|
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetComments {
|
||||||
|
type Response = GetCommentsResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetCommentsResponse, LemmyError> {
|
||||||
|
let data: &GetComments = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
let person_id = local_user_view.map(|u| u.person.id);
|
||||||
|
|
||||||
|
let type_ = ListingType::from_str(&data.type_)?;
|
||||||
|
let sort = SortType::from_str(&data.sort)?;
|
||||||
|
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let community_name = data.community_name.to_owned();
|
||||||
|
let saved_only = data.saved_only;
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let comments = blocking(context.pool(), move |conn| {
|
||||||
|
CommentQueryBuilder::create(conn)
|
||||||
|
.listing_type(type_)
|
||||||
|
.sort(&sort)
|
||||||
|
.saved_only(saved_only)
|
||||||
|
.community_id(community_id)
|
||||||
|
.community_name(community_name)
|
||||||
|
.my_person_id(person_id)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let comments = match comments {
|
||||||
|
Ok(comments) => comments,
|
||||||
|
Err(_) => return Err(ApiError::err("couldnt_get_comments").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(GetCommentsResponse { comments })
|
||||||
|
}
|
||||||
|
}
|
103
crates/api_crud/src/comment/update.rs
Normal file
103
crates/api_crud/src/comment/update.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_community_ban,
|
||||||
|
comment::*,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
send_local_notifs,
|
||||||
|
};
|
||||||
|
use lemmy_apub::ApubObjectType;
|
||||||
|
use lemmy_db_queries::source::comment::Comment_;
|
||||||
|
use lemmy_db_schema::source::comment::*;
|
||||||
|
use lemmy_db_views::comment_view::CommentView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
utils::{remove_slurs, scrape_text_for_mentions},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for EditComment {
|
||||||
|
type Response = CommentResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommentResponse, LemmyError> {
|
||||||
|
let data: &EditComment = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let orig_comment = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(&conn, comment_id, None)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
check_community_ban(
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_comment.community.id,
|
||||||
|
context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that only the creator can edit
|
||||||
|
if local_user_view.person.id != orig_comment.creator.id {
|
||||||
|
return Err(ApiError::err("no_comment_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the update
|
||||||
|
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let updated_comment = match blocking(context.pool(), move |conn| {
|
||||||
|
Comment::update_content(conn, comment_id, &content_slurs_removed)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(comment) => comment,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the apub update
|
||||||
|
updated_comment
|
||||||
|
.send_update(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Do the mentions / recipients
|
||||||
|
let updated_comment_content = updated_comment.content.to_owned();
|
||||||
|
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
||||||
|
let recipient_ids = send_local_notifs(
|
||||||
|
mentions,
|
||||||
|
updated_comment,
|
||||||
|
local_user_view.person.clone(),
|
||||||
|
orig_comment.post,
|
||||||
|
context.pool(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommentView::read(conn, comment_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = CommentResponse {
|
||||||
|
comment_view,
|
||||||
|
recipient_ids,
|
||||||
|
form_id: data.form_id.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendComment {
|
||||||
|
op: UserOperation::EditComment,
|
||||||
|
comment: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
134
crates/api_crud/src/community/create.rs
Normal file
134
crates/api_crud/src/community/create.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
community::{CommunityResponse, CreateCommunity},
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
};
|
||||||
|
use lemmy_apub::{
|
||||||
|
generate_apub_endpoint,
|
||||||
|
generate_followers_url,
|
||||||
|
generate_inbox_url,
|
||||||
|
generate_shared_inbox_url,
|
||||||
|
EndpointType,
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::{diesel_option_overwrite_to_url, ApubObject, Crud, Followable, Joinable};
|
||||||
|
use lemmy_db_schema::source::community::{
|
||||||
|
Community,
|
||||||
|
CommunityFollower,
|
||||||
|
CommunityFollowerForm,
|
||||||
|
CommunityForm,
|
||||||
|
CommunityModerator,
|
||||||
|
CommunityModeratorForm,
|
||||||
|
};
|
||||||
|
use lemmy_db_views_actor::community_view::CommunityView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
apub::generate_actor_keypair,
|
||||||
|
utils::{check_slurs, check_slurs_opt, is_valid_community_name},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for CreateCommunity {
|
||||||
|
type Response = CommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError> {
|
||||||
|
let data: &CreateCommunity = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
check_slurs(&data.name)?;
|
||||||
|
check_slurs(&data.title)?;
|
||||||
|
check_slurs_opt(&data.description)?;
|
||||||
|
|
||||||
|
if !is_valid_community_name(&data.name) {
|
||||||
|
return Err(ApiError::err("invalid_community_name").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double check for duplicate community actor_ids
|
||||||
|
let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?;
|
||||||
|
let actor_id_cloned = community_actor_id.to_owned();
|
||||||
|
let community_dupe = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_apub_id(conn, &actor_id_cloned)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
if community_dupe.is_ok() {
|
||||||
|
return Err(ApiError::err("community_already_exists").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to make sure the icon and banners are urls
|
||||||
|
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||||
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
|
|
||||||
|
// When you create a community, make sure the user becomes a moderator and a follower
|
||||||
|
let keypair = generate_actor_keypair()?;
|
||||||
|
|
||||||
|
let community_form = CommunityForm {
|
||||||
|
name: data.name.to_owned(),
|
||||||
|
title: data.title.to_owned(),
|
||||||
|
description: data.description.to_owned(),
|
||||||
|
icon,
|
||||||
|
banner,
|
||||||
|
creator_id: local_user_view.person.id,
|
||||||
|
removed: None,
|
||||||
|
deleted: None,
|
||||||
|
nsfw: data.nsfw,
|
||||||
|
updated: None,
|
||||||
|
actor_id: Some(community_actor_id.to_owned()),
|
||||||
|
local: true,
|
||||||
|
private_key: Some(keypair.private_key),
|
||||||
|
public_key: Some(keypair.public_key),
|
||||||
|
last_refreshed_at: None,
|
||||||
|
published: None,
|
||||||
|
followers_url: Some(generate_followers_url(&community_actor_id)?),
|
||||||
|
inbox_url: Some(generate_inbox_url(&community_actor_id)?),
|
||||||
|
shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_community = match blocking(context.pool(), move |conn| {
|
||||||
|
Community::create(conn, &community_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("community_already_exists").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The community creator becomes a moderator
|
||||||
|
let community_moderator_form = CommunityModeratorForm {
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
person_id: local_user_view.person.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||||
|
if blocking(context.pool(), join).await?.is_err() {
|
||||||
|
return Err(ApiError::err("community_moderator_already_exists").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow your own community
|
||||||
|
let community_follower_form = CommunityFollowerForm {
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
person_id: local_user_view.person.id,
|
||||||
|
pending: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
||||||
|
if blocking(context.pool(), follow).await?.is_err() {
|
||||||
|
return Err(ApiError::err("community_follower_already_exists").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let community_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, inserted_community.id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(CommunityResponse { community_view })
|
||||||
|
}
|
||||||
|
}
|
134
crates/api_crud/src/community/delete.rs
Normal file
134
crates/api_crud/src/community/delete.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use crate::{community::send_community_websocket, PerformCrud};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt, is_admin};
|
||||||
|
use lemmy_apub::CommunityType;
|
||||||
|
use lemmy_db_queries::{source::community::Community_, Crud};
|
||||||
|
use lemmy_db_schema::source::{
|
||||||
|
community::*,
|
||||||
|
moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
|
||||||
|
};
|
||||||
|
use lemmy_db_views_actor::community_view::CommunityView;
|
||||||
|
use lemmy_utils::{utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for DeleteCommunity {
|
||||||
|
type Response = CommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError> {
|
||||||
|
let data: &DeleteCommunity = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// Verify its the creator (only a creator can delete the community)
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let read_community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read(conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if read_community.creator_id != local_user_view.person.id {
|
||||||
|
return Err(ApiError::err("no_community_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the delete
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let deleted = data.deleted;
|
||||||
|
let updated_community = match blocking(context.pool(), move |conn| {
|
||||||
|
Community::update_deleted(conn, community_id, deleted)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send apub messages
|
||||||
|
if deleted {
|
||||||
|
updated_community.send_delete(context).await?;
|
||||||
|
} else {
|
||||||
|
updated_community.send_undo_delete(context).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let community_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = CommunityResponse { community_view };
|
||||||
|
|
||||||
|
send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for RemoveCommunity {
|
||||||
|
type Response = CommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError> {
|
||||||
|
let data: &RemoveCommunity = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// Verify its an admin (only an admin can remove a community)
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
// Do the remove
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let removed = data.removed;
|
||||||
|
let updated_community = match blocking(context.pool(), move |conn| {
|
||||||
|
Community::update_removed(conn, community_id, removed)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mod tables
|
||||||
|
let expires = match data.expires {
|
||||||
|
Some(time) => Some(naive_from_unix(time)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let form = ModRemoveCommunityForm {
|
||||||
|
mod_person_id: local_user_view.person.id,
|
||||||
|
community_id: data.community_id,
|
||||||
|
removed: Some(removed),
|
||||||
|
reason: data.reason.to_owned(),
|
||||||
|
expires,
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
ModRemoveCommunity::create(conn, &form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Apub messages
|
||||||
|
if removed {
|
||||||
|
updated_community.send_remove(context).await?;
|
||||||
|
} else {
|
||||||
|
updated_community.send_undo_remove(context).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let community_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = CommunityResponse { community_view };
|
||||||
|
|
||||||
|
send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
27
crates/api_crud/src/community/mod.rs
Normal file
27
crates/api_crud/src/community/mod.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::community::CommunityResponse;
|
||||||
|
use lemmy_utils::ConnectionId;
|
||||||
|
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
mod create;
|
||||||
|
mod delete;
|
||||||
|
mod read;
|
||||||
|
mod update;
|
||||||
|
|
||||||
|
pub(in crate::community) fn send_community_websocket(
|
||||||
|
res: &CommunityResponse,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
op: UserOperation,
|
||||||
|
) {
|
||||||
|
// Strip out the person id and subscribed when sending to others
|
||||||
|
let mut res_sent = res.clone();
|
||||||
|
res_sent.community_view.subscribed = false;
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
|
op,
|
||||||
|
response: res_sent,
|
||||||
|
community_id: res.community_view.community.id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
121
crates/api_crud/src/community/read.rs
Normal file
121
crates/api_crud/src/community/read.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt};
|
||||||
|
use lemmy_db_queries::{source::community::Community_, ListingType, SortType};
|
||||||
|
use lemmy_db_schema::source::community::*;
|
||||||
|
use lemmy_db_views_actor::{
|
||||||
|
community_moderator_view::CommunityModeratorView,
|
||||||
|
community_view::{CommunityQueryBuilder, CommunityView},
|
||||||
|
};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::GetCommunityUsersOnline, LemmyContext};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetCommunity {
|
||||||
|
type Response = GetCommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetCommunityResponse, LemmyError> {
|
||||||
|
let data: &GetCommunity = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
let person_id = local_user_view.map(|u| u.person.id);
|
||||||
|
|
||||||
|
let community_id = match data.id {
|
||||||
|
Some(id) => id,
|
||||||
|
None => {
|
||||||
|
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
|
||||||
|
match blocking(context.pool(), move |conn| {
|
||||||
|
Community::read_from_name(conn, &name)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
||||||
|
}
|
||||||
|
.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_view = match blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, person_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
|
||||||
|
CommunityModeratorView::for_community(conn, community_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(moderators) => moderators,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let online = context
|
||||||
|
.chat_server()
|
||||||
|
.send(GetCommunityUsersOnline { community_id })
|
||||||
|
.await
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
|
let res = GetCommunityResponse {
|
||||||
|
community_view,
|
||||||
|
moderators,
|
||||||
|
online,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for ListCommunities {
|
||||||
|
type Response = ListCommunitiesResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<ListCommunitiesResponse, LemmyError> {
|
||||||
|
let data: &ListCommunities = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let person_id = match &local_user_view {
|
||||||
|
Some(uv) => Some(uv.person.id),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't show NSFW by default
|
||||||
|
let show_nsfw = match &local_user_view {
|
||||||
|
Some(uv) => uv.local_user.show_nsfw,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let type_ = ListingType::from_str(&data.type_)?;
|
||||||
|
let sort = SortType::from_str(&data.sort)?;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let communities = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityQueryBuilder::create(conn)
|
||||||
|
.listing_type(&type_)
|
||||||
|
.sort(&sort)
|
||||||
|
.show_nsfw(show_nsfw)
|
||||||
|
.my_person_id(person_id)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
Ok(ListCommunitiesResponse { communities })
|
||||||
|
}
|
||||||
|
}
|
109
crates/api_crud/src/community/update.rs
Normal file
109
crates/api_crud/src/community/update.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
use crate::{community::send_community_websocket, PerformCrud};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
community::{CommunityResponse, EditCommunity},
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
naive_now,
|
||||||
|
source::community::{Community, CommunityForm},
|
||||||
|
PersonId,
|
||||||
|
};
|
||||||
|
use lemmy_db_views_actor::{
|
||||||
|
community_moderator_view::CommunityModeratorView,
|
||||||
|
community_view::CommunityView,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{
|
||||||
|
utils::{check_slurs, check_slurs_opt},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for EditCommunity {
|
||||||
|
type Response = CommunityResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<CommunityResponse, LemmyError> {
|
||||||
|
let data: &EditCommunity = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
check_slurs(&data.title)?;
|
||||||
|
check_slurs_opt(&data.description)?;
|
||||||
|
|
||||||
|
// Verify its a mod (only mods can edit it)
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let mods: Vec<PersonId> = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityModeratorView::for_community(conn, community_id)
|
||||||
|
.map(|v| v.into_iter().map(|m| m.moderator.id).collect())
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if !mods.contains(&local_user_view.person.id) {
|
||||||
|
return Err(ApiError::err("not_a_moderator").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let read_community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read(conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||||
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
|
|
||||||
|
let community_form = CommunityForm {
|
||||||
|
name: read_community.name,
|
||||||
|
title: data.title.to_owned(),
|
||||||
|
description: data.description.to_owned(),
|
||||||
|
icon,
|
||||||
|
banner,
|
||||||
|
creator_id: read_community.creator_id,
|
||||||
|
removed: Some(read_community.removed),
|
||||||
|
deleted: Some(read_community.deleted),
|
||||||
|
nsfw: data.nsfw,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
actor_id: Some(read_community.actor_id),
|
||||||
|
local: read_community.local,
|
||||||
|
private_key: read_community.private_key,
|
||||||
|
public_key: read_community.public_key,
|
||||||
|
last_refreshed_at: None,
|
||||||
|
published: None,
|
||||||
|
followers_url: None,
|
||||||
|
inbox_url: None,
|
||||||
|
shared_inbox_url: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let community_id = data.community_id;
|
||||||
|
match blocking(context.pool(), move |conn| {
|
||||||
|
Community::update(conn, community_id, &community_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO there needs to be some kind of an apub update
|
||||||
|
// process for communities and users
|
||||||
|
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let community_view = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = CommunityResponse { community_view };
|
||||||
|
|
||||||
|
send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
22
crates/api_crud/src/lib.rs
Normal file
22
crates/api_crud/src/lib.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
mod comment;
|
||||||
|
mod community;
|
||||||
|
mod post;
|
||||||
|
mod private_message;
|
||||||
|
pub mod routes;
|
||||||
|
mod site;
|
||||||
|
mod user;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
pub trait PerformCrud {
|
||||||
|
type Response: serde::ser::Serialize + Send;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError>;
|
||||||
|
}
|
130
crates/api_crud/src/post/create.rs
Normal file
130
crates/api_crud/src/post/create.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*};
|
||||||
|
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
||||||
|
use lemmy_db_queries::{source::post::Post_, Crud, Likeable};
|
||||||
|
use lemmy_db_schema::source::post::*;
|
||||||
|
use lemmy_db_views::post_view::PostView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
request::fetch_iframely_and_pictrs_data,
|
||||||
|
utils::{check_slurs, check_slurs_opt, is_valid_post_title},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for CreatePost {
|
||||||
|
type Response = PostResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PostResponse, LemmyError> {
|
||||||
|
let data: &CreatePost = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
check_slurs(&data.name)?;
|
||||||
|
check_slurs_opt(&data.body)?;
|
||||||
|
|
||||||
|
if !is_valid_post_title(&data.name) {
|
||||||
|
return Err(ApiError::err("invalid_post_title").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?;
|
||||||
|
|
||||||
|
// Fetch Iframely and pictrs cached image
|
||||||
|
let data_url = data.url.as_ref();
|
||||||
|
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||||
|
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
||||||
|
|
||||||
|
let post_form = PostForm {
|
||||||
|
name: data.name.trim().to_owned(),
|
||||||
|
url: data_url.map(|u| u.to_owned().into()),
|
||||||
|
body: data.body.to_owned(),
|
||||||
|
community_id: data.community_id,
|
||||||
|
creator_id: local_user_view.person.id,
|
||||||
|
removed: None,
|
||||||
|
deleted: None,
|
||||||
|
nsfw: data.nsfw,
|
||||||
|
locked: None,
|
||||||
|
stickied: None,
|
||||||
|
updated: None,
|
||||||
|
embed_title: iframely_title,
|
||||||
|
embed_description: iframely_description,
|
||||||
|
embed_html: iframely_html,
|
||||||
|
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||||
|
ap_id: None,
|
||||||
|
local: true,
|
||||||
|
published: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_post =
|
||||||
|
match blocking(context.pool(), move |conn| Post::create(conn, &post_form)).await? {
|
||||||
|
Ok(post) => post,
|
||||||
|
Err(e) => {
|
||||||
|
let err_type = if e.to_string() == "value too long for type character varying(200)" {
|
||||||
|
"post_title_too_long"
|
||||||
|
} else {
|
||||||
|
"couldnt_create_post"
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(ApiError::err(err_type).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_post_id = inserted_post.id;
|
||||||
|
let updated_post = match blocking(context.pool(), move |conn| -> Result<Post, LemmyError> {
|
||||||
|
let apub_id = generate_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string())?;
|
||||||
|
Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(post) => post,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_post").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
updated_post
|
||||||
|
.send_create(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// They like their own post by default
|
||||||
|
let like_form = PostLikeForm {
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
person_id: local_user_view.person.id,
|
||||||
|
score: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
|
||||||
|
if blocking(context.pool(), like).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_like_post").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
updated_post
|
||||||
|
.send_like(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Refetch the view
|
||||||
|
let inserted_post_id = inserted_post.id;
|
||||||
|
let post_view = match blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, inserted_post_id, Some(local_user_view.person.id))
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(post) => post,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = PostResponse { post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::CreatePost,
|
||||||
|
post: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
161
crates/api_crud/src/post/delete.rs
Normal file
161
crates/api_crud/src/post/delete.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_community_ban,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
is_mod_or_admin,
|
||||||
|
post::*,
|
||||||
|
};
|
||||||
|
use lemmy_apub::ApubObjectType;
|
||||||
|
use lemmy_db_queries::{source::post::Post_, Crud};
|
||||||
|
use lemmy_db_schema::source::{moderator::*, post::*};
|
||||||
|
use lemmy_db_views::post_view::PostView;
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for DeletePost {
|
||||||
|
type Response = PostResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PostResponse, LemmyError> {
|
||||||
|
let data: &DeletePost = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||||
|
|
||||||
|
check_community_ban(
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_post.community_id,
|
||||||
|
context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that only the creator can delete
|
||||||
|
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
||||||
|
return Err(ApiError::err("no_post_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the post
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let deleted = data.deleted;
|
||||||
|
let updated_post = blocking(context.pool(), move |conn| {
|
||||||
|
Post::update_deleted(conn, post_id, deleted)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// apub updates
|
||||||
|
if deleted {
|
||||||
|
updated_post
|
||||||
|
.send_delete(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_post
|
||||||
|
.send_undo_delete(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch the post
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::DeletePost,
|
||||||
|
post: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for RemovePost {
|
||||||
|
type Response = PostResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PostResponse, LemmyError> {
|
||||||
|
let data: &RemovePost = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||||
|
|
||||||
|
check_community_ban(
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_post.community_id,
|
||||||
|
context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that only the mods can remove
|
||||||
|
is_mod_or_admin(
|
||||||
|
context.pool(),
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_post.community_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Update the post
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let removed = data.removed;
|
||||||
|
let updated_post = blocking(context.pool(), move |conn| {
|
||||||
|
Post::update_removed(conn, post_id, removed)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Mod tables
|
||||||
|
let form = ModRemovePostForm {
|
||||||
|
mod_person_id: local_user_view.person.id,
|
||||||
|
post_id: data.post_id,
|
||||||
|
removed: Some(removed),
|
||||||
|
reason: data.reason.to_owned(),
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
ModRemovePost::create(conn, &form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// apub updates
|
||||||
|
if removed {
|
||||||
|
updated_post
|
||||||
|
.send_remove(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_post
|
||||||
|
.send_undo_remove(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch the post
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, Some(person_id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::RemovePost,
|
||||||
|
post: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
4
crates/api_crud/src/post/mod.rs
Normal file
4
crates/api_crud/src/post/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod create;
|
||||||
|
mod delete;
|
||||||
|
mod read;
|
||||||
|
mod update;
|
135
crates/api_crud/src/post/read.rs
Normal file
135
crates/api_crud/src/post/read.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, post::*};
|
||||||
|
use lemmy_db_queries::{ListingType, SortType};
|
||||||
|
use lemmy_db_views::{
|
||||||
|
comment_view::CommentQueryBuilder,
|
||||||
|
post_view::{PostQueryBuilder, PostView},
|
||||||
|
};
|
||||||
|
use lemmy_db_views_actor::{
|
||||||
|
community_moderator_view::CommunityModeratorView,
|
||||||
|
community_view::CommunityView,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::GetPostUsersOnline, LemmyContext};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetPost {
|
||||||
|
type Response = GetPostResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetPostResponse, LemmyError> {
|
||||||
|
let data: &GetPost = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
let person_id = local_user_view.map(|u| u.person.id);
|
||||||
|
|
||||||
|
let id = data.id;
|
||||||
|
let post_view = match blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, id, person_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(post) => post,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = data.id;
|
||||||
|
let comments = blocking(context.pool(), move |conn| {
|
||||||
|
CommentQueryBuilder::create(conn)
|
||||||
|
.my_person_id(person_id)
|
||||||
|
.post_id(id)
|
||||||
|
.limit(9999)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_id = post_view.community.id;
|
||||||
|
let moderators = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityModeratorView::for_community(conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Necessary for the sidebar
|
||||||
|
let community_view = match blocking(context.pool(), move |conn| {
|
||||||
|
CommunityView::read(conn, community_id, person_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(community) => community,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let online = context
|
||||||
|
.chat_server()
|
||||||
|
.send(GetPostUsersOnline { post_id: data.id })
|
||||||
|
.await
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
Ok(GetPostResponse {
|
||||||
|
post_view,
|
||||||
|
community_view,
|
||||||
|
comments,
|
||||||
|
moderators,
|
||||||
|
online,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetPosts {
|
||||||
|
type Response = GetPostsResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetPostsResponse, LemmyError> {
|
||||||
|
let data: &GetPosts = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let person_id = match &local_user_view {
|
||||||
|
Some(uv) => Some(uv.person.id),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let show_nsfw = match &local_user_view {
|
||||||
|
Some(uv) => uv.local_user.show_nsfw,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let type_ = ListingType::from_str(&data.type_)?;
|
||||||
|
let sort = SortType::from_str(&data.sort)?;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let community_id = data.community_id;
|
||||||
|
let community_name = data.community_name.to_owned();
|
||||||
|
let saved_only = data.saved_only;
|
||||||
|
|
||||||
|
let posts = match blocking(context.pool(), move |conn| {
|
||||||
|
PostQueryBuilder::create(conn)
|
||||||
|
.listing_type(&type_)
|
||||||
|
.sort(&sort)
|
||||||
|
.show_nsfw(show_nsfw)
|
||||||
|
.community_id(community_id)
|
||||||
|
.community_name(community_name)
|
||||||
|
.saved_only(saved_only)
|
||||||
|
.my_person_id(person_id)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(posts) => posts,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_get_posts").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(GetPostsResponse { posts })
|
||||||
|
}
|
||||||
|
}
|
116
crates/api_crud/src/post/update.rs
Normal file
116
crates/api_crud/src/post/update.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*};
|
||||||
|
use lemmy_apub::ApubObjectType;
|
||||||
|
use lemmy_db_queries::{source::post::Post_, Crud};
|
||||||
|
use lemmy_db_schema::{naive_now, source::post::*};
|
||||||
|
use lemmy_db_views::post_view::PostView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
request::fetch_iframely_and_pictrs_data,
|
||||||
|
utils::{check_slurs, check_slurs_opt, is_valid_post_title},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for EditPost {
|
||||||
|
type Response = PostResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PostResponse, LemmyError> {
|
||||||
|
let data: &EditPost = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
check_slurs(&data.name)?;
|
||||||
|
check_slurs_opt(&data.body)?;
|
||||||
|
|
||||||
|
if !is_valid_post_title(&data.name) {
|
||||||
|
return Err(ApiError::err("invalid_post_title").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||||
|
|
||||||
|
check_community_ban(
|
||||||
|
local_user_view.person.id,
|
||||||
|
orig_post.community_id,
|
||||||
|
context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that only the creator can edit
|
||||||
|
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
||||||
|
return Err(ApiError::err("no_post_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Iframely and Pictrs cached image
|
||||||
|
let data_url = data.url.as_ref();
|
||||||
|
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||||
|
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
||||||
|
|
||||||
|
let post_form = PostForm {
|
||||||
|
name: data.name.trim().to_owned(),
|
||||||
|
url: data_url.map(|u| u.to_owned().into()),
|
||||||
|
body: data.body.to_owned(),
|
||||||
|
nsfw: data.nsfw,
|
||||||
|
creator_id: orig_post.creator_id.to_owned(),
|
||||||
|
community_id: orig_post.community_id,
|
||||||
|
removed: Some(orig_post.removed),
|
||||||
|
deleted: Some(orig_post.deleted),
|
||||||
|
locked: Some(orig_post.locked),
|
||||||
|
stickied: Some(orig_post.stickied),
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
embed_title: iframely_title,
|
||||||
|
embed_description: iframely_description,
|
||||||
|
embed_html: iframely_html,
|
||||||
|
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||||
|
ap_id: Some(orig_post.ap_id),
|
||||||
|
local: orig_post.local,
|
||||||
|
published: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let res = blocking(context.pool(), move |conn| {
|
||||||
|
Post::update(conn, post_id, &post_form)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let updated_post: Post = match res {
|
||||||
|
Ok(post) => post,
|
||||||
|
Err(e) => {
|
||||||
|
let err_type = if e.to_string() == "value too long for type character varying(200)" {
|
||||||
|
"post_title_too_long"
|
||||||
|
} else {
|
||||||
|
"couldnt_update_post"
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(ApiError::err(err_type).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send apub update
|
||||||
|
updated_post
|
||||||
|
.send_update(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let post_id = data.post_id;
|
||||||
|
let post_view = blocking(context.pool(), move |conn| {
|
||||||
|
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PostResponse { post_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendPost {
|
||||||
|
op: UserOperation::EditPost,
|
||||||
|
post: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
112
crates/api_crud/src/private_message/create.rs
Normal file
112
crates/api_crud/src/private_message/create.rs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
person::{CreatePrivateMessage, PrivateMessageResponse},
|
||||||
|
send_email_to_user,
|
||||||
|
};
|
||||||
|
use lemmy_apub::{generate_apub_endpoint, ApubObjectType, EndpointType};
|
||||||
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||||
|
use lemmy_db_schema::source::private_message::{PrivateMessage, PrivateMessageForm};
|
||||||
|
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||||
|
use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for CreatePrivateMessage {
|
||||||
|
type Response = PrivateMessageResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PrivateMessageResponse, LemmyError> {
|
||||||
|
let data: &CreatePrivateMessage = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||||
|
|
||||||
|
let private_message_form = PrivateMessageForm {
|
||||||
|
content: content_slurs_removed.to_owned(),
|
||||||
|
creator_id: local_user_view.person.id,
|
||||||
|
recipient_id: data.recipient_id,
|
||||||
|
deleted: None,
|
||||||
|
read: None,
|
||||||
|
updated: None,
|
||||||
|
ap_id: None,
|
||||||
|
local: true,
|
||||||
|
published: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_private_message = match blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::create(conn, &private_message_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => {
|
||||||
|
return Err(ApiError::err("couldnt_create_private_message").into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_private_message_id = inserted_private_message.id;
|
||||||
|
let updated_private_message = match blocking(
|
||||||
|
context.pool(),
|
||||||
|
move |conn| -> Result<PrivateMessage, LemmyError> {
|
||||||
|
let apub_id = generate_apub_endpoint(
|
||||||
|
EndpointType::PrivateMessage,
|
||||||
|
&inserted_private_message_id.to_string(),
|
||||||
|
)?;
|
||||||
|
Ok(PrivateMessage::update_ap_id(
|
||||||
|
&conn,
|
||||||
|
inserted_private_message_id,
|
||||||
|
apub_id,
|
||||||
|
)?)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_create_private_message").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
updated_private_message
|
||||||
|
.send_create(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let private_message_view = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageView::read(conn, inserted_private_message.id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
private_message_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notifications to the local recipient, if one exists
|
||||||
|
let recipient_id = data.recipient_id;
|
||||||
|
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
||||||
|
LocalUserView::read_person(conn, recipient_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
send_email_to_user(
|
||||||
|
&local_recipient,
|
||||||
|
"Private Message from",
|
||||||
|
"Private Message",
|
||||||
|
&content_slurs_removed,
|
||||||
|
);
|
||||||
|
|
||||||
|
let local_recipient_id = local_recipient.local_user.id;
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::CreatePrivateMessage,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
88
crates/api_crud/src/private_message/delete.rs
Normal file
88
crates/api_crud/src/private_message/delete.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
person::{DeletePrivateMessage, PrivateMessageResponse},
|
||||||
|
};
|
||||||
|
use lemmy_apub::ApubObjectType;
|
||||||
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||||
|
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||||
|
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for DeletePrivateMessage {
|
||||||
|
type Response = PrivateMessageResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PrivateMessageResponse, LemmyError> {
|
||||||
|
let data: &DeletePrivateMessage = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// Checking permissions
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let orig_private_message = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if local_user_view.person.id != orig_private_message.creator_id {
|
||||||
|
return Err(ApiError::err("no_private_message_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doing the update
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let deleted = data.deleted;
|
||||||
|
let updated_private_message = match blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::update_deleted(conn, private_message_id, deleted)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the apub update
|
||||||
|
if data.deleted {
|
||||||
|
updated_private_message
|
||||||
|
.send_delete(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
updated_private_message
|
||||||
|
.send_undo_delete(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let private_message_view = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageView::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
private_message_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notifications to the local recipient, if one exists
|
||||||
|
let recipient_id = orig_private_message.recipient_id;
|
||||||
|
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
||||||
|
LocalUserView::read_person(conn, recipient_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let local_recipient_id = local_recipient.local_user.id;
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::DeletePrivateMessage,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
4
crates/api_crud/src/private_message/mod.rs
Normal file
4
crates/api_crud/src/private_message/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod create;
|
||||||
|
mod delete;
|
||||||
|
mod read;
|
||||||
|
mod update;
|
41
crates/api_crud/src/private_message/read.rs
Normal file
41
crates/api_crud/src/private_message/read.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
person::{GetPrivateMessages, PrivateMessagesResponse},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::private_message_view::PrivateMessageQueryBuilder;
|
||||||
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetPrivateMessages {
|
||||||
|
type Response = PrivateMessagesResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PrivateMessagesResponse, LemmyError> {
|
||||||
|
let data: &GetPrivateMessages = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let unread_only = data.unread_only;
|
||||||
|
let messages = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageQueryBuilder::create(&conn, person_id)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.unread_only(unread_only)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(PrivateMessagesResponse {
|
||||||
|
private_messages: messages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
82
crates/api_crud/src/private_message/update.rs
Normal file
82
crates/api_crud/src/private_message/update.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
person::{EditPrivateMessage, PrivateMessageResponse},
|
||||||
|
};
|
||||||
|
use lemmy_apub::ApubObjectType;
|
||||||
|
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||||
|
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||||
|
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||||
|
use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for EditPrivateMessage {
|
||||||
|
type Response = PrivateMessageResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<PrivateMessageResponse, LemmyError> {
|
||||||
|
let data: &EditPrivateMessage = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// Checking permissions
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let orig_private_message = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if local_user_view.person.id != orig_private_message.creator_id {
|
||||||
|
return Err(ApiError::err("no_private_message_edit_allowed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doing the update
|
||||||
|
let content_slurs_removed = remove_slurs(&data.content);
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let updated_private_message = match blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(private_message) => private_message,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the apub update
|
||||||
|
updated_private_message
|
||||||
|
.send_update(&local_user_view.person, context)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let private_message_id = data.private_message_id;
|
||||||
|
let private_message_view = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageView::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PrivateMessageResponse {
|
||||||
|
private_message_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send notifications to the local recipient, if one exists
|
||||||
|
let recipient_id = orig_private_message.recipient_id;
|
||||||
|
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
||||||
|
LocalUserView::read_person(conn, recipient_id)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let local_recipient_id = local_recipient.local_user.id;
|
||||||
|
context.chat_server().do_send(SendUserRoomMessage {
|
||||||
|
op: UserOperation::EditPrivateMessage,
|
||||||
|
response: res.clone(),
|
||||||
|
local_recipient_id,
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
133
crates/api_crud/src/routes.rs
Normal file
133
crates/api_crud/src/routes.rs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::{error::ErrorBadRequest, *};
|
||||||
|
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*};
|
||||||
|
use lemmy_utils::rate_limit::RateLimit;
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
|
cfg
|
||||||
|
.service(
|
||||||
|
web::scope("/api/v2")
|
||||||
|
// Site
|
||||||
|
.service(
|
||||||
|
web::scope("/site")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("", web::get().to(route_get::<GetSite>))
|
||||||
|
// Admin Actions
|
||||||
|
.route("", web::post().to(route_post::<CreateSite>))
|
||||||
|
.route("", web::put().to(route_post::<EditSite>)),
|
||||||
|
)
|
||||||
|
// Community
|
||||||
|
.service(
|
||||||
|
web::resource("/community")
|
||||||
|
.guard(guard::Post())
|
||||||
|
.wrap(rate_limit.register())
|
||||||
|
.route(web::post().to(route_post::<CreateCommunity>)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::scope("/community")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("", web::get().to(route_get::<GetCommunity>))
|
||||||
|
.route("", web::put().to(route_post::<EditCommunity>))
|
||||||
|
.route("/list", web::get().to(route_get::<ListCommunities>))
|
||||||
|
.route("/delete", web::post().to(route_post::<DeleteCommunity>))
|
||||||
|
// Mod Actions
|
||||||
|
.route("/remove", web::post().to(route_post::<RemoveCommunity>)),
|
||||||
|
)
|
||||||
|
// Post
|
||||||
|
.service(
|
||||||
|
// Handle POST to /post separately to add the post() rate limitter
|
||||||
|
web::resource("/post")
|
||||||
|
.guard(guard::Post())
|
||||||
|
.wrap(rate_limit.post())
|
||||||
|
.route(web::post().to(route_post::<CreatePost>)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::scope("/post")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("", web::get().to(route_get::<GetPost>))
|
||||||
|
.route("", web::put().to(route_post::<EditPost>))
|
||||||
|
.route("/delete", web::post().to(route_post::<DeletePost>))
|
||||||
|
.route("/remove", web::post().to(route_post::<RemovePost>))
|
||||||
|
.route("/list", web::get().to(route_get::<GetPosts>)),
|
||||||
|
)
|
||||||
|
// Comment
|
||||||
|
.service(
|
||||||
|
web::scope("/comment")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("", web::post().to(route_post::<CreateComment>))
|
||||||
|
.route("", web::put().to(route_post::<EditComment>))
|
||||||
|
.route("/delete", web::post().to(route_post::<DeleteComment>))
|
||||||
|
.route("/remove", web::post().to(route_post::<RemoveComment>))
|
||||||
|
.route("/list", web::get().to(route_get::<GetComments>)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Private Message
|
||||||
|
.service(
|
||||||
|
web::scope("/private_message")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("/list", web::get().to(route_get::<GetPrivateMessages>))
|
||||||
|
.route("", web::post().to(route_post::<CreatePrivateMessage>))
|
||||||
|
.route("", web::put().to(route_post::<EditPrivateMessage>))
|
||||||
|
.route(
|
||||||
|
"/delete",
|
||||||
|
web::post().to(route_post::<DeletePrivateMessage>),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// User
|
||||||
|
.service(
|
||||||
|
// Account action, I don't like that it's in /user maybe /accounts
|
||||||
|
// Handle /user/register separately to add the register() rate limitter
|
||||||
|
web::resource("/user/register")
|
||||||
|
.guard(guard::Post())
|
||||||
|
.wrap(rate_limit.register())
|
||||||
|
.route(web::post().to(route_post::<Register>)),
|
||||||
|
)
|
||||||
|
// User actions
|
||||||
|
.service(
|
||||||
|
web::scope("/user")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("", web::get().to(route_get::<GetPersonDetails>))
|
||||||
|
.route(
|
||||||
|
"/delete_account",
|
||||||
|
web::post().to(route_post::<DeleteAccount>),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn perform<Request>(
|
||||||
|
data: Request,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
Request: PerformCrud,
|
||||||
|
Request: Send + 'static,
|
||||||
|
{
|
||||||
|
let res = data
|
||||||
|
.perform(&context, None)
|
||||||
|
.await
|
||||||
|
.map(|json| HttpResponse::Ok().json(json))
|
||||||
|
.map_err(ErrorBadRequest)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_get<'a, Data>(
|
||||||
|
data: web::Query<Data>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
Data: Deserialize<'a> + Send + 'static + PerformCrud,
|
||||||
|
{
|
||||||
|
perform::<Data>(data.0, context).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_post<'a, Data>(
|
||||||
|
data: web::Json<Data>,
|
||||||
|
context: web::Data<LemmyContext>,
|
||||||
|
) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
Data: Deserialize<'a> + Send + 'static + PerformCrud,
|
||||||
|
{
|
||||||
|
perform::<Data>(data.0, context).await
|
||||||
|
}
|
60
crates/api_crud/src/site/create.rs
Normal file
60
crates/api_crud/src/site/create.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, get_local_user_view_from_jwt, is_admin, site::*};
|
||||||
|
use lemmy_db_queries::{source::site::Site_, Crud};
|
||||||
|
use lemmy_db_schema::source::site::{Site, *};
|
||||||
|
use lemmy_db_views::site_view::SiteView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
utils::{check_slurs, check_slurs_opt},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for CreateSite {
|
||||||
|
type Response = SiteResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<SiteResponse, LemmyError> {
|
||||||
|
let data: &CreateSite = &self;
|
||||||
|
|
||||||
|
let read_site = move |conn: &'_ _| Site::read_simple(conn);
|
||||||
|
if blocking(context.pool(), read_site).await?.is_ok() {
|
||||||
|
return Err(ApiError::err("site_already_exists").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
check_slurs(&data.name)?;
|
||||||
|
check_slurs_opt(&data.description)?;
|
||||||
|
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let site_form = SiteForm {
|
||||||
|
name: data.name.to_owned(),
|
||||||
|
description: data.description.to_owned(),
|
||||||
|
icon: Some(data.icon.to_owned().map(|url| url.into())),
|
||||||
|
banner: Some(data.banner.to_owned().map(|url| url.into())),
|
||||||
|
creator_id: local_user_view.person.id,
|
||||||
|
enable_downvotes: data.enable_downvotes,
|
||||||
|
open_registration: data.open_registration,
|
||||||
|
enable_nsfw: data.enable_nsfw,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
|
||||||
|
if blocking(context.pool(), create_site).await?.is_err() {
|
||||||
|
return Err(ApiError::err("site_already_exists").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
|
||||||
|
|
||||||
|
Ok(SiteResponse { site_view })
|
||||||
|
}
|
||||||
|
}
|
3
crates/api_crud/src/site/mod.rs
Normal file
3
crates/api_crud/src/site/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod create;
|
||||||
|
mod read;
|
||||||
|
mod update;
|
97
crates/api_crud/src/site/read.rs
Normal file
97
crates/api_crud/src/site/read.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
build_federated_instances,
|
||||||
|
get_local_user_settings_view_from_jwt_opt,
|
||||||
|
person::Register,
|
||||||
|
site::*,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::site_view::SiteView;
|
||||||
|
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||||
|
use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::{messages::GetUsersOnline, LemmyContext};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetSite {
|
||||||
|
type Response = GetSiteResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetSiteResponse, LemmyError> {
|
||||||
|
let data: &GetSite = &self;
|
||||||
|
|
||||||
|
let site_view = match blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
|
||||||
|
Ok(site_view) => Some(site_view),
|
||||||
|
// If the site isn't created yet, check the setup
|
||||||
|
Err(_) => {
|
||||||
|
if let Some(setup) = Settings::get().setup().as_ref() {
|
||||||
|
let register = Register {
|
||||||
|
username: setup.admin_username.to_owned(),
|
||||||
|
email: setup.admin_email.to_owned(),
|
||||||
|
password: setup.admin_password.to_owned(),
|
||||||
|
password_verify: setup.admin_password.to_owned(),
|
||||||
|
show_nsfw: true,
|
||||||
|
captcha_uuid: None,
|
||||||
|
captcha_answer: None,
|
||||||
|
};
|
||||||
|
let login_response = register.perform(context, websocket_id).await?;
|
||||||
|
info!("Admin {} created", setup.admin_username);
|
||||||
|
|
||||||
|
let create_site = CreateSite {
|
||||||
|
name: setup.site_name.to_owned(),
|
||||||
|
description: None,
|
||||||
|
icon: None,
|
||||||
|
banner: None,
|
||||||
|
enable_downvotes: true,
|
||||||
|
open_registration: true,
|
||||||
|
enable_nsfw: true,
|
||||||
|
auth: login_response.jwt,
|
||||||
|
};
|
||||||
|
create_site.perform(context, websocket_id).await?;
|
||||||
|
info!("Site {} created", setup.site_name);
|
||||||
|
Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
|
||||||
|
|
||||||
|
// Make sure the site creator is the top admin
|
||||||
|
if let Some(site_view) = site_view.to_owned() {
|
||||||
|
let site_creator_id = site_view.creator.id;
|
||||||
|
// TODO investigate why this is sometimes coming back null
|
||||||
|
// Maybe user_.admin isn't being set to true?
|
||||||
|
if let Some(creator_index) = admins.iter().position(|r| r.person.id == site_creator_id) {
|
||||||
|
let creator_person = admins.remove(creator_index);
|
||||||
|
admins.insert(0, creator_person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
|
||||||
|
|
||||||
|
let online = context
|
||||||
|
.chat_server()
|
||||||
|
.send(GetUsersOnline)
|
||||||
|
.await
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
|
let my_user = get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
let federated_instances = build_federated_instances(context.pool()).await?;
|
||||||
|
|
||||||
|
Ok(GetSiteResponse {
|
||||||
|
site_view,
|
||||||
|
admins,
|
||||||
|
banned,
|
||||||
|
online,
|
||||||
|
version: version::VERSION.to_string(),
|
||||||
|
my_user,
|
||||||
|
federated_instances,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
74
crates/api_crud/src/site/update.rs
Normal file
74
crates/api_crud/src/site/update.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
get_local_user_view_from_jwt,
|
||||||
|
is_admin,
|
||||||
|
site::{EditSite, SiteResponse},
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::{diesel_option_overwrite_to_url, source::site::Site_, Crud};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
naive_now,
|
||||||
|
source::site::{Site, SiteForm},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::site_view::SiteView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
utils::{check_slurs, check_slurs_opt},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for EditSite {
|
||||||
|
type Response = SiteResponse;
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<SiteResponse, LemmyError> {
|
||||||
|
let data: &EditSite = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
check_slurs(&data.name)?;
|
||||||
|
check_slurs_opt(&data.description)?;
|
||||||
|
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
|
||||||
|
|
||||||
|
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||||
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
|
|
||||||
|
let site_form = SiteForm {
|
||||||
|
name: data.name.to_owned(),
|
||||||
|
description: data.description.to_owned(),
|
||||||
|
icon,
|
||||||
|
banner,
|
||||||
|
creator_id: found_site.creator_id,
|
||||||
|
updated: Some(naive_now()),
|
||||||
|
enable_downvotes: data.enable_downvotes,
|
||||||
|
open_registration: data.open_registration,
|
||||||
|
enable_nsfw: data.enable_nsfw,
|
||||||
|
};
|
||||||
|
|
||||||
|
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
|
||||||
|
if blocking(context.pool(), update_site).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_update_site").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
|
||||||
|
|
||||||
|
let res = SiteResponse { site_view };
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendAllMessage {
|
||||||
|
op: UserOperation::EditSite,
|
||||||
|
response: res.clone(),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
244
crates/api_crud/src/user/create.rs
Normal file
244
crates/api_crud/src/user/create.rs
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, password_length_check, person::*};
|
||||||
|
use lemmy_apub::{
|
||||||
|
generate_apub_endpoint,
|
||||||
|
generate_followers_url,
|
||||||
|
generate_inbox_url,
|
||||||
|
generate_shared_inbox_url,
|
||||||
|
EndpointType,
|
||||||
|
};
|
||||||
|
use lemmy_db_queries::{
|
||||||
|
source::{local_user::LocalUser_, site::Site_},
|
||||||
|
Crud,
|
||||||
|
Followable,
|
||||||
|
Joinable,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
community::*,
|
||||||
|
local_user::{LocalUser, LocalUserForm},
|
||||||
|
person::*,
|
||||||
|
site::*,
|
||||||
|
},
|
||||||
|
CommunityId,
|
||||||
|
};
|
||||||
|
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||||
|
use lemmy_utils::{
|
||||||
|
apub::generate_actor_keypair,
|
||||||
|
claims::Claims,
|
||||||
|
settings::structs::Settings,
|
||||||
|
utils::{check_slurs, is_valid_username},
|
||||||
|
ApiError,
|
||||||
|
ConnectionId,
|
||||||
|
LemmyError,
|
||||||
|
};
|
||||||
|
use lemmy_websocket::{messages::CheckCaptcha, LemmyContext};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for Register {
|
||||||
|
type Response = LoginResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<LoginResponse, LemmyError> {
|
||||||
|
let data: &Register = &self;
|
||||||
|
|
||||||
|
// Make sure site has open registration
|
||||||
|
if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
|
||||||
|
if !site.open_registration {
|
||||||
|
return Err(ApiError::err("registration_closed").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
password_length_check(&data.password)?;
|
||||||
|
|
||||||
|
// Make sure passwords match
|
||||||
|
if data.password != data.password_verify {
|
||||||
|
return Err(ApiError::err("passwords_dont_match").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are admins. False if admins exist
|
||||||
|
let no_admins = blocking(context.pool(), move |conn| {
|
||||||
|
PersonViewSafe::admins(conn).map(|a| a.is_empty())
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// If its not the admin, check the captcha
|
||||||
|
if !no_admins && Settings::get().captcha().enabled {
|
||||||
|
let check = context
|
||||||
|
.chat_server()
|
||||||
|
.send(CheckCaptcha {
|
||||||
|
uuid: data
|
||||||
|
.captcha_uuid
|
||||||
|
.to_owned()
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
answer: data
|
||||||
|
.captcha_answer
|
||||||
|
.to_owned()
|
||||||
|
.unwrap_or_else(|| "".to_string()),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
if !check {
|
||||||
|
return Err(ApiError::err("captcha_incorrect").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check_slurs(&data.username)?;
|
||||||
|
|
||||||
|
let actor_keypair = generate_actor_keypair()?;
|
||||||
|
if !is_valid_username(&data.username) {
|
||||||
|
return Err(ApiError::err("invalid_username").into());
|
||||||
|
}
|
||||||
|
let actor_id = generate_apub_endpoint(EndpointType::Person, &data.username)?;
|
||||||
|
|
||||||
|
// We have to create both a person, and local_user
|
||||||
|
|
||||||
|
// Register the new person
|
||||||
|
let person_form = PersonForm {
|
||||||
|
name: data.username.to_owned(),
|
||||||
|
avatar: None,
|
||||||
|
banner: None,
|
||||||
|
preferred_username: None,
|
||||||
|
published: None,
|
||||||
|
updated: None,
|
||||||
|
banned: None,
|
||||||
|
deleted: None,
|
||||||
|
actor_id: Some(actor_id.clone()),
|
||||||
|
bio: None,
|
||||||
|
local: Some(true),
|
||||||
|
private_key: Some(Some(actor_keypair.private_key)),
|
||||||
|
public_key: Some(Some(actor_keypair.public_key)),
|
||||||
|
last_refreshed_at: None,
|
||||||
|
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
||||||
|
shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// insert the person
|
||||||
|
let inserted_person = match blocking(context.pool(), move |conn| {
|
||||||
|
Person::create(conn, &person_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ApiError::err("user_already_exists").into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the local user
|
||||||
|
let local_user_form = LocalUserForm {
|
||||||
|
person_id: inserted_person.id,
|
||||||
|
email: Some(data.email.to_owned()),
|
||||||
|
matrix_user_id: None,
|
||||||
|
password_encrypted: data.password.to_owned(),
|
||||||
|
admin: Some(no_admins),
|
||||||
|
show_nsfw: Some(data.show_nsfw),
|
||||||
|
theme: Some("browser".into()),
|
||||||
|
default_sort_type: Some(SortType::Active as i16),
|
||||||
|
default_listing_type: Some(ListingType::Subscribed as i16),
|
||||||
|
lang: Some("browser".into()),
|
||||||
|
show_avatars: Some(true),
|
||||||
|
send_notifications_to_email: Some(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_local_user = match blocking(context.pool(), move |conn| {
|
||||||
|
LocalUser::register(conn, &local_user_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(lu) => lu,
|
||||||
|
Err(e) => {
|
||||||
|
let err_type = if e.to_string()
|
||||||
|
== "duplicate key value violates unique constraint \"local_user_email_key\""
|
||||||
|
{
|
||||||
|
"email_already_exists"
|
||||||
|
} else {
|
||||||
|
"user_already_exists"
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the local user creation errored, then delete that person
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Person::delete(&conn, inserted_person.id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
return Err(ApiError::err(err_type).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let main_community_keypair = generate_actor_keypair()?;
|
||||||
|
|
||||||
|
// Create the main community if it doesn't exist
|
||||||
|
let main_community = match blocking(context.pool(), move |conn| {
|
||||||
|
Community::read(conn, CommunityId(2))
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_e) => {
|
||||||
|
let default_community_name = "main";
|
||||||
|
let actor_id = generate_apub_endpoint(EndpointType::Community, default_community_name)?;
|
||||||
|
let community_form = CommunityForm {
|
||||||
|
name: default_community_name.to_string(),
|
||||||
|
title: "The Default Community".to_string(),
|
||||||
|
description: Some("The Default Community".to_string()),
|
||||||
|
nsfw: false,
|
||||||
|
creator_id: inserted_person.id,
|
||||||
|
removed: None,
|
||||||
|
deleted: None,
|
||||||
|
updated: None,
|
||||||
|
actor_id: Some(actor_id.to_owned()),
|
||||||
|
local: true,
|
||||||
|
private_key: Some(main_community_keypair.private_key),
|
||||||
|
public_key: Some(main_community_keypair.public_key),
|
||||||
|
last_refreshed_at: None,
|
||||||
|
published: None,
|
||||||
|
icon: None,
|
||||||
|
banner: None,
|
||||||
|
followers_url: Some(generate_followers_url(&actor_id)?),
|
||||||
|
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
||||||
|
shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Community::create(conn, &community_form)
|
||||||
|
})
|
||||||
|
.await??
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sign them up for main community no matter what
|
||||||
|
let community_follower_form = CommunityFollowerForm {
|
||||||
|
community_id: main_community.id,
|
||||||
|
person_id: inserted_person.id,
|
||||||
|
pending: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
||||||
|
if blocking(context.pool(), follow).await?.is_err() {
|
||||||
|
return Err(ApiError::err("community_follower_already_exists").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
// If its an admin, add them as a mod and follower to main
|
||||||
|
if no_admins {
|
||||||
|
let community_moderator_form = CommunityModeratorForm {
|
||||||
|
community_id: main_community.id,
|
||||||
|
person_id: inserted_person.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||||
|
if blocking(context.pool(), join).await?.is_err() {
|
||||||
|
return Err(ApiError::err("community_moderator_already_exists").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
Ok(LoginResponse {
|
||||||
|
jwt: Claims::jwt(inserted_local_user.id.0)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
54
crates/api_crud/src/user/delete.rs
Normal file
54
crates/api_crud/src/user/delete.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use bcrypt::verify;
|
||||||
|
use lemmy_api_common::{blocking, get_local_user_view_from_jwt, person::*};
|
||||||
|
use lemmy_db_queries::source::{comment::Comment_, person::Person_, post::Post_};
|
||||||
|
use lemmy_db_schema::source::{comment::Comment, person::*, post::Post};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for DeleteAccount {
|
||||||
|
type Response = LoginResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<LoginResponse, LemmyError> {
|
||||||
|
let data: &DeleteAccount = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
// Verify the password
|
||||||
|
let valid: bool = verify(
|
||||||
|
&data.password,
|
||||||
|
&local_user_view.local_user.password_encrypted,
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !valid {
|
||||||
|
return Err(ApiError::err("password_incorrect").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
|
||||||
|
if blocking(context.pool(), permadelete).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_update_comment").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
|
||||||
|
if blocking(context.pool(), permadelete).await?.is_err() {
|
||||||
|
return Err(ApiError::err("couldnt_update_post").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
Person::delete_account(conn, person_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(LoginResponse {
|
||||||
|
jwt: data.auth.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
3
crates/api_crud/src/user/mod.rs
Normal file
3
crates/api_crud/src/user/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod create;
|
||||||
|
mod delete;
|
||||||
|
mod read;
|
122
crates/api_crud/src/user/read.rs
Normal file
122
crates/api_crud/src/user/read.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use crate::PerformCrud;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*};
|
||||||
|
use lemmy_db_queries::{source::person::Person_, SortType};
|
||||||
|
use lemmy_db_schema::source::person::*;
|
||||||
|
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
|
||||||
|
use lemmy_db_views_actor::{
|
||||||
|
community_follower_view::CommunityFollowerView,
|
||||||
|
community_moderator_view::CommunityModeratorView,
|
||||||
|
person_view::PersonViewSafe,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl PerformCrud for GetPersonDetails {
|
||||||
|
type Response = GetPersonDetailsResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<GetPersonDetailsResponse, LemmyError> {
|
||||||
|
let data: &GetPersonDetails = &self;
|
||||||
|
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
|
let show_nsfw = match &local_user_view {
|
||||||
|
Some(uv) => uv.local_user.show_nsfw,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sort = SortType::from_str(&data.sort)?;
|
||||||
|
|
||||||
|
let username = data
|
||||||
|
.username
|
||||||
|
.to_owned()
|
||||||
|
.unwrap_or_else(|| "admin".to_string());
|
||||||
|
let person_details_id = match data.person_id {
|
||||||
|
Some(id) => id,
|
||||||
|
None => {
|
||||||
|
let person = blocking(context.pool(), move |conn| {
|
||||||
|
Person::find_by_name(conn, &username)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
match person {
|
||||||
|
Ok(p) => p.id,
|
||||||
|
Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let person_id = local_user_view.map(|uv| uv.person.id);
|
||||||
|
|
||||||
|
// You don't need to return settings for the user, since this comes back with GetSite
|
||||||
|
// `my_user`
|
||||||
|
let person_view = blocking(context.pool(), move |conn| {
|
||||||
|
PersonViewSafe::read(conn, person_details_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let saved_only = data.saved_only;
|
||||||
|
let community_id = data.community_id;
|
||||||
|
|
||||||
|
let (posts, comments) = blocking(context.pool(), move |conn| {
|
||||||
|
let mut posts_query = PostQueryBuilder::create(conn)
|
||||||
|
.sort(&sort)
|
||||||
|
.show_nsfw(show_nsfw)
|
||||||
|
.saved_only(saved_only)
|
||||||
|
.community_id(community_id)
|
||||||
|
.my_person_id(person_id)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
let mut comments_query = CommentQueryBuilder::create(conn)
|
||||||
|
.my_person_id(person_id)
|
||||||
|
.sort(&sort)
|
||||||
|
.saved_only(saved_only)
|
||||||
|
.community_id(community_id)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
// If its saved only, you don't care what creator it was
|
||||||
|
// Or, if its not saved, then you only want it for that specific creator
|
||||||
|
if !saved_only {
|
||||||
|
posts_query = posts_query.creator_id(person_details_id);
|
||||||
|
comments_query = comments_query.creator_id(person_details_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let posts = posts_query.list()?;
|
||||||
|
let comments = comments_query.list()?;
|
||||||
|
|
||||||
|
Ok((posts, comments)) as Result<_, LemmyError>
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let mut follows = vec![];
|
||||||
|
if let Some(pid) = person_id {
|
||||||
|
if pid == person_details_id {
|
||||||
|
follows = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityFollowerView::for_person(conn, person_details_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let moderates = blocking(context.pool(), move |conn| {
|
||||||
|
CommunityModeratorView::for_person(conn, person_details_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Return the jwt
|
||||||
|
Ok(GetPersonDetailsResponse {
|
||||||
|
person_view,
|
||||||
|
follows,
|
||||||
|
moderates,
|
||||||
|
comments,
|
||||||
|
posts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,191 +0,0 @@
|
||||||
pub mod comment;
|
|
||||||
pub mod community;
|
|
||||||
pub mod person;
|
|
||||||
pub mod post;
|
|
||||||
pub mod site;
|
|
||||||
pub mod websocket;
|
|
||||||
|
|
||||||
use diesel::PgConnection;
|
|
||||||
use lemmy_db_queries::{Crud, DbPool};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
source::{
|
|
||||||
comment::Comment,
|
|
||||||
person::Person,
|
|
||||||
person_mention::{PersonMention, PersonMentionForm},
|
|
||||||
post::Post,
|
|
||||||
},
|
|
||||||
LocalUserId,
|
|
||||||
};
|
|
||||||
use lemmy_db_views::local_user_view::LocalUserView;
|
|
||||||
use lemmy_utils::{email::send_email, settings::structs::Settings, utils::MentionData, LemmyError};
|
|
||||||
use log::error;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct WebFingerLink {
|
|
||||||
pub rel: Option<String>,
|
|
||||||
#[serde(rename(serialize = "type", deserialize = "type"))]
|
|
||||||
pub type_: Option<String>,
|
|
||||||
pub href: Option<Url>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub template: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct WebFingerResponse {
|
|
||||||
pub subject: String,
|
|
||||||
pub aliases: Vec<Url>,
|
|
||||||
pub links: Vec<WebFingerLink>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
|
|
||||||
where
|
|
||||||
F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
|
|
||||||
T: Send + 'static,
|
|
||||||
{
|
|
||||||
let pool = pool.clone();
|
|
||||||
let res = actix_web::web::block(move || {
|
|
||||||
let conn = pool.get()?;
|
|
||||||
let res = (f)(&conn);
|
|
||||||
Ok(res) as Result<_, LemmyError>
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_local_notifs(
|
|
||||||
mentions: Vec<MentionData>,
|
|
||||||
comment: Comment,
|
|
||||||
person: Person,
|
|
||||||
post: Post,
|
|
||||||
pool: &DbPool,
|
|
||||||
do_send_email: bool,
|
|
||||||
) -> Result<Vec<LocalUserId>, LemmyError> {
|
|
||||||
let ids = blocking(pool, move |conn| {
|
|
||||||
do_send_local_notifs(conn, &mentions, &comment, &person, &post, do_send_email)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_send_local_notifs(
|
|
||||||
conn: &PgConnection,
|
|
||||||
mentions: &[MentionData],
|
|
||||||
comment: &Comment,
|
|
||||||
person: &Person,
|
|
||||||
post: &Post,
|
|
||||||
do_send_email: bool,
|
|
||||||
) -> Vec<LocalUserId> {
|
|
||||||
let mut recipient_ids = Vec::new();
|
|
||||||
|
|
||||||
// Send the local mentions
|
|
||||||
for mention in mentions
|
|
||||||
.iter()
|
|
||||||
.filter(|m| m.is_local() && m.name.ne(&person.name))
|
|
||||||
.collect::<Vec<&MentionData>>()
|
|
||||||
{
|
|
||||||
if let Ok(mention_user_view) = LocalUserView::read_from_name(&conn, &mention.name) {
|
|
||||||
// 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 = PersonMentionForm {
|
|
||||||
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(&conn, &user_mention_form).ok();
|
|
||||||
|
|
||||||
// Send an email to those local users that have notifications on
|
|
||||||
if do_send_email {
|
|
||||||
send_email_to_user(
|
|
||||||
&mention_user_view,
|
|
||||||
"Mentioned by",
|
|
||||||
"Person Mention",
|
|
||||||
&comment.content,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send notifs to the parent commenter / poster
|
|
||||||
match comment.parent_id {
|
|
||||||
Some(parent_id) => {
|
|
||||||
if let Ok(parent_comment) = Comment::read(&conn, parent_id) {
|
|
||||||
// Don't send a notif to yourself
|
|
||||||
if parent_comment.creator_id != person.id {
|
|
||||||
// Get the parent commenter local_user
|
|
||||||
if let Ok(parent_user_view) = LocalUserView::read_person(&conn, parent_comment.creator_id)
|
|
||||||
{
|
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
|
||||||
|
|
||||||
if do_send_email {
|
|
||||||
send_email_to_user(
|
|
||||||
&parent_user_view,
|
|
||||||
"Reply from",
|
|
||||||
"Comment Reply",
|
|
||||||
&comment.content,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Its a post
|
|
||||||
None => {
|
|
||||||
if post.creator_id != person.id {
|
|
||||||
if let Ok(parent_user_view) = LocalUserView::read_person(&conn, post.creator_id) {
|
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
|
||||||
|
|
||||||
if do_send_email {
|
|
||||||
send_email_to_user(
|
|
||||||
&parent_user_view,
|
|
||||||
"Reply from",
|
|
||||||
"Post Reply",
|
|
||||||
&comment.content,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
recipient_ids
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_email_to_user(
|
|
||||||
local_user_view: &LocalUserView,
|
|
||||||
subject_text: &str,
|
|
||||||
body_text: &str,
|
|
||||||
comment_content: &str,
|
|
||||||
) {
|
|
||||||
if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(user_email) = &local_user_view.local_user.email {
|
|
||||||
let subject = &format!(
|
|
||||||
"{} - {} {}",
|
|
||||||
subject_text,
|
|
||||||
Settings::get().hostname(),
|
|
||||||
local_user_view.person.name,
|
|
||||||
);
|
|
||||||
let html = &format!(
|
|
||||||
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
|
||||||
body_text,
|
|
||||||
local_user_view.person.name,
|
|
||||||
comment_content,
|
|
||||||
Settings::get().get_protocol_and_hostname()
|
|
||||||
);
|
|
||||||
match send_email(subject, &user_email, &local_user_view.person.name, html) {
|
|
||||||
Ok(_o) => _o,
|
|
||||||
Err(e) => error!("{}", e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,7 +14,7 @@ lemmy_db_queries = { path = "../db_queries" }
|
||||||
lemmy_db_schema = { path = "../db_schema" }
|
lemmy_db_schema = { path = "../db_schema" }
|
||||||
lemmy_db_views = { path = "../db_views" }
|
lemmy_db_views = { path = "../db_views" }
|
||||||
lemmy_db_views_actor = { path = "../db_views_actor" }
|
lemmy_db_views_actor = { path = "../db_views_actor" }
|
||||||
lemmy_api_structs = { path = "../api_structs" }
|
lemmy_api_common = { path = "../api_common" }
|
||||||
lemmy_websocket = { path = "../websocket" }
|
lemmy_websocket = { path = "../websocket" }
|
||||||
diesel = "1.4.5"
|
diesel = "1.4.5"
|
||||||
activitystreams = "0.7.0-alpha.11"
|
activitystreams = "0.7.0-alpha.11"
|
||||||
|
|
|
@ -4,7 +4,7 @@ use activitystreams::{
|
||||||
base::ExtendsExt,
|
base::ExtendsExt,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::{blocking, comment::CommentResponse, send_local_notifs};
|
use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs};
|
||||||
use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
|
use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
comment::{Comment, CommentLike, CommentLikeForm},
|
comment::{Comment, CommentLike, CommentLikeForm},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::activities::receive::get_actor_as_person;
|
use crate::activities::receive::get_actor_as_person;
|
||||||
use activitystreams::activity::{Dislike, Like};
|
use activitystreams::activity::{Dislike, Like};
|
||||||
use lemmy_api_structs::{blocking, comment::CommentResponse};
|
use lemmy_api_common::{blocking, comment::CommentResponse};
|
||||||
use lemmy_db_queries::{source::comment::Comment_, Likeable};
|
use lemmy_db_queries::{source::comment::Comment_, Likeable};
|
||||||
use lemmy_db_schema::source::comment::{Comment, CommentLike};
|
use lemmy_db_schema::source::comment::{Comment, CommentLike};
|
||||||
use lemmy_db_views::comment_view::CommentView;
|
use lemmy_db_views::comment_view::CommentView;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use lemmy_api_structs::{blocking, community::CommunityResponse};
|
use lemmy_api_common::{blocking, community::CommunityResponse};
|
||||||
use lemmy_db_queries::source::community::Community_;
|
use lemmy_db_queries::source::community::Community_;
|
||||||
use lemmy_db_schema::source::community::Community;
|
use lemmy_db_schema::source::community::Community;
|
||||||
use lemmy_db_views_actor::community_view::CommunityView;
|
use lemmy_db_views_actor::community_view::CommunityView;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use activitystreams::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::{blocking, post::PostResponse};
|
use lemmy_api_common::{blocking, post::PostResponse};
|
||||||
use lemmy_db_queries::{source::post::Post_, ApubObject, Crud, Likeable};
|
use lemmy_db_queries::{source::post::Post_, ApubObject, Crud, Likeable};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::activities::receive::get_actor_as_person;
|
use crate::activities::receive::get_actor_as_person;
|
||||||
use activitystreams::activity::{Dislike, Like};
|
use activitystreams::activity::{Dislike, Like};
|
||||||
use lemmy_api_structs::{blocking, post::PostResponse};
|
use lemmy_api_common::{blocking, post::PostResponse};
|
||||||
use lemmy_db_queries::{source::post::Post_, Likeable};
|
use lemmy_db_queries::{source::post::Post_, Likeable};
|
||||||
use lemmy_db_schema::source::post::{Post, PostLike};
|
use lemmy_db_schema::source::post::{Post, PostLike};
|
||||||
use lemmy_db_views::post_view::PostView;
|
use lemmy_db_views::post_view::PostView;
|
||||||
|
|
|
@ -13,7 +13,7 @@ use activitystreams::{
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use lemmy_api_structs::{blocking, person::PrivateMessageResponse};
|
use lemmy_api_common::{blocking, person::PrivateMessageResponse};
|
||||||
use lemmy_db_queries::source::private_message::PrivateMessage_;
|
use lemmy_db_queries::source::private_message::PrivateMessage_;
|
||||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||||
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||||
|
|
|
@ -26,7 +26,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lemmy_api_structs::{blocking, WebFingerResponse};
|
use lemmy_api_common::{blocking, WebFingerResponse};
|
||||||
use lemmy_db_queries::{Crud, DbPool};
|
use lemmy_db_queries::{Crud, DbPool};
|
||||||
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
|
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
|
|
@ -28,7 +28,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::DbPool;
|
use lemmy_db_queries::DbPool;
|
||||||
use lemmy_db_schema::source::{community::Community, person::Person};
|
use lemmy_db_schema::source::{community::Community, person::Person};
|
||||||
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
||||||
|
|
|
@ -14,7 +14,7 @@ use activitystreams::{
|
||||||
base::{BaseExt, ExtendsExt},
|
base::{BaseExt, ExtendsExt},
|
||||||
object::ObjectExt,
|
object::ObjectExt,
|
||||||
};
|
};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{ApubObject, Followable};
|
use lemmy_db_queries::{ApubObject, Followable};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||||
|
|
|
@ -21,7 +21,7 @@ use activitystreams::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::Crud;
|
use lemmy_db_queries::Crud;
|
||||||
use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
|
use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -16,7 +16,7 @@ use activitystreams::{
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::Crud;
|
use lemmy_db_queries::Crud;
|
||||||
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -15,7 +15,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable};
|
use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::community::{Community, CommunityModerator, CommunityModeratorForm},
|
source::community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt};
|
use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{ApubObject, Crud};
|
use lemmy_db_queries::{ApubObject, Crud};
|
||||||
use lemmy_db_schema::source::{comment::Comment, post::Post};
|
use lemmy_db_schema::source::{comment::Comment, post::Post};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{source::person::Person_, ApubObject};
|
use lemmy_db_queries::{source::person::Person_, ApubObject};
|
||||||
use lemmy_db_schema::source::person::Person;
|
use lemmy_db_schema::source::person::Person;
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitystreams::base::BaseExt;
|
use activitystreams::base::BaseExt;
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use lemmy_api_structs::{blocking, site::SearchResponse};
|
use lemmy_api_common::{blocking, site::SearchResponse};
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
source::{
|
source::{
|
||||||
comment::Comment_,
|
comment::Comment_,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, web, web::Path, HttpResponse};
|
use actix_web::{body::Body, web, web::Path, HttpResponse};
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::Crud;
|
use lemmy_db_queries::Crud;
|
||||||
use lemmy_db_schema::{source::comment::Comment, CommentId};
|
use lemmy_db_schema::{source::comment::Comment, CommentId};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -11,7 +11,7 @@ use activitystreams::{
|
||||||
url::Url,
|
url::Url,
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::source::{activity::Activity_, community::Community_};
|
use lemmy_db_queries::source::{activity::Activity_, community::Community_};
|
||||||
use lemmy_db_schema::source::{activity::Activity, community::Community};
|
use lemmy_db_schema::source::{activity::Activity, community::Community};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::APUB_JSON_CONTENT_TYPE;
|
use crate::APUB_JSON_CONTENT_TYPE;
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::source::activity::Activity_;
|
use lemmy_db_queries::source::activity::Activity_;
|
||||||
use lemmy_db_schema::source::activity::Activity;
|
use lemmy_db_schema::source::activity::Activity;
|
||||||
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||||
|
|
|
@ -9,7 +9,7 @@ use activitystreams::{
|
||||||
collection::{CollectionExt, OrderedCollection},
|
collection::{CollectionExt, OrderedCollection},
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::source::person::Person_;
|
use lemmy_db_queries::source::person::Person_;
|
||||||
use lemmy_db_schema::source::person::Person;
|
use lemmy_db_schema::source::person::Person;
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use actix_web::{body::Body, web, HttpResponse};
|
use actix_web::{body::Body, web, HttpResponse};
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::Crud;
|
use lemmy_db_queries::Crud;
|
||||||
use lemmy_db_schema::{source::post::Post, PostId};
|
use lemmy_db_schema::{source::post::Post, PostId};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
|
|
|
@ -29,7 +29,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable};
|
use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
|
|
@ -12,7 +12,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
source::{activity::Activity_, community::Community_},
|
source::{activity::Activity_, community::Community_},
|
||||||
ApubObject,
|
ApubObject,
|
||||||
|
|
|
@ -49,7 +49,7 @@ use activitystreams::{
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use diesel::NotFound;
|
use diesel::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{source::person::Person_, ApubObject, Followable};
|
use lemmy_db_queries::{source::person::Person_, ApubObject, Followable};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
community::{Community, CommunityFollower},
|
community::{Community, CommunityFollower},
|
||||||
|
|
|
@ -61,7 +61,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Crud, Joinable};
|
use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Crud, Joinable};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
use activitystreams::{activity::ActorAndObject, prelude::*};
|
use activitystreams::{activity::ActorAndObject, prelude::*};
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{ApubObject, DbPool};
|
use lemmy_db_queries::{ApubObject, DbPool};
|
||||||
use lemmy_db_schema::source::community::Community;
|
use lemmy_db_schema::source::community::Community;
|
||||||
use lemmy_utils::{location_info, LemmyError};
|
use lemmy_utils::{location_info, LemmyError};
|
||||||
|
|
|
@ -24,7 +24,7 @@ use activitystreams::{
|
||||||
use activitystreams_ext::{Ext1, Ext2};
|
use activitystreams_ext::{Ext1, Ext2};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use diesel::NotFound;
|
use diesel::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
|
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
|
|
@ -21,7 +21,7 @@ use activitystreams::{
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{Crud, DbPool};
|
use lemmy_db_queries::{Crud, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
|
|
@ -23,7 +23,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext2;
|
use activitystreams_ext::Ext2;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::DbPool;
|
use lemmy_db_queries::DbPool;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
naive_now,
|
naive_now,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use activitystreams::{
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
||||||
use lemmy_db_schema::{source::community::Community, CommunityId, DbUrl};
|
use lemmy_db_schema::{source::community::Community, CommunityId, DbUrl};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
|
|
@ -18,7 +18,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams_ext::Ext1;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{ApubObject, DbPool};
|
use lemmy_db_queries::{ApubObject, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
naive_now,
|
naive_now,
|
||||||
|
|
|
@ -23,7 +23,7 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams_ext::Ext1;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{Crud, DbPool};
|
use lemmy_db_queries::{Crud, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -19,7 +19,7 @@ use activitystreams::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{Crud, DbPool};
|
use lemmy_db_queries::{Crud, DbPool};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
person::Person,
|
person::Person,
|
||||||
|
|
|
@ -13,7 +13,7 @@ lemmy_db_queries = { path = "../db_queries" }
|
||||||
lemmy_db_views = { path = "../db_views" }
|
lemmy_db_views = { path = "../db_views" }
|
||||||
lemmy_db_views_actor = { path = "../db_views_actor" }
|
lemmy_db_views_actor = { path = "../db_views_actor" }
|
||||||
lemmy_db_schema = { path = "../db_schema" }
|
lemmy_db_schema = { path = "../db_schema" }
|
||||||
lemmy_api_structs = { path = "../api_structs" }
|
lemmy_api_common = { path = "../api_common" }
|
||||||
diesel = "1.4.5"
|
diesel = "1.4.5"
|
||||||
actix = "0.10.0"
|
actix = "0.10.0"
|
||||||
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
|
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
|
||||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::{error::ErrorBadRequest, *};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_queries::{
|
use lemmy_db_queries::{
|
||||||
source::{community::Community_, person::Person_},
|
source::{community::Community_, person::Person_},
|
||||||
Crud,
|
Crud,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use actix_web::{body::Body, error::ErrorBadRequest, *};
|
use actix_web::{body::Body, error::ErrorBadRequest, *};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_db_views::site_view::SiteView;
|
use lemmy_db_views::site_view::SiteView;
|
||||||
use lemmy_utils::{settings::structs::Settings, version, LemmyError};
|
use lemmy_utils::{settings::structs::Settings, version, LemmyError};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use actix_web::{error::ErrorBadRequest, web::Query, *};
|
use actix_web::{error::ErrorBadRequest, web::Query, *};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lemmy_api_structs::{blocking, WebFingerLink, WebFingerResponse};
|
use lemmy_api_common::{blocking, WebFingerLink, WebFingerResponse};
|
||||||
use lemmy_db_queries::source::{community::Community_, person::Person_};
|
use lemmy_db_queries::source::{community::Community_, person::Person_};
|
||||||
use lemmy_db_schema::source::{community::Community, person::Person};
|
use lemmy_db_schema::source::{community::Community, person::Person};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
|
|
@ -10,7 +10,7 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_utils = { path = "../utils" }
|
lemmy_utils = { path = "../utils" }
|
||||||
lemmy_api_structs = { path = "../api_structs" }
|
lemmy_api_common = { path = "../api_common" }
|
||||||
lemmy_db_queries = { path = "../db_queries" }
|
lemmy_db_queries = { path = "../db_queries" }
|
||||||
lemmy_db_schema = { path = "../db_schema" }
|
lemmy_db_schema = { path = "../db_schema" }
|
||||||
reqwest = { version = "0.10.10", features = ["json"] }
|
reqwest = { version = "0.10.10", features = ["json"] }
|
||||||
|
|
|
@ -6,7 +6,7 @@ use diesel::{
|
||||||
r2d2::{ConnectionManager, Pool},
|
r2d2::{ConnectionManager, Pool},
|
||||||
PgConnection,
|
PgConnection,
|
||||||
};
|
};
|
||||||
use lemmy_api_structs::{comment::*, post::*};
|
use lemmy_api_common::{comment::*, post::*};
|
||||||
use lemmy_db_schema::{CommunityId, LocalUserId, PostId};
|
use lemmy_db_schema::{CommunityId, LocalUserId, PostId};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
location_info,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::UserOperation;
|
use crate::UserOperation;
|
||||||
use actix::{prelude::*, Recipient};
|
use actix::{prelude::*, Recipient};
|
||||||
use lemmy_api_structs::{comment::CommentResponse, post::PostResponse};
|
use lemmy_api_common::{comment::CommentResponse, post::PostResponse};
|
||||||
use lemmy_db_schema::{CommunityId, LocalUserId, PostId};
|
use lemmy_db_schema::{CommunityId, LocalUserId, PostId};
|
||||||
use lemmy_utils::{ConnectionId, IpAddr};
|
use lemmy_utils::{ConnectionId, IpAddr};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use diesel::{
|
||||||
PgConnection,
|
PgConnection,
|
||||||
};
|
};
|
||||||
use lemmy_api::match_websocket_operation;
|
use lemmy_api::match_websocket_operation;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub::activity_queue::create_activity_queue;
|
use lemmy_apub::activity_queue::create_activity_queue;
|
||||||
use lemmy_db_queries::get_database_url_from_env;
|
use lemmy_db_queries::get_database_url_from_env;
|
||||||
use lemmy_routes::{feeds, images, nodeinfo, webfinger};
|
use lemmy_routes::{feeds, images, nodeinfo, webfinger};
|
||||||
|
@ -88,6 +88,7 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.data(context)
|
.data(context)
|
||||||
// The routes
|
// The routes
|
||||||
|
.configure(|cfg| lemmy_api_crud::routes::config(cfg, &rate_limiter))
|
||||||
.configure(|cfg| lemmy_api::routes::config(cfg, &rate_limiter))
|
.configure(|cfg| lemmy_api::routes::config(cfg, &rate_limiter))
|
||||||
.configure(lemmy_apub::routes::config)
|
.configure(lemmy_apub::routes::config)
|
||||||
.configure(feeds::config)
|
.configure(feeds::config)
|
||||||
|
|
Loading…
Reference in a new issue