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 actix_web::web::Data;
|
||||||
|
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
blocking,
|
blocking,
|
||||||
check_community_ban,
|
check_community_ban,
|
||||||
check_downvotes_enabled,
|
check_downvotes_enabled,
|
||||||
comment::*,
|
comment::{CommentResponse, CreateCommentLike},
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
};
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
|
@ -18,111 +16,13 @@ use lemmy_apub::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
source::comment::*,
|
source::comment::{CommentLike, CommentLikeForm},
|
||||||
traits::{Likeable, Saveable},
|
traits::Likeable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
|
use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
|
||||||
use lemmy_utils::{ConnectionId, LemmyError};
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
|
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
|
||||||
|
use std::convert::TryInto;
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for CreateCommentLike {
|
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
|
do_websocket_operation::<PasswordReset>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
UserOperation::PasswordChange => {
|
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::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
|
||||||
UserOperation::PostJoin => do_websocket_operation::<PostJoin>(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 {}
|
pub struct PasswordResetResponse {}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct PasswordChange {
|
pub struct PasswordChangeAfterReset {
|
||||||
pub token: Sensitive<String>,
|
pub token: Sensitive<String>,
|
||||||
pub password: Sensitive<String>,
|
pub password: Sensitive<String>,
|
||||||
pub password_verify: 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 create;
|
||||||
mod delete;
|
mod delete;
|
||||||
|
mod list;
|
||||||
mod read;
|
mod read;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
|
@ -6,15 +6,7 @@ use lemmy_api_common::{
|
||||||
comment::*,
|
comment::*,
|
||||||
get_local_user_view_from_jwt_opt,
|
get_local_user_view_from_jwt_opt,
|
||||||
};
|
};
|
||||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
use lemmy_db_views::comment_view::CommentView;
|
||||||
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_utils::{ConnectionId, LemmyError};
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
use lemmy_websocket::LemmyContext;
|
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 create;
|
||||||
mod delete;
|
mod delete;
|
||||||
|
mod list;
|
||||||
mod read;
|
mod read;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
|
@ -11,15 +11,12 @@ use lemmy_apub::{
|
||||||
objects::{community::ApubCommunity, instance::instance_actor_id_from_url},
|
objects::{community::ApubCommunity, instance::instance_actor_id_from_url},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
from_opt_str_to_opt_enum,
|
|
||||||
source::{community::Community, site::Site},
|
source::{community::Community, site::Site},
|
||||||
traits::DeleteableOrRemoveable,
|
traits::DeleteableOrRemoveable,
|
||||||
ListingType,
|
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
community_moderator_view::CommunityModeratorView,
|
community_moderator_view::CommunityModeratorView,
|
||||||
community_view::{CommunityQueryBuilder, CommunityView},
|
community_view::CommunityView,
|
||||||
};
|
};
|
||||||
use lemmy_utils::{ConnectionId, LemmyError};
|
use lemmy_utils::{ConnectionId, LemmyError};
|
||||||
use lemmy_websocket::{messages::GetCommunityUsersOnline, LemmyContext};
|
use lemmy_websocket::{messages::GetCommunityUsersOnline, LemmyContext};
|
||||||
|
@ -102,60 +99,3 @@ impl PerformCrud for GetCommunity {
|
||||||
Ok(res)
|
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 create;
|
||||||
mod delete;
|
mod delete;
|
||||||
|
mod list;
|
||||||
mod read;
|
mod read;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
|
@ -7,18 +7,8 @@ use lemmy_api_common::{
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
post::*,
|
post::*,
|
||||||
};
|
};
|
||||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
use lemmy_db_schema::traits::DeleteableOrRemoveable;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostView};
|
||||||
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_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
community_moderator_view::CommunityModeratorView,
|
community_moderator_view::CommunityModeratorView,
|
||||||
community_view::CommunityView,
|
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(
|
.route(
|
||||||
"/password_change",
|
"/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
|
// mark_all_as_read feels off being in this section as well
|
||||||
.route(
|
.route(
|
||||||
|
|
Loading…
Reference in a new issue