pub mod addon;
pub mod module;
pub mod action;
pub mod tools;
pub mod swagger;
use std::collections::HashMap;
use std::{fs, io};
use std::path::PathBuf;
use std::sync::Mutex;
#[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql"))]
use br_db::types::TableOptions;
use json::{array, object, JsonValue};
use lazy_static::lazy_static;
use log::info;
use crate::action::Action;
use crate::module::Module;
use crate::tools::Tools;
use crate::addon::Addon;
lazy_static! {
static ref PLUGIN_TOOLS: Mutex<HashMap<String,Tools>> =Mutex::new(HashMap::new());
}
pub trait Plugin {
fn addon(name: &str) -> Result<Box<dyn Addon>, String>;
fn api(name: &str) -> Result<Box<dyn Action>, String> {
let res = name.split(".").collect::<Vec<&str>>();
if res.len() != 3 {
return Err("api 格式不正确".to_string());
}
Self::addon(res[0])?.module(res[1])?.action(res[2])
}
fn load_tools(path: PathBuf) -> Result<(), String> {
if PLUGIN_TOOLS.lock().unwrap().get("tools").is_none() {
let res = Tools::new(path)?;
PLUGIN_TOOLS.lock().unwrap().insert("tools".into(), res.clone());
}
Ok(())
}
fn get_tools() -> Tools {
let tools = PLUGIN_TOOLS.lock().unwrap();
let tools = tools.get("tools").unwrap().clone();
tools
}
#[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql"))]
fn init_db(path: PathBuf) -> Result<(), String> {
#[cfg(not(debug_assertions))]
{
info!("=============数据库更新开始=============");
let json_data = match fs::read_to_string("sql.json") {
Ok(e) => e,
Err(e) => return Err(e.to_string())
};
let json = match json::parse(&*json_data) {
Ok(e) => e,
Err(e) => return Err(e.to_string())
};
for module in json.members() {
let table_name = module["table_name"].as_str().unwrap();
let table_title = module["table_title"].as_str().unwrap();
let table_key = module["table_key"].as_str().unwrap();
let table_fields = module["table_fields"].clone();
let table_unique = module["table_unique"].members().map(|x| x.as_str().unwrap().to_string()).collect::<Vec<String>>();
let table_index = module["table_index"].members().map(|x| x.members().map(|x| x.as_str().unwrap().to_string()).collect::<Vec<String>>()).collect::<Vec<Vec<String>>>();
let table_partition = module["table_partition"].as_bool().unwrap();
let table_partition_columns = module["table_partition_columns"].clone();
if Self::get_tools().db.table_is_exist(&table_name) {
let res = Self::get_tools().db.table_update_new(table_name, table_title, table_key, table_fields, table_unique, table_index, table_partition, table_partition_columns);
match res.as_i32().unwrap() {
-1 => {}
0 => {
info!("数据库更新情况: {} 失败", table_name);
}
1 => {
info!("数据库更新情况: {} 成功", table_name);
}
_ => {}
}
} else {
let res = Self::get_tools().db.table_create_new(table_name, table_title, table_key, table_fields, table_unique, table_index, table_partition, table_partition_columns);
info!("安装完成情况: {} {}",table_name, res);
}
}
info!("=============数据库更新完成=============");
return Ok(());
}
info!("=============数据库更新=============");
let mut sql = array![];
let mut r = Self::get_module_list(path, 0)?;
for module in r.iter_mut() {
if !module.table() {
continue;
}
let info = object! {
table_name: module.table_name(),
table_title:module.title().as_str(),
table_key:module.table_key().as_str(),
table_fields:module.fields().clone(),
table_unique:module.table_unique().clone(),
table_index:module.table_index().clone(),
table_partition:module.table_partition(),
table_partition_columns:module.table_partition_columns().clone(),
};
sql.push(info).unwrap();
let mut opt = TableOptions::default();
opt.set_table_name(module.table_name().as_str());
opt.set_table_title(module.title().as_str());
opt.set_table_key(module.table_key().as_str());
opt.set_table_fields(module.fields().clone());
opt.set_table_unique(module.table_unique().iter().map(|x| x.as_str()).collect::<Vec<&str>>());
opt.set_table_index(module.table_index().iter().map(|x| x.iter().map(|y| y.as_str()).collect::<Vec<&str>>()).collect::<Vec<Vec<&str>>>());
opt.set_table_partition(module.table_partition());
opt.set_table_partition_columns(module.table_partition_columns());
if Self::get_tools().db.table_is_exist(&module.table_name()) {
let res = Self::get_tools().db.table_update_new(opt);
match res.as_i32().unwrap() {
-1 => {}
0 => {
info!("数据库更新情况: {} 失败", module.table_name());
}
1 => {
info!("数据库更新情况: {} 成功", module.table_name());
}
_ => {}
}
} else {
let res = Self::get_tools().db.table_create_new(opt);
info!("安装完成情况: {} {}",module.table_name(), res);
}
}
fs::write("sql.json", sql.to_string()).unwrap();
info!("=============数据库更新完成=============");
Ok(())
}
fn get_module_list(path: PathBuf, index: usize) -> Result<Vec<Box<dyn Module>>, String> {
let entries = match fs::read_dir(path) {
Ok(e) => e,
Err(_) => {
return Err("Failed to read directory".to_string());
}
}; let mut list = vec![];
for entry in entries {
let entry = match entry {
Ok(e) => e,
Err(_) => {
return Err("Failed to read directory".to_string());
}
};
let path = entry.path();
if path.is_dir() {
let res = Self::get_module_list(path, index + 1)?;
list.extend(res);
} else if path.is_file() && path.file_name().unwrap().to_str().unwrap().ends_with("mod.rs") && index == 2 {
let module = path.parent().unwrap().file_name().unwrap().to_str().unwrap();
let plugin = path.parent().unwrap().parent().unwrap().file_name().unwrap().to_str().unwrap();
let res = Self::addon(plugin)?.module(module)?;
list.push(res);
}
}
Ok(list)
}
fn swagger(title: &str, description: &str, version: &str, uaturl: &str, produrl: &str, tags: JsonValue, paths: JsonValue) -> JsonValue {
let info = object! {
openapi:"3.0.0",
info:{
title:title,
description:description,
version:version
},
components: {
securitySchemes: {
BearerToken: {
"type": "http",
"scheme": "bearer",
"bearerFormat": "Token"
}
}
},
tags:tags,
security: [
{
"BearerToken": []
}
],
servers:[
{
"url":uaturl,
"description": "测试地址"
},
{
"url":produrl,
"description": "正式地址"
}
],
paths:paths
};
info
}
fn generate_api_list(apipath: PathBuf, path: PathBuf, index: usize) -> io::Result<Vec<String>> {
let mut plugin_list = vec![];
if path.is_dir() {
let res = fs::read_dir(path);
match res {
Ok(entries) => {
for entry in entries {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
let res = Self::generate_api_list(apipath.clone(), path.to_str().unwrap().parse().unwrap(), index + 1)?;
plugin_list.extend(res);
} else if path.is_file() {
if path.to_str().unwrap().ends_with("mod.rs") {
continue;
}
let addon = path.parent().unwrap().parent().unwrap().file_name().unwrap().to_str().unwrap();
let model = path.parent().unwrap().file_name().unwrap().to_str().unwrap();
let action = path.file_name().unwrap().to_str().unwrap().trim_end_matches(".rs");
plugin_list.push(format!("{}.{}.{}", addon, model, action));
}
}
}
Err(e) => {
return Err(std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
}
}
}
if index == 0 {
fs::create_dir_all(apipath.clone().parent().unwrap()).unwrap();
fs::write(apipath, JsonValue::from(plugin_list.clone()).to_string()).unwrap();
}
Ok(plugin_list)
}
}
#[derive(Debug, Clone)]
pub struct ApiResponse {
pub types: ApiType,
pub code: i32,
pub message: String,
pub data: JsonValue,
pub success: bool,
}
impl ApiResponse {
pub fn json(self) -> JsonValue {
match self.types {
ApiType::Json => object! {
code: self.code,
message: self.message,
data: self.data,
success: self.success
},
ApiType::Redirect => self.data,
ApiType::Download => self.data,
ApiType::Preview => self.data,
ApiType::Txt => self.data
}
}
pub fn swagger(&mut self) -> JsonValue {
let mut content = object! {};
content[self.types.str().as_str()] = object! {};
content[self.types.str().as_str()]["schema"]["type"] = if self.data.is_array() { "array" } else { "object" }.into();
content[self.types.str().as_str()]["schema"]["properties"] = self.data.clone();
content[self.types.str().as_str()]["schema"]["type"] = match content[self.types.str().as_str()]["schema"]["type"].as_str().unwrap() {
"int" => "integer".into(),
_ => content[self.types.str().as_str()]["schema"]["type"].clone()
};
let data = object! {
"description":self.message.clone(),
"content":content
};
data
}
pub fn success(data: JsonValue, mut message: &str) -> Self {
if message.is_empty() {
message = "success";
}
Self {
success: true,
types: ApiType::Json,
code: 0,
message: message.to_string(),
data,
}
}
pub fn fail(code: i32, message: &str) -> Self {
Self {
types: ApiType::Json,
code,
message: message.to_string(),
data: JsonValue::Null,
success: false,
}
}
pub fn error(data: JsonValue, message: &str) -> Self {
Self {
types: ApiType::Json,
code: -1,
message: message.to_string(),
data,
success: false,
}
}
pub fn redirect(url: &str) -> Self {
Self {
types: ApiType::Redirect,
code: 0,
message: "".to_string(),
data: url.into(),
success: true,
}
}
pub fn download(filename: &str) -> Self {
Self {
types: ApiType::Download,
code: 0,
message: "".to_string(),
data: filename.into(),
success: true,
}
}
pub fn preview(filename: &str) -> Self {
Self {
types: ApiType::Preview,
code: 0,
message: "".to_string(),
data: filename.into(),
success: true,
}
}
pub fn txt(txt: &str) -> Self {
Self {
types: ApiType::Txt,
code: 0,
message: "".to_string(),
data: txt.into(),
success: true,
}
}
}
impl Default for ApiResponse {
fn default() -> Self {
Self {
types: ApiType::Json,
code: 0,
message: "".to_string(),
data: JsonValue::Null,
success: false,
}
}
}
#[derive(Debug, Clone)]
pub enum ApiType {
Json,
Redirect,
Download,
Preview,
Txt,
}
impl ApiType {
pub fn str(&mut self) -> String {
match self {
ApiType::Json => "application/json",
ApiType::Redirect | ApiType::Download | ApiType::Preview => "text/html",
ApiType::Txt => "text/plain",
}.to_string()
}
}