数据库:重构结构
后端:重构结构
This commit is contained in:
parent
2c1923da07
commit
dbdfbf5d8a
@ -1,6 +1,6 @@
|
||||
use crate::auth;
|
||||
use crate::database::relational::builder;
|
||||
use crate::error::{AppResult, AppResultInto};
|
||||
use crate::security;
|
||||
use crate::storage::sql::builder;
|
||||
use crate::common::error::{AppResult, AppResultInto};
|
||||
use crate::AppState;
|
||||
use chrono::Duration;
|
||||
use rocket::{
|
||||
@ -15,7 +15,7 @@ use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TokenSystemData {
|
||||
name: String,
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
#[post("/system", format = "application/json", data = "<data>")]
|
||||
@ -24,18 +24,18 @@ pub async fn token_system(
|
||||
data: Json<TokenSystemData>,
|
||||
) -> AppResult<String> {
|
||||
let mut builder =
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Select, "persons".to_string())
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Select, "users".to_string())
|
||||
.into_app_result()?;
|
||||
builder
|
||||
.add_field("person_password".to_string())
|
||||
.add_field("password_hash".to_string())
|
||||
.into_app_result()?
|
||||
.add_condition(builder::WhereClause::And(vec![
|
||||
builder::WhereClause::Condition(
|
||||
builder::Condition::new(
|
||||
"person_name".to_string(),
|
||||
"username".to_string(),
|
||||
builder::Operator::Eq,
|
||||
Some(builder::SafeValue::Text(
|
||||
data.name.clone(),
|
||||
data.username.clone(),
|
||||
builder::ValidationLevel::Relaxed,
|
||||
)),
|
||||
)
|
||||
@ -43,7 +43,7 @@ pub async fn token_system(
|
||||
),
|
||||
builder::WhereClause::Condition(
|
||||
builder::Condition::new(
|
||||
"person_email".to_string(),
|
||||
"email".to_string(),
|
||||
builder::Operator::Eq,
|
||||
Some(builder::SafeValue::Text(
|
||||
"author@lsy22.com".into(),
|
||||
@ -54,11 +54,11 @@ pub async fn token_system(
|
||||
),
|
||||
builder::WhereClause::Condition(
|
||||
builder::Condition::new(
|
||||
"person_level".to_string(),
|
||||
"role".to_string(),
|
||||
builder::Operator::Eq,
|
||||
Some(builder::SafeValue::Enum(
|
||||
"administrators".into(),
|
||||
"privilege_level".into(),
|
||||
"administrator".into(),
|
||||
"user_role".into(),
|
||||
builder::ValidationLevel::Standard,
|
||||
)),
|
||||
)
|
||||
@ -77,17 +77,17 @@ pub async fn token_system(
|
||||
|
||||
let password = values
|
||||
.first()
|
||||
.and_then(|row| row.get("person_password"))
|
||||
.and_then(|row| row.get("password_hash"))
|
||||
.and_then(|val| val.as_str())
|
||||
.ok_or_else(|| {
|
||||
status::Custom(Status::NotFound, "Invalid system user or password".into())
|
||||
})?;
|
||||
|
||||
auth::bcrypt::verify_hash(&data.password, password)
|
||||
security::bcrypt::verify_hash(&data.password, password)
|
||||
.map_err(|_| status::Custom(Status::Forbidden, "Invalid password".into()))?;
|
||||
|
||||
Ok(auth::jwt::generate_jwt(
|
||||
auth::jwt::CustomClaims {
|
||||
Ok(security::jwt::generate_jwt(
|
||||
security::jwt::CustomClaims {
|
||||
name: "system".into(),
|
||||
},
|
||||
Duration::minutes(1),
|
@ -1,8 +1,8 @@
|
||||
pub mod auth;
|
||||
pub mod configure;
|
||||
pub mod install;
|
||||
pub mod person;
|
||||
use crate::auth::jwt;
|
||||
pub mod settings;
|
||||
pub mod setup;
|
||||
pub mod users;
|
||||
use crate::security::jwt;
|
||||
use rocket::http::Status;
|
||||
use rocket::request::{FromRequest, Outcome, Request};
|
||||
use rocket::routes;
|
||||
@ -48,5 +48,5 @@ pub fn jwt_routes() -> Vec<rocket::Route> {
|
||||
}
|
||||
|
||||
pub fn configure_routes() -> Vec<rocket::Route> {
|
||||
routes![configure::system_config_get]
|
||||
routes![settings::system_config_get]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use super::SystemToken;
|
||||
use crate::database::{relational, relational::builder};
|
||||
use crate::error::{AppResult, AppResultInto, CustomResult};
|
||||
use crate::storage::{sql, sql::builder};
|
||||
use crate::common::error::{AppResult, AppResultInto, CustomResult};
|
||||
use crate::AppState;
|
||||
use rocket::{
|
||||
get,
|
||||
@ -34,13 +34,13 @@ impl Default for SystemConfigure {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_configure(
|
||||
sql: &relational::Database,
|
||||
pub async fn get_setting(
|
||||
sql: &sql::Database,
|
||||
comfig_type: String,
|
||||
name: String,
|
||||
) -> CustomResult<Json<Value>> {
|
||||
let name_condition = builder::Condition::new(
|
||||
"config_name".to_string(),
|
||||
"key".to_string(),
|
||||
builder::Operator::Eq,
|
||||
Some(builder::SafeValue::Text(
|
||||
format!("{}_{}", comfig_type, name),
|
||||
@ -48,40 +48,37 @@ pub async fn get_configure(
|
||||
)),
|
||||
)?;
|
||||
|
||||
println!(
|
||||
"Searching for config_name: {}",
|
||||
format!("{}_{}", comfig_type, name)
|
||||
);
|
||||
|
||||
let where_clause = builder::WhereClause::Condition(name_condition);
|
||||
|
||||
let mut sql_builder =
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Select, "config".to_string())?;
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Select, "settings".to_string())?;
|
||||
|
||||
sql_builder
|
||||
.add_condition(where_clause)
|
||||
.add_field("config_data".to_string())?;
|
||||
.add_field("data".to_string())?;
|
||||
println!("{:?}", sql_builder.build());
|
||||
|
||||
let result = sql.get_db().execute_query(&sql_builder).await?;
|
||||
Ok(Json(json!(result)))
|
||||
}
|
||||
|
||||
pub async fn insert_configure(
|
||||
sql: &relational::Database,
|
||||
pub async fn insert_setting(
|
||||
sql: &sql::Database,
|
||||
comfig_type: String,
|
||||
name: String,
|
||||
data: Json<Value>,
|
||||
) -> CustomResult<()> {
|
||||
let mut builder =
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Insert, "config".to_string())?;
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Insert, "settings".to_string())?;
|
||||
builder.set_value(
|
||||
"config_name".to_string(),
|
||||
"key".to_string(),
|
||||
builder::SafeValue::Text(
|
||||
format!("{}_{}", comfig_type, name).to_string(),
|
||||
builder::ValidationLevel::Strict,
|
||||
),
|
||||
)?;
|
||||
builder.set_value(
|
||||
"config_data".to_string(),
|
||||
"data".to_string(),
|
||||
builder::SafeValue::Json(data.into_inner()),
|
||||
)?;
|
||||
sql.get_db().execute_query(&builder).await?;
|
||||
@ -94,7 +91,7 @@ pub async fn system_config_get(
|
||||
_token: SystemToken,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let sql = state.sql_get().await.into_app_result()?;
|
||||
let configure = get_configure(&sql, "system".to_string(), "config".to_string())
|
||||
let configure = get_setting(&sql, "system".to_string(), "settings".to_string())
|
||||
.await
|
||||
.into_app_result()?;
|
||||
Ok(configure)
|
@ -1,26 +1,27 @@
|
||||
use super::{configure, person};
|
||||
use crate::auth;
|
||||
use crate::database::relational;
|
||||
use crate::error::{AppResult, AppResultInto};
|
||||
use super::{settings, users};
|
||||
use crate::security;
|
||||
use crate::storage::sql;
|
||||
use crate::common::error::{AppResult, AppResultInto};
|
||||
use crate::AppState;
|
||||
use crate::{config, utils};
|
||||
use crate::common::config;
|
||||
use crate::common::helpers;
|
||||
use chrono::Duration;
|
||||
use rocket::{http::Status, post, response::status, serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize,Debug)]
|
||||
pub struct InstallData {
|
||||
name: String,
|
||||
username: String,
|
||||
email: String,
|
||||
password: String,
|
||||
sql_config: config::SqlConfig,
|
||||
}
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize,Debug)]
|
||||
pub struct InstallReplyData {
|
||||
token: String,
|
||||
name: String,
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
@ -36,64 +37,63 @@ pub async fn install(
|
||||
"Database already initialized".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let data = data.into_inner();
|
||||
let sql = {
|
||||
config.info.install = true;
|
||||
config.sql_config = data.sql_config.clone();
|
||||
|
||||
relational::Database::initial_setup(data.sql_config.clone())
|
||||
sql::Database::initial_setup(data.sql_config.clone())
|
||||
.await
|
||||
.into_app_result()?;
|
||||
auth::jwt::generate_key().into_app_result()?;
|
||||
|
||||
security::jwt::generate_key().into_app_result()?;
|
||||
state.sql_link(&data.sql_config).await.into_app_result()?;
|
||||
state.sql_get().await.into_app_result()?
|
||||
};
|
||||
|
||||
|
||||
let system_credentials = (
|
||||
utils::generate_random_string(20),
|
||||
utils::generate_random_string(20),
|
||||
helpers::generate_random_string(20),
|
||||
helpers::generate_random_string(20),
|
||||
);
|
||||
|
||||
person::insert(
|
||||
users::insert_user(
|
||||
&sql,
|
||||
person::RegisterData {
|
||||
name: data.name.clone(),
|
||||
users::RegisterData {
|
||||
username: data.username.clone(),
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
level: "administrators".to_string(),
|
||||
role: "administrator".to_string(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.into_app_result()?;
|
||||
|
||||
person::insert(
|
||||
users::insert_user(
|
||||
&sql,
|
||||
person::RegisterData {
|
||||
name: system_credentials.0.clone(),
|
||||
users::RegisterData {
|
||||
username: system_credentials.0.clone(),
|
||||
email: "author@lsy22.com".to_string(),
|
||||
password: system_credentials.1.clone(),
|
||||
level: "administrators".to_string(),
|
||||
role: "administrator".to_string(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.into_app_result()?;
|
||||
|
||||
configure::insert_configure(
|
||||
|
||||
settings::insert_setting(
|
||||
&sql,
|
||||
"system".to_string(),
|
||||
"config".to_string(),
|
||||
Json(json!(configure::SystemConfigure {
|
||||
author_name: data.name.clone(),
|
||||
..configure::SystemConfigure::default()
|
||||
"settings".to_string(),
|
||||
Json(json!(settings::SystemConfigure {
|
||||
author_name: data.username.clone(),
|
||||
..settings::SystemConfigure::default()
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.into_app_result()?;
|
||||
|
||||
let token = auth::jwt::generate_jwt(
|
||||
auth::jwt::CustomClaims { name: data.name },
|
||||
let token = security::jwt::generate_jwt(
|
||||
security::jwt::CustomClaims { name: data.username },
|
||||
Duration::days(7),
|
||||
)
|
||||
.into_app_result()?;
|
||||
@ -105,7 +105,7 @@ pub async fn install(
|
||||
Status::Ok,
|
||||
Json(InstallReplyData {
|
||||
token,
|
||||
name: system_credentials.0,
|
||||
username: system_credentials.0,
|
||||
password: system_credentials.1,
|
||||
}),
|
||||
))
|
@ -1,49 +1,54 @@
|
||||
use crate::auth;
|
||||
use crate::auth::bcrypt;
|
||||
use crate::database::{relational, relational::builder};
|
||||
use crate::error::{CustomErrorInto, CustomResult};
|
||||
use crate::{config, utils};
|
||||
use crate::security;
|
||||
use crate::security::bcrypt;
|
||||
use crate::storage::{sql, sql::builder};
|
||||
use crate::common::error::{CustomErrorInto, CustomResult};
|
||||
use rocket::{get, http::Status, post, response::status, serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct LoginData {
|
||||
pub name: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegisterData {
|
||||
pub name: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub level: String,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
pub async fn insert(sql: &relational::Database, data: RegisterData) -> CustomResult<()> {
|
||||
pub async fn insert_user(sql: &sql::Database, data: RegisterData) -> CustomResult<()> {
|
||||
let role = match data.role.as_str() {
|
||||
"administrator" | "contributor" => data.role,
|
||||
_ => return Err("Invalid role. Must be either 'administrator' or 'contributor'".into_custom_error()),
|
||||
};
|
||||
|
||||
let mut builder =
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Insert, "persons".to_string())?;
|
||||
builder::QueryBuilder::new(builder::SqlOperation::Insert, "users".to_string())?;
|
||||
builder
|
||||
.set_value(
|
||||
"person_name".to_string(),
|
||||
builder::SafeValue::Text(data.name, builder::ValidationLevel::Relaxed),
|
||||
"username".to_string(),
|
||||
builder::SafeValue::Text(data.username, builder::ValidationLevel::Relaxed),
|
||||
)?
|
||||
.set_value(
|
||||
"person_email".to_string(),
|
||||
"email".to_string(),
|
||||
builder::SafeValue::Text(data.email, builder::ValidationLevel::Relaxed),
|
||||
)?
|
||||
.set_value(
|
||||
"person_password".to_string(),
|
||||
"password_hash".to_string(),
|
||||
builder::SafeValue::Text(
|
||||
bcrypt::generate_hash(&data.password)?,
|
||||
builder::ValidationLevel::Relaxed,
|
||||
),
|
||||
)?
|
||||
.set_value(
|
||||
"person_level".to_string(),
|
||||
"role".to_string(),
|
||||
builder::SafeValue::Enum(
|
||||
data.level,
|
||||
"privilege_level".to_string(),
|
||||
role,
|
||||
"user_role".to_string(),
|
||||
builder::ValidationLevel::Standard,
|
||||
),
|
||||
)?;
|
@ -1,4 +1,4 @@
|
||||
use crate::error::CustomResult;
|
||||
use crate::common::error::CustomResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
3
backend/src/common/mod.rs
Normal file
3
backend/src/common/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod config;
|
@ -1 +0,0 @@
|
||||
pub mod relational;
|
@ -1,84 +0,0 @@
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
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_role VARCHAR(50),
|
||||
person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
person_level privilege_level NOT NULL DEFAULT 'contributor'
|
||||
);
|
||||
CREATE TYPE publication_status AS ENUM ('draft', 'published', 'private','hide');
|
||||
CREATE TABLE pages
|
||||
(
|
||||
page_id SERIAL PRIMARY KEY,
|
||||
page_meta_keywords VARCHAR(255) NOT NULL,
|
||||
page_meta_description VARCHAR(255) NOT NULL,
|
||||
page_title VARCHAR(255) NOT NULL,
|
||||
page_content TEXT NOT NULL,
|
||||
page_mould VARCHAR(50),
|
||||
page_fields JSON,
|
||||
page_status publication_status DEFAULT 'draft'
|
||||
);
|
||||
CREATE TABLE posts
|
||||
(
|
||||
post_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE,
|
||||
post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
post_picture VARCHAR(255),
|
||||
post_title VARCHAR(255) NOT NULL,
|
||||
post_meta_keywords VARCHAR(255) NOT NULL,
|
||||
post_meta_description VARCHAR(255) NOT NULL,
|
||||
post_content TEXT NOT NULL,
|
||||
post_status publication_status DEFAULT 'draft',
|
||||
post_editor BOOLEAN DEFAULT FALSE,
|
||||
post_unsaved_content TEXT,
|
||||
post_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
post_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
post_published_at TIMESTAMP,
|
||||
CONSTRAINT post_updated_at_check CHECK (post_updated_at >= post_created_at)
|
||||
);
|
||||
CREATE TABLE tags
|
||||
(
|
||||
tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name),
|
||||
tag_icon VARCHAR(255)
|
||||
);
|
||||
CREATE TABLE post_tags
|
||||
(
|
||||
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE,
|
||||
tag_name VARCHAR(50) REFERENCES tags (tag_name) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, tag_name)
|
||||
);
|
||||
CREATE TABLE categories
|
||||
(
|
||||
category_name VARCHAR(50) PRIMARY KEY,
|
||||
parent_category_name VARCHAR(50),
|
||||
FOREIGN KEY (parent_category_name) REFERENCES categories (category_name)
|
||||
);
|
||||
CREATE TABLE post_categories
|
||||
(
|
||||
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE,
|
||||
category_id VARCHAR(50) REFERENCES categories (category_name) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, category_id)
|
||||
);
|
||||
CREATE TABLE library
|
||||
(
|
||||
library_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE,
|
||||
library_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
library_name VARCHAR(255) NOT NULL,
|
||||
library_size BIGINT NOT NULL,
|
||||
library_storage_path VARCHAR(255) NOT NULL UNIQUE,
|
||||
library_type VARCHAR(50) NOT NULL,
|
||||
library_class VARCHAR(50),
|
||||
library_description VARCHAR(255),
|
||||
library_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE config
|
||||
(
|
||||
config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name),
|
||||
config_data JSON
|
||||
);
|
@ -1,18 +1,16 @@
|
||||
mod auth;
|
||||
mod config;
|
||||
mod database;
|
||||
mod error;
|
||||
mod routes;
|
||||
mod utils;
|
||||
mod security;
|
||||
mod common;
|
||||
mod storage;
|
||||
mod api;
|
||||
|
||||
use database::relational;
|
||||
use error::{CustomErrorInto, CustomResult};
|
||||
use storage::sql;
|
||||
use common::error::{CustomErrorInto, CustomResult};
|
||||
use rocket::Shutdown;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::common::config;
|
||||
pub struct AppState {
|
||||
db: Arc<Mutex<Option<relational::Database>>>,
|
||||
db: Arc<Mutex<Option<sql::Database>>>,
|
||||
shutdown: Arc<Mutex<Option<Shutdown>>>,
|
||||
restart_progress: Arc<Mutex<bool>>,
|
||||
}
|
||||
@ -26,7 +24,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sql_get(&self) -> CustomResult<relational::Database> {
|
||||
pub async fn sql_get(&self) -> CustomResult<sql::Database> {
|
||||
self.db
|
||||
.lock()
|
||||
.await
|
||||
@ -35,7 +33,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub async fn sql_link(&self, config: &config::SqlConfig) -> CustomResult<()> {
|
||||
*self.db.lock().await = Some(relational::Database::link(config).await?);
|
||||
*self.db.lock().await = Some(sql::Database::link(config).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -69,12 +67,12 @@ async fn main() -> CustomResult<()> {
|
||||
.manage(state.clone());
|
||||
|
||||
if !config.info.install {
|
||||
rocket_builder = rocket_builder.mount("/", rocket::routes![routes::install::install]);
|
||||
rocket_builder = rocket_builder.mount("/", rocket::routes![api::setup::install]);
|
||||
} else {
|
||||
state.sql_link(&config.sql_config).await?;
|
||||
rocket_builder = rocket_builder
|
||||
.mount("/auth/token", routes::jwt_routes())
|
||||
.mount("/config", routes::configure_routes());
|
||||
.mount("/auth/token", api::jwt_routes())
|
||||
.mount("/config", api::configure_routes());
|
||||
}
|
||||
|
||||
let rocket = rocket_builder.ignite().await?;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::error::{CustomErrorInto, CustomResult};
|
||||
use crate::common::error::{CustomErrorInto, CustomResult};
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
|
||||
pub fn generate_hash(s: &str) -> CustomResult<String> {
|
@ -1,4 +1,4 @@
|
||||
use crate::error::CustomResult;
|
||||
use crate::common::error::CustomResult;
|
||||
use chrono::{Duration, Utc};
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, TimeOptions, Token, UntrustedToken};
|
1
backend/src/storage/mod.rs
Normal file
1
backend/src/storage/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod sql;
|
@ -1,4 +1,4 @@
|
||||
use crate::error::{CustomErrorInto, CustomResult};
|
||||
use crate::common::error::{CustomErrorInto, CustomResult};
|
||||
use chrono::{DateTime, Utc};
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
@ -1,6 +1,6 @@
|
||||
mod postgresql;
|
||||
use crate::config;
|
||||
use crate::error::{CustomErrorInto, CustomResult};
|
||||
use crate::common::error::{CustomErrorInto, CustomResult};
|
||||
use async_trait::async_trait;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
pub mod builder;
|
@ -1,7 +1,6 @@
|
||||
use super::{builder, DatabaseTrait};
|
||||
use crate::config;
|
||||
use crate::error::CustomErrorInto;
|
||||
use crate::error::CustomResult;
|
||||
use crate::common::error::CustomResult;
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
use sqlx::{Column, Executor, PgPool, Row, TypeInfo};
|
||||
@ -17,10 +16,10 @@ impl DatabaseTrait for Postgresql {
|
||||
async fn initialization(db_config: config::SqlConfig) -> CustomResult<()> {
|
||||
let path = env::current_dir()?
|
||||
.join("src")
|
||||
.join("database")
|
||||
.join("relational")
|
||||
.join("storage")
|
||||
.join("sql")
|
||||
.join("postgresql")
|
||||
.join("init.sql");
|
||||
.join("schema.sql");
|
||||
let grammar = fs::read_to_string(&path)?;
|
||||
|
||||
let connection_str = format!(
|
102
backend/src/storage/sql/postgresql/schema.sql
Normal file
102
backend/src/storage/sql/postgresql/schema.sql
Normal file
@ -0,0 +1,102 @@
|
||||
-- 自定义类型定义
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
CREATE TYPE user_role AS ENUM ('contributor', 'administrator');
|
||||
CREATE TYPE content_status AS ENUM ('draft', 'published', 'private', 'hidden');
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE users
|
||||
(
|
||||
username VARCHAR(100) PRIMARY KEY,
|
||||
avatar_url VARCHAR(255),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
profile_icon VARCHAR(255),
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role user_role NOT NULL DEFAULT 'contributor',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 页面表
|
||||
CREATE TABLE pages
|
||||
(
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
meta_keywords VARCHAR(255) NOT NULL,
|
||||
meta_description VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
template VARCHAR(50),
|
||||
custom_fields JSON,
|
||||
status content_status DEFAULT 'draft'
|
||||
);
|
||||
|
||||
-- 文章表
|
||||
CREATE TABLE posts
|
||||
(
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
author_id VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE,
|
||||
cover_image VARCHAR(255),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
meta_keywords VARCHAR(255) NOT NULL,
|
||||
meta_description VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
status content_status DEFAULT 'draft',
|
||||
is_editor BOOLEAN DEFAULT FALSE,
|
||||
draft_content TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
published_at TIMESTAMP,
|
||||
CONSTRAINT check_update_time CHECK (updated_at >= created_at)
|
||||
);
|
||||
|
||||
-- 标签表
|
||||
CREATE TABLE tags
|
||||
(
|
||||
name VARCHAR(50) PRIMARY KEY CHECK (LOWER(name) = name),
|
||||
icon VARCHAR(255)
|
||||
);
|
||||
|
||||
-- 文章标签关联表
|
||||
CREATE TABLE post_tags
|
||||
(
|
||||
post_id UUID REFERENCES posts (id) ON DELETE CASCADE,
|
||||
tag_id VARCHAR(50) REFERENCES tags (name) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, tag_id)
|
||||
);
|
||||
|
||||
-- 分类表
|
||||
CREATE TABLE categories
|
||||
(
|
||||
name VARCHAR(50) PRIMARY KEY,
|
||||
parent_id VARCHAR(50),
|
||||
FOREIGN KEY (parent_id) REFERENCES categories (name)
|
||||
);
|
||||
|
||||
-- 文章分类关联表
|
||||
CREATE TABLE post_categories
|
||||
(
|
||||
post_id UUID REFERENCES posts (id) ON DELETE CASCADE,
|
||||
category_id VARCHAR(50) REFERENCES categories (name) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, category_id)
|
||||
);
|
||||
|
||||
-- 资源库表
|
||||
CREATE TABLE resources
|
||||
(
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
author_id VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
storage_path VARCHAR(255) NOT NULL UNIQUE,
|
||||
file_type VARCHAR(50) NOT NULL,
|
||||
category VARCHAR(50),
|
||||
description VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
-- 配置表
|
||||
CREATE TABLE settings
|
||||
(
|
||||
key VARCHAR(50) PRIMARY KEY CHECK (LOWER(key) = key),
|
||||
data JSON
|
||||
);
|
@ -53,6 +53,37 @@ export class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
private async getToken(username: string, password: string): Promise<string> {
|
||||
if (username.split(" ").length === 0 || password.split(" ").length === 0) {
|
||||
throw new Error(
|
||||
"Username or password cannot be empty",
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseURL}/auth/token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to get system token");
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error getting system token:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {},
|
||||
|
Loading…
Reference in New Issue
Block a user