nexus_common/models/post/
details.rs1use super::{PostRelationships, PostStream};
2use crate::db::{exec_single_row, execute_graph_operation, OperationOutcome};
3use crate::db::{get_neo4j_graph, queries, RedisOps};
4use crate::types::DynError;
5use chrono::Utc;
6use pubky_app_specs::{PubkyAppPost, PubkyAppPostKind, PubkyId};
7use serde::{Deserialize, Serialize};
8use utoipa::ToSchema;
9
10#[derive(Serialize, Deserialize, ToSchema, Default, Debug)]
12pub struct PostDetails {
15 pub content: String,
16 pub id: String,
17 pub indexed_at: i64,
18 pub author: String,
19 pub kind: PubkyAppPostKind,
20 pub uri: String,
21 pub attachments: Option<Vec<String>>,
22}
23
24impl RedisOps for PostDetails {}
25
26impl PostDetails {
27 pub async fn get_by_id(
29 author_id: &str,
30 post_id: &str,
31 ) -> Result<Option<PostDetails>, DynError> {
32 match Self::get_from_index(author_id, post_id).await? {
33 Some(details) => Ok(Some(details)),
34 None => {
35 let graph_response = Self::get_from_graph(author_id, post_id).await?;
36 if let Some((post_details, reply)) = graph_response {
37 post_details.put_to_index(author_id, reply, false).await?;
38 return Ok(Some(post_details));
39 }
40 Ok(None)
41 }
42 }
43 }
44
45 pub async fn get_from_index(
46 author_id: &str,
47 post_id: &str,
48 ) -> Result<Option<PostDetails>, DynError> {
49 if let Some(post_details) = Self::try_from_index_json(&[author_id, post_id], None).await? {
50 return Ok(Some(post_details));
51 }
52 Ok(None)
53 }
54
55 pub async fn get_from_graph(
57 author_id: &str,
58 post_id: &str,
59 ) -> Result<Option<(PostDetails, Option<(String, String)>)>, DynError> {
60 let mut result;
61 {
62 let graph = get_neo4j_graph()?;
63 let query = queries::get::get_post_by_id(author_id, post_id);
64
65 let graph = graph.lock().await;
66 result = graph.execute(query).await?;
67 }
68
69 match result.next().await? {
70 Some(row) => {
71 let post: PostDetails = row.get("details")?;
72 let reply_value: Vec<(String, String)> = row.get("reply").unwrap_or(Vec::new());
73 let reply_key = match reply_value.is_empty() {
74 true => None,
75 false => Some(reply_value[0].clone()),
76 };
77 Ok(Some((post, reply_key)))
78 }
79 None => Ok(None),
80 }
81 }
82
83 pub async fn put_to_index(
84 &self,
85 author_id: &str,
86 parent_key_wrapper: Option<(String, String)>,
87 is_edit: bool,
88 ) -> Result<(), DynError> {
89 self.put_index_json(&[author_id, &self.id], None, None)
90 .await?;
91 if is_edit {
93 return Ok(());
94 }
95 match parent_key_wrapper {
97 None => {
98 PostStream::add_to_timeline_sorted_set(self).await?;
99 PostStream::add_to_per_user_sorted_set(self).await?;
100 }
101 Some((parent_author_id, parent_post_id)) => {
102 PostStream::add_to_post_reply_sorted_set(
103 &[&parent_author_id, &parent_post_id],
104 author_id,
105 &self.id,
106 self.indexed_at,
107 )
108 .await?;
109 PostStream::add_to_replies_per_user_sorted_set(self).await?;
110 }
111 }
112 Ok(())
113 }
114
115 pub async fn from_homeserver(
116 homeserver_post: PubkyAppPost,
117 author_id: &PubkyId,
118 post_id: &String,
119 ) -> Result<Self, DynError> {
120 Ok(PostDetails {
121 uri: format!("pubky://{author_id}/pub/pubky.app/posts/{post_id}"),
122 content: homeserver_post.content,
123 id: post_id.clone(),
124 indexed_at: Utc::now().timestamp_millis(),
125 author: author_id.to_string(),
126 kind: homeserver_post.kind,
127 attachments: homeserver_post.attachments,
128 })
129 }
130
131 pub async fn reindex(author_id: &str, post_id: &str) -> Result<(), DynError> {
132 match Self::get_from_graph(author_id, post_id).await? {
133 Some((details, reply)) => details.put_to_index(author_id, reply, false).await?,
134 None => tracing::error!(
135 "{}:{} Could not found post counts in the graph",
136 author_id,
137 post_id
138 ),
139 }
140 Ok(())
141 }
142
143 pub async fn put_to_graph(
145 &self,
146 post_relationships: &PostRelationships,
147 ) -> Result<OperationOutcome, DynError> {
148 match queries::put::create_post(self, post_relationships) {
149 Ok(query) => execute_graph_operation(query).await,
150 Err(_) => Err("QUERY: Error while creating the query".into()),
151 }
152 }
153
154 pub async fn delete(
155 author_id: &str,
156 post_id: &str,
157 parent_post_key_wrapper: Option<[String; 2]>,
158 ) -> Result<(), DynError> {
159 Self::remove_from_index_multiple_json(&[&[author_id, post_id]]).await?;
161 exec_single_row(queries::del::delete_post(author_id, post_id)).await?;
163 match parent_post_key_wrapper {
165 None => {
166 PostStream::remove_from_timeline_sorted_set(author_id, post_id).await?;
167 PostStream::remove_from_per_user_sorted_set(author_id, post_id).await?;
168 }
169 Some([parent_author_id, parent_post_id]) => {
170 PostStream::remove_from_post_reply_sorted_set(
171 &[&parent_author_id, &parent_post_id],
172 author_id,
173 post_id,
174 )
175 .await?;
176 PostStream::remove_from_replies_per_user_sorted_set(author_id, post_id).await?;
177 }
178 }
179 Ok(())
180 }
181}