Skip to main content

nexus_common/models/post/
relationships.rs

1use crate::db::{get_neo4j_graph, queries, RedisOps};
2use crate::types::DynError;
3use pubky_app_specs::{PubkyAppPost, PubkyAppPostKind};
4use serde::{Deserialize, Serialize};
5use utoipa::ToSchema;
6
7#[derive(Serialize, Deserialize, ToSchema, Default, Debug)]
8pub struct PostRelationships {
9    // URI of the replied post
10    pub replied: Option<String>,
11    // URI of the reposted post
12    pub reposted: Option<String>,
13    // List of user IDs
14    pub mentioned: Vec<String>,
15}
16
17impl RedisOps for PostRelationships {}
18
19impl PostRelationships {
20    /// Retrieves post relationships by user ID, first trying to get from Redis, then from Neo4j if not found.
21    pub async fn get_by_id(
22        author_id: &str,
23        post_id: &str,
24    ) -> Result<Option<PostRelationships>, DynError> {
25        match Self::get_from_index(author_id, post_id).await? {
26            Some(counts) => Ok(Some(counts)),
27            None => {
28                let graph_response = Self::get_from_graph(author_id, post_id).await?;
29                if let Some(post_relationships) = graph_response {
30                    post_relationships.put_to_index(author_id, post_id).await?;
31                    return Ok(Some(post_relationships));
32                }
33                Ok(None)
34            }
35        }
36    }
37
38    pub async fn get_from_index(
39        author_id: &str,
40        post_id: &str,
41    ) -> Result<Option<PostRelationships>, DynError> {
42        if let Some(post_relationships) =
43            Self::try_from_index_json(&[author_id, post_id], None).await?
44        {
45            return Ok(Some(post_relationships));
46        }
47        Ok(None)
48    }
49
50    /// Retrieves the counts from Neo4j.
51    pub async fn get_from_graph(
52        author_id: &str,
53        post_id: &str,
54    ) -> Result<Option<PostRelationships>, DynError> {
55        let mut result;
56        {
57            let graph = get_neo4j_graph()?;
58            let query = queries::get::post_relationships(author_id, post_id);
59
60            let graph = graph.lock().await;
61            result = graph.execute(query).await?;
62        }
63
64        if let Some(row) = result.next().await? {
65            let replied_post_id: Option<String> = row.get("replied_post_id").unwrap_or(None);
66            let replied_author_id: Option<String> = row.get("replied_author_id").unwrap_or(None);
67            let reposted_post_id: Option<String> = row.get("reposted_post_id").unwrap_or(None);
68            let reposted_author_id: Option<String> = row.get("reposted_author_id").unwrap_or(None);
69            let mentioned: Vec<String> = row.get("mentioned_user_ids").unwrap_or(Vec::new());
70
71            let replied = match (replied_author_id, replied_post_id) {
72                (Some(author_id), Some(post_id)) => {
73                    Some(format!("pubky://{author_id}/pub/pubky.app/posts/{post_id}"))
74                }
75                _ => None,
76            };
77            let reposted = match (reposted_author_id, reposted_post_id) {
78                (Some(author_id), Some(post_id)) => {
79                    Some(format!("pubky://{author_id}/pub/pubky.app/posts/{post_id}"))
80                }
81                _ => None,
82            };
83            Ok(Some(Self {
84                replied,
85                reposted,
86                mentioned,
87            }))
88        } else {
89            Ok(None)
90        }
91    }
92
93    /// Constructs a `Self` instance by extracting relationships from a `PubkyAppPost` object
94    pub fn from_homeserver(post: &PubkyAppPost) -> Self {
95        let mut relationship = Self::default();
96
97        if let Some(parent_uri) = &post.parent {
98            relationship.replied = Some(parent_uri.to_string());
99        }
100
101        if let Some(embed) = &post.embed {
102            if let PubkyAppPostKind::Short = embed.kind {
103                relationship.reposted = Some(embed.uri.clone());
104            }
105        }
106        relationship
107    }
108
109    pub async fn put_to_index(&self, author_id: &str, post_id: &str) -> Result<(), DynError> {
110        self.put_index_json(&[author_id, post_id], None, None)
111            .await?;
112        Ok(())
113    }
114
115    pub async fn delete(author_id: &str, post_id: &str) -> Result<(), DynError> {
116        Self::remove_from_index_multiple_json(&[&[author_id, post_id]]).await?;
117        Ok(())
118    }
119
120    pub async fn reindex(author_id: &str, post_id: &str) -> Result<(), DynError> {
121        match Self::get_from_graph(author_id, post_id).await? {
122            Some(relationships) => relationships.put_to_index(author_id, post_id).await?,
123            None => tracing::error!(
124                "{}:{} Could not found post relationships in the graph",
125                author_id,
126                post_id
127            ),
128        }
129        Ok(())
130    }
131}