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

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

            
6
use sylvia_iot_corelib::{
7
    constants::DbEngine,
8
    http::{Json, Query},
9
};
10

            
11
use crate::{
12
    libs::config::{self, Config},
13
    models::{self, ConnOptions, Model, MongoDbOptions, SqliteOptions},
14
};
15

            
16
pub mod oauth2;
17
mod v1;
18

            
19
/// The resources used by this service.
20
#[derive(Clone)]
21
pub struct State {
22
    /// The scope root path for the service.
23
    ///
24
    /// For example `/auth`, the APIs are
25
    /// - `http://host:port/auth/oauth2/xxx`
26
    /// - `http://host:port/auth/api/v1/user/xxx`
27
    /// - `http://host:port/auth/api/v1/client/xxx`
28
    pub scope_path: &'static str,
29
    /// The scopes for accessing APIs.
30
    pub api_scopes: HashMap<String, Vec<String>>,
31
    /// Jinja2 templates.
32
    pub templates: HashMap<String, String>,
33
    /// The database model.
34
    pub model: Arc<dyn Model>,
35
}
36

            
37
/// The sylvia-iot module specific error codes in addition to standard [`ErrResp`].
38
pub struct ErrReq;
39

            
40
/// Query parameters for `GET /version`
41
7
#[derive(Deserialize)]
42
pub struct GetVersionQuery {
43
    q: Option<String>,
44
}
45

            
46
#[derive(Serialize)]
47
struct GetVersionRes<'a> {
48
    data: GetVersionResData<'a>,
49
}
50

            
51
#[derive(Serialize)]
52
struct GetVersionResData<'a> {
53
    name: &'a str,
54
    version: &'a str,
55
}
56

            
57
const SERV_NAME: &'static str = env!("CARGO_PKG_NAME");
58
const SERV_VER: &'static str = env!("CARGO_PKG_VERSION");
59

            
60
impl ErrReq {
61
    pub const USER_EXIST: (u16, &'static str) = (400, "err_auth_user_exist");
62
    pub const USER_NOT_EXIST: (u16, &'static str) = (400, "err_auth_user_not_exist");
63
}
64

            
65
/// To create resources for the service.
66
8
pub async fn new_state(
67
8
    scope_path: &'static str,
68
8
    conf: &Config,
69
8
) -> Result<State, Box<dyn StdError>> {
70
8
    let conf = config::apply_default(conf);
71
8
    let db_opts = match conf.db.as_ref().unwrap().engine.as_ref().unwrap().as_str() {
72
8
        DbEngine::MONGODB => {
73
3
            let conf = conf.db.as_ref().unwrap().mongodb.as_ref().unwrap();
74
3
            ConnOptions::MongoDB(MongoDbOptions {
75
3
                url: conf.url.as_ref().unwrap().to_string(),
76
3
                db: conf.database.as_ref().unwrap().to_string(),
77
3
                pool_size: conf.pool_size,
78
3
            })
79
        }
80
        _ => {
81
5
            let conf = conf.db.as_ref().unwrap().sqlite.as_ref().unwrap();
82
5
            ConnOptions::Sqlite(SqliteOptions {
83
5
                path: conf.path.as_ref().unwrap().to_string(),
84
5
            })
85
        }
86
    };
87
232
    let model = models::new(&db_opts).await?;
88
    Ok(State {
89
8
        scope_path: match scope_path.len() {
90
            0 => "/",
91
8
            _ => scope_path,
92
        },
93
8
        api_scopes: conf.api_scopes.as_ref().unwrap().clone(),
94
8
        templates: conf.templates.as_ref().unwrap().clone(),
95
8
        model,
96
    })
97
8
}
98

            
99
/// To register service URIs in the specified root path.
100
974
pub fn new_service(state: &State) -> Router {
101
974
    Router::new().nest(
102
974
        &state.scope_path,
103
974
        Router::new()
104
974
            .nest("/oauth2", oauth2::new_service(state))
105
974
            .merge(v1::auth::new_service("/api/v1/auth", state))
106
974
            .merge(v1::user::new_service("/api/v1/user", state))
107
974
            .merge(v1::client::new_service("/api/v1/client", state)),
108
974
    )
109
974
}
110

            
111
4
pub async fn get_version(Query(query): Query<GetVersionQuery>) -> impl IntoResponse {
112
4
    if let Some(q) = query.q.as_ref() {
113
3
        match q.as_str() {
114
3
            "name" => return SERV_NAME.into_response(),
115
2
            "version" => return SERV_VER.into_response(),
116
1
            _ => (),
117
        }
118
1
    }
119

            
120
2
    Json(GetVersionRes {
121
2
        data: GetVersionResData {
122
2
            name: SERV_NAME,
123
2
            version: SERV_VER,
124
2
        },
125
2
    })
126
2
    .into_response()
127
4
}