revolt_database/models/users/ops/
mongodb.rs1use ::mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
2use futures::StreamExt;
3use revolt_result::Result;
4
5use crate::DocumentId;
6use crate::IntoDocumentPath;
7use crate::MongoDb;
8use crate::{FieldsUser, PartialUser, RelationshipStatus, User};
9
10use super::AbstractUsers;
11
12static COL: &str = "users";
13
14#[async_trait]
15impl AbstractUsers for MongoDb {
16 async fn insert_user(&self, user: &User) -> Result<()> {
18 query!(self, insert_one, COL, &user).map(|_| ())
19 }
20
21 async fn fetch_user(&self, id: &str) -> Result<User> {
23 query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
24 }
25
26 async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result<User> {
28 query!(
29 self,
30 find_one_with_options,
31 COL,
32 doc! {
33 "username": username,
34 "discriminator": discriminator
35 },
36 FindOneOptions::builder()
37 .collation(
38 Collation::builder()
39 .locale("en")
40 .strength(CollationStrength::Secondary)
41 .build(),
42 )
43 .build()
44 )?
45 .ok_or_else(|| create_error!(NotFound))
46 }
47
48 async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>> {
50 Ok(self
51 .col::<User>(COL)
52 .find(doc! {
53 "_id": {
54 "$in": ids
55 }
56 })
57 .await
58 .map_err(|_| create_database_error!("find", COL))?
59 .filter_map(|s| async {
60 if cfg!(debug_assertions) {
61 Some(s.unwrap())
62 } else {
63 s.ok()
64 }
65 })
66 .collect()
67 .await)
68 }
69
70 async fn fetch_discriminators_in_use(&self, username: &str) -> Result<Vec<String>> {
72 #[derive(Deserialize)]
73 struct UserDocument {
74 discriminator: String,
75 }
76
77 Ok(self
78 .col::<UserDocument>(COL)
79 .find(doc! {
80 "username": username
81 })
82 .with_options(
83 FindOptions::builder()
84 .collation(
85 Collation::builder()
86 .locale("en")
87 .strength(CollationStrength::Secondary)
88 .build(),
89 )
90 .projection(doc! { "_id": 0, "discriminator": 1 })
91 .build(),
92 )
93 .await
94 .map_err(|_| create_database_error!("find", COL))?
95 .filter_map(|s| async { s.ok() })
96 .collect::<Vec<UserDocument>>()
97 .await
98 .into_iter()
99 .map(|user| user.discriminator)
100 .collect::<Vec<String>>())
101 }
102
103 async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
105 Ok(self
106 .col::<DocumentId>(COL)
107 .find(doc! {
108 "$and": [
109 { "relations": { "$elemMatch": { "_id": &user_a, "status": "Friend" } } },
110 { "relations": { "$elemMatch": { "_id": &user_b, "status": "Friend" } } }
111 ]
112 })
113 .with_options(FindOptions::builder().projection(doc! { "_id": 1 }).build())
114 .await
115 .map_err(|_| create_database_error!("find", COL))?
116 .filter_map(|s| async { s.ok() })
117 .map(|user| user.id)
118 .collect()
119 .await)
120 }
121
122 async fn fetch_mutual_channel_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
124 Ok(self
125 .col::<DocumentId>("channels")
126 .find(doc! {
127 "channel_type": {
128 "$in": ["Group", "DirectMessage"]
129 },
130 "recipients": {
131 "$all": [ user_a, user_b ]
132 }
133 })
134 .with_options(FindOptions::builder().projection(doc! { "_id": 1 }).build())
135 .await
136 .map_err(|_| create_database_error!("find", "channels"))?
137 .filter_map(|s| async { s.ok() })
138 .map(|user| user.id)
139 .collect()
140 .await)
141 }
142
143 async fn fetch_mutual_server_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
145 Ok(self
146 .col::<DocumentId>("server_members")
147 .aggregate(vec![
148 doc! {
149 "$match": {
150 "_id.user": user_a
151 }
152 },
153 doc! {
154 "$lookup": {
155 "from": "server_members",
156 "as": "members",
157 "let": {
158 "server": "$_id.server"
159 },
160 "pipeline": [
161 {
162 "$match": {
163 "$expr": {
164 "$and": [
165 { "$eq": [ "$_id.user", user_b ] },
166 { "$eq": [ "$_id.server", "$$server" ] }
167 ]
168 }
169 }
170 }
171 ]
172 }
173 },
174 doc! {
175 "$match": {
176 "members": {
177 "$size": 1_i32
178 }
179 }
180 },
181 doc! {
182 "$project": {
183 "_id": "$_id.server"
184 }
185 },
186 ])
187 .await
188 .map_err(|_| create_database_error!("aggregate", "server_members"))?
189 .filter_map(|s| async { s.ok() })
190 .filter_map(|doc| async move { doc.get_str("_id").map(|id| id.to_string()).ok() })
191 .collect()
192 .await)
193 }
194
195 async fn update_user(
197 &self,
198 id: &str,
199 partial: &PartialUser,
200 remove: Vec<FieldsUser>,
201 ) -> Result<()> {
202 if remove.contains(&FieldsUser::StatusText) && partial.status.is_some() {
203 let _: Result<()> = query!(
205 self,
206 update_one_by_id,
207 COL,
208 id,
209 PartialUser {
210 ..Default::default()
211 },
212 remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
213 None
214 )
215 .map(|_| ());
216
217 query!(self, update_one_by_id, COL, id, partial, vec![], None).map(|_| ())
218 } else {
219 query!(
220 self,
221 update_one_by_id,
222 COL,
223 id,
224 partial,
225 remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
226 None
227 )
228 .map(|_| ())
229 }
230 }
231
232 async fn set_relationship(
236 &self,
237 user_id: &str,
238 target_id: &str,
239 relationship: &RelationshipStatus,
240 ) -> Result<()> {
241 if let RelationshipStatus::None = relationship {
242 return self.pull_relationship(user_id, target_id).await;
243 }
244
245 self.col::<User>(COL)
246 .update_one(
247 doc! {
248 "_id": user_id
249 },
250 vec![doc! {
251 "$set": {
252 "relations": {
253 "$concatArrays": [
254 {
255 "$ifNull": [
256 {
257 "$filter": {
258 "input": "$relations",
259 "cond": {
260 "$ne": [
261 "$$this._id",
262 target_id
263 ]
264 }
265 }
266 },
267 []
268 ]
269 },
270 [
271 {
272 "_id": target_id,
273 "status": format!("{relationship:?}")
274 }
275 ]
276 ]
277 }
278 }
279 }],
280 )
281 .await
282 .map(|_| ())
283 .map_err(|_| create_database_error!("update_one", "user"))
284 }
285
286 async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> {
288 self.col::<User>(COL)
289 .update_one(
290 doc! {
291 "_id": user_id
292 },
293 doc! {
294 "$pull": {
295 "relations": {
296 "_id": target_id
297 }
298 }
299 },
300 )
301 .await
302 .map(|_| ())
303 .map_err(|_| create_database_error!("update_one", COL))
304 }
305
306 async fn delete_user(&self, id: &str) -> Result<()> {
308 query!(self, delete_one_by_id, COL, id).map(|_| ())
309 }
310
311 async fn clear_user_relationships(&self, target_id: &str, user_ids: Vec<String>) -> Result<()> {
313 self.col::<User>(COL)
314 .update_many(
315 doc! { "_id": { "$in": user_ids } },
316 doc! {
317 "$pull": {
318 "relations": {
319 "_id": target_id.to_string()
320 }
321 }
322 },
323 )
324 .await
325 .map(|_| ())
326 .map_err(|_| create_database_error!("bulk_write", COL))
327 }
328}
329
330impl IntoDocumentPath for FieldsUser {
331 fn as_path(&self) -> Option<&'static str> {
332 Some(match self {
333 FieldsUser::Avatar => "avatar",
334 FieldsUser::ProfileBackground => "profile.background",
335 FieldsUser::ProfileContent => "profile.content",
336 FieldsUser::StatusPresence => "status.presence",
337 FieldsUser::StatusText => "status.text",
338 FieldsUser::DisplayName => "display_name",
339 FieldsUser::Pronouns => "pronouns",
340 FieldsUser::Suspension => "suspended_until",
341 FieldsUser::None => "none",
342 })
343 }
344}