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
Some checks failed
CI / Check / Test (push) Has been cancelled
This commit is contained in:
@@ -7,7 +7,7 @@ edition = "2024"
|
||||
sqlx = { workspace = true }
|
||||
activitypub = { workspace = true }
|
||||
adapter-common = { workspace = true }
|
||||
k-ap = { version = "0.4.6", registry = "gitea" }
|
||||
k-ap = { version = "0.5.0", registry = "gitea" }
|
||||
domain = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use k_ap::{ActorRepository, RemoteActor};
|
||||
use k_ap::{AnnounceRepository, Keypair, KeypairRepository, RemoteActor, RemoteActorCache};
|
||||
use sqlx::Row;
|
||||
|
||||
use super::{SqliteFederationRepository, remote_actor_from_row};
|
||||
use adapter_common::datetime_to_str;
|
||||
|
||||
#[async_trait]
|
||||
impl ActorRepository for SqliteFederationRepository {
|
||||
async fn get_local_actor_keypair(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
) -> Result<Option<(String, String)>> {
|
||||
impl KeypairRepository for SqliteFederationRepository {
|
||||
async fn get_local_actor_keypair(&self, user_id: uuid::Uuid) -> Result<Option<Keypair>> {
|
||||
let uid = user_id.to_string();
|
||||
let row =
|
||||
sqlx::query("SELECT public_key, private_key FROM ap_local_actors WHERE user_id = ?")
|
||||
.bind(&uid)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
Ok(row.map(|r| (r.get("public_key"), r.get("private_key"))))
|
||||
Ok(row.map(|r| Keypair {
|
||||
public_key: r.get("public_key"),
|
||||
private_key: r.get("private_key"),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn save_local_actor_keypair(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
public_key: String,
|
||||
private_key: String,
|
||||
) -> Result<()> {
|
||||
async fn save_local_actor_keypair(&self, user_id: uuid::Uuid, keypair: Keypair) -> Result<()> {
|
||||
let uid = user_id.to_string();
|
||||
let now = Utc::now().naive_utc();
|
||||
let created_at = datetime_to_str(&now);
|
||||
@@ -39,14 +34,17 @@ impl ActorRepository for SqliteFederationRepository {
|
||||
private_key = excluded.private_key",
|
||||
)
|
||||
.bind(&uid)
|
||||
.bind(&public_key)
|
||||
.bind(&private_key)
|
||||
.bind(&keypair.public_key)
|
||||
.bind(&keypair.private_key)
|
||||
.bind(&created_at)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RemoteActorCache for SqliteFederationRepository {
|
||||
async fn upsert_remote_actor(&self, actor: RemoteActor) -> Result<()> {
|
||||
let now = Utc::now().naive_utc();
|
||||
let fetched_at = datetime_to_str(&now);
|
||||
@@ -84,7 +82,10 @@ impl ActorRepository for SqliteFederationRepository {
|
||||
).bind(actor_url).fetch_optional(&self.pool).await?;
|
||||
Ok(row.as_ref().map(|r| remote_actor_from_row(r, "url")))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AnnounceRepository for SqliteFederationRepository {
|
||||
async fn add_announce(
|
||||
&self,
|
||||
activity_id: &str,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use k_ap::{BlockedDomain, BlocklistRepository};
|
||||
use k_ap::{ActorBlocklist, BlockedDomain, DomainBlocklist};
|
||||
use sqlx::Row;
|
||||
|
||||
use super::SqliteFederationRepository;
|
||||
use adapter_common::datetime_to_str;
|
||||
|
||||
#[async_trait]
|
||||
impl BlocklistRepository for SqliteFederationRepository {
|
||||
impl DomainBlocklist for SqliteFederationRepository {
|
||||
async fn add_blocked_domain(&self, domain: &str, reason: Option<&str>) -> Result<()> {
|
||||
let now = Utc::now().naive_utc();
|
||||
let ts = datetime_to_str(&now);
|
||||
@@ -56,7 +56,10 @@ impl BlocklistRepository for SqliteFederationRepository {
|
||||
.await?;
|
||||
Ok(count > 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ActorBlocklist for SqliteFederationRepository {
|
||||
async fn add_blocked_actor(&self, local_user_id: uuid::Uuid, actor_url: &str) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let ts = datetime_to_str(&Utc::now().naive_utc());
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use k_ap::{
|
||||
ActorRepository, FollowRepository, Follower, FollowerStatus, FollowingStatus, RemoteActor,
|
||||
};
|
||||
use k_ap::{Follower, FollowerReader, FollowerStatus, FollowerWriter, RemoteActor};
|
||||
use sqlx::Row;
|
||||
|
||||
use super::{SqliteFederationRepository, remote_actor_from_row, status_to_str, str_to_status};
|
||||
use crate::{SqliteFederationRepository, remote_actor_from_row, status_to_str, str_to_status};
|
||||
use adapter_common::datetime_to_str;
|
||||
|
||||
#[async_trait]
|
||||
impl FollowRepository for SqliteFederationRepository {
|
||||
impl FollowerWriter for SqliteFederationRepository {
|
||||
async fn add_follower(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
@@ -69,6 +67,31 @@ impl FollowRepository for SqliteFederationRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_follower_status(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowerStatus,
|
||||
) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let status_str = status_to_str(&status);
|
||||
let result = sqlx::query(
|
||||
"UPDATE ap_followers SET status = ? WHERE local_user_id = ? AND remote_actor_url = ?",
|
||||
)
|
||||
.bind(status_str)
|
||||
.bind(&uid)
|
||||
.bind(remote_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
if result.rows_affected() == 0 {
|
||||
tracing::warn!(local_user_id = %local_user_id, remote_actor_url, "update_follower_status: no row found");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FollowerReader for SqliteFederationRepository {
|
||||
async fn get_followers(&self, local_user_id: uuid::Uuid) -> Result<Vec<Follower>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let rows = sqlx::query(
|
||||
@@ -138,28 +161,6 @@ impl FollowRepository for SqliteFederationRepository {
|
||||
Ok(count as usize)
|
||||
}
|
||||
|
||||
async fn update_follower_status(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowerStatus,
|
||||
) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let status_str = status_to_str(&status);
|
||||
let result = sqlx::query(
|
||||
"UPDATE ap_followers SET status = ? WHERE local_user_id = ? AND remote_actor_url = ?",
|
||||
)
|
||||
.bind(status_str)
|
||||
.bind(&uid)
|
||||
.bind(remote_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
if result.rows_affected() == 0 {
|
||||
tracing::warn!(local_user_id = %local_user_id, remote_actor_url, "update_follower_status: no row found");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_pending_followers(&self, local_user_id: uuid::Uuid) -> Result<Vec<RemoteActor>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let rows = sqlx::query(
|
||||
@@ -232,173 +233,4 @@ impl FollowRepository for SqliteFederationRepository {
|
||||
.map(|row| remote_actor_from_row(row, "remote_actor_url"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn add_following(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor: RemoteActor,
|
||||
follow_activity_id: &str,
|
||||
) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let now = Utc::now().naive_utc();
|
||||
let created_at = datetime_to_str(&now);
|
||||
ActorRepository::upsert_remote_actor(self, actor.clone()).await?;
|
||||
sqlx::query(
|
||||
"INSERT OR IGNORE INTO ap_following (local_user_id, remote_actor_url, follow_activity_id, created_at)
|
||||
VALUES (?, ?, ?, ?)",
|
||||
).bind(&uid).bind(&actor.url).bind(follow_activity_id).bind(&created_at).execute(&self.pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_follow_activity_id(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let row: Option<Option<String>> = sqlx::query_scalar(
|
||||
"SELECT follow_activity_id FROM ap_following WHERE local_user_id = ? AND remote_actor_url = ?",
|
||||
).bind(&uid).bind(remote_actor_url).fetch_optional(&self.pool).await?;
|
||||
Ok(row.flatten())
|
||||
}
|
||||
|
||||
async fn remove_following(&self, local_user_id: uuid::Uuid, actor_url: &str) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
sqlx::query("DELETE FROM ap_following WHERE local_user_id = ? AND remote_actor_url = ?")
|
||||
.bind(&uid)
|
||||
.bind(actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_following(&self, local_user_id: uuid::Uuid) -> Result<Vec<RemoteActor>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let rows = sqlx::query(
|
||||
"SELECT a.url, a.handle, a.inbox_url, a.shared_inbox_url, a.display_name, a.avatar_url,
|
||||
a.outbox_url, a.bio, a.banner_url, a.followers_url, a.following_url, a.also_known_as, a.fetched_at
|
||||
FROM ap_following f
|
||||
INNER JOIN ap_remote_actors a ON a.url = f.remote_actor_url
|
||||
WHERE f.local_user_id = ? AND f.status = 'accepted'",
|
||||
).bind(&uid).fetch_all(&self.pool).await?;
|
||||
Ok(rows
|
||||
.iter()
|
||||
.map(|row| remote_actor_from_row(row, "url"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn count_following(&self, local_user_id: uuid::Uuid) -> Result<usize> {
|
||||
let uid = local_user_id.to_string();
|
||||
let count: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM ap_following WHERE local_user_id = ? AND status = 'accepted'",
|
||||
)
|
||||
.bind(&uid)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
Ok(count as usize)
|
||||
}
|
||||
|
||||
async fn get_following_page(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
offset: u32,
|
||||
limit: usize,
|
||||
) -> Result<Vec<RemoteActor>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let rows = sqlx::query(
|
||||
"SELECT a.url, a.handle, a.inbox_url, a.shared_inbox_url, a.display_name, a.avatar_url,
|
||||
a.outbox_url, a.bio, a.banner_url, a.followers_url, a.following_url, a.also_known_as, a.fetched_at
|
||||
FROM ap_following f
|
||||
INNER JOIN ap_remote_actors a ON a.url = f.remote_actor_url
|
||||
WHERE f.local_user_id = ? AND f.status = 'accepted'
|
||||
ORDER BY f.created_at ASC LIMIT ? OFFSET ?",
|
||||
).bind(&uid).bind(limit as i64).bind(offset as i64).fetch_all(&self.pool).await?;
|
||||
Ok(rows
|
||||
.iter()
|
||||
.map(|row| remote_actor_from_row(row, "url"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn update_following_status(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowingStatus,
|
||||
) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let status_str = match status {
|
||||
FollowingStatus::Pending => "pending",
|
||||
FollowingStatus::Accepted => "accepted",
|
||||
};
|
||||
let result = sqlx::query(
|
||||
"UPDATE ap_following SET status = ? WHERE local_user_id = ? AND remote_actor_url = ?",
|
||||
)
|
||||
.bind(status_str)
|
||||
.bind(&uid)
|
||||
.bind(remote_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
if result.rows_affected() == 0 {
|
||||
tracing::warn!(local_user_id = %local_user_id, remote_actor_url, "update_following_status: no row found");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_following_outbox_url(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let row: Option<Option<String>> = sqlx::query_scalar(
|
||||
"SELECT a.outbox_url
|
||||
FROM ap_following f
|
||||
INNER JOIN ap_remote_actors a ON a.url = f.remote_actor_url
|
||||
WHERE f.local_user_id = ? AND f.remote_actor_url = ?",
|
||||
)
|
||||
.bind(&uid)
|
||||
.bind(remote_actor_url)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
Ok(row.flatten())
|
||||
}
|
||||
|
||||
async fn migrate_follower_actor(
|
||||
&self,
|
||||
old_actor_url: &str,
|
||||
new_actor_url: &str,
|
||||
) -> Result<Vec<uuid::Uuid>> {
|
||||
let candidates: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT local_user_id FROM ap_following
|
||||
WHERE remote_actor_url = ?1
|
||||
AND local_user_id NOT IN (
|
||||
SELECT local_user_id FROM ap_following WHERE remote_actor_url = ?2
|
||||
)",
|
||||
)
|
||||
.bind(old_actor_url)
|
||||
.bind(new_actor_url)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
if candidates.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE ap_following SET remote_actor_url = ?1
|
||||
WHERE remote_actor_url = ?2
|
||||
AND local_user_id NOT IN (
|
||||
SELECT local_user_id FROM ap_following WHERE remote_actor_url = ?1
|
||||
)",
|
||||
)
|
||||
.bind(new_actor_url)
|
||||
.bind(old_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
candidates
|
||||
.into_iter()
|
||||
.map(|s| uuid::Uuid::parse_str(&s).map_err(|e| anyhow::anyhow!(e)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
124
crates/adapters/sqlite-federation/src/follow/following.rs
Normal file
124
crates/adapters/sqlite-federation/src/follow/following.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use k_ap::{FollowingReader, FollowingStatus, FollowingWriter, RemoteActor, RemoteActorCache};
|
||||
|
||||
use crate::{SqliteFederationRepository, remote_actor_from_row};
|
||||
use adapter_common::datetime_to_str;
|
||||
|
||||
#[async_trait]
|
||||
impl FollowingWriter for SqliteFederationRepository {
|
||||
async fn add_following(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor: RemoteActor,
|
||||
follow_activity_id: &str,
|
||||
) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let now = Utc::now().naive_utc();
|
||||
let created_at = datetime_to_str(&now);
|
||||
RemoteActorCache::upsert_remote_actor(self, actor.clone()).await?;
|
||||
sqlx::query(
|
||||
"INSERT OR IGNORE INTO ap_following (local_user_id, remote_actor_url, follow_activity_id, created_at)
|
||||
VALUES (?, ?, ?, ?)",
|
||||
).bind(&uid).bind(&actor.url).bind(follow_activity_id).bind(&created_at).execute(&self.pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_follow_activity_id(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let row: Option<Option<String>> = sqlx::query_scalar(
|
||||
"SELECT follow_activity_id FROM ap_following WHERE local_user_id = ? AND remote_actor_url = ?",
|
||||
).bind(&uid).bind(remote_actor_url).fetch_optional(&self.pool).await?;
|
||||
Ok(row.flatten())
|
||||
}
|
||||
|
||||
async fn remove_following(&self, local_user_id: uuid::Uuid, actor_url: &str) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
sqlx::query("DELETE FROM ap_following WHERE local_user_id = ? AND remote_actor_url = ?")
|
||||
.bind(&uid)
|
||||
.bind(actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_following_status(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowingStatus,
|
||||
) -> Result<()> {
|
||||
let uid = local_user_id.to_string();
|
||||
let status_str = match status {
|
||||
FollowingStatus::Pending => "pending",
|
||||
FollowingStatus::Accepted => "accepted",
|
||||
};
|
||||
let result = sqlx::query(
|
||||
"UPDATE ap_following SET status = ? WHERE local_user_id = ? AND remote_actor_url = ?",
|
||||
)
|
||||
.bind(status_str)
|
||||
.bind(&uid)
|
||||
.bind(remote_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
if result.rows_affected() == 0 {
|
||||
tracing::warn!(local_user_id = %local_user_id, remote_actor_url, "update_following_status: no row found");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FollowingReader for SqliteFederationRepository {
|
||||
async fn get_following(&self, local_user_id: uuid::Uuid) -> Result<Vec<RemoteActor>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let rows = sqlx::query(
|
||||
"SELECT a.url, a.handle, a.inbox_url, a.shared_inbox_url, a.display_name, a.avatar_url,
|
||||
a.outbox_url, a.bio, a.banner_url, a.followers_url, a.following_url, a.also_known_as, a.fetched_at
|
||||
FROM ap_following f
|
||||
INNER JOIN ap_remote_actors a ON a.url = f.remote_actor_url
|
||||
WHERE f.local_user_id = ? AND f.status = 'accepted'",
|
||||
).bind(&uid).fetch_all(&self.pool).await?;
|
||||
Ok(rows
|
||||
.iter()
|
||||
.map(|row| remote_actor_from_row(row, "url"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn count_following(&self, local_user_id: uuid::Uuid) -> Result<usize> {
|
||||
let uid = local_user_id.to_string();
|
||||
let count: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM ap_following WHERE local_user_id = ? AND status = 'accepted'",
|
||||
)
|
||||
.bind(&uid)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
Ok(count as usize)
|
||||
}
|
||||
|
||||
async fn get_following_page(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
offset: u32,
|
||||
limit: usize,
|
||||
) -> Result<Vec<RemoteActor>> {
|
||||
let uid = local_user_id.to_string();
|
||||
let rows = sqlx::query(
|
||||
"SELECT a.url, a.handle, a.inbox_url, a.shared_inbox_url, a.display_name, a.avatar_url,
|
||||
a.outbox_url, a.bio, a.banner_url, a.followers_url, a.following_url, a.also_known_as, a.fetched_at
|
||||
FROM ap_following f
|
||||
INNER JOIN ap_remote_actors a ON a.url = f.remote_actor_url
|
||||
WHERE f.local_user_id = ? AND f.status = 'accepted'
|
||||
ORDER BY f.created_at ASC LIMIT ? OFFSET ?",
|
||||
).bind(&uid).bind(limit as i64).bind(offset as i64).fetch_all(&self.pool).await?;
|
||||
Ok(rows
|
||||
.iter()
|
||||
.map(|row| remote_actor_from_row(row, "url"))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
47
crates/adapters/sqlite-federation/src/follow/migration.rs
Normal file
47
crates/adapters/sqlite-federation/src/follow/migration.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use k_ap::FollowMigration;
|
||||
|
||||
use crate::SqliteFederationRepository;
|
||||
|
||||
#[async_trait]
|
||||
impl FollowMigration for SqliteFederationRepository {
|
||||
async fn migrate_follower_actor(
|
||||
&self,
|
||||
old_actor_url: &str,
|
||||
new_actor_url: &str,
|
||||
) -> Result<Vec<uuid::Uuid>> {
|
||||
let candidates: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT local_user_id FROM ap_following
|
||||
WHERE remote_actor_url = ?1
|
||||
AND local_user_id NOT IN (
|
||||
SELECT local_user_id FROM ap_following WHERE remote_actor_url = ?2
|
||||
)",
|
||||
)
|
||||
.bind(old_actor_url)
|
||||
.bind(new_actor_url)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
if candidates.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE ap_following SET remote_actor_url = ?1
|
||||
WHERE remote_actor_url = ?2
|
||||
AND local_user_id NOT IN (
|
||||
SELECT local_user_id FROM ap_following WHERE remote_actor_url = ?1
|
||||
)",
|
||||
)
|
||||
.bind(new_actor_url)
|
||||
.bind(old_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
candidates
|
||||
.into_iter()
|
||||
.map(|s| uuid::Uuid::parse_str(&s).map_err(|e| anyhow::anyhow!(e)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
3
crates/adapters/sqlite-federation/src/follow/mod.rs
Normal file
3
crates/adapters/sqlite-federation/src/follow/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod followers;
|
||||
mod following;
|
||||
mod migration;
|
||||
@@ -102,10 +102,6 @@ pub fn wire(pool: SqlitePool) -> activitypub::FederationRepos {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/outbox_url.rs"]
|
||||
mod outbox_url_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/actor_block_tests.rs"]
|
||||
mod actor_block_tests;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use k_ap::BlocklistRepository;
|
||||
use k_ap::ActorBlocklist;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
async fn test_pool() -> SqlitePool {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use k_ap::BlocklistRepository;
|
||||
use k_ap::DomainBlocklist;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
async fn test_pool() -> SqlitePool {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
use domain::ports::FederationAdminQuery;
|
||||
use k_ap::ActorRepository;
|
||||
use k_ap::AnnounceRepository;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
async fn test_pool() -> SqlitePool {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
use super::*;
|
||||
use k_ap::{FollowRepository, FollowingStatus, RemoteActor};
|
||||
|
||||
async fn setup_pool() -> SqlitePool {
|
||||
let pool = SqlitePool::connect(":memory:").await.unwrap();
|
||||
sqlx::query(
|
||||
"CREATE TABLE ap_remote_actors (
|
||||
url TEXT PRIMARY KEY, handle TEXT NOT NULL, inbox_url TEXT NOT NULL,
|
||||
shared_inbox_url TEXT, display_name TEXT, avatar_url TEXT,
|
||||
outbox_url TEXT, bio TEXT, banner_url TEXT, followers_url TEXT,
|
||||
following_url TEXT, also_known_as TEXT, fetched_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE ap_following (
|
||||
local_user_id TEXT NOT NULL, remote_actor_url TEXT NOT NULL,
|
||||
follow_activity_id TEXT, created_at TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
PRIMARY KEY (local_user_id, remote_actor_url)
|
||||
);",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
pool
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_following_outbox_url_returns_stored_url() {
|
||||
let pool = setup_pool().await;
|
||||
let repo = SqliteFederationRepository::new(pool);
|
||||
let local_user = uuid::Uuid::new_v4();
|
||||
let actor = RemoteActor {
|
||||
url: "https://remote.example/users/alice".to_string(),
|
||||
handle: "alice@remote.example".to_string(),
|
||||
inbox_url: "https://remote.example/users/alice/inbox".to_string(),
|
||||
shared_inbox_url: None,
|
||||
display_name: None,
|
||||
avatar_url: None,
|
||||
outbox_url: Some("https://remote.example/users/alice/outbox".to_string()),
|
||||
bio: None,
|
||||
banner_url: None,
|
||||
followers_url: None,
|
||||
following_url: None,
|
||||
also_known_as: vec![],
|
||||
fetched_at: None,
|
||||
};
|
||||
repo.add_following(local_user, actor, "https://local/activities/1")
|
||||
.await
|
||||
.unwrap();
|
||||
repo.update_following_status(
|
||||
local_user,
|
||||
"https://remote.example/users/alice",
|
||||
FollowingStatus::Accepted,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = repo
|
||||
.get_following_outbox_url(local_user, "https://remote.example/users/alice")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Some("https://remote.example/users/alice/outbox".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_following_outbox_url_returns_none_when_not_following() {
|
||||
let pool = setup_pool().await;
|
||||
let repo = SqliteFederationRepository::new(pool);
|
||||
let result = repo
|
||||
.get_following_outbox_url(uuid::Uuid::new_v4(), "https://remote.example/users/alice")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
Reference in New Issue
Block a user