1
use std::{error::Error as StdError, sync::Arc};
2

            
3
use async_trait::async_trait;
4
use futures::TryStreamExt;
5
use mongodb::{
6
    action::Find,
7
    bson::{doc, Bson, DateTime, Document, Regex},
8
    Cursor as MongoDbCursor, Database,
9
};
10
use serde::{Deserialize, Serialize};
11

            
12
use super::super::client::{
13
    Client, ClientModel, Cursor, ListOptions, ListQueryCond, QueryCond, SortKey, UpdateQueryCond,
14
    Updates,
15
};
16

            
17
/// Model instance.
18
pub struct Model {
19
    /// The associated database connection.
20
    conn: Arc<Database>,
21
}
22

            
23
/// Cursor instance.
24
struct DbCursor {
25
    /// The associated collection cursor.
26
    cursor: MongoDbCursor<Schema>,
27
    /// (Useless) only for Cursor trait implementation.
28
    offset: u64,
29
}
30

            
31
/// MongoDB schema.
32
#[derive(Deserialize, Serialize)]
33
struct Schema {
34
    #[serde(rename = "clientId")]
35
    client_id: String,
36
    #[serde(rename = "createdAt")]
37
    created_at: DateTime,
38
    #[serde(rename = "modifiedAt")]
39
    modified_at: DateTime,
40
    #[serde(rename = "clientSecret")]
41
    client_secret: Option<String>,
42
    #[serde(rename = "redirectUris")]
43
    redirect_uris: Vec<String>,
44
    scopes: Vec<String>,
45
    #[serde(rename = "userId")]
46
    user_id: String,
47
    name: String,
48
    #[serde(rename = "imageUrl")]
49
    image_url: Option<String>,
50
}
51

            
52
const COL_NAME: &'static str = "client";
53

            
54
impl Model {
55
    /// To create the model instance with a database connection.
56
14
    pub async fn new(conn: Arc<Database>) -> Result<Self, Box<dyn StdError>> {
57
14
        let model = Model { conn };
58
14
        model.init().await?;
59
14
        Ok(model)
60
14
    }
61
}
62

            
63
#[async_trait]
64
impl ClientModel for Model {
65
24
    async fn init(&self) -> Result<(), Box<dyn StdError>> {
66
24
        let indexes = vec![
67
24
            doc! {"name": "clientId_1", "key": {"clientId": 1}, "unique": true},
68
24
            doc! {"name": "createdAt_1", "key": {"createdAt": 1}},
69
24
            doc! {"name": "modifiedAt_1", "key": {"modifiedAt": 1}},
70
24
            doc! {"name": "userId_1", "key": {"userId": 1}},
71
24
            doc! {"name": "name_1", "key": {"name": 1}},
72
24
        ];
73
24
        let command = doc! {
74
24
            "createIndexes": COL_NAME,
75
24
            "indexes": indexes,
76
24
        };
77
24
        self.conn.run_command(command).await?;
78
24
        Ok(())
79
48
    }
80

            
81
20
    async fn count(&self, cond: &ListQueryCond) -> Result<u64, Box<dyn StdError>> {
82
20
        let filter = get_list_query_filter(cond);
83
20
        let count = self
84
20
            .conn
85
20
            .collection::<Schema>(COL_NAME)
86
20
            .count_documents(filter)
87
20
            .await?;
88
20
        Ok(count)
89
40
    }
90

            
91
    async fn list(
92
        &self,
93
        opts: &ListOptions,
94
        cursor: Option<Box<dyn Cursor>>,
95
114
    ) -> Result<(Vec<Client>, Option<Box<dyn Cursor>>), Box<dyn StdError>> {
96
114
        let mut cursor = match cursor {
97
            None => {
98
92
                let filter = get_list_query_filter(opts.cond);
99
92
                Box::new(DbCursor::new(
100
92
                    build_find_options(opts, self.conn.collection::<Schema>(COL_NAME).find(filter))
101
92
                        .await?,
102
                ))
103
            }
104
22
            Some(cursor) => cursor,
105
        };
106

            
107
114
        let mut count: u64 = 0;
108
114
        let mut list = Vec::new();
109
1970
        while let Some(item) = cursor.try_next().await? {
110
1878
            list.push(item);
111
1878
            if let Some(cursor_max) = opts.cursor_max {
112
1760
                count += 1;
113
1760
                if count >= cursor_max {
114
22
                    return Ok((list, Some(cursor)));
115
1738
                }
116
118
            }
117
        }
118
92
        Ok((list, None))
119
228
    }
120

            
121
1726
    async fn get(&self, cond: &QueryCond) -> Result<Option<Client>, Box<dyn StdError>> {
122
1726
        let filter = get_query_filter(cond);
123
1726
        let mut cursor = self
124
1726
            .conn
125
1726
            .collection::<Schema>(COL_NAME)
126
1726
            .find(filter)
127
1726
            .await?;
128
1726
        if let Some(item) = cursor.try_next().await? {
129
1692
            return Ok(Some(Client {
130
1692
                client_id: item.client_id,
131
1692
                created_at: item.created_at.into(),
132
1692
                modified_at: item.modified_at.into(),
133
1692
                client_secret: item.client_secret,
134
1692
                redirect_uris: item.redirect_uris,
135
1692
                scopes: item.scopes,
136
1692
                user_id: item.user_id,
137
1692
                name: item.name,
138
1692
                image_url: item.image_url,
139
1692
            }));
140
34
        }
141
34
        Ok(None)
142
3452
    }
143

            
144
1068
    async fn add(&self, client: &Client) -> Result<(), Box<dyn StdError>> {
145
1068
        let item = Schema {
146
1068
            client_id: client.client_id.clone(),
147
1068
            created_at: client.created_at.into(),
148
1068
            modified_at: client.modified_at.into(),
149
1068
            client_secret: client.client_secret.clone(),
150
1068
            redirect_uris: client.redirect_uris.clone(),
151
1068
            scopes: client.scopes.clone(),
152
1068
            user_id: client.user_id.clone(),
153
1068
            name: client.name.clone(),
154
1068
            image_url: client.image_url.clone(),
155
1068
        };
156
1068
        self.conn
157
1068
            .collection::<Schema>(COL_NAME)
158
1068
            .insert_one(item)
159
1068
            .await?;
160
1066
        Ok(())
161
2136
    }
162

            
163
20
    async fn del(&self, cond: &QueryCond) -> Result<(), Box<dyn StdError>> {
164
20
        let filter = get_query_filter(cond);
165
20
        self.conn
166
20
            .collection::<Schema>(COL_NAME)
167
20
            .delete_many(filter)
168
20
            .await?;
169
20
        Ok(())
170
40
    }
171

            
172
    async fn update(
173
        &self,
174
        cond: &UpdateQueryCond,
175
        updates: &Updates,
176
28
    ) -> Result<(), Box<dyn StdError>> {
177
28
        let filter = get_update_query_filter(cond);
178
28
        if let Some(updates) = get_update_doc(updates) {
179
26
            self.conn
180
26
                .collection::<Schema>(COL_NAME)
181
26
                .update_one(filter, updates)
182
26
                .await?;
183
2
        }
184
28
        return Ok(());
185
56
    }
186
}
187

            
188
impl DbCursor {
189
    /// To create the cursor instance with a collection cursor.
190
92
    pub fn new(cursor: MongoDbCursor<Schema>) -> Self {
191
92
        DbCursor { cursor, offset: 0 }
192
92
    }
193
}
194

            
195
#[async_trait]
196
impl Cursor for DbCursor {
197
1970
    async fn try_next(&mut self) -> Result<Option<Client>, Box<dyn StdError>> {
198
1970
        if let Some(item) = self.cursor.try_next().await? {
199
1878
            self.offset += 1;
200
1878
            return Ok(Some(Client {
201
1878
                client_id: item.client_id,
202
1878
                created_at: item.created_at.into(),
203
1878
                modified_at: item.modified_at.into(),
204
1878
                client_secret: item.client_secret,
205
1878
                redirect_uris: item.redirect_uris,
206
1878
                scopes: item.scopes,
207
1878
                user_id: item.user_id,
208
1878
                name: item.name,
209
1878
                image_url: item.image_url,
210
1878
            }));
211
92
        }
212
92
        Ok(None)
213
3940
    }
214

            
215
8
    fn offset(&self) -> u64 {
216
8
        self.offset
217
8
    }
218
}
219

            
220
/// Transforms query conditions to the MongoDB document.
221
1746
fn get_query_filter(cond: &QueryCond) -> Document {
222
1746
    let mut filter = Document::new();
223
1746
    if let Some(value) = cond.user_id {
224
24
        filter.insert("userId", value);
225
1722
    }
226
1746
    if let Some(value) = cond.client_id {
227
1742
        filter.insert("clientId", value);
228
1742
    }
229
1746
    filter
230
1746
}
231

            
232
/// Transforms query conditions to the MongoDB document.
233
112
fn get_list_query_filter(cond: &ListQueryCond) -> Document {
234
112
    let mut filter = Document::new();
235
112
    if let Some(value) = cond.user_id {
236
50
        filter.insert("userId", value);
237
62
    }
238
112
    if let Some(value) = cond.client_id {
239
8
        filter.insert("clientId", value);
240
104
    }
241
112
    if let Some(value) = cond.name_contains {
242
16
        filter.insert(
243
16
            "name",
244
16
            Regex {
245
16
                pattern: value.to_string(),
246
16
                options: "i".to_string(),
247
16
            },
248
16
        );
249
96
    }
250
112
    filter
251
112
}
252

            
253
/// Transforms model options to the options.
254
92
fn build_find_options<'a, T>(opts: &ListOptions, mut find: Find<'a, T>) -> Find<'a, T>
255
92
where
256
92
    T: Send + Sync,
257
92
{
258
92
    if let Some(offset) = opts.offset {
259
20
        find = find.skip(offset);
260
72
    }
261
92
    if let Some(limit) = opts.limit {
262
50
        if limit > 0 {
263
48
            find = find.limit(limit as i64);
264
48
        }
265
42
    }
266
92
    if let Some(sort_list) = opts.sort.as_ref() {
267
76
        if sort_list.len() > 0 {
268
74
            let mut sort_opts = Document::new();
269
88
            for cond in sort_list.iter() {
270
88
                let key = match cond.key {
271
22
                    SortKey::CreatedAt => "createdAt",
272
8
                    SortKey::ModifiedAt => "modifiedAt",
273
58
                    SortKey::Name => "name",
274
                };
275
88
                if cond.asc {
276
74
                    sort_opts.insert(key.to_string(), 1);
277
74
                } else {
278
14
                    sort_opts.insert(key.to_string(), -1);
279
14
                }
280
            }
281
74
            find = find.sort(sort_opts);
282
2
        }
283
16
    }
284
92
    find
285
92
}
286

            
287
/// Transforms query conditions to the MongoDB document.
288
28
fn get_update_query_filter(cond: &UpdateQueryCond) -> Document {
289
28
    doc! {
290
28
        "userId": cond.user_id,
291
28
        "clientId": cond.client_id,
292
28
    }
293
28
}
294

            
295
/// Transforms the model object to the MongoDB document.
296
28
fn get_update_doc(updates: &Updates) -> Option<Document> {
297
28
    let mut count = 0;
298
28
    let mut document = Document::new();
299
28
    if let Some(value) = updates.modified_at.as_ref() {
300
26
        document.insert(
301
26
            "modifiedAt",
302
26
            DateTime::from_millis(value.timestamp_millis()),
303
26
        );
304
26
        count += 1;
305
26
    }
306
28
    if let Some(value) = updates.client_secret.as_ref() {
307
6
        match value {
308
2
            None => {
309
2
                document.insert("clientSecret", Bson::Null);
310
2
            }
311
4
            Some(value) => {
312
4
                document.insert("clientSecret", value);
313
4
            }
314
        }
315
6
        count += 1;
316
22
    }
317
28
    if let Some(value) = updates.redirect_uris {
318
16
        document.insert("redirectUris", value);
319
16
        count += 1;
320
16
    }
321
28
    if let Some(value) = updates.scopes {
322
16
        document.insert("scopes", value);
323
16
        count += 1;
324
16
    }
325
28
    if let Some(value) = updates.name {
326
10
        document.insert("name", value);
327
10
        count += 1;
328
18
    }
329
28
    if let Some(value) = updates.image_url.as_ref() {
330
16
        match value {
331
8
            None => {
332
8
                document.insert("imageUrl", Bson::Null);
333
8
            }
334
8
            Some(value) => {
335
8
                document.insert("imageUrl", value);
336
8
            }
337
        }
338
16
        count += 1;
339
12
    }
340
28
    if count == 0 {
341
2
        return None;
342
26
    }
343
26
    Some(doc! {"$set": document})
344
28
}