feat(ap): ActivityPub spec compliance and profile completeness
Phase 1 — spec compliance: - Add AS_PUBLIC constant; add to/cc fields to CreateActivity, DeleteActivity, UpdateActivity, AddActivity; populate on all broadcast call sites - Add @context to outbox CreateActivity items - Set manuallyApprovesFollowers: true to match actual Pending follow flow - Gate PermissiveVerifier behind FEDERATION_DEBUG env var - Add updated timestamp to Person actor JSON - Improve actor update delivery logging Phase 2a Batch 1 — AP layer: - Add /inbox shared inbox route; add endpoints.sharedInbox to Person - Paginate followers and following collections (20/page, OrderedCollectionPage) Phase 2a Batch 2 — profile completeness: - DB migrations: banner_path, also_known_as columns; user_profile_fields table - ProfileField value object; UserProfileFieldsRepository port - Banner image upload (stored via image-converter, surfaced as image in Person) - alsoKnownAs field in Person (account migration support) - Custom profile fields (up to 4 PropertyValue attachments in Person) - Profile settings UI: banner preview/upload, alsoKnownAs input, fields form - PUT /api/v1/profile/fields API endpoint
This commit is contained in:
@@ -2,30 +2,45 @@ use std::sync::Arc;
|
||||
|
||||
use activitypub_base::{ApUser, ApUserRepository};
|
||||
use async_trait::async_trait;
|
||||
use domain::{ports::UserRepository, value_objects::UserId};
|
||||
use domain::{
|
||||
models::ProfileField,
|
||||
ports::{UserProfileFieldsRepository, UserRepository},
|
||||
value_objects::UserId,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
pub struct DomainUserRepoAdapter {
|
||||
pub repo: Arc<dyn UserRepository>,
|
||||
pub fields_repo: Arc<dyn UserProfileFieldsRepository>,
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
impl DomainUserRepoAdapter {
|
||||
pub fn new(repo: Arc<dyn UserRepository>, base_url: String) -> Self {
|
||||
Self { repo, base_url }
|
||||
pub fn new(
|
||||
repo: Arc<dyn UserRepository>,
|
||||
fields_repo: Arc<dyn UserProfileFieldsRepository>,
|
||||
base_url: String,
|
||||
) -> Self {
|
||||
Self { repo, fields_repo, base_url }
|
||||
}
|
||||
|
||||
fn build_user(&self, u: &domain::models::User) -> ApUser {
|
||||
fn build_user(&self, u: &domain::models::User, fields: Vec<ProfileField>) -> ApUser {
|
||||
let avatar_url = u.avatar_path().and_then(|p| {
|
||||
Url::parse(&format!("{}/images/{}", self.base_url, p)).ok()
|
||||
});
|
||||
let banner_url = u.banner_path().and_then(|p| {
|
||||
Url::parse(&format!("{}/images/{}", self.base_url, p)).ok()
|
||||
});
|
||||
let profile_url = Url::parse(&format!("{}/u/{}", self.base_url, u.username().value())).ok();
|
||||
ApUser {
|
||||
id: u.id().value(),
|
||||
username: u.username().value().to_string(),
|
||||
bio: u.bio().map(|s| s.to_string()),
|
||||
avatar_url,
|
||||
banner_url,
|
||||
also_known_as: u.also_known_as().map(|s| s.to_string()),
|
||||
profile_url,
|
||||
attachment: fields,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,13 +49,23 @@ impl DomainUserRepoAdapter {
|
||||
impl ApUserRepository for DomainUserRepoAdapter {
|
||||
async fn find_by_id(&self, id: uuid::Uuid) -> anyhow::Result<Option<ApUser>> {
|
||||
let user_id = UserId::from_uuid(id);
|
||||
Ok(self.repo.find_by_id(&user_id).await?.as_ref().map(|u| self.build_user(u)))
|
||||
let user = match self.repo.find_by_id(&user_id).await? {
|
||||
Some(u) => u,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let fields = self.fields_repo.get_fields(&user_id).await.unwrap_or_default();
|
||||
Ok(Some(self.build_user(&user, fields)))
|
||||
}
|
||||
|
||||
async fn find_by_username(&self, username: &str) -> anyhow::Result<Option<ApUser>> {
|
||||
use domain::value_objects::Username;
|
||||
let uname = Username::new(username.to_string()).map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
||||
Ok(self.repo.find_by_username(&uname).await?.as_ref().map(|u| self.build_user(u)))
|
||||
let user = match self.repo.find_by_username(&uname).await? {
|
||||
Some(u) => u,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let fields = self.fields_repo.get_fields(user.id()).await.unwrap_or_default();
|
||||
Ok(Some(self.build_user(&user, fields)))
|
||||
}
|
||||
|
||||
async fn count_users(&self) -> anyhow::Result<usize> {
|
||||
|
||||
Reference in New Issue
Block a user