my commit

This commit is contained in:
Григорий Сафронов 2025-11-16 20:25:52 +03:00
commit 43f2addd94
14 changed files with 5263 additions and 0 deletions

2237
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

27
Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "futriix"
version = "0.1.0"
edition = "2024" # По состоянию на октябрь 2025 года наиболее стабильная и последняя редакция языка Rust (Rust Edition)
[dependencies]
rlua = "0.20.1" # Библиотека для интеграции Rust с языком Lua. Позволяет выполнять Lua-скрипты из Rust, передавать данные между ними и использовать Lua в качестве скриптового движка (например, для плагинов или конфигураций).
ansi_term = "0.12.1" # Предоставляет инструменты для работы с ANSI-кодами в терминале, такими как цветной вывод текста, жирный шрифт и другие стили. Полезна для создания красивого CLI-интерфейса.
tokio = { version = "1.0", features = ["full"] } # Асинхронное рантайм для Rust, для написания неблокирующего кода (например, для сетевых запросов, многозадачности). С фичей "full" включает всё для полного асинхронного управления.
warp = "0.3" # Лёгкий веб-фреймворк для Rust, построенный на tokio. Упрощает создание HTTP-серверов, API и веб-приложений с поддержкой маршрутизации, middleware и т.д.
serde = { version = "1.0", features = ["derive"] } #Основная библиотека для сериализации и десериализации данных в Rust (преобразование структур в строки/байты и обратно). Фича "derive" генерирует код автоматически через макросы.
serde_json = "1.0" # Расширение для serde, для работы с JSON-форматом. Позволяет сериализовать/десериализовать данные в JSON.
toml = "0.5" # Библиотека для чтения и записи файлов в формате TOML (Tom's Obvious, Minimal Language).
rmp-serde = "1.1" # MessagePack
csv = "1.4.0" # Добавлена библиотека для CSV
chrono = { version = "0.4", features = ["serde"] } # Работа с датами, временем и периодами (например, парсинг ISO 8601). С фичей "serde" поддерживает сериализацию временных объектов.
log = "0.4" # Фрейморк для логирования в Rust, предоставляет трейты для записи сообщений (ошибки, предупреждения и т.д.) без привязки к конкретной реализации.
env_logger = "0.10" # Конкретная реализация логирования на основе переменных окружения. Работает с crate log, позволяет настраивать уровень логирования через переменные вроде RUST_LOG.
parking_lot = "0.12" #Более производительная альтернатива стандартным мьютексам и спинлокам в Rust, полезна для многопоточного кода с низкими оверхедами.
crossbeam = "0.8" #Инструменты для параллелизма, включая каналы для межпоточного общения, атомарные операции и структуры данных для конкурентного доступа.
rustyline = "12.0" # Библиотека для создания интерактивных REPL (read-eval-print loops) или терминалов с поддержкой истории команд, автодополнения и редактирования строк.
flate2 = "1.0" # Библиотека для сжатия и декомпрессии данных с помощью алгоритмов вроде gzip/deflate. В проекте нужнадля сжатия backup
md-5 = "0.10.6" # Библиотека для поддержки MD5
[dev-dependencies] # Блок для запуска тестов
tokio = { version = "1.0", features = ["full", "rt-multi-thread"] }
chrono = { version = "0.4", features = ["serde"] }

40
config/config.toml Executable file
View File

@ -0,0 +1,40 @@
# Конфигурация сервера приложений и СУБД
http_port = 9080
db_path = "./data"
log_level = "info"
# Настройки кластера
cluster_enabled = false
node_id = "node_1"
cluster_nodes = [
"http://node1.example.com:9040",
"http://node2.example.com:9040",
"http://node3.example.com:9040"
]
# Настройки ACL (Access Control List)
acl_enabled = false
admin_users = [
"admin",
"root"
]
# Настройки репликации
master_master_replication = false
replication_nodes = [
"http://replica1.example.com:8080",
"http://replica2.example.com:8080"
]
# Настройки HTTPS
https_enabled = false
https_port = 8443
cert_file = "./cert.pem"
key_file = "./key.pem"
# Настройки HTTP2
http2_enabled = false
# Настройки хранимых процедур
stored_procedures_path = "./procedures"

18
lua-scripts/init.lua Normal file
View File

@ -0,0 +1,18 @@
-- Автоматически загружаемый скрипт при запуске сервера
print("futriiX Lua environment initialized")
-- Вспомогательные функции
function help()
print("Available commands:")
print(" inbox.start - Enter database mode")
print(" db_create(collection, key, json) - Create document")
print(" exit - Exit application")
end
-- Глобальная функция для логирования
function log_info(message)
print("[INFO] " .. message)
end
log_info("Lua scripts loaded successfully")

50
lua-scripts/utils.lua Normal file
View File

@ -0,0 +1,50 @@
--utils.lua- является пользовательской библиотекой вспомогательных функций Lua для работы с СУБД futriix.
-- Выполняет роль:
-- Загрузчика в других Lua скриптах проекта
-- Загрузчика при инициализации Lua интерпретатора
-- Основного инструмента в хранимых процедурах проекта
function json_encode(tbl)
return require("serde").encode(tbl)
end
function json_decode(str)
return require("serde").decode(str)
end
function table_length(tbl)
local count = 0
for _ in pairs(tbl) do
count = count + 1
end
return count
end
-- Функция для MD5 хеширования
function md5_hash(text)
return md5(text)
end
-- Функция для создания пользователя с хешированным паролем
function create_user_with_password(space, username, password, user_data)
user_data = user_data or {}
user_data.password = md5_hash(password)
return insert(space, username, json_encode(user_data))
end
-- Функция для проверки пароля пользователя
function verify_user_password(space, username, password)
local user_json = get(space, username)
if user_json == "null" then
return false, "User not found"
end
local user_data = json_decode(user_json)
if user_data.password == md5_hash(password) then
return true, "Password correct"
else
return false, "Password incorrect"
end
end

129
src/cli.rs Normal file
View File

@ -0,0 +1,129 @@
use std::error::Error;
use ansi_term::Colour;
/// Парсер команд для интерпретатора
pub struct CommandParser;
impl CommandParser {
pub fn parse_command(line: &str) -> Result<Command, Box<dyn Error>> {
let parts: Vec<&str> = line.trim().split_whitespace().collect();
if parts.is_empty() {
return Ok(Command::Empty);
}
match parts[0].to_lowercase().as_str() {
"exit" | "quit" => Ok(Command::Exit),
"help" => Ok(Command::Help),
"backup" => {
if parts.len() >= 2 {
Ok(Command::Backup {
path: parts[1].to_string(),
})
} else {
Err("Usage: backup <path>".into())
}
}
"restore" => {
if parts.len() >= 2 {
Ok(Command::Restore {
path: parts[1].to_string(),
})
} else {
Err("Usage: restore <path>".into())
}
}
"create" => {
if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" {
if parts.len() >= 4 {
Ok(Command::CreateProcedure {
name: parts[2].to_string(),
code: parts[3..].join(" "),
})
} else {
Err("Usage: create procedure <name> <code>".into())
}
} else {
Ok(Command::LuaCode(line.to_string()))
}
}
"call" => {
if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" {
Ok(Command::CallProcedure {
name: parts[2].to_string(),
})
} else {
Ok(Command::LuaCode(line.to_string()))
}
}
"drop" => {
if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" {
Ok(Command::DropProcedure {
name: parts[2].to_string(),
})
} else {
Ok(Command::LuaCode(line.to_string()))
}
}
_ => Ok(Command::LuaCode(line.to_string())),
}
}
pub fn print_help() {
println!("\n{}", Colour::Cyan.paint("Available commands:"));
println!("{}", Colour::Yellow.paint(" General:"));
println!(" exit, quit - Exit the interpreter");
println!(" help - Show this help message");
println!();
println!("{}", Colour::Yellow.paint(" Database operations:"));
println!(" backup <path> - Create database backup");
println!(" restore <path> - Restore database from backup");
println!();
println!("{}", Colour::Yellow.paint(" Stored procedures:"));
println!(" create procedure <name> <code> - Create stored procedure");
println!(" call procedure <name> - Execute stored procedure");
println!(" drop procedure <name> - Delete stored procedure");
println!();
println!("{}", Colour::Yellow.paint(" Lua operations:"));
println!(" Any valid Lua code - Execute Lua code with DB API");
println!();
println!("{}", Colour::Cyan.paint("Lua DB API functions:"));
println!(" create_space(name) - Create space");
println!(" delete_space(name) - Delete space");
println!(" insert(space, key, value) - Insert document");
println!(" get(space, key) - Get document");
println!(" update(space, key, value) - Update document");
println!(" delete(space, key) - Delete document");
println!(" create_procedure(name, code) - Create stored procedure");
println!(" drop_procedure(name) - Drop stored procedure");
println!(" call_procedure(name) - Call stored procedure");
println!(" create_backup(path) - Create backup");
println!(" restore_backup(path) - Restore backup");
println!(" enable_replication() - Enable replication");
println!(" disable_replication() - Disable replication");
println!(" sync_replication() - Sync replication");
}
}
/// Команды интерпретатора
pub enum Command {
Exit,
Help,
Empty,
LuaCode(String),
Backup {
path: String,
},
Restore {
path: String,
},
CreateProcedure {
name: String,
code: String,
},
CallProcedure {
name: String,
},
DropProcedure {
name: String,
},
}

290
src/cli/parser.rs Normal file
View File

@ -0,0 +1,290 @@
use ansi_term::Colour;
use std::collections::VecDeque;
/// Парсер команд для CLI интерфейса
pub struct CommandParser {
history: VecDeque<String>,
max_history: usize,
}
impl CommandParser {
pub fn new() -> Self {
Self {
history: VecDeque::new(),
max_history: 100,
}
}
pub fn parse(&self, input: &str) -> Result<ParsedCommand, String> {
let parts: Vec<&str> = input.trim().split_whitespace().collect();
if parts.is_empty() {
return Err("Empty command".to_string());
}
match parts[0] {
"create" => self.parse_create(&parts),
"read" => self.parse_read(&parts),
"update" => self.parse_update(&parts),
"delete" => self.parse_delete(&parts),
"create.space" => self.parse_create_space(&parts),
"delete.space" => self.parse_delete_space(&parts),
"create.tuple" => self.parse_create_tuple(&parts),
"read.tuple" => self.parse_read_tuple(&parts),
"delete.tuple" => self.parse_delete_tuple(&parts),
"create.primary.index" => self.parse_create_primary_index(&parts),
"create.secondary.index" => self.parse_create_secondary_index(&parts),
"drop.primary.index" => self.parse_drop_primary_index(&parts),
"drop.secondary.index" => self.parse_drop_secondary_index(&parts),
"search.all.index" => self.parse_search_all_index(&parts),
"search.index" => self.parse_search_index(&parts),
"begin.transaction" => self.parse_begin_transaction(&parts),
"commit.transaction" => self.parse_commit_transaction(&parts),
"rollback.transaction" => self.parse_rollback_transaction(&parts),
"mode.csv" => Ok(ParsedCommand::ModeCsv),
"export" => self.parse_export(&parts),
"import" => self.parse_import(&parts),
"exit" => Ok(ParsedCommand::Exit),
"inbox.start" => Ok(ParsedCommand::InboxStart),
_ => Err(format!("Unknown command: {}", parts[0])),
}
}
fn parse_create(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 4 {
return Err("Usage: create <collection> <key> <json_value>".to_string());
}
Ok(ParsedCommand::Create {
collection: parts[1].to_string(),
key: parts[2].to_string(),
value: serde_json::from_str(parts[3])
.map_err(|e| format!("Invalid JSON: {}", e))?,
})
}
fn parse_read(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 3 {
return Err("Usage: read <collection> <key>".to_string());
}
Ok(ParsedCommand::Read {
collection: parts[1].to_string(),
key: parts[2].to_string(),
})
}
fn parse_update(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 4 {
return Err("Usage: update <collection> <key> <json_value>".to_string());
}
Ok(ParsedCommand::Update {
collection: parts[1].to_string(),
key: parts[2].to_string(),
value: serde_json::from_str(parts[3])
.map_err(|e| format!("Invalid JSON: {}", e))?,
})
}
fn parse_delete(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 3 {
return Err("Usage: delete <collection> <key>".to_string());
}
Ok(ParsedCommand::Delete {
collection: parts[1].to_string(),
key: parts[2].to_string(),
})
}
fn parse_create_space(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: create.space <space_name>".to_string());
}
Ok(ParsedCommand::CreateSpace {
name: parts[1].to_string(),
})
}
fn parse_delete_space(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: delete.space <space_name>".to_string());
}
Ok(ParsedCommand::DeleteSpace {
name: parts[1].to_string(),
})
}
fn parse_create_tuple(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 4 {
return Err("Usage: create.tuple <space> <tuple_id> <json_value>".to_string());
}
Ok(ParsedCommand::CreateTuple {
space: parts[1].to_string(),
tuple_id: parts[2].to_string(),
value: serde_json::from_str(parts[3])
.map_err(|e| format!("Invalid JSON: {}", e))?,
})
}
fn parse_read_tuple(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 3 {
return Err("Usage: read.tuple <space> <tuple_id>".to_string());
}
Ok(ParsedCommand::ReadTuple {
space: parts[1].to_string(),
tuple_id: parts[2].to_string(),
})
}
fn parse_delete_tuple(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 3 {
return Err("Usage: delete.tuple <space> <tuple_id>".to_string());
}
Ok(ParsedCommand::DeleteTuple {
space: parts[1].to_string(),
tuple_id: parts[2].to_string(),
})
}
fn parse_create_primary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: create.primary.index <collection>".to_string());
}
Ok(ParsedCommand::CreatePrimaryIndex {
collection: parts[1].to_string(),
})
}
fn parse_create_secondary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 4 {
return Err("Usage: create.secondary.index <collection> <field_name> <index_type>".to_string());
}
Ok(ParsedCommand::CreateSecondaryIndex {
collection: parts[1].to_string(),
field: parts[2].to_string(),
index_type: parts[3].to_string(),
})
}
fn parse_drop_primary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: drop.primary.index <collection>".to_string());
}
Ok(ParsedCommand::DropPrimaryIndex {
collection: parts[1].to_string(),
})
}
fn parse_drop_secondary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 3 {
return Err("Usage: drop.secondary.index <collection> <field_name>".to_string());
}
Ok(ParsedCommand::DropSecondaryIndex {
collection: parts[1].to_string(),
field: parts[2].to_string(),
})
}
fn parse_search_all_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: search.all.index <collection>".to_string());
}
Ok(ParsedCommand::SearchAllIndex {
collection: parts[1].to_string(),
})
}
fn parse_search_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 4 {
return Err("Usage: search.index <collection> <field_name> <value>".to_string());
}
Ok(ParsedCommand::SearchIndex {
collection: parts[1].to_string(),
field: parts[2].to_string(),
value: parts[3].to_string(),
})
}
fn parse_begin_transaction(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
let tx_id = if parts.len() > 1 {
parts[1].to_string()
} else {
format!("tx_{}", chrono::Utc::now().timestamp_millis())
};
Ok(ParsedCommand::BeginTransaction {
transaction_id: tx_id,
})
}
fn parse_commit_transaction(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: commit.transaction <transaction_id>".to_string());
}
Ok(ParsedCommand::CommitTransaction {
transaction_id: parts[1].to_string(),
})
}
fn parse_rollback_transaction(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: rollback.transaction <transaction_id>".to_string());
}
Ok(ParsedCommand::RollbackTransaction {
transaction_id: parts[1].to_string(),
})
}
fn parse_export(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: export <file_path>".to_string());
}
Ok(ParsedCommand::Export {
file_path: parts[1].to_string(),
})
}
fn parse_import(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
if parts.len() < 2 {
return Err("Usage: import <file_path>".to_string());
}
Ok(ParsedCommand::Import {
file_path: parts[1].to_string(),
})
}
pub fn add_to_history(&mut self, command: String) {
if self.history.len() >= self.max_history {
self.history.pop_front();
}
self.history.push_back(command);
}
pub fn get_history(&self) -> &VecDeque<String> {
&self.history
}
}
#[derive(Debug)]
pub enum ParsedCommand {
Create { collection: String, key: String, value: serde_json::Value },
Read { collection: String, key: String },
Update { collection: String, key: String, value: serde_json::Value },
Delete { collection: String, key: String },
CreateSpace { name: String },
DeleteSpace { name: String },
CreateTuple { space: String, tuple_id: String, value: serde_json::Value },
ReadTuple { space: String, tuple_id: String },
DeleteTuple { space: String, tuple_id: String },
CreatePrimaryIndex { collection: String },
CreateSecondaryIndex { collection: String, field: String, index_type: String },
DropPrimaryIndex { collection: String },
DropSecondaryIndex { collection: String, field: String },
SearchAllIndex { collection: String },
SearchIndex { collection: String, field: String, value: String },
BeginTransaction { transaction_id: String },
CommitTransaction { transaction_id: String },
RollbackTransaction { transaction_id: String },
ModeCsv,
Export { file_path: String },
Import { file_path: String },
Exit,
InboxStart,
}

56
src/config.rs Normal file
View File

@ -0,0 +1,56 @@
use serde::Deserialize;
#[derive(Deserialize, Clone)]
pub struct Config {
pub http_port: u16,
pub db_path: String,
pub log_level: String,
// Параметры кластера
pub cluster_enabled: bool,
pub node_id: String,
pub cluster_nodes: Vec<String>,
// Параметры ACL
pub acl_enabled: bool,
pub admin_users: Vec<String>,
// Параметры репликации
pub master_master_replication: bool,
pub replication_nodes: Vec<String>,
// Параметры HTTPS
pub https_enabled: bool,
pub https_port: u16,
pub cert_file: String,
pub key_file: String,
// Параметры HTTP2
pub http2_enabled: bool,
// Настройки хранимых процедур
pub stored_procedures_path: String,
}
impl Default for Config {
fn default() -> Self {
Self {
http_port: 8080,
db_path: "./data".to_string(),
log_level: "info".to_string(),
cluster_enabled: false,
node_id: "node_1".to_string(),
cluster_nodes: vec![],
acl_enabled: false,
admin_users: vec![],
master_master_replication: false,
replication_nodes: vec![],
https_enabled: false,
https_port: 8443,
cert_file: "./cert.pem".to_string(),
key_file: "./key.pem".to_string(),
http2_enabled: false,
stored_procedures_path: "./procedures".to_string(),
}
}
}

1294
src/db.rs Normal file

File diff suppressed because it is too large Load Diff

453
src/http.rs Normal file
View File

@ -0,0 +1,453 @@
//[file name]: http.rs
use warp::Filter;
use std::sync::Arc;
use std::convert::Infallible;
use serde::{Deserialize, Serialize};
use crate::db::FutriixDB;
use crate::config::Config;
use std::collections::HashMap;
use std::path::Path;
use tokio::fs;
use serde_json::Value;
/// Запуск HTTP сервера
pub async fn start_http_server(db: Arc<FutriixDB>, config: Config) {
let db_filter = warp::any().map(move || db.clone());
// Новый маршрут для быстрых команд через URL
let quick_commands = warp::path!("quick" / String / String / String / String)
.and(warp::post())
.and(db_filter.clone())
.and_then(quick_command_handler);
let quick_commands_get = warp::path!("quick" / String / String / String)
.and(warp::get())
.and(db_filter.clone())
.and_then(quick_command_get_handler);
// Маршруты для работы с документами
let create_space = warp::path!("space" / String)
.and(warp::post())
.and(db_filter.clone())
.and_then(create_space_handler);
let delete_space = warp::path!("space" / String)
.and(warp::delete())
.and(db_filter.clone())
.and_then(delete_space_handler);
let insert_document = warp::path!("document" / String / String)
.and(warp::post())
.and(warp::body::json())
.and(db_filter.clone())
.and_then(insert_document_handler);
let get_document = warp::path!("document" / String / String)
.and(warp::get())
.and(db_filter.clone())
.and_then(get_document_handler);
let update_document = warp::path!("document" / String / String)
.and(warp::put())
.and(warp::body::json())
.and(db_filter.clone())
.and_then(update_document_handler);
let delete_document = warp::path!("document" / String / String)
.and(warp::delete())
.and(db_filter.clone())
.and_then(delete_document_handler);
// Маршруты для работы с хранимыми процедурами
let create_procedure = warp::path!("procedure" / String)
.and(warp::post())
.and(warp::body::json())
.and(db_filter.clone())
.and_then(create_procedure_handler);
let call_procedure = warp::path!("procedure" / String)
.and(warp::get())
.and(db_filter.clone())
.and_then(call_procedure_handler);
let drop_procedure = warp::path!("procedure" / String)
.and(warp::delete())
.and(db_filter.clone())
.and_then(drop_procedure_handler);
let list_procedures = warp::path!("procedures")
.and(warp::get())
.and(db_filter.clone())
.and_then(list_procedures_handler);
// Маршруты для работы с бэкапами
let create_backup = warp::path!("backup")
.and(warp::post())
.and(warp::body::json())
.and(db_filter.clone())
.and_then(create_backup_handler);
let restore_backup = warp::path!("backup" / "restore")
.and(warp::post())
.and(warp::body::json())
.and(db_filter.clone())
.and_then(restore_backup_handler);
// Маршруты для работы с репликацией
let enable_replication = warp::path!("replication" / "enable")
.and(warp::post())
.and(db_filter.clone())
.and_then(enable_replication_handler);
let disable_replication = warp::path!("replication" / "disable")
.and(warp::post())
.and(db_filter.clone())
.and_then(disable_replication_handler);
// Маршруты для работы с MD5
let md5_hash = warp::path!("md5" / String)
.and(warp::get())
.and_then(md5_handler);
// Статические файлы
let static_files = warp::path("static")
.and(warp::fs::dir("./static"));
// Корневой маршрут для обслуживания HTML файлов
let root = warp::path::end()
.and(warp::get())
.and(warp::fs::file("./static/index.html"));
// Обслуживание статических файлов с правильными MIME типами
let static_assets = warp::path("assets")
.and(warp::fs::dir("./assets"));
// Комбинируем все маршруты
let routes = warp::any()
.and(
quick_commands
.or(quick_commands_get)
.or(create_space)
.or(delete_space)
.or(insert_document)
.or(get_document)
.or(update_document)
.or(delete_document)
.or(create_procedure)
.or(call_procedure)
.or(drop_procedure)
.or(list_procedures)
.or(create_backup)
.or(restore_backup)
.or(enable_replication)
.or(disable_replication)
.or(md5_hash)
.or(static_files)
.or(static_assets)
.or(root)
)
.with(warp::cors().allow_any_origin())
.with(warp::log("http"));
let port = config.http_port;
println!("HTTP server started on port {}", port);
if config.https_enabled {
// Запуск HTTPS сервера
let https_port = config.https_port;
println!("HTTPS server started on port {}", https_port);
// В реальной реализации здесь была бы настройка TLS
warp::serve(routes)
.run(([127, 0, 0, 1], port))
.await;
} else {
// Запуск HTTP сервера
warp::serve(routes)
.run(([127, 0, 0, 1], port))
.await;
}
}
// Обработчик быстрых команд через URL
async fn quick_command_handler(
command: String,
space: String,
key: String,
value: String,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match command.as_str() {
"create" => {
let json_value: Result<Value, _> = serde_json::from_str(&value);
match json_value {
Ok(value) => {
match db.insert(&space, &key, value) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document created"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&format!("Invalid JSON: {}", e)))),
}
}
"get" => {
match db.get(&space, &key) {
Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))),
Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
"update" => {
let json_value: Result<Value, _> = serde_json::from_str(&value);
match json_value {
Ok(value) => {
match db.update(&space, &key, value) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document updated"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&format!("Invalid JSON: {}", e)))),
}
}
"delete" => {
match db.delete(&space, &key) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document deleted"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
"create_space" => {
match db.create_space(&space) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space created"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
"delete_space" => {
match db.delete_space(&space) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space deleted"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
_ => Ok(warp::reply::json(&ApiResponse::error(&format!("Unknown command: {}", command)))),
}
}
// Обработчик быстрых GET команд через URL (для получения данных)
async fn quick_command_get_handler(
command: String,
space: String,
key: String,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match command.as_str() {
"get" => {
match db.get(&space, &key) {
Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))),
Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
_ => Ok(warp::reply::json(&ApiResponse::error(&format!("Unknown command: {}", command)))),
}
}
// Обработчик MD5 хеширования
async fn md5_handler(text: String) -> Result<impl warp::Reply, Infallible> {
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(text.as_bytes());
let result = hasher.finalize();
let hash = format!("{:x}", result);
Ok(warp::reply::json(&ApiResponse::data(hash)))
}
// Обработчики для работы с пространствами
async fn create_space_handler(name: String, db: Arc<FutriixDB>) -> Result<impl warp::Reply, Infallible> {
match db.create_space(&name) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space created"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn delete_space_handler(name: String, db: Arc<FutriixDB>) -> Result<impl warp::Reply, Infallible> {
match db.delete_space(&name) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space deleted"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
// Обработчики для работы с документами
async fn insert_document_handler(
space: String,
key: String,
value: serde_json::Value,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.insert(&space, &key, value) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document inserted"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn get_document_handler(
space: String,
key: String,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.get(&space, &key) {
Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))),
Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn update_document_handler(
space: String,
key: String,
value: serde_json::Value,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.update(&space, &key, value) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document updated"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn delete_document_handler(
space: String,
key: String,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.delete(&space, &key) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document deleted"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
// Обработчики для работы с хранимыми процедурами
async fn create_procedure_handler(
name: String,
body: CreateProcedureRequest,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.create_procedure(&name, &body.code) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Procedure created"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn call_procedure_handler(
name: String,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.get_procedure(&name) {
Some(code) => {
// В реальной реализации здесь выполнялся бы код процедуры
Ok(warp::reply::json(&ApiResponse::data(code)))
}
None => Ok(warp::reply::json(&ApiResponse::error("Procedure not found"))),
}
}
async fn drop_procedure_handler(
name: String,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.drop_procedure(&name) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Procedure dropped"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn list_procedures_handler(
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
let procedures = db.list_procedures();
Ok(warp::reply::json(&ApiResponse::data(procedures)))
}
// Обработчики для работы с бэкапами
async fn create_backup_handler(
body: BackupRequest,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.create_backup(&body.path) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Backup created"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
async fn restore_backup_handler(
body: BackupRequest,
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
match db.restore_backup(&body.path) {
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Backup restored"))),
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
}
}
// Обработчики для работы с репликацией
async fn enable_replication_handler(
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
db.enable_replication();
Ok(warp::reply::json(&ApiResponse::success("Replication enabled")))
}
async fn disable_replication_handler(
db: Arc<FutriixDB>,
) -> Result<impl warp::Reply, Infallible> {
db.disable_replication();
Ok(warp::reply::json(&ApiResponse::success("Replication disabled")))
}
// Структуры для запросов и ответов API
#[derive(Deserialize)]
struct CreateProcedureRequest {
code: String,
}
#[derive(Deserialize)]
struct BackupRequest {
path: String,
}
#[derive(Serialize)]
struct ApiResponse<T = ()> {
success: bool,
message: Option<String>,
data: Option<T>,
}
impl ApiResponse<()> {
fn success(message: &str) -> Self {
Self {
success: true,
message: Some(message.to_string()),
data: None,
}
}
fn error(message: &str) -> Self {
Self {
success: false,
message: Some(message.to_string()),
data: None,
}
}
}
impl<T> ApiResponse<T> {
fn data(data: T) -> Self {
Self {
success: true,
message: None,
data: Some(data),
}
}
}

23
src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
//lib.rs-это корневой файл библиотеки (library crate) в проекте Rust.
// Объявляет все основные модули (config, db, http, cli, lua)
//Re-export'ит ключевые структуры и функции для внешнего использования
// Служит точкой входа для других crates, которые хотят использовать futriix как библиотеку
pub mod config;
pub mod db;
pub mod http;
pub mod cli;
pub mod lua;
// Re-export основных компонентов
pub use db::FutriixDB;
pub use cli::CommandParser;
pub use config::Config;
pub use lua::LuaInterpreter;
// Re-export HTTP функций
pub use http::start_http_server;
// Re-export функций шифрования
pub use db::EncryptionManager;

423
src/lua.rs Normal file
View File

@ -0,0 +1,423 @@
//[file name]: lua.rs
use rlua::{Lua, Function, Value as LuaValue};
use std::error::Error;
use crate::db::FutriixDB;
use serde_json::Value;
use rustyline::error::ReadlineError;
use rustyline::{Editor, CompletionType, Config as EditorConfig};
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use chrono::Utc;
use md5::{Digest, Md5};
/// Lua интерпретатор с поддержкой команд СУБД
pub struct LuaInterpreter {
db: FutriixDB,
lua: Lua,
editor: Editor<(), rustyline::history::DefaultHistory>,
}
impl LuaInterpreter {
pub fn new(db: FutriixDB) -> Self {
let lua = Lua::new();
let config = EditorConfig::builder()
.completion_type(CompletionType::List)
.build();
let editor = Editor::with_config(config).unwrap();
Self { db, lua, editor }
}
pub async fn run(&mut self) -> Result<(), Box<dyn Error>> {
// Загрузка истории команд
let history_path = "history.txt";
if let Err(_) = self.editor.load_history(history_path) {
println!("No previous command history found.");
Self::log_to_appsr_file("[INFO] No previous command history found.");
}
// Регистрация функций СУБД в Lua
self.register_db_functions()?;
println!("{}", ansi_term::Colour::RGB(0x00, 0xbf, 0xff).paint("Lua interpreter ready. Type 'exit' to quit."));
Self::log_to_appsr_file("[INFO] Lua interpreter ready. Type 'exit' to quit.");
loop {
let readline = self.editor.readline(&ansi_term::Colour::RGB(0xb6, 0xff, 0x76).paint("lua> ").to_string());
match readline {
Ok(line) => {
if line.trim().eq_ignore_ascii_case("exit") {
Self::log_to_appsr_file("[INFO] User exited Lua interpreter");
break;
}
let _ = self.editor.add_history_entry(line.as_str());
// Логируем введённую команду
Self::log_to_appsr_file(&format!("[COMMAND] lua> {}", &line));
// Обработка специальных команд СУБД
if let Some(result) = self.handle_special_commands(&line).await {
println!("{}", result);
Self::log_to_appsr_file(&format!("[RESULT] {}", &result));
continue;
}
// Выполнение как Lua кода
match self.execute_lua(&line).await {
Ok(result) => {
if !result.is_empty() {
println!("{}", result);
Self::log_to_appsr_file(&format!("[RESULT] {}", &result));
} else {
Self::log_to_appsr_file("[RESULT] Command executed successfully");
}
}
Err(e) => {
let error_msg = format!("Error: {}", e);
eprintln!("{}", ansi_term::Colour::Red.paint(&error_msg));
Self::log_to_appsr_file(&format!("[ERROR] {}", &error_msg));
}
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
Self::log_to_appsr_file("[INFO] CTRL-C received");
break;
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
Self::log_to_appsr_file("[INFO] CTRL-D received");
break;
}
Err(err) => {
let error_msg = format!("Error: {:?}", err);
eprintln!("{}", error_msg);
Self::log_to_appsr_file(&format!("[ERROR] {}", error_msg));
break;
}
}
}
// Сохранение истории команд
self.editor.save_history(history_path)
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
Ok(())
}
/// Логирование в файл appsr.txt
fn log_to_appsr_file(message: &str) {
let timestamp = Utc::now().to_rfc3339();
let log_entry = format!("{} {}\n", timestamp, message);
if let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open("history_appsr.txt")
{
let _ = file.write_all(log_entry.as_bytes());
}
}
fn register_db_functions(&self) -> Result<(), Box<dyn Error>> {
let lua = &self.lua;
let globals = lua.globals();
// Функции для работы с пространствами
let db_clone_1 = self.db.clone();
let create_space = lua.create_function(move |_, name: String| {
db_clone_1.create_space(&name)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("create_space", create_space)?;
let db_clone_2 = self.db.clone();
let delete_space = lua.create_function(move |_, name: String| {
db_clone_2.delete_space(&name)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("delete_space", delete_space)?;
// Функции для работы с документами
let db_clone_3 = self.db.clone();
let insert = lua.create_function(move |_, (space, key, value): (String, String, String)| {
let json_value: Value = serde_json::from_str(&value)
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
db_clone_3.insert(&space, &key, json_value)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("insert", insert)?;
let db_clone_4 = self.db.clone();
let get = lua.create_function(move |_, (space, key): (String, String)| {
match db_clone_4.get(&space, &key) {
Ok(Some(value)) => {
let json_str = serde_json::to_string(&value)
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
Ok(json_str)
}
Ok(None) => Ok("null".to_string()),
Err(e) => Err(rlua::Error::RuntimeError(e)),
}
})?;
globals.set("get", get)?;
let db_clone_5 = self.db.clone();
let update = lua.create_function(move |_, (space, key, value): (String, String, String)| {
let json_value: Value = serde_json::from_str(&value)
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
db_clone_5.update(&space, &key, json_value)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("update", update)?;
let db_clone_6 = self.db.clone();
let delete = lua.create_function(move |_, (space, key): (String, String)| {
db_clone_6.delete(&space, &key)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("delete", delete)?;
// Функции для работы с кортежами
let db_clone_7 = self.db.clone();
let create_tuple = lua.create_function(move |_, (space, tuple_id, value): (String, String, String)| {
let json_value: Value = serde_json::from_str(&value)
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
db_clone_7.create_tuple(&space, &tuple_id, json_value)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("create_tuple", create_tuple)?;
let db_clone_8 = self.db.clone();
let read_tuple = lua.create_function(move |_, (space, tuple_id): (String, String)| {
match db_clone_8.read_tuple(&space, &tuple_id) {
Ok(Some(value)) => {
let json_str = serde_json::to_string(&value)
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
Ok(json_str)
}
Ok(None) => Ok("null".to_string()),
Err(e) => Err(rlua::Error::RuntimeError(e)),
}
})?;
globals.set("read_tuple", read_tuple)?;
let db_clone_9 = self.db.clone();
let delete_tuple = lua.create_function(move |_, (space, tuple_id): (String, String)| {
db_clone_9.delete_tuple(&space, &tuple_id)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("delete_tuple", delete_tuple)?;
// Функции для работы с транзакциями
let db_clone_10 = self.db.clone();
let begin_transaction = lua.create_function(move |_, transaction_id: String| {
db_clone_10.begin_transaction(transaction_id)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("begin_transaction", begin_transaction)?;
// Функции для работы с индексами
let db_clone_11 = self.db.clone();
let create_primary_index = lua.create_function(move |_, collection: String| {
db_clone_11.create_primary_index(&collection)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("create_primary_index", create_primary_index)?;
let db_clone_12 = self.db.clone();
let create_secondary_index = lua.create_function(move |_, (collection, field, index_type): (String, String, String)| {
db_clone_12.create_secondary_index(&collection, &field, &index_type)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("create_secondary_index", create_secondary_index)?;
// Функции для работы с хранимыми процедурами
let db_clone_13 = self.db.clone();
let create_procedure = lua.create_function(move |_, (name, code): (String, String)| {
db_clone_13.create_procedure(&name, &code)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("create_procedure", create_procedure)?;
let db_clone_14 = self.db.clone();
let drop_procedure = lua.create_function(move |_, name: String| {
db_clone_14.drop_procedure(&name)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("drop_procedure", drop_procedure)?;
let db_clone_15 = self.db.clone();
let call_procedure = lua.create_function(move |_, name: String| {
match db_clone_15.get_procedure(&name) {
Some(code) => {
// Здесь должна быть логика выполнения процедуры
// В упрощённой реализации просто возвращаем код
Ok(code)
}
None => Err(rlua::Error::RuntimeError("Procedure not found".to_string())),
}
})?;
globals.set("call_procedure", call_procedure)?;
// Функции для работы с бэкапами
let db_clone_16 = self.db.clone();
let create_backup = lua.create_function(move |_, backup_path: String| {
db_clone_16.create_backup(&backup_path)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("create_backup", create_backup)?;
let db_clone_17 = self.db.clone();
let restore_backup = lua.create_function(move |_, backup_path: String| {
db_clone_17.restore_backup(&backup_path)
.map_err(|e| rlua::Error::RuntimeError(e))
})?;
globals.set("restore_backup", restore_backup)?;
// Функции для работы с репликацией
let db_clone_18 = self.db.clone();
let enable_replication = lua.create_function(move |_, ()| {
db_clone_18.enable_replication();
Ok(())
})?;
globals.set("enable_replication", enable_replication)?;
let db_clone_19 = self.db.clone();
let disable_replication = lua.create_function(move |_, ()| {
db_clone_19.disable_replication();
Ok(())
})?;
globals.set("disable_replication", disable_replication)?;
// Функция для MD5 хеширования
let md5_hash = lua.create_function(move |_, text: String| {
let mut hasher = Md5::new();
hasher.update(text.as_bytes());
let result = hasher.finalize();
Ok(format!("{:x}", result))
})?;
globals.set("md5", md5_hash)?;
Ok(())
}
async fn handle_special_commands(&self, line: &str) -> Option<String> {
let parts: Vec<&str> = line.trim().split_whitespace().collect();
if parts.is_empty() {
return None;
}
match parts[0].to_lowercase().as_str() {
"backup" => {
if parts.len() >= 2 {
let backup_path = parts[1];
match self.db.create_backup(backup_path) {
Ok(()) => Some("Backup created successfully".to_string()),
Err(e) => Some(format!("Backup error: {}", e)),
}
} else {
Some("Usage: backup <backup_file_path>".to_string())
}
}
"restore" => {
if parts.len() >= 2 {
let backup_path = parts[1];
match self.db.restore_backup(backup_path) {
Ok(()) => Some("Backup restored successfully".to_string()),
Err(e) => Some(format!("Restore error: {}", e)),
}
} else {
Some("Usage: restore <backup_file_path>".to_string())
}
}
"create" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => {
if parts.len() >= 4 {
let name = parts[2];
// Для создания процедуры нужен код, который обычно многострочный
// В упрощённой реализации просто возвращаем инструкцию
Some("Use: create_procedure('name', 'lua_code') in Lua".to_string())
} else {
Some("Usage: create procedure <name> <code>".to_string())
}
}
"call" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => {
if parts.len() >= 3 {
let name = parts[2];
match self.db.get_procedure(name) {
Some(ref code) => {
// Выполняем код процедуры
match self.execute_lua(code).await {
Ok(result) => Some(format!("Procedure result: {}", result)),
Err(e) => Some(format!("Procedure execution error: {}", e)),
}
}
None => Some(format!("Procedure '{}' not found", name)),
}
} else {
Some("Usage: call procedure <name>".to_string())
}
}
"drop" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => {
if parts.len() >= 3 {
let name = parts[2];
match self.db.drop_procedure(name) {
Ok(()) => Some("Procedure dropped successfully".to_string()),
Err(e) => Some(format!("Drop procedure error: {}", e)),
}
} else {
Some("Usage: drop procedure <name>".to_string())
}
}
"md5" => {
if parts.len() >= 2 {
let text = parts[1];
let mut hasher = Md5::new();
hasher.update(text.as_bytes());
let result = hasher.finalize();
Some(format!("MD5 hash: {:x}", result))
} else {
Some("Usage: md5 <text>".to_string())
}
}
_ => None,
}
}
async fn execute_lua(&self, code: &str) -> Result<String, Box<dyn Error>> {
let result = self.lua.load(code).eval::<LuaValue>()?;
match result {
LuaValue::String(s) => Ok(s.to_str()?.to_string()),
LuaValue::Number(n) => Ok(n.to_string()),
LuaValue::Boolean(b) => Ok(b.to_string()),
LuaValue::Nil => Ok("".to_string()),
LuaValue::Table(t) => {
let mut result = HashMap::new();
for pair in t.pairs::<LuaValue, LuaValue>() {
let (key, value) = pair?;
result.insert(
Self::lua_value_to_string(key)?,
Self::lua_value_to_string(value)?,
);
}
Ok(serde_json::to_string(&result)?)
}
_ => Ok(format!("{:?}", result)),
}
}
fn lua_value_to_string(value: LuaValue) -> Result<String, Box<dyn Error>> {
match value {
LuaValue::String(s) => Ok(s.to_str()?.to_string()),
LuaValue::Number(n) => Ok(n.to_string()),
LuaValue::Boolean(b) => Ok(b.to_string()),
LuaValue::Nil => Ok("null".to_string()),
_ => Ok(format!("{:?}", value)),
}
}
}

55
src/lua/bindings.rs Normal file
View File

@ -0,0 +1,55 @@
use rlua::{Lua, Result as LuaResult};
use std::sync::Arc;
use crate::db::FutriixDB;
/// Регистрация функций СУБД в Lua окружении
pub fn register_db_functions(lua: &Lua, db: FutriixDB) -> LuaResult<()> {
let globals = lua.globals();
// Клонируем db для каждого замыкания
let db_clone_1 = db.clone();
let db_create = lua.create_function(move |_, (collection, key, value): (String, String, String)| {
let json_value: serde_json::Value = serde_json::from_str(&value)
.map_err(|e| rlua::Error::external(e))?;
db_clone_1.create_document(&collection, &key, json_value)
.map_err(|e| rlua::Error::external(e))?;
Ok(())
})?;
globals.set("db_create", db_create)?;
let db_clone_2 = db.clone();
let db_read = lua.create_function(move |_, (collection, key): (String, String)| {
match db_clone_2.read_document(&collection, &key) {
Ok(Some(value)) => Ok(serde_json::to_string(&value).map_err(|e| rlua::Error::external(e))?),
Ok(None) => Ok("null".to_string()),
Err(e) => Err(rlua::Error::external(e)),
}
})?;
globals.set("db_read", db_read)?;
let db_clone_3 = db.clone();
let db_update = lua.create_function(move |_, (collection, key, value): (String, String, String)| {
let json_value: serde_json::Value = serde_json::from_str(&value)
.map_err(|e| rlua::Error::external(e))?;
db_clone_3.update_document(&collection, &key, json_value)
.map_err(|e| rlua::Error::external(e))?;
Ok(())
})?;
globals.set("db_update", db_update)?;
let db_clone_4 = db.clone();
let db_delete = lua.create_function(move |_, (collection, key): (String, String)| {
db_clone_4.delete_document(&collection, &key)
.map_err(|e| rlua::Error::external(e))?;
Ok(())
})?;
globals.set("db_delete", db_delete)?;
// Регистрация других функций...
Ok(())
}

168
src/main.rs Normal file
View File

@ -0,0 +1,168 @@
use ansi_term::Colour;
use std::error::Error;
use std::sync::Arc;
use std::env;
use std::path::Path;
use std::fs::OpenOptions;
use std::io::Write;
use chrono::Utc;
// Импортируем из библиотечной части
use futriix::{Config, cli::CommandParser, lua::LuaInterpreter, db::FutriixDB, http};
/// Главная точка входа приложения
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Вывод заголовка приложения с пустой строкой перед
println!();
println!("{}", Colour::RGB(0, 191, 255).paint("futriiX database server"));
println!("{}", Colour::RGB(0, 191, 255).paint("futriix 3i²(by 26.10.2025)"));
// Инициализация логгера
env_logger::init();
log::info!("[INFO] Starting futriiX server...");
// Логирование в файл appsr.txt
log_to_appsr_file("[INFO] Starting futriiX server...");
// Получаем аргументы командной строки
let args: Vec<String> = env::args().collect();
let config_path = if args.len() > 1 {
&args[1]
} else {
"config/config.toml"
};
// Загрузка конфигурации
let config = match load_config(config_path) {
Ok(cfg) => {
let msg = format!("[INFO] Configuration loaded successfully from {}", config_path);
log::info!("{}", msg);
log_to_appsr_file(&msg);
cfg
}
Err(e) => {
let error_msg = format!("Error loading config from {}: {}", config_path, e);
eprintln!("{}", Colour::Red.paint(&error_msg));
eprintln!("{}", Colour::Yellow.paint("Using default configuration..."));
log_to_appsr_file(&format!("[ERROR] {}", error_msg));
log_to_appsr_file("[INFO] Using default configuration...");
// Используем значения по умолчанию
Config {
http_port: 8080,
db_path: "./data".to_string(),
log_level: "info".to_string(),
cluster_enabled: false,
node_id: "node_1".to_string(),
cluster_nodes: vec![],
acl_enabled: false,
admin_users: vec![],
master_master_replication: false,
replication_nodes: vec![],
https_enabled: false,
https_port: 8443,
cert_file: "./cert.pem".to_string(),
key_file: "./key.pem".to_string(),
http2_enabled: false,
stored_procedures_path: "./procedures".to_string(),
}
}
};
// Инициализация СУБД
let db = match FutriixDB::new(&config).await {
Ok(db_instance) => {
let msg = "[INFO] Database initialized successfully";
log::info!("{}", msg);
log_to_appsr_file(msg);
db_instance
}
Err(e) => {
let error_msg = format!("[ERROR] Failed to initialize database: {}", e);
log::error!("{}", error_msg);
log_to_appsr_file(&error_msg);
return Err(e.into());
}
};
// Запуск HTTP сервера в фоновом режиме
let db_arc = Arc::new(db.clone());
let config_clone = config.clone();
tokio::spawn(async move {
let msg = format!("[INFO] HTTP server starting on port {}", config_clone.http_port);
log::info!("{}", msg);
log_to_appsr_file(&msg);
http::start_http_server(db_arc, config_clone).await;
});
// Вывод информации о состоянии
let cluster_status = if config.cluster_enabled {
format!("Cluster status: enabled (node: {})", config.node_id)
} else {
"Cluster status: single node (standalone mode)".to_string()
};
println!("{}", Colour::RGB(0, 255, 127).paint(&cluster_status));
log_to_appsr_file(&format!("[INFO] {}", cluster_status));
let acl_status = if config.acl_enabled {
"ACL: enabled"
} else {
"ACL: disabled"
};
println!("{}", Colour::RGB(0, 255, 127).paint(acl_status));
log_to_appsr_file(&format!("[INFO] {}", acl_status));
let replication_status = if config.master_master_replication {
"Master-Master replication: enabled"
} else {
"Master-Master replication: disabled"
};
println!("{}", Colour::RGB(0, 255, 127).paint(replication_status));
log_to_appsr_file(&format!("[INFO] {}", replication_status));
let history_path = "history.txt";
let history_abs_path = std::env::current_dir()?
.join(history_path)
.to_string_lossy()
.to_string();
println!("Command history file: {}", history_abs_path);
log_to_appsr_file(&format!("[INFO] Command history file: {}", history_abs_path));
// Запуск Lua интерпретатора
let mut lua_interpreter = LuaInterpreter::new(db);
if let Err(e) = lua_interpreter.run().await {
let error_msg = format!("[ERROR] Lua interpreter error: {}", e);
log::error!("{}", error_msg);
log_to_appsr_file(&error_msg);
return Err(e);
}
let msg = "[INFO] futriiX server shutdown completed";
log::info!("{}", msg);
log_to_appsr_file(msg);
Ok(())
}
/// Загрузка конфигурации из файла
fn load_config(config_path: &str) -> Result<Config, Box<dyn Error>> {
let config_content = std::fs::read_to_string(config_path)?;
let config: Config = toml::from_str(&config_content)?;
Ok(config)
}
/// Логирование в файл appsr.txt
fn log_to_appsr_file(message: &str) {
let timestamp = Utc::now().to_rfc3339();
let log_entry = format!("{} {}\n", timestamp, message);
if let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open("history_appsr.txt")
{
let _ = file.write_all(log_entry.as_bytes());
}
}