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

            
3
use async_trait::async_trait;
4
use chrono::TimeDelta;
5
use futures::TryStreamExt;
6
use mongodb::{
7
    bson::{doc, DateTime, Document},
8
    Database,
9
};
10
use serde::{Deserialize, Serialize};
11

            
12
use sylvia_iot_corelib::err::E_UNKNOWN;
13

            
14
use super::super::authorization_code::{
15
    AuthorizationCode, AuthorizationCodeModel, QueryCond, EXPIRES,
16
};
17

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

            
24
/// MongoDB schema.
25
#[derive(Deserialize, Serialize)]
26
struct Schema {
27
    code: String,
28
    #[serde(rename = "expiresAt")]
29
    expires_at: DateTime,
30
    #[serde(rename = "redirectUri")]
31
    redirect_uri: String,
32
    scope: Option<String>,
33
    #[serde(rename = "clientId")]
34
    client_id: String,
35
    #[serde(rename = "userId")]
36
    user_id: String,
37
    #[serde(rename = "createdAt")]
38
    created_at: DateTime,
39
}
40

            
41
const COL_NAME: &'static str = "authorizationCode";
42

            
43
impl Model {
44
    /// To create the model instance with a database connection.
45
14
    pub async fn new(conn: Arc<Database>) -> Result<Self, Box<dyn StdError>> {
46
14
        let model = Model { conn };
47
14
        model.init().await?;
48
14
        Ok(model)
49
14
    }
50
}
51

            
52
#[async_trait]
53
impl AuthorizationCodeModel for Model {
54
24
    async fn init(&self) -> Result<(), Box<dyn StdError>> {
55
24
        let indexes = vec![
56
24
            doc! {"name": "code_1", "key": {"code": 1}, "unique": true},
57
24
            doc! {"name": "clientId_1", "key": {"clientId": 1}},
58
24
            doc! {"name": "userId_1", "key": {"userId": 1}},
59
24
            doc! {"name": "ttl_1", "key": {"createdAt": 1}, "expireAfterSeconds": EXPIRES + 60},
60
24
        ];
61
24
        let command = doc! {
62
24
            "createIndexes": COL_NAME,
63
24
            "indexes": indexes,
64
24
        };
65
24
        self.conn.run_command(command).await?;
66
24
        Ok(())
67
48
    }
68

            
69
290
    async fn get(&self, code: &str) -> Result<Option<AuthorizationCode>, Box<dyn StdError>> {
70
290
        let mut cursor = self
71
290
            .conn
72
290
            .collection::<Schema>(COL_NAME)
73
290
            .find(doc! {"code": code})
74
290
            .await?;
75
290
        if let Some(item) = cursor.try_next().await? {
76
272
            return Ok(Some(AuthorizationCode {
77
272
                code: item.code,
78
272
                expires_at: item.expires_at.into(),
79
272
                redirect_uri: item.redirect_uri,
80
272
                scope: item.scope,
81
272
                client_id: item.client_id,
82
272
                user_id: item.user_id,
83
272
            }));
84
18
        }
85
18
        Ok(None)
86
580
    }
87

            
88
310
    async fn add(&self, code: &AuthorizationCode) -> Result<(), Box<dyn StdError>> {
89
310
        let item = Schema {
90
310
            code: code.code.clone(),
91
310
            expires_at: code.expires_at.into(),
92
310
            redirect_uri: code.redirect_uri.clone(),
93
310
            scope: code.scope.clone(),
94
310
            client_id: code.client_id.clone(),
95
310
            user_id: code.user_id.clone(),
96
310
            created_at: match TimeDelta::try_seconds(EXPIRES) {
97
                None => panic!("{}", E_UNKNOWN),
98
310
                Some(t) => (code.expires_at - t).into(),
99
310
            },
100
310
        };
101
310
        self.conn
102
310
            .collection::<Schema>(COL_NAME)
103
310
            .insert_one(item)
104
310
            .await?;
105
308
        Ok(())
106
620
    }
107

            
108
274
    async fn del(&self, cond: &QueryCond) -> Result<(), Box<dyn StdError>> {
109
274
        let filter = get_query_filter(cond);
110
274
        self.conn
111
274
            .collection::<Schema>(COL_NAME)
112
274
            .delete_many(filter)
113
274
            .await?;
114
274
        Ok(())
115
548
    }
116
}
117

            
118
/// Transforms query conditions to the MongoDB document.
119
274
fn get_query_filter(cond: &QueryCond) -> Document {
120
274
    let mut filter = Document::new();
121
274
    if let Some(value) = cond.code {
122
256
        filter.insert("code", value);
123
256
    }
124
274
    if let Some(value) = cond.client_id {
125
6
        filter.insert("clientId", value);
126
268
    }
127
274
    if let Some(value) = cond.user_id {
128
14
        filter.insert("userId", value);
129
260
    }
130
274
    filter
131
274
}