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
1224
#[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
7
    pub async fn new(conn: Arc<Database>) -> Result<Self, Box<dyn StdError>> {
46
7
        let model = Model { conn };
47
14
        model.init().await?;
48
7
        Ok(model)
49
7
    }
50
}
51

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

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

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

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

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