rcommunity_core/client/
mod.rs1use std::marker::PhantomData;
2
3use crate::{
4 error::Result,
5 markers::{ItemType, ReactionType, Storable, UserType},
6 store::{Store, Transaction},
7};
8
9pub struct Reaction<TU, TI, TR>
10where
11 TU: UserType,
12 TI: ItemType,
13 TR: ReactionType,
14{
15 pub id: String,
16 pub user: TU,
17 pub item: TI,
18 pub reaction: TR,
19}
20
21#[derive(Debug)]
22pub struct UserItemUnboundedReactionClient<
23 'store,
24 TS: Store,
25 TU: UserType,
26 TI: ItemType,
27 TR: ReactionType,
28> {
29 store: &'store mut TS,
30 user: TU,
31 item: TI,
32 reaction_type: PhantomData<TR>,
33}
34
35impl<'store, TS: Store, TU: UserType, TI: ItemType, TR: ReactionType>
36 UserItemUnboundedReactionClient<'store, TS, TU, TI, TR>
37{
38 pub async fn react(&mut self, reaction: impl Into<TR>) -> Result<Reaction<TU, TI, TR>> {
43 let mut txn = self.store.begin_txn().await?;
44 let r = reaction.into();
45 let rid = uuid::Uuid::new_v4().to_string(); r.store_reaction(&mut txn, &rid, &self.user, &self.item)
47 .await?;
48 r.store_unique_index(&mut txn, &rid, &self.user, &self.item)
49 .await?;
50
51 txn.commit().await?;
52
53 Ok(Reaction {
54 id: rid,
55 user: self.user.clone(),
56 item: self.item.clone(),
57 reaction: r,
58 })
59 }
60}
61
62#[cfg(test)]
63mod test {
64 use std::marker::PhantomData;
65
66 use crate::{
67 markers::{ItemType, ReactionType, Serializable, UserType, ID},
68 store::{memory::MemoryStore, Store, Transaction},
69 };
70
71 use super::UserItemUnboundedReactionClient;
72
73 #[derive(Clone, Debug)]
75 struct User(String);
76 impl ID for User {
77 fn id(&self) -> &str {
78 &self.0
79 }
80 }
81 impl UserType for User {}
82 #[derive(Clone, Debug)]
83 struct Item(String);
84 impl ID for Item {
85 fn id(&self) -> &str {
86 &self.0
87 }
88 }
89 impl ItemType for Item {}
90
91 #[derive(Copy, Clone, Debug)]
92 enum Vote {
93 Upvote,
94 Downvote,
95 }
96 impl Serializable for Vote {
97 fn serialize(&self) -> String {
98 match *self {
99 Vote::Upvote => "Upvote",
100 Vote::Downvote => "Downvote",
101 }
102 .into()
103 }
104 }
105 impl ReactionType for Vote {}
106
107 #[derive(Clone, Debug)]
108 struct Comment(String);
109 impl ReactionType for Comment {}
110 impl ItemType for Comment {}
111 impl ID for Comment {
112 fn id(&self) -> &str {
113 &self.0
114 }
115 }
116
117 #[tokio::test]
118 async fn test_reaction() {
119 let mut store = MemoryStore::default();
120 let txn = store.begin_txn().await.unwrap();
121
122 let mut client = UserItemUnboundedReactionClient {
123 store: &mut store,
124 user: User("1000".into()),
125 item: Item("2000".into()),
126 reaction_type: PhantomData::<Vote>,
127 };
128 let vote = client.react(Vote::Upvote).await.unwrap();
129
130 let value = txn
132 .get(format!("Vote:User:1000:Item:2000:{}", vote.id))
133 .await
134 .unwrap()
135 .unwrap();
136 assert_eq!(&value, "Upvote");
137
138 let value = txn
139 .get("Vote:User:1000:Item:2000:Upvote".into())
140 .await
141 .unwrap();
142 assert!(value.is_none());
143
144 let vote = client.react(Vote::Downvote).await.unwrap();
145 let value = txn
146 .get(format!("Vote:User:1000:Item:2000:{}", vote.id))
147 .await
148 .unwrap()
149 .unwrap();
150 assert_eq!(&value, "Downvote");
151
152 let mut client = UserItemUnboundedReactionClient {
154 store: &mut store,
155 user: User("1000".into()),
156 item: Item("2000".into()),
157 reaction_type: PhantomData::<Comment>,
158 };
159 let comment = client.react(Comment("3000".into())).await.unwrap();
160
161 let value = txn
162 .get(format!("Comment:User:1000:Item:2000:{}", comment.id))
163 .await
164 .unwrap()
165 .unwrap();
166 assert_eq!(&value, "Comment:3000");
167 let value = txn
168 .get("Comment:User:1000:Item:2000:Comment:3000".into())
169 .await
170 .unwrap()
171 .unwrap();
172 assert_eq!(value, comment.id);
173 }
174}