格式化代码,后端:新增rocket实例管理

This commit is contained in:
lsy 2024-11-22 13:13:04 +08:00
parent 4bf55506b9
commit eb53c72203
31 changed files with 525 additions and 462 deletions

View File

@ -1,5 +1,5 @@
[info]
install = true
install = false
non_relational = false
[sql_config]

View File

@ -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();

View File

@ -1,21 +1,21 @@
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)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Config {
pub info: Info,
pub sql_config: SqlConfig,
}
#[derive(Deserialize,Serialize,Debug,Clone,)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Info {
pub install: bool,
pub non_relational: bool,
}
#[derive(Deserialize,Serialize,Debug,Clone)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SqlConfig {
pub db_type: String,
pub address: String,
@ -25,7 +25,7 @@ pub struct SqlConfig {
pub db_name: String,
}
#[derive(Deserialize,Serialize,Debug,Clone)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct NoSqlConfig {
pub db_type: String,
pub address: String,
@ -37,18 +37,16 @@ pub struct NoSqlConfig {
impl Config {
pub fn read() -> CustomResult<Self> {
let path=Self::get_path()?;
let path = Self::get_path()?;
Ok(toml::from_str(&fs::read_to_string(path)?)?)
}
pub fn write(config:Config) -> CustomResult<()> {
let path=Self::get_path()?;
pub fn write(config: Config) -> CustomResult<()> {
let path = Self::get_path()?;
fs::write(path, toml::to_string(&config)?)?;
Ok(())
}
pub fn get_path() -> CustomResult<PathBuf> {
Ok(env::current_dir()?
.join("assets")
.join("config.toml"))
Ok(env::current_dir()?.join("assets").join("config.toml"))
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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 {

View File

@ -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
View 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);
}
}

View File

@ -0,0 +1 @@
pub mod token;

View 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()))
}

View File

@ -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]
}

View File

@ -1,64 +1,52 @@
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::database::{relational, relational::builder};
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 struct LoginData {
pub name: String,
pub password: String,
}
pub struct RegisterData{
pub name:String,
pub email:String,
pub password:String
pub struct RegisterData {
pub name: String,
pub email: String,
pub password: String,
}
pub async fn insert(sql:&relational::Database,data:RegisterData) -> CustomResult<()>{
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();
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(())
}
pub fn delete(){}
pub fn delete() {}
pub fn update(){}
pub fn update() {}
pub fn select(){}
pub fn select() {}
pub fn check(){}
pub fn check() {}

View 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(()))))
}

View File

@ -13,6 +13,6 @@ startTransition(() => {
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
</StrictMode>,
);
});

View File

@ -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);

View File

@ -18,5 +18,5 @@ interface ImportMetaEnv {
}
interface ImportMeta {
readonly env: ImportMetaEnv
readonly env: ImportMetaEnv;
}

View File

@ -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;
};
}

View File

@ -15,4 +15,3 @@ export interface PluginConfig {
path: string;
}>;
}

View File

@ -20,7 +20,7 @@ export interface ThemeConfig {
error: string;
loding: string;
page: Map<string, string>;
}
};
}
export interface ThemeTemplate {

View File

@ -1,6 +1,6 @@
import { createContext, useContext, ReactNode, FC } from 'react';
import { createContext, useContext, ReactNode, FC } from "react";
type ServiceContextReturn<N extends string,T> = {
type ServiceContextReturn<N extends string, T> = {
[K in `${N}Provider`]: FC<{ children: ReactNode }>;
} & {
[K in `use${N}`]: () => T;
@ -8,8 +8,8 @@ type ServiceContextReturn<N extends string,T> = {
export function createServiceContext<T, N extends string>(
serviceName: N,
getServiceInstance: () => T
): ServiceContextReturn<N,T> {
getServiceInstance: () => T,
): ServiceContextReturn<N, T> {
const ServiceContext = createContext<T | undefined>(undefined);
const Provider: FC<{ children: ReactNode }> = ({ children }) => (
@ -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;
};
@ -29,5 +31,5 @@ export function createServiceContext<T, N extends string>(
return {
[`${serviceName}Provider`]: Provider,
[`use${serviceName}`]: useService,
} as ServiceContextReturn<N,T>;
} as ServiceContextReturn<N, T>;
}

View File

@ -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>
);

View File

@ -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",

View File

@ -23,15 +23,17 @@ export class ApiService {
private async getSystemToken(): Promise<string> {
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');
if (!username || !password) {
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 {

View File

@ -1,13 +1,17 @@
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;
private constructor() { }
private constructor() {}
public static getInstance(): CapabilityService {
if (!this.instance) {
@ -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);
});

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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: "",
},
};