"http-signature-normalization-actix",
"itertools",
"lazy_static",
- "lemmy_api_structs",
+ "lemmy_api_common",
"lemmy_apub",
"lemmy_db_queries",
"lemmy_db_schema",
]
[[package]]
-name = "lemmy_api_structs"
+name = "lemmy_api_common"
version = "0.1.0"
dependencies = [
"actix-web",
"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]]
name = "lemmy_apub"
version = "0.1.0"
"http-signature-normalization-reqwest",
"itertools",
"lazy_static",
- "lemmy_api_structs",
+ "lemmy_api_common",
"lemmy_db_queries",
"lemmy_db_schema",
"lemmy_db_views",
"chrono",
"diesel",
"lazy_static",
- "lemmy_api_structs",
+ "lemmy_api_common",
"lemmy_db_queries",
"lemmy_db_schema",
"lemmy_db_views",
"env_logger",
"http-signature-normalization-actix",
"lemmy_api",
- "lemmy_api_structs",
+ "lemmy_api_common",
+ "lemmy_api_crud",
"lemmy_apub",
"lemmy_db_queries",
"lemmy_db_schema",
"background-jobs",
"chrono",
"diesel",
- "lemmy_api_structs",
+ "lemmy_api_common",
"lemmy_db_queries",
"lemmy_db_schema",
"lemmy_utils",
[workspace]
members = [
"crates/api",
+ "crates/api_crud",
+ "crates/api_common",
"crates/apub",
"crates/utils",
"crates/db_queries",
"crates/db_views",
"crates/db_views_actor",
"crates/db_views_actor",
- "crates/api_structs",
"crates/websocket",
"crates/routes"
]
[dependencies]
lemmy_api = { path = "./crates/api" }
+lemmy_api_crud = { path = "./crates/api_crud" }
lemmy_apub = { path = "./crates/apub" }
lemmy_utils = { path = "./crates/utils" }
lemmy_db_schema = { path = "./crates/db_schema" }
lemmy_db_views = { path = "./crates/db_views" }
lemmy_db_views_moderator = { path = "./crates/db_views_moderator" }
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_routes = { path = "./crates/routes" }
diesel = "1.4.5"
lemmy_db_views = { path = "../db_views" }
lemmy_db_views_moderator = { path = "../db_views_moderator" }
lemmy_db_views_actor = { path = "../db_views_actor" }
-lemmy_api_structs = { path = "../api_structs" }
+lemmy_api_common = { path = "../api_common" }
lemmy_websocket = { path = "../websocket" }
diesel = "1.4.5"
bcrypt = "0.9.0"
-use crate::{
+use crate::Perform;
+use actix_web::web::Data;
+use lemmy_api_common::{
+ blocking,
check_community_ban,
check_downvotes_enabled,
- collect_moderated_communities,
+ comment::*,
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_api_structs::{blocking, comment::*, send_local_notifs};
-use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
-use lemmy_db_queries::{
- source::comment::Comment_,
- Crud,
- 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)
- }
-}
+use lemmy_apub::ApubLikeableType;
+use lemmy_db_queries::{source::comment::Comment_, Likeable, Saveable};
+use lemmy_db_schema::{source::comment::*, LocalUserId};
+use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
+use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
#[async_trait::async_trait(?Send)]
impl Perform for MarkCommentAsRead {
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)
- }
-}
--- /dev/null
+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)
+ }
+}
-use crate::{
+use crate::Perform;
+use actix_web::web::Data;
+use anyhow::Context;
+use lemmy_api_common::{
+ blocking,
check_community_ban,
+ community::*,
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 anyhow::Context;
-use lemmy_api_structs::{blocking, community::*};
-use lemmy_apub::{
- generate_apub_endpoint,
- generate_followers_url,
- generate_inbox_url,
- generate_shared_inbox_url,
- ActorType,
- CommunityType,
- EndpointType,
- UserType,
};
+use lemmy_apub::{ActorType, CommunityType, UserType};
use lemmy_db_queries::{
- diesel_option_overwrite_to_url,
source::{
comment::Comment_,
community::{CommunityModerator_, Community_},
post::Post_,
},
- ApubObject,
Bannable,
Crud,
Followable,
Joinable,
- ListingType,
- SortType,
};
-use lemmy_db_schema::{
- naive_now,
- source::{comment::Comment, community::*, moderator::*, person::Person, post::Post, site::*},
- PersonId,
+use lemmy_db_schema::source::{
+ comment::Comment,
+ community::*,
+ moderator::*,
+ person::Person,
+ post::Post,
+ site::*,
};
use lemmy_db_views::comment_view::CommentQueryBuilder;
use lemmy_db_views_actor::{
- community_follower_view::CommunityFollowerView,
community_moderator_view::CommunityModeratorView,
- community_view::{CommunityQueryBuilder, CommunityView},
+ community_view::CommunityView,
person_view::PersonViewSafe,
};
-use lemmy_utils::{
- apub::generate_actor_keypair,
- 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 })
- }
-}
+use lemmy_utils::{location_info, utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
+use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
#[async_trait::async_trait(?Send)]
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)]
impl Perform for BanFromCommunity {
type Response = BanFromCommunityResponse;
})
}
}
-
-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,
- });
-}
use actix_web::{web, web::Data};
-use lemmy_api_structs::{
- blocking,
- 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_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
+use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize;
use std::{env, process::Command};
-use url::Url;
-pub mod comment;
-pub mod community;
-pub mod local_user;
-pub mod post;
+mod comment;
+mod comment_report;
+mod community;
+mod local_user;
+mod post;
+mod post_report;
+mod private_message;
pub mod routes;
-pub mod site;
-pub mod websocket;
+mod site;
+mod websocket;
#[async_trait::async_trait(?Send)]
pub trait Perform {
) -> 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(
context: LemmyContext,
id: ConnectionId,
op: UserOperation,
data: &str,
) -> Result<String, LemmyError> {
+ //TODO: handle commented out actions in crud crate
+
match op {
// User ops
- UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
- UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
+ UserOperation::Login => {
+ //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::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::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
UserOperation::GetPersonMentions => {
- do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
+ //do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
+ todo!()
}
UserOperation::MarkPersonMentionAsRead => {
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
}
UserOperation::DeleteAccount => {
- do_websocket_operation::<DeleteAccount>(context, id, op, data).await
+ //do_websocket_operation::<DeleteAccount>(context, id, op, data).await
+ todo!()
}
UserOperation::PasswordReset => {
do_websocket_operation::<PasswordReset>(context, id, op, data).await
// Private Message ops
UserOperation::CreatePrivateMessage => {
- do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
+ //do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
+ todo!()
}
UserOperation::EditPrivateMessage => {
- do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
+ //do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
+ todo!()
}
UserOperation::DeletePrivateMessage => {
- do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
+ //do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
+ todo!()
}
UserOperation::MarkPrivateMessageAsRead => {
do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
}
UserOperation::GetPrivateMessages => {
- do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
+ //do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
+ todo!()
}
// Site ops
UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
- UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
- UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
- UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
+ UserOperation::CreateSite => {
+ //do_websocket_operation::<CreateSite>(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 => {
do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
}
// Community ops
UserOperation::GetCommunity => {
- do_websocket_operation::<GetCommunity>(context, id, op, data).await
+ //do_websocket_operation::<GetCommunity>(context, id, op, data).await
+ todo!()
}
UserOperation::ListCommunities => {
- do_websocket_operation::<ListCommunities>(context, id, op, data).await
+ //do_websocket_operation::<ListCommunities>(context, id, op, data).await
+ todo!()
}
UserOperation::CreateCommunity => {
- do_websocket_operation::<CreateCommunity>(context, id, op, data).await
+ //do_websocket_operation::<CreateCommunity>(context, id, op, data).await
+ todo!()
}
UserOperation::EditCommunity => {
- do_websocket_operation::<EditCommunity>(context, id, op, data).await
+ //do_websocket_operation::<EditCommunity>(context, id, op, data).await
+ todo!()
}
UserOperation::DeleteCommunity => {
- do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
+ //do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
+ todo!()
}
UserOperation::RemoveCommunity => {
- do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
+ //do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
+ todo!()
}
UserOperation::FollowCommunity => {
do_websocket_operation::<FollowCommunity>(context, id, op, data).await
}
// Post ops
- UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
- UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
- UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
- UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
- UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
- UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
+ UserOperation::CreatePost => {
+ //do_websocket_operation::<CreatePost>(context, id, op, data).await
+ todo!()
+ }
+ UserOperation::GetPost => {
+ //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::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
UserOperation::CreatePostLike => {
// Comment ops
UserOperation::CreateComment => {
- do_websocket_operation::<CreateComment>(context, id, op, data).await
+ //do_websocket_operation::<CreateComment>(context, id, op, data).await
+ todo!()
}
UserOperation::EditComment => {
- do_websocket_operation::<EditComment>(context, id, op, data).await
+ //do_websocket_operation::<EditComment>(context, id, op, data).await
+ todo!()
}
UserOperation::DeleteComment => {
- do_websocket_operation::<DeleteComment>(context, id, op, data).await
+ //do_websocket_operation::<DeleteComment>(context, id, op, data).await
+ todo!()
}
UserOperation::RemoveComment => {
- do_websocket_operation::<RemoveComment>(context, id, op, data).await
+ //do_websocket_operation::<RemoveComment>(context, id, op, data).await
+ todo!()
}
UserOperation::MarkCommentAsRead => {
do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
do_websocket_operation::<SaveComment>(context, id, op, data).await
}
UserOperation::GetComments => {
- do_websocket_operation::<GetComments>(context, id, op, data).await
+ //do_websocket_operation::<GetComments>(context, id, op, data).await
+ todo!()
}
UserOperation::CreateCommentLike => {
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
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)]
mod tests {
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_schema::source::{
local_user::{LocalUser, LocalUserForm},
-use crate::{
- 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 crate::{captcha_espeak_wav_base64, Perform};
use actix_web::web::Data;
use anyhow::Context;
use bcrypt::verify;
use captcha::{gen, Difficulty};
use chrono::Duration;
-use lemmy_api_structs::{blocking, person::*, send_email_to_user};
-use lemmy_apub::{
- generate_apub_endpoint,
- generate_followers_url,
- generate_inbox_url,
- generate_shared_inbox_url,
- ApubObjectType,
- EndpointType,
+use lemmy_api_common::{
+ blocking,
+ collect_moderated_communities,
+ community::{GetFollowedCommunities, GetFollowedCommunitiesResponse},
+ get_local_user_view_from_jwt,
+ is_admin,
+ password_length_check,
+ person::*,
};
use lemmy_db_queries::{
diesel_option_overwrite,
person_mention::PersonMention_,
post::Post_,
private_message::PrivateMessage_,
- site::Site_,
},
Crud,
- Followable,
- Joinable,
- ListingType,
SortType,
};
use lemmy_db_schema::{
person::*,
person_mention::*,
post::Post,
- private_message::*,
+ private_message::PrivateMessage,
site::*,
},
- CommunityId,
};
use lemmy_db_views::{
comment_report_view::CommentReportView,
comment_view::CommentQueryBuilder,
local_user_view::LocalUserView,
post_report_view::PostReportView,
- post_view::PostQueryBuilder,
- private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
};
use lemmy_db_views_actor::{
community_follower_view::CommunityFollowerView,
- community_moderator_view::CommunityModeratorView,
person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
person_view::PersonViewSafe,
};
use lemmy_utils::{
- apub::generate_actor_keypair,
claims::Claims,
email::send_email,
location_info,
settings::structs::Settings,
- utils::{
- check_slurs,
- generate_random_string,
- is_valid_preferred_username,
- is_valid_username,
- naive_from_unix,
- remove_slurs,
- },
+ utils::{generate_random_string, is_valid_preferred_username, naive_from_unix},
ApiError,
ConnectionId,
LemmyError,
};
use lemmy_websocket::{
- messages::{CaptchaItem, CheckCaptcha, SendAllMessage, SendUserRoomMessage},
+ messages::{CaptchaItem, SendAllMessage, SendUserRoomMessage},
LemmyContext,
UserOperation,
};
}
}
-#[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)]
impl Perform for GetCaptcha {
type Response = GetCaptchaResponse;
}
}
-#[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)]
impl Perform for AddAdmin {
type Response = AddAdminResponse;
}
}
-#[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)]
impl Perform for PasswordReset {
type Response = PasswordResetResponse;
}
}
-#[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)]
impl Perform for GetReportCount {
type Response = GetReportCountResponse;
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 })
+ }
+}
-use crate::{
+use crate::Perform;
+use actix_web::web::Data;
+use lemmy_api_common::{
+ blocking,
check_community_ban,
check_downvotes_enabled,
- collect_moderated_communities,
get_local_user_view_from_jwt,
- get_local_user_view_from_jwt_opt,
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_report::{PostReport, PostReportForm},
- },
-};
-use lemmy_db_views::{
- comment_view::CommentQueryBuilder,
- post_report_view::{PostReportQueryBuilder, PostReportView},
- post_view::{PostQueryBuilder, PostView},
-};
-use lemmy_db_views_actor::{
- community_moderator_view::CommunityModeratorView,
- community_view::CommunityView,
+ post::*,
};
-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 })
- }
-}
+use lemmy_apub::{ApubLikeableType, ApubObjectType};
+use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable};
+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 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)]
impl Perform for LockPost {
type Response = PostResponse;
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)
- }
-}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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)
+ }
+}
use crate::Perform;
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_websocket::{routes::chat_route, LemmyContext};
use serde::Deserialize;
.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>))
.route("/transfer", web::post().to(route_post::<TransferSite>))
.route("/config", web::get().to(route_get::<GetSiteConfig>))
.route("/config", web::put().to(route_post::<SaveSiteConfig>)),
.route(web::get().to(route_get::<Search>)),
)
// 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("/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("/ban_user", web::post().to(route_post::<BanFromCommunity>))
.route("/mod", web::post().to(route_post::<AddModToCommunity>))
.route("/mod/join", web::post().to(route_post::<ModJoin>)),
)
// 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("/lock", web::post().to(route_post::<LockPost>))
.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("/save", web::put().to(route_post::<SavePost>))
.route("/join", web::post().to(route_post::<PostJoin>))
.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(
"/mark_as_read",
web::post().to(route_post::<MarkCommentAsRead>),
)
.route("/like", web::post().to(route_post::<CreateCommentLike>))
.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/resolve",
.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>),
- )
.route(
"/mark_as_read",
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
.service(
web::scope("/user")
.wrap(rate_limit.message())
- .route("", web::get().to(route_get::<GetPersonDetails>))
.route("/mention", web::get().to(route_get::<GetPersonMentions>))
.route(
"/mention/mark_as_read",
// Account actions. I don't like that they're in /user maybe /accounts
.route("/login", web::post().to(route_post::<Login>))
.route("/get_captcha", web::get().to(route_get::<GetCaptcha>))
- .route(
- "/delete_account",
- web::post().to(route_post::<DeleteAccount>),
- )
.route(
"/password_reset",
web::post().to(route_post::<PasswordReset>),
-use crate::{
+use crate::Perform;
+use actix_web::web::Data;
+use anyhow::Context;
+use lemmy_api_common::{
+ blocking,
build_federated_instances,
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_opt,
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_db_queries::{
- diesel_option_overwrite_to_url,
- source::site::Site_,
- Crud,
- SearchType,
- SortType,
-};
-use lemmy_db_schema::{
- naive_now,
- source::{
- moderator::*,
- site::{Site, *},
- },
-};
+use lemmy_db_queries::{source::site::Site_, Crud, SearchType, SortType};
+use lemmy_db_schema::source::{moderator::*, site::Site};
use lemmy_db_views::{
comment_view::CommentQueryBuilder,
post_view::PostQueryBuilder,
use lemmy_utils::{
location_info,
settings::structs::Settings,
- utils::{check_slurs, check_slurs_opt},
version,
ApiError,
ConnectionId,
LemmyError,
};
-use lemmy_websocket::{
- messages::{GetUsersOnline, SendAllMessage},
- LemmyContext,
- UserOperation,
-};
-use log::{debug, info};
+use lemmy_websocket::LemmyContext;
+use log::debug;
use std::str::FromStr;
#[async_trait::async_trait(?Send)]
}
}
-#[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)]
impl Perform for Search {
type Response = SearchResponse;
-use crate::{get_local_user_view_from_jwt, Perform};
+use crate::Perform;
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_websocket::{
messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
[package]
-name = "lemmy_api_structs"
+name = "lemmy_api_common"
version = "0.1.0"
edition = "2018"
[lib]
-name = "lemmy_api_structs"
+name = "lemmy_api_common"
path = "src/lib.rs"
doctest = false
--- /dev/null
+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(())
+ }
+}
--- /dev/null
+[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"] }
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+mod create;
+mod delete;
+mod read;
+mod update;
--- /dev/null
+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 })
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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 })
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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,
+ });
+}
--- /dev/null
+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 })
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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>;
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+mod create;
+mod delete;
+mod read;
+mod update;
--- /dev/null
+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 })
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+mod create;
+mod delete;
+mod read;
+mod update;
--- /dev/null
+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,
+ })
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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
+}
--- /dev/null
+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 })
+ }
+}
--- /dev/null
+mod create;
+mod read;
+mod update;
--- /dev/null
+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,
+ })
+ }
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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)?,
+ })
+ }
+}
--- /dev/null
+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(),
+ })
+ }
+}
--- /dev/null
+mod create;
+mod delete;
+mod read;
--- /dev/null
+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,
+ })
+ }
+}
+++ /dev/null
-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),
- };
- }
-}
lemmy_db_schema = { path = "../db_schema" }
lemmy_db_views = { path = "../db_views" }
lemmy_db_views_actor = { path = "../db_views_actor" }
-lemmy_api_structs = { path = "../api_structs" }
+lemmy_api_common = { path = "../api_common" }
lemmy_websocket = { path = "../websocket" }
diesel = "1.4.5"
activitystreams = "0.7.0-alpha.11"
base::ExtendsExt,
};
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_schema::source::{
comment::{Comment, CommentLike, CommentLikeForm},
use crate::activities::receive::get_actor_as_person;
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_schema::source::comment::{Comment, CommentLike};
use lemmy_db_views::comment_view::CommentView;
-use lemmy_api_structs::{blocking, community::CommunityResponse};
+use lemmy_api_common::{blocking, community::CommunityResponse};
use lemmy_db_queries::source::community::Community_;
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_view::CommunityView;
prelude::*,
};
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_schema::{
source::{
use crate::activities::receive::get_actor_as_person;
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_schema::source::post::{Post, PostLike};
use lemmy_db_views::post_view::PostView;
public,
};
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_schema::source::private_message::PrivateMessage;
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
};
use anyhow::anyhow;
use itertools::Itertools;
-use lemmy_api_structs::{blocking, WebFingerResponse};
+use lemmy_api_common::{blocking, WebFingerResponse};
use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
use lemmy_utils::{
};
use anyhow::Context;
use itertools::Itertools;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::DbPool;
use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
base::{BaseExt, ExtendsExt},
object::ObjectExt,
};
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, Followable};
use lemmy_db_schema::source::{
community::{Community, CommunityFollower, CommunityFollowerForm},
prelude::*,
public,
};
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
use lemmy_utils::LemmyError;
},
prelude::*,
};
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
use lemmy_utils::LemmyError;
};
use anyhow::Context;
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_schema::{
source::community::{Community, CommunityModerator, CommunityModeratorForm},
use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt};
use anyhow::anyhow;
use diesel::result::Error::NotFound;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, Crud};
use lemmy_db_schema::source::{comment::Comment, post::Post};
use lemmy_utils::LemmyError;
};
use anyhow::anyhow;
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_schema::source::person::Person;
use lemmy_utils::LemmyError;
};
use activitystreams::base::BaseExt;
use anyhow::{anyhow, Context};
-use lemmy_api_structs::{blocking, site::SearchResponse};
+use lemmy_api_common::{blocking, site::SearchResponse};
use lemmy_db_queries::{
source::{
comment::Comment_,
};
use actix_web::{body::Body, web, web::Path, HttpResponse};
use diesel::result::Error::NotFound;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::Crud;
use lemmy_db_schema::{source::comment::Comment, CommentId};
use lemmy_utils::LemmyError;
url::Url,
};
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_schema::source::{activity::Activity, community::Community};
use lemmy_db_views_actor::{
use crate::APUB_JSON_CONTENT_TYPE;
use actix_web::{body::Body, web, HttpResponse};
use http::StatusCode;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::source::activity::Activity_;
use lemmy_db_schema::source::activity::Activity;
use lemmy_utils::{settings::structs::Settings, LemmyError};
collection::{CollectionExt, OrderedCollection},
};
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_schema::source::person::Person;
use lemmy_utils::LemmyError;
};
use actix_web::{body::Body, web, HttpResponse};
use diesel::result::Error::NotFound;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::Crud;
use lemmy_db_schema::{source::post::Post, PostId};
use lemmy_utils::LemmyError;
};
use actix_web::{web, HttpRequest, HttpResponse};
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_schema::{
source::{
};
use actix_web::HttpRequest;
use anyhow::{anyhow, Context};
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{
source::{activity::Activity_, community::Community_},
ApubObject,
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::{anyhow, Context};
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_schema::source::{
community::{Community, CommunityFollower},
};
use anyhow::{anyhow, Context};
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_schema::{
source::{
use activitystreams::{activity::ActorAndObject, prelude::*};
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::Context;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_db_schema::source::community::Community;
use lemmy_utils::{location_info, LemmyError};
use activitystreams_ext::{Ext1, Ext2};
use anyhow::{anyhow, Context};
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_schema::{
source::{
public,
};
use anyhow::{anyhow, Context};
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::{
source::{
};
use activitystreams_ext::Ext2;
use anyhow::Context;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::DbPool;
use lemmy_db_schema::{
naive_now,
use anyhow::{anyhow, Context};
use chrono::NaiveDateTime;
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_schema::{source::community::Community, CommunityId, DbUrl};
use lemmy_utils::{
};
use activitystreams_ext::Ext1;
use anyhow::Context;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_db_schema::{
naive_now,
};
use activitystreams_ext::Ext1;
use anyhow::Context;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::{
self,
prelude::*,
};
use anyhow::Context;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::source::{
person::Person,
lemmy_db_views = { path = "../db_views" }
lemmy_db_views_actor = { path = "../db_views_actor" }
lemmy_db_schema = { path = "../db_schema" }
-lemmy_api_structs = { path = "../api_structs" }
+lemmy_api_common = { path = "../api_common" }
diesel = "1.4.5"
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
use anyhow::anyhow;
use chrono::{DateTime, NaiveDateTime, Utc};
use diesel::PgConnection;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_queries::{
source::{community::Community_, person::Person_},
Crud,
use actix_web::{body::Body, error::ErrorBadRequest, *};
use anyhow::anyhow;
-use lemmy_api_structs::blocking;
+use lemmy_api_common::blocking;
use lemmy_db_views::site_view::SiteView;
use lemmy_utils::{settings::structs::Settings, version, LemmyError};
use lemmy_websocket::LemmyContext;
use actix_web::{error::ErrorBadRequest, web::Query, *};
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_schema::source::{community::Community, person::Person};
use lemmy_utils::{
[dependencies]
lemmy_utils = { path = "../utils" }
-lemmy_api_structs = { path = "../api_structs" }
+lemmy_api_common = { path = "../api_common" }
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_schema = { path = "../db_schema" }
reqwest = { version = "0.10.10", features = ["json"] }
r2d2::{ConnectionManager, Pool},
PgConnection,
};
-use lemmy_api_structs::{comment::*, post::*};
+use lemmy_api_common::{comment::*, post::*};
use lemmy_db_schema::{CommunityId, LocalUserId, PostId};
use lemmy_utils::{
location_info,
use crate::UserOperation;
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_utils::{ConnectionId, IpAddr};
use serde::{Deserialize, Serialize};
PgConnection,
};
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_db_queries::get_database_url_from_env;
use lemmy_routes::{feeds, images, nodeinfo, webfinger};
.wrap(middleware::Logger::default())
.data(context)
// The routes
+ .configure(|cfg| lemmy_api_crud::routes::config(cfg, &rate_limiter))
.configure(|cfg| lemmy_api::routes::config(cfg, &rate_limiter))
.configure(lemmy_apub::routes::config)
.configure(feeds::config)