bump k-ap 0.5.0, decompose repo traits, LocalObject, ActivityStreamsType enum, split follow modules
Some checks failed
CI / Check / Test (push) Has been cancelled

This commit is contained in:
2026-07-27 11:46:24 +02:00
parent fdf286ca94
commit 839cababe4
28 changed files with 475 additions and 497 deletions

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
k-ap = { version = "0.4.6", registry = "gitea" }
k-ap = { version = "0.5.0", registry = "gitea" }
domain = { workspace = true }
axum = { workspace = true }
serde = { workspace = true }

View File

@@ -1,8 +1,7 @@
use std::sync::Arc;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use k_ap::{ApContentReader, ApObjectHandler};
use k_ap::{ApContentReader, ApObjectHandler, LocalObject};
use url::Url;
use crate::{
@@ -21,10 +20,9 @@ impl ApContentReader for CompositeObjectHandler {
async fn get_local_objects_page(
&self,
user_id: uuid::Uuid,
before: Option<DateTime<Utc>>,
before: Option<chrono::DateTime<chrono::Utc>>,
limit: usize,
) -> anyhow::Result<Vec<(Url, serde_json::Value, DateTime<Utc>)>> {
// Fetch from all three sources (watchlist/goals return all, reviews use DB pagination)
) -> anyhow::Result<Vec<LocalObject>> {
let fetch_limit = limit * 3;
let reviews = self
.review
@@ -39,16 +37,15 @@ impl ApContentReader for CompositeObjectHandler {
.get_local_objects_page(user_id, None, usize::MAX)
.await?;
let mut all: Vec<(Url, serde_json::Value, DateTime<Utc>)> = Vec::new();
let mut all: Vec<LocalObject> = Vec::new();
all.extend(reviews);
all.extend(watchlist);
all.extend(goals);
// Apply before filter and sort descending by timestamp
if let Some(before_ts) = before {
all.retain(|(_, _, ts)| *ts < before_ts);
all.retain(|obj| obj.published_at < before_ts);
}
all.sort_by_key(|b| std::cmp::Reverse(b.2));
all.sort_by_key(|obj| std::cmp::Reverse(obj.published_at));
all.truncate(limit);
Ok(all)
}

View File

@@ -217,7 +217,7 @@ impl ActivityPubEventHandler {
let json = serde_json::to_value(obj)?;
self.ap_service
.broadcast_create_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_create(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
let year = review.watched_at().year() as u16;
@@ -283,7 +283,7 @@ impl ActivityPubEventHandler {
let json = serde_json::to_value(obj)?;
self.ap_service
.broadcast_update_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_update(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
Ok(())
@@ -349,7 +349,7 @@ impl ActivityPubEventHandler {
let json = serde_json::to_value(obj)?;
self.ap_service
.broadcast_create_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_create(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
Ok(())
}
@@ -416,7 +416,7 @@ impl ActivityPubEventHandler {
let json = serde_json::to_value(obj)?;
self.ap_service
.broadcast_update_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_update(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
}
@@ -462,7 +462,7 @@ impl ActivityPubEventHandler {
);
let json = serde_json::to_value(obj)?;
self.ap_service
.broadcast_update_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_update(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
Ok(())
}
@@ -494,11 +494,11 @@ impl ActivityPubEventHandler {
let json = serde_json::to_value(obj)?;
if is_create {
self.ap_service
.broadcast_create_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_create(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
} else {
self.ap_service
.broadcast_update_note(user_id.value(), json, ApVisibility::Public, vec![])
.broadcast_update(user_id.value(), json, ApVisibility::Public, vec![])
.await?;
}
Ok(())

View File

@@ -7,7 +7,7 @@ use domain::{
ports::{GoalQuery, RemoteGoalRepository},
value_objects::UserId,
};
use k_ap::{ApContentReader, ApObjectHandler};
use k_ap::{AS_PUBLIC, ApContentReader, ApObjectHandler, LocalObject};
use url::Url;
use crate::objects::{GoalObject, goal_to_ap_object};
@@ -26,7 +26,7 @@ impl ApContentReader for GoalObjectHandler {
user_id: uuid::Uuid,
_before: Option<DateTime<chrono::Utc>>,
_limit: usize,
) -> anyhow::Result<Vec<(Url, serde_json::Value, DateTime<chrono::Utc>)>> {
) -> anyhow::Result<Vec<LocalObject>> {
let uid = UserId::from_uuid(user_id);
let goals = self
.goal_repo
@@ -35,6 +35,7 @@ impl ApContentReader for GoalObjectHandler {
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
let actor = actor_url(&self.base_url, user_id);
let follower_cc = format!("{}/followers", actor);
let mut results = Vec::new();
for goal in goals {
let ap_id = goal_url(&self.base_url, user_id, goal.year());
@@ -47,7 +48,15 @@ impl ApContentReader for GoalObjectHandler {
0,
&self.base_url,
);
results.push((ap_id, serde_json::to_value(obj)?, published));
results.push(LocalObject {
ap_id,
object: serde_json::to_value(obj)?,
published_at: published,
to: vec![AS_PUBLIC.to_string()],
cc: vec![follower_cc.clone()],
bto: vec![],
bcc: vec![],
});
}
Ok(results)
}

View File

@@ -18,7 +18,7 @@ pub const INSTANCE_ACTOR_ID: uuid::Uuid =
pub use k_ap::{
ActivityPubService, ActivityRepository, ActorRepository, ApContentReader, ApFederationConfig,
ApObjectHandler, ApUser, ApUserRepository, BlocklistRepository, FederationData,
FollowRepository, Follower, FollowerStatus, FollowingStatus, RemoteActor,
FollowRepository, Follower, FollowerStatus, FollowingStatus, LocalObject, RemoteActor,
};
pub use event_handler::ActivityPubEventHandler;

View File

@@ -1,11 +1,17 @@
use chrono::{DateTime, Utc};
use k_ap::AS_PUBLIC;
use k_ap::NoteType;
use serde::{Deserialize, Serialize};
use url::Url;
use domain::models::Review;
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub(crate) enum ActivityStreamsType {
#[default]
Note,
Article,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApAttachment {
@@ -34,7 +40,7 @@ pub(crate) fn normalize_hashtag(title: &str) -> String {
#[serde(rename_all = "camelCase")]
pub struct ReviewObject {
#[serde(rename = "type")]
pub(crate) kind: NoteType,
pub(crate) kind: ActivityStreamsType,
pub(crate) id: Url,
pub(crate) attributed_to: Url,
pub(crate) content: String,
@@ -125,7 +131,7 @@ pub fn review_to_ap_object(review: &Review, input: ReviewApInput) -> ReviewObjec
};
ReviewObject {
kind: NoteType::default(),
kind: ActivityStreamsType::default(),
id: ap_id,
attributed_to: actor_url.clone(),
content,
@@ -150,7 +156,7 @@ pub fn review_to_ap_object(review: &Review, input: ReviewApInput) -> ReviewObjec
#[serde(rename_all = "camelCase")]
pub struct WatchlistObject {
#[serde(rename = "type")]
pub(crate) kind: NoteType,
pub(crate) kind: ActivityStreamsType,
pub(crate) id: Url,
pub(crate) attributed_to: Url,
pub(crate) content: String,
@@ -218,7 +224,7 @@ pub fn watchlist_to_ap_object(input: WatchlistApInput) -> WatchlistObject {
];
WatchlistObject {
kind: NoteType::default(),
kind: ActivityStreamsType::default(),
id: ap_id,
attributed_to: actor_url.clone(),
content,
@@ -240,7 +246,7 @@ pub fn watchlist_to_ap_object(input: WatchlistApInput) -> WatchlistObject {
#[serde(rename_all = "camelCase")]
pub struct GoalObject {
#[serde(rename = "type")]
pub(crate) kind: NoteType,
pub(crate) kind: ActivityStreamsType,
pub(crate) id: Url,
pub(crate) attributed_to: Url,
pub(crate) content: String,
@@ -277,7 +283,7 @@ pub fn goal_to_ap_object(
}];
GoalObject {
kind: NoteType::default(),
kind: ActivityStreamsType::default(),
id: ap_id,
attributed_to: actor_url.clone(),
content,

View File

@@ -7,7 +7,7 @@ use domain::{
ports::{DiaryQuery, EventPublisher, LocalApContentQuery, MovieQuery},
value_objects::{Comment, ExternalMetadataId, MovieId, Rating, ReviewId, UserId},
};
use k_ap::{ApContentReader, ApObjectHandler};
use k_ap::{AS_PUBLIC, ApContentReader, ApObjectHandler, LocalObject};
use url::Url;
use crate::objects::{ReviewApInput, ReviewObject, review_to_ap_object};
@@ -30,7 +30,7 @@ impl ApContentReader for ReviewObjectHandler {
user_id: uuid::Uuid,
before: Option<chrono::DateTime<chrono::Utc>>,
limit: usize,
) -> anyhow::Result<Vec<(url::Url, serde_json::Value, chrono::DateTime<chrono::Utc>)>> {
) -> anyhow::Result<Vec<LocalObject>> {
let domain_user_id = UserId::from_uuid(user_id);
let before_naive = before.map(|dt| dt.naive_utc());
let entries = self
@@ -65,7 +65,16 @@ impl ApContentReader for ReviewObjectHandler {
base_url: self.base_url.clone(),
},
);
results.push((ap_id, serde_json::to_value(obj)?, published));
let follower_cc = format!("{}/followers", actor);
results.push(LocalObject {
ap_id,
object: serde_json::to_value(obj)?,
published_at: published,
to: vec![AS_PUBLIC.to_string()],
cc: vec![follower_cc],
bto: vec![],
bcc: vec![],
});
}
Ok(results)
}

View File

@@ -7,7 +7,7 @@ use domain::{
ports::{LocalApContentQuery, RemoteWatchlistRepository},
value_objects::UserId,
};
use k_ap::{ApContentReader, ApObjectHandler};
use k_ap::{AS_PUBLIC, ApContentReader, ApObjectHandler, LocalObject};
use url::Url;
use crate::objects::{WatchlistApInput, WatchlistObject, watchlist_to_ap_object};
@@ -26,7 +26,7 @@ impl ApContentReader for WatchlistObjectHandler {
user_id: uuid::Uuid,
_before: Option<DateTime<chrono::Utc>>,
_limit: usize,
) -> anyhow::Result<Vec<(Url, serde_json::Value, DateTime<chrono::Utc>)>> {
) -> anyhow::Result<Vec<LocalObject>> {
let uid = UserId::from_uuid(user_id);
let entries = self
.content_query
@@ -35,6 +35,7 @@ impl ApContentReader for WatchlistObjectHandler {
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
let actor = actor_url(&self.base_url, user_id);
let follower_cc = format!("{}/followers", actor);
let mut results = Vec::new();
for WatchlistWithMovie { entry, movie } in entries {
let ap_id = watchlist_entry_url(&self.base_url, user_id, entry.movie_id.value());
@@ -54,7 +55,15 @@ impl ApContentReader for WatchlistObjectHandler {
added_at: published,
base_url: self.base_url.clone(),
});
results.push((ap_id, serde_json::to_value(obj)?, published));
results.push(LocalObject {
ap_id,
object: serde_json::to_value(obj)?,
published_at: published,
to: vec![AS_PUBLIC.to_string()],
cc: vec![follower_cc.clone()],
bto: vec![],
bcc: vec![],
});
}
Ok(results)
}