Our Personal Data Server from scratch!
tranquil.farm
oauth
atproto
pds
rust
postgresql
objectstorage
fun
1use async_trait::async_trait;
2use sqlx::PgPool;
3use tranquil_db_traits::{Backlink, BacklinkRepository, DbError};
4use tranquil_types::{AtUri, Nsid};
5use uuid::Uuid;
6
7use super::user::map_sqlx_error;
8
9pub struct PostgresBacklinkRepository {
10 pool: PgPool,
11}
12
13impl PostgresBacklinkRepository {
14 pub fn new(pool: PgPool) -> Self {
15 Self { pool }
16 }
17}
18
19#[async_trait]
20impl BacklinkRepository for PostgresBacklinkRepository {
21 async fn get_backlink_conflicts(
22 &self,
23 repo_id: Uuid,
24 collection: &Nsid,
25 backlinks: &[Backlink],
26 ) -> Result<Vec<AtUri>, DbError> {
27 if backlinks.is_empty() {
28 return Ok(Vec::new());
29 }
30
31 let paths: Vec<&str> = backlinks.iter().map(|b| b.path.as_str()).collect();
32 let link_tos: Vec<&str> = backlinks.iter().map(|b| b.link_to.as_str()).collect();
33 let collection_pattern = format!("%/{}/%", collection.as_str());
34
35 let results = sqlx::query_scalar!(
36 r#"
37 SELECT DISTINCT uri
38 FROM backlinks
39 WHERE repo_id = $1
40 AND uri LIKE $4
41 AND (path, link_to) IN (SELECT unnest($2::text[]), unnest($3::text[]))
42 "#,
43 repo_id,
44 &paths as &[&str],
45 &link_tos as &[&str],
46 collection_pattern
47 )
48 .fetch_all(&self.pool)
49 .await
50 .map_err(map_sqlx_error)?;
51
52 Ok(results.into_iter().map(Into::into).collect())
53 }
54
55 async fn add_backlinks(&self, repo_id: Uuid, backlinks: &[Backlink]) -> Result<(), DbError> {
56 if backlinks.is_empty() {
57 return Ok(());
58 }
59
60 let uris: Vec<&str> = backlinks.iter().map(|b| b.uri.as_str()).collect();
61 let paths: Vec<&str> = backlinks.iter().map(|b| b.path.as_str()).collect();
62 let link_tos: Vec<&str> = backlinks.iter().map(|b| b.link_to.as_str()).collect();
63
64 sqlx::query!(
65 r#"
66 INSERT INTO backlinks (uri, path, link_to, repo_id)
67 SELECT unnest($1::text[]), unnest($2::text[]), unnest($3::text[]), $4
68 ON CONFLICT (uri, path) DO NOTHING
69 "#,
70 &uris as &[&str],
71 &paths as &[&str],
72 &link_tos as &[&str],
73 repo_id
74 )
75 .execute(&self.pool)
76 .await
77 .map_err(map_sqlx_error)?;
78
79 Ok(())
80 }
81
82 async fn remove_backlinks_by_uri(&self, uri: &AtUri) -> Result<(), DbError> {
83 sqlx::query!("DELETE FROM backlinks WHERE uri = $1", uri.as_str())
84 .execute(&self.pool)
85 .await
86 .map_err(map_sqlx_error)?;
87
88 Ok(())
89 }
90
91 async fn remove_backlinks_by_repo(&self, repo_id: Uuid) -> Result<(), DbError> {
92 sqlx::query!("DELETE FROM backlinks WHERE repo_id = $1", repo_id)
93 .execute(&self.pool)
94 .await
95 .map_err(map_sqlx_error)?;
96
97 Ok(())
98 }
99}