mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-15 01:07:23 +00:00
Rewrite person apub
This commit is contained in:
parent
fe95fbe9f2
commit
bec54d07f5
12 changed files with 234 additions and 235 deletions
83
Cargo.lock
generated
83
Cargo.lock
generated
|
@ -795,8 +795,18 @@ version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.10.2",
|
||||||
"darling_macro",
|
"darling_macro 0.10.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.13.0",
|
||||||
|
"darling_macro 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -809,7 +819,21 @@ dependencies = [
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2 1.0.27",
|
"proc-macro2 1.0.27",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
"strsim",
|
"strsim 0.9.3",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2 1.0.27",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"strsim 0.10.0",
|
||||||
"syn 1.0.73",
|
"syn 1.0.73",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -819,7 +843,18 @@ version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.10.2",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.13.0",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
"syn 1.0.73",
|
"syn 1.0.73",
|
||||||
]
|
]
|
||||||
|
@ -840,7 +875,7 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.10.2",
|
||||||
"derive_builder_core",
|
"derive_builder_core",
|
||||||
"proc-macro2 1.0.27",
|
"proc-macro2 1.0.27",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
|
@ -853,7 +888,7 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.10.2",
|
||||||
"proc-macro2 1.0.27",
|
"proc-macro2 1.0.27",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
"syn 1.0.73",
|
"syn 1.0.73",
|
||||||
|
@ -1695,6 +1730,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
"sha2",
|
"sha2",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
@ -2732,6 +2768,12 @@ dependencies = [
|
||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -2861,6 +2903,29 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "1.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad9fdbb69badc8916db738c25efd04f0a65297d26c2f8de4b62e57b8c12bc72"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"serde_with_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1569374bd54623ec8bd592cf22ba6e03c0f177ff55fbc8c29a49e296e7adecf"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.13.0",
|
||||||
|
"proc-macro2 1.0.27",
|
||||||
|
"quote 1.0.9",
|
||||||
|
"syn 1.0.73",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serial_test"
|
name = "serial_test"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -3056,6 +3121,12 @@ version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
|
|
@ -24,6 +24,7 @@ bcrypt = "0.10.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||||
serde = { version = "1.0.126", features = ["derive"] }
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
|
serde_with = "1.9.4"
|
||||||
actix = "0.12.0"
|
actix = "0.12.0"
|
||||||
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
actix-web = { version = "4.0.0-beta.8", default-features = false }
|
||||||
actix-rt = { version = "2.2.0", default-features = false }
|
actix-rt = { version = "2.2.0", default-features = false }
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub(crate) mod group_extension;
|
pub(crate) mod group_extension;
|
||||||
pub(crate) mod person_extension;
|
|
||||||
pub mod signatures;
|
pub mod signatures;
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
use activitystreams::unparsed::UnparsedMutExt;
|
|
||||||
use activitystreams_ext::UnparsedExtension;
|
|
||||||
use lemmy_utils::LemmyError;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Activitystreams extension to allow (de)serializing additional Person field
|
|
||||||
/// `also_known_as` (used for Matrix profile link).
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PersonExtension {
|
|
||||||
pub matrix_user_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PersonExtension {
|
|
||||||
pub fn new(matrix_user_id: Option<String>) -> Result<PersonExtension, LemmyError> {
|
|
||||||
Ok(PersonExtension { matrix_user_id })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U> UnparsedExtension<U> for PersonExtension
|
|
||||||
where
|
|
||||||
U: UnparsedMutExt,
|
|
||||||
{
|
|
||||||
type Error = serde_json::Error;
|
|
||||||
|
|
||||||
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
|
|
||||||
Ok(PersonExtension {
|
|
||||||
matrix_user_id: unparsed_mut.remove("matrix_user_id")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
|
|
||||||
unparsed_mut.insert("matrix_user_id", self.matrix_user_id)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor},
|
fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor},
|
||||||
objects::FromApub,
|
objects::{person::Person as ApubPerson, FromApub},
|
||||||
PersonExt,
|
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
|
@ -33,7 +32,7 @@ pub async fn get_or_fetch_and_upsert_person(
|
||||||
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
||||||
debug!("Fetching and updating from remote person: {}", apub_id);
|
debug!("Fetching and updating from remote person: {}", apub_id);
|
||||||
let person =
|
let person =
|
||||||
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await;
|
fetch_remote_object::<ApubPerson>(context.client(), apub_id, recursion_counter).await;
|
||||||
|
|
||||||
if is_deleted(&person) {
|
if is_deleted(&person) {
|
||||||
// TODO: use Person::update_deleted() once implemented
|
// TODO: use Person::update_deleted() once implemented
|
||||||
|
@ -67,7 +66,7 @@ pub async fn get_or_fetch_and_upsert_person(
|
||||||
Err(NotFound {}) => {
|
Err(NotFound {}) => {
|
||||||
debug!("Fetching and creating remote person: {}", apub_id);
|
debug!("Fetching and creating remote person: {}", apub_id);
|
||||||
let person =
|
let person =
|
||||||
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
|
fetch_remote_object::<ApubPerson>(context.client(), apub_id, recursion_counter).await?;
|
||||||
|
|
||||||
let person = Person::from_apub(
|
let person = Person::from_apub(
|
||||||
&person,
|
&person,
|
||||||
|
|
|
@ -6,10 +6,9 @@ use crate::{
|
||||||
is_deleted,
|
is_deleted,
|
||||||
},
|
},
|
||||||
find_object_by_id,
|
find_object_by_id,
|
||||||
objects::{comment::Note, post::Page, FromApub},
|
objects::{comment::Note, person::Person as ApubPerson, post::Page, FromApub},
|
||||||
GroupExt,
|
GroupExt,
|
||||||
Object,
|
Object,
|
||||||
PersonExt,
|
|
||||||
};
|
};
|
||||||
use activitystreams::base::BaseExt;
|
use activitystreams::base::BaseExt;
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
|
@ -42,7 +41,7 @@ use url::Url;
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum SearchAcceptedObjects {
|
enum SearchAcceptedObjects {
|
||||||
Person(Box<PersonExt>),
|
Person(Box<ApubPerson>),
|
||||||
Group(Box<GroupExt>),
|
Group(Box<GroupExt>),
|
||||||
Page(Box<Page>),
|
Page(Box<Page>),
|
||||||
Comment(Box<Note>),
|
Comment(Box<Note>),
|
||||||
|
@ -120,9 +119,8 @@ async fn build_response(
|
||||||
|
|
||||||
match fetch_response {
|
match fetch_response {
|
||||||
SearchAcceptedObjects::Person(p) => {
|
SearchAcceptedObjects::Person(p) => {
|
||||||
let person_uri = p.inner.id(domain)?.context("person has no id")?;
|
let person_id = p.id(&query_url)?;
|
||||||
|
let person = get_or_fetch_and_upsert_person(person_id, context, recursion_counter).await?;
|
||||||
let person = get_or_fetch_and_upsert_person(person_uri, context, recursion_counter).await?;
|
|
||||||
|
|
||||||
response.users = vec![
|
response.users = vec![
|
||||||
blocking(context.pool(), move |conn| {
|
blocking(context.pool(), move |conn| {
|
||||||
|
|
|
@ -12,7 +12,6 @@ pub mod objects;
|
||||||
use crate::{
|
use crate::{
|
||||||
extensions::{
|
extensions::{
|
||||||
group_extension::GroupExtension,
|
group_extension::GroupExtension,
|
||||||
person_extension::PersonExtension,
|
|
||||||
signatures::{PublicKey, PublicKeyExtension},
|
signatures::{PublicKey, PublicKeyExtension},
|
||||||
},
|
},
|
||||||
fetcher::community::get_or_fetch_and_upsert_community,
|
fetcher::community::get_or_fetch_and_upsert_community,
|
||||||
|
@ -49,17 +48,8 @@ use url::{ParseError, Url};
|
||||||
/// Activitystreams type for community
|
/// Activitystreams type for community
|
||||||
pub type GroupExt =
|
pub type GroupExt =
|
||||||
Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
|
Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
|
||||||
/// Activitystreams type for person
|
|
||||||
type PersonExt =
|
|
||||||
Ext2<actor::ApActor<ApObject<actor::Actor<UserTypes>>>, PersonExtension, PublicKeyExtension>;
|
|
||||||
pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
|
pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
|
|
||||||
pub enum UserTypes {
|
|
||||||
Person,
|
|
||||||
Service,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
||||||
|
|
||||||
/// Checks if the ID is allowed for sending or receiving.
|
/// Checks if the ID is allowed for sending or receiving.
|
||||||
|
@ -168,15 +158,17 @@ pub trait ActorType {
|
||||||
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
|
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_public_key(&self) -> Result<PublicKey, LemmyError> {
|
||||||
|
Ok(PublicKey {
|
||||||
|
id: format!("{}#main-key", self.actor_id()),
|
||||||
|
owner: self.actor_id(),
|
||||||
|
public_key_pem: self.public_key().context(location_info!())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: can delete this
|
||||||
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
|
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
|
||||||
Ok(
|
Ok(self.get_public_key()?.to_ext())
|
||||||
PublicKey {
|
|
||||||
id: format!("{}#main-key", self.actor_id()),
|
|
||||||
owner: self.actor_id(),
|
|
||||||
public_key_pem: self.public_key().context(location_info!())?,
|
|
||||||
}
|
|
||||||
.to_ext(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,9 +41,11 @@ use lemmy_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use activitystreams::{
|
||||||
base::{AsBase, BaseExt, ExtendsExt},
|
base::{AsBase, BaseExt, ExtendsExt},
|
||||||
markers::Base,
|
markers::Base,
|
||||||
mime::{FromStrError, Mime},
|
mime::{FromStrError, Mime},
|
||||||
object::{ApObjectExt, Object, ObjectExt, Tombstone, TombstoneExt},
|
object::{kind::ImageType, ApObjectExt, Object, ObjectExt, Tombstone, TombstoneExt},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
@ -74,6 +74,13 @@ pub struct Source {
|
||||||
media_type: MediaTypeMarkdown,
|
media_type: MediaTypeMarkdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ImageObject {
|
||||||
|
content: ImageType,
|
||||||
|
url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
/// Updated is actually the deletion time
|
/// Updated is actually the deletion time
|
||||||
fn create_tombstone<T>(
|
fn create_tombstone<T>(
|
||||||
deleted: bool,
|
deleted: bool,
|
||||||
|
|
|
@ -1,95 +1,128 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
extensions::{context::lemmy_context, person_extension::PersonExtension},
|
check_is_apub_id_valid,
|
||||||
objects::{
|
extensions::{context::lemmy_context, signatures::PublicKey},
|
||||||
check_object_domain,
|
objects::{FromApub, ImageObject, Source, ToApub},
|
||||||
get_source_markdown_value,
|
|
||||||
set_content_and_source,
|
|
||||||
FromApub,
|
|
||||||
FromApubToForm,
|
|
||||||
ToApub,
|
|
||||||
},
|
|
||||||
ActorType,
|
ActorType,
|
||||||
PersonExt,
|
|
||||||
UserTypes,
|
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
actor::{Actor, ApActor, ApActorExt, Endpoints},
|
actor::Endpoints,
|
||||||
base::{BaseExt, ExtendsExt},
|
base::AnyBase,
|
||||||
object::{ApObject, Image, Object, ObjectExt, Tombstone},
|
chrono::{DateTime, FixedOffset},
|
||||||
|
object::{kind::ImageType, Tombstone},
|
||||||
|
primitives::OneOrMany,
|
||||||
|
unparsed::Unparsed,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext2;
|
|
||||||
use anyhow::Context;
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
use lemmy_apub_lib::{
|
||||||
|
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||||
|
verify_domains_match,
|
||||||
|
};
|
||||||
use lemmy_db_queries::{ApubObject, DbPool};
|
use lemmy_db_queries::{ApubObject, DbPool};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
naive_now,
|
naive_now,
|
||||||
source::person::{Person as DbPerson, PersonForm},
|
source::person::{Person as DbPerson, PersonForm},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
location_info,
|
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
|
||||||
settings::structs::Settings,
|
|
||||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub enum UserTypes {
|
||||||
|
Person,
|
||||||
|
Service,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Person {
|
||||||
|
#[serde(rename = "@context")]
|
||||||
|
context: OneOrMany<AnyBase>,
|
||||||
|
kind: UserTypes,
|
||||||
|
id: Url,
|
||||||
|
/// username, set at account creation and can never be changed
|
||||||
|
preferred_username: String,
|
||||||
|
/// displayname (can be changed at any time)
|
||||||
|
name: Option<String>,
|
||||||
|
content: Option<String>,
|
||||||
|
media_type: MediaTypeHtml,
|
||||||
|
source: Option<Source>,
|
||||||
|
/// user avatar
|
||||||
|
icon: Option<ImageObject>,
|
||||||
|
/// user banner
|
||||||
|
image: Option<ImageObject>,
|
||||||
|
matrix_user_id: Option<String>,
|
||||||
|
inbox: Url,
|
||||||
|
/// mandatory field in activitypub, currently empty in lemmy
|
||||||
|
outbox: Url,
|
||||||
|
endpoints: Endpoints<Url>,
|
||||||
|
public_key: PublicKey,
|
||||||
|
published: DateTime<FixedOffset>,
|
||||||
|
updated: Option<DateTime<FixedOffset>>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
unparsed: Unparsed,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: can generate this with a derive macro
|
||||||
|
impl Person {
|
||||||
|
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||||
|
verify_domains_match(&self.id, expected_domain)?;
|
||||||
|
Ok(&self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ToApub for DbPerson {
|
impl ToApub for DbPerson {
|
||||||
type ApubType = PersonExt;
|
type ApubType = Person;
|
||||||
|
|
||||||
async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
|
async fn to_apub(&self, _pool: &DbPool) -> Result<Person, LemmyError> {
|
||||||
let object = Object::<UserTypes>::new_none_type();
|
|
||||||
let mut actor = Actor(object);
|
|
||||||
let kind = if self.bot_account {
|
let kind = if self.bot_account {
|
||||||
UserTypes::Service
|
UserTypes::Service
|
||||||
} else {
|
} else {
|
||||||
UserTypes::Person
|
UserTypes::Person
|
||||||
};
|
};
|
||||||
actor.set_kind(kind);
|
let source = self.bio.clone().map(|bio| Source {
|
||||||
let mut person = ApObject::new(actor);
|
content: bio,
|
||||||
|
media_type: MediaTypeMarkdown::Markdown,
|
||||||
|
});
|
||||||
|
let icon = self.avatar.clone().map(|url| ImageObject {
|
||||||
|
content: ImageType::Image,
|
||||||
|
url: url.into(),
|
||||||
|
});
|
||||||
|
let image = self.banner.clone().map(|url| ImageObject {
|
||||||
|
content: ImageType::Image,
|
||||||
|
url: url.into(),
|
||||||
|
});
|
||||||
|
|
||||||
person
|
let person = Person {
|
||||||
.set_many_contexts(lemmy_context())
|
context: lemmy_context(),
|
||||||
.set_id(self.actor_id.to_owned().into_inner())
|
kind,
|
||||||
.set_published(convert_datetime(self.published));
|
id: self.actor_id.to_owned().into_inner(),
|
||||||
|
preferred_username: self.name.clone(),
|
||||||
if let Some(u) = self.updated {
|
name: self.display_name.clone(),
|
||||||
person.set_updated(convert_datetime(u));
|
content: self.bio.as_ref().map(|b| markdown_to_html(b)),
|
||||||
}
|
media_type: MediaTypeHtml::Html,
|
||||||
|
source,
|
||||||
if let Some(avatar_url) = &self.avatar {
|
icon,
|
||||||
let mut image = Image::new();
|
image,
|
||||||
image.set_url::<Url>(avatar_url.to_owned().into());
|
matrix_user_id: self.matrix_user_id.clone(),
|
||||||
person.set_icon(image.into_any_base()?);
|
published: convert_datetime(self.published),
|
||||||
}
|
outbox: self.get_outbox_url()?,
|
||||||
|
endpoints: Endpoints {
|
||||||
if let Some(banner_url) = &self.banner {
|
|
||||||
let mut image = Image::new();
|
|
||||||
image.set_url::<Url>(banner_url.to_owned().into());
|
|
||||||
person.set_image(image.into_any_base()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(bio) = &self.bio {
|
|
||||||
set_content_and_source(&mut person, bio)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In apub, the "name" is a display name
|
|
||||||
if let Some(i) = self.display_name.to_owned() {
|
|
||||||
person.set_name(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), person);
|
|
||||||
ap_actor
|
|
||||||
.set_preferred_username(self.name.to_owned())
|
|
||||||
.set_outbox(self.get_outbox_url()?)
|
|
||||||
.set_endpoints(Endpoints {
|
|
||||||
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
|
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
public_key: self.get_public_key()?,
|
||||||
let person_ext = PersonExtension::new(self.matrix_user_id.to_owned())?;
|
updated: self.updated.map(convert_datetime),
|
||||||
Ok(Ext2::new(ap_actor, person_ext, self.get_public_key_ext()?))
|
unparsed: Default::default(),
|
||||||
|
inbox: self.inbox_url.clone().into(),
|
||||||
|
};
|
||||||
|
Ok(person)
|
||||||
}
|
}
|
||||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -98,118 +131,54 @@ impl ToApub for DbPerson {
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl FromApub for DbPerson {
|
impl FromApub for DbPerson {
|
||||||
type ApubType = PersonExt;
|
type ApubType = Person;
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
person: &PersonExt,
|
person: &Person,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
expected_domain: Url,
|
_expected_domain: Url,
|
||||||
request_counter: &mut i32,
|
|
||||||
mod_action_allowed: bool,
|
|
||||||
) -> Result<DbPerson, LemmyError> {
|
|
||||||
let person_id = person.id_unchecked().context(location_info!())?.to_owned();
|
|
||||||
let domain = person_id.domain().context(location_info!())?;
|
|
||||||
if domain == Settings::get().hostname {
|
|
||||||
let person = blocking(context.pool(), move |conn| {
|
|
||||||
DbPerson::read_from_apub_id(conn, &person_id.into())
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(person)
|
|
||||||
} else {
|
|
||||||
let person_form = PersonForm::from_apub(
|
|
||||||
person,
|
|
||||||
context,
|
|
||||||
expected_domain,
|
|
||||||
request_counter,
|
|
||||||
mod_action_allowed,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let person = blocking(context.pool(), move |conn| {
|
|
||||||
DbPerson::upsert(conn, &person_form)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
Ok(person)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl FromApubToForm<PersonExt> for PersonForm {
|
|
||||||
async fn from_apub(
|
|
||||||
person: &PersonExt,
|
|
||||||
_context: &LemmyContext,
|
|
||||||
expected_domain: Url,
|
|
||||||
_request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
_mod_action_allowed: bool,
|
_mod_action_allowed: bool,
|
||||||
) -> Result<Self, LemmyError> {
|
) -> Result<DbPerson, LemmyError> {
|
||||||
let avatar = match person.icon() {
|
let name = person.preferred_username.clone();
|
||||||
Some(any_image) => Some(
|
let display_name: Option<String> = person.name.clone();
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())?
|
let bio = person.source.clone().map(|s| s.content);
|
||||||
.context(location_info!())?
|
let shared_inbox = person.endpoints.shared_inbox.clone().map(|s| s.into());
|
||||||
.url()
|
let bot_account = match person.kind {
|
||||||
.context(location_info!())?
|
UserTypes::Person => false,
|
||||||
.as_single_xsd_any_uri()
|
UserTypes::Service => true,
|
||||||
.map(|url| url.to_owned()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let banner = match person.image() {
|
|
||||||
Some(any_image) => Some(
|
|
||||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
|
||||||
.context(location_info!())?
|
|
||||||
.context(location_info!())?
|
|
||||||
.url()
|
|
||||||
.context(location_info!())?
|
|
||||||
.as_single_xsd_any_uri()
|
|
||||||
.map(|url| url.to_owned()),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let name: String = person
|
|
||||||
.inner
|
|
||||||
.preferred_username()
|
|
||||||
.context(location_info!())?
|
|
||||||
.to_string();
|
|
||||||
let display_name: Option<String> = person
|
|
||||||
.name()
|
|
||||||
.map(|n| n.one())
|
|
||||||
.flatten()
|
|
||||||
.map(|n| n.to_owned().xsd_string())
|
|
||||||
.flatten();
|
|
||||||
let bio = get_source_markdown_value(person)?;
|
|
||||||
let shared_inbox = person
|
|
||||||
.inner
|
|
||||||
.endpoints()?
|
|
||||||
.map(|e| e.shared_inbox)
|
|
||||||
.flatten()
|
|
||||||
.map(|s| s.to_owned().into());
|
|
||||||
|
|
||||||
check_slurs(&name)?;
|
check_slurs(&name)?;
|
||||||
check_slurs_opt(&display_name)?;
|
check_slurs_opt(&display_name)?;
|
||||||
check_slurs_opt(&bio)?;
|
check_slurs_opt(&bio)?;
|
||||||
|
check_is_apub_id_valid(&person.id, false)?;
|
||||||
|
|
||||||
Ok(PersonForm {
|
let person_form = PersonForm {
|
||||||
name,
|
name,
|
||||||
display_name: Some(display_name),
|
display_name: Some(display_name),
|
||||||
banned: None,
|
banned: None,
|
||||||
deleted: None,
|
deleted: None,
|
||||||
avatar: avatar.map(|o| o.map(|i| i.into())),
|
avatar: Some(person.icon.clone().map(|i| i.url.into())),
|
||||||
banner: banner.map(|o| o.map(|i| i.into())),
|
banner: Some(person.image.clone().map(|i| i.url.into())),
|
||||||
published: person.inner.published().map(|u| u.to_owned().naive_local()),
|
published: Some(person.published.naive_local()),
|
||||||
updated: person.updated().map(|u| u.to_owned().naive_local()),
|
updated: person.updated.map(|u| u.clone().naive_local()),
|
||||||
actor_id: Some(check_object_domain(person, expected_domain, false)?),
|
actor_id: Some(person.id.clone().into()),
|
||||||
bio: Some(bio),
|
bio: Some(bio),
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
admin: Some(false),
|
admin: Some(false),
|
||||||
bot_account: Some(person.inner.is_kind(&UserTypes::Service)),
|
bot_account: Some(bot_account),
|
||||||
private_key: None,
|
private_key: None,
|
||||||
public_key: Some(Some(person.ext_two.public_key.to_owned().public_key_pem)),
|
public_key: Some(Some(person.public_key.public_key_pem.clone())),
|
||||||
last_refreshed_at: Some(naive_now()),
|
last_refreshed_at: Some(naive_now()),
|
||||||
inbox_url: Some(person.inner.inbox()?.to_owned().into()),
|
inbox_url: Some(person.inbox.to_owned().into()),
|
||||||
shared_inbox_url: Some(shared_inbox),
|
shared_inbox_url: Some(shared_inbox),
|
||||||
matrix_user_id: Some(person.ext_one.matrix_user_id.to_owned()),
|
matrix_user_id: Some(person.matrix_user_id.clone()),
|
||||||
|
};
|
||||||
|
let person = blocking(context.pool(), move |conn| {
|
||||||
|
DbPerson::upsert(conn, &person_form)
|
||||||
})
|
})
|
||||||
|
.await??;
|
||||||
|
Ok(person)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
activities::{extract_community, verify_person_in_community},
|
activities::{extract_community, verify_person_in_community},
|
||||||
extensions::context::lemmy_context,
|
extensions::context::lemmy_context,
|
||||||
fetcher::person::get_or_fetch_and_upsert_person,
|
fetcher::person::get_or_fetch_and_upsert_person,
|
||||||
objects::{create_tombstone, FromApub, Source, ToApub},
|
objects::{create_tombstone, FromApub, ImageObject, Source, ToApub},
|
||||||
ActorType,
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
|
@ -37,8 +37,10 @@ use lemmy_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
|
@ -63,13 +65,6 @@ pub struct Page {
|
||||||
unparsed: Unparsed,
|
unparsed: Unparsed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ImageObject {
|
|
||||||
content: ImageType,
|
|
||||||
url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
||||||
/// the current value, it is a mod action and needs to be verified as such.
|
/// the current value, it is a mod action and needs to be verified as such.
|
||||||
|
|
|
@ -24,8 +24,10 @@ use lemmy_db_schema::source::{
|
||||||
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
|
|
Loading…
Reference in a new issue