fmt
Some checks failed
CI / Check / Test / Build (push) Has been cancelled

This commit is contained in:
2026-05-13 23:38:57 +02:00
parent 7415b91e23
commit 19171806b9
142 changed files with 4140 additions and 2025 deletions

View File

@@ -27,7 +27,9 @@ impl ApObjectHandler for CompositeObjectHandler {
before: Option<DateTime<Utc>>,
limit: usize,
) -> anyhow::Result<Vec<(Url, serde_json::Value, DateTime<Utc>)>> {
self.review.get_local_objects_page(user_id, before, limit).await
self.review
.get_local_objects_page(user_id, before, limit)
.await
}
async fn on_create(

View File

@@ -40,11 +40,15 @@ impl ActivityPubEventHandler {
impl EventHandler for ActivityPubEventHandler {
async fn handle(&self, event: &DomainEvent) -> Result<(), DomainError> {
match event {
DomainEvent::ReviewLogged { review_id, user_id, .. } => self
DomainEvent::ReviewLogged {
review_id, user_id, ..
} => self
.on_review_logged(user_id, review_id)
.await
.map_err(|e| DomainError::InfrastructureError(e.to_string())),
DomainEvent::ReviewUpdated { review_id, user_id, .. } => self
DomainEvent::ReviewUpdated {
review_id, user_id, ..
} => self
.on_review_updated(user_id, review_id)
.await
.map_err(|e| DomainError::InfrastructureError(e.to_string())),
@@ -65,7 +69,14 @@ impl EventHandler for ActivityPubEventHandler {
external_metadata_id,
added_at,
} => self
.on_watchlist_added(user_id, movie_id, movie_title, *release_year, external_metadata_id, added_at)
.on_watchlist_added(
user_id,
movie_id,
movie_title,
*release_year,
external_metadata_id,
added_at,
)
.await
.map_err(|e| DomainError::InfrastructureError(e.to_string())),
DomainEvent::WatchlistEntryRemoved { user_id, movie_id } => self
@@ -124,7 +135,11 @@ impl ActivityPubEventHandler {
Ok(())
}
async fn on_review_updated(&self, user_id: &UserId, review_id: &ReviewId) -> anyhow::Result<()> {
async fn on_review_updated(
&self,
user_id: &UserId,
review_id: &ReviewId,
) -> anyhow::Result<()> {
let review = match self.review_repository.get_review_by_id(review_id).await? {
Some(r) => r,
None => return Ok(()),
@@ -170,7 +185,11 @@ impl ActivityPubEventHandler {
Ok(())
}
async fn on_review_deleted(&self, user_id: &UserId, review_id: &ReviewId) -> anyhow::Result<()> {
async fn on_review_deleted(
&self,
user_id: &UserId,
review_id: &ReviewId,
) -> anyhow::Result<()> {
let ap_id = review_url(&self.base_url, review_id);
self.ap_service
.broadcast_delete_to_followers(user_id.value(), ap_id)
@@ -197,7 +216,10 @@ impl ActivityPubEventHandler {
.await
.ok()
.flatten()
.and_then(|m| m.poster_path().map(|p| format!("{}/images/{}", self.base_url, p.value())));
.and_then(|m| {
m.poster_path()
.map(|p| format!("{}/images/{}", self.base_url, p.value()))
});
let added_at_utc =
chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(*added_at, chrono::Utc);

View File

@@ -4,9 +4,9 @@ pub mod objects;
pub mod port;
pub mod remote_review_repository;
pub mod review_handler;
pub mod watchlist_handler;
pub(crate) mod urls;
pub mod user_adapter;
pub mod watchlist_handler;
// Re-export the generic base types that callers need
pub use activitypub_base::{
@@ -21,22 +21,22 @@ pub use review_handler::ReviewObjectHandler;
pub use user_adapter::DomainUserRepoAdapter;
pub struct ActivityPubWire {
pub service: std::sync::Arc<dyn ActivityPubPort>,
pub router: axum::Router,
pub service: std::sync::Arc<dyn ActivityPubPort>,
pub router: axum::Router,
pub event_handler: std::sync::Arc<dyn domain::ports::EventHandler>,
}
pub async fn wire(
federation_repo: std::sync::Arc<dyn FederationRepository>,
review_store: std::sync::Arc<dyn RemoteReviewRepository>,
federation_repo: std::sync::Arc<dyn FederationRepository>,
review_store: std::sync::Arc<dyn RemoteReviewRepository>,
remote_watchlist_repo: std::sync::Arc<dyn domain::ports::RemoteWatchlistRepository>,
user_repo: std::sync::Arc<dyn domain::ports::UserRepository>,
movie_repo: std::sync::Arc<dyn domain::ports::MovieRepository>,
review_repo: std::sync::Arc<dyn domain::ports::ReviewRepository>,
diary_repo: std::sync::Arc<dyn domain::ports::DiaryRepository>,
base_url: String,
allow_registration: bool,
event_publisher: std::sync::Arc<dyn domain::ports::EventPublisher>,
user_repo: std::sync::Arc<dyn domain::ports::UserRepository>,
movie_repo: std::sync::Arc<dyn domain::ports::MovieRepository>,
review_repo: std::sync::Arc<dyn domain::ports::ReviewRepository>,
diary_repo: std::sync::Arc<dyn domain::ports::DiaryRepository>,
base_url: String,
allow_registration: bool,
event_publisher: std::sync::Arc<dyn domain::ports::EventPublisher>,
) -> anyhow::Result<ActivityPubWire> {
let review_handler = std::sync::Arc::new(ReviewObjectHandler {
movie_repository: std::sync::Arc::clone(&movie_repo),

View File

@@ -74,8 +74,7 @@ pub fn review_to_ap_object(
let tag = vec![
ApHashtag {
kind: "Hashtag".to_string(),
href: Url::parse(&format!("{}/tags/moviesdiary", base_url))
.expect("valid base_url"),
href: Url::parse(&format!("{}/tags/moviesdiary", base_url)).expect("valid base_url"),
name: "#MoviesDiary".to_string(),
},
ApHashtag {
@@ -152,8 +151,7 @@ pub fn watchlist_to_ap_object(
let tag = vec![
ApHashtag {
kind: "Hashtag".to_string(),
href: Url::parse(&format!("{}/tags/moviesdiary", base_url))
.expect("valid base_url"),
href: Url::parse(&format!("{}/tags/moviesdiary", base_url)).expect("valid base_url"),
name: "#MoviesDiary".to_string(),
},
ApHashtag {

View File

@@ -101,9 +101,10 @@ impl ApObjectHandler for ReviewObjectHandler {
chrono::DateTime::from_naive_utc_and_offset(*review.watched_at(), chrono::Utc);
if let Some(cutoff) = before
&& published >= cutoff {
continue;
}
&& published >= cutoff
{
continue;
}
let ap_id = review_url(&self.base_url, review.id());
let actor_url = actor_url(&self.base_url, user_id);
@@ -118,7 +119,10 @@ impl ApObjectHandler for ReviewObjectHandler {
.as_ref()
.map(|m| m.title().value().to_string())
.unwrap_or_else(|| "Unknown".to_string());
let release_year = movie.as_ref().map(|m| m.release_year().value()).unwrap_or(0);
let release_year = movie
.as_ref()
.map(|m| m.release_year().value())
.unwrap_or(0);
let poster_url = movie
.as_ref()
.and_then(|m| m.poster_path())

View File

@@ -4,7 +4,10 @@ use super::*;
fn normalize_hashtag_strips_non_alphanumeric() {
assert_eq!(normalize_hashtag("The Dark Knight"), "TheDarkKnight");
assert_eq!(normalize_hashtag("Schindler's List"), "SchindlersList");
assert_eq!(normalize_hashtag("2001: A Space Odyssey"), "2001ASpaceOdyssey");
assert_eq!(
normalize_hashtag("2001: A Space Odyssey"),
"2001ASpaceOdyssey"
);
}
#[test]

View File

@@ -15,6 +15,9 @@ pub fn review_url(base_url: &str, review_id: &ReviewId) -> Url {
/// Builds the canonical watchlist entry URL: `{base_url}/users/{user_id}/watchlist/{movie_id}`
pub fn watchlist_entry_url(base_url: &str, user_id: uuid::Uuid, movie_id: uuid::Uuid) -> Url {
Url::parse(&format!("{}/users/{}/watchlist/{}", base_url, user_id, movie_id))
.expect("base_url is always a valid URL prefix")
Url::parse(&format!(
"{}/users/{}/watchlist/{}",
base_url, user_id, movie_id
))
.expect("base_url is always a valid URL prefix")
}

View File

@@ -2,10 +2,7 @@ use std::sync::Arc;
use activitypub_base::{ApProfileField, ApUser, ApUserRepository};
use async_trait::async_trait;
use domain::{
ports::UserRepository,
value_objects::UserId,
};
use domain::{ports::UserRepository, value_objects::UserId};
use url::Url;
pub struct DomainUserRepoAdapter {
@@ -14,20 +11,17 @@ pub struct DomainUserRepoAdapter {
}
impl DomainUserRepoAdapter {
pub fn new(
repo: Arc<dyn UserRepository>,
base_url: String,
) -> Self {
pub fn new(repo: Arc<dyn UserRepository>, base_url: String) -> Self {
Self { repo, base_url }
}
fn build_user(&self, u: &domain::models::User) -> 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 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(),
@@ -37,7 +31,14 @@ impl DomainUserRepoAdapter {
banner_url,
also_known_as: u.also_known_as().map(|s| s.to_string()),
profile_url,
attachment: u.profile_fields().iter().map(|f| ApProfileField { name: f.name.clone(), value: f.value.clone() }).collect(),
attachment: u
.profile_fields()
.iter()
.map(|f| ApProfileField {
name: f.name.clone(),
value: f.value.clone(),
})
.collect(),
}
}
}
@@ -55,7 +56,8 @@ impl ApUserRepository for DomainUserRepoAdapter {
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()))?;
let uname =
Username::new(username.to_string()).map_err(|e| anyhow::anyhow!(e.to_string()))?;
let user = match self.repo.find_by_username(&uname).await? {
Some(u) => u,
None => return Ok(None),
@@ -64,7 +66,10 @@ impl ApUserRepository for DomainUserRepoAdapter {
}
async fn count_users(&self) -> anyhow::Result<usize> {
Ok(self.repo.list_with_stats().await
Ok(self
.repo
.list_with_stats()
.await
.map_err(|e| anyhow::anyhow!(e.to_string()))?
.len())
}