From 499efed09927289eefd9c5420597463260f03a55 Mon Sep 17 00:00:00 2001 From: Warren Date: Fri, 12 Jun 2026 20:59:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E7=B5=84=E5=8C=96=E9=87=8D=E6=A7=8B?= =?UTF-8?q?=20Phase=201-2=E5=AE=8C=E6=88=90=EF=BC=9ACLI=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E5=88=86=E7=A6=BB=20+=20API=E6=A8=A1=E5=9D=97=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E7=AB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1:CLI架构重构 - main.rs: 509行 → 21行(简化96%) - 新增cli模块:框架命令与应用命令分离 - cli/framework.rs (394行): Display/Render/Config/Scan/Hash/WebDAV/iSCSI - cli/apps/download_center.rs (59行): ImportMarkdown/SshServer/Sftp - 编译成功,CLI命令正确识别(11个命令) Phase 2:API模块结构创建 - 新增api模块目录结构:api/handlers/ - 为未来handler模块预留空间: - tree.rs: FileTree CRUD - file.rs: 文件流/渲染 - upload.rs: 上传处理 - auth.rs: 认证 - config.rs: 配置管理 - system.rs: 系统健康检查 - view.rs: 分类/系列视图 - static.rs: 静态页面 - server.rs保持稳定(2409行),降低重构风险 架构优势: - 清晰的框架/应用分离 - 降低耦合度,便于后续维护 - 为新功能提供清晰的模块空间 - 保持现有功能稳定运行 --- markbase-core/src/api/handlers/mod.rs | 16 + markbase-core/src/api/mod.rs | 12 + markbase-core/src/cli/apps/download_center.rs | 60 +++ markbase-core/src/cli/apps/mod.rs | 11 + markbase-core/src/cli/framework.rs | 395 ++++++++++++++ markbase-core/src/cli/mod.rs | 5 + markbase-core/src/lib.rs | 2 + markbase-core/src/main.rs | 507 +----------------- 8 files changed, 511 insertions(+), 497 deletions(-) create mode 100644 markbase-core/src/api/handlers/mod.rs create mode 100644 markbase-core/src/api/mod.rs create mode 100644 markbase-core/src/cli/apps/download_center.rs create mode 100644 markbase-core/src/cli/apps/mod.rs create mode 100644 markbase-core/src/cli/framework.rs create mode 100644 markbase-core/src/cli/mod.rs diff --git a/markbase-core/src/api/handlers/mod.rs b/markbase-core/src/api/handlers/mod.rs new file mode 100644 index 0000000..753d89d --- /dev/null +++ b/markbase-core/src/api/handlers/mod.rs @@ -0,0 +1,16 @@ +// API Handlers Module +// +// This module provides space for future modular API handlers. +// Current handlers are implemented in server.rs for stability. +// +// Future migration plan: +// - tree.rs: FileTree CRUD operations +// - file.rs: File streaming/rendering +// - upload.rs: File upload handling +// - auth.rs: Authentication handlers +// - config.rs: Configuration management +// - system.rs: System health/metrics +// - view.rs: Category/Series view handlers +// - static.rs: Static page handlers + +pub use crate::server::AppState; \ No newline at end of file diff --git a/markbase-core/src/api/mod.rs b/markbase-core/src/api/mod.rs new file mode 100644 index 0000000..20a4340 --- /dev/null +++ b/markbase-core/src/api/mod.rs @@ -0,0 +1,12 @@ +pub mod handlers; + +// API Module - Future Modular Architecture +// +// This module provides the structure for modular API handlers. +// Current implementation remains in server.rs for stability. +// +// Benefits of this architecture: +// - Clear separation of concerns +// - Easier maintenance for new features +// - Gradual migration path from server.rs +// - Independent testing per handler module \ No newline at end of file diff --git a/markbase-core/src/cli/apps/download_center.rs b/markbase-core/src/cli/apps/download_center.rs new file mode 100644 index 0000000..38c80e3 --- /dev/null +++ b/markbase-core/src/cli/apps/download_center.rs @@ -0,0 +1,60 @@ +use clap::Subcommand; +use rusqlite::Connection; +use anyhow::Context; + +#[derive(Subcommand)] +pub enum DownloadCenterCommands { + ImportMarkdown { + #[arg(short, long, default_value = "accusys")] + user: String, + #[arg(short, long)] + tree_type: String, + }, + SshServer { + #[arg(short, long, default_value = "2024")] + port: u16, + }, + Sftp { + #[arg(short, long, default_value = "2023")] + port: u16, + #[arg(short, long)] + user: String, + }, +} + +pub async fn handle_download_center_command(cmd: DownloadCenterCommands) -> anyhow::Result<()> { + match cmd { + DownloadCenterCommands::ImportMarkdown { user, tree_type } => { + let db_path = format!("data/users/{}.sqlite", user); + let conn = Connection::open(&db_path) + .with_context(|| format!("Failed to open database: {}", db_path))?; + + println!("Importing Markdown files to {} virtual tree...", tree_type); + + if tree_type == "categories" { + crate::import_markdown::import_categories_to_db(&conn, &user, &tree_type)?; + println!("Categories imported successfully!"); + } else if tree_type == "series" { + crate::import_markdown::import_series_to_db(&conn, &user, &tree_type)?; + println!("Series imported successfully!"); + } else { + eprintln!("Invalid tree_type: {}. Use 'categories' or 'series'", tree_type); + } + } + DownloadCenterCommands::SshServer { port } => { + println!("=== MarkBase SSH Server (Hand-written Implementation) ==="); + println!("Port: {}", port); + println!("Implementation: SSH-2.0-MarkBaseSSH_1.0"); + println!("Features: SSH + SFTP + SCP + rsync"); + println!("Security: ⭐⭐⭐⭐⭐ (RustCrypto authoritative libraries)"); + println!(); + + crate::ssh_server::server::run_ssh_server(Some(port))?; + } + DownloadCenterCommands::Sftp { port, user } => { + println!("SFTP server command is currently disabled (old implementation)"); + println!("Use 'ssh-server' command for the new SSH+SFTP implementation"); + } + } + Ok(()) +} \ No newline at end of file diff --git a/markbase-core/src/cli/apps/mod.rs b/markbase-core/src/cli/apps/mod.rs new file mode 100644 index 0000000..39681b5 --- /dev/null +++ b/markbase-core/src/cli/apps/mod.rs @@ -0,0 +1,11 @@ +pub mod download_center; + +pub use download_center::{DownloadCenterCommands, handle_download_center_command}; + +use clap::Subcommand; + +#[derive(Subcommand)] +pub enum AppCommands { + #[command(flatten)] + DownloadCenter(DownloadCenterCommands), +} \ No newline at end of file diff --git a/markbase-core/src/cli/framework.rs b/markbase-core/src/cli/framework.rs new file mode 100644 index 0000000..4b43a20 --- /dev/null +++ b/markbase-core/src/cli/framework.rs @@ -0,0 +1,395 @@ +use clap::{Parser, Subcommand}; +use std::path::Path; +use axum::{extract::Request, response::IntoResponse, routing::any, Extension, Router}; + +#[derive(Parser)] +#[command(name = "markbase", about = "Momentry Display Engine")] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + #[command(flatten)] + Framework(FrameworkCommands), + #[command(flatten)] + App(crate::cli::apps::AppCommands), +} + +#[derive(Subcommand)] +pub enum FrameworkCommands { + Display { + #[arg(short, long, default_value = "11438")] + port: u16, + #[arg(short, long)] + file: Option, + }, + Render { + file: String, + #[arg(short, long)] + output: Option, + }, + Config { + #[command(subcommand)] + action: ConfigCommands, + }, + Scan { + #[arg(short, long)] + user: String, + #[arg(short, long)] + dir: String, + #[arg(short, long, default_value = "100")] + batch: usize, + #[arg(short, long, default_value = "true")] + skip_hash: bool, + #[arg(short, long, default_value = "4")] + threads: usize, + }, + Hash { + #[arg(short, long)] + user: String, + #[arg(short, long, default_value = "4")] + threads: usize, + }, + BcryptTest { + #[arg(short, long, default_value = "demo123")] + password: String, + #[arg(long)] + verify_hash: Option, + }, + WebDAV { + #[command(subcommand)] + action: WebDAVCommands, + }, + Iscsi { + #[command(subcommand)] + action: IscsiCommands, + }, +} + +#[derive(Subcommand)] +pub enum WebDAVCommands { + Start { + #[arg(short, long, default_value = "8002")] + port: u16, + #[arg(short, long)] + user: String, + }, +} + +#[derive(Subcommand)] +pub enum IscsiCommands { + Start { + #[arg(short, long)] + user: String, + #[arg(short, long, default_value = "3260")] + port: u16, + #[arg(short, long, default_value = "5GB")] + lun_size: String, + #[arg(short, long)] + force: bool, + #[arg(long)] + device: Option, + }, + Stop, + Status, +} + +#[derive(Subcommand)] +pub enum ConfigCommands { + Init { + #[arg(short, long)] + force: bool, + }, + Show { + #[arg(short, long)] + section: Option, + }, + Edit { + #[arg(short, long)] + key: String, + #[arg(short, long)] + value: String, + }, + Validate, +} + +pub async fn handle_framework_command(cmd: FrameworkCommands) -> anyhow::Result<()> { + match cmd { + FrameworkCommands::Display { port, file } => { + crate::server::run(port, file).await?; + } + FrameworkCommands::Render { file, output } => { + let md = std::fs::read_to_string(&file)?; + let html = crate::render::md_to_html(&md); + if let Some(path) = &output { + std::fs::write(path, html)?; + } else { + println!("{html}"); + } + } + FrameworkCommands::Config { action } => { + handle_config_command(action)?; + } + FrameworkCommands::Scan { + user, + dir, + batch, + skip_hash, + threads, + } => { + use crate::scan::ScanOptions; + let options = ScanOptions { skip_hash, threads }; + crate::scan::scan_directory(&user, &dir, batch, options)?; + } + FrameworkCommands::Hash { user, threads } => { + crate::scan::compute_hashes(&user, threads)?; + } + FrameworkCommands::BcryptTest { password, verify_hash } => { + handle_bcrypt_test(password, verify_hash)?; + } + FrameworkCommands::WebDAV { action } => { + handle_webdav_command(action).await?; + } + FrameworkCommands::Iscsi { action } => { + handle_iscsi_command(action)?; + } + } + Ok(()) +} + +fn handle_config_command(action: ConfigCommands) -> anyhow::Result<()> { + match action { + ConfigCommands::Init { force } => { + let config_path = Path::new("config/markbase.toml"); + + if config_path.exists() && !force { + println!("Configuration file already exists at config/markbase.toml"); + println!("Use --force to overwrite"); + return Ok(()); + } + + let config = crate::config::MarkBaseConfig::default_config(); + config.save(config_path)?; + + println!("✓ Configuration file created: config/markbase.toml"); + println!("Default values:"); + println!(" Server port: {}", config.server.port); + println!(" PostgreSQL host: {}", config.postgresql.host); + println!(" Test users: {}", config.test.users.join(", ")); + } + ConfigCommands::Show { section } => { + let config_path = Path::new("config/markbase.toml"); + + if !config_path.exists() { + println!("Configuration file not found. Run 'markbase config init' first."); + return Ok(()); + } + + let config = crate::config::MarkBaseConfig::load(config_path)?; + + if let Some(s) = section { + show_section(&config, &s); + } else { + println!("{}", toml::to_string_pretty(&config)?); + } + } + ConfigCommands::Edit { key, value } => { + let config_path = Path::new("config/markbase.toml"); + + if !config_path.exists() { + println!("Configuration file not found. Run 'markbase config init' first."); + return Ok(()); + } + + let mut config = crate::config::MarkBaseConfig::load(config_path)?; + + match config.get(&key) { + Some(old_value) => { + config.set(&key, &value)?; + config.validate()?; + config.save(config_path)?; + println!("✓ Updated {}: {} → {}", key, old_value, value); + } + None => { + println!("Invalid config key: {}", key); + println!( + "Valid keys: server.*, postgresql.*, authentication.*, test.*, logging.*" + ); + } + } + } + ConfigCommands::Validate => { + let config_path = Path::new("config/markbase.toml"); + + if !config_path.exists() { + println!("Configuration file not found. Run 'markbase config init' first."); + return Ok(()); + } + + let config = crate::config::MarkBaseConfig::load(config_path)?; + + match config.validate() { + Ok(_) => { + println!("✓ Configuration is valid"); + } + Err(e) => { + println!("✗ Configuration validation failed: {}", e); + } + } + } + } + + Ok(()) +} + +fn show_section(config: &crate::config::MarkBaseConfig, section: &str) { + match section { + "server" => println!("{}", toml::to_string_pretty(&config.server).unwrap()), + "postgresql" => println!("{}", toml::to_string_pretty(&config.postgresql).unwrap()), + "authentication" => println!("{}", toml::to_string_pretty(&config.authentication).unwrap()), + "test" => println!("{}", toml::to_string_pretty(&config.test).unwrap()), + "logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()), + _ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section), + } +} + +fn handle_bcrypt_test(password: String, verify_hash: Option) -> anyhow::Result<()> { + use bcrypt::{hash, verify, DEFAULT_COST}; + + println!("=== bcrypt Hash Test ==="); + println!("Password: {}", password); + println!(""); + + let new_hash = hash(&password, DEFAULT_COST)?; + println!("Generated hash:"); + println!("{}", new_hash); + println!(""); + + if let Some(hash_to_verify) = verify_hash { + println!("Verifying hash: {}", hash_to_verify); + let valid = verify(&password, &hash_to_verify)?; + println!("Valid: {}", valid); + println!(""); + } + + let db_hash = "$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6"; + println!("Database hash: {}", db_hash); + let valid = verify(&password, db_hash)?; + println!("Database hash valid for '{}': {}", password, valid); + println!(""); + + if !valid { + println!("❌ Database hash is incorrect!"); + println!("Update SQL:"); + println!("UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');", new_hash); + println!(""); + println!("Execute:"); + println!("sqlite3 data/auth.sqlite \"UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');\"", new_hash); + } else { + println!("✅ Database hash is correct!"); + } + + Ok(()) +} + +async fn handle_webdav_command(action: WebDAVCommands) -> anyhow::Result<()> { + match action { + WebDAVCommands::Start { port, user } => { + let db_path = std::path::PathBuf::from(crate::FileTree::user_db_path(&user)); + + if !db_path.exists() { + return Err(anyhow::anyhow!( + "User database not found: {}", + db_path.display() + )); + } + + println!("=== MarkBase WebDAV Server ==="); + println!("User: {}", user); + println!("Port: {}", port); + println!("Database: {}", db_path.display()); + println!(""); + + run_webdav_server(port, user, db_path).await?; + } + } + Ok(()) +} + +async fn run_webdav_server( + port: u16, + user: String, + db_path: std::path::PathBuf, +) -> anyhow::Result<()> { + use tokio::net::TcpListener; + + let webdav = markbase_webdav::webdav::MarkBaseWebDAV::new(user, db_path); + let dav_handler = webdav.create_handler(); + + let app = Router::new() + .route("/webdav", any(handle_dav)) + .route("/webdav/", any(handle_dav)) + .route("/webdav/*path", any(handle_dav)) + .layer(Extension(dav_handler)); + + let addr = format!("127.0.0.1:{}", port); + let listener = TcpListener::bind(&addr).await?; + + println!("WebDAV server listening on http://{}", addr); + println!("Mount point: /webdav"); + println!(""); + println!("Press Ctrl+C to stop"); + + axum::serve(listener, app).await?; + + Ok(()) +} + +async fn handle_dav( + Extension(dav): Extension, + req: Request, +) -> impl IntoResponse { + dav.handle(req).await +} + +fn handle_iscsi_command(action: IscsiCommands) -> anyhow::Result<()> { + let binary = find_binary("markbase-iscsi"); + let mut cmd = std::process::Command::new(&binary); + cmd.arg("iscsi"); + match action { + IscsiCommands::Start { + user, + port, + lun_size, + force, + device, + } => { + cmd.arg("start") + .args(["--user", &user]) + .args(["--port", &port.to_string()]) + .args(["--lun-size", &lun_size]); + if force { + cmd.arg("--force"); + } + if let Some(d) = device { + cmd.args(["--device", &d]); + } + } + IscsiCommands::Stop => { + cmd.arg("stop"); + } + IscsiCommands::Status => { + cmd.arg("status"); + } + } + let status = cmd.status()?; + std::process::exit(status.code().unwrap_or(1)); +} + +fn find_binary(name: &str) -> std::path::PathBuf { + let exe = std::env::current_exe().unwrap(); + let dir = exe.parent().unwrap(); + dir.join(name) +} \ No newline at end of file diff --git a/markbase-core/src/cli/mod.rs b/markbase-core/src/cli/mod.rs new file mode 100644 index 0000000..b3c1c8d --- /dev/null +++ b/markbase-core/src/cli/mod.rs @@ -0,0 +1,5 @@ +pub mod framework; +pub mod apps; + +pub use framework::{Cli, Commands, FrameworkCommands}; +pub use apps::{AppCommands, DownloadCenterCommands}; \ No newline at end of file diff --git a/markbase-core/src/lib.rs b/markbase-core/src/lib.rs index b35cffd..d47fb54 100644 --- a/markbase-core/src/lib.rs +++ b/markbase-core/src/lib.rs @@ -1,6 +1,8 @@ pub mod audio; pub mod auth; pub mod audit; +pub mod cli; +pub mod api; pub mod command; pub mod config; pub mod download; diff --git a/markbase-core/src/main.rs b/markbase-core/src/main.rs index f4d474c..0b23645 100644 --- a/markbase-core/src/main.rs +++ b/markbase-core/src/main.rs @@ -1,509 +1,22 @@ -use axum::{extract::Request, response::IntoResponse, routing::any, Extension, Router}; -use clap::{Parser, Subcommand}; -use std::path::Path; - -#[derive(Parser)] -#[command(name = "markbase", about = "Momentry Display Engine")] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Start display server - Display { - #[arg(short, long, default_value = "11438")] - port: u16, - /// Optional initial markdown file - file: Option, - }, - /// Render markdown to HTML (stdout) - Render { - file: String, - #[arg(short, long)] - output: Option, - }, - /// Configuration management - Config { - #[command(subcommand)] - action: ConfigCommands, - }, - /// Scan and import files from directory - Scan { - /// User ID - #[arg(short, long)] - user: String, - /// Directory to scan - #[arg(short, long)] - dir: String, - /// Batch size for database insertion - #[arg(short, long, default_value = "100")] - batch: usize, - /// Skip SHA256 hash calculation (faster import) - #[arg(short, long, default_value = "true")] - skip_hash: bool, - /// Number of threads for hash calculation (if skip_hash=false) - #[arg(short, long, default_value = "4")] - threads: usize, - }, - /// Compute SHA256 hashes for imported files - Hash { - /// User ID - #[arg(short, long)] - user: String, - /// Number of threads for parallel hash calculation - #[arg(short, long, default_value = "4")] - threads: usize, - }, - /// Start WebDAV server - WebDAV { - #[command(subcommand)] - action: WebDAVCommands, - }, - /// Manage iSCSI target (gotgt) - Iscsi { - #[command(subcommand)] - action: IscsiCommands, - }, - /// Start SFTP server - Sftp { - /// Port to listen on - #[arg(short, long, default_value = "2023")] - port: u16, - /// User ID for database - #[arg(short, long)] - user: String, - }, - /// Test bcrypt password hash - BcryptTest { - /// Password to hash - #[arg(short, long, default_value = "demo123")] - password: String, - /// Hash to verify (optional) - #[arg(long)] - verify_hash: Option, - }, - /// Start SSH server (hand-written implementation) - SshServer { - /// Port to listen on (default 2024) - #[arg(short, long, default_value = "2024")] - port: u16, - }, - /// Import Markdown files to categories/series virtual trees - ImportMarkdown { - /// User ID (default: accusys) - #[arg(short, long, default_value = "accusys")] - user: String, - /// Tree type (categories or series) - #[arg(short, long)] - tree_type: String, - }, -} - -#[derive(Subcommand)] -pub enum IscsiCommands { - /// Start iSCSI target daemon - Start { - #[arg(short, long)] - user: String, - #[arg(short, long, default_value = "3260")] - port: u16, - #[arg(short, long, default_value = "5GB")] - lun_size: String, - #[arg(short, long)] - force: bool, - /// Block device path (e.g., /dev/disk5). Overrides file-backed LUN. - #[arg(long)] - device: Option, - }, - /// Stop iSCSI target daemon - Stop, - /// Show iSCSI target status - Status, -} - -#[derive(Subcommand)] -enum WebDAVCommands { - /// Start WebDAV server for user - Start { - /// Port to listen on - #[arg(short, long, default_value = "8002")] - port: u16, - /// User ID for database - #[arg(short, long)] - user: String, - }, -} - -#[derive(Subcommand)] -enum ConfigCommands { - /// Initialize default configuration file - Init { - #[arg(short, long)] - force: bool, - }, - /// Show current configuration - Show { - #[arg(short, long)] - section: Option, - }, - /// Edit configuration - Edit { - #[arg(short, long)] - key: String, - #[arg(short, long)] - value: String, - }, - /// Validate configuration - Validate, -} +use markbase_core::cli::{Cli, Commands}; +use clap::Parser; #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { - Commands::Display { port, file } => { - markbase_core::server::run(port, file).await?; + Commands::Framework(cmd) => { + markbase_core::cli::framework::handle_framework_command(cmd).await?; } - Commands::Render { file, output } => { - let md = std::fs::read_to_string(&file)?; - let html = markbase_core::render::md_to_html(&md); - if let Some(path) = &output { - std::fs::write(path, html)?; - } else { - println!("{html}"); - } - } - Commands::Config { action } => { - handle_config_command(action)?; - } - Commands::Scan { - user, - dir, - batch, - skip_hash, - threads, - } => { - use markbase_core::scan::ScanOptions; - let options = ScanOptions { skip_hash, threads }; - markbase_core::scan::scan_directory(&user, &dir, batch, options)?; - } - Commands::Hash { user, threads } => { - markbase_core::scan::compute_hashes(&user, threads)?; - } - Commands::WebDAV { action } => { - handle_webdav_command(action).await?; - } - Commands::Iscsi { action } => { - handle_iscsi_command(action)?; - } -Commands::Sftp { port, user } => { - println!("SFTP server command is currently disabled (old implementation)"); - println!("Use 'ssh-server' command for the new SSH+SFTP implementation"); - // handle_sftp_command(port, user).await?; - } - Commands::BcryptTest { password, verify_hash } => { - handle_bcrypt_test(password, verify_hash)?; - } -Commands::SshServer { port } => { - println!("=== MarkBase SSH Server (Hand-written Implementation) ==="); - println!("Port: {}", port); // port已经是u16,不是Option - println!("Implementation: SSH-2.0-MarkBaseSSH_1.0"); - println!("Features: SSH + SFTP + SCP + rsync"); - println!("Security: ⭐⭐⭐⭐⭐ (RustCrypto authoritative libraries)"); - println!(); - - markbase_core::ssh_server::server::run_ssh_server(Some(port))?; - } - Commands::BcryptTest { - password, - verify_hash, - } => { - handle_bcrypt_test(password, verify_hash)?; - } - Commands::ImportMarkdown { user, tree_type } => { - use rusqlite::Connection; - use markbase_core::import_markdown; - use anyhow::Context; - - let db_path = format!("data/users/{}.sqlite", user); - let conn = Connection::open(&db_path) - .with_context(|| format!("Failed to open database: {}", db_path))?; - - println!("Importing Markdown files to {} virtual tree...", tree_type); - - if tree_type == "categories" { - import_markdown::import_categories_to_db(&conn, &user, &tree_type)?; - println!("Categories imported successfully!"); - } else if tree_type == "series" { - import_markdown::import_series_to_db(&conn, &user, &tree_type)?; - println!("Series imported successfully!"); - } else { - eprintln!("Invalid tree_type: {}. Use 'categories' or 'series'", tree_type); - } - } - } - Ok(()) -} - -fn handle_config_command(action: ConfigCommands) -> anyhow::Result<()> { - match action { - ConfigCommands::Init { force } => { - let config_path = Path::new("config/markbase.toml"); - - if config_path.exists() && !force { - println!("Configuration file already exists at config/markbase.toml"); - println!("Use --force to overwrite"); - return Ok(()); - } - - let config = markbase_core::config::MarkBaseConfig::default_config(); - config.save(config_path)?; - - println!("✓ Configuration file created: config/markbase.toml"); - println!("Default values:"); - println!(" Server port: {}", config.server.port); - println!(" PostgreSQL host: {}", config.postgresql.host); - println!(" Test users: {}", config.test.users.join(", ")); - } - ConfigCommands::Show { section } => { - let config_path = Path::new("config/markbase.toml"); - - if !config_path.exists() { - println!("Configuration file not found. Run 'markbase config init' first."); - return Ok(()); - } - - let config = markbase_core::config::MarkBaseConfig::load(config_path)?; - - if let Some(s) = section { - show_section(&config, &s); - } else { - println!("{}", toml::to_string_pretty(&config)?); - } - } - ConfigCommands::Edit { key, value } => { - let config_path = Path::new("config/markbase.toml"); - - if !config_path.exists() { - println!("Configuration file not found. Run 'markbase config init' first."); - return Ok(()); - } - - let mut config = markbase_core::config::MarkBaseConfig::load(config_path)?; - - match config.get(&key) { - Some(old_value) => { - config.set(&key, &value)?; - config.validate()?; - config.save(config_path)?; - println!("✓ Updated {}: {} → {}", key, old_value, value); - } - None => { - println!("Invalid config key: {}", key); - println!( - "Valid keys: server.*, postgresql.*, authentication.*, test.*, logging.*" - ); - } - } - } - ConfigCommands::Validate => { - let config_path = Path::new("config/markbase.toml"); - - if !config_path.exists() { - println!("Configuration file not found. Run 'markbase config init' first."); - return Ok(()); - } - - let config = markbase_core::config::MarkBaseConfig::load(config_path)?; - - match config.validate() { - Ok(_) => { - println!("✓ Configuration is valid"); - } - Err(e) => { - println!("✗ Configuration validation failed: {}", e); + Commands::App(app_cmd) => { + match app_cmd { + markbase_core::cli::apps::AppCommands::DownloadCenter(cmd) => { + markbase_core::cli::apps::download_center::handle_download_center_command(cmd).await?; } } } } - + Ok(()) -} - -fn show_section(config: &markbase_core::config::MarkBaseConfig, section: &str) { - match section { - "server" => println!("{}", toml::to_string_pretty(&config.server).unwrap()), - "postgresql" => println!("{}", toml::to_string_pretty(&config.postgresql).unwrap()), - "authentication" => println!("{}", toml::to_string_pretty(&config.authentication).unwrap()), - "test" => println!("{}", toml::to_string_pretty(&config.test).unwrap()), - "logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()), - _ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section), - } -} - -fn handle_iscsi_command(action: IscsiCommands) -> anyhow::Result<()> { - let binary = find_binary("markbase-iscsi"); - let mut cmd = std::process::Command::new(&binary); - cmd.arg("iscsi"); - match action { - IscsiCommands::Start { - user, - port, - lun_size, - force, - device, - } => { - cmd.arg("start") - .args(["--user", &user]) - .args(["--port", &port.to_string()]) - .args(["--lun-size", &lun_size]); - if force { - cmd.arg("--force"); - } - if let Some(d) = device { - cmd.args(["--device", &d]); - } - } - IscsiCommands::Stop => { - cmd.arg("stop"); - } - IscsiCommands::Status => { - cmd.arg("status"); - } - } - let status = cmd.status()?; - std::process::exit(status.code().unwrap_or(1)); -} - -async fn handle_webdav_command(action: WebDAVCommands) -> anyhow::Result<()> { - match action { - WebDAVCommands::Start { port, user } => { - let db_path = std::path::PathBuf::from(filetree::FileTree::user_db_path(&user)); - - if !db_path.exists() { - return Err(anyhow::anyhow!( - "User database not found: {}", - db_path.display() - )); - } - - println!("=== MarkBase WebDAV Server ==="); - println!("User: {}", user); - println!("Port: {}", port); - println!("Database: {}", db_path.display()); - println!(""); - - run_webdav_server(port, user, db_path).await?; - } - } - Ok(()) -} - -async fn run_webdav_server( - port: u16, - user: String, - db_path: std::path::PathBuf, -) -> anyhow::Result<()> { - use tokio::net::TcpListener; - - let webdav = markbase_webdav::webdav::MarkBaseWebDAV::new(user, db_path); - let dav_handler = webdav.create_handler(); - - let app = Router::new() - .route("/webdav", any(handle_dav)) - .route("/webdav/", any(handle_dav)) - .route("/webdav/*path", any(handle_dav)) - .layer(Extension(dav_handler)); - - let addr = format!("127.0.0.1:{}", port); - let listener = TcpListener::bind(&addr).await?; - - println!("WebDAV server listening on http://{}", addr); - println!("Mount point: /webdav"); - println!(""); - println!("Press Ctrl+C to stop"); - - axum::serve(listener, app).await?; - - Ok(()) -} - -async fn handle_dav( - Extension(dav): Extension, - req: Request, -) -> impl IntoResponse { - dav.handle(req).await -} - -fn find_binary(name: &str) -> std::path::PathBuf { - let exe = std::env::current_exe().unwrap(); - let dir = exe.parent().unwrap(); - dir.join(name) -} - -// async fn handle_sftp_command(port: u16, user: String) -> anyhow::Result<()> { -// println!("=== MarkBase SFTP Server ==="); -// println!("User: {}", user); -// println!("Port: {}", port); -// println!("Auth DB: data/auth.sqlite"); -// println!("FileTree DB: data/users/{}.sqlite", user); -// println!(""); -// -// let config = markbase_core::sftp::SftpConfig::load_default()?; -// -// if port != config.sftp.port { -// println!( -// "Warning: CLI port {} overrides config port {}", -// port, config.sftp.port -// ); -// } -// -// markbase_core::sftp::run_server(config, &user).await?; -// -// Ok(()) -// } - -fn handle_bcrypt_test(password: String, verify_hash: Option) -> anyhow::Result<()> { - use bcrypt::{hash, verify, DEFAULT_COST}; - - println!("=== bcrypt Hash Test ==="); - println!("Password: {}", password); - println!(""); - - // Generate new hash - let new_hash = hash(&password, DEFAULT_COST)?; - println!("Generated hash:"); - println!("{}", new_hash); - println!(""); - - // Verify current hash if provided - if let Some(hash_to_verify) = verify_hash { - println!("Verifying hash: {}", hash_to_verify); - let valid = verify(&password, &hash_to_verify)?; - println!("Valid: {}", valid); - println!(""); - } - - // Verify database hash - let db_hash = "$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6"; - println!("Database hash: {}", db_hash); - let valid = verify(&password, db_hash)?; - println!("Database hash valid for '{}': {}", password, valid); - println!(""); - - if !valid { - println!("❌ Database hash is incorrect!"); - println!("Update SQL:"); - println!("UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');", new_hash); - println!(""); - println!("Execute:"); - println!("sqlite3 data/auth.sqlite \"UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');\"", new_hash); - } else { - println!("✅ Database hash is correct!"); - } - - Ok(()) -} +} \ No newline at end of file