nexus_common/models/post/
counts.rs1use crate::db::kv::JsonAction;
2use crate::db::{get_neo4j_graph, queries, RedisOps};
3use crate::models::tag::post::POST_TAGS_KEY_PARTS;
4use crate::types::DynError;
5use serde::{Deserialize, Serialize};
6use utoipa::ToSchema;
7
8use super::PostStream;
9
10#[derive(Serialize, Deserialize, ToSchema, Default, Debug)]
12pub struct PostCounts {
13 pub tags: u32,
15 pub unique_tags: u32,
17 pub replies: u32,
18 pub reposts: u32,
19}
20
21impl RedisOps for PostCounts {}
22
23impl PostCounts {
24 pub async fn get_by_id(author_id: &str, post_id: &str) -> Result<Option<PostCounts>, DynError> {
26 match Self::get_from_index(author_id, post_id).await? {
27 Some(counts) => Ok(Some(counts)),
28 None => {
29 let graph_response = Self::get_from_graph(author_id, post_id).await?;
30 if let Some((post_counts, is_reply)) = graph_response {
31 post_counts
32 .put_to_index(author_id, post_id, !is_reply)
33 .await?;
34 return Ok(Some(post_counts));
35 }
36 Ok(None)
37 }
38 }
39 }
40
41 pub async fn get_from_index(
42 author_id: &str,
43 post_id: &str,
44 ) -> Result<Option<PostCounts>, DynError> {
45 if let Some(post_counts) = Self::try_from_index_json(&[author_id, post_id], None).await? {
46 return Ok(Some(post_counts));
47 }
48 Ok(None)
49 }
50
51 pub async fn get_from_graph(
53 author_id: &str,
54 post_id: &str,
55 ) -> Result<Option<(PostCounts, bool)>, DynError> {
56 let mut result;
57 {
58 let graph = get_neo4j_graph()?;
59 let query = queries::get::post_counts(author_id, post_id);
60
61 let graph = graph.lock().await;
62 result = graph.execute(query).await?;
63 }
64
65 if let Some(row) = result.next().await? {
66 let post_exists: bool = row.get("exists").unwrap_or(false);
67 if post_exists {
68 let counts: PostCounts = row.get("counts")?;
69 let is_reply: bool = row.get("is_reply").unwrap_or(false);
70
71 return Ok(Some((counts, is_reply)));
72 }
73 }
74 Ok(None)
75 }
76
77 pub async fn put_to_index(
78 &self,
79 author_id: &str,
80 post_id: &str,
81 is_reply: bool,
82 ) -> Result<(), DynError> {
83 self.put_index_json(&[author_id, post_id], None, None)
84 .await?;
85
86 if !is_reply {
88 PostStream::add_to_engagement_sorted_set(self, author_id, post_id).await?;
89 }
90 Ok(())
91 }
92
93 pub async fn update_index_field(
102 index_key: &[&str],
103 field: &str,
104 action: JsonAction,
105 tag_label: Option<&str>,
106 ) -> Result<(), DynError> {
107 if let Some(label) = tag_label {
109 let index_parts = [&POST_TAGS_KEY_PARTS[..], index_key].concat();
110 let score = Self::check_sorted_set_member(None, &index_parts, &[label]).await?;
111 match (score, &action) {
112 (Some(tag_value), _) if tag_value < 1 => (),
114
115 (None, JsonAction::Increment(_)) => (),
117
118 _ => return Ok(()),
120 }
121 }
122
123 Self::modify_json_field(index_key, field, action).await?;
124 Ok(())
125 }
126
127 pub async fn reindex(author_id: &str, post_id: &str) -> Result<(), DynError> {
128 match Self::get_from_graph(author_id, post_id).await? {
129 Some((counts, is_reply)) => counts.put_to_index(author_id, post_id, is_reply).await?,
130 None => tracing::error!(
131 "{}:{} Could not found post counts in the graph",
132 author_id,
133 post_id
134 ),
135 }
136 Ok(())
137 }
138
139 pub async fn delete(
140 author_id: &str,
141 post_id: &str,
142 remove_from_feeds: bool,
143 ) -> Result<(), DynError> {
144 Self::remove_from_index_multiple_json(&[&[author_id, post_id]]).await?;
146 if remove_from_feeds {
148 PostStream::delete_from_engagement_sorted_set(author_id, post_id).await?;
149 }
150 Ok(())
151 }
152}