格式化代码,后端:新增rocket实例管理
This commit is contained in:
parent
4bf55506b9
commit
eb53c72203
@ -1,5 +1,5 @@
|
||||
[info]
|
||||
install = true
|
||||
install = false
|
||||
non_relational = false
|
||||
|
||||
[sql_config]
|
||||
|
@ -1,12 +1,12 @@
|
||||
use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, Token, UntrustedToken, TimeOptions};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::utils::CustomResult;
|
||||
use chrono::{Duration, Utc};
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, TimeOptions, Token, UntrustedToken};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::{env, fs};
|
||||
use crate::utils::CustomResult;
|
||||
use rand::{SeedableRng, RngCore};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CustomClaims {
|
||||
@ -36,9 +36,7 @@ pub fn generate_key() -> CustomResult<()> {
|
||||
let signing_key = SigningKey::from_bytes(&private_key_bytes);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
|
||||
let base_path = env::current_dir()?
|
||||
.join("assets")
|
||||
.join("key");
|
||||
let base_path = env::current_dir()?.join("assets").join("key");
|
||||
|
||||
fs::create_dir_all(&base_path)?;
|
||||
File::create(base_path.join(SecretKey::Signing.as_string()))?
|
||||
@ -64,10 +62,7 @@ pub fn generate_jwt(claims: CustomClaims, duration: Duration) -> CustomResult<St
|
||||
let key_bytes = get_key(SecretKey::Signing)?;
|
||||
let signing_key = SigningKey::from_bytes(&key_bytes);
|
||||
|
||||
let time_options = TimeOptions::new(
|
||||
Duration::seconds(0),
|
||||
Utc::now
|
||||
);
|
||||
let time_options = TimeOptions::new(Duration::seconds(0), Utc::now);
|
||||
let claims = jwt_compact::Claims::new(claims)
|
||||
.set_duration_and_issuance(&time_options, duration)
|
||||
.set_not_before(Utc::now());
|
||||
@ -85,11 +80,9 @@ pub fn validate_jwt(token: &str) -> CustomResult<CustomClaims> {
|
||||
let token = UntrustedToken::new(token)?;
|
||||
let token: Token<CustomClaims> = Ed25519.validator(&verifying).validate(&token)?;
|
||||
|
||||
let time_options = TimeOptions::new(
|
||||
Duration::seconds(0),
|
||||
Utc::now
|
||||
);
|
||||
token.claims()
|
||||
let time_options = TimeOptions::new(Duration::seconds(0), Utc::now);
|
||||
token
|
||||
.claims()
|
||||
.validate_expiration(&time_options)?
|
||||
.validate_maturity(&time_options)?;
|
||||
let claims = token.claims().custom.clone();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize,Serialize};
|
||||
use std::{ env, fs};
|
||||
use std::path::PathBuf;
|
||||
use crate::utils::CustomResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct Config {
|
||||
@ -9,7 +9,7 @@ pub struct Config {
|
||||
pub sql_config: SqlConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Serialize,Debug,Clone,)]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct Info {
|
||||
pub install: bool,
|
||||
pub non_relational: bool,
|
||||
@ -47,8 +47,6 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn get_path() -> CustomResult<PathBuf> {
|
||||
Ok(env::current_dir()?
|
||||
.join("assets")
|
||||
.join("config.toml"))
|
||||
Ok(env::current_dir()?.join("assets").join("config.toml"))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::utils::{CustomError, CustomResult};
|
||||
use regex::Regex;
|
||||
use crate::utils::{CustomResult,CustomError};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
@ -107,11 +107,7 @@ pub struct WhereCondition {
|
||||
}
|
||||
|
||||
impl WhereCondition {
|
||||
pub fn new(
|
||||
field: String,
|
||||
operator: Operator,
|
||||
value: Option<String>,
|
||||
) -> CustomResult<Self> {
|
||||
pub fn new(field: String, operator: Operator, value: Option<String>) -> CustomResult<Self> {
|
||||
let field = ValidatedValue::new_identifier(field)?;
|
||||
|
||||
let value = match value {
|
||||
|
@ -1,8 +1,8 @@
|
||||
mod postgresql;
|
||||
use crate::config;
|
||||
use crate::utils::{CustomError, CustomResult};
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
use crate::utils::{CustomResult,CustomError};
|
||||
use std::sync::Arc;
|
||||
pub mod builder;
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
use super::{DatabaseTrait,builder};
|
||||
use super::{builder, DatabaseTrait};
|
||||
use crate::config;
|
||||
use crate::utils::CustomResult;
|
||||
use async_trait::async_trait;
|
||||
use sqlx::{Column, PgPool, Row, Executor};
|
||||
use sqlx::{Column, Executor, PgPool, Row};
|
||||
use std::collections::HashMap;
|
||||
use std::{env, fs};
|
||||
use crate::utils::CustomResult;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Postgresql {
|
||||
@ -23,12 +22,23 @@ impl DatabaseTrait for Postgresql {
|
||||
.join("init.sql");
|
||||
let grammar = fs::read_to_string(&path)?;
|
||||
|
||||
let connection_str = format!("postgres://{}:{}@{}:{}", db_config.user, db_config.password, db_config.address, db_config.port);
|
||||
let connection_str = format!(
|
||||
"postgres://{}:{}@{}:{}",
|
||||
db_config.user, db_config.password, db_config.address, db_config.port
|
||||
);
|
||||
let pool = PgPool::connect(&connection_str).await?;
|
||||
|
||||
pool.execute(format!("CREATE DATABASE {}", db_config.db_name).as_str()).await?;
|
||||
pool.execute(format!("CREATE DATABASE {}", db_config.db_name).as_str())
|
||||
.await?;
|
||||
|
||||
let new_connection_str = format!("postgres://{}:{}@{}:{}/{}", db_config.user, db_config.password, db_config.address, db_config.port, db_config.db_name);
|
||||
let new_connection_str = format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
db_config.user,
|
||||
db_config.password,
|
||||
db_config.address,
|
||||
db_config.port,
|
||||
db_config.db_name
|
||||
);
|
||||
let new_pool = PgPool::connect(&new_connection_str).await?;
|
||||
|
||||
new_pool.execute(grammar.as_str()).await?;
|
||||
@ -39,11 +49,14 @@ impl DatabaseTrait for Postgresql {
|
||||
async fn connect(db_config: &config::SqlConfig) -> CustomResult<Self> {
|
||||
let connection_str = format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
db_config.user, db_config.password, db_config.address, db_config.port, db_config.db_name
|
||||
db_config.user,
|
||||
db_config.password,
|
||||
db_config.address,
|
||||
db_config.port,
|
||||
db_config.db_name
|
||||
);
|
||||
|
||||
let pool = PgPool::connect(&connection_str)
|
||||
.await?;
|
||||
let pool = PgPool::connect(&connection_str).await?;
|
||||
|
||||
Ok(Postgresql { pool })
|
||||
}
|
||||
@ -60,9 +73,7 @@ impl DatabaseTrait for Postgresql {
|
||||
sqlx_query = sqlx_query.bind(value);
|
||||
}
|
||||
|
||||
let rows = sqlx_query
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
let rows = sqlx_query.fetch_all(&self.pool).await?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for row in rows {
|
||||
|
@ -1,18 +1,14 @@
|
||||
mod auth;
|
||||
mod config;
|
||||
mod database;
|
||||
mod auth;
|
||||
mod utils;
|
||||
mod manage;
|
||||
mod routes;
|
||||
use chrono::Duration;
|
||||
mod utils;
|
||||
use database::relational;
|
||||
use rocket::{
|
||||
get, http::Status, launch, response::status, State
|
||||
};
|
||||
use rocket::launch;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use utils::{CustomResult, AppResult,CustomError};
|
||||
|
||||
|
||||
use utils::{AppResult, CustomError, CustomResult};
|
||||
|
||||
struct AppState {
|
||||
db: Arc<Mutex<Option<relational::Database>>>,
|
||||
@ -28,30 +24,13 @@ impl AppState {
|
||||
.ok_or_else(|| CustomError::from_str("Database not initialized"))
|
||||
}
|
||||
|
||||
async fn link_sql(&self, config: config::SqlConfig) -> Result<(),CustomError> {
|
||||
let database = relational::Database::link(&config)
|
||||
.await?;
|
||||
async fn link_sql(&self, config: &config::SqlConfig) -> CustomResult<()> {
|
||||
let database = relational::Database::link(config).await?;
|
||||
*self.db.lock().await = Some(database);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[get("/system")]
|
||||
async fn token_system(_state: &State<AppState>) -> AppResult<status::Custom<String>> {
|
||||
let claims = auth::jwt::CustomClaims {
|
||||
name: "system".into(),
|
||||
};
|
||||
|
||||
auth::jwt::generate_jwt(claims, Duration::seconds(1))
|
||||
.map(|token| status::Custom(Status::Ok, token))
|
||||
.map_err(|e| status::Custom(Status::InternalServerError, e.to_string()))
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[launch]
|
||||
async fn rocket() -> _ {
|
||||
let config = config::Config::read().expect("Failed to read config");
|
||||
@ -65,35 +44,16 @@ async fn rocket() -> _ {
|
||||
|
||||
if config.info.install {
|
||||
if let Some(state) = rocket_builder.state::<AppState>() {
|
||||
state.link_sql(config.sql_config.clone())
|
||||
state
|
||||
.link_sql(&config.sql_config)
|
||||
.await
|
||||
.expect("Failed to connect to database");
|
||||
}
|
||||
} else {
|
||||
rocket_builder = rocket_builder.mount("/", rocket::routes![routes::intsall::install]);
|
||||
}
|
||||
|
||||
if ! config.info.install {
|
||||
rocket_builder = rocket_builder
|
||||
.mount("/", rocket::routes![routes::intsall::install]);
|
||||
}
|
||||
|
||||
rocket_builder = rocket_builder
|
||||
.mount("/auth/token", rocket::routes![token_system]);
|
||||
rocket_builder = rocket_builder.mount("/auth/token", routes::jwt_routes());
|
||||
|
||||
rocket_builder
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_placeholder() {
|
||||
let config = config::Config::read().expect("Failed to read config");
|
||||
|
||||
let state = AppState {
|
||||
db: Arc::new(Mutex::new(None)),
|
||||
configure: Arc::new(Mutex::new(config.clone())),
|
||||
};
|
||||
state.link_sql(config.sql_config.clone())
|
||||
.await
|
||||
.expect("Failed to connect to database");
|
||||
let sql=state.get_sql().await.expect("Failed to get sql");
|
||||
let _=routes::person::insert(&sql,routes::person::RegisterData{ name: String::from("test"), email: String::from("lsy22@vip.qq.com"), password:String::from("test") }).await.unwrap();
|
||||
}
|
||||
|
||||
|
57
backend/src/manage.rs
Normal file
57
backend/src/manage.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use rocket::shutdown::Shutdown;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::process::{exit, Command};
|
||||
use tokio::signal;
|
||||
|
||||
// 应用管理器
|
||||
pub struct AppManager {
|
||||
shutdown: Shutdown,
|
||||
executable_path: String,
|
||||
}
|
||||
|
||||
impl AppManager {
|
||||
pub fn new(shutdown: Shutdown) -> Self {
|
||||
let executable_path = env::current_exe()
|
||||
.expect("Failed to get executable path")
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
Self {
|
||||
shutdown,
|
||||
executable_path,
|
||||
}
|
||||
}
|
||||
|
||||
// 优雅关闭
|
||||
pub async fn graceful_shutdown(&self) {
|
||||
println!("Initiating graceful shutdown...");
|
||||
|
||||
// 触发 Rocket 的优雅关闭
|
||||
self.shutdown.notify();
|
||||
|
||||
// 等待一段时间以确保连接正确关闭
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||
}
|
||||
|
||||
// 重启应用
|
||||
pub async fn restart(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Preparing to restart application...");
|
||||
|
||||
// 执行优雅关闭
|
||||
self.graceful_shutdown().await;
|
||||
|
||||
// 在新进程中启动应用
|
||||
if cfg!(target_os = "windows") {
|
||||
Command::new("cmd")
|
||||
.args(&["/C", &self.executable_path])
|
||||
.spawn()?;
|
||||
} else {
|
||||
Command::new(&self.executable_path).spawn()?;
|
||||
}
|
||||
|
||||
// 退出当前进程
|
||||
println!("Application restarting...");
|
||||
exit(0);
|
||||
}
|
||||
}
|
1
backend/src/routes/auth/mod.rs
Normal file
1
backend/src/routes/auth/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod token;
|
15
backend/src/routes/auth/token.rs
Normal file
15
backend/src/routes/auth/token.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::auth;
|
||||
use crate::{AppResult, AppState};
|
||||
use chrono::Duration;
|
||||
use rocket::{get, http::Status, response::status, State};
|
||||
|
||||
#[get("/system")]
|
||||
pub async fn token_system(_state: &State<AppState>) -> AppResult<status::Custom<String>> {
|
||||
let claims = auth::jwt::CustomClaims {
|
||||
name: "system".into(),
|
||||
};
|
||||
|
||||
auth::jwt::generate_jwt(claims, Duration::seconds(1))
|
||||
.map(|token| status::Custom(Status::Ok, token))
|
||||
.map_err(|e| status::Custom(Status::InternalServerError, e.to_string()))
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
pub mod auth;
|
||||
pub mod intsall;
|
||||
pub mod person;
|
||||
pub mod theme;
|
||||
use rocket::routes;
|
||||
|
||||
pub fn create_routes() -> Vec<rocket::Route> {
|
||||
routes![
|
||||
intsall::install,
|
||||
]
|
||||
pub fn jwt_routes() -> Vec<rocket::Route> {
|
||||
routes![auth::token::token_system]
|
||||
}
|
||||
|
||||
|
@ -1,55 +1,43 @@
|
||||
use serde::{Deserialize,Serialize};
|
||||
use crate::{config,utils};
|
||||
use crate::database::{relational, relational::builder};
|
||||
use rocket::{
|
||||
get, post,
|
||||
http::Status,
|
||||
response::status,
|
||||
serde::json::Json,
|
||||
State,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use crate::utils::CustomResult;
|
||||
|
||||
|
||||
use crate::{config, utils};
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use rocket::{get, http::Status, post, response::status, serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct LoginData {
|
||||
pub name: String,
|
||||
pub password:String
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub struct RegisterData {
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub password:String
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub async fn insert(sql: &relational::Database, data: RegisterData) -> CustomResult<()> {
|
||||
let hashed_password = hash(data.password, DEFAULT_COST).expect("Failed to hash password");
|
||||
|
||||
|
||||
let mut user_params = HashMap::new();
|
||||
user_params.insert(
|
||||
builder::ValidatedValue::Identifier(String::from("person_name"))
|
||||
,
|
||||
builder::ValidatedValue::PlainText(data.name)
|
||||
builder::ValidatedValue::Identifier(String::from("person_name")),
|
||||
builder::ValidatedValue::PlainText(data.name),
|
||||
);
|
||||
user_params.insert(
|
||||
builder::ValidatedValue::Identifier(String::from("person_email"))
|
||||
,
|
||||
builder::ValidatedValue::PlainText(data.email)
|
||||
builder::ValidatedValue::Identifier(String::from("person_email")),
|
||||
builder::ValidatedValue::PlainText(data.email),
|
||||
);
|
||||
user_params.insert(
|
||||
builder::ValidatedValue::Identifier(String::from("person_password"))
|
||||
,
|
||||
builder::ValidatedValue::PlainText(hashed_password)
|
||||
builder::ValidatedValue::Identifier(String::from("person_password")),
|
||||
builder::ValidatedValue::PlainText(hashed_password),
|
||||
);
|
||||
|
||||
let builder = builder::QueryBuilder::new(builder::SqlOperation::Insert,String::from("persons"))?
|
||||
.params(user_params)
|
||||
;
|
||||
let builder =
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Insert, String::from("persons"))?
|
||||
.params(user_params);
|
||||
|
||||
sql.get_db().execute_query(&builder).await?;
|
||||
Ok(())
|
||||
|
12
backend/src/routes/theme.rs
Normal file
12
backend/src/routes/theme.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use crate::utils::AppResult;
|
||||
use rocket::{
|
||||
http::Status,
|
||||
post,
|
||||
response::status,
|
||||
serde::json::{Json, Value},
|
||||
};
|
||||
|
||||
#[post("/current", format = "application/json", data = "<data>")]
|
||||
pub fn theme_current(data: Json<String>) -> AppResult<status::Custom<Json<Value>>> {
|
||||
Ok(status::Custom(Status::Ok, Json(Value::Object(()))))
|
||||
}
|
@ -13,6 +13,6 @@ startTransition(() => {
|
||||
document,
|
||||
<StrictMode>
|
||||
<RemixBrowser />
|
||||
</StrictMode>
|
||||
</StrictMode>,
|
||||
);
|
||||
});
|
||||
|
@ -22,20 +22,20 @@ export default function handleRequest(
|
||||
// This is ignored so we can keep it in the template for visibility. Feel
|
||||
// free to delete this parameter in your app if you're not using it!
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
loadContext: AppLoadContext
|
||||
loadContext: AppLoadContext,
|
||||
) {
|
||||
return isbot(request.headers.get("user-agent") || "")
|
||||
? handleBotRequest(
|
||||
request,
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext
|
||||
remixContext,
|
||||
)
|
||||
: handleBrowserRequest(
|
||||
request,
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext
|
||||
remixContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ function handleBotRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext
|
||||
remixContext: EntryContext,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let shellRendered = false;
|
||||
@ -65,7 +65,7 @@ function handleBotRequest(
|
||||
new Response(stream, {
|
||||
headers: responseHeaders,
|
||||
status: responseStatusCode,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
pipe(body);
|
||||
@ -82,7 +82,7 @@ function handleBotRequest(
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(abort, ABORT_DELAY);
|
||||
@ -93,7 +93,7 @@ function handleBrowserRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext
|
||||
remixContext: EntryContext,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let shellRendered = false;
|
||||
@ -115,7 +115,7 @@ function handleBrowserRequest(
|
||||
new Response(stream, {
|
||||
headers: responseHeaders,
|
||||
status: responseStatusCode,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
pipe(body);
|
||||
@ -132,7 +132,7 @@ function handleBrowserRequest(
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(abort, ABORT_DELAY);
|
||||
|
2
frontend/app/env.d.ts
vendored
2
frontend/app/env.d.ts
vendored
@ -18,5 +18,5 @@ interface ImportMetaEnv {
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
export type SerializeType = null | number | string | boolean | { [key: string]: SerializeType } | Array<SerializeType>;
|
||||
export type SerializeType =
|
||||
| null
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| { [key: string]: SerializeType }
|
||||
| Array<SerializeType>;
|
||||
export interface Configuration {
|
||||
[key: string]: {
|
||||
title: string;
|
||||
@ -6,4 +12,3 @@ export interface Configuration {
|
||||
data: SerializeType;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -15,4 +15,3 @@ export interface PluginConfig {
|
||||
path: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ export interface ThemeConfig {
|
||||
error: string;
|
||||
loding: string;
|
||||
page: Map<string, string>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ThemeTemplate {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, ReactNode, FC } from 'react';
|
||||
import { createContext, useContext, ReactNode, FC } from "react";
|
||||
|
||||
type ServiceContextReturn<N extends string, T> = {
|
||||
[K in `${N}Provider`]: FC<{ children: ReactNode }>;
|
||||
@ -8,7 +8,7 @@ type ServiceContextReturn<N extends string,T> = {
|
||||
|
||||
export function createServiceContext<T, N extends string>(
|
||||
serviceName: N,
|
||||
getServiceInstance: () => T
|
||||
getServiceInstance: () => T,
|
||||
): ServiceContextReturn<N, T> {
|
||||
const ServiceContext = createContext<T | undefined>(undefined);
|
||||
|
||||
@ -21,7 +21,9 @@ export function createServiceContext<T, N extends string>(
|
||||
const useService = (): T => {
|
||||
const context = useContext(ServiceContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(`use${serviceName} must be used within a ${serviceName}Provider`);
|
||||
throw new Error(
|
||||
`use${serviceName} must be used within a ${serviceName}Provider`,
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
@ -5,23 +5,22 @@ import { createServiceContext } from "hooks/createServiceContext";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const { CapabilityProvider, useCapability } = createServiceContext(
|
||||
"Capability", () => CapabilityService.getInstance(),
|
||||
"Capability",
|
||||
() => CapabilityService.getInstance(),
|
||||
);
|
||||
|
||||
export const { ThemeProvider, useTheme } = createServiceContext(
|
||||
"Theme", () => ThemeService.getInstance(),
|
||||
export const { ThemeProvider, useTheme } = createServiceContext("Theme", () =>
|
||||
ThemeService.getInstance(),
|
||||
);
|
||||
|
||||
export const { ApiProvider, useApi } = createServiceContext(
|
||||
"Api", () => ThemeService.getInstance(),
|
||||
export const { ApiProvider, useApi } = createServiceContext("Api", () =>
|
||||
ThemeService.getInstance(),
|
||||
);
|
||||
|
||||
export const ServiceProvider = ({ children }: { children: ReactNode }) => (
|
||||
<ApiProvider>
|
||||
<CapabilityProvider>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</CapabilityProvider>
|
||||
</ApiProvider>
|
||||
);
|
||||
|
@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"build": "remix vite:build",
|
||||
"dev": "remix vite:dev",
|
||||
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
|
||||
"format": "prettier --write \"./**/*.{ts,tsx,js,jsx}\"",
|
||||
"lint": "eslint \"./**/*.{ts,tsx,js,jsx}\" --fix",
|
||||
"start": "remix-serve ./build/server/index.js",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
@ -27,13 +28,14 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^5.1.0",
|
||||
|
@ -24,14 +24,16 @@ export class ApiService {
|
||||
const username = import.meta.env.VITE_SYSTEM_USERNAME;
|
||||
const password = import.meta.env.VITE_SYSTEM_PASSWORD;
|
||||
if (!username || !password) {
|
||||
throw new Error('Failed to obtain the username or password of the front-end system');
|
||||
throw new Error(
|
||||
"Failed to obtain the username or password of the front-end system",
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseURL}/auth/token/system`, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
@ -40,13 +42,13 @@ export class ApiService {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get system token');
|
||||
throw new Error("Failed to get system token");
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error getting system token:', error);
|
||||
console.error("Error getting system token:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -54,7 +56,7 @@ export class ApiService {
|
||||
public async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {},
|
||||
toekn ?: string
|
||||
toekn?: string,
|
||||
): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
@ -63,7 +65,7 @@ export class ApiService {
|
||||
const headers = new Headers(options.headers);
|
||||
|
||||
if (toekn) {
|
||||
headers.set('Authorization', `Bearer ${toekn}`);
|
||||
headers.set("Authorization", `Bearer ${toekn}`);
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
@ -79,8 +81,8 @@ export class ApiService {
|
||||
const data = await response.json();
|
||||
return data as T;
|
||||
} catch (error: any) {
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('Request timeout');
|
||||
if (error.name === "AbortError") {
|
||||
throw new Error("Request timeout");
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { CapabilityProps } from "contracts/capabilityContract";
|
||||
|
||||
export class CapabilityService {
|
||||
private capabilities: Map<string, Set<{
|
||||
private capabilities: Map<
|
||||
string,
|
||||
Set<{
|
||||
source: string;
|
||||
capability: CapabilityProps<any>}>> = new Map();
|
||||
capability: CapabilityProps<any>;
|
||||
}>
|
||||
> = new Map();
|
||||
|
||||
private static instance: CapabilityService;
|
||||
|
||||
@ -16,22 +20,31 @@ export class CapabilityService {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private register(capabilityName: string, source: string, capability: CapabilityProps<any>) {
|
||||
private register(
|
||||
capabilityName: string,
|
||||
source: string,
|
||||
capability: CapabilityProps<any>,
|
||||
) {
|
||||
const handlers = this.capabilities.get(capabilityName) || new Set();
|
||||
handlers.add({ source, capability });
|
||||
}
|
||||
|
||||
private executeCapabilityMethod<T>(capabilityName: string, ...args: any[]): Set<T> {
|
||||
private executeCapabilityMethod<T>(
|
||||
capabilityName: string,
|
||||
...args: any[]
|
||||
): Set<T> {
|
||||
const results = new Set<T>();
|
||||
const handlers = this.capabilities.get(capabilityName);
|
||||
|
||||
if (handlers) {
|
||||
handlers.forEach(({ capability }) => {
|
||||
const methodFunction = capability['execute'];
|
||||
const methodFunction = capability["execute"];
|
||||
if (methodFunction) {
|
||||
methodFunction(...args)
|
||||
.then((data) => results.add(data as T))
|
||||
.catch((error) => console.error(`Error executing method ${capabilityName}:`, error));
|
||||
.catch((error) =>
|
||||
console.error(`Error executing method ${capabilityName}:`, error),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -41,7 +54,9 @@ export class CapabilityService {
|
||||
private removeCapability(source: string) {
|
||||
this.capabilities.forEach((capability_s, capabilityName) => {
|
||||
const newHandlers = new Set(
|
||||
Array.from(capability_s).filter(capability => capability.source !== source)
|
||||
Array.from(capability_s).filter(
|
||||
(capability) => capability.source !== source,
|
||||
),
|
||||
);
|
||||
this.capabilities.set(capabilityName, newHandlers);
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PluginConfiguration } from 'types/pluginRequirement';
|
||||
import { Contracts } from 'contracts/capabilityContract';
|
||||
import { PluginConfiguration } from "types/pluginRequirement";
|
||||
import { Contracts } from "contracts/capabilityContract";
|
||||
|
||||
export class PluginManager {
|
||||
private plugins: Map<string, PluginProps> = new Map();
|
||||
@ -35,7 +35,9 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
async getPluginConfig(pluginName: string): Promise<PluginConfiguration | undefined> {
|
||||
async getPluginConfig(
|
||||
pluginName: string,
|
||||
): Promise<PluginConfiguration | undefined> {
|
||||
const dbConfig = await this.fetchConfigFromDB(pluginName);
|
||||
if (dbConfig) {
|
||||
return dbConfig;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'; // Import React
|
||||
import { LoaderFunction, RouteObject } from 'react-router-dom';
|
||||
import React from "react"; // Import React
|
||||
import { LoaderFunction, RouteObject } from "react-router-dom";
|
||||
|
||||
export class RouteManager {
|
||||
private static instance: RouteManager;
|
||||
@ -14,18 +14,21 @@ export class RouteManager {
|
||||
return RouteManager.instance;
|
||||
}
|
||||
|
||||
|
||||
private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction,children?:RouteObject[]) {
|
||||
private createRouteElement(
|
||||
path: string,
|
||||
element: React.ReactNode,
|
||||
loader?: LoaderFunction,
|
||||
children?: RouteObject[],
|
||||
) {
|
||||
this.routes.push({
|
||||
path,
|
||||
element,
|
||||
loader,
|
||||
children,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private getRoutes(): RouteObject[] {
|
||||
return this.routes;
|
||||
}
|
||||
|
||||
}
|
@ -20,12 +20,12 @@ export class ThemeService {
|
||||
public async initialize(): Promise<void> {
|
||||
try {
|
||||
const themeConfig = await this.api.request<ThemeConfig>(
|
||||
'/theme/current',
|
||||
{ method: 'GET' }
|
||||
"/theme/current",
|
||||
{ method: "GET" },
|
||||
);
|
||||
await this.loadTheme(themeConfig);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize theme:', error);
|
||||
console.error("Failed to initialize theme:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -35,14 +35,14 @@ export class ThemeService {
|
||||
this.currentTheme = config;
|
||||
await this.loadTemplates();
|
||||
} catch (error) {
|
||||
console.error('Failed to load theme:', error);
|
||||
console.error("Failed to load theme:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadTemplates(): Promise<void> {
|
||||
if (!this.currentTheme) {
|
||||
throw new Error('No theme configuration loaded');
|
||||
throw new Error("No theme configuration loaded");
|
||||
}
|
||||
|
||||
const loadTemplate = async (template: ThemeTemplate) => {
|
||||
@ -56,8 +56,9 @@ export class ThemeService {
|
||||
}
|
||||
};
|
||||
|
||||
const loadPromises = Array.from(this.currentTheme.templates.values())
|
||||
.map(template => loadTemplate(template));
|
||||
const loadPromises = Array.from(this.currentTheme.templates.values()).map(
|
||||
(template) => loadTemplate(template),
|
||||
);
|
||||
|
||||
await Promise.all(loadPromises);
|
||||
}
|
||||
@ -76,18 +77,18 @@ export class ThemeService {
|
||||
|
||||
public getTemplateByRoute(route: string): string {
|
||||
if (!this.currentTheme) {
|
||||
throw new Error('No theme configuration loaded');
|
||||
throw new Error("No theme configuration loaded");
|
||||
}
|
||||
|
||||
let templateName: string | undefined;
|
||||
|
||||
if (route === '/') {
|
||||
if (route === "/") {
|
||||
templateName = this.currentTheme.routes.index;
|
||||
} else if (route.startsWith('/post/')) {
|
||||
} else if (route.startsWith("/post/")) {
|
||||
templateName = this.currentTheme.routes.post;
|
||||
} else if (route.startsWith('/tag/')) {
|
||||
} else if (route.startsWith("/tag/")) {
|
||||
templateName = this.currentTheme.routes.tag;
|
||||
} else if (route.startsWith('/category/')) {
|
||||
} else if (route.startsWith("/category/")) {
|
||||
templateName = this.currentTheme.routes.category;
|
||||
} else {
|
||||
templateName = this.currentTheme.routes.page.get(route);
|
||||
@ -103,19 +104,19 @@ export class ThemeService {
|
||||
public async updateThemeConfig(config: Partial<ThemeConfig>): Promise<void> {
|
||||
try {
|
||||
const updatedConfig = await this.api.request<ThemeConfig>(
|
||||
'/theme/config',
|
||||
"/theme/config",
|
||||
{
|
||||
method: 'PUT',
|
||||
method: "PUT",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(config),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await this.loadTheme(updatedConfig);
|
||||
} catch (error) {
|
||||
console.error('Failed to update theme configuration:', error);
|
||||
console.error("Failed to update theme configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,27 @@
|
||||
import { ThemeConfig } from "contracts/themeContract";
|
||||
|
||||
export const themeConfig: ThemeConfig = {
|
||||
name: 'default',
|
||||
displayName: '默认主题',
|
||||
version: '1.0.0',
|
||||
description: '一个简约风格的博客主题',
|
||||
author: 'lsy',
|
||||
entry: './index.tsx',
|
||||
name: "default",
|
||||
displayName: "默认主题",
|
||||
version: "1.0.0",
|
||||
description: "一个简约风格的博客主题",
|
||||
author: "lsy",
|
||||
entry: "default",
|
||||
templates: new Map([
|
||||
['page', {
|
||||
path: './templates/page',
|
||||
name: '文章列表模板',
|
||||
description: '博客首页展示模板'
|
||||
}],
|
||||
[
|
||||
"page",
|
||||
{
|
||||
path: "./templates/page",
|
||||
name: "文章列表模板",
|
||||
description: "博客首页展示模板",
|
||||
},
|
||||
],
|
||||
]),
|
||||
|
||||
routes: {
|
||||
post: "",
|
||||
tag: "",
|
||||
category: "",
|
||||
page: ""
|
||||
}
|
||||
}
|
||||
page: "",
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user