"actix-web",
"chrono",
"diesel",
+ "itertools",
"lemmy_db_schema",
"lemmy_db_views",
"lemmy_db_views_actor",
get_local_user_view_from_jwt,
get_local_user_view_from_jwt_opt,
is_admin,
+ resolve_actor_identifier,
send_application_approved_email,
site::*,
};
-use lemmy_apub::{
- fetcher::{
- search::{search_by_apub_id, SearchableObjects},
- webfinger::webfinger_resolve,
- },
- objects::community::ApubCommunity,
- EndpointType,
-};
+use lemmy_apub::fetcher::search::{search_by_apub_id, SearchableObjects};
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,
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 {
- webfinger_resolve::<ApubCommunity>(name, EndpointType::Community, context, &mut 0)
+ resolve_actor_identifier::<Community>(name, context.pool())
.await
.ok()
+ .map(|c| c.actor_id)
} else {
None
};
serde_json = { version = "1.0.72", features = ["preserve_order"] }
tracing = "0.1.29"
url = "2.2.2"
+itertools = "0.10.3"
pub mod websocket;
use crate::site::FederatedInstances;
+use itertools::Itertools;
use lemmy_db_schema::{
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
source::{
secret::Secret,
site::Site,
},
- traits::{Crud, Readable},
+ traits::{ApubActor, Crud, Readable},
DbPool,
};
use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
}
Ok(())
}
+
+/// Resolve actor identifier (eg `!news@example.com`) from local database to avoid network requests.
+/// This only works for local actors, and remote actors which were previously fetched (so it doesnt
+/// trigger any new fetch).
+#[tracing::instrument(skip_all)]
+pub async fn resolve_actor_identifier<Actor>(
+ identifier: &str,
+ pool: &DbPool,
+) -> Result<Actor, LemmyError>
+where
+ Actor: ApubActor + Send + 'static,
+{
+ // remote actor
+ if identifier.contains('@') {
+ let (name, domain) = identifier
+ .splitn(2, '@')
+ .collect_tuple()
+ .expect("invalid query");
+ let name = name.to_string();
+ let domain = format!("{}://{}", Settings::get().get_protocol_string(), domain);
+ Ok(
+ blocking(pool, move |conn| {
+ Actor::read_from_name_and_domain(conn, &name, &domain)
+ })
+ .await??,
+ )
+ }
+ // local actor
+ else {
+ let identifier = identifier.to_string();
+ Ok(blocking(pool, move |conn| Actor::read_from_name(conn, &identifier)).await??)
+ }
+}
check_private_instance,
comment::*,
get_local_user_view_from_jwt_opt,
-};
-use lemmy_apub::{
- fetcher::webfinger::webfinger_resolve,
- objects::community::ApubCommunity,
- EndpointType,
+ resolve_actor_identifier,
};
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
+ source::community::Community,
traits::DeleteableOrRemoveable,
ListingType,
SortType,
let community_id = data.community_id;
let community_actor_id = if let Some(name) = &data.community_name {
- webfinger_resolve::<ApubCommunity>(name, EndpointType::Community, context, &mut 0)
+ resolve_actor_identifier::<Community>(name, context.pool())
.await
.ok()
+ .map(|c| c.actor_id)
} else {
None
};
check_private_instance,
community::*,
get_local_user_view_from_jwt_opt,
+ resolve_actor_identifier,
};
-use lemmy_apub::{
- fetcher::webfinger::webfinger_resolve,
- objects::community::ApubCommunity,
- EndpointType,
-};
-use lemmy_apub_lib::object_id::ObjectId;
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
+ source::community::Community,
traits::DeleteableOrRemoveable,
ListingType,
SortType,
Some(id) => id,
None => {
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
- let community_actor_id =
- webfinger_resolve::<ApubCommunity>(&name, EndpointType::Community, context, &mut 0)
- .await?;
-
- ObjectId::<ApubCommunity>::new(community_actor_id)
- .dereference(context, context.client(), &mut 0)
+ resolve_actor_identifier::<Community>(&name, context.pool())
.await
.map_err(LemmyError::from)
.map_err(|e| e.with_message("couldnt_find_community"))?
get_local_user_view_from_jwt_opt,
mark_post_as_read,
post::*,
-};
-use lemmy_apub::{
- fetcher::webfinger::webfinger_resolve,
- objects::community::ApubCommunity,
- EndpointType,
+ resolve_actor_identifier,
};
use lemmy_db_schema::{
from_opt_str_to_opt_enum,
+ source::community::Community,
traits::DeleteableOrRemoveable,
ListingType,
SortType,
let limit = data.limit;
let community_id = data.community_id;
let community_actor_id = if let Some(name) = &data.community_name {
- webfinger_resolve::<ApubCommunity>(name, EndpointType::Community, context, &mut 0)
+ resolve_actor_identifier::<Community>(name, context.pool())
.await
.ok()
+ .map(|c| c.actor_id)
} else {
None
};
check_private_instance,
get_local_user_view_from_jwt_opt,
person::*,
+ resolve_actor_identifier,
};
-use lemmy_apub::{
- fetcher::webfinger::webfinger_resolve,
- objects::person::ApubPerson,
- EndpointType,
-};
-use lemmy_apub_lib::object_id::ObjectId;
-use lemmy_db_schema::{from_opt_str_to_opt_enum, SortType};
+use lemmy_db_schema::{from_opt_str_to_opt_enum, source::person::Person, SortType};
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
use lemmy_db_views_actor::{
community_moderator_view::CommunityModeratorView,
.username
.to_owned()
.unwrap_or_else(|| "admin".to_string());
- let actor_id =
- webfinger_resolve::<ApubPerson>(&name, EndpointType::Person, context, &mut 0).await?;
- let person = ObjectId::<ApubPerson>::new(actor_id)
- .dereference(context, context.client(), &mut 0)
- .await;
- person
+ resolve_actor_identifier::<Person>(&name, context.pool())
+ .await
.map_err(LemmyError::from)
.map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?
.id
use crate::{
- fetcher::webfinger::webfinger_resolve,
+ fetcher::webfinger::webfinger_resolve_actor,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::objects::{group::Group, note::Note, page::Page, person::Person},
- EndpointType,
};
use chrono::NaiveDateTime;
use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
let (kind, identifier) = query.split_at(1);
match kind {
"@" => {
- let id = webfinger_resolve::<ApubPerson>(
- identifier,
- EndpointType::Person,
- context,
- request_counter,
- )
- .await?;
+ let id =
+ webfinger_resolve_actor::<ApubPerson>(identifier, context, request_counter).await?;
Ok(SearchableObjects::Person(
ObjectId::new(id)
.dereference(context, context.client(), request_counter)
))
}
"!" => {
- let id = webfinger_resolve::<ApubCommunity>(
- identifier,
- EndpointType::Community,
- context,
- request_counter,
- )
- .await?;
+ let id =
+ webfinger_resolve_actor::<ApubCommunity>(identifier, context, request_counter).await?;
Ok(SearchableObjects::Community(
ObjectId::new(id)
.dereference(context, context.client(), request_counter)
-use crate::{generate_local_apub_endpoint, EndpointType};
use itertools::Itertools;
use lemmy_apub_lib::{
object_id::ObjectId,
pub links: Vec<WebfingerLink>,
}
-/// Takes in a shortname of the type dessalines@xyz.tld or dessalines (assumed to be local), and
-/// outputs the actor id. Used in the API for communities and users.
-///
-/// TODO: later provide a method in ApubObject to generate the endpoint, so that we dont have to
-/// pass in EndpointType
-#[tracing::instrument(skip_all)]
-pub async fn webfinger_resolve<Kind>(
- identifier: &str,
- endpoint_type: EndpointType,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<DbUrl, LemmyError>
-where
- Kind: ApubObject<DataType = LemmyContext> + ActorType + Send + 'static,
- for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
-{
- // remote actor
- if identifier.contains('@') {
- webfinger_resolve_actor::<Kind>(identifier, context, request_counter).await
- }
- // local actor
- else {
- let domain = context.settings().get_protocol_and_hostname();
- Ok(generate_local_apub_endpoint(
- endpoint_type,
- identifier,
- &domain,
- )?)
- }
-}
-
/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
/// using webfinger.
#[tracing::instrument(skip_all)]
use actix_web::{web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
-use lemmy_db_schema::source::community::Community;
+use lemmy_db_schema::{source::community::Community, traits::ApubActor};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use actix_web::{web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::ApubObject;
-use lemmy_db_schema::source::person::Person;
+use lemmy_db_schema::{source::person::Person, traits::ApubActor};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
let user_name = info.into_inner().user_name;
// TODO: this needs to be able to read deleted persons, so that it can send tombstones
let person: ApubPerson = blocking(context.pool(), move |conn| {
- Person::find_by_name(conn, &user_name)
+ Person::read_from_name(conn, &user_name)
})
.await??
.into();
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let person = blocking(context.pool(), move |conn| {
- Person::find_by_name(conn, &info.user_name)
+ Person::read_from_name(conn, &info.user_name)
})
.await??;
let outbox = PersonOutbox::new(person).await?;
traits::{ActorType, ApubObject},
values::MediaTypeMarkdown,
};
-use lemmy_db_schema::source::community::Community;
+use lemmy_db_schema::{source::community::Community, traits::ApubActor};
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::{
utils::{convert_datetime, markdown_to_html},
use lemmy_db_schema::{
naive_now,
source::person::{Person as DbPerson, PersonForm},
+ traits::ApubActor,
};
use lemmy_utils::{
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
CommunityPersonBanForm,
CommunitySafe,
},
- traits::{Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
+ traits::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
+};
+use diesel::{
+ dsl::*,
+ result::Error,
+ ExpressionMethods,
+ PgConnection,
+ QueryDsl,
+ RunQueryDsl,
+ TextExpressionMethods,
};
-use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use url::Url;
mod safe_type {
}
impl Community {
- pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error> {
- use crate::schema::community::dsl::*;
- community
- .filter(local.eq(true))
- .filter(lower(name).eq(lower(community_name)))
- .first::<Self>(conn)
- }
-
pub fn update_deleted(
conn: &PgConnection,
community_id: CommunityId,
.set(community_form)
.get_result::<Self>(conn)
}
- pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
- use crate::schema::community::dsl::*;
- let object_id: DbUrl = object_id.into();
- Ok(
- community
- .filter(actor_id.eq(object_id))
- .first::<Community>(conn)
- .ok()
- .map(Into::into),
- )
- }
}
impl Joinable for CommunityModerator {
}
}
+impl ApubActor for Community {
+ fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
+ use crate::schema::community::dsl::*;
+ let object_id: DbUrl = object_id.into();
+ Ok(
+ community
+ .filter(actor_id.eq(object_id))
+ .first::<Community>(conn)
+ .ok()
+ .map(Into::into),
+ )
+ }
+
+ fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error> {
+ use crate::schema::community::dsl::*;
+ community
+ .filter(local.eq(true))
+ .filter(lower(name).eq(lower(community_name)))
+ .first::<Self>(conn)
+ }
+
+ fn read_from_name_and_domain(
+ conn: &PgConnection,
+ community_name: &str,
+ protocol_domain: &str,
+ ) -> Result<Community, Error> {
+ use crate::schema::community::dsl::*;
+ community
+ .filter(lower(name).eq(lower(community_name)))
+ .filter(actor_id.like(format!("{}%", protocol_domain)))
+ .first::<Self>(conn)
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::{
newtypes::{DbUrl, PersonId},
schema::person::dsl::*,
source::person::{Person, PersonForm, PersonSafe},
- traits::Crud,
+ traits::{ApubActor, Crud},
+};
+use diesel::{
+ dsl::*,
+ result::Error,
+ ExpressionMethods,
+ PgConnection,
+ QueryDsl,
+ RunQueryDsl,
+ TextExpressionMethods,
};
-use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use url::Url;
mod safe_type {
.get_result::<Self>(conn)
}
- pub fn find_by_name(conn: &PgConnection, from_name: &str) -> Result<Person, Error> {
- person
- .filter(deleted.eq(false))
- .filter(local.eq(true))
- .filter(lower(name).eq(lower(from_name)))
- .first::<Person>(conn)
- }
-
pub fn mark_as_updated(conn: &PgConnection, person_id: PersonId) -> Result<Person, Error> {
diesel::update(person.find(person_id))
.set((last_refreshed_at.eq(naive_now()),))
.get_result::<Self>(conn)
}
- pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
- use crate::schema::person::dsl::*;
- let object_id: DbUrl = object_id.into();
- Ok(
- person
- .filter(deleted.eq(false))
- .filter(actor_id.eq(object_id))
- .first::<Person>(conn)
- .ok()
- .map(Into::into),
- )
- }
-
pub fn update_deleted(
conn: &PgConnection,
person_id: PersonId,
}
}
+impl ApubActor for Person {
+ fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
+ use crate::schema::person::dsl::*;
+ let object_id: DbUrl = object_id.into();
+ Ok(
+ person
+ .filter(deleted.eq(false))
+ .filter(actor_id.eq(object_id))
+ .first::<Person>(conn)
+ .ok()
+ .map(Into::into),
+ )
+ }
+
+ fn read_from_name(conn: &PgConnection, from_name: &str) -> Result<Person, Error> {
+ person
+ .filter(deleted.eq(false))
+ .filter(local.eq(true))
+ .filter(lower(name).eq(lower(from_name)))
+ .first::<Person>(conn)
+ }
+
+ fn read_from_name_and_domain(
+ conn: &PgConnection,
+ person_name: &str,
+ protocol_domain: &str,
+ ) -> Result<Person, Error> {
+ use crate::schema::person::dsl::*;
+ person
+ .filter(lower(name).eq(lower(person_name)))
+ .filter(actor_id.like(format!("{}%", protocol_domain)))
+ .first::<Self>(conn)
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::{establish_unpooled_connection, source::person::*, traits::Crud};
use crate::newtypes::{CommunityId, PersonId};
use diesel::{result::Error, PgConnection};
+use url::Url;
pub trait Crud {
type Form;
where
Self: Sized;
}
+
+pub trait ApubActor {
+ // TODO: this should be in a trait ApubObject (and implemented for Post, Comment, PrivateMessage as well)
+ fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error>
+ where
+ Self: Sized;
+ fn read_from_name(conn: &PgConnection, actor_name: &str) -> Result<Self, Error>
+ where
+ Self: Sized;
+ fn read_from_name_and_domain(
+ conn: &PgConnection,
+ actor_name: &str,
+ protocol_domain: &str,
+ ) -> Result<Self, Error>
+ where
+ Self: Sized;
+}
use lemmy_db_schema::{
newtypes::LocalUserId,
source::{community::Community, local_user::LocalUser, person::Person},
- traits::Crud,
+ traits::{ApubActor, Crud},
ListingType,
SortType,
};
protocol_and_hostname: &str,
) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(conn)?;
- let person = Person::find_by_name(conn, user_name)?;
+ let person = Person::read_from_name(conn, user_name)?;
let posts = PostQueryBuilder::create(conn)
.listing_type(ListingType::All)
use anyhow::Context;
use lemmy_api_common::blocking;
use lemmy_apub::fetcher::webfinger::{WebfingerLink, WebfingerResponse};
-use lemmy_db_schema::source::{community::Community, person::Person};
+use lemmy_db_schema::{
+ source::{community::Community, person::Person},
+ traits::ApubActor,
+};
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
let name_ = name.clone();
let user_id: Option<Url> = blocking(context.pool(), move |conn| {
- Person::find_by_name(conn, &name_)
+ Person::read_from_name(conn, &name_)
})
.await?
.ok()