Adding ability to make a note for a given person. (#5765)
* Adding ability to make a note for a given person. - This comes back with PersonActions.note, already on post and comment views. - Also adds person_actions to PersonView, so that the read person API action can return the note. - Fixes #2353 * Adding unit test for person_view read. * Fixing bindings check * Addressing PR comments * Moving API action to person/note
This commit is contained in:
parent
ea9b19bea8
commit
d9df8335ea
@ -30,6 +30,7 @@ pub async fn ban_from_community(
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<BanFromCommunityResponse>> {
|
||||
let banned_person_id = data.person_id;
|
||||
let my_person_id = local_user_view.person.id;
|
||||
let expires_at = check_expire_time(data.expires_at)?;
|
||||
let local_instance_id = local_user_view.person.instance_id;
|
||||
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||
@ -40,7 +41,7 @@ pub async fn ban_from_community(
|
||||
LocalUser::is_higher_mod_or_admin_check(
|
||||
&mut context.pool(),
|
||||
data.community_id,
|
||||
local_user_view.person.id,
|
||||
my_person_id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
@ -76,7 +77,7 @@ pub async fn ban_from_community(
|
||||
let remove_data = tx_data.ban;
|
||||
remove_or_restore_user_data_in_community(
|
||||
tx_data.community_id,
|
||||
local_user_view.person.id,
|
||||
my_person_id,
|
||||
banned_person_id,
|
||||
remove_data,
|
||||
&tx_data.reason,
|
||||
@ -87,7 +88,7 @@ pub async fn ban_from_community(
|
||||
|
||||
// Mod tables
|
||||
let form = ModBanFromCommunityForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
mod_person_id: my_person_id,
|
||||
other_person_id: tx_data.person_id,
|
||||
community_id: tx_data.community_id,
|
||||
reason: tx_data.reason.clone(),
|
||||
@ -106,6 +107,7 @@ pub async fn ban_from_community(
|
||||
let person_view = PersonView::read(
|
||||
&mut context.pool(),
|
||||
data.person_id,
|
||||
Some(my_person_id),
|
||||
local_instance_id,
|
||||
false,
|
||||
)
|
||||
|
@ -19,17 +19,15 @@ pub async fn add_admin(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<AddAdminResponse>> {
|
||||
let my_person_id = local_user_view.person.id;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// If its an admin removal, also check that you're a higher admin
|
||||
if !data.added {
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
LocalUser::is_higher_admin_check(&mut context.pool(), my_person_id, vec![data.person_id])
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Make sure that the person_id added is local
|
||||
@ -47,7 +45,7 @@ pub async fn add_admin(
|
||||
|
||||
// Mod tables
|
||||
let form = ModAddForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
mod_person_id: my_person_id,
|
||||
other_person_id: added_local_user.person.id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
@ -58,7 +56,11 @@ pub async fn add_admin(
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(local_user_view.person.instance_id, &mut context.pool())
|
||||
.list(
|
||||
Some(my_person_id),
|
||||
local_user_view.person.instance_id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(AddAdminResponse { admins }))
|
||||
|
@ -26,17 +26,13 @@ pub async fn ban_from_site(
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<BanPersonResponse>> {
|
||||
let local_instance_id = local_user_view.person.instance_id;
|
||||
let my_person_id = local_user_view.person.id;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Also make sure you're a higher admin than the target
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
LocalUser::is_higher_admin_check(&mut context.pool(), my_person_id, vec![data.person_id]).await?;
|
||||
|
||||
if let Some(reason) = &data.reason {
|
||||
is_valid_body_field(reason, false)?;
|
||||
@ -59,7 +55,7 @@ pub async fn ban_from_site(
|
||||
if data.remove_or_restore_data.unwrap_or(false) {
|
||||
let removed = data.ban;
|
||||
remove_or_restore_user_data(
|
||||
local_user_view.person.id,
|
||||
my_person_id,
|
||||
data.person_id,
|
||||
removed,
|
||||
&data.reason,
|
||||
@ -70,7 +66,7 @@ pub async fn ban_from_site(
|
||||
|
||||
// Mod tables
|
||||
let form = ModBanForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
mod_person_id: my_person_id,
|
||||
other_person_id: data.person_id,
|
||||
reason: data.reason.clone(),
|
||||
banned: Some(data.ban),
|
||||
@ -83,6 +79,7 @@ pub async fn ban_from_site(
|
||||
let person_view = PersonView::read(
|
||||
&mut context.pool(),
|
||||
data.person_id,
|
||||
Some(my_person_id),
|
||||
local_instance_id,
|
||||
false,
|
||||
)
|
||||
|
@ -17,15 +17,15 @@ pub async fn user_block_person(
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<BlockPersonResponse>> {
|
||||
let target_id = data.person_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let my_person_id = local_user_view.person.id;
|
||||
let local_instance_id = local_user_view.person.instance_id;
|
||||
|
||||
// Don't let a person block themselves
|
||||
if target_id == person_id {
|
||||
if target_id == my_person_id {
|
||||
Err(LemmyErrorType::CantBlockYourself)?
|
||||
}
|
||||
|
||||
let person_block_form = PersonBlockForm::new(person_id, target_id);
|
||||
let person_block_form = PersonBlockForm::new(my_person_id, target_id);
|
||||
|
||||
let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
|
||||
.await
|
||||
@ -41,8 +41,14 @@ pub async fn user_block_person(
|
||||
PersonActions::unblock(&mut context.pool(), &person_block_form).await?;
|
||||
}
|
||||
|
||||
let person_view =
|
||||
PersonView::read(&mut context.pool(), target_id, local_instance_id, false).await?;
|
||||
let person_view = PersonView::read(
|
||||
&mut context.pool(),
|
||||
target_id,
|
||||
Some(my_person_id),
|
||||
local_instance_id,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(BlockPersonResponse {
|
||||
person_view,
|
||||
blocked: data.block,
|
||||
|
@ -14,6 +14,7 @@ pub mod list_read;
|
||||
pub mod list_saved;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod note_person;
|
||||
pub mod notifications;
|
||||
pub mod report_count;
|
||||
pub mod resend_verification_email;
|
||||
|
44
crates/api/api/src/local_user/note_person.rs
Normal file
44
crates/api/api/src/local_user/note_person.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::{
|
||||
context::LemmyContext,
|
||||
utils::{get_url_blocklist, process_markdown, slur_regex},
|
||||
};
|
||||
use lemmy_db_schema::source::person::{PersonActions, PersonNoteForm};
|
||||
use lemmy_db_views_api_misc::{NotePerson, SuccessResponse};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
utils::{slurs::check_slurs, validation::is_valid_body_field},
|
||||
};
|
||||
|
||||
pub async fn user_note_person(
|
||||
data: Json<NotePerson>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let target_id = data.person_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let slur_regex = slur_regex(&context).await?;
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
|
||||
// Don't let a person note themselves
|
||||
if target_id == person_id {
|
||||
Err(LemmyErrorType::CantNoteYourself)?
|
||||
}
|
||||
|
||||
// If the note is empty, delete it
|
||||
if data.note.is_empty() {
|
||||
PersonActions::delete_note(&mut context.pool(), person_id, target_id).await?;
|
||||
} else {
|
||||
check_slurs(&data.note, &slur_regex)?;
|
||||
is_valid_body_field(&data.note, false)?;
|
||||
|
||||
let note = process_markdown(&data.note, &slur_regex, &url_blocklist, &context).await?;
|
||||
let note_form = PersonNoteForm::new(person_id, target_id, note);
|
||||
|
||||
PersonActions::note(&mut context.pool(), ¬e_form).await?;
|
||||
}
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
@ -24,6 +24,8 @@ pub async fn leave_admin(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetSiteResponse>> {
|
||||
let my_person_id = local_user_view.person.id;
|
||||
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
||||
@ -31,7 +33,11 @@ pub async fn leave_admin(
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(local_user_view.person.instance_id, &mut context.pool())
|
||||
.list(
|
||||
Some(my_person_id),
|
||||
local_user_view.person.instance_id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
if admins.len() == 1 {
|
||||
Err(LemmyErrorType::CannotLeaveAdmin)?
|
||||
@ -51,10 +57,9 @@ pub async fn leave_admin(
|
||||
.await?;
|
||||
|
||||
// Mod tables
|
||||
let person_id = local_user_view.person.id;
|
||||
let form = ModAddForm {
|
||||
mod_person_id: person_id,
|
||||
other_person_id: person_id,
|
||||
mod_person_id: my_person_id,
|
||||
other_person_id: my_person_id,
|
||||
removed: Some(true),
|
||||
};
|
||||
|
||||
@ -66,7 +71,11 @@ pub async fn leave_admin(
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(site_view.instance.id, &mut context.pool())
|
||||
.list(
|
||||
Some(my_person_id),
|
||||
site_view.instance.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
|
@ -41,7 +41,7 @@ async fn read_site(context: &LemmyContext) -> LemmyResult<GetSiteResponse> {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(site_view.instance.id, &mut context.pool())
|
||||
.list(None, site_view.instance.id, &mut context.pool())
|
||||
.await?;
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
|
@ -76,7 +76,7 @@ pub async fn check_is_mod_or_admin(
|
||||
let is_mod =
|
||||
CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await;
|
||||
if is_mod.is_ok()
|
||||
|| PersonView::read(pool, person_id, local_instance_id, false)
|
||||
|| PersonView::read(pool, person_id, None, local_instance_id, false)
|
||||
.await
|
||||
.is_ok_and(|t| t.is_admin)
|
||||
{
|
||||
@ -94,7 +94,7 @@ pub(crate) async fn check_is_mod_of_any_or_admin(
|
||||
) -> LemmyResult<()> {
|
||||
let is_mod_of_any = CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await;
|
||||
if is_mod_of_any.is_ok()
|
||||
|| PersonView::read(pool, person_id, local_instance_id, false)
|
||||
|| PersonView::read(pool, person_id, None, local_instance_id, false)
|
||||
.await
|
||||
.is_ok_and(|t| t.is_admin)
|
||||
{
|
||||
|
@ -22,6 +22,7 @@ pub async fn read_person(
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let local_site = site_view.local_site;
|
||||
let local_instance_id = site_view.site.instance_id;
|
||||
let my_person_id = local_user_view.as_ref().map(|l| l.person.id);
|
||||
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
|
||||
@ -43,6 +44,7 @@ pub async fn read_person(
|
||||
let person_view = PersonView::read(
|
||||
&mut context.pool(),
|
||||
person_details_id,
|
||||
my_person_id,
|
||||
local_instance_id,
|
||||
is_admin,
|
||||
)
|
||||
|
@ -48,6 +48,7 @@ pub(super) async fn resolve_object_internal(
|
||||
}
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)?;
|
||||
|
||||
let my_person_id = local_user_view.as_ref().map(|l| l.person.id);
|
||||
let local_user = local_user_view.as_ref().map(|l| l.local_user.clone());
|
||||
let is_admin = local_user.as_ref().map(|l| l.admin).unwrap_or_default();
|
||||
let pool = &mut context.pool();
|
||||
@ -60,7 +61,9 @@ pub(super) async fn resolve_object_internal(
|
||||
Left(Right(c)) => {
|
||||
Comment(CommentView::read(pool, c.id, local_user.as_ref(), local_instance_id).await?)
|
||||
}
|
||||
Right(Left(u)) => Person(PersonView::read(pool, u.id, local_instance_id, is_admin).await?),
|
||||
Right(Left(u)) => {
|
||||
Person(PersonView::read(pool, u.id, my_person_id, local_instance_id, is_admin).await?)
|
||||
}
|
||||
Right(Right(c)) => {
|
||||
Community(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use crate::{
|
||||
PersonBlockForm,
|
||||
PersonFollowerForm,
|
||||
PersonInsertForm,
|
||||
PersonNoteForm,
|
||||
PersonUpdateForm,
|
||||
},
|
||||
traits::{ApubActor, Blockable, Crud, Followable},
|
||||
@ -341,6 +342,33 @@ impl PersonActions {
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn note(pool: &mut DbPool<'_>, form: &PersonNoteForm) -> LemmyResult<Self> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(person_actions::table)
|
||||
.values(form)
|
||||
.on_conflict((person_actions::person_id, person_actions::target_id))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn delete_note(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
target_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(person_actions::table.find((person_id, target_id)))
|
||||
.set_null(person_actions::note)
|
||||
.set_null(person_actions::noted_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -151,6 +151,12 @@ pub struct PersonActions {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
/// When the person was blocked.
|
||||
pub blocked_at: Option<DateTime<Utc>>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
/// When the person was noted.
|
||||
pub noted_at: Option<DateTime<Utc>>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
/// A note about the person.
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_new::new)]
|
||||
@ -168,9 +174,19 @@ pub struct PersonFollowerForm {
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_actions))]
|
||||
pub struct PersonBlockForm {
|
||||
// This order is switched so blocks can work the same.
|
||||
pub person_id: PersonId,
|
||||
pub target_id: PersonId,
|
||||
#[new(value = "Utc::now()")]
|
||||
pub blocked_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(derive_new::new)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_actions))]
|
||||
pub struct PersonNoteForm {
|
||||
pub person_id: PersonId,
|
||||
pub target_id: PersonId,
|
||||
pub note: String,
|
||||
#[new(value = "Utc::now()")]
|
||||
pub noted_at: DateTime<Utc>,
|
||||
}
|
||||
|
@ -785,6 +785,8 @@ diesel::table! {
|
||||
followed_at -> Nullable<Timestamptz>,
|
||||
follow_pending -> Nullable<Bool>,
|
||||
blocked_at -> Nullable<Timestamptz>,
|
||||
noted_at -> Nullable<Timestamptz>,
|
||||
note -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{InstanceId, LanguageId, PaginationCursor},
|
||||
newtypes::{InstanceId, LanguageId, PaginationCursor, PersonId},
|
||||
sensitive::SensitiveString,
|
||||
source::{community::Community, instance::Instance, login_token::LoginToken, person::Person},
|
||||
};
|
||||
@ -177,6 +177,17 @@ pub struct LoginResponse {
|
||||
pub verify_email_sent: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Make a note for a person.
|
||||
///
|
||||
/// An empty string deletes the note.
|
||||
pub struct NotePerson {
|
||||
pub person_id: PersonId,
|
||||
pub note: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
@ -10,7 +10,11 @@ use lemmy_db_schema::{
|
||||
get_conn,
|
||||
limit_fetch,
|
||||
paginate,
|
||||
queries::{creator_home_instance_actions_join, creator_local_instance_actions_join},
|
||||
queries::{
|
||||
creator_home_instance_actions_join,
|
||||
creator_local_instance_actions_join,
|
||||
my_person_actions_join,
|
||||
},
|
||||
DbPool,
|
||||
},
|
||||
};
|
||||
@ -35,12 +39,14 @@ impl PaginationCursorBuilder for PersonView {
|
||||
|
||||
impl PersonView {
|
||||
#[diesel::dsl::auto_type(no_type_alias)]
|
||||
fn joins(local_instance_id: InstanceId) -> _ {
|
||||
fn joins(my_person_id: Option<PersonId>, local_instance_id: InstanceId) -> _ {
|
||||
let creator_local_instance_actions_join: creator_local_instance_actions_join =
|
||||
creator_local_instance_actions_join(local_instance_id);
|
||||
let my_person_actions_join: my_person_actions_join = my_person_actions_join(my_person_id);
|
||||
|
||||
person::table
|
||||
.left_join(local_user::table)
|
||||
.left_join(my_person_actions_join)
|
||||
.left_join(creator_home_instance_actions_join())
|
||||
.left_join(creator_local_instance_actions_join)
|
||||
}
|
||||
@ -48,11 +54,12 @@ impl PersonView {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
my_person_id: Option<PersonId>,
|
||||
local_instance_id: InstanceId,
|
||||
is_admin: bool,
|
||||
) -> LemmyResult<Self> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let mut query = Self::joins(local_instance_id)
|
||||
let mut query = Self::joins(my_person_id, local_instance_id)
|
||||
.filter(person::id.eq(person_id))
|
||||
.select(Self::as_select())
|
||||
.into_boxed();
|
||||
@ -79,11 +86,12 @@ pub struct PersonQuery {
|
||||
impl PersonQuery {
|
||||
pub async fn list(
|
||||
self,
|
||||
my_person_id: Option<PersonId>,
|
||||
local_instance_id: InstanceId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Vec<PersonView>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let mut query = PersonView::joins(local_instance_id)
|
||||
let mut query = PersonView::joins(my_person_id, local_instance_id)
|
||||
.filter(person::deleted.eq(false))
|
||||
.select(PersonView::as_select())
|
||||
.into_boxed();
|
||||
@ -124,7 +132,7 @@ mod tests {
|
||||
source::{
|
||||
instance::Instance,
|
||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
person::{Person, PersonInsertForm, PersonUpdateForm},
|
||||
person::{Person, PersonActions, PersonInsertForm, PersonNoteForm, PersonUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::build_db_pool_for_tests,
|
||||
@ -194,11 +202,11 @@ mod tests {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let read = PersonView::read(pool, data.alice.id, data.alice.instance_id, false).await;
|
||||
let read = PersonView::read(pool, data.alice.id, None, data.alice.instance_id, false).await;
|
||||
assert!(read.is_err());
|
||||
|
||||
// only admin can view deleted users
|
||||
let read = PersonView::read(pool, data.alice.id, data.alice.instance_id, true).await;
|
||||
let read = PersonView::read(pool, data.alice.id, None, data.alice.instance_id, true).await;
|
||||
assert!(read.is_ok());
|
||||
|
||||
cleanup(data, pool).await
|
||||
@ -225,21 +233,50 @@ mod tests {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(data.alice.instance_id, pool)
|
||||
.list(None, data.alice.instance_id, pool)
|
||||
.await?;
|
||||
assert_length!(1, list);
|
||||
assert_eq!(list[0].person.id, data.alice.id);
|
||||
|
||||
let is_admin = PersonView::read(pool, data.alice.id, data.alice.instance_id, false)
|
||||
let is_admin = PersonView::read(pool, data.alice.id, None, data.alice.instance_id, false)
|
||||
.await?
|
||||
.is_admin;
|
||||
assert!(is_admin);
|
||||
|
||||
let is_admin = PersonView::read(pool, data.bob.id, data.alice.instance_id, false)
|
||||
let is_admin = PersonView::read(pool, data.bob.id, None, data.alice.instance_id, false)
|
||||
.await?
|
||||
.is_admin;
|
||||
assert!(!is_admin);
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn note() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
let note_str = "Bob hates cats.";
|
||||
|
||||
let note_form = PersonNoteForm::new(data.alice.id, data.bob.id, note_str.to_string());
|
||||
let inserted_note = PersonActions::note(pool, ¬e_form).await?;
|
||||
assert_eq!(Some(note_str.to_string()), inserted_note.note);
|
||||
|
||||
let read = PersonView::read(
|
||||
pool,
|
||||
data.bob.id,
|
||||
Some(data.alice.id),
|
||||
data.alice.instance_id,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(read
|
||||
.person_actions
|
||||
.is_some_and(|t| t.note == Some(note_str.to_string()) && t.noted_at.is_some()));
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use lemmy_db_schema::source::{instance::InstanceActions, person::Person};
|
||||
use lemmy_db_schema::source::{
|
||||
instance::InstanceActions,
|
||||
person::{Person, PersonActions},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "full")]
|
||||
use {
|
||||
@ -38,6 +41,9 @@ pub struct PersonView {
|
||||
)
|
||||
)]
|
||||
pub is_admin: bool,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub person_actions: Option<PersonActions>,
|
||||
#[cfg_attr(feature = "full", diesel(
|
||||
select_expression_type = Nullable<CreatorHomeInstanceActionsAllColumnsTuple>,
|
||||
select_expression = creator_home_instance_actions_select()))]
|
||||
|
@ -547,7 +547,7 @@ mod tests {
|
||||
keyword_block::LocalUserKeywordBlock,
|
||||
language::Language,
|
||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
person::{Person, PersonActions, PersonBlockForm, PersonInsertForm},
|
||||
person::{Person, PersonActions, PersonBlockForm, PersonInsertForm, PersonNoteForm},
|
||||
post::{
|
||||
Post,
|
||||
PostActions,
|
||||
@ -1004,6 +1004,58 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_context(Data)]
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn person_note(data: &mut Data) -> LemmyResult<()> {
|
||||
let pool = &data.pool();
|
||||
let pool = &mut pool.into();
|
||||
|
||||
let note_str = "Tegan loves cats.";
|
||||
|
||||
let note_form = PersonNoteForm::new(
|
||||
data.john_local_user_view.person.id,
|
||||
data.tegan_local_user_view.person.id,
|
||||
note_str.to_string(),
|
||||
);
|
||||
let inserted_note = PersonActions::note(pool, ¬e_form).await?;
|
||||
assert_eq!(Some(note_str.to_string()), inserted_note.note);
|
||||
|
||||
let post_listing = PostView::read(
|
||||
pool,
|
||||
data.post.id,
|
||||
Some(&data.john_local_user_view.local_user),
|
||||
data.instance.id,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(post_listing
|
||||
.person_actions
|
||||
.is_some_and(|t| t.note == Some(note_str.to_string()) && t.noted_at.is_some()));
|
||||
|
||||
let note_removed = PersonActions::delete_note(
|
||||
pool,
|
||||
data.john_local_user_view.person.id,
|
||||
data.tegan_local_user_view.person.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let post_listing = PostView::read(
|
||||
pool,
|
||||
data.post.id,
|
||||
Some(&data.john_local_user_view.local_user),
|
||||
data.instance.id,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(uplete::Count::only_deleted(1), note_removed);
|
||||
assert!(post_listing.person_actions.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_context(Data)]
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
|
@ -453,6 +453,7 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
|
||||
Some(SearchCombinedView::Person(PersonView {
|
||||
person,
|
||||
is_admin: v.item_creator_is_admin,
|
||||
person_actions: v.person_actions,
|
||||
home_instance_actions: v.creator_home_instance_actions,
|
||||
local_instance_actions: v.creator_local_instance_actions,
|
||||
creator_banned: v.creator_banned,
|
||||
|
@ -18,6 +18,7 @@ pub enum LemmyErrorType {
|
||||
NotAModerator,
|
||||
NotAnAdmin,
|
||||
CantBlockYourself,
|
||||
CantNoteYourself,
|
||||
CantBlockAdmin,
|
||||
CouldntUpdateUser,
|
||||
PasswordsDoNotMatch,
|
||||
|
@ -106,7 +106,7 @@ pub fn is_valid_post_title(title: &str) -> LemmyResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This could be post bodies, comments, or any description field
|
||||
/// This could be post bodies, comments, notes, or any description field
|
||||
pub fn is_valid_body_field(body: &str, post: bool) -> LemmyResult<()> {
|
||||
if post {
|
||||
max_length_check(body, POST_BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?;
|
||||
|
4
migrations/2025-06-09-114549_person_note/down.sql
Normal file
4
migrations/2025-06-09-114549_person_note/down.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE person_actions
|
||||
DROP COLUMN noted_at,
|
||||
DROP COLUMN note;
|
||||
|
4
migrations/2025-06-09-114549_person_note/up.sql
Normal file
4
migrations/2025-06-09-114549_person_note/up.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE person_actions
|
||||
ADD COLUMN noted_at timestamptz,
|
||||
ADD COLUMN note text;
|
||||
|
@ -37,6 +37,7 @@ use lemmy_api::{
|
||||
list_saved::list_person_saved,
|
||||
login::login,
|
||||
logout::logout,
|
||||
note_person::user_note_person,
|
||||
notifications::{
|
||||
list_inbox::list_inbox,
|
||||
mark_all_read::mark_all_notifications_read,
|
||||
@ -386,7 +387,8 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
||||
.service(
|
||||
scope("/person")
|
||||
.route("", get().to(read_person))
|
||||
.route("/content", get().to(list_person_content)),
|
||||
.route("/content", get().to(list_person_content))
|
||||
.route("/note", post().to(user_note_person)),
|
||||
)
|
||||
// Admin Actions
|
||||
.service(
|
||||
|
Loading…
x
Reference in New Issue
Block a user