后端:添加sql构建器防止sql注入,config增加写入,获取功能,将路由提取出来

This commit is contained in:
lsy 2024-11-20 19:53:18 +08:00
parent 7e4d9c1b48
commit 8071a16510
18 changed files with 517 additions and 325 deletions

View File

@ -11,8 +11,8 @@ toml = "0.8.19"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres"] } sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres"] }
async-trait = "0.1.83" async-trait = "0.1.83"
once_cell = "1.10.0"
jwt-compact = { version = "0.8.0", features = ["ed25519-dalek"] } jwt-compact = { version = "0.8.0", features = ["ed25519-dalek"] }
ed25519-dalek = "2.1.1" ed25519-dalek = "2.1.1"
rand = "0.8.5" rand = "0.8.5"
chrono = "0.4" chrono = "0.4"
regex = "1.11.1"

View File

@ -1,16 +1,11 @@
# config/config.toml
# 配置文件
# 信息
[info] [info]
install = false # 是否为第一次安装 install = true
non_relational = true # 是否使用非关系型数据库 non_relational = false
# 关系型数据库
[sql_config] [sql_config]
db_type = "postgresql" # 数据库类型 db_type = "postgresql"
address = "localhost" # 地址 address = "localhost"
port = 5432 # 端口 port = 5432
user = "postgres" # 用户名 user = "postgres"
password = "postgres" # 密码 password = "postgres"
db_name = "echoes" # 数据库 db_name = "echoes"

1
backend/src/auth/mod.rs Normal file
View File

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

View File

@ -1,19 +1,20 @@
use serde::Deserialize; use serde::{Deserialize,Serialize};
use std::{ env, fs}; use std::{ env, fs};
use std::path::PathBuf;
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Serialize,Debug,Clone)]
pub struct Config { pub struct Config {
pub info: Info, pub info: Info,
pub sql_config: SqlConfig, pub sql_config: SqlConfig,
} }
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Serialize,Debug,Clone,)]
pub struct Info { pub struct Info {
pub install: bool, pub install: bool,
pub non_relational: bool, pub non_relational: bool,
} }
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Serialize,Debug,Clone)]
pub struct SqlConfig { pub struct SqlConfig {
pub db_type: String, pub db_type: String,
pub address: String, pub address: String,
@ -23,7 +24,7 @@ pub struct SqlConfig {
pub db_name: String, pub db_name: String,
} }
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Serialize,Debug,Clone)]
pub struct NoSqlConfig { pub struct NoSqlConfig {
pub db_type: String, pub db_type: String,
pub address: String, pub address: String,
@ -35,9 +36,18 @@ pub struct NoSqlConfig {
impl Config { impl Config {
pub fn read() -> Result<Self, Box<dyn std::error::Error>> { pub fn read() -> Result<Self, Box<dyn std::error::Error>> {
let path = env::current_dir()? let path=Self::get_path()?;
.join("assets")
.join("config.toml");
Ok(toml::from_str(&fs::read_to_string(path)?)?) Ok(toml::from_str(&fs::read_to_string(path)?)?)
} }
pub fn write(config:Config) -> Result<(), Box<dyn std::error::Error>> {
let path=Self::get_path()?;
fs::write(path, toml::to_string(&config)?)?;
Ok(())
}
pub fn get_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
Ok(env::current_dir()?
.join("assets")
.join("config.toml"))
}
} }

View File

@ -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<Self, DatabaseError> {
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<Self, DatabaseError> {
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<Self, DatabaseError> {
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<ValidatedValue>,
}
impl WhereCondition {
pub fn new(
field: String,
operator: Operator,
value: Option<String>,
) -> Result<Self, DatabaseError> {
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<WhereClause>),
Or(Vec<WhereClause>),
Condition(WhereCondition),
}
pub struct QueryBuilder {
operation: SqlOperation,
table: ValidatedValue,
fields: Vec<ValidatedValue>,
params: HashMap<ValidatedValue, ValidatedValue>,
where_clause: Option<WhereClause>,
order_by: Option<ValidatedValue>,
limit: Option<i32>,
}
impl QueryBuilder {
pub fn new(operation: SqlOperation, table: String) -> Result<Self, DatabaseError> {
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<String>), 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::<Vec<_>>()
.join(", ")
};
query.push_str(&format!("SELECT {} FROM {}", fields, self.table.get()));
}
SqlOperation::Insert => {
let fields: Vec<String> = self.params.keys()
.map(|k| k.get().to_string())
.collect();
let placeholders: Vec<String> = (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<String> = 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<String>), 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))
}
}

View File

@ -1,36 +1,46 @@
mod postgresql; mod postgresql;
use std::collections::HashMap;
use crate::config; use crate::config;
use async_trait::async_trait; use async_trait::async_trait;
use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
use std::fmt;
pub mod builder;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug)]
pub enum SqlOperation { pub enum DatabaseError {
Select, ValidationError(String),
Insert, SqlInjectionAttempt(String),
Update, InvalidParameter(String),
Delete, ExecutionError(String),
} }
pub struct QueryBuilder { impl fmt::Display for DatabaseError {
operation: SqlOperation, fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
table: String, match self {
fields: Vec<String>, DatabaseError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
params: HashMap<String, String>, DatabaseError::SqlInjectionAttempt(msg) => write!(f, "SQL injection attempt: {}", msg),
where_conditions: HashMap<String, String>, DatabaseError::InvalidParameter(msg) => write!(f, "Invalid parameter: {}", msg),
order_by: Option<String>, DatabaseError::ExecutionError(msg) => write!(f, "Execution error: {}", msg),
limit: Option<i32>, }
}
} }
impl Error for DatabaseError {}
#[async_trait] #[async_trait]
pub trait DatabaseTrait: Send + Sync { pub trait DatabaseTrait: Send + Sync {
async fn connect(database: config::SqlConfig) -> Result<Self, Box<dyn Error>> where Self: Sized; async fn connect(database: config::SqlConfig) -> Result<Self, Box<dyn Error>>
where
Self: Sized;
async fn execute_query<'a>( async fn execute_query<'a>(
&'a self, &'a self,
builder: &QueryBuilder, builder: &builder::QueryBuilder,
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>; ) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>;
async fn initialization(database: config::SqlConfig) -> Result<(), Box<dyn Error>> where Self: Sized; async fn initialization(database: config::SqlConfig) -> Result<(), Box<dyn Error>>
where
Self: Sized;
} }
#[derive(Clone)] #[derive(Clone)]
@ -49,7 +59,9 @@ impl Database {
_ => return Err("unknown database type".into()), _ => 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<dyn Error>> { pub async fn initial_setup(database: config::SqlConfig) -> Result<(), Box<dyn Error>> {
@ -60,148 +72,3 @@ impl Database {
Ok(()) 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<String>) {
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<String> = 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<String> = self.params.keys().cloned().collect();
let placeholders: Vec<String> = (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<String> = 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<String> = 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<String> = 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<String>) -> &mut Self {
self.fields = fields;
self
}
pub fn params(&mut self, params: HashMap<String, String>) -> &mut Self {
self.params = params;
self
}
pub fn where_conditions(&mut self, conditions: HashMap<String, String>) -> &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
}
}

View File

@ -3,12 +3,12 @@ CREATE TYPE privilege_level AS ENUM ( 'contributor', 'administrators');
CREATE TABLE persons CREATE TABLE persons
( (
person_name VARCHAR(100) PRIMARY KEY, person_name VARCHAR(100) PRIMARY KEY,
person_avatar VARCHAR(255),
person_email VARCHAR(255) UNIQUE NOT NULL, person_email VARCHAR(255) UNIQUE NOT NULL,
person_icon VARCHAR(255), person_icon VARCHAR(255),
person_password VARCHAR(255) NOT NULL, person_password VARCHAR(255) NOT NULL,
person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
person_avatar VARCHAR(255),
person_role VARCHAR(50), person_role VARCHAR(50),
person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP, person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
person_level privilege_level NOT NULL DEFAULT 'contributor' person_level privilege_level NOT NULL DEFAULT 'contributor'

View File

@ -1,4 +1,4 @@
use super::{DatabaseTrait, QueryBuilder}; use super::{DatabaseTrait,builder};
use crate::config; use crate::config;
use async_trait::async_trait; use async_trait::async_trait;
use sqlx::{Column, PgPool, Row, Executor}; use sqlx::{Column, PgPool, Row, Executor};
@ -49,9 +49,9 @@ impl DatabaseTrait for Postgresql {
async fn execute_query<'a>( async fn execute_query<'a>(
&'a self, &'a self,
builder: &QueryBuilder, builder: &builder::QueryBuilder,
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> { ) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> {
let (query, values) = builder.build(); let (query, values) = builder.build()?;
let mut sqlx_query = sqlx::query(&query); let mut sqlx_query = sqlx::query(&query);

View File

@ -1,73 +1,104 @@
mod config; mod config;
mod database; mod database;
mod secret; mod auth;
mod utils;
mod routes;
use chrono::Duration; use chrono::Duration;
use database::relational; use database::relational;
use once_cell::sync::Lazy; use rocket::{
use rocket::{get, post, http::Status, launch, response::status, routes, serde::json::Json}; get, post,
http::Status,
launch,
response::status,
State,
};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
static SQL: Lazy<Arc<Mutex<Option<relational::Database>>>> = #[derive(Debug)]
Lazy::new(|| Arc::new(Mutex::new(None))); pub enum AppError {
Database(String),
async fn init_sql(database: config::SqlConfig) -> Result<(), Box<dyn std::error::Error>> { Config(String),
let database = relational::Database::link(database).await?; Auth(String),
*SQL.lock().await = Some(database);
Ok(())
} }
async fn get_sql() -> Result<relational::Database, Box<dyn std::error::Error>> { impl From<AppError> for status::Custom<String> {
SQL.lock() 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)),
}
}
}
type AppResult<T> = Result<T, AppError>;
struct AppState {
db: Arc<Mutex<Option<relational::Database>>>,
configure: Arc<Mutex<config::Config>>,
}
impl AppState {
async fn get_sql(&self) -> AppResult<relational::Database> {
self.db
.lock()
.await .await
.clone() .clone()
.ok_or_else(|| "Database not initialized".into()) .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(())
}
} }
#[post("/install", format = "json", data = "<data>")]
async fn install(data: Json<config::SqlConfig>) -> Result<status::Custom<String>, status::Custom<String>> {
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"),
))
}
#[get("/system")] #[get("/system")]
async fn token_system() -> Result<status::Custom<String>, status::Custom<String>> { async fn token_system(_state: &State<AppState>) -> Result<status::Custom<String>, status::Custom<String>> {
let claims = secret::CustomClaims { let claims = auth::jwt::CustomClaims {
user_id: String::from("system"), user_id: "system".into(),
device_ua: String::from("system"), 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] #[launch]
async fn rocket() -> _ { async fn rocket() -> _ {
let config = config::Config::read().expect("Failed to read config"); 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 { if config.info.install {
init_sql(config.sql_config) if let Some(state) = rocket_builder.state::<AppState>() {
state.link_sql(config.sql_config.clone())
.await .await
.expect("Failed to connect to database"); .expect("Failed to connect to database");
rocket::build()
.mount("/auth/token", routes![token_system])
} else {
rocket::build()
.mount("/", routes![install])
} }
}
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
} }

View File

@ -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 = "<data>")]
async fn install(
data: Json<InstallData>,
state: &State<AppState>
) -> AppResult<status::Custom<Json<InstallReplyData>>, 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()
}

View File

@ -0,0 +1,6 @@
mod intsall;
use rocket::routes;
pub fn create_routes() -> routes {
routes!["/", intsall::install]
}

9
backend/src/utils.rs Normal file
View File

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

View File

@ -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<Props, State> {
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 <div dangerouslySetInnerHTML={{ __html: errorTemplate }} />;
} catch (e) {
return (
<div className="error-page">
<h1>Something went wrong</h1>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
}
return this.props.children;
}
}

View File

@ -1,17 +0,0 @@
import React, { Suspense } from 'react';
interface LoadingBoundaryProps {
children: React.ReactNode;
fallback?: React.ReactNode;
}
export const LoadingBoundary: React.FC<LoadingBoundaryProps> = ({
children,
fallback = <div>Loading...</div>
}) => {
return (
<Suspense fallback={fallback}>
{children}
</Suspense>
);
};

View File

@ -18,6 +18,7 @@ export interface ThemeConfig {
tag: string; tag: string;
category: string; category: string;
error: string; error: string;
loding: string;
page: Map<string, string>; page: Map<string, string>;
} }
} }

View File

@ -1,5 +1,4 @@
import React from 'react'; // Import React 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 { export class RouteManager {
@ -15,30 +14,18 @@ export class RouteManager {
return RouteManager.instance; return RouteManager.instance;
} }
private register(path:string, element: React.ReactNode) {
private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction,children?:RouteObject[]) {
this.routes.push({ this.routes.push({
path, path,
element, element,
}); loader,
} children,
private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction) {
this.routes.push({
path,
element,
loader?: loader
}) })
} }
public getRoutes(): RouteObject[] { private getRoutes(): RouteObject[] {
return this.routes; return this.routes;
} }
public addRoute(path: string, templateName: string): void {
this.routes.push({
path,
element: this.createRouteElement(templateName),
});
}
} }

View File

@ -4,7 +4,6 @@ import { ApiService } from "./apiService";
export class ThemeService { export class ThemeService {
private static instance: ThemeService; private static instance: ThemeService;
private currentTheme?: ThemeConfig; private currentTheme?: ThemeConfig;
private templates: Map<string, string> = new Map();
private api: ApiService; private api: ApiService;
private constructor(api: ApiService) { private constructor(api: ApiService) {