diff --git a/backend/Cargo.toml b/backend/Cargo.toml index fdde733..698455c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,5 +11,8 @@ toml = "0.8.19" tokio = { version = "1", features = ["full"] } sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres"] } async-trait = "0.1.83" -anyhow = "1.0" -once_cell = "1.10.0" \ No newline at end of file +once_cell = "1.10.0" +jwt-compact = { version = "0.8.0", features = ["ed25519-dalek"] } +ed25519-dalek = "2.1.1" +rand = "0.8.5" +chrono = "0.4" \ No newline at end of file diff --git a/backend/config.toml b/backend/assets/config.toml similarity index 100% rename from backend/config.toml rename to backend/assets/config.toml diff --git a/backend/src/config.rs b/backend/src/config.rs index 6a0a35e..c7482bb 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -32,6 +32,7 @@ impl Config { /// 读取配置文件 pub fn read() -> Result> { let path = env::current_dir()? + .join("assets") .join("config.toml"); Ok(toml::from_str(&fs::read_to_string(path)?)?) } diff --git a/backend/src/main.rs b/backend/src/main.rs index 45e14fe..1a1e27a 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -9,11 +9,15 @@ */ mod config; // 配置模块 -mod database; // 数据库模块 +mod database; +mod secret; +use chrono::Duration; +// 数据库模块 use database::relational; // 引入关系型数据库 use once_cell::sync::Lazy; // 用于延迟初始化 use rocket::{get, http::Status, launch, response::status, routes, serde::json::Json}; // 引入Rocket框架相关功能 -use std::sync::Arc; // 引入Arc用于线程安全的引用计数 +use std::sync::Arc; +// 引入Arc用于线程安全的引用计数 use tokio::sync::Mutex; // 引入Mutex用于异步锁 // 全局数据库连接变量 @@ -86,3 +90,24 @@ async fn rocket() -> _ { .expect("Failed to connect to database"); // 初始化数据库连接 rocket::build().mount("/api", routes![install, ssql]) // 挂载API路由 } + + +// fn main(){ +// // secret::generate_key().expect("msg"); +// // 创建claims +// let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; + +// // 创建 Claims +// let claims = secret::CustomClaims { +// user_id: String::from("lsy"), +// device_ua: String::from("lsy"), +// }; +// let t=String::from("eyJhbGciOiJFZERTQSJ9.eyJleHAiOjE3MzE3NTczMDMsIm5iZiI6MTczMTc1NzI4MywiaWF0IjoxNzMxNzU3MjgzLCJ1c2VyX2lkIjoibHN5IiwiZGV2aWNlX3VhIjoibHN5In0.C8t5XZFSKnnDVmc6WkY-gzGNSAP7lNAjP9yBjhdvIRO7r_QjDnfcm0INIqCt5cyvnRlE2rFJIx_axOfLx2QJAw"); +// // 生成JWT +// let token = secret::generate_jwt(claims,Duration::seconds(20)).expect("msg"); +// println!("{}", token); + +// // 验证JWT +// let a=secret::validate_jwt(&t).expect("msg"); +// println!("\n\n{}",a.user_id) +// } \ No newline at end of file diff --git a/backend/src/secret.rs b/backend/src/secret.rs new file mode 100644 index 0000000..455c118 --- /dev/null +++ b/backend/src/secret.rs @@ -0,0 +1,119 @@ +// File path: src/secret.rs + +/** + * 本文件包含JWT的生成和验证功能。 + * 提供了生成密钥、生成JWT、验证JWT的相关函数。 + */ + +use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, Token, UntrustedToken, TimeOptions}; +use serde::{Serialize, Deserialize}; +use chrono::{Duration, Utc}; +use ed25519_dalek::{SigningKey, VerifyingKey}; +use std::fs::File; +use std::io::Write; +use std::{env, fs}; +use std::error::Error; +use rand::{SeedableRng, RngCore}; + +// 定义JWT的Claims结构体(有效载荷) +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CustomClaims { + pub user_id: String, // 用户ID + pub device_ua: String, // 用户UA +} + +pub enum SecretKey { + Signing, // 签名密钥 + Verifying, // 验证密钥 +} + +impl SecretKey { + fn as_string(&self) -> String { + match self { + Self::Signing => String::from("signing"), + Self::Verifying => String::from("verifying"), + } + } +} + +/** + * 生成签名密钥和验证密钥,并将其保存到文件中。 + */ +pub fn generate_key() -> Result<(), Box> { + let mut csprng = rand::rngs::StdRng::from_entropy(); // 使用系统熵创建随机数生成器 + + let mut private_key_bytes = [0u8; 32]; // 存储签名密钥的字节数组 + csprng.fill_bytes(&mut private_key_bytes); // 生成签名密钥 + + let signing_key = SigningKey::from_bytes(&private_key_bytes); // 从签名密钥获取SigningKey + let verifying_key = signing_key.verifying_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()))? + .write_all(signing_key.as_bytes())?; // 保存签名密钥 + File::create(base_path.join(SecretKey::Verifying.as_string()))? + .write_all(verifying_key.as_bytes())?; // 保存验证密钥 + + Ok(()) +} + +/** + * 从文件中读取指定类型的密钥。 + */ +pub fn get_key(key_type: SecretKey) -> Result<[u8; 32], Box> { + let path = env::current_dir()? + .join("assets") + .join("key") + .join(key_type.as_string()); + let key_bytes = fs::read(path)?; // 读取密钥文件 + let mut key = [0u8; 32]; // 固定长度的数组 + key.copy_from_slice(&key_bytes[..32]); // 拷贝前32个字节 + Ok(key) +} + +/** + * 生成JWT,包含自定义声明和有效期。 + */ +pub fn generate_jwt(claims: CustomClaims, duration: Duration) -> Result> { + let key_bytes = get_key(SecretKey::Signing)?; // 从文件读取私钥 + let signing_key = SigningKey::from_bytes(&key_bytes); // 创建SigningKey + + let time_options = TimeOptions::new( + Duration::seconds(0), // 设置时间容差为0 + Utc::now // 使用当前UTC时间作为时钟函数 + ); // 设置时间容差为); // 默认时间选项 + let claims = jwt_compact::Claims::new(claims) // 创建JWT的有效载荷 + .set_duration_and_issuance(&time_options, duration) + .set_not_before(Utc::now()); // 设置不早于时间 + + let header = Header::empty(); // 创建自定义的头部 + + let token = Ed25519.token(&header, &claims, &signing_key)?; // 使用Ed25519签名JWT + + Ok(token) +} + +/** + * 验证JWT并返回自定义声明。 + */ +pub fn validate_jwt(token: &str) -> Result> { + let key_bytes = get_key(SecretKey::Verifying)?; // 从文件读取验证密钥 + let verifying = VerifyingKey::from_bytes(&key_bytes)?; // 创建VerifyingKey + let token = UntrustedToken::new(token)?; // 创建未受信任的Token + let token: Token = Ed25519.validator(&verifying).validate(&token)?; // 验证Token + + let time_options = TimeOptions::new( + Duration::seconds(0), // 设置时间容差为0 + Utc::now // 使用当前UTC时间作为时钟函数 + ); // 设置时间容差为); // 默认时间选项 + token.claims() + .validate_expiration(&time_options)? // 验证过期时间 + .validate_maturity(&time_options)?; // 验证成熟时间 + let claims = token.claims().custom.clone(); // 获取自定义声明 + + Ok(claims) +} \ No newline at end of file diff --git a/frontend/.env b/frontend/.env index 7d49f92..5adf422 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,2 +1,3 @@ -VITE_SOME_KEY = 1 -VITE_APP_SERVER = false \ No newline at end of file +VITE_SERVER_STATUS = false +VITE_SERVER_ADDRESS = "localhost:8000" +VITE_SERVER_TOKEN = "" \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs deleted file mode 100644 index 4f6f59e..0000000 --- a/frontend/.eslintrc.cjs +++ /dev/null @@ -1,84 +0,0 @@ -/** - * This is intended to be a basic starting point for linting in your app. - * It relies on recommended configs out of the box for simplicity, but you can - * and should modify this configuration to best suit your team's needs. - */ - -/** @type {import('eslint').Linter.Config} */ -module.exports = { - root: true, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - ecmaFeatures: { - jsx: true, - }, - }, - env: { - browser: true, - commonjs: true, - es6: true, - }, - ignorePatterns: ["!**/.server", "!**/.client"], - - // Base config - extends: ["eslint:recommended"], - - overrides: [ - // React - { - files: ["**/*.{js,jsx,ts,tsx}"], - plugins: ["react", "jsx-a11y"], - extends: [ - "plugin:react/recommended", - "plugin:react/jsx-runtime", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended", - ], - settings: { - react: { - version: "detect", - }, - formComponents: ["Form"], - linkComponents: [ - { name: "Link", linkAttribute: "to" }, - { name: "NavLink", linkAttribute: "to" }, - ], - "import/resolver": { - typescript: {}, - }, - }, - }, - - // Typescript - { - files: ["**/*.{ts,tsx}"], - plugins: ["@typescript-eslint", "import"], - parser: "@typescript-eslint/parser", - settings: { - "import/internal-regex": "^~/", - "import/resolver": { - node: { - extensions: [".ts", ".tsx"], - }, - typescript: { - alwaysTryTypes: true, - }, - }, - }, - extends: [ - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript", - ], - }, - - // Node - { - files: [".eslintrc.cjs"], - env: { - node: true, - }, - }, - ], -}; diff --git a/frontend/Requirements/extensionRequirement.ts b/frontend/Requirements/extensionRequirement.ts deleted file mode 100644 index 9092c56..0000000 --- a/frontend/Requirements/extensionRequirement.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * File path: types/extensionType.ts - * - * 该文件定义了扩展类型接口 ExtensionType,包含可选的操作、组件和文本生成函数。 - * - * 接口属性说明: - * - action: 可选的操作函数,接受任意参数并返回 void。 - * - component: 可选的组件函数,接受任意参数并返回一个 React 组件。 - * - text: 可选的文本生成函数,接受任意参数并返回一个字符串。 - */ - - -export class ExtensionProps { - - /** 可选的操作函数,接受任意参数并返回 void */ - action?: (...args: any[]) => void; - - /** 可选的组件函数,接受任意参数并返回一个 React 组件 */ - component?: (...args: any[]) => React.FC; - - /** 可选的文本生成函数,接受任意参数并返回一个字符串 */ - text?: (...args: any[]) => string; -} - - diff --git a/frontend/Requirements/templateTypeRequirement.ts b/frontend/Requirements/templateTypeRequirement.ts deleted file mode 100644 index 750dba1..0000000 --- a/frontend/Requirements/templateTypeRequirement.ts +++ /dev/null @@ -1,34 +0,0 @@ -// File path: types/templateType.ts -/** - * 插件配置接口 - * - * 该接口定义了模板的基本配置,包括依赖项、钩子和页面渲染函数。 - */ -import React from "react"; -import { ExtensionProps } from "types/extensionRequirement"; - -export interface TemplateConfig { - /** - * 依赖项配置 - * - * 记录每个依赖字段的名称、描述和是否必填。 - */ - dependencies: Record; - - - extensions?: Record; - - /** - * 页面渲染函数 - * - * 接受参数并返回一个 React 组件。 - */ - page(params: Map): React.FC; -} diff --git a/frontend/app/entry.client.tsx b/frontend/app/entry.client.tsx deleted file mode 100644 index 94d5dc0..0000000 --- a/frontend/app/entry.client.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/** - * By default, Remix will handle hydrating your app on the client for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/file-conventions/entry.client - */ - -import { RemixBrowser } from "@remix-run/react"; -import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; - -startTransition(() => { - hydrateRoot( - document, - - - - ); -}); diff --git a/frontend/app/entry.server.tsx b/frontend/app/entry.server.tsx deleted file mode 100644 index 45db322..0000000 --- a/frontend/app/entry.server.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/** - * By default, Remix will handle generating the HTTP Response for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/file-conventions/entry.server - */ - -import { PassThrough } from "node:stream"; - -import type { AppLoadContext, EntryContext } from "@remix-run/node"; -import { createReadableStreamFromReadable } from "@remix-run/node"; -import { RemixServer } from "@remix-run/react"; -import { isbot } from "isbot"; -import { renderToPipeableStream } from "react-dom/server"; - -const ABORT_DELAY = 5_000; - -export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, - // 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 -) { - return isbot(request.headers.get("user-agent") || "") - ? handleBotRequest( - request, - responseStatusCode, - responseHeaders, - remixContext - ) - : handleBrowserRequest( - request, - responseStatusCode, - responseHeaders, - remixContext - ); -} - -function handleBotRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext -) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - , - { - onAllReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); - - responseHeaders.set("Content-Type", "text/html"); - - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }) - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - } - ); - - setTimeout(abort, ABORT_DELAY); - }); -} - -function handleBrowserRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext -) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - , - { - onShellReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); - - responseHeaders.set("Content-Type", "text/html"); - - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }) - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - } - ); - - setTimeout(abort, ABORT_DELAY); - }); -} diff --git a/frontend/app/env.d.ts b/frontend/app/env.d.ts deleted file mode 100644 index 4553dcc..0000000 --- a/frontend/app/env.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -// File path: app/end.d.ts - -/** - * 配置 - */ - -/// - -interface ImportMetaEnv { - readonly VITE_APP_API: string; // 用于访问API的基础URL - readonly VITE_THEME_PATH: string; // 存储主题文件的目录路径 - readonly VITE_CONTENT_PATH: string; //mark文章存储的位置 - readonly VITE_CONTENT_STATIC_PATH: string; //导出文章静态存储的位置 - readonly VITE_PLUGINS_PATH: string; // 存储插件文件的目录路径 - readonly VITE_ASSETS_PATH: string; // 存储静态资源的目录路径 -} - -interface ImportMeta { - readonly env: ImportMetaEnv -} \ No newline at end of file diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx deleted file mode 100644 index 62596fe..0000000 --- a/frontend/app/root.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react"; -import type { LoaderFunction } from "@remix-run/node"; -import { useLoaderData } from "@remix-run/react"; -import { createContext, useContext, ReactNode } from 'react'; -import { ServiceProvider } from "hooks/servicesProvider"; -import "./tailwind.css"; - -export function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - - ); -} - -export default function App() { - return ( - - - - ); -} \ No newline at end of file diff --git a/frontend/app/routes/_index.tsx b/frontend/app/routes/_index.tsx deleted file mode 100644 index 13a5c00..0000000 --- a/frontend/app/routes/_index.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import type { MetaFunction } from "@remix-run/node"; - -export const meta: MetaFunction = () => { - return [ - { title: "New Remix App" }, - { name: "description", content: "Welcome to Remix!" }, - ]; -}; - -export default function Index() { - return ( -
-
-
-

- Welcome to Remix -

-
- Remix - Remix -
-
- -
-
- ); -} - -const resources = [ - { - href: "https://remix.run/start/quickstart", - text: "Quick Start (5 min)", - icon: ( - - - - ), - }, - { - href: "https://remix.run/start/tutorial", - text: "Tutorial (30 min)", - icon: ( - - - - ), - }, - { - href: "https://remix.run/docs", - text: "Remix Docs", - icon: ( - - - - ), - }, - { - href: "https://rmx.as/discord", - text: "Join Discord", - icon: ( - - - - ), - }, -]; diff --git a/frontend/app/tailwind.css b/frontend/app/tailwind.css deleted file mode 100644 index 303fe15..0000000 --- a/frontend/app/tailwind.css +++ /dev/null @@ -1,12 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -html, -body { - @apply bg-white dark:bg-gray-950; - - @media (prefers-color-scheme: dark) { - color-scheme: dark; - } -} diff --git a/frontend/contracts/capabilityContract.ts b/frontend/contracts/capabilityContract.ts new file mode 100644 index 0000000..ce0aa3b --- /dev/null +++ b/frontend/contracts/capabilityContract.ts @@ -0,0 +1,29 @@ +// src/contracts/CapabilityContract.ts +/** + * 能力契约接口 + */ +export interface CapabilityProps { + // 能力名称 + name: string; + // 能力描述 + description?: string; + // 能力版本 + version: string; + // 能力参数定义 + parameters?: { + type: 'object'; + properties: Record; + }; + // 能力返回值定义 + returns?: { + type: string; + description?: string; + }; + // 能力执行函数 + execute: (...args: any[]) => Promise; + } + \ No newline at end of file diff --git a/frontend/Requirements/generalRequirement.ts b/frontend/contracts/generalContract.ts similarity index 82% rename from frontend/Requirements/generalRequirement.ts rename to frontend/contracts/generalContract.ts index 3a36913..c839311 100644 --- a/frontend/Requirements/generalRequirement.ts +++ b/frontend/contracts/generalContract.ts @@ -1,4 +1,4 @@ -// File path: /d:/data/echoes/frontend/Requirements/generalRequirement.ts +// File path: contracts\generalContract.ts /** * 表示可以序列化的类型。 * 可以是以下类型之一: diff --git a/frontend/Requirements/pluginRequirement.ts b/frontend/contracts/pluginContract.ts similarity index 59% rename from frontend/Requirements/pluginRequirement.ts rename to frontend/contracts/pluginContract.ts index 3d5f365..85c37a9 100644 --- a/frontend/Requirements/pluginRequirement.ts +++ b/frontend/contracts/pluginContract.ts @@ -1,4 +1,4 @@ -// File path: ../Requirements/pluginRequirement.ts +// File path: contracts\pluginContract.ts /** * 插件配置接口 @@ -6,10 +6,9 @@ * 该接口定义了插件的基本配置,包括插件的名称、版本、描述、作者等信息。 * 还包括插件的生命周期钩子和依赖项的配置。 */ -import { SerializeType } from "./generalRequirement"; -import { ExtensionProps } from "types/extensionRequirement"; -import { ExtensionService } from "service/extensionService"; -import { useExtension } from "hooks/servicesProvider"; +import { SerializeType } from "contracts/generalContract"; +import { CapabilityProps } from "contracts/capabilityContract"; + export interface PluginConfig { name: string; // 插件名称 @@ -21,16 +20,16 @@ export interface PluginConfig { icon?: string; // 插件图标URL(可选) managePath?: string; // 插件管理页面路径(可选) configuration?: PluginConfiguration; // 插件配置 - hooks?: { - onInstall?: (context: any) => {}; // 安装时调用的钩子(可选) - onUninstall?: (context: any) => {}; // 卸载时调用的钩子(可选) - onEnable?: (context: any) => {}; // 启用时调用的钩子(可选) - onDisable?: (context: any) => {}; // 禁用时调用的钩子(可选) - }; + /** 能力 */ + capabilities?: Set; routs: Set<{ description?: string; // 路由描述(可选) path: string; // 路由路径 }>; + // 模块初始化函数 + initialize?: () => Promise; + // 模块销毁函数 + destroy?: () => Promise } /** @@ -46,14 +45,3 @@ export interface PluginConfiguration { }; } - -/** - * 插件属性接口 - * - * 该接口定义了插件的属性和行为。 - */ -export class usePluginProps extends ExtensionProps { - -} - - diff --git a/frontend/contracts/templateContract.ts b/frontend/contracts/templateContract.ts new file mode 100644 index 0000000..e5457ae --- /dev/null +++ b/frontend/contracts/templateContract.ts @@ -0,0 +1,35 @@ +export interface TemplateContract { + // 模板名称 + name: string; + // 模板描述 + description?: string; + // 模板配置 + config: { + // 模板布局 + layout?: string; + // 模板样式 + styles?: string[]; + // 模板脚本 + scripts?: string[]; + // 模板区域定义 + zones?: Record; + }; + // 模板数据契约 + dataContract?: { + // 必需的数据字段 + required: string[]; + // 可选的数据字段 + optional?: string[]; + // 数据验证规则 + validation?: Record; + }; + // 渲染函数 + render: (props: any) => React.ReactNode; + } \ No newline at end of file diff --git a/frontend/Requirements/themeTypeRequirement.ts b/frontend/contracts/themeContract.ts similarity index 87% rename from frontend/Requirements/themeTypeRequirement.ts rename to frontend/contracts/themeContract.ts index efb9bb6..598bac8 100644 --- a/frontend/Requirements/themeTypeRequirement.ts +++ b/frontend/contracts/themeContract.ts @@ -1,4 +1,4 @@ -// File path: types/themeType.ts +// File path: contracts\themeTypeContract.ts /** * 主题配置和模板接口定义文件 * 该文件包含主题配置接口和主题模板接口的定义,用于主题管理和渲染。 @@ -8,7 +8,8 @@ * 主题配置接口 * 定义主题的基本信息、模板、全局配置、依赖、钩子和路由。 */ -import { SerializeType } from "./generalRequirement"; +import { CapabilityProps } from "contracts/capabilityContract"; +import { SerializeType } from "contracts/generalContract"; export interface ThemeConfig { name: string; // 主题的名称 displayName: string; // 主题的显示名称 @@ -33,11 +34,9 @@ export interface ThemeConfig { plugins?: string[]; // 主题所依赖的插件列表 assets?: string[]; // 主题所依赖的资源列表 }; - /** 钩子 */ - hooks?: { - onActivate?: () => {}; // 主题激活时执行的钩子 - onDeactivate?: () => {}; // 主题停用时执行的钩子 - }; + /** 能力 */ + capabilities?: Set; + /** 路由 */ routes: { index: string; // 首页使用的模板 diff --git a/frontend/hooks/servicesProvider.tsx b/frontend/hooks/servicesProvider.tsx index 9c1feb8..9fc38e5 100644 --- a/frontend/hooks/servicesProvider.tsx +++ b/frontend/hooks/servicesProvider.tsx @@ -1,5 +1,5 @@ -import { ExtensionService } from 'service/extensionService'; -import { ThemeService } from 'service/themeService'; +import { ExtensionService } from 'services/extensionService'; +import { ThemeService } from 'services/themeService'; import { createServiceContext } from './createServiceContext'; import { ReactNode } from 'react'; diff --git a/frontend/package.json b/frontend/package.json index 65cd9f3..fe9796d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,43 +1,23 @@ { "name": "frontend", - "private": true, - "sideEffects": false, - "type": "module", + "version": "1.0.0", + "main": "index.js", "scripts": { - "build": "remix vite:build", - "dev": "remix vite:dev", - "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", - "start": "remix-serve ./build/server/index.js", - "typecheck": "tsc" + "test": "echo \"Error: no test specified\" && exit 1" }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", "dependencies": { - "@remix-run/node": "^2.14.0", - "@remix-run/react": "^2.14.0", - "@remix-run/serve": "^2.14.0", - "isbot": "^4.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" }, "devDependencies": { - "@remix-run/dev": "^2.14.0", - "@types/react": "^18.2.20", - "@types/react-dom": "^18.2.7", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", - "autoprefixer": "^10.4.19", - "eslint": "^8.38.0", - "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", - "tailwindcss": "^3.4.4", - "typescript": "^5.1.6", - "vite": "^5.1.0", - "vite-tsconfig-paths": "^4.2.1" - }, - "engines": { - "node": ">=20.0.0" + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@types/react-router-dom": "^5.3.3", + "typescript": "^5.6.3" } -} \ No newline at end of file +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index 2aa7205..0000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index 8830cf6..0000000 Binary files a/frontend/public/favicon.ico and /dev/null differ diff --git a/frontend/public/logo-dark.png b/frontend/public/logo-dark.png deleted file mode 100644 index b24c7ae..0000000 Binary files a/frontend/public/logo-dark.png and /dev/null differ diff --git a/frontend/public/logo-light.png b/frontend/public/logo-light.png deleted file mode 100644 index 4490ae7..0000000 Binary files a/frontend/public/logo-light.png and /dev/null differ diff --git a/frontend/service/pluginService.ts b/frontend/service/pluginService.ts deleted file mode 100644 index 6ea7f9e..0000000 --- a/frontend/service/pluginService.ts +++ /dev/null @@ -1,34 +0,0 @@ -// File path: /service/pluginService.ts - -/** - * 插件服务类,采用单例模式管理插件组件。 - * 提供获取插件实例的方法,并缓存插件组件信息。 - */ - -import { PluginConfiguration } from "types/pluginRequirement"; -export class PluginService { - /** 单例实例 */ - private static pluginInstance: PluginService | null = null; - /** 插件组件缓存 */ - private pluginComponents: Map> = new Map(); - - /** - * 私有构造函数,防止外部实例化。 - */ - private constructor() {}; - - /** - * 获取插件服务的单例实例。 - * @returns {PluginService} 插件服务实例 - */ - public static getInstance(): PluginService { - if (!this.pluginInstance) { - this.pluginInstance = new PluginService(); - } - return this.pluginInstance; - } -} diff --git a/frontend/service/extensionService.ts b/frontend/services/extensionService.ts similarity index 100% rename from frontend/service/extensionService.ts rename to frontend/services/extensionService.ts diff --git a/frontend/services/pluginService.ts b/frontend/services/pluginService.ts new file mode 100644 index 0000000..7098c7e --- /dev/null +++ b/frontend/services/pluginService.ts @@ -0,0 +1,64 @@ +// src/core/PluginManager.ts +import { PluginConfiguration } from 'types/pluginRequirement'; +import { Contracts } from 'contracts/capabilityContract'; + +export class PluginManager { + private plugins: Map = new Map(); + private configurations: Map = new Map(); + private extensions: Map = new Map(); + + async loadPlugins() { + // 扫描插件目录 + const pluginDirs = await this.scanPluginDirectory(); + + for (const dir of pluginDirs) { + try { + const config = await import(`@/plugins/${dir}/plugin.config.ts`); + const plugin: PluginProps = config.default; + + // 注册插件 + this.plugins.set(plugin.name, plugin); + + // 加载默认配置 + if (plugin.settingsSchema) { + this.configurations.set(plugin.name, plugin.settingsSchema); + } + + // 注册扩展 + if (plugin.extensions) { + Object.entries(plugin.extensions).forEach(([key, value]) => { + this.extensions.set(`${plugin.name}.${key}`, value.extension); + }); + } + + // 执行安装钩子 + if (plugin.hooks?.onInstall) { + await plugin.hooks.onInstall({}); + } + } catch (error) { + console.error(`Failed to load plugin from directory ${dir}:`, error); + } + } + } + + // 获取插件配置 + async getPluginConfig(pluginName: string): Promise { + // 先尝试从数据库获取 + const dbConfig = await this.fetchConfigFromDB(pluginName); + if (dbConfig) { + return dbConfig; + } + // 返回默认配置 + return this.configurations.get(pluginName); + } + + private async fetchConfigFromDB(pluginName: string) { + // 实现数据库查询逻辑 + return null; + } + + private async scanPluginDirectory(): Promise { + // 实现插件目录扫描逻辑 + return []; + } +} \ No newline at end of file diff --git a/frontend/service/themeService.ts b/frontend/services/themeService.ts similarity index 96% rename from frontend/service/themeService.ts rename to frontend/services/themeService.ts index e1f0ec2..4f4f1d5 100644 --- a/frontend/service/themeService.ts +++ b/frontend/services/themeService.ts @@ -1,5 +1,5 @@ // service/theme/themeService.ts -import type { ThemeConfig } from 'types/themeTypeRequirement'; +import { ThemeConfig } from "contracts/themeContract" export class ThemeService { private static themeInstance: ThemeService; // 单例实例 diff --git a/frontend/src/a.ts b/frontend/src/a.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts deleted file mode 100644 index 5f06ad4..0000000 --- a/frontend/tailwind.config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Config } from "tailwindcss"; - -export default { - content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"], - theme: { - extend: { - fontFamily: { - sans: [ - "Inter", - "ui-sans-serif", - "system-ui", - "sans-serif", - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", - "Noto Color Emoji", - ], - }, - }, - }, - plugins: [], -} satisfies Config; diff --git a/frontend/theme/default/theme.config.ts b/frontend/themes/default/theme.config.ts similarity index 100% rename from frontend/theme/default/theme.config.ts rename to frontend/themes/default/theme.config.ts diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index d7c93bf..f3107c8 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,35 +1,19 @@ { - "include": [ - "**/*.ts", - "**/*.tsx", - "**/.server/**/*.ts", - "**/.server/**/*.tsx", - "**/.client/**/*.ts", - "**/.client/**/*.tsx" - ], "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["@remix-run/node", "vite/client"], - "isolatedModules": true, - "esModuleInterop": true, - "jsx": "react-jsx", - "module": "ESNext", - "moduleResolution": "Bundler", - "resolveJsonModule": true, - "target": "ES2022", - "strict": true, - "allowJs": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": ".", - "paths": { - "theme/*":["./theme/*"], - "types/*":["Requirements/*"], - "service/*":["./service/*"], - "hooks/*":["./hooks/*"], - }, - - // Vite takes care of building everything, not tsc. - "noEmit": true - } + "target": "es6", // 编译到 ES5 + "lib": ["dom", "dom.iterable", "esnext"], // 指定要编译的库 + "allowJs": true, // 允许 JavaScript 文件 + "skipLibCheck": true, // 跳过库检查 + "esModuleInterop": true, // 支持 CommonJS 模块 + "allowSyntheticDefaultImports": true, // 允许导入默认值 + "strict": true, // 启用严格模式 + "forceConsistentCasingInFileNames": true, // 强制文件名大小写一致 + "module": "esnext", // 使用 ES 模块 + "moduleResolution": "node", // 模块解析策略 + "resolveJsonModule": true, // 允许导入 JSON 模块 + "isolatedModules": true, // 每个文件单独编译 + "noEmit": true // 不输出任何文件,只检查类型 + }, + "include": ["src/**/*"], // 包含 src 文件夹中的所有 TypeScript 文件 + "exclude": ["node_modules"] // 排除 node_modules 文件夹 } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts deleted file mode 100644 index e4e8cef..0000000 --- a/frontend/vite.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { vitePlugin as remix } from "@remix-run/dev"; -import { defineConfig } from "vite"; -import tsconfigPaths from "vite-tsconfig-paths"; - -declare module "@remix-run/node" { - interface Future { - v3_singleFetch: true; - } -} - -export default defineConfig({ - plugins: [ - remix({ - future: { - v3_fetcherPersist: true, - v3_relativeSplatPath: true, - v3_throwAbortReason: true, - v3_singleFetch: true, - v3_lazyRouteDiscovery: true, - }, - }), - tsconfigPaths(), - ], -});