Skip to content

Commit 587c56b

Browse files
committed
Finally, a working version
1 parent ce0133c commit 587c56b

File tree

15 files changed

+342
-64
lines changed

15 files changed

+342
-64
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/src/routes/api.patr.cloud/workspace/deployment/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ pub async fn setup_routes(state: &AppState) -> Router {
5353
.mount_auth_endpoint(delete_deployment, state)
5454
.mount_auth_endpoint(update_deployment, state)
5555
.mount_auth_endpoint(get_deployment_metric, state)
56-
.mount_auth_endpoint(stream_deployment_logs, state)
56+
.mount_auth_stream(stream_deployment_logs, state)
5757
}

api/src/routes/api.patr.cloud/workspace/deployment/start_deployment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub async fn start_deployment(
3434

3535
let now = OffsetDateTime::now_utc();
3636

37-
let (registry, image_tag, region) = query!(
37+
let (registry, ..) = query!(
3838
r#"
3939
SELECT
4040
registry,

api/src/routes/api.patr.cloud/workspace/runner/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use self::{
1919
#[instrument(skip(state))]
2020
pub async fn setup_routes(state: &AppState) -> Router {
2121
Router::new()
22-
.mount_auth_endpoint(stream_runner_data_for_workspace, state)
22+
.mount_auth_stream(stream_runner_data_for_workspace, state)
2323
.mount_auth_endpoint(add_runner_to_workspace, state)
2424
.mount_auth_endpoint(remove_runner_from_workspace, state)
2525
.mount_auth_endpoint(list_runners_for_workspace, state)

api/src/utils/router_ext.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use models::{
1010
utils::{AppAuthentication, BearerToken, HasHeader, NoAuthentication},
1111
};
1212
use preprocess::Preprocessable;
13+
use serde::{de::DeserializeOwned, Serialize};
1314
use tower::{
1415
ServiceBuilder,
1516
util::{BoxCloneService, BoxLayer},
@@ -44,6 +45,17 @@ where
4445
/// Rate limiter using tower layers.
4546
#[track_caller]
4647
fn mount_endpoint<E, H>(self, handler: H, state: &AppState) -> Self
48+
where
49+
for<'req> H: EndpointHandler<'req, E> + Clone + Send + Sync + 'static,
50+
E: ApiEndpoint<Authenticator = NoAuthentication> + Sync,
51+
<E::RequestBody as Preprocessable>::Processed: Send,
52+
E::RequestBody: Serialize + DeserializeOwned,
53+
E::ResponseBody: Serialize + DeserializeOwned;
54+
55+
/// Mount an API stream endpoint directly along with the required request
56+
/// parser, Rate limiter using tower layers.
57+
#[track_caller]
58+
fn mount_stream<E, H>(self, handler: H, state: &AppState) -> Self
4759
where
4860
for<'req> H: EndpointHandler<'req, E> + Clone + Send + Sync + 'static,
4961
E: ApiEndpoint<Authenticator = NoAuthentication> + Sync,
@@ -53,6 +65,19 @@ where
5365
/// Rate limiter, Audit logger and Auth middlewares, using tower layers.
5466
#[track_caller]
5567
fn mount_auth_endpoint<E, H>(self, handler: H, state: &AppState) -> Self
68+
where
69+
for<'req> H: AuthEndpointHandler<'req, E> + Clone + Send + Sync + 'static,
70+
E: ApiEndpoint<Authenticator = AppAuthentication<E>> + Sync,
71+
<E::RequestBody as Preprocessable>::Processed: Send,
72+
E::RequestBody: Serialize + DeserializeOwned,
73+
E::ResponseBody: Serialize + DeserializeOwned,
74+
E::RequestHeaders: HasHeader<BearerToken>;
75+
76+
/// Mount an API stream endpoint directly along with the required request
77+
/// parser, Rate limiter, Audit logger and Auth middlewares, using tower
78+
/// layers.
79+
#[track_caller]
80+
fn mount_auth_stream<E, H>(self, handler: H, state: &AppState) -> Self
5681
where
5782
for<'req> H: AuthEndpointHandler<'req, E> + Clone + Send + Sync + 'static,
5883
E: ApiEndpoint<Authenticator = AppAuthentication<E>> + Sync,
@@ -66,6 +91,73 @@ where
6691
{
6792
#[instrument(skip_all)]
6893
fn mount_endpoint<E, H>(self, handler: H, state: &AppState) -> Self
94+
where
95+
for<'req> H: EndpointHandler<'req, E> + Clone + Send + Sync + 'static,
96+
E: ApiEndpoint<Authenticator = NoAuthentication> + Sync,
97+
<E::RequestBody as Preprocessable>::Processed: Send,
98+
E::RequestBody: Serialize + DeserializeOwned,
99+
E::ResponseBody: Serialize + DeserializeOwned,
100+
{
101+
frontend::utils::API_CALL_REGISTRY
102+
.get_or_init(|| RwLock::new(Default::default()))
103+
.write()
104+
.expect("API call registry poisoned")
105+
.entry(E::METHOD)
106+
.or_default()
107+
.insert(
108+
<E::RequestPath as TypedPath>::PATH,
109+
Box::new(BoxLayer::<
110+
BoxCloneService<(ApiRequest<E>, IpAddr), AppResponse<E>, ErrorType>,
111+
(ApiRequest<E>, IpAddr),
112+
AppResponse<E>,
113+
ErrorType,
114+
>::new(
115+
ServiceBuilder::new()
116+
// .layer(todo!("Add rate limiter checker middleware here")),
117+
.layer(DataStoreConnectionLayer::<E>::with_state(state.clone()))
118+
.layer(PreprocessLayer::new())
119+
.layer(UserAgentValidationLayer::new())
120+
// .layer(todo!("Add rate limiter value updater middleware here"))
121+
.layer(EndpointLayer::new(handler.clone())),
122+
)),
123+
)
124+
.unwrap_or_else(|_| {
125+
panic!(
126+
"API endpoint `{} {}` already registered",
127+
E::METHOD,
128+
<E::RequestPath as TypedPath>::PATH
129+
);
130+
});
131+
132+
frontend::utils::register_api_call::<E>();
133+
134+
// Setup the layers for the backend
135+
if <E as ApiEndpoint>::API_ALLOWED || cfg!(debug_assertions) {
136+
self.route(
137+
<<E as ApiEndpoint>::RequestPath as TypedPath>::PATH,
138+
MethodRouter::<S>::new()
139+
.on(
140+
MethodFilter::try_from(<E as ApiEndpoint>::METHOD).unwrap(),
141+
|| async {},
142+
)
143+
.layer(
144+
ServiceBuilder::new()
145+
// .layer(todo!("Add rate limiter checker middleware here")),
146+
.layer(RequestParserLayer::new())
147+
.layer(DataStoreConnectionLayer::with_state(state.clone()))
148+
// .layer(todo!("Add rate limiter value updater middleware here"))
149+
.layer(PreprocessLayer::new())
150+
.layer(UserAgentValidationLayer::new())
151+
.layer(EndpointLayer::new(handler)),
152+
),
153+
)
154+
} else {
155+
self
156+
}
157+
}
158+
159+
#[instrument(skip_all)]
160+
fn mount_stream<E, H>(self, handler: H, state: &AppState) -> Self
69161
where
70162
for<'req> H: EndpointHandler<'req, E> + Clone + Send + Sync + 'static,
71163
E: ApiEndpoint<Authenticator = NoAuthentication> + Sync,
@@ -129,6 +221,80 @@ where
129221

130222
#[instrument(skip_all)]
131223
fn mount_auth_endpoint<E, H>(self, handler: H, state: &AppState) -> Self
224+
where
225+
for<'req> H: AuthEndpointHandler<'req, E> + Clone + Send + Sync + 'static,
226+
E: ApiEndpoint<Authenticator = AppAuthentication<E>> + Sync,
227+
<E::RequestBody as Preprocessable>::Processed: Send,
228+
E::RequestBody: Serialize + DeserializeOwned,
229+
E::ResponseBody: Serialize + DeserializeOwned,
230+
E::RequestHeaders: HasHeader<BearerToken>,
231+
{
232+
frontend::utils::API_CALL_REGISTRY
233+
.get_or_init(|| RwLock::new(Default::default()))
234+
.write()
235+
.expect("API call registry poisoned")
236+
.entry(E::METHOD)
237+
.or_default()
238+
.insert(
239+
<E::RequestPath as TypedPath>::PATH,
240+
Box::new(BoxLayer::<
241+
BoxCloneService<(ApiRequest<E>, IpAddr), AppResponse<E>, ErrorType>,
242+
(ApiRequest<E>, IpAddr),
243+
AppResponse<E>,
244+
ErrorType,
245+
>::new(
246+
ServiceBuilder::new()
247+
// .layer(todo!("Add rate limiter checker middleware here")),
248+
.layer(DataStoreConnectionLayer::with_state(state.clone()))
249+
.layer(PreprocessLayer::new())
250+
.layer(UserAgentValidationLayer::new())
251+
.layer(AuthenticationLayer::new(ClientType::WebDashboard))
252+
// .layer(todo!("Add permission checker middleware here"))
253+
// .layer(todo!("Add rate limiter value updater middleware here"))
254+
// .layer(todo!("Add audit logger middleware here"))
255+
.layer(AuthEndpointLayer::new(handler.clone())),
256+
)),
257+
)
258+
.unwrap_or_else(|_| {
259+
panic!(
260+
"API endpoint `{} {}` already registered",
261+
E::METHOD,
262+
<E::RequestPath as TypedPath>::PATH
263+
);
264+
});
265+
266+
frontend::utils::register_api_call::<E>();
267+
268+
// Setup the layers for the backend
269+
if <E as ApiEndpoint>::API_ALLOWED || cfg!(debug_assertions) {
270+
self.route(
271+
<<E as ApiEndpoint>::RequestPath as TypedPath>::PATH,
272+
MethodRouter::<S>::new()
273+
.on(
274+
MethodFilter::try_from(<E as ApiEndpoint>::METHOD).unwrap(),
275+
|| async {},
276+
)
277+
.layer(
278+
ServiceBuilder::new()
279+
// .layer(todo!("Add rate limiter checker middleware here")),
280+
.layer(RequestParserLayer::new())
281+
.layer(DataStoreConnectionLayer::with_state(state.clone()))
282+
.layer(PreprocessLayer::new())
283+
.layer(UserAgentValidationLayer::new())
284+
.layer(AuthenticationLayer::new(ClientType::ApiToken))
285+
// .layer(todo!("Add permission checker middleware here"))
286+
// .layer(todo!("Add rate limiter value updater middleware here"))
287+
// .layer(todo!("Add audit logger middleware here"))
288+
.layer(AuthEndpointLayer::new(handler)),
289+
),
290+
)
291+
} else {
292+
self
293+
}
294+
}
295+
296+
#[instrument(skip_all)]
297+
fn mount_auth_stream<E, H>(self, handler: H, state: &AppState) -> Self
132298
where
133299
for<'req> H: AuthEndpointHandler<'req, E> + Clone + Send + Sync + 'static,
134300
E: ApiEndpoint<Authenticator = AppAuthentication<E>> + Sync,

frontend/common/src/utils/client/make_request.rs

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
#[cfg(not(target_arch = "wasm32"))]
2-
use std::{any::Any, collections::HashMap, sync::RwLock};
3-
use std::{
4-
marker::PhantomData,
5-
sync::{Arc, OnceLock},
6-
};
1+
use std::sync::Arc;
72

83
use axum_extra::routing::TypedPath;
94
use http::Method;
@@ -17,48 +12,10 @@ use leptos::{
1712
},
1813
ServerFnError,
1914
};
20-
use matchit::Router;
21-
use models::{prelude::*, utils::GenericResponse};
15+
use models::prelude::*;
2216
use preprocess::Preprocessable;
2317
use serde::{Serialize, de::DeserializeOwned};
2418

25-
#[cfg(not(target_arch = "wasm32"))]
26-
/// The type used for the [`API_CALL_REGISTRY`] static. This is a map of all the
27-
/// API calls that are registered to the backend. This is used internally and
28-
/// should not be used by any other part of the code.
29-
type ApiCallRegistryData = OnceLock<RwLock<HashMap<Method, Router<Box<dyn Any + Send + Sync>>>>>;
30-
31-
#[cfg(not(target_arch = "wasm32"))]
32-
#[doc(hidden)]
33-
/// Used internally for registering API calls to the backend. DO NOT USE THIS ON
34-
/// YOUR OWN. Use the [`make_request`] fn instead.
35-
pub static API_CALL_REGISTRY: ApiCallRegistryData = OnceLock::new();
36-
37-
#[derive(Debug, Clone, Copy, Default)]
38-
struct ApiEncoding<E>(PhantomData<E>)
39-
where
40-
E: ApiEndpoint;
41-
42-
impl<E> Encoding for ApiEncoding<E>
43-
where
44-
E: ApiEndpoint,
45-
{
46-
const CONTENT_TYPE: &'static str =
47-
if std::any::TypeId::of::<E::ResponseBody>() == std::any::TypeId::of::<GenericResponse>() {
48-
// If the response body is a GenericResponse, then we can't know the
49-
// content type of the response. So we just return the default content
50-
// type of binary data.
51-
"application/octet-stream"
52-
} else {
53-
GetUrl::CONTENT_TYPE
54-
};
55-
const METHOD: Method = if Method::GET == E::METHOD {
56-
Method::GET
57-
} else {
58-
Method::POST
59-
};
60-
}
61-
6219
/// A struct that holds the request to be made to the backend. This is used
6320
/// for the server fn to make the request to the backend.
6421
struct MakeRequest<E>
@@ -94,6 +51,7 @@ where
9451

9552
const PATH: &'static str = <E::RequestPath as TypedPath>::PATH;
9653

54+
#[cfg(not(target_arch = "wasm32"))]
9755
async fn run_body(self) -> Result<Self::Output, ServerFnError<Self::Error>> {
9856
use std::net::{IpAddr, SocketAddr};
9957

@@ -109,7 +67,7 @@ where
10967
let ConnectInfo(socket_addr) = leptos_axum::extract::<ConnectInfo<SocketAddr>>()
11068
.await
11169
.map_err(ErrorType::server_error)?;
112-
let layer = API_CALL_REGISTRY
70+
let layer = super::API_CALL_REGISTRY
11371
.get()
11472
.expect("API call registry not initialized")
11573
.read()
@@ -142,6 +100,11 @@ where
142100
.map_err(ServerFnError::WrappedServerError)
143101
}
144102

103+
#[cfg(target_arch = "wasm32")]
104+
async fn run_body(self) -> Result<Self::Output, ServerFnError<Self::Error>> {
105+
unreachable!()
106+
}
107+
145108
fn middlewares() -> Vec<Arc<dyn Layer<Self::ServerRequest, Self::ServerResponse>>> {
146109
vec![]
147110
}
@@ -232,3 +195,16 @@ where
232195
MakeRequest { request }.run_on_client().await
233196
}
234197
}
198+
199+
/// Register an API call to the backend. This will register the API call to the
200+
/// backend so that it can be used by the frontend. This is used internally and
201+
/// should not be used by any other part of the code.
202+
#[cfg(not(target_arch = "wasm32"))]
203+
pub fn register_api_call<E>()
204+
where
205+
E: ApiEndpoint,
206+
E::RequestBody: Serialize + DeserializeOwned,
207+
E::ResponseBody: Serialize + DeserializeOwned,
208+
{
209+
leptos::server_fn::axum::register_explicit::<MakeRequest<E>>();
210+
}

0 commit comments

Comments
 (0)