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

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

            
16
use sylvia_iot_corelib::err::ErrResp;
17

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

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

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

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

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

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

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

            
81
2
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
82
2
}
83

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

            
90
2
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
91
2
}
92

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

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

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

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

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

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

            
194
2
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
195
2
}
196

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

            
207
2
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
208
2
}
209

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

            
220
2
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
221
2
}
222

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

            
233
2
    api_bridge(FN_NAME, &client, req, api_path.as_str()).await
234
2
}