mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 11:21:32 +00:00
Split apart api files (#2216)
This commit is contained in:
parent
ce4682caa0
commit
3951a16447
76 changed files with 3800 additions and 3275 deletions
|
@ -1,12 +1,10 @@
|
|||
use std::convert::TryInto;
|
||||
|
||||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_downvotes_enabled,
|
||||
comment::*,
|
||||
comment::{CommentResponse, CreateCommentLike},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
|
@ -18,111 +16,13 @@ use lemmy_apub::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::LocalUserId,
|
||||
source::comment::*,
|
||||
traits::{Likeable, Saveable},
|
||||
source::comment::{CommentLike, CommentLikeForm},
|
||||
traits::Likeable,
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
|
||||
|
||||
use crate::Perform;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkCommentAsRead {
|
||||
type Response = CommentResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &MarkCommentAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, None)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Verify that only the recipient can mark as read
|
||||
if local_user_view.person.id != orig_comment.get_recipient_id() {
|
||||
return Err(LemmyError::from_message("no_comment_edit_allowed"));
|
||||
}
|
||||
|
||||
// Do the mark as read
|
||||
let read = data.read;
|
||||
blocking(context.pool(), move |conn| {
|
||||
Comment::update_read(conn, comment_id, read)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// 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??;
|
||||
|
||||
let res = CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
form_id: None,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SaveComment {
|
||||
type Response = CommentResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &SaveComment = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let comment_saved_form = CommentSavedForm {
|
||||
comment_id: data.comment_id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
if data.save {
|
||||
let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
|
||||
blocking(context.pool(), save_comment)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_comment"))?;
|
||||
} else {
|
||||
let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
|
||||
blocking(context.pool(), unsave_comment)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_comment"))?;
|
||||
}
|
||||
|
||||
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??;
|
||||
|
||||
Ok(CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
form_id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreateCommentLike {
|
62
crates/api/src/comment/mark_as_read.rs
Normal file
62
crates/api/src/comment/mark_as_read.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
comment::{CommentResponse, MarkCommentAsRead},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkCommentAsRead {
|
||||
type Response = CommentResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &MarkCommentAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, None)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Verify that only the recipient can mark as read
|
||||
if local_user_view.person.id != orig_comment.get_recipient_id() {
|
||||
return Err(LemmyError::from_message("no_comment_edit_allowed"));
|
||||
}
|
||||
|
||||
// Do the mark as read
|
||||
let read = data.read;
|
||||
blocking(context.pool(), move |conn| {
|
||||
Comment::update_read(conn, comment_id, read)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// 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??;
|
||||
|
||||
let res = CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
form_id: None,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
3
crates/api/src/comment/mod.rs
Normal file
3
crates/api/src/comment/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod like;
|
||||
mod mark_as_read;
|
||||
mod save;
|
60
crates/api/src/comment/save.rs
Normal file
60
crates/api/src/comment/save.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
comment::{CommentResponse, SaveComment},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::comment::{CommentSaved, CommentSavedForm},
|
||||
traits::Saveable,
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SaveComment {
|
||||
type Response = CommentResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &SaveComment = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let comment_saved_form = CommentSavedForm {
|
||||
comment_id: data.comment_id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
if data.save {
|
||||
let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
|
||||
blocking(context.pool(), save_comment)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_comment"))?;
|
||||
} else {
|
||||
let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
|
||||
blocking(context.pool(), unsave_comment)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_comment"))?;
|
||||
}
|
||||
|
||||
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??;
|
||||
|
||||
Ok(CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
form_id: None,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
comment::*,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
};
|
||||
use lemmy_apub::protocol::activities::community::report::Report;
|
||||
use lemmy_apub_lib::object_id::ObjectId;
|
||||
use lemmy_db_schema::{source::comment_report::*, traits::Reportable};
|
||||
use lemmy_db_views::{
|
||||
comment_report_view::{CommentReportQueryBuilder, CommentReportView},
|
||||
comment_view::CommentView,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
/// Creates a comment report and notifies the moderators of the community
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreateCommentReport {
|
||||
type Response = CommentReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentReportResponse, LemmyError> {
|
||||
let data: &CreateCommentReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// check size of report and check for whitespace
|
||||
let reason = data.reason.trim();
|
||||
if reason.is_empty() {
|
||||
return Err(LemmyError::from_message("report_reason_required"));
|
||||
}
|
||||
if reason.chars().count() > 1000 {
|
||||
return Err(LemmyError::from_message("report_too_long"));
|
||||
}
|
||||
|
||||
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 = blocking(context.pool(), move |conn| {
|
||||
CommentReport::report(conn, &report_form)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?;
|
||||
|
||||
let comment_report_view = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::read(conn, report.id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = CommentReportResponse {
|
||||
comment_report_view,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendModRoomMessage {
|
||||
op: UserOperation::CreateCommentReport,
|
||||
response: res.clone(),
|
||||
community_id: comment_view.community.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Report::send(
|
||||
ObjectId::new(comment_view.comment.ap_id),
|
||||
&local_user_view.person.into(),
|
||||
ObjectId::new(comment_view.community.actor_id),
|
||||
reason.to_string(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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 = CommentReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentReportResponse, LemmyError> {
|
||||
let data: &ResolveCommentReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let report = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::read(conn, report_id, person_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)
|
||||
}
|
||||
};
|
||||
|
||||
blocking(context.pool(), resolve_fun)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let comment_report_view = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::read(conn, report_id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = CommentReportResponse {
|
||||
comment_report_view,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
let unresolved_only = data.unresolved_only;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let comment_reports = blocking(context.pool(), move |conn| {
|
||||
CommentReportQueryBuilder::create(conn, person_id, admin)
|
||||
.community_id(community_id)
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = ListCommentReportsResponse { comment_reports };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
92
crates/api/src/comment_report/create.rs
Normal file
92
crates/api/src/comment_report/create.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
comment::{CommentReportResponse, CreateCommentReport},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_apub::protocol::activities::community::report::Report;
|
||||
use lemmy_apub_lib::object_id::ObjectId;
|
||||
use lemmy_db_schema::{
|
||||
source::comment_report::{CommentReport, CommentReportForm},
|
||||
traits::Reportable,
|
||||
};
|
||||
use lemmy_db_views::{comment_report_view::CommentReportView, comment_view::CommentView};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
/// Creates a comment report and notifies the moderators of the community
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreateCommentReport {
|
||||
type Response = CommentReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentReportResponse, LemmyError> {
|
||||
let data: &CreateCommentReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// check size of report and check for whitespace
|
||||
let reason = data.reason.trim();
|
||||
if reason.is_empty() {
|
||||
return Err(LemmyError::from_message("report_reason_required"));
|
||||
}
|
||||
if reason.chars().count() > 1000 {
|
||||
return Err(LemmyError::from_message("report_too_long"));
|
||||
}
|
||||
|
||||
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 = blocking(context.pool(), move |conn| {
|
||||
CommentReport::report(conn, &report_form)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?;
|
||||
|
||||
let comment_report_view = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::read(conn, report.id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = CommentReportResponse {
|
||||
comment_report_view,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendModRoomMessage {
|
||||
op: UserOperation::CreateCommentReport,
|
||||
response: res.clone(),
|
||||
community_id: comment_view.community.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Report::send(
|
||||
ObjectId::new(comment_view.comment.ap_id),
|
||||
&local_user_view.person.into(),
|
||||
ObjectId::new(comment_view.community.actor_id),
|
||||
reason.to_string(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
49
crates/api/src/comment_report/list.rs
Normal file
49
crates/api/src/comment_report/list.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
comment::{ListCommentReports, ListCommentReportsResponse},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_db_views::comment_report_view::CommentReportQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
/// 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;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
let unresolved_only = data.unresolved_only;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let comment_reports = blocking(context.pool(), move |conn| {
|
||||
CommentReportQueryBuilder::create(conn, person_id, admin)
|
||||
.community_id(community_id)
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = ListCommentReportsResponse { comment_reports };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
3
crates/api/src/comment_report/mod.rs
Normal file
3
crates/api/src/comment_report/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod create;
|
||||
mod list;
|
||||
mod resolve;
|
71
crates/api/src/comment_report/resolve.rs
Normal file
71
crates/api/src/comment_report/resolve.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
comment::{CommentReportResponse, ResolveCommentReport},
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
};
|
||||
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
|
||||
use lemmy_db_views::comment_report_view::CommentReportView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
/// Resolves or unresolves a comment report and notifies the moderators of the community
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ResolveCommentReport {
|
||||
type Response = CommentReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentReportResponse, LemmyError> {
|
||||
let data: &ResolveCommentReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let report = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::read(conn, report_id, person_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)
|
||||
}
|
||||
};
|
||||
|
||||
blocking(context.pool(), resolve_fun)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let comment_report_view = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::read(conn, report_id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = CommentReportResponse {
|
||||
comment_report_view,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendModRoomMessage {
|
||||
op: UserOperation::ResolveCommentReport,
|
||||
response: res.clone(),
|
||||
community_id: report.community.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
|
@ -1,513 +0,0 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use anyhow::Context;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
community::*,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
remove_user_data_in_community,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
activities::block::SiteOrCommunity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::{
|
||||
block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
community::{add_mod::AddMod, remove_mod::RemoveMod},
|
||||
following::{follow::FollowCommunity as FollowCommunityApub, undo_follow::UndoFollowCommunity},
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{
|
||||
Community,
|
||||
CommunityFollower,
|
||||
CommunityFollowerForm,
|
||||
CommunityModerator,
|
||||
CommunityModeratorForm,
|
||||
CommunityPersonBan,
|
||||
CommunityPersonBanForm,
|
||||
},
|
||||
community_block::{CommunityBlock, CommunityBlockForm},
|
||||
moderator::{
|
||||
ModAddCommunity,
|
||||
ModAddCommunityForm,
|
||||
ModBanFromCommunity,
|
||||
ModBanFromCommunityForm,
|
||||
ModTransferCommunity,
|
||||
ModTransferCommunityForm,
|
||||
},
|
||||
person::Person,
|
||||
},
|
||||
traits::{Bannable, Blockable, Crud, Followable, Joinable},
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::CommunityView,
|
||||
person_view::PersonViewSafe,
|
||||
};
|
||||
use lemmy_utils::{location_info, utils::naive_from_unix, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for FollowCommunity {
|
||||
type Response = CommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &FollowCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let community: ApubCommunity = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
person_id: local_user_view.person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
if community.local {
|
||||
if data.follow {
|
||||
check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
|
||||
check_community_deleted_or_removed(community_id, context.pool()).await?;
|
||||
|
||||
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
||||
blocking(context.pool(), follow)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?;
|
||||
} else {
|
||||
let unfollow =
|
||||
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||
blocking(context.pool(), unfollow)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?;
|
||||
}
|
||||
} else if data.follow {
|
||||
// Dont actually add to the community followers here, because you need
|
||||
// to wait for the accept
|
||||
FollowCommunityApub::send(&local_user_view.person.clone().into(), &community, context)
|
||||
.await?;
|
||||
} else {
|
||||
UndoFollowCommunity::send(&local_user_view.person.clone().into(), &community, context)
|
||||
.await?;
|
||||
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||
blocking(context.pool(), unfollow)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?;
|
||||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this needs to return a "pending" state, until Accept is received from the remote server
|
||||
// For now, just assume that remote follows are accepted.
|
||||
// Otherwise, the subscribed will be null
|
||||
if !community.local {
|
||||
community_view.subscribed = data.follow;
|
||||
}
|
||||
|
||||
Ok(CommunityResponse { community_view })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BlockCommunity {
|
||||
type Response = BlockCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BlockCommunityResponse, LemmyError> {
|
||||
let data: &BlockCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_block_form = CommunityBlockForm {
|
||||
person_id,
|
||||
community_id,
|
||||
};
|
||||
|
||||
if data.block {
|
||||
let block = move |conn: &'_ _| CommunityBlock::block(conn, &community_block_form);
|
||||
blocking(context.pool(), block)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?;
|
||||
|
||||
// Also, unfollow the community, and send a federated unfollow
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
person_id,
|
||||
pending: false,
|
||||
};
|
||||
blocking(context.pool(), move |conn: &'_ _| {
|
||||
CommunityFollower::unfollow(conn, &community_follower_form)
|
||||
})
|
||||
.await?
|
||||
.ok();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
UndoFollowCommunity::send(&local_user_view.person.into(), &community.into(), context).await?;
|
||||
} else {
|
||||
let unblock = move |conn: &'_ _| CommunityBlock::unblock(conn, &community_block_form);
|
||||
blocking(context.pool(), unblock)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?;
|
||||
}
|
||||
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(BlockCommunityResponse {
|
||||
blocked: data.block,
|
||||
community_view,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BanFromCommunity {
|
||||
type Response = BanFromCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BanFromCommunityResponse, LemmyError> {
|
||||
let data: &BanFromCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let banned_person_id = data.person_id;
|
||||
let remove_data = data.remove_data.unwrap_or(false);
|
||||
let expires = data.expires.map(naive_from_unix);
|
||||
|
||||
// Verify that only mods or admins can ban
|
||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||
|
||||
let community_user_ban_form = CommunityPersonBanForm {
|
||||
community_id: data.community_id,
|
||||
person_id: data.person_id,
|
||||
expires: Some(expires),
|
||||
};
|
||||
|
||||
let community: ApubCommunity = blocking(context.pool(), move |conn: &'_ _| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
let banned_person: ApubPerson = blocking(context.pool(), move |conn: &'_ _| {
|
||||
Person::read(conn, banned_person_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
if data.ban {
|
||||
let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
|
||||
blocking(context.pool(), ban)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_user_already_banned"))?;
|
||||
|
||||
// Also unsubscribe them from the community, if they are subscribed
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
person_id: banned_person_id,
|
||||
pending: false,
|
||||
};
|
||||
blocking(context.pool(), move |conn: &'_ _| {
|
||||
CommunityFollower::unfollow(conn, &community_follower_form)
|
||||
})
|
||||
.await?
|
||||
.ok();
|
||||
|
||||
BlockUser::send(
|
||||
&SiteOrCommunity::Community(community),
|
||||
&banned_person,
|
||||
&local_user_view.person.clone().into(),
|
||||
remove_data,
|
||||
data.reason.clone(),
|
||||
expires,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form);
|
||||
blocking(context.pool(), unban)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_user_already_banned"))?;
|
||||
UndoBlockUser::send(
|
||||
&SiteOrCommunity::Community(community),
|
||||
&banned_person,
|
||||
&local_user_view.person.clone().into(),
|
||||
data.reason.clone(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Remove/Restore their data if that's desired
|
||||
if remove_data {
|
||||
remove_user_data_in_community(community_id, banned_person_id, context.pool()).await?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModBanFromCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
reason: data.reason.to_owned(),
|
||||
banned: Some(data.ban),
|
||||
expires,
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModBanFromCommunity::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let person_id = data.person_id;
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = BanFromCommunityResponse {
|
||||
person_view,
|
||||
banned: data.ban,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||
op: UserOperation::BanFromCommunity,
|
||||
response: res.clone(),
|
||||
community_id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for AddModToCommunity {
|
||||
type Response = AddModToCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<AddModToCommunityResponse, LemmyError> {
|
||||
let data: &AddModToCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
|
||||
// Verify that only mods or admins can add mod
|
||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
if local_user_view.person.admin && !community.local {
|
||||
return Err(LemmyError::from_message("not_a_moderator"));
|
||||
}
|
||||
|
||||
// Update in local database
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: data.community_id,
|
||||
person_id: data.person_id,
|
||||
};
|
||||
if data.added {
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
blocking(context.pool(), join)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?;
|
||||
} else {
|
||||
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
|
||||
blocking(context.pool(), leave)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModAddCommunity::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Send to federated instances
|
||||
let updated_mod_id = data.person_id;
|
||||
let updated_mod: ApubPerson = blocking(context.pool(), move |conn| {
|
||||
Person::read(conn, updated_mod_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
let community: ApubCommunity = community.into();
|
||||
if data.added {
|
||||
AddMod::send(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
RemoveMod::send(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Note: in case a remote mod is added, this returns the old moderators list, it will only get
|
||||
// updated once we receive an activity from the community (like `Announce/Add/Moderator`)
|
||||
let community_id = data.community_id;
|
||||
let moderators = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = AddModToCommunityResponse { moderators };
|
||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||
op: UserOperation::AddModToCommunity,
|
||||
response: res.clone(),
|
||||
community_id,
|
||||
websocket_id,
|
||||
});
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we dont do anything for federation here, it should be updated the next time the community
|
||||
// gets fetched. i hope we can get rid of the community creator role soon.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for TransferCommunity {
|
||||
type Response = GetCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetCommunityResponse, LemmyError> {
|
||||
let data: &TransferCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
|
||||
// Fetch the community mods
|
||||
let community_id = data.community_id;
|
||||
let mut community_mods = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Make sure transferrer is either the top community mod, or an admin
|
||||
if local_user_view.person.id != community_mods[0].moderator.id
|
||||
&& !admins
|
||||
.iter()
|
||||
.map(|a| a.person.id)
|
||||
.any(|x| x == local_user_view.person.id)
|
||||
{
|
||||
return Err(LemmyError::from_message("not_an_admin"));
|
||||
}
|
||||
|
||||
// You have to re-do the community_moderator table, reordering it.
|
||||
// Add the transferee to the top
|
||||
let creator_index = community_mods
|
||||
.iter()
|
||||
.position(|r| r.moderator.id == data.person_id)
|
||||
.context(location_info!())?;
|
||||
let creator_person = community_mods.remove(creator_index);
|
||||
community_mods.insert(0, creator_person);
|
||||
|
||||
// Delete all the mods
|
||||
let community_id = data.community_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommunityModerator::delete_for_community(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
// Re-add the mods, in the new order
|
||||
for cmod in &community_mods {
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: cmod.community.id,
|
||||
person_id: cmod.moderator.id,
|
||||
};
|
||||
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
blocking(context.pool(), join)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModTransferCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(false),
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModTransferCommunity::create(conn, &form)
|
||||
})
|
||||
.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?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let moderators = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(GetCommunityResponse {
|
||||
community_view,
|
||||
site: None,
|
||||
moderators,
|
||||
online: 0,
|
||||
})
|
||||
}
|
||||
}
|
123
crates/api/src/community/add_mod.rs
Normal file
123
crates/api/src/community/add_mod.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
community::{AddModToCommunity, AddModToCommunityResponse},
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::community::{add_mod::AddMod, remove_mod::RemoveMod},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
};
|
||||
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for AddModToCommunity {
|
||||
type Response = AddModToCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<AddModToCommunityResponse, LemmyError> {
|
||||
let data: &AddModToCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
|
||||
// Verify that only mods or admins can add mod
|
||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
if local_user_view.person.admin && !community.local {
|
||||
return Err(LemmyError::from_message("not_a_moderator"));
|
||||
}
|
||||
|
||||
// Update in local database
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: data.community_id,
|
||||
person_id: data.person_id,
|
||||
};
|
||||
if data.added {
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
blocking(context.pool(), join)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?;
|
||||
} else {
|
||||
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
|
||||
blocking(context.pool(), leave)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModAddCommunity::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Send to federated instances
|
||||
let updated_mod_id = data.person_id;
|
||||
let updated_mod: ApubPerson = blocking(context.pool(), move |conn| {
|
||||
Person::read(conn, updated_mod_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
let community: ApubCommunity = community.into();
|
||||
if data.added {
|
||||
AddMod::send(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
RemoveMod::send(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Note: in case a remote mod is added, this returns the old moderators list, it will only get
|
||||
// updated once we receive an activity from the community (like `Announce/Add/Moderator`)
|
||||
let community_id = data.community_id;
|
||||
let moderators = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = AddModToCommunityResponse { moderators };
|
||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||
op: UserOperation::AddModToCommunity,
|
||||
response: res.clone(),
|
||||
community_id,
|
||||
websocket_id,
|
||||
});
|
||||
Ok(res)
|
||||
}
|
||||
}
|
154
crates/api/src/community/ban.rs
Normal file
154
crates/api/src/community/ban.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
community::{BanFromCommunity, BanFromCommunityResponse},
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
remove_user_data_in_community,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
activities::block::SiteOrCommunity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{
|
||||
Community,
|
||||
CommunityFollower,
|
||||
CommunityFollowerForm,
|
||||
CommunityPersonBan,
|
||||
CommunityPersonBanForm,
|
||||
},
|
||||
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::{Bannable, Crud, Followable},
|
||||
};
|
||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||
use lemmy_utils::{utils::naive_from_unix, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BanFromCommunity {
|
||||
type Response = BanFromCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BanFromCommunityResponse, LemmyError> {
|
||||
let data: &BanFromCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let banned_person_id = data.person_id;
|
||||
let remove_data = data.remove_data.unwrap_or(false);
|
||||
let expires = data.expires.map(naive_from_unix);
|
||||
|
||||
// Verify that only mods or admins can ban
|
||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||
|
||||
let community_user_ban_form = CommunityPersonBanForm {
|
||||
community_id: data.community_id,
|
||||
person_id: data.person_id,
|
||||
expires: Some(expires),
|
||||
};
|
||||
|
||||
let community: ApubCommunity = blocking(context.pool(), move |conn: &'_ _| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
let banned_person: ApubPerson = blocking(context.pool(), move |conn: &'_ _| {
|
||||
Person::read(conn, banned_person_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
if data.ban {
|
||||
let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
|
||||
blocking(context.pool(), ban)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_user_already_banned"))?;
|
||||
|
||||
// Also unsubscribe them from the community, if they are subscribed
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
person_id: banned_person_id,
|
||||
pending: false,
|
||||
};
|
||||
blocking(context.pool(), move |conn: &'_ _| {
|
||||
CommunityFollower::unfollow(conn, &community_follower_form)
|
||||
})
|
||||
.await?
|
||||
.ok();
|
||||
|
||||
BlockUser::send(
|
||||
&SiteOrCommunity::Community(community),
|
||||
&banned_person,
|
||||
&local_user_view.person.clone().into(),
|
||||
remove_data,
|
||||
data.reason.clone(),
|
||||
expires,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form);
|
||||
blocking(context.pool(), unban)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_user_already_banned"))?;
|
||||
UndoBlockUser::send(
|
||||
&SiteOrCommunity::Community(community),
|
||||
&banned_person,
|
||||
&local_user_view.person.clone().into(),
|
||||
data.reason.clone(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Remove/Restore their data if that's desired
|
||||
if remove_data {
|
||||
remove_user_data_in_community(community_id, banned_person_id, context.pool()).await?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModBanFromCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
reason: data.reason.to_owned(),
|
||||
banned: Some(data.ban),
|
||||
expires,
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModBanFromCommunity::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let person_id = data.person_id;
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = BanFromCommunityResponse {
|
||||
person_view,
|
||||
banned: data.ban,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||
op: UserOperation::BanFromCommunity,
|
||||
response: res.clone(),
|
||||
community_id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
80
crates/api/src/community/block.rs
Normal file
80
crates/api/src/community/block.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
community::{BlockCommunity, BlockCommunityResponse},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_apub::protocol::activities::following::undo_follow::UndoFollowCommunity;
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||
community_block::{CommunityBlock, CommunityBlockForm},
|
||||
},
|
||||
traits::{Blockable, Crud, Followable},
|
||||
};
|
||||
use lemmy_db_views_actor::community_view::CommunityView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BlockCommunity {
|
||||
type Response = BlockCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BlockCommunityResponse, LemmyError> {
|
||||
let data: &BlockCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_block_form = CommunityBlockForm {
|
||||
person_id,
|
||||
community_id,
|
||||
};
|
||||
|
||||
if data.block {
|
||||
let block = move |conn: &'_ _| CommunityBlock::block(conn, &community_block_form);
|
||||
blocking(context.pool(), block)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?;
|
||||
|
||||
// Also, unfollow the community, and send a federated unfollow
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
person_id,
|
||||
pending: false,
|
||||
};
|
||||
blocking(context.pool(), move |conn: &'_ _| {
|
||||
CommunityFollower::unfollow(conn, &community_follower_form)
|
||||
})
|
||||
.await?
|
||||
.ok();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
UndoFollowCommunity::send(&local_user_view.person.into(), &community.into(), context).await?;
|
||||
} else {
|
||||
let unblock = move |conn: &'_ _| CommunityBlock::unblock(conn, &community_block_form);
|
||||
blocking(context.pool(), unblock)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?;
|
||||
}
|
||||
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(BlockCommunityResponse {
|
||||
blocked: data.block,
|
||||
community_view,
|
||||
})
|
||||
}
|
||||
}
|
97
crates/api/src/community/follow.rs
Normal file
97
crates/api/src/community/follow.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
community::{CommunityResponse, FollowCommunity},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
objects::community::ApubCommunity,
|
||||
protocol::activities::following::{
|
||||
follow::FollowCommunity as FollowCommunityApub,
|
||||
undo_follow::UndoFollowCommunity,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||
traits::{Crud, Followable},
|
||||
};
|
||||
use lemmy_db_views_actor::community_view::CommunityView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for FollowCommunity {
|
||||
type Response = CommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &FollowCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let community: ApubCommunity = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
person_id: local_user_view.person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
if community.local {
|
||||
if data.follow {
|
||||
check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
|
||||
check_community_deleted_or_removed(community_id, context.pool()).await?;
|
||||
|
||||
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
||||
blocking(context.pool(), follow)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?;
|
||||
} else {
|
||||
let unfollow =
|
||||
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||
blocking(context.pool(), unfollow)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?;
|
||||
}
|
||||
} else if data.follow {
|
||||
// Dont actually add to the community followers here, because you need
|
||||
// to wait for the accept
|
||||
FollowCommunityApub::send(&local_user_view.person.clone().into(), &community, context)
|
||||
.await?;
|
||||
} else {
|
||||
UndoFollowCommunity::send(&local_user_view.person.clone().into(), &community, context)
|
||||
.await?;
|
||||
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||
blocking(context.pool(), unfollow)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?;
|
||||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this needs to return a "pending" state, until Accept is received from the remote server
|
||||
// For now, just assume that remote follows are accepted.
|
||||
// Otherwise, the subscribed will be null
|
||||
if !community.local {
|
||||
community_view.subscribed = data.follow;
|
||||
}
|
||||
|
||||
Ok(CommunityResponse { community_view })
|
||||
}
|
||||
}
|
5
crates/api/src/community/mod.rs
Normal file
5
crates/api/src/community/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod add_mod;
|
||||
mod ban;
|
||||
mod block;
|
||||
mod follow;
|
||||
mod transfer;
|
124
crates/api/src/community/transfer.rs
Normal file
124
crates/api/src/community/transfer.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use anyhow::Context;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
community::{GetCommunityResponse, TransferCommunity},
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{CommunityModerator, CommunityModeratorForm},
|
||||
moderator::{ModTransferCommunity, ModTransferCommunityForm},
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::CommunityView,
|
||||
person_view::PersonViewSafe,
|
||||
};
|
||||
use lemmy_utils::{location_info, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
// TODO: we dont do anything for federation here, it should be updated the next time the community
|
||||
// gets fetched. i hope we can get rid of the community creator role soon.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for TransferCommunity {
|
||||
type Response = GetCommunityResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetCommunityResponse, LemmyError> {
|
||||
let data: &TransferCommunity = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
|
||||
// Fetch the community mods
|
||||
let community_id = data.community_id;
|
||||
let mut community_mods = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Make sure transferrer is either the top community mod, or an admin
|
||||
if local_user_view.person.id != community_mods[0].moderator.id
|
||||
&& !admins
|
||||
.iter()
|
||||
.map(|a| a.person.id)
|
||||
.any(|x| x == local_user_view.person.id)
|
||||
{
|
||||
return Err(LemmyError::from_message("not_an_admin"));
|
||||
}
|
||||
|
||||
// You have to re-do the community_moderator table, reordering it.
|
||||
// Add the transferee to the top
|
||||
let creator_index = community_mods
|
||||
.iter()
|
||||
.position(|r| r.moderator.id == data.person_id)
|
||||
.context(location_info!())?;
|
||||
let creator_person = community_mods.remove(creator_index);
|
||||
community_mods.insert(0, creator_person);
|
||||
|
||||
// Delete all the mods
|
||||
let community_id = data.community_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommunityModerator::delete_for_community(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
// Re-add the mods, in the new order
|
||||
for cmod in &community_mods {
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: cmod.community.id,
|
||||
person_id: cmod.moderator.id,
|
||||
};
|
||||
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
blocking(context.pool(), join)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "community_moderator_already_exists"))?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModTransferCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(false),
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModTransferCommunity::create(conn, &form)
|
||||
})
|
||||
.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?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let moderators = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(GetCommunityResponse {
|
||||
community_view,
|
||||
site: None,
|
||||
moderators,
|
||||
online: 0,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ pub async fn match_websocket_operation(
|
|||
do_websocket_operation::<PasswordReset>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::PasswordChange => {
|
||||
do_websocket_operation::<PasswordChange>(context, id, op, data).await
|
||||
do_websocket_operation::<PasswordChangeAfterReset>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
|
||||
UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
|
||||
|
|
|
@ -1,966 +0,0 @@
|
|||
use crate::{captcha_as_wav_base64, Perform};
|
||||
use actix_web::web::Data;
|
||||
use bcrypt::verify;
|
||||
use captcha::{gen, Difficulty};
|
||||
use chrono::Duration;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_image_has_local_domain,
|
||||
check_registration_application,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
password_length_check,
|
||||
person::*,
|
||||
remove_user_data,
|
||||
send_email_verification_success,
|
||||
send_password_reset_email,
|
||||
send_verification_email,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
activities::block::SiteOrCommunity,
|
||||
protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
diesel_option_overwrite,
|
||||
diesel_option_overwrite_to_url,
|
||||
from_opt_str_to_opt_enum,
|
||||
naive_now,
|
||||
source::{
|
||||
comment::Comment,
|
||||
email_verification::EmailVerification,
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
moderator::*,
|
||||
password_reset_request::*,
|
||||
person::*,
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
person_mention::*,
|
||||
private_message::PrivateMessage,
|
||||
site::*,
|
||||
},
|
||||
traits::{Blockable, Crud},
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
comment_report_view::CommentReportView,
|
||||
comment_view::{CommentQueryBuilder, CommentView},
|
||||
local_user_view::LocalUserView,
|
||||
post_report_view::PostReportView,
|
||||
private_message_view::PrivateMessageView,
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
|
||||
person_view::PersonViewSafe,
|
||||
};
|
||||
use lemmy_utils::{
|
||||
claims::Claims,
|
||||
utils::{is_valid_display_name, is_valid_matrix_id, naive_from_unix},
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::{
|
||||
messages::{CaptchaItem, SendAllMessage},
|
||||
LemmyContext,
|
||||
UserOperation,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for Login {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &Login = self;
|
||||
|
||||
// Fetch that username / email
|
||||
let username_or_email = data.username_or_email.clone();
|
||||
let local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::find_by_email_or_name(conn, &username_or_email)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_that_username_or_email"))?;
|
||||
|
||||
// Verify the password
|
||||
let valid: bool = verify(
|
||||
&data.password,
|
||||
&local_user_view.local_user.password_encrypted,
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if !valid {
|
||||
return Err(LemmyError::from_message("password_incorrect"));
|
||||
}
|
||||
|
||||
let site = blocking(context.pool(), Site::read_local_site).await??;
|
||||
if site.require_email_verification && !local_user_view.local_user.email_verified {
|
||||
return Err(LemmyError::from_message("email_not_verified"));
|
||||
}
|
||||
|
||||
check_registration_application(&site, &local_user_view, context.pool()).await?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
local_user_view.local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetCaptcha {
|
||||
type Response = GetCaptchaResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let captcha_settings = context.settings().captcha;
|
||||
|
||||
if !captcha_settings.enabled {
|
||||
return Ok(GetCaptchaResponse { ok: None });
|
||||
}
|
||||
|
||||
let captcha = match captcha_settings.difficulty.as_str() {
|
||||
"easy" => gen(Difficulty::Easy),
|
||||
"medium" => gen(Difficulty::Medium),
|
||||
"hard" => gen(Difficulty::Hard),
|
||||
_ => gen(Difficulty::Medium),
|
||||
};
|
||||
|
||||
let answer = captcha.chars_as_string();
|
||||
|
||||
let png = captcha.as_base64().expect("failed to generate captcha");
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let wav = captcha_as_wav_base64(&captcha);
|
||||
|
||||
let captcha_item = CaptchaItem {
|
||||
answer,
|
||||
uuid: uuid.to_owned(),
|
||||
expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
|
||||
};
|
||||
|
||||
// Stores the captcha item on the queue
|
||||
context.chat_server().do_send(captcha_item);
|
||||
|
||||
Ok(GetCaptchaResponse {
|
||||
ok: Some(CaptchaResponse { png, wav, uuid }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SaveUserSettings {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &SaveUserSettings = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
|
||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||
let bio = diesel_option_overwrite(&data.bio);
|
||||
let display_name = diesel_option_overwrite(&data.display_name);
|
||||
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
|
||||
let bot_account = data.bot_account;
|
||||
let email_deref = data.email.as_deref().map(|e| e.to_owned());
|
||||
let email = diesel_option_overwrite(&email_deref);
|
||||
|
||||
check_image_has_local_domain(avatar.as_ref().unwrap_or(&None))?;
|
||||
check_image_has_local_domain(banner.as_ref().unwrap_or(&None))?;
|
||||
|
||||
if let Some(Some(email)) = &email {
|
||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||
// Only send the verification email if there was an email change
|
||||
if previous_email.ne(email) {
|
||||
send_verification_email(&local_user_view, email, context.pool(), &context.settings())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
|
||||
if let Some(email) = &email {
|
||||
let site_fut = blocking(context.pool(), Site::read_local_site);
|
||||
if email.is_none() && site_fut.await??.require_email_verification {
|
||||
return Err(LemmyError::from_message("email_required"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(bio)) = &bio {
|
||||
if bio.chars().count() > 300 {
|
||||
return Err(LemmyError::from_message("bio_length_overflow"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(display_name)) = &display_name {
|
||||
if !is_valid_display_name(
|
||||
display_name.trim(),
|
||||
context.settings().actor_name_max_length,
|
||||
) {
|
||||
return Err(LemmyError::from_message("invalid_username"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(matrix_user_id)) = &matrix_user_id {
|
||||
if !is_valid_matrix_id(matrix_user_id) {
|
||||
return Err(LemmyError::from_message("invalid_matrix_id"));
|
||||
}
|
||||
}
|
||||
|
||||
let local_user_id = local_user_view.local_user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let default_listing_type = data.default_listing_type;
|
||||
let default_sort_type = data.default_sort_type;
|
||||
let password_encrypted = local_user_view.local_user.password_encrypted;
|
||||
let public_key = local_user_view.person.public_key;
|
||||
|
||||
let person_form = PersonForm {
|
||||
name: local_user_view.person.name,
|
||||
avatar,
|
||||
banner,
|
||||
inbox_url: None,
|
||||
display_name,
|
||||
published: None,
|
||||
updated: Some(naive_now()),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
actor_id: None,
|
||||
bio,
|
||||
local: None,
|
||||
admin: None,
|
||||
private_key: None,
|
||||
public_key,
|
||||
last_refreshed_at: None,
|
||||
shared_inbox_url: None,
|
||||
matrix_user_id,
|
||||
bot_account,
|
||||
ban_expires: None,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
Person::update(conn, person_id, &person_form)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?;
|
||||
|
||||
let local_user_form = LocalUserForm {
|
||||
person_id: Some(person_id),
|
||||
email,
|
||||
password_encrypted: Some(password_encrypted),
|
||||
show_nsfw: data.show_nsfw,
|
||||
show_bot_accounts: data.show_bot_accounts,
|
||||
show_scores: data.show_scores,
|
||||
theme: data.theme.to_owned(),
|
||||
default_sort_type,
|
||||
default_listing_type,
|
||||
lang: data.lang.to_owned(),
|
||||
show_avatars: data.show_avatars,
|
||||
show_read_posts: data.show_read_posts,
|
||||
show_new_post_notifs: data.show_new_post_notifs,
|
||||
send_notifications_to_email: data.send_notifications_to_email,
|
||||
email_verified: None,
|
||||
accepted_application: None,
|
||||
};
|
||||
|
||||
let local_user_res = blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, local_user_id, &local_user_form)
|
||||
})
|
||||
.await?;
|
||||
let updated_local_user = match local_user_res {
|
||||
Ok(u) => u,
|
||||
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"
|
||||
};
|
||||
|
||||
return Err(LemmyError::from_error_message(e, err_type));
|
||||
}
|
||||
};
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
updated_local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ChangePassword {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &ChangePassword = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
|
||||
|
||||
password_length_check(&data.new_password)?;
|
||||
|
||||
// Make sure passwords match
|
||||
if data.new_password != data.new_password_verify {
|
||||
return Err(LemmyError::from_message("passwords_dont_match"));
|
||||
}
|
||||
|
||||
// Check the old password
|
||||
let valid: bool = verify(
|
||||
&data.old_password,
|
||||
&local_user_view.local_user.password_encrypted,
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if !valid {
|
||||
return Err(LemmyError::from_message("password_incorrect"));
|
||||
}
|
||||
|
||||
let local_user_id = local_user_view.local_user.id;
|
||||
let new_password = data.new_password.to_owned();
|
||||
let updated_local_user = blocking(context.pool(), move |conn| {
|
||||
LocalUser::update_password(conn, local_user_id, &new_password)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
updated_local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for AddAdmin {
|
||||
type Response = AddAdminResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<AddAdminResponse, LemmyError> {
|
||||
let data: &AddAdmin = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let added = data.added;
|
||||
let added_person_id = data.person_id;
|
||||
let added_admin = blocking(context.pool(), move |conn| {
|
||||
Person::add_admin(conn, added_person_id, added)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: added_admin.id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
|
||||
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
|
||||
let res = AddAdminResponse { admins };
|
||||
|
||||
context.chat_server().do_send(SendAllMessage {
|
||||
op: UserOperation::AddAdmin,
|
||||
response: res.clone(),
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BanPerson {
|
||||
type Response = BanPersonResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BanPersonResponse, LemmyError> {
|
||||
let data: &BanPerson = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let ban = data.ban;
|
||||
let banned_person_id = data.person_id;
|
||||
let expires = data.expires.map(naive_from_unix);
|
||||
|
||||
let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban, expires);
|
||||
let person = blocking(context.pool(), ban_person)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
|
||||
|
||||
// Remove their data if that's desired
|
||||
let remove_data = data.remove_data.unwrap_or(false);
|
||||
if remove_data {
|
||||
remove_user_data(person.id, context.pool()).await?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModBanForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
reason: data.reason.to_owned(),
|
||||
banned: Some(data.ban),
|
||||
expires,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
|
||||
|
||||
let person_id = data.person_id;
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let site = SiteOrCommunity::Site(
|
||||
blocking(context.pool(), Site::read_local_site)
|
||||
.await??
|
||||
.into(),
|
||||
);
|
||||
// if the action affects a local user, federate to other instances
|
||||
if person.local {
|
||||
if ban {
|
||||
BlockUser::send(
|
||||
&site,
|
||||
&person.into(),
|
||||
&local_user_view.person.into(),
|
||||
remove_data,
|
||||
data.reason.clone(),
|
||||
expires,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
UndoBlockUser::send(
|
||||
&site,
|
||||
&person.into(),
|
||||
&local_user_view.person.into(),
|
||||
data.reason.clone(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let res = BanPersonResponse {
|
||||
person_view,
|
||||
banned: data.ban,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendAllMessage {
|
||||
op: UserOperation::BanPerson,
|
||||
response: res.clone(),
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetBannedPersons {
|
||||
type Response = BannedPersonsResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data: &GetBannedPersons = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
|
||||
|
||||
let res = Self::Response { banned };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BlockPerson {
|
||||
type Response = BlockPersonResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BlockPersonResponse, LemmyError> {
|
||||
let data: &BlockPerson = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let target_id = data.person_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Don't let a person block themselves
|
||||
if target_id == person_id {
|
||||
return Err(LemmyError::from_message("cant_block_yourself"));
|
||||
}
|
||||
|
||||
let person_block_form = PersonBlockForm {
|
||||
person_id,
|
||||
target_id,
|
||||
};
|
||||
|
||||
if data.block {
|
||||
let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
|
||||
blocking(context.pool(), block)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "person_block_already_exists"))?;
|
||||
} else {
|
||||
let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
|
||||
blocking(context.pool(), unblock)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "person_block_already_exists"))?;
|
||||
}
|
||||
|
||||
// TODO does any federated stuff need to be done here?
|
||||
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, target_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = BlockPersonResponse {
|
||||
person_view,
|
||||
blocked: data.block,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetReplies {
|
||||
type Response = GetRepliesResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetRepliesResponse, LemmyError> {
|
||||
let data: &GetReplies = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let person_id = local_user_view.person.id;
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.recipient_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(GetRepliesResponse { replies })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetPersonMentions {
|
||||
type Response = GetPersonMentionsResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetPersonMentionsResponse, LemmyError> {
|
||||
let data: &GetPersonMentions = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let person_id = local_user_view.person.id;
|
||||
let mentions = blocking(context.pool(), move |conn| {
|
||||
PersonMentionQueryBuilder::create(conn)
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(GetPersonMentionsResponse { mentions })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkPersonMentionAsRead {
|
||||
type Response = PersonMentionResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PersonMentionResponse, LemmyError> {
|
||||
let data: &MarkPersonMentionAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_mention_id = data.person_mention_id;
|
||||
let read_person_mention = blocking(context.pool(), move |conn| {
|
||||
PersonMention::read(conn, person_mention_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if local_user_view.person.id != read_person_mention.recipient_id {
|
||||
return Err(LemmyError::from_message("couldnt_update_comment"));
|
||||
}
|
||||
|
||||
let person_mention_id = read_person_mention.id;
|
||||
let read = data.read;
|
||||
let update_mention =
|
||||
move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
|
||||
blocking(context.pool(), update_mention)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
let person_mention_id = read_person_mention.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let person_mention_view = blocking(context.pool(), move |conn| {
|
||||
PersonMentionView::read(conn, person_mention_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(PersonMentionResponse {
|
||||
person_mention_view,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkAllAsRead {
|
||||
type Response = GetRepliesResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetRepliesResponse, LemmyError> {
|
||||
let data: &MarkAllAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.my_person_id(person_id)
|
||||
.recipient_id(person_id)
|
||||
.unread_only(true)
|
||||
.page(1)
|
||||
.limit(999)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
// Not easy to do as a bulk operation,
|
||||
// because recipient_id isn't in the comment table
|
||||
for comment_view in &replies {
|
||||
let reply_id = comment_view.comment.id;
|
||||
let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
|
||||
blocking(context.pool(), mark_as_read)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
}
|
||||
|
||||
// Mark all user mentions as read
|
||||
let update_person_mentions =
|
||||
move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
|
||||
blocking(context.pool(), update_person_mentions)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// Mark all private_messages as read
|
||||
let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
|
||||
blocking(context.pool(), update_pm)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||
|
||||
Ok(GetRepliesResponse { replies: vec![] })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PasswordReset {
|
||||
type Response = PasswordResetResponse;
|
||||
|
||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PasswordResetResponse, LemmyError> {
|
||||
let data: &PasswordReset = self;
|
||||
|
||||
// Fetch that email
|
||||
let email = data.email.clone();
|
||||
let local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::find_by_email(conn, &email)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_that_username_or_email"))?;
|
||||
|
||||
// Email the pure token to the user.
|
||||
send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
|
||||
Ok(PasswordResetResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PasswordChange {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &PasswordChange = self;
|
||||
|
||||
// Fetch the user_id from the token
|
||||
let token = data.token.clone();
|
||||
let local_user_id = blocking(context.pool(), move |conn| {
|
||||
PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
password_length_check(&data.password)?;
|
||||
|
||||
// Make sure passwords match
|
||||
if data.password != data.password_verify {
|
||||
return Err(LemmyError::from_message("passwords_dont_match"));
|
||||
}
|
||||
|
||||
// Update the user with the new password
|
||||
let password = data.password.clone();
|
||||
let updated_local_user = blocking(context.pool(), move |conn| {
|
||||
LocalUser::update_password(conn, local_user_id, &password)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
updated_local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetReportCount {
|
||||
type Response = GetReportCountResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetReportCountResponse, LemmyError> {
|
||||
let data: &GetReportCount = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
|
||||
let comment_reports = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::get_report_count(conn, person_id, admin, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let post_reports = blocking(context.pool(), move |conn| {
|
||||
PostReportView::get_report_count(conn, person_id, admin, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = GetReportCountResponse {
|
||||
community_id,
|
||||
comment_reports,
|
||||
post_reports,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetUnreadCount {
|
||||
type Response = GetUnreadCountResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentView::get_unread_replies(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mentions = blocking(context.pool(), move |conn| {
|
||||
PersonMentionView::get_unread_mentions(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let private_messages = blocking(context.pool(), move |conn| {
|
||||
PrivateMessageView::get_unread_messages(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response {
|
||||
replies,
|
||||
mentions,
|
||||
private_messages,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for VerifyEmail {
|
||||
type Response = VerifyEmailResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<usize>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let token = self.token.clone();
|
||||
let verification = blocking(context.pool(), move |conn| {
|
||||
EmailVerification::read_for_token(conn, &token)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "token_not_found"))?;
|
||||
|
||||
let form = LocalUserForm {
|
||||
// necessary in case this is a new signup
|
||||
email_verified: Some(true),
|
||||
// necessary in case email of an existing user was changed
|
||||
email: Some(Some(verification.email)),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
let local_user_id = verification.local_user_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, local_user_id, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read(conn, local_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
send_email_verification_success(&local_user_view, &context.settings())?;
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(VerifyEmailResponse {})
|
||||
}
|
||||
}
|
66
crates/api/src/local_user/add_admin.rs
Normal file
66
crates/api/src/local_user/add_admin.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
person::{AddAdmin, AddAdminResponse},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{ModAdd, ModAddForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for AddAdmin {
|
||||
type Response = AddAdminResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<AddAdminResponse, LemmyError> {
|
||||
let data: &AddAdmin = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let added = data.added;
|
||||
let added_person_id = data.person_id;
|
||||
let added_admin = blocking(context.pool(), move |conn| {
|
||||
Person::add_admin(conn, added_person_id, added)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: added_admin.id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
|
||||
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
|
||||
let res = AddAdminResponse { admins };
|
||||
|
||||
context.chat_server().do_send(SendAllMessage {
|
||||
op: UserOperation::AddAdmin,
|
||||
response: res.clone(),
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
118
crates/api/src/local_user/ban_person.rs
Normal file
118
crates/api/src/local_user/ban_person.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
person::{BanPerson, BanPersonResponse},
|
||||
remove_user_data,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
activities::block::SiteOrCommunity,
|
||||
protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{ModBan, ModBanForm},
|
||||
person::Person,
|
||||
site::Site,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||
use lemmy_utils::{utils::naive_from_unix, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BanPerson {
|
||||
type Response = BanPersonResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BanPersonResponse, LemmyError> {
|
||||
let data: &BanPerson = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let ban = data.ban;
|
||||
let banned_person_id = data.person_id;
|
||||
let expires = data.expires.map(naive_from_unix);
|
||||
|
||||
let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban, expires);
|
||||
let person = blocking(context.pool(), ban_person)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
|
||||
|
||||
// Remove their data if that's desired
|
||||
let remove_data = data.remove_data.unwrap_or(false);
|
||||
if remove_data {
|
||||
remove_user_data(person.id, context.pool()).await?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let form = ModBanForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
reason: data.reason.to_owned(),
|
||||
banned: Some(data.ban),
|
||||
expires,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
|
||||
|
||||
let person_id = data.person_id;
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let site = SiteOrCommunity::Site(
|
||||
blocking(context.pool(), Site::read_local_site)
|
||||
.await??
|
||||
.into(),
|
||||
);
|
||||
// if the action affects a local user, federate to other instances
|
||||
if person.local {
|
||||
if ban {
|
||||
BlockUser::send(
|
||||
&site,
|
||||
&person.into(),
|
||||
&local_user_view.person.into(),
|
||||
remove_data,
|
||||
data.reason.clone(),
|
||||
expires,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
UndoBlockUser::send(
|
||||
&site,
|
||||
&person.into(),
|
||||
&local_user_view.person.into(),
|
||||
data.reason.clone(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let res = BanPersonResponse {
|
||||
person_view,
|
||||
banned: data.ban,
|
||||
};
|
||||
|
||||
context.chat_server().do_send(SendAllMessage {
|
||||
op: UserOperation::BanPerson,
|
||||
response: res.clone(),
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
67
crates/api/src/local_user/block.rs
Normal file
67
crates/api/src/local_user/block.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{BlockPerson, BlockPersonResponse},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::person_block::{PersonBlock, PersonBlockForm},
|
||||
traits::Blockable,
|
||||
};
|
||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for BlockPerson {
|
||||
type Response = BlockPersonResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BlockPersonResponse, LemmyError> {
|
||||
let data: &BlockPerson = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let target_id = data.person_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Don't let a person block themselves
|
||||
if target_id == person_id {
|
||||
return Err(LemmyError::from_message("cant_block_yourself"));
|
||||
}
|
||||
|
||||
let person_block_form = PersonBlockForm {
|
||||
person_id,
|
||||
target_id,
|
||||
};
|
||||
|
||||
if data.block {
|
||||
let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
|
||||
blocking(context.pool(), block)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "person_block_already_exists"))?;
|
||||
} else {
|
||||
let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
|
||||
blocking(context.pool(), unblock)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "person_block_already_exists"))?;
|
||||
}
|
||||
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, target_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = BlockPersonResponse {
|
||||
person_view,
|
||||
blocked: data.block,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
66
crates/api/src/local_user/change_password.rs
Normal file
66
crates/api/src/local_user/change_password.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use bcrypt::verify;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
password_length_check,
|
||||
person::{ChangePassword, LoginResponse},
|
||||
};
|
||||
use lemmy_db_schema::source::local_user::LocalUser;
|
||||
use lemmy_utils::{claims::Claims, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ChangePassword {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &ChangePassword = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
|
||||
|
||||
password_length_check(&data.new_password)?;
|
||||
|
||||
// Make sure passwords match
|
||||
if data.new_password != data.new_password_verify {
|
||||
return Err(LemmyError::from_message("passwords_dont_match"));
|
||||
}
|
||||
|
||||
// Check the old password
|
||||
let valid: bool = verify(
|
||||
&data.old_password,
|
||||
&local_user_view.local_user.password_encrypted,
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if !valid {
|
||||
return Err(LemmyError::from_message("password_incorrect"));
|
||||
}
|
||||
|
||||
let local_user_id = local_user_view.local_user.id;
|
||||
let new_password = data.new_password.to_owned();
|
||||
let updated_local_user = blocking(context.pool(), move |conn| {
|
||||
LocalUser::update_password(conn, local_user_id, &new_password)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
updated_local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
63
crates/api/src/local_user/change_password_after_reset.rs
Normal file
63
crates/api/src/local_user/change_password_after_reset.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
password_length_check,
|
||||
person::{LoginResponse, PasswordChangeAfterReset},
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
local_user::LocalUser,
|
||||
password_reset_request::PasswordResetRequest,
|
||||
};
|
||||
use lemmy_utils::{claims::Claims, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PasswordChangeAfterReset {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &PasswordChangeAfterReset = self;
|
||||
|
||||
// Fetch the user_id from the token
|
||||
let token = data.token.clone();
|
||||
let local_user_id = blocking(context.pool(), move |conn| {
|
||||
PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
password_length_check(&data.password)?;
|
||||
|
||||
// Make sure passwords match
|
||||
if data.password != data.password_verify {
|
||||
return Err(LemmyError::from_message("passwords_dont_match"));
|
||||
}
|
||||
|
||||
// Update the user with the new password
|
||||
let password = data.password.clone();
|
||||
let updated_local_user = blocking(context.pool(), move |conn| {
|
||||
LocalUser::update_password(conn, local_user_id, &password)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
updated_local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
53
crates/api/src/local_user/get_captcha.rs
Normal file
53
crates/api/src/local_user/get_captcha.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use crate::{captcha_as_wav_base64, Perform};
|
||||
use actix_web::web::Data;
|
||||
use captcha::{gen, Difficulty};
|
||||
use chrono::Duration;
|
||||
use lemmy_api_common::person::{CaptchaResponse, GetCaptcha, GetCaptchaResponse};
|
||||
use lemmy_db_schema::naive_now;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::CaptchaItem, LemmyContext};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetCaptcha {
|
||||
type Response = GetCaptchaResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let captcha_settings = context.settings().captcha;
|
||||
|
||||
if !captcha_settings.enabled {
|
||||
return Ok(GetCaptchaResponse { ok: None });
|
||||
}
|
||||
|
||||
let captcha = gen(match captcha_settings.difficulty.as_str() {
|
||||
"easy" => Difficulty::Easy,
|
||||
"hard" => Difficulty::Hard,
|
||||
_ => Difficulty::Medium,
|
||||
});
|
||||
|
||||
let answer = captcha.chars_as_string();
|
||||
|
||||
let png = captcha.as_base64().expect("failed to generate captcha");
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let wav = captcha_as_wav_base64(&captcha);
|
||||
|
||||
let captcha_item = CaptchaItem {
|
||||
answer,
|
||||
uuid: uuid.to_owned(),
|
||||
expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
|
||||
};
|
||||
|
||||
// Stores the captcha item on the queue
|
||||
context.chat_server().do_send(captcha_item);
|
||||
|
||||
Ok(GetCaptchaResponse {
|
||||
ok: Some(CaptchaResponse { png, wav, uuid }),
|
||||
})
|
||||
}
|
||||
}
|
35
crates/api/src/local_user/list_banned.rs
Normal file
35
crates/api/src/local_user/list_banned.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
person::{BannedPersonsResponse, GetBannedPersons},
|
||||
};
|
||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetBannedPersons {
|
||||
type Response = BannedPersonsResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data: &GetBannedPersons = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
|
||||
|
||||
let res = Self::Response { banned };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
65
crates/api/src/local_user/login.rs
Normal file
65
crates/api/src/local_user/login.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use bcrypt::verify;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_registration_application,
|
||||
person::{Login, LoginResponse},
|
||||
};
|
||||
use lemmy_db_schema::source::site::Site;
|
||||
use lemmy_db_views::local_user_view::LocalUserView;
|
||||
use lemmy_utils::{claims::Claims, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for Login {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &Login = self;
|
||||
|
||||
// Fetch that username / email
|
||||
let username_or_email = data.username_or_email.clone();
|
||||
let local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::find_by_email_or_name(conn, &username_or_email)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_that_username_or_email"))?;
|
||||
|
||||
// Verify the password
|
||||
let valid: bool = verify(
|
||||
&data.password,
|
||||
&local_user_view.local_user.password_encrypted,
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if !valid {
|
||||
return Err(LemmyError::from_message("password_incorrect"));
|
||||
}
|
||||
|
||||
let site = blocking(context.pool(), Site::read_local_site).await??;
|
||||
if site.require_email_verification && !local_user_view.local_user.email_verified {
|
||||
return Err(LemmyError::from_message("email_not_verified"));
|
||||
}
|
||||
|
||||
check_registration_application(&site, &local_user_view, context.pool()).await?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
local_user_view.local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
13
crates/api/src/local_user/mod.rs
Normal file
13
crates/api/src/local_user/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
mod add_admin;
|
||||
mod ban_person;
|
||||
mod block;
|
||||
mod change_password;
|
||||
mod change_password_after_reset;
|
||||
mod get_captcha;
|
||||
mod list_banned;
|
||||
mod login;
|
||||
mod notifications;
|
||||
mod report_count;
|
||||
mod reset_password;
|
||||
mod save_settings;
|
||||
mod verify_email;
|
47
crates/api/src/local_user/notifications/list_mentions.rs
Normal file
47
crates/api/src/local_user/notifications/list_mentions.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{GetPersonMentions, GetPersonMentionsResponse},
|
||||
};
|
||||
use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType};
|
||||
use lemmy_db_views_actor::person_mention_view::PersonMentionQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetPersonMentions {
|
||||
type Response = GetPersonMentionsResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetPersonMentionsResponse, LemmyError> {
|
||||
let data: &GetPersonMentions = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let person_id = local_user_view.person.id;
|
||||
let mentions = blocking(context.pool(), move |conn| {
|
||||
PersonMentionQueryBuilder::create(conn)
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(GetPersonMentionsResponse { mentions })
|
||||
}
|
||||
}
|
50
crates/api/src/local_user/notifications/list_replies.rs
Normal file
50
crates/api/src/local_user/notifications/list_replies.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{GetReplies, GetRepliesResponse},
|
||||
};
|
||||
use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetReplies {
|
||||
type Response = GetRepliesResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetRepliesResponse, LemmyError> {
|
||||
let data: &GetReplies = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let person_id = local_user_view.person.id;
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.recipient_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(GetRepliesResponse { replies })
|
||||
}
|
||||
}
|
69
crates/api/src/local_user/notifications/mark_all_read.rs
Normal file
69
crates/api/src/local_user/notifications/mark_all_read.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{GetRepliesResponse, MarkAllAsRead},
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::Comment,
|
||||
person_mention::PersonMention,
|
||||
private_message::PrivateMessage,
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkAllAsRead {
|
||||
type Response = GetRepliesResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetRepliesResponse, LemmyError> {
|
||||
let data: &MarkAllAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.my_person_id(person_id)
|
||||
.recipient_id(person_id)
|
||||
.unread_only(true)
|
||||
.page(1)
|
||||
.limit(999)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
// Not easy to do as a bulk operation,
|
||||
// because recipient_id isn't in the comment table
|
||||
for comment_view in &replies {
|
||||
let reply_id = comment_view.comment.id;
|
||||
let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
|
||||
blocking(context.pool(), mark_as_read)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
}
|
||||
|
||||
// Mark all user mentions as read
|
||||
let update_person_mentions =
|
||||
move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
|
||||
blocking(context.pool(), update_person_mentions)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// Mark all private_messages as read
|
||||
let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
|
||||
blocking(context.pool(), update_pm)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||
|
||||
Ok(GetRepliesResponse { replies: vec![] })
|
||||
}
|
||||
}
|
56
crates/api/src/local_user/notifications/mark_mention_read.rs
Normal file
56
crates/api/src/local_user/notifications/mark_mention_read.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{MarkPersonMentionAsRead, PersonMentionResponse},
|
||||
};
|
||||
use lemmy_db_schema::{source::person_mention::PersonMention, traits::Crud};
|
||||
use lemmy_db_views_actor::person_mention_view::PersonMentionView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkPersonMentionAsRead {
|
||||
type Response = PersonMentionResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PersonMentionResponse, LemmyError> {
|
||||
let data: &MarkPersonMentionAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_mention_id = data.person_mention_id;
|
||||
let read_person_mention = blocking(context.pool(), move |conn| {
|
||||
PersonMention::read(conn, person_mention_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if local_user_view.person.id != read_person_mention.recipient_id {
|
||||
return Err(LemmyError::from_message("couldnt_update_comment"));
|
||||
}
|
||||
|
||||
let person_mention_id = read_person_mention.id;
|
||||
let read = data.read;
|
||||
let update_mention =
|
||||
move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
|
||||
blocking(context.pool(), update_mention)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
let person_mention_id = read_person_mention.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let person_mention_view = blocking(context.pool(), move |conn| {
|
||||
PersonMentionView::read(conn, person_mention_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(PersonMentionResponse {
|
||||
person_mention_view,
|
||||
})
|
||||
}
|
||||
}
|
5
crates/api/src/local_user/notifications/mod.rs
Normal file
5
crates/api/src/local_user/notifications/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod list_mentions;
|
||||
mod list_replies;
|
||||
mod mark_all_read;
|
||||
mod mark_mention_read;
|
||||
mod unread_count;
|
52
crates/api/src/local_user/notifications/unread_count.rs
Normal file
52
crates/api/src/local_user/notifications/unread_count.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{GetUnreadCount, GetUnreadCountResponse},
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentView, private_message_view::PrivateMessageView};
|
||||
use lemmy_db_views_actor::person_mention_view::PersonMentionView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetUnreadCount {
|
||||
type Response = GetUnreadCountResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentView::get_unread_replies(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mentions = blocking(context.pool(), move |conn| {
|
||||
PersonMentionView::get_unread_mentions(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let private_messages = blocking(context.pool(), move |conn| {
|
||||
PrivateMessageView::get_unread_messages(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response {
|
||||
replies,
|
||||
mentions,
|
||||
private_messages,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
48
crates/api/src/local_user/report_count.rs
Normal file
48
crates/api/src/local_user/report_count.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{GetReportCount, GetReportCountResponse},
|
||||
};
|
||||
use lemmy_db_views::{comment_report_view::CommentReportView, post_report_view::PostReportView};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetReportCount {
|
||||
type Response = GetReportCountResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetReportCountResponse, LemmyError> {
|
||||
let data: &GetReportCount = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
|
||||
let comment_reports = blocking(context.pool(), move |conn| {
|
||||
CommentReportView::get_report_count(conn, person_id, admin, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let post_reports = blocking(context.pool(), move |conn| {
|
||||
PostReportView::get_report_count(conn, person_id, admin, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = GetReportCountResponse {
|
||||
community_id,
|
||||
comment_reports,
|
||||
post_reports,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
36
crates/api/src/local_user/reset_password.rs
Normal file
36
crates/api/src/local_user/reset_password.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
person::{PasswordReset, PasswordResetResponse},
|
||||
send_password_reset_email,
|
||||
};
|
||||
use lemmy_db_views::local_user_view::LocalUserView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PasswordReset {
|
||||
type Response = PasswordResetResponse;
|
||||
|
||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PasswordResetResponse, LemmyError> {
|
||||
let data: &PasswordReset = self;
|
||||
|
||||
// Fetch that email
|
||||
let email = data.email.clone();
|
||||
let local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::find_by_email(conn, &email)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_that_username_or_email"))?;
|
||||
|
||||
// Email the pure token to the user.
|
||||
send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
|
||||
Ok(PasswordResetResponse {})
|
||||
}
|
||||
}
|
181
crates/api/src/local_user/save_settings.rs
Normal file
181
crates/api/src/local_user/save_settings.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_image_has_local_domain,
|
||||
get_local_user_view_from_jwt,
|
||||
person::{LoginResponse, SaveUserSettings},
|
||||
send_verification_email,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
diesel_option_overwrite,
|
||||
diesel_option_overwrite_to_url,
|
||||
naive_now,
|
||||
source::{
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
person::{Person, PersonForm},
|
||||
site::Site,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{
|
||||
claims::Claims,
|
||||
utils::{is_valid_display_name, is_valid_matrix_id},
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SaveUserSettings {
|
||||
type Response = LoginResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &SaveUserSettings = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
|
||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||
let bio = diesel_option_overwrite(&data.bio);
|
||||
let display_name = diesel_option_overwrite(&data.display_name);
|
||||
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
|
||||
let bot_account = data.bot_account;
|
||||
let email_deref = data.email.as_deref().map(|e| e.to_owned());
|
||||
let email = diesel_option_overwrite(&email_deref);
|
||||
|
||||
check_image_has_local_domain(avatar.as_ref().unwrap_or(&None))?;
|
||||
check_image_has_local_domain(banner.as_ref().unwrap_or(&None))?;
|
||||
|
||||
if let Some(Some(email)) = &email {
|
||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||
// Only send the verification email if there was an email change
|
||||
if previous_email.ne(email) {
|
||||
send_verification_email(&local_user_view, email, context.pool(), &context.settings())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
|
||||
if let Some(email) = &email {
|
||||
let site_fut = blocking(context.pool(), Site::read_local_site);
|
||||
if email.is_none() && site_fut.await??.require_email_verification {
|
||||
return Err(LemmyError::from_message("email_required"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(bio)) = &bio {
|
||||
if bio.chars().count() > 300 {
|
||||
return Err(LemmyError::from_message("bio_length_overflow"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(display_name)) = &display_name {
|
||||
if !is_valid_display_name(
|
||||
display_name.trim(),
|
||||
context.settings().actor_name_max_length,
|
||||
) {
|
||||
return Err(LemmyError::from_message("invalid_username"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(matrix_user_id)) = &matrix_user_id {
|
||||
if !is_valid_matrix_id(matrix_user_id) {
|
||||
return Err(LemmyError::from_message("invalid_matrix_id"));
|
||||
}
|
||||
}
|
||||
|
||||
let local_user_id = local_user_view.local_user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let default_listing_type = data.default_listing_type;
|
||||
let default_sort_type = data.default_sort_type;
|
||||
let password_encrypted = local_user_view.local_user.password_encrypted;
|
||||
let public_key = local_user_view.person.public_key;
|
||||
|
||||
let person_form = PersonForm {
|
||||
name: local_user_view.person.name,
|
||||
avatar,
|
||||
banner,
|
||||
inbox_url: None,
|
||||
display_name,
|
||||
published: None,
|
||||
updated: Some(naive_now()),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
actor_id: None,
|
||||
bio,
|
||||
local: None,
|
||||
admin: None,
|
||||
private_key: None,
|
||||
public_key,
|
||||
last_refreshed_at: None,
|
||||
shared_inbox_url: None,
|
||||
matrix_user_id,
|
||||
bot_account,
|
||||
ban_expires: None,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
Person::update(conn, person_id, &person_form)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?;
|
||||
|
||||
let local_user_form = LocalUserForm {
|
||||
person_id: Some(person_id),
|
||||
email,
|
||||
password_encrypted: Some(password_encrypted),
|
||||
show_nsfw: data.show_nsfw,
|
||||
show_bot_accounts: data.show_bot_accounts,
|
||||
show_scores: data.show_scores,
|
||||
theme: data.theme.to_owned(),
|
||||
default_sort_type,
|
||||
default_listing_type,
|
||||
lang: data.lang.to_owned(),
|
||||
show_avatars: data.show_avatars,
|
||||
show_read_posts: data.show_read_posts,
|
||||
show_new_post_notifs: data.show_new_post_notifs,
|
||||
send_notifications_to_email: data.send_notifications_to_email,
|
||||
email_verified: None,
|
||||
accepted_application: None,
|
||||
};
|
||||
|
||||
let local_user_res = blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, local_user_id, &local_user_form)
|
||||
})
|
||||
.await?;
|
||||
let updated_local_user = match local_user_res {
|
||||
Ok(u) => u,
|
||||
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"
|
||||
};
|
||||
|
||||
return Err(LemmyError::from_error_message(e, err_type));
|
||||
}
|
||||
};
|
||||
|
||||
// Return the jwt
|
||||
Ok(LoginResponse {
|
||||
jwt: Some(
|
||||
Claims::jwt(
|
||||
updated_local_user.id.0,
|
||||
&context.secret().jwt_secret,
|
||||
&context.settings().hostname,
|
||||
)?
|
||||
.into(),
|
||||
),
|
||||
verify_email_sent: false,
|
||||
registration_created: false,
|
||||
})
|
||||
}
|
||||
}
|
62
crates/api/src/local_user/verify_email.rs
Normal file
62
crates/api/src/local_user/verify_email.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
person::{VerifyEmail, VerifyEmailResponse},
|
||||
send_email_verification_success,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
email_verification::EmailVerification,
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::local_user_view::LocalUserView;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for VerifyEmail {
|
||||
type Response = VerifyEmailResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<usize>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let token = self.token.clone();
|
||||
let verification = blocking(context.pool(), move |conn| {
|
||||
EmailVerification::read_for_token(conn, &token)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "token_not_found"))?;
|
||||
|
||||
let form = LocalUserForm {
|
||||
// necessary in case this is a new signup
|
||||
email_verified: Some(true),
|
||||
// necessary in case email of an existing user was changed
|
||||
email: Some(Some(verification.email)),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
let local_user_id = verification.local_user_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, local_user_id, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read(conn, local_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
send_email_verification_success(&local_user_view, &context.settings())?;
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(VerifyEmailResponse {})
|
||||
}
|
||||
}
|
|
@ -1,361 +0,0 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
check_downvotes_enabled,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
mark_post_as_read,
|
||||
mark_post_as_unread,
|
||||
post::*,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
fetcher::post_or_comment::PostOrComment,
|
||||
objects::post::ApubPost,
|
||||
protocol::activities::{
|
||||
create_or_update::post::CreateOrUpdatePost,
|
||||
voting::{
|
||||
undo_vote::UndoVote,
|
||||
vote::{Vote, VoteType},
|
||||
},
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{moderator::*, post::*},
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_utils::{request::fetch_site_metadata, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreatePostLike {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &CreatePostLike = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, context.pool()).await?;
|
||||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, post_id))
|
||||
.await??
|
||||
.into();
|
||||
|
||||
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
||||
check_community_deleted_or_removed(post.community_id, context.pool()).await?;
|
||||
|
||||
let like_form = PostLikeForm {
|
||||
post_id: data.post_id,
|
||||
person_id: local_user_view.person.id,
|
||||
score: data.score,
|
||||
};
|
||||
|
||||
// Remove any likes first
|
||||
let person_id = local_user_view.person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, person_id, post_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let object = PostOrComment::Post(Box::new(post));
|
||||
|
||||
// Only add the like if the score isnt 0
|
||||
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
|
||||
if do_add {
|
||||
let like_form2 = like_form.clone();
|
||||
let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
|
||||
blocking(context.pool(), like)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_like_post"))?;
|
||||
|
||||
Vote::send(
|
||||
&object,
|
||||
&local_user_view.person.clone().into(),
|
||||
community_id,
|
||||
like_form.score.try_into()?,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
// API doesn't distinguish between Undo/Like and Undo/Dislike
|
||||
UndoVote::send(
|
||||
&object,
|
||||
&local_user_view.person.clone().into(),
|
||||
community_id,
|
||||
VoteType::Like,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
|
||||
send_post_ws_message(
|
||||
data.post_id,
|
||||
UserOperation::CreatePostLike,
|
||||
websocket_id,
|
||||
Some(local_user_view.person.id),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkPostAsRead {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Mark the post as read / unread
|
||||
if data.read {
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
} else {
|
||||
mark_post_as_unread(person_id, post_id, context.pool()).await?;
|
||||
}
|
||||
|
||||
// Fetch it
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response { post_view };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for LockPost {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &LockPost = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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?;
|
||||
check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
|
||||
|
||||
// Verify that only the mods can lock
|
||||
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 locked = data.locked;
|
||||
let updated_post: ApubPost = blocking(context.pool(), move |conn| {
|
||||
Post::update_locked(conn, post_id, locked)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
// Mod tables
|
||||
let form = ModLockPostForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
locked: Some(locked),
|
||||
};
|
||||
blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
|
||||
|
||||
// apub updates
|
||||
CreateOrUpdatePost::send(
|
||||
updated_post,
|
||||
&local_user_view.person.clone().into(),
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_post_ws_message(
|
||||
data.post_id,
|
||||
UserOperation::LockPost,
|
||||
websocket_id,
|
||||
Some(local_user_view.person.id),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for StickyPost {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &StickyPost = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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?;
|
||||
check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
|
||||
|
||||
// Verify that only the mods can sticky
|
||||
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 stickied = data.stickied;
|
||||
let updated_post: ApubPost = blocking(context.pool(), move |conn| {
|
||||
Post::update_stickied(conn, post_id, stickied)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
// Mod tables
|
||||
let form = ModStickyPostForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
stickied: Some(stickied),
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModStickyPost::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Apub updates
|
||||
// TODO stickied should pry work like locked for ease of use
|
||||
CreateOrUpdatePost::send(
|
||||
updated_post,
|
||||
&local_user_view.person.clone().into(),
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_post_ws_message(
|
||||
data.post_id,
|
||||
UserOperation::StickyPost,
|
||||
websocket_id,
|
||||
Some(local_user_view.person.id),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SavePost {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &SavePost = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let post_saved_form = PostSavedForm {
|
||||
post_id: data.post_id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
if data.save {
|
||||
let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
|
||||
blocking(context.pool(), save)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_post"))?;
|
||||
} else {
|
||||
let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
|
||||
blocking(context.pool(), unsave)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_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??;
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
|
||||
Ok(PostResponse { post_view })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetSiteMetadata {
|
||||
type Response = GetSiteMetadataResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteMetadataResponse, LemmyError> {
|
||||
let data: &Self = self;
|
||||
|
||||
let metadata = fetch_site_metadata(context.client(), &data.url).await?;
|
||||
|
||||
Ok(GetSiteMetadataResponse { metadata })
|
||||
}
|
||||
}
|
23
crates/api/src/post/get_link_metadata.rs
Normal file
23
crates/api/src/post/get_link_metadata.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::post::{GetSiteMetadata, GetSiteMetadataResponse};
|
||||
use lemmy_utils::{request::fetch_site_metadata, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetSiteMetadata {
|
||||
type Response = GetSiteMetadataResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteMetadataResponse, LemmyError> {
|
||||
let data: &Self = self;
|
||||
|
||||
let metadata = fetch_site_metadata(context.client(), &data.url).await?;
|
||||
|
||||
Ok(GetSiteMetadataResponse { metadata })
|
||||
}
|
||||
}
|
110
crates/api/src/post/like.rs
Normal file
110
crates/api/src/post/like.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
check_downvotes_enabled,
|
||||
get_local_user_view_from_jwt,
|
||||
mark_post_as_read,
|
||||
post::{CreatePostLike, PostResponse},
|
||||
};
|
||||
use lemmy_apub::{
|
||||
fetcher::post_or_comment::PostOrComment,
|
||||
objects::post::ApubPost,
|
||||
protocol::activities::voting::{
|
||||
undo_vote::UndoVote,
|
||||
vote::{Vote, VoteType},
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::post::{Post, PostLike, PostLikeForm},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreatePostLike {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &CreatePostLike = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, context.pool()).await?;
|
||||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, post_id))
|
||||
.await??
|
||||
.into();
|
||||
|
||||
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
||||
check_community_deleted_or_removed(post.community_id, context.pool()).await?;
|
||||
|
||||
let like_form = PostLikeForm {
|
||||
post_id: data.post_id,
|
||||
person_id: local_user_view.person.id,
|
||||
score: data.score,
|
||||
};
|
||||
|
||||
// Remove any likes first
|
||||
let person_id = local_user_view.person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, person_id, post_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let object = PostOrComment::Post(Box::new(post));
|
||||
|
||||
// Only add the like if the score isnt 0
|
||||
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
|
||||
if do_add {
|
||||
let like_form2 = like_form.clone();
|
||||
let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
|
||||
blocking(context.pool(), like)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_like_post"))?;
|
||||
|
||||
Vote::send(
|
||||
&object,
|
||||
&local_user_view.person.clone().into(),
|
||||
community_id,
|
||||
like_form.score.try_into()?,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
// API doesn't distinguish between Undo/Like and Undo/Dislike
|
||||
UndoVote::send(
|
||||
&object,
|
||||
&local_user_view.person.clone().into(),
|
||||
community_id,
|
||||
VoteType::Like,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
|
||||
send_post_ws_message(
|
||||
data.post_id,
|
||||
UserOperation::CreatePostLike,
|
||||
websocket_id,
|
||||
Some(local_user_view.person.id),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
93
crates/api/src/post/lock.rs
Normal file
93
crates/api/src/post/lock.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
post::{LockPost, PostResponse},
|
||||
};
|
||||
use lemmy_apub::{
|
||||
objects::post::ApubPost,
|
||||
protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{ModLockPost, ModLockPostForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for LockPost {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &LockPost = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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?;
|
||||
check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
|
||||
|
||||
// Verify that only the mods can lock
|
||||
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 locked = data.locked;
|
||||
let updated_post: ApubPost = blocking(context.pool(), move |conn| {
|
||||
Post::update_locked(conn, post_id, locked)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
// Mod tables
|
||||
let form = ModLockPostForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
locked: Some(locked),
|
||||
};
|
||||
blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
|
||||
|
||||
// apub updates
|
||||
CreateOrUpdatePost::send(
|
||||
updated_post,
|
||||
&local_user_view.person.clone().into(),
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_post_ws_message(
|
||||
data.post_id,
|
||||
UserOperation::LockPost,
|
||||
websocket_id,
|
||||
Some(local_user_view.person.id),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
48
crates/api/src/post/mark_read.rs
Normal file
48
crates/api/src/post/mark_read.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
mark_post_as_read,
|
||||
mark_post_as_unread,
|
||||
post::{MarkPostAsRead, PostResponse},
|
||||
};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkPostAsRead {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Mark the post as read / unread
|
||||
if data.read {
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
} else {
|
||||
mark_post_as_unread(person_id, post_id, context.pool()).await?;
|
||||
}
|
||||
|
||||
// Fetch it
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response { post_view };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
6
crates/api/src/post/mod.rs
Normal file
6
crates/api/src/post/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod get_link_metadata;
|
||||
mod like;
|
||||
mod lock;
|
||||
mod mark_read;
|
||||
mod save;
|
||||
mod sticky;
|
60
crates/api/src/post/save.rs
Normal file
60
crates/api/src/post/save.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
mark_post_as_read,
|
||||
post::{PostResponse, SavePost},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::post::{PostSaved, PostSavedForm},
|
||||
traits::Saveable,
|
||||
};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SavePost {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &SavePost = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let post_saved_form = PostSavedForm {
|
||||
post_id: data.post_id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
if data.save {
|
||||
let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
|
||||
blocking(context.pool(), save)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_post"))?;
|
||||
} else {
|
||||
let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
|
||||
blocking(context.pool(), unsave)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_save_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??;
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
|
||||
Ok(PostResponse { post_view })
|
||||
}
|
||||
}
|
97
crates/api/src/post/sticky.rs
Normal file
97
crates/api/src/post/sticky.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
post::{PostResponse, StickyPost},
|
||||
};
|
||||
use lemmy_apub::{
|
||||
objects::post::ApubPost,
|
||||
protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{ModStickyPost, ModStickyPostForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for StickyPost {
|
||||
type Response = PostResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &StickyPost = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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?;
|
||||
check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
|
||||
|
||||
// Verify that only the mods can sticky
|
||||
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 stickied = data.stickied;
|
||||
let updated_post: ApubPost = blocking(context.pool(), move |conn| {
|
||||
Post::update_stickied(conn, post_id, stickied)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
// Mod tables
|
||||
let form = ModStickyPostForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
stickied: Some(stickied),
|
||||
};
|
||||
blocking(context.pool(), move |conn| {
|
||||
ModStickyPost::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Apub updates
|
||||
// TODO stickied should pry work like locked for ease of use
|
||||
CreateOrUpdatePost::send(
|
||||
updated_post,
|
||||
&local_user_view.person.clone().into(),
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_post_ws_message(
|
||||
data.post_id,
|
||||
UserOperation::StickyPost,
|
||||
websocket_id,
|
||||
Some(local_user_view.person.id),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
post::{
|
||||
CreatePostReport,
|
||||
ListPostReports,
|
||||
ListPostReportsResponse,
|
||||
PostReportResponse,
|
||||
ResolvePostReport,
|
||||
},
|
||||
};
|
||||
use lemmy_apub::protocol::activities::community::report::Report;
|
||||
use lemmy_apub_lib::object_id::ObjectId;
|
||||
use lemmy_db_schema::{
|
||||
source::post_report::{PostReport, PostReportForm},
|
||||
traits::Reportable,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
post_report_view::{PostReportQueryBuilder, PostReportView},
|
||||
post_view::PostView,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
/// Creates a post report and notifies the moderators of the community
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreatePostReport {
|
||||
type Response = PostReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostReportResponse, LemmyError> {
|
||||
let data: &CreatePostReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// check size of report and check for whitespace
|
||||
let reason = data.reason.trim();
|
||||
if reason.is_empty() {
|
||||
return Err(LemmyError::from_message("report_reason_required"));
|
||||
}
|
||||
if reason.chars().count() > 1000 {
|
||||
return Err(LemmyError::from_message("report_too_long"));
|
||||
}
|
||||
|
||||
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 = blocking(context.pool(), move |conn| {
|
||||
PostReport::report(conn, &report_form)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?;
|
||||
|
||||
let post_report_view = blocking(context.pool(), move |conn| {
|
||||
PostReportView::read(conn, report.id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = PostReportResponse { post_report_view };
|
||||
|
||||
context.chat_server().do_send(SendModRoomMessage {
|
||||
op: UserOperation::CreatePostReport,
|
||||
response: res.clone(),
|
||||
community_id: post_view.community.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Report::send(
|
||||
ObjectId::new(post_view.post.ap_id),
|
||||
&local_user_view.person.into(),
|
||||
ObjectId::new(post_view.community.actor_id),
|
||||
reason.to_string(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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 = PostReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostReportResponse, LemmyError> {
|
||||
let data: &ResolvePostReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let report = blocking(context.pool(), move |conn| {
|
||||
PostReportView::read(conn, report_id, person_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)
|
||||
}
|
||||
};
|
||||
|
||||
blocking(context.pool(), resolve_fun)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?;
|
||||
|
||||
let post_report_view = blocking(context.pool(), move |conn| {
|
||||
PostReportView::read(conn, report_id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = PostReportResponse { post_report_view };
|
||||
|
||||
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;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
let unresolved_only = data.unresolved_only;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let post_reports = blocking(context.pool(), move |conn| {
|
||||
PostReportQueryBuilder::create(conn, person_id, admin)
|
||||
.community_id(community_id)
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = ListPostReportsResponse { post_reports };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
92
crates/api/src/post_report/create.rs
Normal file
92
crates/api/src/post_report/create.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_community_ban,
|
||||
get_local_user_view_from_jwt,
|
||||
post::{CreatePostReport, PostReportResponse},
|
||||
};
|
||||
use lemmy_apub::protocol::activities::community::report::Report;
|
||||
use lemmy_apub_lib::object_id::ObjectId;
|
||||
use lemmy_db_schema::{
|
||||
source::post_report::{PostReport, PostReportForm},
|
||||
traits::Reportable,
|
||||
};
|
||||
use lemmy_db_views::{post_report_view::PostReportView, post_view::PostView};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
/// Creates a post report and notifies the moderators of the community
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreatePostReport {
|
||||
type Response = PostReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostReportResponse, LemmyError> {
|
||||
let data: &CreatePostReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// check size of report and check for whitespace
|
||||
let reason = data.reason.trim();
|
||||
if reason.is_empty() {
|
||||
return Err(LemmyError::from_message("report_reason_required"));
|
||||
}
|
||||
if reason.chars().count() > 1000 {
|
||||
return Err(LemmyError::from_message("report_too_long"));
|
||||
}
|
||||
|
||||
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 = blocking(context.pool(), move |conn| {
|
||||
PostReport::report(conn, &report_form)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?;
|
||||
|
||||
let post_report_view = blocking(context.pool(), move |conn| {
|
||||
PostReportView::read(conn, report.id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = PostReportResponse { post_report_view };
|
||||
|
||||
context.chat_server().do_send(SendModRoomMessage {
|
||||
op: UserOperation::CreatePostReport,
|
||||
response: res.clone(),
|
||||
community_id: post_view.community.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Report::send(
|
||||
ObjectId::new(post_view.post.ap_id),
|
||||
&local_user_view.person.into(),
|
||||
ObjectId::new(post_view.community.actor_id),
|
||||
reason.to_string(),
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
49
crates/api/src/post_report/list.rs
Normal file
49
crates/api/src/post_report/list.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
post::{ListPostReports, ListPostReportsResponse},
|
||||
};
|
||||
use lemmy_db_views::post_report_view::PostReportQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
/// 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;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
let unresolved_only = data.unresolved_only;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let post_reports = blocking(context.pool(), move |conn| {
|
||||
PostReportQueryBuilder::create(conn, person_id, admin)
|
||||
.community_id(community_id)
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = ListPostReportsResponse { post_reports };
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
3
crates/api/src/post_report/mod.rs
Normal file
3
crates/api/src/post_report/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod create;
|
||||
mod list;
|
||||
mod resolve;
|
68
crates/api/src/post_report/resolve.rs
Normal file
68
crates/api/src/post_report/resolve.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_mod_or_admin,
|
||||
post::{PostReportResponse, ResolvePostReport},
|
||||
};
|
||||
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
|
||||
use lemmy_db_views::post_report_view::PostReportView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||
|
||||
/// Resolves or unresolves a post report and notifies the moderators of the community
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ResolvePostReport {
|
||||
type Response = PostReportResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostReportResponse, LemmyError> {
|
||||
let data: &ResolvePostReport = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let report = blocking(context.pool(), move |conn| {
|
||||
PostReportView::read(conn, report_id, person_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)
|
||||
}
|
||||
};
|
||||
|
||||
blocking(context.pool(), resolve_fun)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?;
|
||||
|
||||
let post_report_view = blocking(context.pool(), move |conn| {
|
||||
PostReportView::read(conn, report_id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = PostReportResponse { post_report_view };
|
||||
|
||||
context.chat_server().do_send(SendModRoomMessage {
|
||||
op: UserOperation::ResolvePostReport,
|
||||
response: res.clone(),
|
||||
community_id: report.community.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
1
crates/api/src/private_message/mod.rs
Normal file
1
crates/api/src/private_message/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod mark_read;
|
|
@ -1,710 +0,0 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use diesel::NotFound;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
build_federated_instances,
|
||||
check_private_instance,
|
||||
get_local_user_view_from_jwt,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
is_admin,
|
||||
send_application_approved_email,
|
||||
site::*,
|
||||
};
|
||||
use lemmy_apub::{
|
||||
fetcher::{
|
||||
resolve_actor_identifier,
|
||||
search::{search_by_apub_id, SearchableObjects},
|
||||
},
|
||||
objects::community::ApubCommunity,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
diesel_option_overwrite,
|
||||
from_opt_str_to_opt_enum,
|
||||
newtypes::PersonId,
|
||||
source::{
|
||||
community::Community,
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
moderator::*,
|
||||
person::Person,
|
||||
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||
site::Site,
|
||||
},
|
||||
traits::{Crud, DeleteableOrRemoveable},
|
||||
DbPool,
|
||||
ListingType,
|
||||
SearchType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
comment_view::{CommentQueryBuilder, CommentView},
|
||||
local_user_view::LocalUserView,
|
||||
post_view::{PostQueryBuilder, PostView},
|
||||
registration_application_view::{
|
||||
RegistrationApplicationQueryBuilder,
|
||||
RegistrationApplicationView,
|
||||
},
|
||||
site_view::SiteView,
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
community_view::{CommunityQueryBuilder, CommunityView},
|
||||
person_view::{PersonQueryBuilder, PersonViewSafe},
|
||||
};
|
||||
use lemmy_db_views_moderator::{
|
||||
mod_add_community_view::ModAddCommunityView,
|
||||
mod_add_view::ModAddView,
|
||||
mod_ban_from_community_view::ModBanFromCommunityView,
|
||||
mod_ban_view::ModBanView,
|
||||
mod_hide_community_view::ModHideCommunityView,
|
||||
mod_lock_post_view::ModLockPostView,
|
||||
mod_remove_comment_view::ModRemoveCommentView,
|
||||
mod_remove_community_view::ModRemoveCommunityView,
|
||||
mod_remove_post_view::ModRemovePostView,
|
||||
mod_sticky_post_view::ModStickyPostView,
|
||||
mod_transfer_community_view::ModTransferCommunityView,
|
||||
};
|
||||
use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetModlog {
|
||||
type Response = GetModlogResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetModlogResponse, LemmyError> {
|
||||
let data: &GetModlog = self;
|
||||
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let mod_person_id = data.mod_person_id;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let removed_posts = blocking(context.pool(), move |conn| {
|
||||
ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let locked_posts = blocking(context.pool(), move |conn| {
|
||||
ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let stickied_posts = blocking(context.pool(), move |conn| {
|
||||
ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let removed_comments = blocking(context.pool(), move |conn| {
|
||||
ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let banned_from_community = blocking(context.pool(), move |conn| {
|
||||
ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let added_to_community = blocking(context.pool(), move |conn| {
|
||||
ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let transferred_to_community = blocking(context.pool(), move |conn| {
|
||||
ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let hidden_communities = blocking(context.pool(), move |conn| {
|
||||
ModHideCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// These arrays are only for the full modlog, when a community isn't given
|
||||
let (removed_communities, banned, added) = if data.community_id.is_none() {
|
||||
blocking(context.pool(), move |conn| {
|
||||
Ok((
|
||||
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
|
||||
ModBanView::list(conn, mod_person_id, page, limit)?,
|
||||
ModAddView::list(conn, mod_person_id, page, limit)?,
|
||||
)) as Result<_, LemmyError>
|
||||
})
|
||||
.await??
|
||||
} else {
|
||||
(Vec::new(), Vec::new(), Vec::new())
|
||||
};
|
||||
|
||||
// Return the jwt
|
||||
Ok(GetModlogResponse {
|
||||
removed_posts,
|
||||
locked_posts,
|
||||
stickied_posts,
|
||||
removed_comments,
|
||||
removed_communities,
|
||||
banned_from_community,
|
||||
banned,
|
||||
added_to_community,
|
||||
added,
|
||||
transferred_to_community,
|
||||
hidden_communities,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for Search {
|
||||
type Response = SearchResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<SearchResponse, LemmyError> {
|
||||
let data: &Search = self;
|
||||
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let show_read_posts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_read_posts);
|
||||
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let mut posts = Vec::new();
|
||||
let mut comments = Vec::new();
|
||||
let mut communities = Vec::new();
|
||||
let mut users = Vec::new();
|
||||
|
||||
// TODO no clean / non-nsfw searching rn
|
||||
|
||||
let q = data.q.to_owned();
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
|
||||
let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
|
||||
let community_id = data.community_id;
|
||||
let community_actor_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let creator_id = data.creator_id;
|
||||
match search_type {
|
||||
SearchType::Posts => {
|
||||
posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::Comments => {
|
||||
comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::Communities => {
|
||||
communities = blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::Users => {
|
||||
users = blocking(context.pool(), move |conn| {
|
||||
PersonQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::All => {
|
||||
// If the community or creator is included, dont search communities or users
|
||||
let community_or_creator_included =
|
||||
data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
|
||||
let community_actor_id_2 = community_actor_id.to_owned();
|
||||
|
||||
posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id_2)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let q = data.q.to_owned();
|
||||
let community_actor_id = community_actor_id.to_owned();
|
||||
|
||||
comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let q = data.q.to_owned();
|
||||
|
||||
communities = if community_or_creator_included {
|
||||
vec![]
|
||||
} else {
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??
|
||||
};
|
||||
|
||||
let q = data.q.to_owned();
|
||||
|
||||
users = if community_or_creator_included {
|
||||
vec![]
|
||||
} else {
|
||||
blocking(context.pool(), move |conn| {
|
||||
PersonQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??
|
||||
};
|
||||
}
|
||||
SearchType::Url => {
|
||||
posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.listing_type(listing_type)
|
||||
.my_person_id(person_id)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.url_search(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
};
|
||||
|
||||
// Blank out deleted or removed info for non logged in users
|
||||
if person_id.is_none() {
|
||||
for cv in communities
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.community.deleted || cv.community.removed)
|
||||
{
|
||||
cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.post.deleted || p.post.removed)
|
||||
{
|
||||
pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
Ok(SearchResponse {
|
||||
type_: search_type.to_string(),
|
||||
comments,
|
||||
posts,
|
||||
communities,
|
||||
users,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ResolveObject {
|
||||
type Response = ResolveObjectResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ResolveObjectResponse, LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let res = search_by_apub_id(&self.q, context)
|
||||
.await
|
||||
.map_err(|e| e.with_message("couldnt_find_object"))?;
|
||||
convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
|
||||
.await
|
||||
.map_err(|e| e.with_message("couldnt_find_object"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
object: SearchableObjects,
|
||||
user_id: Option<PersonId>,
|
||||
pool: &DbPool,
|
||||
) -> Result<ResolveObjectResponse, LemmyError> {
|
||||
let removed_or_deleted;
|
||||
let mut res = ResolveObjectResponse {
|
||||
comment: None,
|
||||
post: None,
|
||||
community: None,
|
||||
person: None,
|
||||
};
|
||||
use SearchableObjects::*;
|
||||
match object {
|
||||
Person(p) => {
|
||||
removed_or_deleted = p.deleted;
|
||||
res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
|
||||
}
|
||||
Community(c) => {
|
||||
removed_or_deleted = c.deleted || c.removed;
|
||||
res.community =
|
||||
Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
|
||||
}
|
||||
Post(p) => {
|
||||
removed_or_deleted = p.deleted || p.removed;
|
||||
res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
|
||||
}
|
||||
Comment(c) => {
|
||||
removed_or_deleted = c.deleted || c.removed;
|
||||
res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
|
||||
}
|
||||
};
|
||||
// if the object was deleted from database, dont return it
|
||||
if removed_or_deleted {
|
||||
return Err(NotFound {}.into());
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for LeaveAdmin {
|
||||
type Response = GetSiteResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteResponse, LemmyError> {
|
||||
let data: &LeaveAdmin = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
if admins.len() == 1 {
|
||||
return Err(LemmyError::from_message("cannot_leave_admin"));
|
||||
}
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
Person::leave_admin(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddForm {
|
||||
mod_person_id: person_id,
|
||||
other_person_id: person_id,
|
||||
removed: Some(true),
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
|
||||
|
||||
// Reread site and admins
|
||||
let site_view = blocking(context.pool(), SiteView::read_local).await??;
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
|
||||
let federated_instances =
|
||||
build_federated_instances(context.pool(), &context.settings()).await?;
|
||||
|
||||
Ok(GetSiteResponse {
|
||||
site_view: Some(site_view),
|
||||
admins,
|
||||
online: 0,
|
||||
version: version::VERSION.to_string(),
|
||||
my_user: None,
|
||||
federated_instances,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetSiteConfig {
|
||||
type Response = GetSiteConfigResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteConfigResponse, LemmyError> {
|
||||
let data: &GetSiteConfig = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins read this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let config_hjson = Settings::read_config_file()?;
|
||||
|
||||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SaveSiteConfig {
|
||||
type Response = GetSiteConfigResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteConfigResponse, LemmyError> {
|
||||
let data: &SaveSiteConfig = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins read this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
|
||||
let config_hjson = Settings::save_config_file(&data.config_hjson)
|
||||
.map_err(|e| e.with_message("couldnt_update_site"))?;
|
||||
|
||||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists registration applications, filterable by undenied only.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ListRegistrationApplications {
|
||||
type Response = ListRegistrationApplicationsResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let unread_only = data.unread_only;
|
||||
let verified_email_only = blocking(context.pool(), Site::read_local_site)
|
||||
.await??
|
||||
.require_email_verification;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let registration_applications = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationQueryBuilder::create(conn)
|
||||
.unread_only(unread_only)
|
||||
.verified_email_only(verified_email_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response {
|
||||
registration_applications,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ApproveRegistrationApplication {
|
||||
type Response = RegistrationApplicationResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let app_id = data.id;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Update the registration with reason, admin_id
|
||||
let deny_reason = diesel_option_overwrite(&data.deny_reason);
|
||||
let app_form = RegistrationApplicationForm {
|
||||
admin_id: Some(local_user_view.person.id),
|
||||
deny_reason,
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
let registration_application = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplication::update(conn, app_id, &app_form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Update the local_user row
|
||||
let local_user_form = LocalUserForm {
|
||||
accepted_application: Some(data.approve),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
let approved_user_id = registration_application.local_user_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, approved_user_id, &local_user_form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if data.approve {
|
||||
let approved_local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read(conn, approved_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if approved_local_user_view.local_user.email.is_some() {
|
||||
send_application_approved_email(&approved_local_user_view, &context.settings())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the view
|
||||
let registration_application = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationView::read(conn, app_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_application,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetUnreadRegistrationApplicationCount {
|
||||
type Response = GetUnreadRegistrationApplicationCountResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let verified_email_only = blocking(context.pool(), Site::read_local_site)
|
||||
.await??
|
||||
.require_email_verification;
|
||||
|
||||
let registration_applications = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationView::get_unread_count(conn, verified_email_only)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_applications,
|
||||
})
|
||||
}
|
||||
}
|
2
crates/api/src/site/config/mod.rs
Normal file
2
crates/api/src/site/config/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod read;
|
||||
mod update;
|
32
crates/api/src/site/config/read.rs
Normal file
32
crates/api/src/site/config/read.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
site::{GetSiteConfig, GetSiteConfigResponse},
|
||||
};
|
||||
use lemmy_utils::{settings::structs::Settings, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetSiteConfig {
|
||||
type Response = GetSiteConfigResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteConfigResponse, LemmyError> {
|
||||
let data: &GetSiteConfig = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins read this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let config_hjson = Settings::read_config_file()?;
|
||||
|
||||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
34
crates/api/src/site/config/update.rs
Normal file
34
crates/api/src/site/config/update.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
site::{GetSiteConfigResponse, SaveSiteConfig},
|
||||
};
|
||||
use lemmy_utils::{settings::structs::Settings, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for SaveSiteConfig {
|
||||
type Response = GetSiteConfigResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteConfigResponse, LemmyError> {
|
||||
let data: &SaveSiteConfig = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins read this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
|
||||
let config_hjson = Settings::save_config_file(&data.config_hjson)
|
||||
.map_err(|e| e.with_message("couldnt_update_site"))?;
|
||||
|
||||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
75
crates/api/src/site/leave_admin.rs
Normal file
75
crates/api/src/site/leave_admin.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
build_federated_instances,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
site::{GetSiteResponse, LeaveAdmin},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{ModAdd, ModAddForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::site_view::SiteView;
|
||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||
use lemmy_utils::{version, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for LeaveAdmin {
|
||||
type Response = GetSiteResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteResponse, LemmyError> {
|
||||
let data: &LeaveAdmin = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
if admins.len() == 1 {
|
||||
return Err(LemmyError::from_message("cannot_leave_admin"));
|
||||
}
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
Person::leave_admin(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddForm {
|
||||
mod_person_id: person_id,
|
||||
other_person_id: person_id,
|
||||
removed: Some(true),
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
|
||||
|
||||
// Reread site and admins
|
||||
let site_view = blocking(context.pool(), SiteView::read_local).await??;
|
||||
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
|
||||
|
||||
let federated_instances =
|
||||
build_federated_instances(context.pool(), &context.settings()).await?;
|
||||
|
||||
Ok(GetSiteResponse {
|
||||
site_view: Some(site_view),
|
||||
admins,
|
||||
online: 0,
|
||||
version: version::VERSION.to_string(),
|
||||
my_user: None,
|
||||
federated_instances,
|
||||
})
|
||||
}
|
||||
}
|
6
crates/api/src/site/mod.rs
Normal file
6
crates/api/src/site/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod config;
|
||||
mod leave_admin;
|
||||
mod mod_log;
|
||||
mod registration_applications;
|
||||
mod resolve_object;
|
||||
mod search;
|
116
crates/api/src/site/mod_log.rs
Normal file
116
crates/api/src/site/mod_log.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_private_instance,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
site::{GetModlog, GetModlogResponse},
|
||||
};
|
||||
use lemmy_db_views_moderator::{
|
||||
mod_add_community_view::ModAddCommunityView,
|
||||
mod_add_view::ModAddView,
|
||||
mod_ban_from_community_view::ModBanFromCommunityView,
|
||||
mod_ban_view::ModBanView,
|
||||
mod_hide_community_view::ModHideCommunityView,
|
||||
mod_lock_post_view::ModLockPostView,
|
||||
mod_remove_comment_view::ModRemoveCommentView,
|
||||
mod_remove_community_view::ModRemoveCommunityView,
|
||||
mod_remove_post_view::ModRemovePostView,
|
||||
mod_sticky_post_view::ModStickyPostView,
|
||||
mod_transfer_community_view::ModTransferCommunityView,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetModlog {
|
||||
type Response = GetModlogResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetModlogResponse, LemmyError> {
|
||||
let data: &GetModlog = self;
|
||||
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let mod_person_id = data.mod_person_id;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let removed_posts = blocking(context.pool(), move |conn| {
|
||||
ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let locked_posts = blocking(context.pool(), move |conn| {
|
||||
ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let stickied_posts = blocking(context.pool(), move |conn| {
|
||||
ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let removed_comments = blocking(context.pool(), move |conn| {
|
||||
ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let banned_from_community = blocking(context.pool(), move |conn| {
|
||||
ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let added_to_community = blocking(context.pool(), move |conn| {
|
||||
ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let transferred_to_community = blocking(context.pool(), move |conn| {
|
||||
ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let hidden_communities = blocking(context.pool(), move |conn| {
|
||||
ModHideCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// These arrays are only for the full modlog, when a community isn't given
|
||||
let (removed_communities, banned, added) = if data.community_id.is_none() {
|
||||
blocking(context.pool(), move |conn| {
|
||||
Ok((
|
||||
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
|
||||
ModBanView::list(conn, mod_person_id, page, limit)?,
|
||||
ModAddView::list(conn, mod_person_id, page, limit)?,
|
||||
)) as Result<_, LemmyError>
|
||||
})
|
||||
.await??
|
||||
} else {
|
||||
(Vec::new(), Vec::new(), Vec::new())
|
||||
};
|
||||
|
||||
// Return the jwt
|
||||
Ok(GetModlogResponse {
|
||||
removed_posts,
|
||||
locked_posts,
|
||||
stickied_posts,
|
||||
removed_comments,
|
||||
removed_communities,
|
||||
banned_from_community,
|
||||
banned,
|
||||
added_to_community,
|
||||
added,
|
||||
transferred_to_community,
|
||||
hidden_communities,
|
||||
})
|
||||
}
|
||||
}
|
89
crates/api/src/site/registration_applications/approve.rs
Normal file
89
crates/api/src/site/registration_applications/approve.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
send_application_approved_email,
|
||||
site::*,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
diesel_option_overwrite,
|
||||
source::{
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
local_user_view::LocalUserView,
|
||||
registration_application_view::RegistrationApplicationView,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ApproveRegistrationApplication {
|
||||
type Response = RegistrationApplicationResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let app_id = data.id;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Update the registration with reason, admin_id
|
||||
let deny_reason = diesel_option_overwrite(&data.deny_reason);
|
||||
let app_form = RegistrationApplicationForm {
|
||||
admin_id: Some(local_user_view.person.id),
|
||||
deny_reason,
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
let registration_application = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplication::update(conn, app_id, &app_form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Update the local_user row
|
||||
let local_user_form = LocalUserForm {
|
||||
accepted_application: Some(data.approve),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
let approved_user_id = registration_application.local_user_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, approved_user_id, &local_user_form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if data.approve {
|
||||
let approved_local_user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read(conn, approved_user_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if approved_local_user_view.local_user.email.is_some() {
|
||||
send_application_approved_email(&approved_local_user_view, &context.settings())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the view
|
||||
let registration_application = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationView::read(conn, app_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_application,
|
||||
})
|
||||
}
|
||||
}
|
54
crates/api/src/site/registration_applications/list.rs
Normal file
54
crates/api/src/site/registration_applications/list.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
site::{ListRegistrationApplications, ListRegistrationApplicationsResponse},
|
||||
};
|
||||
use lemmy_db_schema::source::site::Site;
|
||||
use lemmy_db_views::registration_application_view::RegistrationApplicationQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
/// Lists registration applications, filterable by undenied only.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ListRegistrationApplications {
|
||||
type Response = ListRegistrationApplicationsResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let unread_only = data.unread_only;
|
||||
let verified_email_only = blocking(context.pool(), Site::read_local_site)
|
||||
.await??
|
||||
.require_email_verification;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let registration_applications = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationQueryBuilder::create(conn)
|
||||
.unread_only(unread_only)
|
||||
.verified_email_only(verified_email_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response {
|
||||
registration_applications,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
3
crates/api/src/site/registration_applications/mod.rs
Normal file
3
crates/api/src/site/registration_applications/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod approve;
|
||||
mod list;
|
||||
mod unread_count;
|
|
@ -0,0 +1,43 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
get_local_user_view_from_jwt,
|
||||
is_admin,
|
||||
site::{GetUnreadRegistrationApplicationCount, GetUnreadRegistrationApplicationCountResponse},
|
||||
};
|
||||
use lemmy_db_schema::source::site::Site;
|
||||
use lemmy_db_views::registration_application_view::RegistrationApplicationView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetUnreadRegistrationApplicationCount {
|
||||
type Response = GetUnreadRegistrationApplicationCountResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let verified_email_only = blocking(context.pool(), Site::read_local_site)
|
||||
.await??
|
||||
.require_email_verification;
|
||||
|
||||
let registration_applications = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationView::get_unread_count(conn, verified_email_only)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_applications,
|
||||
})
|
||||
}
|
||||
}
|
78
crates/api/src/site/resolve_object.rs
Normal file
78
crates/api/src/site/resolve_object.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use diesel::NotFound;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_private_instance,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
site::{ResolveObject, ResolveObjectResponse},
|
||||
};
|
||||
use lemmy_apub::fetcher::search::{search_by_apub_id, SearchableObjects};
|
||||
use lemmy_db_schema::{newtypes::PersonId, DbPool};
|
||||
use lemmy_db_views::{comment_view::CommentView, post_view::PostView};
|
||||
use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ResolveObject {
|
||||
type Response = ResolveObjectResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ResolveObjectResponse, LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let res = search_by_apub_id(&self.q, context)
|
||||
.await
|
||||
.map_err(|e| e.with_message("couldnt_find_object"))?;
|
||||
convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
|
||||
.await
|
||||
.map_err(|e| e.with_message("couldnt_find_object"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
object: SearchableObjects,
|
||||
user_id: Option<PersonId>,
|
||||
pool: &DbPool,
|
||||
) -> Result<ResolveObjectResponse, LemmyError> {
|
||||
let removed_or_deleted;
|
||||
let mut res = ResolveObjectResponse {
|
||||
comment: None,
|
||||
post: None,
|
||||
community: None,
|
||||
person: None,
|
||||
};
|
||||
use SearchableObjects::*;
|
||||
match object {
|
||||
Person(p) => {
|
||||
removed_or_deleted = p.deleted;
|
||||
res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
|
||||
}
|
||||
Community(c) => {
|
||||
removed_or_deleted = c.deleted || c.removed;
|
||||
res.community =
|
||||
Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
|
||||
}
|
||||
Post(p) => {
|
||||
removed_or_deleted = p.deleted || p.removed;
|
||||
res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
|
||||
}
|
||||
Comment(c) => {
|
||||
removed_or_deleted = c.deleted || c.removed;
|
||||
res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
|
||||
}
|
||||
};
|
||||
// if the object was deleted from database, dont return it
|
||||
if removed_or_deleted {
|
||||
return Err(NotFound {}.into());
|
||||
}
|
||||
Ok(res)
|
||||
}
|
269
crates/api/src/site/search.rs
Normal file
269
crates/api/src/site/search.rs
Normal file
|
@ -0,0 +1,269 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_private_instance,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
site::{Search, SearchResponse},
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
source::community::Community,
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SearchType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
|
||||
use lemmy_db_views_actor::{
|
||||
community_view::CommunityQueryBuilder,
|
||||
person_view::PersonQueryBuilder,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for Search {
|
||||
type Response = SearchResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<SearchResponse, LemmyError> {
|
||||
let data: &Search = self;
|
||||
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let show_read_posts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_read_posts);
|
||||
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let mut posts = Vec::new();
|
||||
let mut comments = Vec::new();
|
||||
let mut communities = Vec::new();
|
||||
let mut users = Vec::new();
|
||||
|
||||
// TODO no clean / non-nsfw searching rn
|
||||
|
||||
let q = data.q.to_owned();
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
|
||||
let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
|
||||
let community_id = data.community_id;
|
||||
let community_actor_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let creator_id = data.creator_id;
|
||||
match search_type {
|
||||
SearchType::Posts => {
|
||||
posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::Comments => {
|
||||
comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::Communities => {
|
||||
communities = blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::Users => {
|
||||
users = blocking(context.pool(), move |conn| {
|
||||
PersonQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
SearchType::All => {
|
||||
// If the community or creator is included, dont search communities or users
|
||||
let community_or_creator_included =
|
||||
data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
|
||||
let community_actor_id_2 = community_actor_id.to_owned();
|
||||
|
||||
posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id_2)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let q = data.q.to_owned();
|
||||
let community_actor_id = community_actor_id.to_owned();
|
||||
|
||||
comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let q = data.q.to_owned();
|
||||
|
||||
communities = if community_or_creator_included {
|
||||
vec![]
|
||||
} else {
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??
|
||||
};
|
||||
|
||||
let q = data.q.to_owned();
|
||||
|
||||
users = if community_or_creator_included {
|
||||
vec![]
|
||||
} else {
|
||||
blocking(context.pool(), move |conn| {
|
||||
PersonQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??
|
||||
};
|
||||
}
|
||||
SearchType::Url => {
|
||||
posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.listing_type(listing_type)
|
||||
.my_person_id(person_id)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.creator_id(creator_id)
|
||||
.url_search(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
};
|
||||
|
||||
// Blank out deleted or removed info for non logged in users
|
||||
if person_id.is_none() {
|
||||
for cv in communities
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.community.deleted || cv.community.removed)
|
||||
{
|
||||
cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.post.deleted || p.post.removed)
|
||||
{
|
||||
pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
Ok(SearchResponse {
|
||||
type_: search_type.to_string(),
|
||||
comments,
|
||||
posts,
|
||||
communities,
|
||||
users,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -222,7 +222,7 @@ pub struct PasswordReset {
|
|||
pub struct PasswordResetResponse {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PasswordChange {
|
||||
pub struct PasswordChangeAfterReset {
|
||||
pub token: Sensitive<String>,
|
||||
pub password: Sensitive<String>,
|
||||
pub password_verify: Sensitive<String>,
|
||||
|
|
84
crates/api_crud/src/comment/list.rs
Normal file
84
crates/api_crud/src/comment/list.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use crate::PerformCrud;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_private_instance,
|
||||
comment::*,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
source::community::Community,
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for GetComments {
|
||||
type Response = GetCommentsResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
|
||||
|
||||
let community_id = data.community_id;
|
||||
let community_actor_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let mut comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.saved_only(saved_only)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
Ok(GetCommentsResponse { comments })
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod create;
|
||||
mod delete;
|
||||
mod list;
|
||||
mod read;
|
||||
mod update;
|
||||
|
|
|
@ -6,15 +6,7 @@ use lemmy_api_common::{
|
|||
comment::*,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
source::community::Community,
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::comment_view::{CommentQueryBuilder, CommentView};
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
|
@ -50,68 +42,3 @@ impl PerformCrud for GetComment {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for GetComments {
|
||||
type Response = GetCommentsResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
|
||||
|
||||
let community_id = data.community_id;
|
||||
let community_actor_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let mut comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.saved_only(saved_only)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
Ok(GetCommentsResponse { comments })
|
||||
}
|
||||
}
|
||||
|
|
74
crates/api_crud/src/community/list.rs
Normal file
74
crates/api_crud/src/community/list.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use crate::PerformCrud;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_private_instance,
|
||||
community::*,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views_actor::community_view::CommunityQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for ListCommunities {
|
||||
type Response = ListCommunitiesResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
||||
|
||||
// Don't show NSFW by default
|
||||
let show_nsfw = match &local_user_view {
|
||||
Some(uv) => uv.local_user.show_nsfw,
|
||||
None => false,
|
||||
};
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let mut communities = blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info for non-logged in users
|
||||
if person_id.is_none() {
|
||||
for cv in communities
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.community.deleted || cv.community.removed)
|
||||
{
|
||||
cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
Ok(ListCommunitiesResponse { communities })
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod create;
|
||||
mod delete;
|
||||
mod list;
|
||||
mod read;
|
||||
mod update;
|
||||
|
|
|
@ -11,15 +11,12 @@ use lemmy_apub::{
|
|||
objects::{community::ApubCommunity, instance::instance_actor_id_from_url},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
source::{community::Community, site::Site},
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::{CommunityQueryBuilder, CommunityView},
|
||||
community_view::CommunityView,
|
||||
};
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::GetCommunityUsersOnline, LemmyContext};
|
||||
|
@ -102,60 +99,3 @@ impl PerformCrud for GetCommunity {
|
|||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for ListCommunities {
|
||||
type Response = ListCommunitiesResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
||||
|
||||
// Don't show NSFW by default
|
||||
let show_nsfw = match &local_user_view {
|
||||
Some(uv) => uv.local_user.show_nsfw,
|
||||
None => false,
|
||||
};
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let mut communities = blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info for non-logged in users
|
||||
if person_id.is_none() {
|
||||
for cv in communities
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.community.deleted || cv.community.removed)
|
||||
{
|
||||
cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
Ok(ListCommunitiesResponse { communities })
|
||||
}
|
||||
}
|
||||
|
|
101
crates/api_crud/src/post/list.rs
Normal file
101
crates/api_crud/src/post/list.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use crate::PerformCrud;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
blocking,
|
||||
check_private_instance,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
post::*,
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
source::community::Community,
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::post_view::PostQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for GetPosts {
|
||||
type Response = GetPostsResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
||||
|
||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let show_read_posts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_read_posts);
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let community_id = data.community_id;
|
||||
let community_actor_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
let mut posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.saved_only(saved_only)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?;
|
||||
|
||||
// Blank out deleted or removed info for non-logged in users
|
||||
if person_id.is_none() {
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.post.deleted || p.post.removed)
|
||||
{
|
||||
pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.community.deleted || p.community.removed)
|
||||
{
|
||||
pv.community = pv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetPostsResponse { posts })
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod create;
|
||||
mod delete;
|
||||
mod list;
|
||||
mod read;
|
||||
mod update;
|
||||
|
|
|
@ -7,18 +7,8 @@ use lemmy_api_common::{
|
|||
mark_post_as_read,
|
||||
post::*,
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{
|
||||
from_opt_str_to_opt_enum,
|
||||
source::community::Community,
|
||||
traits::DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentQueryBuilder,
|
||||
post_view::{PostQueryBuilder, PostView},
|
||||
};
|
||||
use lemmy_db_schema::traits::DeleteableOrRemoveable;
|
||||
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostView};
|
||||
use lemmy_db_views_actor::{
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::CommunityView,
|
||||
|
@ -117,85 +107,3 @@ impl PerformCrud for GetPost {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for GetPosts {
|
||||
type Response = GetPostsResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
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.as_ref(), context.pool(), context.secret())
|
||||
.await?;
|
||||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
||||
|
||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let show_read_posts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_read_posts);
|
||||
|
||||
let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
|
||||
let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.type_);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let community_id = data.community_id;
|
||||
let community_actor_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.actor_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
let mut posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.show_read_posts(show_read_posts)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.saved_only(saved_only)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?;
|
||||
|
||||
// Blank out deleted or removed info for non-logged in users
|
||||
if person_id.is_none() {
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.post.deleted || p.post.removed)
|
||||
{
|
||||
pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.community.deleted || p.community.removed)
|
||||
{
|
||||
pv.community = pv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetPostsResponse { posts })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,7 +195,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
)
|
||||
.route(
|
||||
"/password_change",
|
||||
web::post().to(route_post::<PasswordChange>),
|
||||
web::post().to(route_post::<PasswordChangeAfterReset>),
|
||||
)
|
||||
// mark_all_as_read feels off being in this section as well
|
||||
.route(
|
||||
|
|
Loading…
Reference in a new issue