Skip to main content

nexus_common/models/post/
bookmark.rs

1use crate::db::{execute_graph_operation, get_neo4j_graph, queries, OperationOutcome, RedisOps};
2use crate::types::DynError;
3use neo4rs::Relation;
4use serde::{Deserialize, Serialize};
5use utoipa::ToSchema;
6
7use super::PostStream;
8
9#[derive(Serialize, Deserialize, ToSchema, Default, Debug)]
10pub struct Bookmark {
11    pub id: String,
12    pub indexed_at: i64,
13}
14
15impl RedisOps for Bookmark {}
16
17impl Bookmark {
18    pub async fn put_to_graph(
19        author_id: &str,
20        post_id: &str,
21        user_id: &str,
22        bookmark_id: &str,
23        indexed_at: i64,
24    ) -> Result<OperationOutcome, DynError> {
25        let query = queries::put::create_post_bookmark(
26            user_id,
27            author_id,
28            post_id,
29            bookmark_id,
30            indexed_at,
31        );
32
33        execute_graph_operation(query).await
34    }
35
36    /// Retrieves counts by user ID, first trying to get from Redis, then from Neo4j if not found.
37    pub async fn get_by_id(
38        author_id: &str,
39        post_id: &str,
40        viewer_id: Option<&str>,
41    ) -> Result<Option<Bookmark>, DynError> {
42        // Return None early if no viewer_id supplied
43        let viewer_id = match viewer_id {
44            Some(viewer_id) => viewer_id,
45            None => return Ok(None),
46        };
47        match Self::get_from_index(author_id, post_id, viewer_id).await? {
48            Some(counts) => Ok(Some(counts)),
49            None => {
50                let graph_response = Self::get_from_graph(author_id, post_id, viewer_id).await?;
51                if let Some(bookmark) = graph_response {
52                    bookmark.put_to_index(author_id, post_id, viewer_id).await?;
53                    return Ok(Some(bookmark));
54                }
55                Ok(None)
56            }
57        }
58    }
59
60    pub async fn get_from_index(
61        author_id: &str,
62        post_id: &str,
63        viewer_id: &str,
64    ) -> Result<Option<Bookmark>, DynError> {
65        if let Some(bookmark) =
66            Self::try_from_index_json(&[author_id, post_id, viewer_id], None).await?
67        {
68            return Ok(Some(bookmark));
69        }
70        Ok(None)
71    }
72
73    /// Retrieves a bookmark from Neo4j.
74    pub async fn get_from_graph(
75        author_id: &str,
76        post_id: &str,
77        viewer_id: &str,
78    ) -> Result<Option<Bookmark>, DynError> {
79        let mut result;
80        {
81            let graph = get_neo4j_graph()?;
82            let query = queries::get::post_bookmark(author_id, post_id, viewer_id);
83
84            let graph = graph.lock().await;
85            result = graph.execute(query).await?;
86        }
87
88        if let Some(row) = result.next().await? {
89            // TODO, research why sometimes there is a result that is not a Relation here ?
90            let relation: Relation = match row.get("b") {
91                Ok(value) => value,
92                Err(_) => return Ok(None),
93            };
94            let bookmark = Self {
95                id: relation.get("id").unwrap_or_default(),
96                indexed_at: relation.get("indexed_at").unwrap_or_default(),
97            };
98            Ok(Some(bookmark))
99        } else {
100            Ok(None)
101        }
102    }
103
104    pub async fn put_to_index(
105        &self,
106        author_id: &str,
107        post_id: &str,
108        viewer_id: &str,
109    ) -> Result<(), DynError> {
110        self.put_index_json(&[author_id, post_id, viewer_id], None, None)
111            .await?;
112        PostStream::add_to_bookmarks_sorted_set(self, viewer_id, post_id, author_id).await?;
113        Ok(())
114    }
115
116    /// Retrieves all post_keys a user bookmarked from Neo4j
117    /// TODO: using in reindex, Refactor
118    pub async fn reindex(user_id: &str) -> Result<(), DynError> {
119        let mut result;
120        {
121            let graph = get_neo4j_graph()?;
122            let query = queries::get::user_bookmarks(user_id);
123
124            let graph = graph.lock().await;
125            result = graph.execute(query).await?;
126        }
127
128        while let Some(row) = result.next().await? {
129            if let Some(relation) = row.get::<Option<Relation>>("b")? {
130                let bookmark = Bookmark {
131                    id: relation.get("id").unwrap_or_default(),
132                    indexed_at: relation.get("indexed_at").unwrap_or_default(),
133                };
134                let author_id = row.get("author_id")?;
135                let post_id = row.get("post_id")?;
136                bookmark.put_to_index(author_id, post_id, user_id).await?;
137            }
138        }
139        Ok(())
140    }
141
142    pub async fn del_from_graph(
143        user_id: &str,
144        bookmark_id: &str,
145    ) -> Result<Option<(String, String)>, DynError> {
146        let mut result;
147        {
148            let graph = get_neo4j_graph()?;
149            let query = queries::del::delete_bookmark(user_id, bookmark_id);
150
151            let graph = graph.lock().await;
152            result = graph.execute(query).await?;
153        }
154
155        while let Some(row) = result.next().await? {
156            let post_id: Option<String> = row.get("post_id").unwrap_or(None);
157            let author_id: Option<String> = row.get("author_id").unwrap_or(None);
158            if let (Some(post_id), Some(author_id)) = (post_id, author_id) {
159                return Ok(Some((post_id, author_id)));
160            }
161        }
162        Ok(None)
163    }
164
165    pub async fn del_from_index(
166        bookmarker_id: &str,
167        post_id: &str,
168        author_id: &str,
169    ) -> Result<(), DynError> {
170        Self::remove_from_index_multiple_json(&[&[author_id, post_id, bookmarker_id]]).await?;
171        PostStream::remove_from_bookmarks_sorted_set(bookmarker_id, post_id, author_id).await?;
172        Ok(())
173    }
174}