1
use std::error::Error as StdError;
2

            
3
use axum::{
4
    Router,
5
    body::Body,
6
    extract::{Path, Request, State},
7
    response::IntoResponse,
8
    routing,
9
};
10
use bytes::{Bytes, BytesMut};
11
use csv::WriterBuilder;
12
use futures_util::StreamExt;
13
use log::error;
14
use serde::{Deserialize, Serialize};
15
use serde_json::Deserializer;
16

            
17
use sylvia_iot_corelib::err::ErrResp;
18

            
19
use super::{super::State as AppState, ListResp, api_bridge, list_api_bridge};
20

            
21
#[derive(Deserialize)]
22
struct ClientIdPath {
23
    client_id: String,
24
}
25

            
26
#[derive(Deserialize)]
27
struct UserIdPath {
28
    user_id: String,
29
}
30

            
31
#[derive(Deserialize, Serialize)]
32
struct Client {
33
    #[serde(rename = "clientId")]
34
    client_id: String,
35
    #[serde(rename = "createdAt")]
36
    created_at: String,
37
    #[serde(rename = "modifiedAt")]
38
    modified_at: String,
39
    #[serde(rename = "clientSecret")]
40
    client_secret: Option<String>,
41
    #[serde(rename = "redirectUris", skip_serializing)]
42
    redirect_uris: Vec<String>,
43
    #[serde(rename(serialize = "redirectUris"))]
44
    redirect_uris_str: Option<String>,
45
    #[serde(skip_serializing)]
46
    scopes: Vec<String>,
47
    #[serde(rename(serialize = "scopes"))]
48
    scopes_str: Option<String>,
49
    #[serde(rename = "userId")]
50
    user_id: Option<String>,
51
    name: String,
52
    image: Option<String>,
53
}
54

            
55
const CSV_FIELDS: &'static [u8] =
56
    b"\xEF\xBB\xBFclientId,createdAt,modifiedAt,clientSecret,redirectUris,scopes,userId,name,image\n";
57

            
58
506
pub fn new_service(scope_path: &str, state: &AppState) -> Router {
59
506
    Router::new().nest(
60
506
        scope_path,
61
506
        Router::new()
62
506
            .route("/", routing::post(post_client))
63
506
            .route("/count", routing::get(get_client_count))
64
506
            .route("/list", routing::get(get_client_list))
65
506
            .route(
66
506
                "/{client_id}",
67
506
                routing::get(get_client)
68
506
                    .patch(patch_client)
69
506
                    .delete(delete_client),
70
            )
71
506
            .route("/user/{user_id}", routing::delete(delete_client_user))
72
506
            .with_state(state.clone()),
73
    )
74
506
}
75

            
76
/// `POST /{base}/api/v1/client`
77
4
async fn post_client(state: State<AppState>, req: Request) -> impl IntoResponse {
78
    const FN_NAME: &'static str = "post_client";
79
4
    let api_path = format!("{}/api/v1/client", state.auth_base);
80
4
    let client = state.client.clone();
81

            
82
4
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
83
4
}
84

            
85
/// `GET /{base}/api/v1/client/count`
86
4
async fn get_client_count(state: State<AppState>, req: Request) -> impl IntoResponse {
87
    const FN_NAME: &'static str = "get_client_count";
88
4
    let api_path = format!("{}/api/v1/client/count", state.auth_base.as_str());
89
4
    let client = state.client.clone();
90

            
91
4
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
92
4
}
93

            
94
/// `GET /{base}/api/v1/client/list`
95
20
async fn get_client_list(state: State<AppState>, req: Request) -> impl IntoResponse {
96
    const FN_NAME: &'static str = "get_client_list";
97
20
    let api_path = format!("{}/api/v1/client/list", state.auth_base.as_str());
98
20
    let api_path = api_path.as_str();
99
20
    let client = state.client.clone();
100

            
101
4
    let (api_resp, resp_builder) =
102
20
        match list_api_bridge(FN_NAME, &client, req, api_path, false, "client").await {
103
16
            ListResp::Axum(resp) => return resp,
104
4
            ListResp::ArrayStream(api_resp, resp_builder) => (api_resp, resp_builder),
105
        };
106

            
107
4
    let mut resp_stream = api_resp.bytes_stream();
108
4
    let body = Body::from_stream(async_stream::stream! {
109
        yield Ok(Bytes::from(CSV_FIELDS));
110

            
111
        let mut buffer = BytesMut::new();
112
        while let Some(body) = resp_stream.next().await {
113
            match body {
114
                Err(e) => {
115
                    error!("[{}] get body error: {}", FN_NAME, e);
116
                    let err: Box<dyn StdError + Send + Sync> = Box::new(e);
117
                    yield Err(err);
118
                    break;
119
                }
120
                Ok(body) => buffer.extend_from_slice(&body[..]),
121
            }
122

            
123
            let mut json_stream = Deserializer::from_slice(&buffer[..]).into_iter::<Client>();
124
            let mut index = 0;
125
            let mut finish = false;
126
            loop {
127
                if let Some(Ok(mut v)) = json_stream.next() {
128
                    if let Ok(redirect_uris_str) = serde_json::to_string(&v.redirect_uris) {
129
                        v.redirect_uris_str = Some(redirect_uris_str);
130
                    }
131
                    if let Ok(scopes_str) = serde_json::to_string(&v.scopes) {
132
                        v.scopes_str = Some(scopes_str);
133
                    }
134
                    let mut writer = WriterBuilder::new().has_headers(false).from_writer(vec![]);
135
                    if let Err(e) = writer.serialize(v) {
136
                        let err: Box<dyn StdError + Send + Sync> = Box::new(e);
137
                        yield Err(err);
138
                        finish = true;
139
                        break;
140
                    }
141
                    match writer.into_inner() {
142
                        Err(e) => {
143
                            let err: Box<dyn StdError + Send + Sync> = Box::new(e);
144
                            yield Err(err);
145
                            finish = true;
146
                            break;
147
                        }
148
                        Ok(row) => yield Ok(Bytes::copy_from_slice(row.as_slice())),
149
                    }
150
                    continue;
151
                }
152
                let offset = json_stream.byte_offset();
153
                if buffer.len() <= index + offset {
154
                    index = buffer.len();
155
                    break;
156
                }
157
                match buffer[index+offset] {
158
                    b'[' | b',' => {
159
                        index += offset + 1;
160
                        if buffer.len() <= index {
161
                            break;
162
                        }
163
                        json_stream =
164
                            Deserializer::from_slice(&buffer[index..]).into_iter::<Client>();
165
                    }
166
                    b']' => {
167
                        finish = true;
168
                        break;
169
                    }
170
                    _ => break,
171
                }
172
            }
173
            if finish {
174
                break;
175
            }
176
            buffer = buffer.split_off(index);
177
        }
178
    });
179
4
    match resp_builder.body(body) {
180
        Err(e) => ErrResp::ErrRsc(Some(e.to_string())).into_response(),
181
4
        Ok(resp) => resp,
182
    }
183
20
}
184

            
185
/// `GET /{base}/api/v1/client/{clientId}`
186
4
async fn get_client(
187
4
    state: State<AppState>,
188
4
    Path(param): Path<ClientIdPath>,
189
4
    req: Request,
190
4
) -> impl IntoResponse {
191
    const FN_NAME: &'static str = "get_client";
192
4
    let api_path = format!("{}/api/v1/client/{}", state.auth_base, param.client_id);
193
4
    let client = state.client.clone();
194

            
195
4
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
196
4
}
197

            
198
/// `PATCH /{base}/api/v1/client/{clientId}`
199
4
async fn patch_client(
200
4
    state: State<AppState>,
201
4
    Path(param): Path<ClientIdPath>,
202
4
    req: Request,
203
4
) -> impl IntoResponse {
204
    const FN_NAME: &'static str = "patch_client";
205
4
    let api_path = format!("{}/api/v1/client/{}", state.auth_base, param.client_id);
206
4
    let client = state.client.clone();
207

            
208
4
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
209
4
}
210

            
211
/// `DELETE /{base}/api/v1/client/{clientId}`
212
4
async fn delete_client(
213
4
    state: State<AppState>,
214
4
    Path(param): Path<ClientIdPath>,
215
4
    req: Request,
216
4
) -> impl IntoResponse {
217
    const FN_NAME: &'static str = "delete_client";
218
4
    let api_path = format!("{}/api/v1/client/{}", state.auth_base, param.client_id);
219
4
    let client = state.client.clone();
220

            
221
4
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
222
4
}
223

            
224
/// `DELETE /{base}/api/v1/client/user/{userId}`
225
4
async fn delete_client_user(
226
4
    state: State<AppState>,
227
4
    Path(param): Path<UserIdPath>,
228
4
    req: Request,
229
4
) -> impl IntoResponse {
230
    const FN_NAME: &'static str = "delete_client_user";
231
4
    let api_path = format!("{}/api/v1/client/user/{}", state.auth_base, param.user_id);
232
4
    let client = state.client.clone();
233

            
234
4
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
235
4
}