refactor: remove loco-rs deps

This commit is contained in:
2025-03-01 15:21:14 +08:00
parent a68aab1452
commit 2844e1fc32
66 changed files with 2565 additions and 1876 deletions

View File

@@ -0,0 +1,52 @@
import type { Injector } from '@outposts/injection-js';
import {
// Link,
Outlet,
createRootRouteWithContext,
} from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
import type { OidcSecurityService } from 'oidc-client-rx';
export type RouterContext =
| {
isAuthenticated: false;
injector: Injector;
oidcSecurityService: OidcSecurityService;
}
| {
isAuthenticated: true;
injector?: Injector;
oidcSecurityService?: OidcSecurityService;
};
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootComponent,
});
function RootComponent() {
return (
<>
{/* <div className="flex gap-2 p-2 text-lg ">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
>
Home
</Link>{' '}
<Link
to="/graphql"
activeProps={{
className: 'font-bold',
}}
>
GraphQL
</Link>
</div> */}
{/* <hr /> */}
<Outlet />
<TanStackRouterDevtools position="bottom-right" />
</>
);
}

View File

@@ -0,0 +1,50 @@
use std::{borrow::Cow, sync::Arc};
use axum::Router;
use crate::app::AppContext;
pub trait ControllerTrait: Sized {
fn apply_to(self, router: Router<Arc<AppContext>>) -> Router<Arc<AppContext>>;
}
pub struct PrefixController {
prefix: Cow<'static, str>,
router: Router<Arc<AppContext>>,
}
impl PrefixController {
pub fn new(prefix: impl Into<Cow<'static, str>>, router: Router<Arc<AppContext>>) -> Self {
Self {
prefix: prefix.into(),
router,
}
}
}
impl ControllerTrait for PrefixController {
fn apply_to(self, router: Router<Arc<AppContext>>) -> Router<Arc<AppContext>> {
router.nest(&self.prefix, self.router)
}
}
pub enum Controller {
Prefix(PrefixController),
}
impl Controller {
pub fn from_prefix(
prefix: impl Into<Cow<'static, str>>,
router: Router<Arc<AppContext>>,
) -> Self {
Self::Prefix(PrefixController::new(prefix, router))
}
}
impl ControllerTrait for Controller {
fn apply_to(self, router: Router<Arc<AppContext>>) -> Router<Arc<AppContext>> {
match self {
Self::Prefix(p) => p.apply_to(router),
}
}
}

View File

@@ -0,0 +1,36 @@
import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
import { createFileRoute } from '@tanstack/react-router';
import GraphiQL from 'graphiql';
import { useMemo } from 'react';
import { beforeLoadGuard } from '../../../auth/guard';
import 'graphiql/graphiql.css';
import { firstValueFrom } from 'rxjs';
import { useAuth } from '../../../auth/hooks';
export const Route = createFileRoute('/graphql/')({
component: RouteComponent,
beforeLoad: beforeLoadGuard,
});
function RouteComponent() {
const { oidcSecurityService } = useAuth();
const fetcher = useMemo(
(): Fetcher => async (props) => {
const accessToken = oidcSecurityService
? await firstValueFrom(oidcSecurityService.getAccessToken())
: undefined;
return createGraphiQLFetcher({
url: '/api/graphql',
headers: accessToken
? {
Authorization: `Bearer ${accessToken}`,
}
: undefined,
})(props);
},
[oidcSecurityService]
);
return <GraphiQL fetcher={fetcher} className="h-svh" />;
}

View File

@@ -0,0 +1,33 @@
use std::sync::Arc;
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use axum::{Extension, Router, extract::State, middleware::from_fn_with_state, routing::post};
use super::core::Controller;
use crate::{
app::AppContext,
auth::{AuthUserInfo, header_www_authenticate_middleware},
errors::RResult,
};
pub const CONTROLLER_PREFIX: &str = "/api/graphql";
async fn graphql_handler(
State(ctx): State<Arc<AppContext>>,
Extension(auth_user_info): Extension<AuthUserInfo>,
req: GraphQLRequest,
) -> GraphQLResponse {
let graphql_service = &ctx.graphql;
let mut req = req.into_inner();
req = req.data(auth_user_info);
graphql_service.schema.execute(req).await.into()
}
pub async fn create(ctx: Arc<AppContext>) -> RResult<Controller> {
let router = Router::<Arc<AppContext>>::new()
.route("/", post(graphql_handler))
.layer(from_fn_with_state(ctx, header_www_authenticate_middleware));
Ok(Controller::from_prefix(CONTROLLER_PREFIX, router))
}

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello to playground!</div>
}

View File

@@ -0,0 +1,5 @@
pub mod core;
pub mod graphql;
pub mod oidc;
pub use core::{Controller, ControllerTrait, PrefixController};

View File

@@ -0,0 +1,32 @@
import { createFileRoute, redirect } from '@tanstack/react-router';
import { EventTypes } from 'oidc-client-rx';
import { useAuth } from '../../../auth/hooks';
export const Route = createFileRoute('/oidc/callback')({
component: RouteComponent,
beforeLoad: ({ context }) => {
if (!context.oidcSecurityService) {
throw redirect({
to: '/',
});
}
},
});
function RouteComponent() {
const auth = useAuth();
if (!auth.checkAuthResultEvent) {
return <div>Loading...</div>;
}
return (
<div>
OpenID Connect Auth Callback:{' '}
{auth.checkAuthResultEvent?.type ===
EventTypes.CheckingAuthFinishedWithError
? auth.checkAuthResultEvent.value
: 'success'}
</div>
);
}

View File

@@ -0,0 +1,79 @@
use std::sync::Arc;
use axum::{
Json, Router,
extract::{Query, State},
http::request::Parts,
routing::get,
};
use super::core::Controller;
use crate::{
app::AppContext,
auth::{
AuthError, AuthService, AuthServiceTrait,
oidc::{OidcAuthCallbackPayload, OidcAuthCallbackQuery, OidcAuthRequest},
},
errors::RResult,
extract::http::ForwardedRelatedInfo,
models::auth::AuthType,
};
pub const CONTROLLER_PREFIX: &str = "/api/oidc";
async fn oidc_callback(
State(ctx): State<Arc<AppContext>>,
Query(query): Query<OidcAuthCallbackQuery>,
) -> Result<Json<OidcAuthCallbackPayload>, AuthError> {
let auth_service = &ctx.auth;
if let AuthService::Oidc(oidc_auth_service) = auth_service {
let response = oidc_auth_service
.extract_authorization_request_callback(query)
.await?;
Ok(Json(response))
} else {
Err(AuthError::NotSupportAuthMethod {
supported: vec![auth_service.auth_type()],
current: AuthType::Oidc,
})
}
}
async fn oidc_auth(
State(ctx): State<Arc<AppContext>>,
parts: Parts,
) -> Result<Json<OidcAuthRequest>, AuthError> {
let auth_service = &ctx.auth;
if let AuthService::Oidc(oidc_auth_service) = auth_service {
let mut redirect_uri = ForwardedRelatedInfo::from_request_parts(&parts)
.resolved_origin()
.ok_or_else(|| AuthError::OidcRequestRedirectUriError(url::ParseError::EmptyHost))?;
redirect_uri.set_path(&format!("{CONTROLLER_PREFIX}/callback"));
let auth_request = oidc_auth_service
.build_authorization_request(redirect_uri.as_str())
.await?;
{
oidc_auth_service
.store_authorization_request(auth_request.clone())
.await?;
}
Ok(Json(auth_request))
} else {
Err(AuthError::NotSupportAuthMethod {
supported: vec![auth_service.auth_type()],
current: AuthType::Oidc,
})
}
}
pub async fn create(_context: Arc<AppContext>) -> RResult<Controller> {
let router = Router::<Arc<AppContext>>::new()
.route("/auth", get(oidc_auth))
.route("/callback", get(oidc_callback));
Ok(Controller::from_prefix(CONTROLLER_PREFIX, router))
}