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

            
3
use axum::{response::IntoResponse, Router};
4
use reqwest;
5
use serde::{Deserialize, Serialize};
6

            
7
use general_mq::Queue;
8
use sylvia_iot_corelib::{
9
    constants::DbEngine,
10
    http::{Json, Query},
11
};
12

            
13
use crate::{
14
    libs::{
15
        config::{self, Config},
16
        mq::{self, Connection},
17
    },
18
    models::{self, ConnOptions, Model, MongoDbOptions, SqliteOptions},
19
};
20

            
21
pub mod middleware;
22
mod v1;
23

            
24
/// The resources used by this service.
25
#[derive(Clone)]
26
pub struct State {
27
    /// The scope root path for the service.
28
    ///
29
    /// For example `/data`, the APIs are
30
    /// - `http://host:port/data/api/v1/application-uldata/xxx`
31
    /// - `http://host:port/data/api/v1/network-uldata/xxx`
32
    pub scope_path: &'static str,
33
    /// The database model.
34
    pub model: Arc<dyn Model>,
35
    /// The sylvia-iot-auth base API path with host.
36
    ///
37
    /// For example, `http://localhost:1080/auth`.
38
    pub auth_base: String,
39
    /// The sylvia-iot-broker base API path with host.
40
    ///
41
    /// For example, `http://localhost:2080/broker`.
42
    pub broker_base: String,
43
    /// The client for internal HTTP requests.
44
    pub client: reqwest::Client,
45
    /// Queue connections. Key is uri.
46
    pub mq_conns: HashMap<String, Connection>,
47
    /// Data channel receivers. Key is data channel name such as `broker.data`, `coremgr.data`, ...
48
    pub data_receivers: HashMap<String, Queue>,
49
}
50

            
51
/// The sylvia-iot module specific error codes in addition to standard
52
/// [`sylvia_iot_corelib::err::ErrResp`].
53
pub struct ErrReq;
54

            
55
/// Query parameters for `GET /version`
56
#[derive(Deserialize)]
57
pub struct GetVersionQuery {
58
    q: Option<String>,
59
}
60

            
61
#[derive(Serialize)]
62
struct GetVersionRes<'a> {
63
    data: GetVersionResData<'a>,
64
}
65

            
66
#[derive(Serialize)]
67
struct GetVersionResData<'a> {
68
    name: &'a str,
69
    version: &'a str,
70
}
71

            
72
const SERV_NAME: &'static str = env!("CARGO_PKG_NAME");
73
const SERV_VER: &'static str = env!("CARGO_PKG_VERSION");
74

            
75
impl ErrReq {
76
    pub const UNIT_NOT_EXIST: (u16, &'static str) = (400, "err_data_unit_not_exist");
77
    pub const USER_NOT_EXIST: (u16, &'static str) = (400, "err_data_user_not_exist");
78
}
79

            
80
/// To create resources for the service.
81
16
pub async fn new_state(
82
16
    scope_path: &'static str,
83
16
    conf: &Config,
84
16
) -> Result<State, Box<dyn StdError>> {
85
16
    let conf = config::apply_default(conf);
86
16
    let db_opts = match conf.db.as_ref().unwrap().engine.as_ref().unwrap().as_str() {
87
16
        DbEngine::MONGODB => {
88
6
            let conf = conf.db.as_ref().unwrap().mongodb.as_ref().unwrap();
89
6
            ConnOptions::MongoDB(MongoDbOptions {
90
6
                url: conf.url.as_ref().unwrap().to_string(),
91
6
                db: conf.database.as_ref().unwrap().to_string(),
92
6
                pool_size: conf.pool_size,
93
6
            })
94
        }
95
        _ => {
96
10
            let conf = conf.db.as_ref().unwrap().sqlite.as_ref().unwrap();
97
10
            ConnOptions::Sqlite(SqliteOptions {
98
10
                path: conf.path.as_ref().unwrap().to_string(),
99
10
            })
100
        }
101
    };
102
16
    let model = models::new(&db_opts).await?;
103
16
    let auth_base = conf.auth.as_ref().unwrap().clone();
104
16
    let broker_base = conf.broker.as_ref().unwrap().clone();
105
16
    let mut mq_conns = HashMap::new();
106
16
    let ch_conf = conf.mq_channels.as_ref().unwrap();
107
16
    let data_receivers = new_data_receivers(&model, &mut mq_conns, ch_conf)?;
108
16
    let state = State {
109
16
        scope_path,
110
16
        model,
111
16
        auth_base,
112
16
        broker_base,
113
16
        client: reqwest::Client::new(),
114
16
        mq_conns,
115
16
        data_receivers,
116
16
    };
117
16
    Ok(state)
118
16
}
119

            
120
/// To register service URIs in the specified root path.
121
2526
pub fn new_service(state: &State) -> Router {
122
2526
    Router::new().nest(
123
2526
        &state.scope_path,
124
2526
        Router::new()
125
2526
            .merge(v1::application_uldata::new_service(
126
2526
                "/api/v1/application-uldata",
127
2526
                state,
128
2526
            ))
129
2526
            .merge(v1::application_dldata::new_service(
130
2526
                "/api/v1/application-dldata",
131
2526
                state,
132
2526
            ))
133
2526
            .merge(v1::network_uldata::new_service(
134
2526
                "/api/v1/network-uldata",
135
2526
                state,
136
2526
            ))
137
2526
            .merge(v1::network_dldata::new_service(
138
2526
                "/api/v1/network-dldata",
139
2526
                state,
140
2526
            ))
141
2526
            .merge(v1::coremgr_opdata::new_service(
142
2526
                "/api/v1/coremgr-opdata",
143
2526
                state,
144
2526
            )),
145
2526
    )
146
2526
}
147

            
148
16
pub fn new_data_receivers(
149
16
    model: &Arc<dyn Model>,
150
16
    mq_conns: &mut HashMap<String, Connection>,
151
16
    ch_conf: &config::MqChannels,
152
16
) -> Result<HashMap<String, Queue>, Box<dyn StdError>> {
153
16
    let mut data_receivers = HashMap::<String, Queue>::new();
154
16

            
155
16
    let conf = ch_conf.broker.as_ref().unwrap();
156
16
    let q = mq::broker::new(model.clone(), mq_conns, &conf)?;
157
16
    data_receivers.insert("broker.data".to_string(), q);
158
16

            
159
16
    let conf = ch_conf.coremgr.as_ref().unwrap();
160
16
    let q = mq::coremgr::new(model.clone(), mq_conns, &conf)?;
161
16
    data_receivers.insert("coremgr.data".to_string(), q);
162
16

            
163
16
    Ok(data_receivers)
164
16
}
165

            
166
8
pub async fn get_version(Query(query): Query<GetVersionQuery>) -> impl IntoResponse {
167
8
    if let Some(q) = query.q.as_ref() {
168
6
        match q.as_str() {
169
6
            "name" => return SERV_NAME.into_response(),
170
4
            "version" => return SERV_VER.into_response(),
171
2
            _ => (),
172
        }
173
2
    }
174

            
175
4
    Json(GetVersionRes {
176
4
        data: GetVersionResData {
177
4
            name: SERV_NAME,
178
4
            version: SERV_VER,
179
4
        },
180
4
    })
181
4
    .into_response()
182
8
}