diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 698455c..8e9b54f 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,8 +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" -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 +chrono = "0.4" +regex = "1.11.1" \ No newline at end of file diff --git a/backend/assets/config.toml b/backend/assets/config.toml index 5b1d688..6c5aaca 100644 --- a/backend/assets/config.toml +++ b/backend/assets/config.toml @@ -1,16 +1,11 @@ -# config/config.toml -# 配置文件 - -# 信息 [info] -install = false # 是否为第一次安装 -non_relational = true # 是否使用非关系型数据库 +install = true +non_relational = false -# 关系型数据库 [sql_config] -db_type = "postgresql" # 数据库类型 -address = "localhost" # 地址 -port = 5432 # 端口 -user = "postgres" # 用户名 -password = "postgres" # 密码 -db_name = "echoes" # 数据库 +db_type = "postgresql" +address = "localhost" +port = 5432 +user = "postgres" +password = "postgres" +db_name = "echoes" diff --git a/backend/src/secret.rs b/backend/src/auth/jwt.rs similarity index 100% rename from backend/src/secret.rs rename to backend/src/auth/jwt.rs diff --git a/backend/src/auth/mod.rs b/backend/src/auth/mod.rs new file mode 100644 index 0000000..6dbefcf --- /dev/null +++ b/backend/src/auth/mod.rs @@ -0,0 +1 @@ +pub mod jwt; \ No newline at end of file diff --git a/backend/src/config.rs b/backend/src/config.rs index 3194ae6..9312fbe 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -1,19 +1,20 @@ -use serde::Deserialize; +use serde::{Deserialize,Serialize}; use std::{ env, fs}; +use std::path::PathBuf; -#[derive(Deserialize,Debug,Clone)] +#[derive(Deserialize,Serialize,Debug,Clone)] pub struct Config { pub info: Info, pub sql_config: SqlConfig, } -#[derive(Deserialize,Debug,Clone)] +#[derive(Deserialize,Serialize,Debug,Clone,)] pub struct Info { pub install: bool, pub non_relational: bool, } -#[derive(Deserialize,Debug,Clone)] +#[derive(Deserialize,Serialize,Debug,Clone)] pub struct SqlConfig { pub db_type: String, pub address: String, @@ -23,7 +24,7 @@ pub struct SqlConfig { pub db_name: String, } -#[derive(Deserialize,Debug,Clone)] +#[derive(Deserialize,Serialize,Debug,Clone)] pub struct NoSqlConfig { pub db_type: String, pub address: String, @@ -35,9 +36,18 @@ pub struct NoSqlConfig { impl Config { pub fn read() -> Result> { - let path = env::current_dir()? - .join("assets") - .join("config.toml"); + let path=Self::get_path()?; Ok(toml::from_str(&fs::read_to_string(path)?)?) } + pub fn write(config:Config) -> Result<(), Box> { + let path=Self::get_path()?; + fs::write(path, toml::to_string(&config)?)?; + Ok(()) + } + + pub fn get_path() -> Result> { + Ok(env::current_dir()? + .join("assets") + .join("config.toml")) + } } diff --git a/backend/src/database/relational/builder.rs b/backend/src/database/relational/builder.rs new file mode 100644 index 0000000..a9f6e13 --- /dev/null +++ b/backend/src/database/relational/builder.rs @@ -0,0 +1,279 @@ +use regex::Regex; +use std::collections::HashMap; +use super::DatabaseError; + + +use std::hash::Hash; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ValidatedValue { + Identifier(String), + RichText(String), + PlainText(String), +} + +impl ValidatedValue { + pub fn new_identifier(value: String) -> Result { + let valid_pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]{0,63}$").unwrap(); + if !valid_pattern.is_match(&value) { + return Err(DatabaseError::ValidationError( + "Invalid identifier format".to_string(), + )); + } + Ok(ValidatedValue::Identifier(value)) + } + + pub fn new_rich_text(value: String) -> Result { + let dangerous_patterns = [ + "UNION ALL SELECT", + "UNION SELECT", + "OR 1=1", + "OR '1'='1", + "DROP TABLE", + "DELETE FROM", + "UPDATE ", + "INSERT INTO", + "--", + "/*", + "*/", + "@@", + ]; + + let value_upper = value.to_uppercase(); + for pattern in dangerous_patterns.iter() { + if value_upper.contains(&pattern.to_uppercase()) { + return Err(DatabaseError::SqlInjectionAttempt( + format!("Dangerous SQL pattern detected: {}", pattern) + )); + } + } + Ok(ValidatedValue::RichText(value)) + } + + pub fn new_plain_text(value: String) -> Result { + if value.contains(';') || value.contains("--") { + return Err(DatabaseError::ValidationError("Invalid characters in text".to_string())); + } + Ok(ValidatedValue::PlainText(value)) + } + + pub fn get(&self) -> &str { + match self { + ValidatedValue::Identifier(s) | ValidatedValue::RichText(s) | ValidatedValue::PlainText(s) => s, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SqlOperation { + Select, + Insert, + Update, + Delete, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Operator { + Eq, + Ne, + Gt, + Lt, + Gte, + Lte, + Like, + In, + IsNull, + IsNotNull, +} + +impl Operator { + fn as_str(&self) -> &'static str { + match self { + Operator::Eq => "=", + Operator::Ne => "!=", + Operator::Gt => ">", + Operator::Lt => "<", + Operator::Gte => ">=", + Operator::Lte => "<=", + Operator::Like => "LIKE", + Operator::In => "IN", + Operator::IsNull => "IS NULL", + Operator::IsNotNull => "IS NOT NULL", + } + } +} + +#[derive(Debug, Clone)] +pub struct WhereCondition { + field: ValidatedValue, + operator: Operator, + value: Option, +} + +impl WhereCondition { + pub fn new( + field: String, + operator: Operator, + value: Option, + ) -> Result { + let field = ValidatedValue::new_identifier(field)?; + + let value = match value { + Some(v) => Some(match operator { + Operator::Like => ValidatedValue::new_plain_text(v)?, + _ => ValidatedValue::new_plain_text(v)?, + }), + None => None, + }; + + Ok(WhereCondition { + field, + operator, + value, + }) + } +} + +#[derive(Debug, Clone)] +pub enum WhereClause { + And(Vec), + Or(Vec), + Condition(WhereCondition), +} + +pub struct QueryBuilder { + operation: SqlOperation, + table: ValidatedValue, + fields: Vec, + params: HashMap, + where_clause: Option, + order_by: Option, + limit: Option, +} + +impl QueryBuilder { + pub fn new(operation: SqlOperation, table: String) -> Result { + Ok(QueryBuilder { + operation, + table: ValidatedValue::new_identifier(table)?, + fields: Vec::new(), + params: HashMap::new(), + where_clause: None, + order_by: None, + limit: None, + }) + } + + pub fn build(&self) -> Result<(String, Vec), DatabaseError> { + let mut query = String::new(); + let mut values = Vec::new(); + let mut param_counter = 1; + + match self.operation { + SqlOperation::Select => { + let fields = if self.fields.is_empty() { + "*".to_string() + } else { + self.fields.iter() + .map(|f| f.get().to_string()) + .collect::>() + .join(", ") + }; + query.push_str(&format!("SELECT {} FROM {}", fields, self.table.get())); + } + SqlOperation::Insert => { + let fields: Vec = self.params.keys() + .map(|k| k.get().to_string()) + .collect(); + let placeholders: Vec = (1..=self.params.len()) + .map(|i| format!("${}", i)) + .collect(); + + query.push_str(&format!( + "INSERT INTO {} ({}) VALUES ({})", + self.table.get(), + fields.join(", "), + placeholders.join(", ") + )); + + values.extend(self.params.values().map(|v| v.get().to_string())); + return Ok((query, values)); + } + SqlOperation::Update => { + query.push_str(&format!("UPDATE {} SET ", self.table.get())); + let set_clauses: Vec = self.params + .iter() + .map(|(key, _)| { + let placeholder = format!("${}", param_counter); + values.push(self.params[key].get().to_string()); + param_counter += 1; + format!("{} = {}", key.get(), placeholder) + }) + .collect(); + query.push_str(&set_clauses.join(", ")); + } + SqlOperation::Delete => { + query.push_str(&format!("DELETE FROM {}", self.table.get())); + } + } + + if let Some(where_clause) = &self.where_clause { + query.push_str(" WHERE "); + let (where_sql, where_values) = self.build_where_clause(where_clause, param_counter)?; + query.push_str(&where_sql); + values.extend(where_values); + } + + if let Some(order) = &self.order_by { + query.push_str(&format!(" ORDER BY {}", order.get())); + } + + if let Some(limit) = self.limit { + query.push_str(&format!(" LIMIT {}", limit)); + } + + Ok((query, values)) + } + + fn build_where_clause( + &self, + clause: &WhereClause, + mut param_counter: i32, + ) -> Result<(String, Vec), DatabaseError> { + let mut values = Vec::new(); + + let sql = match clause { + WhereClause::And(conditions) => { + let mut parts = Vec::new(); + for condition in conditions { + let (sql, mut vals) = self.build_where_clause(condition, param_counter)?; + param_counter += vals.len() as i32; + parts.push(sql); + values.append(&mut vals); + } + format!("({})", parts.join(" AND ")) + } + WhereClause::Or(conditions) => { + let mut parts = Vec::new(); + for condition in conditions { + let (sql, mut vals) = self.build_where_clause(condition, param_counter)?; + param_counter += vals.len() as i32; + parts.push(sql); + values.append(&mut vals); + } + format!("({})", parts.join(" OR ")) + } + WhereClause::Condition(cond) => { + if let Some(value) = &cond.value { + let placeholder = format!("${}", param_counter); + values.push(value.get().to_string()); + format!("{} {} {}", cond.field.get(), cond.operator.as_str(), placeholder) + } else { + format!("{} {}", cond.field.get(), cond.operator.as_str()) + } + } + }; + + Ok((sql, values)) + } +} \ No newline at end of file diff --git a/backend/src/database/relational/mod.rs b/backend/src/database/relational/mod.rs index 4f44484..be46e9b 100644 --- a/backend/src/database/relational/mod.rs +++ b/backend/src/database/relational/mod.rs @@ -1,36 +1,46 @@ mod postgresql; -use std::collections::HashMap; use crate::config; use async_trait::async_trait; +use std::collections::HashMap; use std::error::Error; use std::sync::Arc; +use std::fmt; +pub mod builder; -#[derive(Debug, Clone, PartialEq)] -pub enum SqlOperation { - Select, - Insert, - Update, - Delete, +#[derive(Debug)] +pub enum DatabaseError { + ValidationError(String), + SqlInjectionAttempt(String), + InvalidParameter(String), + ExecutionError(String), } -pub struct QueryBuilder { - operation: SqlOperation, - table: String, - fields: Vec, - params: HashMap, - where_conditions: HashMap, - order_by: Option, - limit: Option, +impl fmt::Display for DatabaseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DatabaseError::ValidationError(msg) => write!(f, "Validation error: {}", msg), + DatabaseError::SqlInjectionAttempt(msg) => write!(f, "SQL injection attempt: {}", msg), + DatabaseError::InvalidParameter(msg) => write!(f, "Invalid parameter: {}", msg), + DatabaseError::ExecutionError(msg) => write!(f, "Execution error: {}", msg), + } + } } +impl Error for DatabaseError {} + + #[async_trait] pub trait DatabaseTrait: Send + Sync { - async fn connect(database: config::SqlConfig) -> Result> where Self: Sized; + async fn connect(database: config::SqlConfig) -> Result> + where + Self: Sized; async fn execute_query<'a>( &'a self, - builder: &QueryBuilder, + builder: &builder::QueryBuilder, ) -> Result>, Box>; - async fn initialization(database: config::SqlConfig) -> Result<(), Box> where Self: Sized; + async fn initialization(database: config::SqlConfig) -> Result<(), Box> + where + Self: Sized; } #[derive(Clone)] @@ -42,14 +52,16 @@ impl Database { pub fn get_db(&self) -> &Box { &self.db } - + pub async fn link(database: config::SqlConfig) -> Result> { let db = match database.db_type.as_str() { "postgresql" => postgresql::Postgresql::connect(database).await?, _ => return Err("unknown database type".into()), }; - Ok(Self { db: Arc::new(Box::new(db)) }) + Ok(Self { + db: Arc::new(Box::new(db)), + }) } pub async fn initial_setup(database: config::SqlConfig) -> Result<(), Box> { @@ -60,148 +72,3 @@ impl Database { Ok(()) } } - -impl QueryBuilder { - pub fn new(operation: SqlOperation, table: &str) -> Self { - QueryBuilder { - operation, - table: table.to_string(), - fields: Vec::new(), - params: HashMap::new(), - where_conditions: HashMap::new(), - order_by: None, - limit: None, - } - } - - pub fn build(&self) -> (String, Vec) { - let mut query = String::new(); - let mut values = Vec::new(); - let mut param_counter = 1; - - match self.operation { - SqlOperation::Select => { - let fields = if self.fields.is_empty() { - "*".to_string() - } else { - self.fields.join(", ") - }; - - query.push_str(&format!("SELECT {} FROM {}", fields, self.table)); - - if !self.where_conditions.is_empty() { - let conditions: Vec = self.where_conditions - .iter() - .map(|(key, _)| { - let placeholder = format!("${}", param_counter); - values.push(self.where_conditions[key].clone()); - param_counter += 1; - format!("{} = {}", key, placeholder) - }) - .collect(); - query.push_str(" WHERE "); - query.push_str(&conditions.join(" AND ")); - } - }, - SqlOperation::Insert => { - let fields: Vec = self.params.keys().cloned().collect(); - let placeholders: Vec = (1..=self.params.len()) - .map(|i| format!("${}", i)) - .collect(); - - query.push_str(&format!( - "INSERT INTO {} ({}) VALUES ({})", - self.table, - fields.join(", "), - placeholders.join(", ") - )); - - for field in fields { - values.push(self.params[&field].clone()); - } - }, - SqlOperation::Update => { - query.push_str(&format!("UPDATE {}", self.table)); - - let set_clauses: Vec = self.params - .keys() - .map(|key| { - let placeholder = format!("${}", param_counter); - values.push(self.params[key].clone()); - param_counter += 1; - format!("{} = {}", key, placeholder) - }) - .collect(); - - query.push_str(" SET "); - query.push_str(&set_clauses.join(", ")); - - if !self.where_conditions.is_empty() { - let conditions: Vec = self.where_conditions - .iter() - .map(|(key, _)| { - let placeholder = format!("${}", param_counter); - values.push(self.where_conditions[key].clone()); - param_counter += 1; - format!("{} = {}", key, placeholder) - }) - .collect(); - query.push_str(" WHERE "); - query.push_str(&conditions.join(" AND ")); - } - }, - SqlOperation::Delete => { - query.push_str(&format!("DELETE FROM {}", self.table)); - - if !self.where_conditions.is_empty() { - let conditions: Vec = self.where_conditions - .iter() - .map(|(key, _)| { - let placeholder = format!("${}", param_counter); - values.push(self.where_conditions[key].clone()); - param_counter += 1; - format!("{} = {}", key, placeholder) - }) - .collect(); - query.push_str(" WHERE "); - query.push_str(&conditions.join(" AND ")); - } - } - } - - if let Some(order) = &self.order_by { - query.push_str(&format!(" ORDER BY {}", order)); - } - - if let Some(limit) = self.limit { - query.push_str(&format!(" LIMIT {}", limit)); - } - - (query, values) - } - - pub fn fields(&mut self, fields: Vec) -> &mut Self { - self.fields = fields; - self - } - - pub fn params(&mut self, params: HashMap) -> &mut Self { - self.params = params; - self - } - - pub fn where_conditions(&mut self, conditions: HashMap) -> &mut Self { - self.where_conditions = conditions; - self - } - - pub fn order_by(&mut self, order: &str) -> &mut Self { - self.order_by = Some(order.to_string()); - self - } - - pub fn limit(&mut self, limit: i32) -> &mut Self { - self.limit = Some(limit); - self - } -} \ No newline at end of file diff --git a/backend/src/database/relational/postgresql/init.sql b/backend/src/database/relational/postgresql/init.sql index 1583a83..43a5201 100644 --- a/backend/src/database/relational/postgresql/init.sql +++ b/backend/src/database/relational/postgresql/init.sql @@ -3,12 +3,12 @@ CREATE TYPE privilege_level AS ENUM ( 'contributor', 'administrators'); CREATE TABLE persons ( person_name VARCHAR(100) PRIMARY KEY, + person_avatar VARCHAR(255), person_email VARCHAR(255) UNIQUE NOT NULL, person_icon VARCHAR(255), person_password VARCHAR(255) NOT NULL, person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - person_avatar VARCHAR(255), person_role VARCHAR(50), person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP, person_level privilege_level NOT NULL DEFAULT 'contributor' diff --git a/backend/src/database/relational/postgresql/mod.rs b/backend/src/database/relational/postgresql/mod.rs index f0dc17b..29b18fe 100644 --- a/backend/src/database/relational/postgresql/mod.rs +++ b/backend/src/database/relational/postgresql/mod.rs @@ -1,4 +1,4 @@ -use super::{DatabaseTrait, QueryBuilder}; +use super::{DatabaseTrait,builder}; use crate::config; use async_trait::async_trait; use sqlx::{Column, PgPool, Row, Executor}; @@ -49,9 +49,9 @@ impl DatabaseTrait for Postgresql { async fn execute_query<'a>( &'a self, - builder: &QueryBuilder, + builder: &builder::QueryBuilder, ) -> Result>, Box> { - let (query, values) = builder.build(); + let (query, values) = builder.build()?; let mut sqlx_query = sqlx::query(&query); diff --git a/backend/src/main.rs b/backend/src/main.rs index 35746c8..e8464d3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,73 +1,104 @@ mod config; mod database; -mod secret; - +mod auth; +mod utils; +mod routes; use chrono::Duration; use database::relational; -use once_cell::sync::Lazy; -use rocket::{get, post, http::Status, launch, response::status, routes, serde::json::Json}; +use rocket::{ + get, post, + http::Status, + launch, + response::status, + State, +}; use std::sync::Arc; use tokio::sync::Mutex; -static SQL: Lazy>>> = - Lazy::new(|| Arc::new(Mutex::new(None))); - -async fn init_sql(database: config::SqlConfig) -> Result<(), Box> { - let database = relational::Database::link(database).await?; - *SQL.lock().await = Some(database); - Ok(()) +#[derive(Debug)] +pub enum AppError { + Database(String), + Config(String), + Auth(String), } -async fn get_sql() -> Result> { - SQL.lock() - .await - .clone() - .ok_or_else(|| "Database not initialized".into()) +impl From for status::Custom { + fn from(error: AppError) -> Self { + match error { + AppError::Database(msg) => status::Custom(Status::InternalServerError, format!("Database error: {}", msg)), + AppError::Config(msg) => status::Custom(Status::InternalServerError, format!("Config error: {}", msg)), + AppError::Auth(msg) => status::Custom(Status::InternalServerError, format!("Auth error: {}", msg)), + } + } } -#[post("/install", format = "json", data = "")] -async fn install(data: Json) -> Result, status::Custom> { - relational::Database::initial_setup(data.into_inner()).await.map_err(|e| { - status::Custom( - Status::InternalServerError, - format!("Database initialization failed: {}", e), - ) - })?; - - Ok(status::Custom( - Status::Ok, - format!("Initialization successful"), - )) +type AppResult = Result; + +struct AppState { + db: Arc>>, + configure: Arc>, } +impl AppState { + async fn get_sql(&self) -> AppResult { + self.db + .lock() + .await + .clone() + .ok_or_else(|| AppError::Database("Database not initialized".into())) + } + + async fn link_sql(&self, config: config::SqlConfig) -> AppResult<()> { + let database = relational::Database::link(config) + .await + .map_err(|e| AppError::Database(e.to_string()))?; + *self.db.lock().await = Some(database); + Ok(()) + } + +} + + + #[get("/system")] -async fn token_system() -> Result, status::Custom> { - let claims = secret::CustomClaims { - user_id: String::from("system"), - device_ua: String::from("system"), +async fn token_system(_state: &State) -> Result, status::Custom> { + let claims = auth::jwt::CustomClaims { + user_id: "system".into(), + device_ua: "system".into(), }; - let token = secret::generate_jwt(claims, Duration::seconds(1)).map_err(|e| { - status::Custom( - Status::InternalServerError, - format!("JWT generation failed: {}", e), - ) - })?; - Ok(status::Custom(Status::Ok, token)) + auth::jwt::generate_jwt(claims, Duration::seconds(1)) + .map(|token| status::Custom(Status::Ok, token)) + .map_err(|e| AppError::Auth(e.to_string()).into()) } + #[launch] async fn rocket() -> _ { 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())), + }; + + let mut rocket_builder = rocket::build().manage(state); if config.info.install { - init_sql(config.sql_config) - .await - .expect("Failed to connect to database"); - rocket::build() - .mount("/auth/token", routes![token_system]) - } else { - rocket::build() - .mount("/", routes![install]) + if let Some(state) = rocket_builder.state::() { + state.link_sql(config.sql_config.clone()) + .await + .expect("Failed to connect to database"); + } } -} + + if ! config.info.install { + rocket_builder = rocket_builder + .mount("/", rocket::routes![routes::install]); + } + + rocket_builder = rocket_builder + .mount("/auth/token", routes![token_system]); + + rocket_builder +} \ No newline at end of file diff --git a/backend/src/routes/intsall.rs b/backend/src/routes/intsall.rs new file mode 100644 index 0000000..65ad73b --- /dev/null +++ b/backend/src/routes/intsall.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize,Serialize}; +use crate::{config,utils}; +use crate::database::{relational,relational::builder}; +use crate::{AppState,AppError,AppResult}; +use rocket::{ + get, post, + http::Status, + response::status, + serde::json::Json, + State, +}; +use std::collections::HashMap; + + +#[derive(Deserialize, Serialize)] +struct InstallData{ + name:String, + email:String, + password:String, + sql_config: config::SqlConfig + +} +#[derive(Deserialize, Serialize)] +struct InstallReplyData{ + token:String, + name:String, + password:String, +} + + +#[post("/install", format = "application/json", data = "")] +async fn install( + data: Json, + state: &State +) -> AppResult>, AppError> { + let mut config = state.configure.lock().await; + if config.info.install { + return Err(AppError::Database("Database already initialized".to_string())); + } + + let data=data.into_inner(); + + relational::Database::initial_setup(data.sql_config.clone()) + .await + .map_err(|e| AppError::Database(e.to_string()))?; + + config.info.install = true; + + state.link_sql(data.sql_config.clone()); + let sql= state.get_sql() + .await? + .get_db(); + + + let system_name=utils::generate_random_string(20); + let system_password=utils::generate_random_string(20); + + let mut builder = builder::QueryBuilder::new(builder::SqlOperation::Insert,String::from("persons"))?; + + let user_params=HashMap::new(); + user_params.insert("person_name", data.name); + user_params.insert("person_email", data.email); + user_params.insert("person_password", data.password); + user_params.insert("person_role", data.name); + + + + config::Config::write(config.clone()) + .map_err(|e| AppError::Config(e.to_string()))?; + Ok() +} \ No newline at end of file diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs new file mode 100644 index 0000000..d661ca0 --- /dev/null +++ b/backend/src/routes/mod.rs @@ -0,0 +1,6 @@ +mod intsall; +use rocket::routes; + +pub fn create_routes() -> routes { + routes!["/", intsall::install] +} \ No newline at end of file diff --git a/backend/src/utils.rs b/backend/src/utils.rs new file mode 100644 index 0000000..de34532 --- /dev/null +++ b/backend/src/utils.rs @@ -0,0 +1,9 @@ +use rand::seq::SliceRandom; + +pub fn generate_random_string(length: usize) -> String { + let charset = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let mut rng = rand::thread_rng(); + (0..length) + .map(|_| *charset.choose(&mut rng).unwrap() as char) + .collect() +} \ No newline at end of file diff --git a/frontend/components/ErrorBoundary.tsx b/frontend/components/ErrorBoundary.tsx deleted file mode 100644 index 0af1cf9..0000000 --- a/frontend/components/ErrorBoundary.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { Component, ErrorInfo, ReactNode } from 'react'; -import { ThemeService } from '../services/themeService'; - -interface Props { - children?: ReactNode; -} - -interface State { - hasError: boolean; - error?: Error; -} - -export default class ErrorBoundary extends Component { - public state: State = { - hasError: false - }; - - public static getDerivedStateFromError(error: Error): State { - return { hasError: true, error }; - } - - public componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('Uncaught error:', error, errorInfo); - } - - public render() { - if (this.state.hasError) { - const themeService = ThemeService.getInstance(); - try { - const errorTemplate = themeService.getTemplate('error'); - return
; - } catch (e) { - return ( -
-

Something went wrong

-

{this.state.error?.message}

- -
- ); - } - } - - return this.props.children; - } -} diff --git a/frontend/components/LoadingBoundary.tsx b/frontend/components/LoadingBoundary.tsx deleted file mode 100644 index 21919dc..0000000 --- a/frontend/components/LoadingBoundary.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Suspense } from 'react'; - -interface LoadingBoundaryProps { - children: React.ReactNode; - fallback?: React.ReactNode; -} - -export const LoadingBoundary: React.FC = ({ - children, - fallback =
Loading...
-}) => { - return ( - - {children} - - ); -}; diff --git a/frontend/contracts/themeContract.ts b/frontend/contracts/themeContract.ts index ac08ebd..5104cc7 100644 --- a/frontend/contracts/themeContract.ts +++ b/frontend/contracts/themeContract.ts @@ -18,6 +18,7 @@ export interface ThemeConfig { tag: string; category: string; error: string; + loding: string; page: Map; } } diff --git a/frontend/services/routeManager.ts b/frontend/services/routeManager.ts index dcc7705..e4cd28a 100644 --- a/frontend/services/routeManager.ts +++ b/frontend/services/routeManager.ts @@ -1,6 +1,5 @@ import React from 'react'; // Import React -import { useEffect } from 'react'; -import { LoaderFunction, RouteObject } from 'react-router-dom'; +import { LoaderFunction, RouteObject } from 'react-router-dom'; export class RouteManager { private static instance: RouteManager; @@ -15,30 +14,18 @@ export class RouteManager { return RouteManager.instance; } - private register(path:string, element: React.ReactNode) { + + private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction,children?:RouteObject[]) { this.routes.push({ path, element, - }); - } - - - private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction) { - this.routes.push({ - path, - element, - loader?: loader + loader, + children, }) } - public getRoutes(): RouteObject[] { + private getRoutes(): RouteObject[] { return this.routes; } - public addRoute(path: string, templateName: string): void { - this.routes.push({ - path, - element: this.createRouteElement(templateName), - }); - } } \ No newline at end of file diff --git a/frontend/services/themeService.ts b/frontend/services/themeService.ts index 040beb1..1e897df 100644 --- a/frontend/services/themeService.ts +++ b/frontend/services/themeService.ts @@ -4,7 +4,6 @@ import { ApiService } from "./apiService"; export class ThemeService { private static instance: ThemeService; private currentTheme?: ThemeConfig; - private templates: Map = new Map(); private api: ApiService; private constructor(api: ApiService) {