数据库:修改config字段

后端:去除全局配置文件,安装时增加创建系统的配置文件,新增或者系统配置文件路由
前端:为系统安装状态添加了环境变量。
This commit is contained in:
lsy 2024-11-25 17:59:18 +08:00
parent d2eac057ca
commit b1854b4fb8
9 changed files with 166 additions and 35 deletions

View File

@ -5,16 +5,39 @@ use std::{env, fs};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Config {
pub address: String,
pub port: u32,
pub info: Info,
pub sql_config: SqlConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
address: "0.0.0.0".to_string(),
port: 22000,
info: Info::default(),
sql_config: SqlConfig::default(),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Info {
pub install: bool,
pub non_relational: bool,
}
impl Default for Info {
fn default() -> Self {
Self {
install: false,
non_relational: false,
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SqlConfig {
pub db_type: String,
@ -25,6 +48,19 @@ pub struct SqlConfig {
pub db_name: String,
}
impl Default for SqlConfig {
fn default() -> Self {
Self {
db_type: "postgresql".to_string(),
address: "localhost".to_string(),
port: 5432,
user: "postgres".to_string(),
password: "postgres".to_string(),
db_name: "echoes".to_string(),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct NoSqlConfig {
pub db_type: String,
@ -35,6 +71,19 @@ pub struct NoSqlConfig {
pub db_name: String,
}
impl Default for NoSqlConfig {
fn default() -> Self {
Self {
db_type: "postgresql".to_string(),
address: "localhost".to_string(),
port: 5432,
user: "postgres".to_string(),
password: "postgres".to_string(),
db_name: "echoes".to_string(),
}
}
}
impl Config {
pub fn read() -> CustomResult<Self> {
let path = Self::get_path()?;

View File

@ -77,13 +77,12 @@ impl TextValidator {
let max_length = self
.level_max_lengths
.get(&level)
.ok_or_else(|| "Invalid validation level".into_custom_error())?;
.ok_or( "Invalid validation level".into_custom_error())?;
if text.len() > *max_length {
return Err("Text exceeds maximum length".into_custom_error());
}
// 简化验证逻辑
if level == ValidationLevel::Relaxed {
return self.validate_sql_patterns(text);
}
@ -514,7 +513,6 @@ impl QueryBuilder {
Ok((sql, params))
}
// 添加新的辅助方法
fn build_where_clause_with_index(
&self,
clause: &WhereClause,

View File

@ -80,5 +80,5 @@ CREATE TABLE library
CREATE TABLE config
(
config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name),
config_config JSON
config_data JSON
);

View File

@ -12,16 +12,14 @@ use error::{CustomErrorInto, CustomResult};
pub struct AppState {
db: Arc<Mutex<Option<relational::Database>>>,
configure: Arc<Mutex<config::Config>>,
shutdown: Arc<Mutex<Option<Shutdown>>>,
restart_progress: Arc<Mutex<bool>>,
}
impl AppState {
pub fn new(config: config::Config) -> Self {
pub fn new() -> Self {
Self {
db: Arc::new(Mutex::new(None)),
configure: Arc::new(Mutex::new(config)),
shutdown: Arc::new(Mutex::new(None)),
restart_progress: Arc::new(Mutex::new(false)),
}
@ -61,22 +59,24 @@ impl AppState {
#[rocket::main]
async fn main() -> CustomResult<()> {
let config = config::Config::read()?;
let config = config::Config::read().unwrap_or_default();
let state = AppState::new(config.clone());
if config.info.install {
state.sql_link(&config.sql_config).await?;
}
let rocket_config = rocket::Config::figment()
.merge(("address", config.address.clone()))
.merge(("port", config.port));
let state = AppState::new();
let state = Arc::new(state);
let rocket_builder = rocket::build().manage(state.clone());
let rocket_builder = rocket::build().configure(rocket_config).manage(state.clone());
let rocket_builder = if !config.info.install {
rocket_builder.mount("/", rocket::routes![routes::install::install])
} else {
rocket_builder.mount("/auth/token", routes::jwt_routes())
state.sql_link(&config.sql_config).await?;
rocket_builder
.mount("/auth/token", routes::jwt_routes())
.mount("/config", routes::configure_routes())
};
let rocket = rocket_builder.ignite().await?;

View File

@ -3,7 +3,6 @@ use crate::database::relational::builder;
use crate::error::{AppResult, AppResultInto};
use crate::AppState;
use chrono::Duration;
use jwt_compact::Token;
use rocket::{
http::Status,
post,
@ -91,12 +90,14 @@ pub async fn token_system(
String::from("该用户密码丢失"),
))?;
auth::bcrypt::verify_hash(&data.password, password).into_app_result()?;
auth::bcrypt::verify_hash(&data.password, password).map_err(|_| {
status::Custom(Status::Forbidden, String::from("密码错误"))
})?;
let claims = auth::jwt::CustomClaims {
name: "system".into(),
};
let token = auth::jwt::generate_jwt(claims, Duration::seconds(1)).into_app_result()?;
let token = auth::jwt::generate_jwt(claims, Duration::minutes(1)).into_app_result()?;
Ok(token)
}

View File

@ -1,10 +1,85 @@
use super::SystemToken;
use crate::error::AppResult;
use crate::database::{relational, relational::builder};
use crate::error::{AppResult, AppResultInto, CustomResult};
use crate::AppState;
use rocket::{
get,
http::Status,
post,
response::status,
serde::json::{Json, Value},
Request,
State,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::sync::Arc;
#[derive(Deserialize, Serialize)]
pub struct SystemConfigure {
pub author_name: String,
pub current_theme: String,
pub site_keyword: Vec<String>,
pub site_description: String,
pub admin_path: String,
}
impl Default for SystemConfigure {
fn default() -> Self {
Self {
author_name: "lsy".to_string(),
current_theme: "default".to_string(),
site_keyword: vec!["echoes".to_string()],
site_description: "echoes是一个高效、可扩展的博客平台".to_string(),
admin_path: "admin".to_string(),
}
}
}
pub async fn get_configure(
sql: &relational::Database,
comfig_type: String,
name: String,
) -> CustomResult<Json<Value>> {
let mut sql_builder =
builder::QueryBuilder::new(builder::SqlOperation::Select, "config".to_string())?;
sql_builder.set_value(
"config_name".to_string(),
builder::SafeValue::Text(
format!("{}_{}", comfig_type, name).to_string(),
builder::ValidationLevel::Strict,
),
)?;
let result = sql.get_db().execute_query(&sql_builder).await?;
Ok(Json(json!(result)))
}
pub async fn insert_configure(
sql: &relational::Database,
comfig_type: String,
name: String,
data: Json<Value>,
) -> CustomResult<()> {
let mut builder =
builder::QueryBuilder::new(builder::SqlOperation::Insert, "config".to_string())?;
builder.set_value(
"config_name".to_string(),
builder::SafeValue::Text(
format!("{}_{}", comfig_type, name).to_string(),
builder::ValidationLevel::Strict,
),
)?;
builder.set_value(
"config_data".to_string(),
builder::SafeValue::Json(data.into_inner()),
)?;
sql.get_db().execute_query(&builder).await?;
Ok(())
}
#[get("/system")]
pub async fn system_config_get(state: &State<Arc<AppState>>,token: SystemToken) -> AppResult<Json<Value>> {
let sql = state.sql_get().await.into_app_result()?;
let configure = get_configure(&sql, "system".to_string(), "configure".to_string())
.await
.into_app_result()?;
Ok(configure)
}

View File

@ -1,13 +1,14 @@
use crate::auth;
use crate::database::relational;
use crate::error::{AppResult, AppResultInto};
use crate::routes::person;
use super::{person, configure};
use crate::AppState;
use crate::{config, utils};
use chrono::Duration;
use rocket::{http::Status, post, response::status, serde::json::Json, State};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use serde_json::json;
#[derive(Deserialize, Serialize)]
pub struct InstallData {
@ -28,7 +29,7 @@ pub async fn install(
data: Json<InstallData>,
state: &State<Arc<AppState>>,
) -> AppResult<status::Custom<Json<InstallReplyData>>> {
let mut config = state.configure.lock().await;
let mut config = config::Config::read().unwrap_or_default();
if config.info.install {
return Err(status::Custom(
Status::BadRequest,
@ -36,6 +37,9 @@ pub async fn install(
));
}
config.info.install = true;
config.sql_config = data.sql_config.clone();
let data = data.into_inner();
relational::Database::initial_setup(data.sql_config.clone())
@ -44,7 +48,7 @@ pub async fn install(
let _ = auth::jwt::generate_key();
config.info.install = true;
state.sql_link(&data.sql_config).await.into_app_result()?;
let sql = state.sql_get().await.into_app_result()?;
@ -76,6 +80,12 @@ pub async fn install(
.await
.into_app_result()?;
let mut system_configure = configure::SystemConfigure::default();
system_configure.author_name = data.name.clone();
configure::insert_configure(&sql, "system".to_string(), "configure".to_string(), Json(json!(system_configure))).await.into_app_result()?;
let token = auth::jwt::generate_jwt(
auth::jwt::CustomClaims {
name: data.name.clone(),

View File

@ -2,6 +2,7 @@ pub mod auth;
pub mod configure;
pub mod install;
pub mod person;
use crate::auth::jwt;
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome, Request};
use rocket::routes;
@ -30,22 +31,14 @@ pub struct SystemToken(String);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for SystemToken {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let token = request
.headers()
.get_one("Authorization")
.map(|value| value.replace("Bearer ", ""));
match token {
Some(token) => {
if token == "system" {
Outcome::Success(SystemToken(token))
} else {
Outcome::Error((Status::Unauthorized, ()))
}
}
None => Outcome::Error((Status::Unauthorized, ())),
match token.and_then(|t| jwt::validate_jwt(&t).ok()) {
Some(claims) if claims.name == "system" => Outcome::Success(SystemToken(claims.name)),
_ => Outcome::Error((Status::Unauthorized, ())),
}
}
}
@ -53,3 +46,7 @@ impl<'r> FromRequest<'r> for SystemToken {
pub fn jwt_routes() -> Vec<rocket::Route> {
routes![auth::token::token_system]
}
pub fn configure_routes() -> Vec<rocket::Route> {
routes![configure::system_config_get]
}

View File

@ -15,6 +15,7 @@ interface ImportMetaEnv {
readonly VITE_ASSETS_PATH: string; // 存储静态资源的目录路径
VITE_SYSTEM_USERNAME: string; // 前端账号名称
VITE_SYSTEM_PASSWORD: string; // 前端账号密码
VITE_SYSTEM_STATUS: boolean; // 系统是否进行安装
}
interface ImportMeta {