hyaenidae/profiles/src/apub/results/profile.rs
asonix 83b447b780 Profiles: Federation fixes
- Fix image federation
- Enable submission & comment create/update federation
- Improve checks to ensure we don't federate when we don't need to
2021-01-17 15:01:45 -06:00

315 lines
9.8 KiB
Rust

use super::{ProfileCreated, ProfileDeleted, ProfileUpdated};
use crate::{
apub::{ExtendedPerson, PublicKey, PublicKeyInner},
store::FileSource,
Context, Error, OnBehalfOf, Outbound, Required,
};
use activitystreams::{
activity::{Create, Delete, Update},
actor::{ApActor, Endpoints, Person},
base::AnyBase,
context,
object::Image,
prelude::*,
public, security,
};
use std::collections::HashSet;
use url::Url;
use uuid::Uuid;
fn build_image(file_id: Uuid, ctx: &Context) -> Result<AnyBase, Error> {
if let Some(apub_id) = ctx.apub.apub_for_file(file_id)? {
ctx.apub.object(&apub_id)?.req("image object by id")
} else {
let file = ctx.store.files.by_id(file_id)?.req("file by id")?;
let FileSource::PictRs(pictrs_file) = file.source();
let image_id = ctx.apub.info.gen_id().req("id generation")?;
ctx.apub.file(&image_id, file_id)?;
let mut image = Image::new();
image
.set_id(image_id)
.set_url(ctx.pictrs.image_url(pictrs_file.key()))
.set_media_type(pictrs_file.media_type().clone());
let image = image.into_any_base()?;
ctx.apub.store_object(&image)?;
Ok(image)
}
}
impl Outbound for ProfileCreated {
fn id(&self) -> Option<Uuid> {
Some(self.profile_id)
}
fn behalf(&self, _: &Context) -> Result<OnBehalfOf, Error> {
Ok(OnBehalfOf::Server)
}
fn inboxes(&self, ctx: &Context) -> Result<Vec<Url>, Error> {
let inboxes = ctx
.store
.federated_servers()
.filter_map(|server_id| {
let endpoints = ctx.apub.endpoints_for_server(server_id).ok()??;
Some(endpoints.inbox().to_owned())
})
.collect::<HashSet<_>>();
Ok(inboxes.into_iter().collect())
}
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
let profile = ctx
.store
.profiles
.by_id(self.profile_id)?
.req("profile by id")?;
let server_id = ctx.store.servers.get_self()?.req("get self server")?;
let application_id = ctx
.apub
.apub_for_server(server_id)?
.req("apub for self server")?;
let person_id = ctx.apub.info.gen_id().req("id generation")?;
let public_key_id = ctx.apub.info.public_key(&person_id).req("public key")?;
let following = ctx.apub.info.following(&person_id).req("following")?;
let followers = ctx.apub.info.followers(&person_id).req("followers")?;
let inbox = ctx.apub.info.inbox(&person_id).req("inbox")?;
let outbox = ctx.apub.info.outbox(&person_id).req("outbox")?;
let shared_inbox = ctx.apub.info.shared_inbox();
ctx.apub.profile(&person_id, profile.id())?;
ctx.apub.store_profile_endpoints(
profile.id(),
&person_id,
crate::apub::Endpoints {
inbox: inbox.clone(),
outbox: outbox.clone(),
following: Some(following.clone()),
followers: Some(followers.clone()),
shared_inbox: Some(shared_inbox.clone()),
public_key: public_key_id.clone(),
},
)?;
let public_key_pem = ctx.apub.gen_profile_keys(profile.id(), &public_key_id)?;
let mut person = ExtendedPerson::new(
ApActor::new(inbox, Person::new()),
PublicKey {
public_key: PublicKeyInner {
id: public_key_id,
owner: person_id.clone(),
public_key_pem,
},
},
);
person
.set_id(person_id.clone())
.set_following(following)
.set_followers(followers)
.set_outbox(outbox)
.set_endpoints(Endpoints {
shared_inbox: Some(shared_inbox),
..Endpoints::default()
})
.set_preferred_username(profile.handle())
.set_published(profile.published().into());
if let Some(display_name) = profile.display_name() {
person.set_name(display_name);
}
if let Some(description) = profile.description() {
person.set_summary(description);
}
if let Some(icon) = profile.icon() {
let image = build_image(icon, ctx)?;
person.set_icon(image);
}
if let Some(banner) = profile.banner() {
let image = build_image(banner, ctx)?;
person.set_image(image);
}
if !profile.login_required() {
person.add_to(public());
}
let any_base = person.into_any_base()?;
ctx.apub.store_object(&any_base)?;
let mut create = Create::new(application_id, any_base);
create
.set_id(ctx.apub.info.gen_id().req("id generation")?)
.add_context(context())
.add_context(security());
let any_base = create.into_any_base()?;
ctx.apub.store_object(&any_base)?;
Ok(any_base)
}
}
impl Outbound for ProfileUpdated {
fn id(&self) -> Option<Uuid> {
Some(self.profile_id)
}
fn behalf(&self, _: &Context) -> Result<OnBehalfOf, Error> {
Ok(OnBehalfOf::Profile(self.profile_id))
}
fn inboxes(&self, ctx: &Context) -> Result<Vec<Url>, Error> {
let inboxes = ctx
.store
.federated_servers()
.filter_map(|server_id| {
let endpoints = ctx.apub.endpoints_for_server(server_id).ok()??;
Some(endpoints.inbox)
})
.collect::<HashSet<_>>();
Ok(inboxes.into_iter().collect())
}
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
let profile = ctx
.store
.profiles
.by_id(self.profile_id)?
.req("profile by id")?;
let endpoints = ctx
.apub
.endpoints_for_profile(self.profile_id)?
.req("endpoints for profile")?;
let public_key_id = ctx
.apub
.key_for_profile(profile.id())?
.req("key for profile")?;
let public_key_pem = ctx
.apub
.public_key_for_id(&public_key_id)?
.req("public key for id")?;
let person_id = ctx
.apub
.apub_for_profile(self.profile_id)?
.req("apub id for profile")?;
let mut person = ExtendedPerson::new(
ApActor::new(endpoints.inbox, Person::new()),
PublicKey {
public_key: PublicKeyInner {
id: public_key_id,
owner: person_id.clone(),
public_key_pem,
},
},
);
person
.set_id(person_id.clone())
.set_outbox(endpoints.outbox)
.set_preferred_username(profile.handle())
.set_published(profile.published().into());
if let Some(following) = endpoints.following {
person.set_following(following);
}
if let Some(followers) = endpoints.followers {
person.set_followers(followers);
}
if let Some(shared_inbox) = endpoints.shared_inbox {
person.set_endpoints(Endpoints {
shared_inbox: Some(shared_inbox),
..Endpoints::default()
});
}
if let Some(display_name) = profile.display_name() {
person.set_name(display_name);
}
if let Some(description) = profile.description() {
person.set_summary(description);
}
if let Some(icon) = profile.icon() {
let image = build_image(icon, ctx)?;
person.set_icon(image);
}
if let Some(banner) = profile.banner() {
let image = build_image(banner, ctx)?;
person.set_image(image);
}
if !profile.login_required() {
person.add_to(public());
}
let any_base = person.into_any_base()?;
ctx.apub.store_object(&any_base)?;
let mut update = Update::new(person_id, any_base);
update
.set_id(ctx.apub.info.gen_id().req("id generation")?)
.add_context(context())
.add_context(security());
let any_base = update.into_any_base()?;
ctx.apub.store_object(&any_base)?;
Ok(any_base)
}
}
impl Outbound for ProfileDeleted {
fn id(&self) -> Option<Uuid> {
None
}
fn behalf(&self, _: &Context) -> Result<OnBehalfOf, Error> {
Ok(OnBehalfOf::Server)
}
fn inboxes(&self, ctx: &Context) -> Result<Vec<Url>, Error> {
let inboxes = ctx
.store
.federated_servers()
.filter_map(|server_id| {
let endpoints = ctx.apub.endpoints_for_server(server_id).ok()??;
Some(endpoints.inbox().to_owned())
})
.collect::<HashSet<_>>();
Ok(inboxes.into_iter().collect())
}
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
let server_id = ctx.store.servers.get_self()?.req("get self server")?;
let application_id = ctx
.apub
.apub_for_server(server_id)?
.req("apub for self server")?;
let person_id = ctx
.apub
.apub_for_profile(self.profile_id)?
.req("apub id for profile")?;
let person = ctx.apub.object(&person_id)?.req("apub for person")?;
let mut delete = Delete::new(application_id, person);
delete
.set_id(ctx.apub.info.gen_id().req("id generation")?)
.add_context(context())
.add_context(security());
let any_base = delete.into_any_base()?;
ctx.apub.store_object(&any_base)?;
Ok(any_base)
}
}