code refactor
Some checks failed
CI / Check / Test (push) Has been cancelled

Reviewed-on: #26
This commit was merged in pull request #26.
This commit is contained in:
2026-10-26 09:36:23 +00:00
parent 2ce7db12c2
commit 017b58d6fa
804 changed files with 33423 additions and 28955 deletions

View File

@@ -1,22 +1,22 @@
[package]
name = "sqlite-federation"
version = "0.1.0"
edition = "2024"
edition.workspace = true
[dependencies]
sqlx = { workspace = true }
activitypub = { workspace = true }
adapter-common = { workspace = true }
k-ap = { version = "0.5.0", registry = "gitea" }
sqlite-social = { workspace = true }
k-ap = { workspace = true }
sqlite = { workspace = true }
domain = { workspace = true }
anyhow = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }
uuid = { workspace = true, features = ["v4", "serde"] }
chrono = { workspace = true, features = ["clock", "serde"] }
tracing = { workspace = true }
async-trait = { workspace = true }
[dev-dependencies]
tokio = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros"] }
domain = { workspace = true, features = ["test-helpers"] }
uuid = { workspace = true }

View File

@@ -4,7 +4,7 @@ use chrono::Utc;
use k_ap::ActivityRepository;
use super::SqliteFederationRepository;
use adapter_common::datetime_to_str;
use sqlite::persistence::datetime_to_str;
#[async_trait]
impl ActivityRepository for SqliteFederationRepository {

View File

@@ -5,7 +5,7 @@ use k_ap::{AnnounceRepository, Keypair, KeypairRepository, RemoteActor, RemoteAc
use sqlx::Row;
use super::{SqliteFederationRepository, remote_actor_from_row};
use adapter_common::datetime_to_str;
use sqlite::persistence::datetime_to_str;
#[async_trait]
impl KeypairRepository for SqliteFederationRepository {

View File

@@ -5,7 +5,7 @@ use k_ap::{ActorBlocklist, BlockedDomain, DomainBlocklist};
use sqlx::Row;
use super::SqliteFederationRepository;
use adapter_common::datetime_to_str;
use sqlite::persistence::datetime_to_str;
#[async_trait]
impl DomainBlocklist for SqliteFederationRepository {

View File

@@ -5,7 +5,7 @@ use k_ap::{Follower, FollowerReader, FollowerStatus, FollowerWriter, RemoteActor
use sqlx::Row;
use crate::{SqliteFederationRepository, remote_actor_from_row, status_to_str, str_to_status};
use adapter_common::datetime_to_str;
use sqlite::persistence::datetime_to_str;
#[async_trait]
impl FollowerWriter for SqliteFederationRepository {

View File

@@ -4,7 +4,7 @@ use chrono::Utc;
use k_ap::{FollowingReader, FollowingStatus, FollowingWriter, RemoteActor, RemoteActorCache};
use crate::{SqliteFederationRepository, remote_actor_from_row};
use adapter_common::datetime_to_str;
use sqlite::persistence::datetime_to_str;
#[async_trait]
impl FollowingWriter for SqliteFederationRepository {

View File

@@ -79,7 +79,7 @@ pub fn wire(
instance: domain::value_objects::InstanceIdentity,
) -> activitypub::FederationRepos {
let fed = std::sync::Arc::new(SqliteFederationRepository::new(pool.clone()));
let social = std::sync::Arc::new(sqlite_social::SqliteSocialRepository::new(pool, instance));
let social = std::sync::Arc::new(sqlite::SqliteSocialRepository::new(pool, instance));
activitypub::FederationRepos {
activity: std::sync::Arc::clone(&fed) as _,
follow: std::sync::Arc::clone(&fed) as _,
@@ -92,15 +92,3 @@ pub fn wire(
follow_query: social as _,
}
}
#[cfg(test)]
#[path = "tests/actor_block_tests.rs"]
mod actor_block_tests;
#[cfg(test)]
#[path = "tests/domain_block_tests.rs"]
mod domain_block_tests;
#[cfg(test)]
#[path = "tests/lib.rs"]
mod tests;

View File

@@ -1,22 +1,23 @@
use activitypub::{RemoteReviewRepository, RemoteReviewUpdate};
use activitypub::{NewRemoteReview, RemoteReviewRepository, RemoteReviewUpdate};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use domain::models::{Review, ReviewSource};
use domain::models::ReviewSource;
use super::SqliteFederationRepository;
use adapter_common::datetime_to_str;
use sqlite::persistence::{datetime_to_str, enqueue_events};
#[async_trait]
impl RemoteReviewRepository for SqliteFederationRepository {
async fn save_remote_review(
&self,
review: &Review,
ap_id: &str,
movie_title: &str,
release_year: u16,
external_metadata_id: Option<&str>,
poster_url: Option<&str>,
) -> Result<()> {
async fn save_remote_review(&self, new: NewRemoteReview<'_>) -> Result<()> {
let NewRemoteReview {
review,
ap_id,
movie_title,
release_year,
external_metadata_id,
poster_url,
events,
} = new;
let actor_url = match review.source() {
ReviewSource::Remote { actor_url } => actor_url.clone(),
ReviewSource::Local => {
@@ -24,6 +25,7 @@ impl RemoteReviewRepository for SqliteFederationRepository {
}
};
let movie_id = review.movie_id().value().to_string();
let mut tx = self.pool.begin().await?;
let _ = sqlx::query(
"INSERT INTO movies (id, external_metadata_id, title, release_year, director, poster_path)
VALUES (?, ?, ?, ?, NULL, ?)
@@ -36,7 +38,7 @@ impl RemoteReviewRepository for SqliteFederationRepository {
.bind(movie_title)
.bind(release_year.max(1888) as i64)
.bind(poster_url)
.execute(&self.pool)
.execute(&mut *tx)
.await?;
let id = review.id().value().to_string();
@@ -59,8 +61,10 @@ impl RemoteReviewRepository for SqliteFederationRepository {
.bind(&actor_url)
.bind(ap_id)
.bind(review.spoilers())
.execute(&self.pool)
.execute(&mut *tx)
.await?;
enqueue_events(&mut tx, events).await?;
tx.commit().await?;
Ok(())
}

View File

@@ -0,0 +1,8 @@
#[path = "federation/actor_block_test.rs"]
mod actor_block_test;
#[path = "federation/announce_test.rs"]
mod announce_test;
#[path = "federation/domain_block_test.rs"]
mod domain_block_test;
#[path = "federation/remote_review_test.rs"]
mod remote_review_test;

View File

@@ -1,5 +1,5 @@
use super::*;
use k_ap::ActorBlocklist;
use sqlite_federation::SqliteFederationRepository;
use sqlx::SqlitePool;
async fn test_pool() -> SqlitePool {
@@ -25,7 +25,7 @@ async fn test_pool() -> SqlitePool {
}
#[tokio::test]
async fn block_and_check_actor() {
async fn blocked_actor_is_detected_listed_and_unblocked() {
let pool = test_pool().await;
let user_id = uuid::Uuid::parse_str(
&sqlx::query_scalar::<_, String>("SELECT id FROM users LIMIT 1")

View File

@@ -1,6 +1,6 @@
use super::*;
use chrono::Utc;
use k_ap::AnnounceRepository;
use sqlite_federation::SqliteFederationRepository;
use sqlx::SqlitePool;
async fn test_pool() -> SqlitePool {
@@ -11,7 +11,7 @@ async fn test_pool() -> SqlitePool {
}
#[tokio::test]
async fn add_announce_stores_and_counts() {
async fn added_announce_is_counted() {
let pool = test_pool().await;
let repo = SqliteFederationRepository::new(pool);
repo.add_announce(

View File

@@ -1,5 +1,5 @@
use super::*;
use k_ap::DomainBlocklist;
use sqlite_federation::SqliteFederationRepository;
use sqlx::SqlitePool;
async fn test_pool() -> SqlitePool {

View File

@@ -0,0 +1,113 @@
use activitypub::{NewRemoteReview, RemoteReviewRepository};
use domain::{
events::DomainEvent,
models::{PersistedReview, Review, ReviewSource},
value_objects::{Comment, ExternalMetadataId, MovieId, Rating, ReviewId, UserId},
};
use sqlite_federation::SqliteFederationRepository;
use sqlx::SqlitePool;
const ACTOR: &str = "https://remote.example/users/ann";
const AP_ID: &str = "https://remote.example/reviews/1";
async fn migrated_pool() -> SqlitePool {
let pool = sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(1)
.connect("sqlite::memory:")
.await
.unwrap();
sqlite::migrate(&pool).await.unwrap();
pool
}
async fn count(pool: &SqlitePool, table: &str) -> i64 {
sqlx::query_scalar(&format!("SELECT COUNT(*) FROM {table}"))
.fetch_one(pool)
.await
.unwrap()
}
fn remote_review(movie_id: &MovieId) -> Review {
let now = chrono::Utc::now().naive_utc();
Review::from_persistence(PersistedReview {
id: ReviewId::generate(),
movie_id: movie_id.clone(),
user_id: UserId::generate(),
rating: Rating::new(4).unwrap(),
comment: Some(Comment::new("good".into()).unwrap()),
watched_at: now,
created_at: now,
source: ReviewSource::Remote {
actor_url: ACTOR.into(),
},
watch_medium: None,
spoilers: false,
})
}
fn enrichment_requested(movie_id: &MovieId) -> DomainEvent {
DomainEvent::MovieEnrichmentRequested {
movie_id: movie_id.clone(),
external_metadata_id: ExternalMetadataId::new("tmdb:603".into()).unwrap(),
}
}
async fn save(pool: &SqlitePool, review: &Review, events: &[DomainEvent]) -> anyhow::Result<()> {
SqliteFederationRepository::new(pool.clone())
.save_remote_review(NewRemoteReview {
review,
ap_id: AP_ID,
movie_title: "The Matrix",
release_year: 1999,
external_metadata_id: Some("tmdb:603"),
poster_url: None,
events,
})
.await
}
#[tokio::test]
async fn saving_a_remote_review_writes_it_and_its_event_in_one_commit() {
let pool = migrated_pool().await;
let movie_id = MovieId::generate();
let review = remote_review(&movie_id);
save(&pool, &review, &[enrichment_requested(&movie_id)])
.await
.unwrap();
assert_eq!(count(&pool, "reviews").await, 1);
assert_eq!(count(&pool, "movies").await, 1);
assert_eq!(count(&pool, "event_queue").await, 1);
}
#[tokio::test]
async fn a_failing_enqueue_rolls_back_the_remote_review_and_its_movie() {
let pool = migrated_pool().await;
sqlx::query(
"CREATE TRIGGER fail_event_insert BEFORE INSERT ON event_queue \
BEGIN SELECT RAISE(ABORT, 'enqueue failed'); END",
)
.execute(&pool)
.await
.unwrap();
let movie_id = MovieId::generate();
let review = remote_review(&movie_id);
let result = save(&pool, &review, &[enrichment_requested(&movie_id)]).await;
assert!(result.is_err());
assert_eq!(count(&pool, "reviews").await, 0);
assert_eq!(count(&pool, "movies").await, 0);
}
#[tokio::test]
async fn a_remote_review_without_events_enqueues_nothing() {
let pool = migrated_pool().await;
let movie_id = MovieId::generate();
save(&pool, &remote_review(&movie_id), &[]).await.unwrap();
assert_eq!(count(&pool, "reviews").await, 1);
assert_eq!(count(&pool, "event_queue").await, 0);
}