refactor: type safety + dedup cleanup across 13 code smells
- typed PagedResponse/CreatedApiKeyResponse/NotificationSummaryResponse replace json! blocks - extract TagRow/ApiKeyRow/OutboxRow to module level, top_friend uses sqlx flatten - add should_broadcast() helper, inline dead let bindings in federation_event - add UploadContext struct, extract_upload_field, wants_activity_json helpers - rename PostgresFederationRepository→PgFederationRepository, PostgresApUserRepository→PgApUserRepository - add IntoAnyhow trait replacing ~30 .map_err(|e| anyhow!(e)) calls - extract build_ap_service shared between bootstrap and worker factories - add postgres/constants.rs, PartialEq+Eq on PasswordHash
This commit is contained in:
@@ -11,3 +11,48 @@ pub use port::{
|
||||
};
|
||||
pub use service::ApFederationAdapter;
|
||||
pub use urls::ThoughtsUrls;
|
||||
|
||||
use domain::ports::RemoteActorConnectionRepository;
|
||||
use k_ap::ActivityPubService;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ApServiceConfig {
|
||||
pub base_url: String,
|
||||
pub activity_repo: Arc<dyn k_ap::ActivityRepository>,
|
||||
pub follow_repo: Arc<dyn k_ap::FollowRepository>,
|
||||
pub actor_repo: Arc<dyn k_ap::ActorRepository>,
|
||||
pub blocklist_repo: Arc<dyn k_ap::BlocklistRepository>,
|
||||
pub user_repo: Arc<dyn k_ap::ApUserRepository>,
|
||||
pub ap_handler: Arc<ThoughtsObjectHandler>,
|
||||
pub connections_repo: Arc<dyn RemoteActorConnectionRepository>,
|
||||
pub event_publisher: Option<Arc<dyn k_ap::data::EventPublisher>>,
|
||||
pub allow_registration: bool,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
pub async fn build_ap_service(
|
||||
cfg: ApServiceConfig,
|
||||
) -> (Arc<ActivityPubService>, Arc<ApFederationAdapter>) {
|
||||
let mut builder = ActivityPubService::builder(cfg.base_url)
|
||||
.activity_repo(cfg.activity_repo)
|
||||
.follow_repo(cfg.follow_repo)
|
||||
.actor_repo(cfg.actor_repo)
|
||||
.blocklist_repo(cfg.blocklist_repo)
|
||||
.user_repo(cfg.user_repo)
|
||||
.content_reader(cfg.ap_handler.clone())
|
||||
.object_handler(cfg.ap_handler)
|
||||
.allow_registration(cfg.allow_registration)
|
||||
.software_name("thoughts")
|
||||
.debug(cfg.debug);
|
||||
if let Some(publisher) = cfg.event_publisher {
|
||||
builder = builder.event_publisher(publisher);
|
||||
}
|
||||
let raw = Arc::new(
|
||||
builder
|
||||
.build()
|
||||
.await
|
||||
.expect("Failed to build ActivityPubService"),
|
||||
);
|
||||
let adapter = Arc::new(ApFederationAdapter::new(raw.clone(), cfg.connections_repo));
|
||||
(raw, adapter)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::PgPool;
|
||||
|
||||
trait IntoAnyhow<T> {
|
||||
fn into_anyhow(self) -> Result<T>;
|
||||
}
|
||||
impl<T> IntoAnyhow<T> for std::result::Result<T, sqlx::Error> {
|
||||
fn into_anyhow(self) -> Result<T> {
|
||||
self.map_err(|e| anyhow::anyhow!(e))
|
||||
}
|
||||
}
|
||||
|
||||
use k_ap::{
|
||||
ActivityRepository, ActorRepository, ApActorType, ApUser, ApUserRepository, BlockedDomain,
|
||||
BlocklistRepository, FollowRepository, Follower, FollowerStatus, FollowingStatus, RemoteActor,
|
||||
@@ -10,11 +19,11 @@ use k_ap::{
|
||||
|
||||
// ── PostgresFederationRepository ─────────────────────────────────────────────
|
||||
|
||||
pub struct PostgresFederationRepository {
|
||||
pub struct PgFederationRepository {
|
||||
pool: PgPool,
|
||||
}
|
||||
|
||||
impl PostgresFederationRepository {
|
||||
impl PgFederationRepository {
|
||||
pub fn new(pool: PgPool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
@@ -71,7 +80,7 @@ fn map_remote_actor(r: RemoteActorRow) -> RemoteActor {
|
||||
// ── ActivityRepository ────────────────────────────────────────────────────────
|
||||
|
||||
#[async_trait]
|
||||
impl ActivityRepository for PostgresFederationRepository {
|
||||
impl ActivityRepository for PgFederationRepository {
|
||||
async fn is_activity_processed(&self, activity_id: &str) -> Result<bool> {
|
||||
let n: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM federation_processed_activities WHERE activity_id=$1",
|
||||
@@ -79,7 +88,7 @@ impl ActivityRepository for PostgresFederationRepository {
|
||||
.bind(activity_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n > 0)
|
||||
}
|
||||
|
||||
@@ -90,7 +99,7 @@ impl ActivityRepository for PostgresFederationRepository {
|
||||
.bind(activity_id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -98,7 +107,7 @@ impl ActivityRepository for PostgresFederationRepository {
|
||||
// ── FollowRepository ──────────────────────────────────────────────────────────
|
||||
|
||||
#[async_trait]
|
||||
impl FollowRepository for PostgresFederationRepository {
|
||||
impl FollowRepository for PgFederationRepository {
|
||||
async fn add_follower(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
@@ -118,7 +127,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(follow_activity_id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -134,7 +143,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(remote_actor_url)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
}
|
||||
|
||||
async fn remove_follower(
|
||||
@@ -149,7 +158,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(remote_actor_url)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -172,7 +181,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| {
|
||||
rows.into_iter()
|
||||
.map(|r| Follower {
|
||||
@@ -210,7 +219,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| {
|
||||
rows.into_iter()
|
||||
.map(|r| Follower {
|
||||
@@ -227,7 +236,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n as usize)
|
||||
}
|
||||
|
||||
@@ -238,7 +247,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n as usize)
|
||||
}
|
||||
|
||||
@@ -262,7 +271,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| rows.into_iter().map(map_remote_actor).collect())
|
||||
}
|
||||
|
||||
@@ -287,7 +296,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
@@ -303,7 +312,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| rows.into_iter().map(map_remote_actor).collect())
|
||||
}
|
||||
|
||||
@@ -321,7 +330,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(status_str(&status))
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -344,7 +353,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(&actor.outbox_url)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -360,7 +369,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(remote_actor_url)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
}
|
||||
|
||||
async fn remove_following(&self, local_user_id: uuid::Uuid, actor_url: &str) -> Result<()> {
|
||||
@@ -371,7 +380,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(actor_url)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -387,7 +396,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| rows.into_iter().map(map_remote_actor).collect())
|
||||
}
|
||||
|
||||
@@ -411,7 +420,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| rows.into_iter().map(map_remote_actor).collect())
|
||||
}
|
||||
|
||||
@@ -421,7 +430,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n as usize)
|
||||
}
|
||||
|
||||
@@ -443,7 +452,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(s)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -459,7 +468,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(remote_actor_url)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
}
|
||||
|
||||
async fn migrate_follower_actor(
|
||||
@@ -467,7 +476,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
old_actor_url: &str,
|
||||
new_actor_url: &str,
|
||||
) -> Result<Vec<uuid::Uuid>> {
|
||||
let mut tx = self.pool.begin().await.map_err(|e| anyhow!(e))?;
|
||||
let mut tx = self.pool.begin().await.into_anyhow()?;
|
||||
|
||||
let affected: Vec<uuid::Uuid> = sqlx::query_scalar(
|
||||
"INSERT INTO federation_following(local_user_id, remote_actor_url, follow_activity_id, outbox_url)
|
||||
@@ -481,15 +490,15 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
.bind(new_actor_url)
|
||||
.fetch_all(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
|
||||
sqlx::query("DELETE FROM federation_following WHERE remote_actor_url = $1")
|
||||
.bind(old_actor_url)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
|
||||
tx.commit().await.map_err(|e| anyhow!(e))?;
|
||||
tx.commit().await.into_anyhow()?;
|
||||
Ok(affected)
|
||||
}
|
||||
}
|
||||
@@ -497,7 +506,7 @@ impl FollowRepository for PostgresFederationRepository {
|
||||
// ── ActorRepository ───────────────────────────────────────────────────────────
|
||||
|
||||
#[async_trait]
|
||||
impl ActorRepository for PostgresFederationRepository {
|
||||
impl ActorRepository for PgFederationRepository {
|
||||
async fn get_local_actor_keypair(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
@@ -513,7 +522,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(user_id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(row.and_then(|r| match (r.public_key, r.private_key) {
|
||||
(Some(pub_k), Some(priv_k)) => Some((pub_k, priv_k)),
|
||||
_ => None,
|
||||
@@ -532,7 +541,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(&private_key)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -568,7 +577,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(also_known_as.as_deref())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -581,7 +590,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(actor_url)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|o| o.map(map_remote_actor))
|
||||
}
|
||||
|
||||
@@ -602,7 +611,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(announced_at)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -612,7 +621,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(actor_url)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -622,7 +631,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
.bind(object_url)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
@@ -630,7 +639,7 @@ impl ActorRepository for PostgresFederationRepository {
|
||||
// ── BlocklistRepository ───────────────────────────────────────────────────────
|
||||
|
||||
#[async_trait]
|
||||
impl BlocklistRepository for PostgresFederationRepository {
|
||||
impl BlocklistRepository for PgFederationRepository {
|
||||
async fn add_blocked_domain(&self, domain: &str, reason: Option<&str>) -> Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO federation_blocked_domains(domain,reason) VALUES($1,$2) ON CONFLICT(domain) DO NOTHING",
|
||||
@@ -639,7 +648,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(reason)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -648,7 +657,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(domain)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -664,7 +673,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|rows| {
|
||||
rows.into_iter()
|
||||
.map(|r| BlockedDomain {
|
||||
@@ -682,7 +691,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(domain)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n > 0)
|
||||
}
|
||||
|
||||
@@ -694,7 +703,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(actor_url)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -704,7 +713,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(actor_url)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -715,7 +724,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(local_user_id)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))
|
||||
.into_anyhow()
|
||||
}
|
||||
|
||||
async fn is_actor_blocked(&self, local_user_id: uuid::Uuid, actor_url: &str) -> Result<bool> {
|
||||
@@ -726,7 +735,7 @@ impl BlocklistRepository for PostgresFederationRepository {
|
||||
.bind(actor_url)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n > 0)
|
||||
}
|
||||
}
|
||||
@@ -744,12 +753,12 @@ struct UserRow {
|
||||
also_known_as: Option<String>,
|
||||
}
|
||||
|
||||
pub struct PostgresApUserRepository {
|
||||
pub struct PgApUserRepository {
|
||||
pool: PgPool,
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl PostgresApUserRepository {
|
||||
impl PgApUserRepository {
|
||||
pub fn new(pool: PgPool, base_url: String) -> Self {
|
||||
Self { pool, base_url }
|
||||
}
|
||||
@@ -777,7 +786,7 @@ impl PostgresApUserRepository {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ApUserRepository for PostgresApUserRepository {
|
||||
impl ApUserRepository for PgApUserRepository {
|
||||
async fn find_by_id(&self, id: uuid::Uuid) -> Result<Option<ApUser>> {
|
||||
let row = sqlx::query_as::<_, UserRow>(
|
||||
"SELECT id,username,display_name,bio,avatar_url,header_url,also_known_as FROM users WHERE id=$1 AND local=true",
|
||||
@@ -785,7 +794,7 @@ impl ApUserRepository for PostgresApUserRepository {
|
||||
.bind(id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(row.map(|r| self.row_to_ap_user(r)))
|
||||
}
|
||||
|
||||
@@ -796,7 +805,7 @@ impl ApUserRepository for PostgresApUserRepository {
|
||||
.bind(username)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(row.map(|r| self.row_to_ap_user(r)))
|
||||
}
|
||||
|
||||
@@ -804,7 +813,7 @@ impl ApUserRepository for PostgresApUserRepository {
|
||||
let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users WHERE local=true")
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.into_anyhow()?;
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,40 @@ use domain::{
|
||||
value_objects::{Content, ThoughtId, UserId, Username},
|
||||
};
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct OutboxRow {
|
||||
id: uuid::Uuid,
|
||||
user_id: uuid::Uuid,
|
||||
content: String,
|
||||
created_at: DateTime<Utc>,
|
||||
in_reply_to_id: Option<uuid::Uuid>,
|
||||
content_warning: Option<String>,
|
||||
sensitive: bool,
|
||||
username: String,
|
||||
updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl OutboxRow {
|
||||
fn into_entry(self) -> OutboxEntry {
|
||||
OutboxEntry {
|
||||
thought: Thought {
|
||||
id: ThoughtId::from_uuid(self.id),
|
||||
user_id: UserId::from_uuid(self.user_id),
|
||||
content: Content::new_remote(self.content),
|
||||
in_reply_to_id: self.in_reply_to_id.map(ThoughtId::from_uuid),
|
||||
visibility: Visibility::Public,
|
||||
content_warning: self.content_warning,
|
||||
sensitive: self.sensitive,
|
||||
local: true,
|
||||
created_at: self.created_at,
|
||||
updated_at: self.updated_at,
|
||||
note_extensions: None,
|
||||
},
|
||||
author_username: Username::from_trusted(self.username),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PgActivityPubRepository {
|
||||
pool: PgPool,
|
||||
}
|
||||
@@ -29,19 +63,7 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Vec<OutboxEntry>, DomainError> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: uuid::Uuid,
|
||||
user_id: uuid::Uuid,
|
||||
content: String,
|
||||
created_at: DateTime<Utc>,
|
||||
in_reply_to_id: Option<uuid::Uuid>,
|
||||
content_warning: Option<String>,
|
||||
sensitive: bool,
|
||||
username: String,
|
||||
updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
sqlx::query_as::<_, Row>(
|
||||
sqlx::query_as::<_, OutboxRow>(
|
||||
"SELECT t.id, t.user_id, t.content, t.created_at, t.in_reply_to_id, t.content_warning, t.sensitive, u.username, t.updated_at
|
||||
FROM thoughts t JOIN users u ON u.id=t.user_id
|
||||
WHERE t.user_id=$1 AND t.local=true AND t.visibility='public'
|
||||
@@ -51,26 +73,7 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.into_domain()
|
||||
.map(|rows| {
|
||||
rows.into_iter()
|
||||
.map(|r| OutboxEntry {
|
||||
thought: Thought {
|
||||
id: ThoughtId::from_uuid(r.id),
|
||||
user_id: UserId::from_uuid(r.user_id),
|
||||
content: Content::new_remote(r.content),
|
||||
in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid),
|
||||
visibility: Visibility::Public,
|
||||
content_warning: r.content_warning,
|
||||
sensitive: r.sensitive,
|
||||
local: true,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
note_extensions: None,
|
||||
},
|
||||
author_username: Username::from_trusted(r.username),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.map(|rows| rows.into_iter().map(OutboxRow::into_entry).collect())
|
||||
}
|
||||
|
||||
async fn outbox_page_for_actor(
|
||||
@@ -79,20 +82,8 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
||||
before: Option<DateTime<Utc>>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<OutboxEntry>, DomainError> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: uuid::Uuid,
|
||||
user_id: uuid::Uuid,
|
||||
content: String,
|
||||
created_at: DateTime<Utc>,
|
||||
in_reply_to_id: Option<uuid::Uuid>,
|
||||
content_warning: Option<String>,
|
||||
sensitive: bool,
|
||||
username: String,
|
||||
updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
let rows = if let Some(before) = before {
|
||||
sqlx::query_as::<_, Row>(
|
||||
sqlx::query_as::<_, OutboxRow>(
|
||||
"SELECT t.id, t.user_id, t.content, t.created_at, t.in_reply_to_id, t.content_warning, t.sensitive, u.username, t.updated_at
|
||||
FROM thoughts t JOIN users u ON u.id=t.user_id
|
||||
WHERE t.user_id=$1 AND t.local=true AND t.visibility='public' AND t.created_at < $2
|
||||
@@ -104,7 +95,7 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
} else {
|
||||
sqlx::query_as::<_, Row>(
|
||||
sqlx::query_as::<_, OutboxRow>(
|
||||
"SELECT t.id, t.user_id, t.content, t.created_at, t.in_reply_to_id, t.content_warning, t.sensitive, u.username, t.updated_at
|
||||
FROM thoughts t JOIN users u ON u.id=t.user_id
|
||||
WHERE t.user_id=$1 AND t.local=true AND t.visibility='public'
|
||||
@@ -117,25 +108,7 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
||||
}
|
||||
.into_domain()?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| OutboxEntry {
|
||||
thought: Thought {
|
||||
id: ThoughtId::from_uuid(r.id),
|
||||
user_id: UserId::from_uuid(r.user_id),
|
||||
content: Content::new_remote(r.content),
|
||||
in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid),
|
||||
visibility: Visibility::Public,
|
||||
content_warning: r.content_warning,
|
||||
sensitive: r.sensitive,
|
||||
local: true,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
note_extensions: None,
|
||||
},
|
||||
author_username: Username::from_trusted(r.username),
|
||||
})
|
||||
.collect())
|
||||
Ok(rows.into_iter().map(OutboxRow::into_entry).collect())
|
||||
}
|
||||
|
||||
async fn find_remote_actor_id(
|
||||
|
||||
@@ -9,6 +9,27 @@ use domain::{
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct ApiKeyRow {
|
||||
id: uuid::Uuid,
|
||||
user_id: uuid::Uuid,
|
||||
key_hash: String,
|
||||
name: String,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ApiKeyRow {
|
||||
fn into_domain(self) -> ApiKey {
|
||||
ApiKey {
|
||||
id: ApiKeyId::from_uuid(self.id),
|
||||
user_id: UserId::from_uuid(self.user_id),
|
||||
key_hash: self.key_hash,
|
||||
name: self.name,
|
||||
created_at: self.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PgApiKeyRepository {
|
||||
pool: PgPool,
|
||||
}
|
||||
@@ -36,45 +57,21 @@ impl ApiKeyRepository for PgApiKeyRepository {
|
||||
}
|
||||
|
||||
async fn find_by_hash(&self, hash: &str) -> Result<Option<ApiKey>, DomainError> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: uuid::Uuid,
|
||||
user_id: uuid::Uuid,
|
||||
key_hash: String,
|
||||
name: String,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
sqlx::query_as::<_, Row>(
|
||||
sqlx::query_as::<_, ApiKeyRow>(
|
||||
"SELECT id,user_id,key_hash,name,created_at FROM api_keys WHERE key_hash=$1",
|
||||
)
|
||||
.bind(hash)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.into_domain()
|
||||
.map(|o| {
|
||||
o.map(|r| ApiKey {
|
||||
id: ApiKeyId::from_uuid(r.id),
|
||||
user_id: UserId::from_uuid(r.user_id),
|
||||
key_hash: r.key_hash,
|
||||
name: r.name,
|
||||
created_at: r.created_at,
|
||||
})
|
||||
})
|
||||
.map(|o| o.map(ApiKeyRow::into_domain))
|
||||
}
|
||||
|
||||
async fn list_for_user(&self, user_id: &UserId) -> Result<Vec<ApiKey>, DomainError> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: uuid::Uuid,
|
||||
user_id: uuid::Uuid,
|
||||
key_hash: String,
|
||||
name: String,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
sqlx::query_as::<_, Row>("SELECT id,user_id,key_hash,name,created_at FROM api_keys WHERE user_id=$1 ORDER BY created_at DESC")
|
||||
sqlx::query_as::<_, ApiKeyRow>("SELECT id,user_id,key_hash,name,created_at FROM api_keys WHERE user_id=$1 ORDER BY created_at DESC")
|
||||
.bind(user_id.as_uuid()).fetch_all(&self.pool).await
|
||||
.into_domain()
|
||||
.map(|rows| rows.into_iter().map(|r| ApiKey { id: ApiKeyId::from_uuid(r.id), user_id: UserId::from_uuid(r.user_id), key_hash: r.key_hash, name: r.name, created_at: r.created_at }).collect())
|
||||
.map(|rows| rows.into_iter().map(ApiKeyRow::into_domain).collect())
|
||||
}
|
||||
|
||||
async fn delete(&self, id: &ApiKeyId, user_id: &UserId) -> Result<(), DomainError> {
|
||||
|
||||
8
crates/adapters/postgres/src/constants.rs
Normal file
8
crates/adapters/postgres/src/constants.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
pub const STATUS_ACCEPTED: &str = "accepted";
|
||||
pub const STATUS_PENDING: &str = "pending";
|
||||
pub const STATUS_REJECTED: &str = "rejected";
|
||||
|
||||
pub const VIS_PUBLIC: &str = "public";
|
||||
pub const VIS_UNLISTED: &str = "unlisted";
|
||||
pub const VIS_FOLLOWERS: &str = "followers";
|
||||
pub const VIS_DIRECT: &str = "direct";
|
||||
@@ -2,6 +2,7 @@ pub mod activitypub;
|
||||
pub mod api_key;
|
||||
pub mod block;
|
||||
pub mod boost;
|
||||
pub mod constants;
|
||||
mod db_error;
|
||||
pub mod engagement;
|
||||
pub mod failed_event;
|
||||
|
||||
@@ -12,6 +12,12 @@ use domain::{
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct TagRow {
|
||||
id: i32,
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub struct PgTagRepository {
|
||||
pool: PgPool,
|
||||
}
|
||||
@@ -30,12 +36,7 @@ impl TagRepository for PgTagRepository {
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.into_domain()?;
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: i32,
|
||||
name: String,
|
||||
}
|
||||
let row = sqlx::query_as::<_, Row>("SELECT id,name FROM tags WHERE name=$1")
|
||||
let row = sqlx::query_as::<_, TagRow>("SELECT id,name FROM tags WHERE name=$1")
|
||||
.bind(&name)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
@@ -72,12 +73,7 @@ impl TagRepository for PgTagRepository {
|
||||
}
|
||||
|
||||
async fn list_for_thought(&self, thought_id: &ThoughtId) -> Result<Vec<Tag>, DomainError> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: i32,
|
||||
name: String,
|
||||
}
|
||||
sqlx::query_as::<_, Row>(
|
||||
sqlx::query_as::<_, TagRow>(
|
||||
"SELECT t.id,t.name FROM tags t JOIN thought_tags tt ON tt.tag_id=t.id WHERE tt.thought_id=$1"
|
||||
).bind(thought_id.as_uuid()).fetch_all(&self.pool).await
|
||||
.into_domain()
|
||||
|
||||
@@ -44,24 +44,14 @@ impl TopFriendRepository for PgTopFriendRepository {
|
||||
|
||||
async fn list_for_user(&self, user_id: &UserId) -> Result<Vec<(TopFriend, User)>, DomainError> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
struct TopFriendRow {
|
||||
tf_user_id: uuid::Uuid,
|
||||
friend_id: uuid::Uuid,
|
||||
position: i16,
|
||||
id: uuid::Uuid,
|
||||
username: String,
|
||||
email: String,
|
||||
password_hash: String,
|
||||
display_name: Option<String>,
|
||||
bio: Option<String>,
|
||||
avatar_url: Option<String>,
|
||||
header_url: Option<String>,
|
||||
custom_css: Option<String>,
|
||||
local: bool,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
updated_at: chrono::DateTime<chrono::Utc>,
|
||||
#[sqlx(flatten)]
|
||||
user: crate::user::UserRow,
|
||||
}
|
||||
let rows = sqlx::query_as::<_, Row>(
|
||||
let rows = sqlx::query_as::<_, TopFriendRow>(
|
||||
"SELECT tf.user_id AS tf_user_id, tf.friend_id, tf.position,
|
||||
u.id, u.username, u.email, u.password_hash, u.display_name, u.bio,
|
||||
u.avatar_url, u.header_url, u.custom_css, u.local,
|
||||
@@ -77,27 +67,12 @@ impl TopFriendRepository for PgTopFriendRepository {
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
use domain::value_objects::{Email, PasswordHash, Username};
|
||||
let tf = TopFriend {
|
||||
user_id: UserId::from_uuid(r.tf_user_id),
|
||||
friend_id: UserId::from_uuid(r.friend_id),
|
||||
position: r.position,
|
||||
};
|
||||
let u = User {
|
||||
id: UserId::from_uuid(r.id),
|
||||
username: Username::from_trusted(r.username),
|
||||
email: Email::from_trusted(r.email),
|
||||
password_hash: PasswordHash(r.password_hash),
|
||||
display_name: r.display_name,
|
||||
bio: r.bio,
|
||||
avatar_url: r.avatar_url,
|
||||
header_url: r.header_url,
|
||||
custom_css: r.custom_css,
|
||||
local: r.local,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
};
|
||||
(tf, u)
|
||||
(tf, User::from(r.user))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user