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

            
3
use async_trait::async_trait;
4
use futures::TryStreamExt;
5
use mongodb::{
6
    bson::{doc, Bson, DateTime, Document, Regex},
7
    options::FindOptions,
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
19701
#[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
7
    pub async fn new(conn: Arc<Database>) -> Result<Self, Box<dyn StdError>> {
57
7
        let model = Model { conn };
58
14
        model.init().await?;
59
7
        Ok(model)
60
7
    }
61
}
62

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

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

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

            
110
57
        let mut count: u64 = 0;
111
57
        let mut list = Vec::new();
112
985
        while let Some(item) = cursor.try_next().await? {
113
939
            list.push(item);
114
939
            if let Some(cursor_max) = opts.cursor_max {
115
880
                count += 1;
116
880
                if count >= cursor_max {
117
57
                    return Ok((list, Some(cursor)));
118
869
                }
119
59
            }
120
57
        }
121
57
        Ok((list, None))
122
57
    }
123

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

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

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

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

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

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

            
218
4
    fn offset(&self) -> u64 {
219
4
        self.offset
220
4
    }
221
}
222

            
223
/// Transforms query conditions to the MongoDB document.
224
880
fn get_query_filter(cond: &QueryCond) -> Document {
225
880
    let mut filter = Document::new();
226
880
    if let Some(value) = cond.user_id {
227
12
        filter.insert("userId", value);
228
868
    }
229
880
    if let Some(value) = cond.client_id {
230
878
        filter.insert("clientId", value);
231
878
    }
232
880
    filter
233
880
}
234

            
235
/// Transforms query conditions to the MongoDB document.
236
56
fn get_list_query_filter(cond: &ListQueryCond) -> Document {
237
56
    let mut filter = Document::new();
238
56
    if let Some(value) = cond.user_id {
239
25
        filter.insert("userId", value);
240
31
    }
241
56
    if let Some(value) = cond.client_id {
242
4
        filter.insert("clientId", value);
243
52
    }
244
56
    if let Some(value) = cond.name_contains {
245
8
        filter.insert(
246
8
            "name",
247
8
            Regex {
248
8
                pattern: value.to_string(),
249
8
                options: "i".to_string(),
250
8
            },
251
8
        );
252
48
    }
253
56
    filter
254
56
}
255

            
256
/// Transforms model options to the options.
257
46
fn get_find_options(opts: &ListOptions) -> FindOptions {
258
46
    let mut options = FindOptions::builder().build();
259
46
    if let Some(offset) = opts.offset {
260
10
        options.skip = Some(offset);
261
36
    }
262
46
    if let Some(limit) = opts.limit {
263
25
        if limit > 0 {
264
24
            options.limit = Some(limit as i64);
265
24
        }
266
21
    }
267
46
    if let Some(sort_list) = opts.sort.as_ref() {
268
38
        if sort_list.len() > 0 {
269
37
            let mut sort_opts = Document::new();
270
44
            for cond in sort_list.iter() {
271
44
                let key = match cond.key {
272
11
                    SortKey::CreatedAt => "createdAt",
273
4
                    SortKey::ModifiedAt => "modifiedAt",
274
29
                    SortKey::Name => "name",
275
                };
276
44
                if cond.asc {
277
37
                    sort_opts.insert(key.to_string(), 1);
278
37
                } else {
279
7
                    sort_opts.insert(key.to_string(), -1);
280
7
                }
281
            }
282
37
            options.sort = Some(sort_opts);
283
1
        }
284
8
    }
285
46
    options
286
46
}
287

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

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