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
38
/// [`sylvia_iot_corelib::err::ErrResp`].
39
pub struct ErrReq;
40

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

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

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

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

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

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

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

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

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