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::login_session::{LoginSession, LoginSessionModel, QueryCond, EXPIRES};
15

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

            
22
/// MongoDB schema.
23
864
#[derive(Deserialize, Serialize)]
24
struct Schema {
25
    #[serde(rename = "sessionId")]
26
    session_id: String,
27
    #[serde(rename = "expiresAt")]
28
    expires_at: DateTime,
29
    #[serde(rename = "userId")]
30
    user_id: String,
31
    #[serde(rename = "createdAt")]
32
    created_at: DateTime,
33
}
34

            
35
const COL_NAME: &'static str = "loginSession";
36

            
37
impl Model {
38
    /// To create the model instance with a database connection.
39
7
    pub async fn new(conn: Arc<Database>) -> Result<Self, Box<dyn StdError>> {
40
7
        let model = Model { conn };
41
14
        model.init().await?;
42
7
        Ok(model)
43
7
    }
44
}
45

            
46
#[async_trait]
47
impl LoginSessionModel for Model {
48
12
    async fn init(&self) -> Result<(), Box<dyn StdError>> {
49
12
        let indexes = vec![
50
12
            doc! {"name": "sessionId_1", "key": {"sessionId": 1}, "unique": true},
51
12
            doc! {"name": "userId_1", "key": {"userId": 1}},
52
12
            doc! {"name": "ttl_1", "key": {"createdAt": 1}, "expireAfterSeconds": EXPIRES + 60},
53
12
        ];
54
12
        let command = doc! {
55
12
            "createIndexes": COL_NAME,
56
12
            "indexes": indexes,
57
12
        };
58
24
        self.conn.run_command(command, None).await?;
59
12
        Ok(())
60
12
    }
61

            
62
149
    async fn get(&self, session_id: &str) -> Result<Option<LoginSession>, Box<dyn StdError>> {
63
149
        let mut cursor = self
64
149
            .conn
65
149
            .collection::<Schema>(COL_NAME)
66
149
            .find(doc! {"sessionId": session_id}, None)
67
298
            .await?;
68
149
        if let Some(item) = cursor.try_next().await? {
69
149
            return Ok(Some(LoginSession {
70
144
                session_id: item.session_id,
71
144
                expires_at: item.expires_at.into(),
72
144
                user_id: item.user_id,
73
144
            }));
74
149
        }
75
5
        Ok(None)
76
149
    }
77

            
78
153
    async fn add(&self, session: &LoginSession) -> Result<(), Box<dyn StdError>> {
79
153
        let item = Schema {
80
153
            session_id: session.session_id.clone(),
81
153
            expires_at: session.expires_at.into(),
82
153
            user_id: session.user_id.clone(),
83
153
            created_at: match TimeDelta::try_seconds(EXPIRES) {
84
153
                None => panic!("{}", E_UNKNOWN),
85
153
                Some(t) => (session.expires_at - t).into(),
86
153
            },
87
153
        };
88
153
        self.conn
89
153
            .collection::<Schema>(COL_NAME)
90
153
            .insert_one(item, None)
91
307
            .await?;
92
153
        Ok(())
93
153
    }
94

            
95
142
    async fn del(&self, cond: &QueryCond) -> Result<(), Box<dyn StdError>> {
96
142
        let filter = get_query_filter(cond);
97
142
        self.conn
98
142
            .collection::<Schema>(COL_NAME)
99
142
            .delete_many(filter, None)
100
284
            .await?;
101
142
        Ok(())
102
142
    }
103
}
104

            
105
/// Transforms query conditions to the MongoDB document.
106
142
fn get_query_filter(cond: &QueryCond) -> Document {
107
142
    let mut filter = Document::new();
108
142
    if let Some(value) = cond.session_id {
109
141
        filter.insert("sessionId", value);
110
141
    }
111
142
    if let Some(value) = cond.user_id {
112
1
        filter.insert("userId", value);
113
141
    }
114
142
    filter
115
142
}