X-Git-Url: http://these/git/?a=blobdiff_plain;f=crates%2Fdb_schema%2Fsrc%2Fimpls%2Fperson.rs;h=2e086dcb6d3f1cb9a8eda5ec61ec38ca9b142103;hb=92568956353f21649ed9aff68b42699c9d036f30;hp=de99d4b63dc2223a58daa749636ea9085a34404a;hpb=c9f140742925d6da20103124b49f2b58a35fc2b8;p=lemmy.git diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index de99d4b6..2e086dcb 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -1,127 +1,74 @@ use crate::{ - newtypes::{DbUrl, PersonId}, - schema::person::dsl::*, - source::person::{Person, PersonForm}, - traits::{ApubActor, Crud}, - utils::{functions::lower, naive_now}, + newtypes::{CommunityId, DbUrl, PersonId}, + schema::{instance, local_user, person, person_follower}, + source::person::{ + Person, + PersonFollower, + PersonFollowerForm, + PersonInsertForm, + PersonUpdateForm, + }, + traits::{ApubActor, Crud, Followable}, + utils::{functions::lower, get_conn, naive_now, DbPool}, }; -use diesel::{ - dsl::*, - result::Error, - ExpressionMethods, - PgConnection, - QueryDsl, - RunQueryDsl, - TextExpressionMethods, -}; - -mod safe_type { - use crate::{schema::person::columns::*, source::person::Person, traits::ToSafe}; - - type Columns = ( - id, - name, - display_name, - avatar, - banned, - published, - updated, - actor_id, - bio, - local, - banner, - deleted, - inbox_url, - shared_inbox_url, - matrix_user_id, - admin, - bot_account, - ban_expires, - ); - - impl ToSafe for Person { - type SafeColumns = Columns; - fn safe_columns_tuple() -> Self::SafeColumns { - ( - id, - name, - display_name, - avatar, - banned, - published, - updated, - actor_id, - bio, - local, - banner, - deleted, - inbox_url, - shared_inbox_url, - matrix_user_id, - admin, - bot_account, - ban_expires, - ) - } - } -} +use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel_async::RunQueryDsl; +#[async_trait] impl Crud for Person { - type Form = PersonForm; + type InsertForm = PersonInsertForm; + type UpdateForm = PersonUpdateForm; type IdType = PersonId; - fn read(conn: &mut PgConnection, person_id: PersonId) -> Result { - person - .filter(deleted.eq(false)) + async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + let conn = &mut get_conn(pool).await?; + person::table + .filter(person::deleted.eq(false)) .find(person_id) .first::(conn) + .await } - fn delete(conn: &mut PgConnection, person_id: PersonId) -> Result { - diesel::delete(person.find(person_id)).execute(conn) + async fn delete(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + let conn = &mut get_conn(pool).await?; + diesel::delete(person::table.find(person_id)) + .execute(conn) + .await } - fn create(conn: &mut PgConnection, form: &PersonForm) -> Result { - insert_into(person).values(form).get_result::(conn) + async fn create(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(person::table) + .values(form) + .get_result::(conn) + .await } - fn update( - conn: &mut PgConnection, + async fn update( + pool: &mut DbPool<'_>, person_id: PersonId, - form: &PersonForm, + form: &PersonUpdateForm, ) -> Result { - diesel::update(person.find(person_id)) + let conn = &mut get_conn(pool).await?; + diesel::update(person::table.find(person_id)) .set(form) .get_result::(conn) + .await } } impl Person { - pub fn ban_person( - conn: &mut PgConnection, - person_id: PersonId, - ban: bool, - expires: Option, - ) -> Result { - diesel::update(person.find(person_id)) - .set((banned.eq(ban), ban_expires.eq(expires))) - .get_result::(conn) - } - - pub fn add_admin( - conn: &mut PgConnection, - person_id: PersonId, - added: bool, - ) -> Result { - diesel::update(person.find(person_id)) - .set(admin.eq(added)) - .get_result::(conn) - } - - pub fn mark_as_updated(conn: &mut PgConnection, person_id: PersonId) -> Result { - diesel::update(person.find(person_id)) - .set((last_refreshed_at.eq(naive_now()),)) + /// Update or insert the person. + /// + /// This is necessary for federation, because Activitypub doesnt distinguish between these actions. + pub async fn upsert(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(person::table) + .values(form) + .on_conflict(person::actor_id) + .do_update() + .set(form) .get_result::(conn) + .await } - - pub fn delete_account(conn: &mut PgConnection, person_id: PersonId) -> Result { - use crate::schema::local_user; + pub async fn delete_account(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + let conn = &mut get_conn(pool).await?; // Set the local user info to none diesel::update(local_user::table.filter(local_user::person_id.eq(person_id))) @@ -129,57 +76,21 @@ impl Person { local_user::email.eq::>(None), local_user::validator_time.eq(naive_now()), )) - .execute(conn)?; - - diesel::update(person.find(person_id)) - .set(( - display_name.eq::>(None), - avatar.eq::>(None), - banner.eq::>(None), - bio.eq::>(None), - matrix_user_id.eq::>(None), - deleted.eq(true), - updated.eq(naive_now()), - )) - .get_result::(conn) - } - - pub fn upsert(conn: &mut PgConnection, person_form: &PersonForm) -> Result { - insert_into(person) - .values(person_form) - .on_conflict(actor_id) - .do_update() - .set(person_form) - .get_result::(conn) - } - - pub fn update_deleted( - conn: &mut PgConnection, - person_id: PersonId, - new_deleted: bool, - ) -> Result { - use crate::schema::person::dsl::*; - diesel::update(person.find(person_id)) - .set(deleted.eq(new_deleted)) - .get_result::(conn) - } - - pub fn leave_admin(conn: &mut PgConnection, person_id: PersonId) -> Result { - diesel::update(person.find(person_id)) - .set(admin.eq(false)) - .get_result::(conn) - } + .execute(conn) + .await?; - pub fn remove_avatar_and_banner( - conn: &mut PgConnection, - person_id: PersonId, - ) -> Result { - diesel::update(person.find(person_id)) + diesel::update(person::table.find(person_id)) .set(( - avatar.eq::>(None), - banner.eq::>(None), + person::display_name.eq::>(None), + person::avatar.eq::>(None), + person::banner.eq::>(None), + person::bio.eq::>(None), + person::matrix_user_id.eq::>(None), + person::deleted.eq(true), + person::updated.eq(naive_now()), )) .get_result::(conn) + .await } } @@ -191,62 +102,134 @@ pub fn is_banned(banned_: bool, expires: Option) -> bool } } +#[async_trait] impl ApubActor for Person { - fn read_from_apub_id(conn: &mut PgConnection, object_id: &DbUrl) -> Result, Error> { - use crate::schema::person::dsl::*; + async fn read_from_apub_id( + pool: &mut DbPool<'_>, + object_id: &DbUrl, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; Ok( - person - .filter(deleted.eq(false)) - .filter(actor_id.eq(object_id)) + person::table + .filter(person::deleted.eq(false)) + .filter(person::actor_id.eq(object_id)) .first::(conn) + .await .ok() .map(Into::into), ) } - fn read_from_name( - conn: &mut PgConnection, + async fn read_from_name( + pool: &mut DbPool<'_>, from_name: &str, include_deleted: bool, ) -> Result { - let mut q = person + let conn = &mut get_conn(pool).await?; + let mut q = person::table .into_boxed() - .filter(local.eq(true)) - .filter(lower(name).eq(lower(from_name))); + .filter(person::local.eq(true)) + .filter(lower(person::name).eq(from_name.to_lowercase())); if !include_deleted { - q = q.filter(deleted.eq(false)) + q = q.filter(person::deleted.eq(false)) } - q.first::(conn) + q.first::(conn).await } - fn read_from_name_and_domain( - conn: &mut PgConnection, + async fn read_from_name_and_domain( + pool: &mut DbPool<'_>, person_name: &str, - protocol_domain: &str, + for_domain: &str, ) -> Result { - use crate::schema::person::dsl::*; - person - .filter(lower(name).eq(lower(person_name))) - .filter(actor_id.like(format!("{}%", protocol_domain))) + let conn = &mut get_conn(pool).await?; + + person::table + .inner_join(instance::table) + .filter(lower(person::name).eq(person_name.to_lowercase())) + .filter(instance::domain.eq(for_domain)) + .select(person::all_columns) .first::(conn) + .await + } +} + +#[async_trait] +impl Followable for PersonFollower { + type Form = PersonFollowerForm; + async fn follow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { + use crate::schema::person_follower::dsl::{follower_id, person_follower, person_id}; + let conn = &mut get_conn(pool).await?; + insert_into(person_follower) + .values(form) + .on_conflict((follower_id, person_id)) + .do_update() + .set(form) + .get_result::(conn) + .await + } + async fn follow_accepted(_: &mut DbPool<'_>, _: CommunityId, _: PersonId) -> Result { + unimplemented!() + } + async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { + use crate::schema::person_follower::dsl::{follower_id, person_follower, person_id}; + let conn = &mut get_conn(pool).await?; + diesel::delete( + person_follower + .filter(follower_id.eq(&form.follower_id)) + .filter(person_id.eq(&form.person_id)), + ) + .execute(conn) + .await + } +} + +impl PersonFollower { + pub async fn list_followers( + pool: &mut DbPool<'_>, + for_person_id: PersonId, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + person_follower::table + .inner_join(person::table.on(person_follower::follower_id.eq(person::id))) + .filter(person_follower::person_id.eq(for_person_id)) + .select(person::all_columns) + .load(conn) + .await } } #[cfg(test)] mod tests { - use crate::{source::person::*, traits::Crud, utils::establish_unpooled_connection}; + #![allow(clippy::unwrap_used)] + #![allow(clippy::indexing_slicing)] - #[test] - fn test_crud() { - let conn = &mut establish_unpooled_connection(); + use crate::{ + source::{ + instance::Instance, + person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm}, + }, + traits::{Crud, Followable}, + utils::build_db_pool_for_tests, + }; + use serial_test::serial; - let new_person = PersonForm { - name: "holly".into(), - public_key: Some("nada".to_owned()), - ..PersonForm::default() - }; + #[tokio::test] + #[serial] + async fn test_crud() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) + .await + .unwrap(); - let inserted_person = Person::create(conn, &new_person).unwrap(); + let new_person = PersonInsertForm::builder() + .name("holly".into()) + .public_key("nada".to_owned()) + .instance_id(inserted_instance.id) + .build(); + + let inserted_person = Person::create(pool, &new_person).await.unwrap(); let expected_person = Person { id: inserted_person.id, @@ -258,7 +241,7 @@ mod tests { deleted: false, published: inserted_person.published, updated: None, - actor_id: inserted_person.actor_id.to_owned(), + actor_id: inserted_person.actor_id.clone(), bio: None, local: true, bot_account: false, @@ -266,19 +249,69 @@ mod tests { private_key: None, public_key: "nada".to_owned(), last_refreshed_at: inserted_person.published, - inbox_url: inserted_person.inbox_url.to_owned(), + inbox_url: inserted_person.inbox_url.clone(), shared_inbox_url: None, matrix_user_id: None, ban_expires: None, + instance_id: inserted_instance.id, }; - let read_person = Person::read(conn, inserted_person.id).unwrap(); - let updated_person = Person::update(conn, inserted_person.id, &new_person).unwrap(); - let num_deleted = Person::delete(conn, inserted_person.id).unwrap(); + let read_person = Person::read(pool, inserted_person.id).await.unwrap(); + + let update_person_form = PersonUpdateForm::builder() + .actor_id(Some(inserted_person.actor_id.clone())) + .build(); + let updated_person = Person::update(pool, inserted_person.id, &update_person_form) + .await + .unwrap(); + + let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await.unwrap(); assert_eq!(expected_person, read_person); assert_eq!(expected_person, inserted_person); assert_eq!(expected_person, updated_person); assert_eq!(1, num_deleted); } + + #[tokio::test] + #[serial] + async fn follow() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) + .await + .unwrap(); + + let person_form_1 = PersonInsertForm::builder() + .name("erich".into()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + let person_1 = Person::create(pool, &person_form_1).await.unwrap(); + let person_form_2 = PersonInsertForm::builder() + .name("michele".into()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + let person_2 = Person::create(pool, &person_form_2).await.unwrap(); + + let follow_form = PersonFollowerForm { + person_id: person_1.id, + follower_id: person_2.id, + pending: false, + }; + let person_follower = PersonFollower::follow(pool, &follow_form).await.unwrap(); + assert_eq!(person_1.id, person_follower.person_id); + assert_eq!(person_2.id, person_follower.follower_id); + assert!(!person_follower.pending); + + let followers = PersonFollower::list_followers(pool, person_1.id) + .await + .unwrap(); + assert_eq!(vec![person_2], followers); + + let unfollow = PersonFollower::unfollow(pool, &follow_form).await.unwrap(); + assert_eq!(1, unfollow); + } }