WASM SDK implementation
This commit is contained in:
parent
8ab6dd9803
commit
fded21f285
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
|
@ -13,6 +13,7 @@ members = [
|
|||
"ext_calckey_model",
|
||||
"fe_calckey",
|
||||
"magnetar_common",
|
||||
"magnetar_sdk",
|
||||
"core"
|
||||
]
|
||||
|
||||
|
@ -21,6 +22,7 @@ version = "0.2.0"
|
|||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "0.1"
|
||||
axum = "0.6"
|
||||
cached = "0.44"
|
||||
cfg-if = "1"
|
||||
|
@ -29,15 +31,19 @@ dotenvy = "0.15"
|
|||
futures-core = "0.3"
|
||||
futures-util = "0.3"
|
||||
headers = "0.3"
|
||||
http = "0.2"
|
||||
hyper = "0.14"
|
||||
js-sys = "0.3"
|
||||
log = "0.4"
|
||||
miette = "5.9"
|
||||
percent-encoding = "2.2"
|
||||
redis = "0.23"
|
||||
reqwest = "0.11"
|
||||
sea-orm = "0.11"
|
||||
sea-orm-migration = "0.11"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
strum = "0.25"
|
||||
tera = { version = "1", default-features = false }
|
||||
thiserror = "1"
|
||||
|
@ -48,8 +54,12 @@ tower = "0.4"
|
|||
tower-http = "0.4"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
ts-rs = "6"
|
||||
url = "2.3"
|
||||
walkdir = "2.3"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
|
||||
[dependencies]
|
||||
magnetar_core = { path = "./core" }
|
||||
|
@ -57,6 +67,7 @@ magnetar_common = { path = "./magnetar_common" }
|
|||
magnetar_webfinger = { path = "./ext_webfinger" }
|
||||
magnetar_nodeinfo = { path = "./ext_nodeinfo" }
|
||||
magnetar_calckey_model = { path = "./ext_calckey_model" }
|
||||
magnetar_sdk = { path = "./magnetar_sdk" }
|
||||
|
||||
cached = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
@ -83,3 +94,6 @@ percent-encoding = { workspace = true }
|
|||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
|
@ -0,0 +1,46 @@
|
|||
[package]
|
||||
name = "magnetar_sdk"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[features]
|
||||
reqwest = ["dep:reqwest"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.features]
|
||||
default = ["reqwest"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.features]
|
||||
default = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
magnetar_sdk_macros = { path = "./macros" }
|
||||
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
reqwest = { workspace = true, features = ["json"], optional = true }
|
||||
|
||||
http = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen-futures = { workspace = true }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde", "wasm-bindgen", "js-sys"] }
|
||||
js-sys = { workspace = true }
|
||||
web-sys = { workspace = true, features = [
|
||||
"Headers",
|
||||
"Request",
|
||||
"RequestInit",
|
||||
"RequestMode",
|
||||
"Response",
|
||||
"Window",
|
||||
"UrlSearchParams"
|
||||
] }
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "magnetar_sdk_macros"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||
quote = "1"
|
|
@ -0,0 +1,237 @@
|
|||
use proc_macro::TokenStream;
|
||||
use std::collections::HashSet;
|
||||
use syn::parse::Parse;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Expr, ExprLit, ExprPath, Ident, Lit, Meta, MetaNameValue, Token};
|
||||
|
||||
struct Field {
|
||||
name: Ident,
|
||||
ty: Ident,
|
||||
}
|
||||
|
||||
struct PackInput {
|
||||
struct_name: syn::Ident,
|
||||
fields: Vec<Field>,
|
||||
}
|
||||
|
||||
impl Parse for PackInput {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let struct_name = input.parse()?;
|
||||
let _ = input.parse::<Token![,]>()?;
|
||||
|
||||
let mut fields = Vec::new();
|
||||
|
||||
loop {
|
||||
let ty = input.parse()?;
|
||||
let _alias_keyword = input.parse::<Token![as]>()?;
|
||||
let name = input.parse()?;
|
||||
|
||||
fields.push(Field { name, ty });
|
||||
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let _ = input.parse::<Token![&]>()?;
|
||||
}
|
||||
|
||||
Ok(PackInput {
|
||||
struct_name,
|
||||
fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn pack(item: TokenStream) -> TokenStream {
|
||||
let parsed = syn::parse_macro_input!(item as PackInput);
|
||||
let struct_name = &parsed.struct_name;
|
||||
let fields = &parsed.fields;
|
||||
|
||||
let names = fields.iter().map(|f| &f.name);
|
||||
let types = fields.iter().map(|f| &f.ty);
|
||||
|
||||
let export_path = format!("bindings/packed/{}.ts", struct_name);
|
||||
|
||||
let expanded = quote::quote! {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = #export_path)]
|
||||
pub struct #struct_name {
|
||||
#(
|
||||
#[serde(flatten)]
|
||||
pub #names: #types
|
||||
),*
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Endpoint, attributes(name, endpoint, method, request, response))]
|
||||
pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::DeriveInput);
|
||||
|
||||
let struct_name = input.ident;
|
||||
assert!(input.generics.params.is_empty(), "generics not supported");
|
||||
let where_clause = input.generics.where_clause;
|
||||
assert!(where_clause.is_none(), "where clauses not supported");
|
||||
let data = match input.data {
|
||||
syn::Data::Struct(data) => data,
|
||||
_ => panic!("only unit structs are supported"),
|
||||
};
|
||||
assert!(
|
||||
matches!(data.fields, syn::Fields::Unit),
|
||||
"only unit structs are supported"
|
||||
);
|
||||
|
||||
let mut name = struct_name.to_string();
|
||||
let mut endpoint = None;
|
||||
let mut method = None;
|
||||
let mut request = None;
|
||||
let mut response = None;
|
||||
|
||||
let mut found = HashSet::new();
|
||||
|
||||
let struct_attrs = input.attrs;
|
||||
|
||||
for struct_attr in struct_attrs {
|
||||
if !struct_attr.meta.path().is_ident("endpoint") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if struct_attr.style != syn::AttrStyle::Outer {
|
||||
panic!("expected outer attribute");
|
||||
}
|
||||
|
||||
let Meta::List(list) = struct_attr.meta else {
|
||||
panic!("expected a list of attributes");
|
||||
};
|
||||
|
||||
let nested = list
|
||||
.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
|
||||
.expect("expected a list of attributes");
|
||||
|
||||
for kvp in nested {
|
||||
let key = kvp
|
||||
.path
|
||||
.get_ident()
|
||||
.expect("expected a single identifier")
|
||||
.to_owned();
|
||||
let value = kvp.value;
|
||||
|
||||
if found.contains(&key) {
|
||||
panic!("duplicate attribute: {}", key);
|
||||
}
|
||||
|
||||
match key.to_string().as_ref() {
|
||||
"name" => {
|
||||
let Expr::Lit(ExprLit { lit: Lit::Str(new_name), .. }) = value else {
|
||||
panic!("expected a string literal");
|
||||
};
|
||||
|
||||
name = new_name.value();
|
||||
}
|
||||
"endpoint" => {
|
||||
let Expr::Lit(ExprLit { lit: Lit::Str(endpoint_val), .. }) = value else {
|
||||
panic!("expected a string literal");
|
||||
};
|
||||
|
||||
endpoint = Some(endpoint_val.value());
|
||||
}
|
||||
"method" => {
|
||||
let Expr::Path(ExprPath { path, .. }) = value else {
|
||||
panic!("expected a an identifier");
|
||||
};
|
||||
|
||||
method = Some(path);
|
||||
}
|
||||
"request" => {
|
||||
let Expr::Path(ExprPath { path, .. }) = value else {
|
||||
panic!("expected a an identifier");
|
||||
};
|
||||
|
||||
request = Some(path);
|
||||
}
|
||||
"response" => {
|
||||
let Expr::Path(ExprPath { path, .. }) = value else {
|
||||
panic!("expected an identifier")
|
||||
};
|
||||
|
||||
response = Some(path);
|
||||
}
|
||||
_ => panic!("unknown attribute: {}", key),
|
||||
}
|
||||
|
||||
found.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
let endpoint = endpoint.expect("missing endpoint attribute");
|
||||
let method = method.expect("missing method attribute");
|
||||
let request = request.expect("missing request attribute");
|
||||
let response = response.expect("missing response attribute");
|
||||
|
||||
let ts_path = format!("bindings/endpoints/{}.ts", name);
|
||||
|
||||
let expanded = quote::quote! {
|
||||
impl Default for #struct_name {
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint for #struct_name {
|
||||
const NAME: &'static str = #name;
|
||||
const ENDPOINT: &'static str = #endpoint;
|
||||
const METHOD: Method = #method;
|
||||
|
||||
type Request = #request;
|
||||
type Response = #response;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn export_bindings_getuserbyid() {
|
||||
#struct_name::export().expect("could not export type");
|
||||
}
|
||||
|
||||
impl TS for #struct_name {
|
||||
const EXPORT_TO: Option<&'static str> = Some(#ts_path);
|
||||
|
||||
fn decl() -> String {
|
||||
format!(
|
||||
"interface {} {{\n \
|
||||
endpoint: \"{}\";\n \
|
||||
method: \"{}\";\n \
|
||||
request: {};\n \
|
||||
response: {};\n\
|
||||
}}
|
||||
",
|
||||
Self::name(),
|
||||
Self::ENDPOINT,
|
||||
Self::METHOD,
|
||||
<#struct_name as Endpoint>::Request::name(),
|
||||
<#struct_name as Endpoint>::Response::name()
|
||||
)
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
#struct_name::NAME.to_string()
|
||||
}
|
||||
|
||||
fn dependencies() -> Vec<Dependency> {
|
||||
vec![
|
||||
Dependency::from_ty::<<#struct_name as Endpoint>::Request>().unwrap(),
|
||||
Dependency::from_ty::<<#struct_name as Endpoint>::Response>().unwrap(),
|
||||
]
|
||||
}
|
||||
|
||||
fn transparent() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
use crate::client::ApiClientImpl;
|
||||
use crate::endpoints::list::match_endpoint;
|
||||
use crate::endpoints::{Endpoint, ErrorKind, ResponseError};
|
||||
use crate::Client;
|
||||
use http::Method;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub struct FetchClient {
|
||||
base_url: String,
|
||||
application: String,
|
||||
}
|
||||
|
||||
impl FetchClient {
|
||||
pub fn new(base_url: &str, application: &str) -> Self {
|
||||
FetchClient {
|
||||
base_url: base_url.to_string(),
|
||||
application: application.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Client for FetchClient {
|
||||
fn base_url(&self) -> &str {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
async fn call<'a, I, O, E>(
|
||||
&self,
|
||||
endpoint: &E,
|
||||
data: &I,
|
||||
path_params: &impl AsRef<[(&'a str, &'a str)]>,
|
||||
) -> Result<O, ResponseError>
|
||||
where
|
||||
I: Serialize + DeserializeOwned + Send + 'static,
|
||||
O: Serialize + DeserializeOwned + Send + 'static,
|
||||
E: Endpoint<Request = I, Response = O> + Send + 'static,
|
||||
{
|
||||
let url = endpoint.template_path(self.base_url(), path_params);
|
||||
let url = if E::METHOD == Method::GET {
|
||||
let search = web_sys::UrlSearchParams::new_with_str_sequence_sequence(
|
||||
&serde_wasm_bindgen::to_value(data).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
format!("{}?{}", url, search.to_string())
|
||||
} else {
|
||||
url
|
||||
};
|
||||
|
||||
let mut opts = web_sys::RequestInit::new();
|
||||
opts.method(E::METHOD.as_str());
|
||||
if E::METHOD != Method::GET {
|
||||
opts.body(Some(&serde_wasm_bindgen::to_value(data).unwrap()));
|
||||
}
|
||||
let headers = web_sys::Headers::new().unwrap();
|
||||
headers.set("Content-Type", "application/json").unwrap();
|
||||
headers.set("User-Agent", &self.application).unwrap();
|
||||
opts.headers(&headers);
|
||||
|
||||
let req = web_sys::Request::new_with_str_and_init(&url, &opts).unwrap();
|
||||
|
||||
let js_response = wasm_bindgen_futures::JsFuture::from(
|
||||
web_sys::window()
|
||||
.ok_or_else(|| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "FetchClient:NoWindow".to_string(),
|
||||
message: "No window object".to_string(),
|
||||
status: None,
|
||||
})?
|
||||
.fetch_with_request(&req),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "FetchClient:FetchFail".to_string(),
|
||||
message: format!("{:#?}", e),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let response = web_sys::Response::from(js_response);
|
||||
let status = response.status();
|
||||
|
||||
if (400..=599).contains(&status) {
|
||||
let body = wasm_bindgen_futures::JsFuture::from(response.json().map_err(|e| {
|
||||
ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "FetchClient:ResponseErrorPromiseFail".to_string(),
|
||||
message: format!("{:#?}", e),
|
||||
status: Some(status),
|
||||
}
|
||||
})?)
|
||||
.await
|
||||
.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "FetchClient:ResponseErrorFail".to_string(),
|
||||
message: format!("{:#?}", e),
|
||||
status: Some(status),
|
||||
})?;
|
||||
|
||||
let data = serde_wasm_bindgen::from_value::<ResponseError>(body).map_err(|e| {
|
||||
ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "FetchClient:ResponseErrorDeserializeFail".to_string(),
|
||||
message: format!("{:#?}", e),
|
||||
status: Some(status),
|
||||
}
|
||||
})?;
|
||||
|
||||
return Err(data);
|
||||
} else if (200..=299).contains(&status) {
|
||||
if status == 204 {
|
||||
return if let Some(val) = E::default_response() {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "FetchClient:ResponseError204".to_string(),
|
||||
message: "Response is empty".to_string(),
|
||||
status: Some(status),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
let body = wasm_bindgen_futures::JsFuture::from(response.json().map_err(|e| {
|
||||
ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "FetchClient:ResponseJsonPromiseFail".to_string(),
|
||||
message: format!("{:#?}", e),
|
||||
status: None,
|
||||
}
|
||||
})?)
|
||||
.await
|
||||
.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "FetchClient:ResponseJsonFail".to_string(),
|
||||
message: format!("{:#?}", e),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let data = serde_wasm_bindgen::from_value::<O>(body).map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "FetchClient:ResponseErrorDeserializeFail".to_string(),
|
||||
message: e.to_string(),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
Err(ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "FetchClient:ApiUnknownStatusError".to_string(),
|
||||
message: response.status().to_string(),
|
||||
status: Some(response.status()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct ApiClientOptions {
|
||||
base_url: String,
|
||||
application: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ApiClient {
|
||||
client: ApiClientImpl,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ApiClient {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(implementation: &str, options: ApiClientOptions) -> Option<ApiClient> {
|
||||
match implementation {
|
||||
"fetch" => Some(Self {
|
||||
client: ApiClientImpl::Fetch(FetchClient::new(
|
||||
&options.base_url,
|
||||
&options.application,
|
||||
)),
|
||||
}),
|
||||
#[cfg(feature = "reqwest")]
|
||||
"reqwest" => Some(Self {
|
||||
client: ApiClientImpl::Reqwest(crate::client::reqwest::ReqwestClient::new(
|
||||
&options.base_url,
|
||||
&options.application,
|
||||
)),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn call<I: Serialize, E: Endpoint, O: DeserializeOwned>(
|
||||
&self,
|
||||
endpoint: &E,
|
||||
data: &I,
|
||||
path_params: &[(&str, &str)],
|
||||
) -> Result<O, ResponseError>
|
||||
where
|
||||
I: Serialize + DeserializeOwned + Send + 'static,
|
||||
O: Serialize + DeserializeOwned + Send + 'static,
|
||||
E: Endpoint<Request = I, Response = O> + Send + 'static,
|
||||
{
|
||||
match &self.client {
|
||||
ApiClientImpl::Fetch(client) => {
|
||||
client.call::<I, O, E>(endpoint, data, &path_params).await
|
||||
}
|
||||
#[cfg(feature = "reqwest")]
|
||||
ApiClientImpl::Reqwest(client) => {
|
||||
client.call::<I, O, E>(endpoint, data, &path_params).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EndpointLike {
|
||||
pub path: String,
|
||||
pub method: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn api_call(
|
||||
client: &ApiClient,
|
||||
endpoint_like: &EndpointLike,
|
||||
data: JsValue,
|
||||
path_params: JsValue,
|
||||
) -> Result<JsValue, ResponseError> {
|
||||
let method = Method::try_from(endpoint_like.method.as_str()).map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "Bindgen::ParseMethod".to_string(),
|
||||
message: e.to_string(),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let endpoint = match_endpoint(&endpoint_like.path, &method).ok_or_else(|| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "Bindgen::MatchEndpoint".to_string(),
|
||||
message: format!(
|
||||
"No endpoint `{}` with method `{}` found",
|
||||
endpoint_like.path, method
|
||||
),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let input = serde_wasm_bindgen::from_value(data).map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "Bindgen::DeserializeInput".to_string(),
|
||||
message: e.to_string(),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let path_params = serde_wasm_bindgen::from_value::<HashMap<String, String>>(path_params)
|
||||
.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "Bindgen::DeserializePathParams".to_string(),
|
||||
message: e.to_string(),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let path_params_borrow = path_params
|
||||
.iter()
|
||||
.map(|(k, v)| (k.as_str(), v.as_str()))
|
||||
.collect::<Vec<(&str, &str)>>();
|
||||
|
||||
serde_wasm_bindgen::to_value(&client.call(&endpoint, &input, &path_params_borrow).await?)
|
||||
.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "Bindgen::SerializeOutput".to_string(),
|
||||
message: e.to_string(),
|
||||
status: None,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#[cfg(feature = "reqwest")]
|
||||
pub mod reqwest;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod fetch;
|
||||
|
||||
pub enum ApiClientImpl {
|
||||
#[cfg(feature = "reqwest")]
|
||||
Reqwest(reqwest::ReqwestClient),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Fetch(fetch::FetchClient),
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
use crate::endpoints::{Endpoint, ErrorKind, ResponseError};
|
||||
use crate::Client;
|
||||
use http::header::{CONTENT_TYPE, USER_AGENT};
|
||||
use http::{HeaderMap, HeaderValue, Method, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
pub struct ReqwestClient {
|
||||
client: reqwest::Client,
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl ReqwestClient {
|
||||
pub fn new(base_url: &str, application: &str) -> Self {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_str("application/json").unwrap(),
|
||||
);
|
||||
headers.insert(USER_AGENT, HeaderValue::from_str(application).unwrap());
|
||||
|
||||
let client_builder = reqwest::ClientBuilder::new().default_headers(headers);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let client_builder = { client_builder.https_only(true) };
|
||||
|
||||
let client = client_builder.build().unwrap();
|
||||
|
||||
ReqwestClient {
|
||||
client,
|
||||
base_url: base_url.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Client for ReqwestClient {
|
||||
fn base_url(&self) -> &str {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
async fn call<'a, I, O, E>(
|
||||
&self,
|
||||
endpoint: &E,
|
||||
data: &I,
|
||||
path_params: &impl AsRef<[(&'a str, &'a str)]>,
|
||||
) -> Result<O, ResponseError>
|
||||
where
|
||||
I: Serialize + DeserializeOwned + Send + 'static,
|
||||
O: Serialize + DeserializeOwned + Send + 'static,
|
||||
E: Endpoint<Request = I, Response = O> + Send + 'static,
|
||||
{
|
||||
let url = endpoint.template_path(self.base_url(), path_params);
|
||||
|
||||
let req = self.client.request(E::METHOD, &url);
|
||||
|
||||
let req = if E::METHOD == Method::GET {
|
||||
req.query(&data)
|
||||
} else {
|
||||
req.json(&data)
|
||||
};
|
||||
|
||||
let response = req.send().await.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::Other,
|
||||
code: "ReqwestClient:Fail".to_string(),
|
||||
message: e.to_string(),
|
||||
status: None,
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
match response.json::<ResponseError>().await {
|
||||
Ok(res) => Err(res),
|
||||
Err(e) => Err(ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "ReqwestClient:ApiGenericError".to_string(),
|
||||
message: e.to_string(),
|
||||
status: Some(status.as_u16()),
|
||||
}),
|
||||
}
|
||||
} else if status.is_success() {
|
||||
if status == StatusCode::NO_CONTENT.as_u16() {
|
||||
return if let Some(val) = E::default_response() {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "ReqwestClient:ResponseError204".to_string(),
|
||||
message: "Response is empty".to_string(),
|
||||
status: Some(status.as_u16()),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
let data = response.json::<O>().await.map_err(|e| ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "ReqwestClient:JsonError".to_string(),
|
||||
message: e.to_string(),
|
||||
status: Some(status.as_u16()),
|
||||
})?;
|
||||
|
||||
Ok(data)
|
||||
} else {
|
||||
Err(ResponseError {
|
||||
kind: ErrorKind::ApiError,
|
||||
code: "ReqwestClient:ApiUnknownStatusError".to_string(),
|
||||
message: status.to_string(),
|
||||
status: Some(status.as_u16()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
use crate::endpoints::Endpoint;
|
||||
|
||||
macro_rules! match_from {
|
||||
(($endpoint:expr, $method:expr) in [$($e:ty,)*]) => {
|
||||
match ($endpoint, $method) {
|
||||
$(
|
||||
(<$e as Endpoint>::ENDPOINT, &<$e as Endpoint>::METHOD) => Some(<$e as Default>::default()),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn match_endpoint(endpoint: &str, method: &http::Method) -> Option<impl Endpoint> {
|
||||
match_from!(
|
||||
(endpoint, method)
|
||||
in [
|
||||
crate::endpoints::user::GetUserById,
|
||||
]
|
||||
)
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
pub(crate) mod list;
|
||||
pub mod user;
|
||||
|
||||
use http::Method;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||
pub struct ResponseError {
|
||||
pub kind: ErrorKind,
|
||||
pub status: Option<u16>,
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, TS)]
|
||||
pub enum ErrorKind {
|
||||
#[default]
|
||||
ApiError,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||
pub struct Empty;
|
||||
|
||||
impl From<Empty> for () {
|
||||
fn from(_: Empty) -> Self {}
|
||||
}
|
||||
|
||||
pub trait Endpoint {
|
||||
const NAME: &'static str;
|
||||
const ENDPOINT: &'static str;
|
||||
const METHOD: Method;
|
||||
|
||||
type Request: Serialize + DeserializeOwned + Send + Sync + 'static;
|
||||
type Response: Serialize + DeserializeOwned + Send + Sync + 'static;
|
||||
|
||||
fn default_response() -> Option<Self::Response> {
|
||||
None
|
||||
}
|
||||
|
||||
fn template_path<'a>(&self, base_path: &str, var: &impl AsRef<[(&'a str, &'a str)]>) -> String {
|
||||
let mut path_suffix = Self::ENDPOINT.to_string();
|
||||
|
||||
for (key, value) in var.as_ref() {
|
||||
path_suffix = path_suffix.replace(&format!(":{}", key), value);
|
||||
}
|
||||
|
||||
format!("{}{}", base_path, path_suffix)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use crate::endpoints::Endpoint;
|
||||
use http::Method;
|
||||
use magnetar_sdk_macros::Endpoint;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::{Dependency, TS};
|
||||
|
||||
use crate::types::user::PackUserProfileFull;
|
||||
|
||||
#[derive(Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserByIdReq {
|
||||
id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
detail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Endpoint, Deserialize)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/:user_id",
|
||||
method = Method::GET,
|
||||
request = UserByIdReq,
|
||||
response = PackUserProfileFull
|
||||
)]
|
||||
pub struct GetUserById;
|
|
@ -0,0 +1,23 @@
|
|||
pub mod client;
|
||||
pub mod endpoints;
|
||||
pub mod types;
|
||||
|
||||
use crate::endpoints::Endpoint;
|
||||
use endpoints::ResponseError;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait Client {
|
||||
fn base_url(&self) -> &str;
|
||||
|
||||
async fn call<'a, I, O, E>(
|
||||
&self,
|
||||
endpoint: &E,
|
||||
data: &I,
|
||||
path_params: &impl AsRef<[(&'a str, &'a str)]>,
|
||||
) -> Result<O, ResponseError>
|
||||
where
|
||||
I: Serialize + DeserializeOwned + Send + 'static,
|
||||
O: Serialize + DeserializeOwned + Send + 'static,
|
||||
E: Endpoint<Request = I, Response = O> + Send + 'static;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::types::user::PackUserBase;
|
||||
|
||||
use magnetar_sdk_macros::pack;
|
||||
|
||||
use crate::types::Id;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ImageMeta {
|
||||
pub width: Option<u64>,
|
||||
pub height: Option<u64>,
|
||||
pub orientation: Option<u64>,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct DriveFolderBase {
|
||||
pub name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub comment: Option<String>,
|
||||
pub file_count: u64,
|
||||
pub folder_count: u64,
|
||||
pub parent_id: Option<String>,
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
pack!(PackDriveFolderBase, Id as id & DriveFolderBase as folder);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct DriveFolderParentExt {
|
||||
pub folder: Box<DriveFolderBase>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackDriveFolderWithParent,
|
||||
Id as id & DriveFolderBase as folder & DriveFolderParentExt as parent
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct DriveFileBase {
|
||||
pub name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub size: u64,
|
||||
pub sha256: String,
|
||||
pub mime_type: String,
|
||||
pub image_meta: ImageMeta,
|
||||
pub url: Option<String>,
|
||||
pub thumbnail_url: Option<String>,
|
||||
pub sensitive: bool,
|
||||
pub comment: Option<String>,
|
||||
pub folder_id: Option<String>,
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
pack!(PackDriveFileBase, Id as id & DriveFileBase as file);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct DriveFileFolderExt {
|
||||
pub folder: Box<PackDriveFolderBase>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackDriveFileWithFolder,
|
||||
Id as id & DriveFileBase as file & DriveFileFolderExt as folder
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct DriveFileUserExt {
|
||||
pub user: Box<PackUserBase>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackDriveFileWithUser,
|
||||
Id as id & DriveFileBase as file & DriveFileUserExt as user
|
||||
);
|
||||
|
||||
pack!(
|
||||
PackDriveFileFull,
|
||||
Id as id & DriveFileBase as file & DriveFileFolderExt as folder & DriveFileUserExt as user
|
||||
);
|
|
@ -0,0 +1,22 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::types::Id;
|
||||
use magnetar_sdk_macros::pack;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct EmojiBase {
|
||||
pub shortcode: String,
|
||||
pub url: String,
|
||||
pub static_url: String,
|
||||
pub visible_in_picker: bool,
|
||||
pub category: Option<String>,
|
||||
}
|
||||
|
||||
pack!(PackEmojiBase, Id as id & EmojiBase as emoji);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[repr(transparent)]
|
||||
pub struct EmojiContext(pub Vec<String>);
|
|
@ -0,0 +1,44 @@
|
|||
pub mod drive;
|
||||
pub mod emoji;
|
||||
pub mod note;
|
||||
pub mod user;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[repr(transparent)]
|
||||
pub struct Id {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum NotificationType {
|
||||
Follow,
|
||||
Mention,
|
||||
Reply,
|
||||
Renote,
|
||||
Quote,
|
||||
Reaction,
|
||||
PollVote,
|
||||
PollEnded,
|
||||
FollowRequest,
|
||||
FollowRequestAccepted,
|
||||
App,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationSettings {
|
||||
pub enabled: Vec<NotificationType>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum FollowVisibility {
|
||||
Public,
|
||||
Followers,
|
||||
Private,
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
use crate::types::emoji::EmojiContext;
|
||||
use crate::types::user::{PackUserBase, UserBase};
|
||||
use crate::types::Id;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::types::drive::PackDriveFileBase;
|
||||
|
||||
use magnetar_sdk_macros::pack;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum NoteVisibility {
|
||||
Public,
|
||||
Home,
|
||||
Followers,
|
||||
Specified,
|
||||
Direct,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct PollChoice {
|
||||
pub title: String,
|
||||
pub votes_count: u64,
|
||||
pub voted: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct PollBase {
|
||||
pub expires_at: DateTime<Utc>,
|
||||
pub expired: bool,
|
||||
pub multiple_choice: bool,
|
||||
pub options: Vec<PollChoice>,
|
||||
}
|
||||
|
||||
pack!(PackPollBase, Id as id & PollBase as poll);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NoteBase {
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub cw: Option<String>,
|
||||
pub uri: Option<String>,
|
||||
pub url: Option<String>,
|
||||
pub text: String,
|
||||
pub visibility: NoteVisibility,
|
||||
pub user: Box<UserBase>,
|
||||
pub parent_note_id: Option<String>,
|
||||
pub renoted_note_id: Option<String>,
|
||||
pub reply_count: u64,
|
||||
pub renote_count: u64,
|
||||
pub hashtags: Vec<String>,
|
||||
pub reactions: Vec<PackReactionBase>,
|
||||
pub emojis: EmojiContext,
|
||||
pub local_only: bool,
|
||||
}
|
||||
|
||||
pack!(PackNoteBase, Id as id & NoteBase as note);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NoteAttachmentExt {
|
||||
pub attachments: Vec<PackDriveFileBase>,
|
||||
pub poll: Option<PollBase>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackNoteWithAttachments,
|
||||
Id as id & NoteBase as note & NoteAttachmentExt as attachment
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub struct NoteDetailExt {
|
||||
pub poll: Option<PollBase>,
|
||||
pub parent_note: Option<Box<NoteBase>>,
|
||||
pub renoted_note: Option<Box<NoteBase>>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackNoteFull,
|
||||
Id as id & NoteBase as note & NoteAttachmentExt as attachment & NoteDetailExt as detail
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub struct ReactionBase {
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
pack!(PackReactionBase, Id as id & ReactionBase as reaction);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub struct ReactionUserExt {
|
||||
pub user: Box<PackUserBase>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackReactionWithUser,
|
||||
Id as id & ReactionBase as reaction & ReactionUserExt as user
|
||||
);
|
|
@ -0,0 +1,187 @@
|
|||
use crate::types::emoji::EmojiContext;
|
||||
use crate::types::note::PackNoteBase;
|
||||
use crate::types::{Id, NotificationSettings};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use magnetar_sdk_macros::pack;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum AvatarDecoration {
|
||||
None,
|
||||
CatEars,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum SpeechTransform {
|
||||
None,
|
||||
Cat,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ProfileField {
|
||||
name: String,
|
||||
value: String,
|
||||
verified_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserBase {
|
||||
pub acct: String,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub host: Option<String>,
|
||||
pub speech_transform: SpeechTransform,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub avatar_blurhash: Option<String>,
|
||||
pub avatar_color: Option<String>,
|
||||
pub avatar_decoration: AvatarDecoration,
|
||||
pub admin: bool,
|
||||
pub moderator: bool,
|
||||
pub bot: bool,
|
||||
pub emojis: EmojiContext,
|
||||
}
|
||||
|
||||
pack!(PackUserBase, Id as id & UserBase as user);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserProfileExt {
|
||||
pub locked: bool,
|
||||
pub silenced: bool,
|
||||
pub suspended: bool,
|
||||
|
||||
pub description: Option<String>,
|
||||
pub location: Option<String>,
|
||||
pub birthday: Option<DateTime<Utc>>,
|
||||
pub fields: Vec<ProfileField>,
|
||||
|
||||
pub follower_count: u64,
|
||||
pub following_count: u64,
|
||||
pub note_count: u64,
|
||||
|
||||
pub url: Option<String>,
|
||||
|
||||
pub moved_to_uri: Option<String>,
|
||||
pub also_known_as: Option<String>,
|
||||
|
||||
pub banner_url: Option<String>,
|
||||
pub banner_color: Option<String>,
|
||||
pub banner_blurhash: Option<String>,
|
||||
|
||||
pub public_reactions: bool,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackUserProfile,
|
||||
Id as id & UserBase as user & UserProfileExt as profile
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserProfilePinsEx {
|
||||
pub pinned_notes: Vec<PackNoteBase>,
|
||||
// pub pinned_page: Option<Page>,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackUserProfileFull,
|
||||
Id as id
|
||||
& UserBase as user
|
||||
& UserProfileExt as profile
|
||||
& UserProfilePinsEx as pins
|
||||
& UserRelationExt as relation
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserRelationExt {
|
||||
pub last_fetched_at: Option<DateTime<Utc>>,
|
||||
pub follows_you: bool,
|
||||
pub you_follow: bool,
|
||||
pub blocks_you: bool,
|
||||
pub you_block: bool,
|
||||
pub mute: bool,
|
||||
pub mute_renotes: bool,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackUserRelation,
|
||||
Id as id & UserBase as user & UserRelationExt as relation
|
||||
);
|
||||
pack!(
|
||||
PackUserProfileRelation,
|
||||
Id as id & UserBase as user & UserProfileExt as profile & UserRelationExt as relation
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserAuthOverviewExt {
|
||||
pub two_factor_enabled: bool,
|
||||
pub use_passwordless_login: bool,
|
||||
pub security_keys: bool,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackUserAuthOverview,
|
||||
Id as id & UserBase as user & UserAuthOverviewExt as auth
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserSelfExt {
|
||||
pub avatar_id: Option<String>,
|
||||
pub banner_id: Option<String>,
|
||||
pub email_announcements: bool,
|
||||
pub always_mark_sensitive: bool,
|
||||
pub reject_bot_follow_requests: bool,
|
||||
pub reject_crawlers: bool,
|
||||
pub reject_ai_training: bool,
|
||||
pub has_unread_announcements: bool,
|
||||
pub has_unread_antenna: bool,
|
||||
pub has_unread_notifications: bool,
|
||||
pub has_pending_follow_requests: bool,
|
||||
pub word_mutes: Vec<String>,
|
||||
pub instance_mutes: Vec<String>,
|
||||
pub notification_settings: NotificationSettings,
|
||||
}
|
||||
|
||||
pack!(
|
||||
PackUserSelf,
|
||||
Id as id & UserBase as user & UserSelfExt as self_info
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserDetailExt {
|
||||
pub uri: Option<String>,
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct SecurityKeyBase {
|
||||
pub name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_used_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pack!(PackSecurityKeyBase, Id as id & SecurityKeyBase as key);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct UserSecretsExt {
|
||||
pub email: Option<String>,
|
||||
pub email_verified: bool,
|
||||
pub security_keys: Vec<SecurityKeyBase>,
|
||||
}
|
||||
pack!(
|
||||
PackUserSelfAll,
|
||||
Id as id & UserBase as user & UserSelfExt as self_info & UserSecretsExt as secrets
|
||||
);
|
Loading…
Reference in New Issue