From 27fac75738e1e9908ff361788abc15829a849a62 Mon Sep 17 00:00:00 2001 From: Maaz Khokhar Date: Mon, 13 Jul 2026 20:09:43 -0500 Subject: [PATCH] made adding a project work changed the sql migration to make names unique --- .vscode/settings.json | 3 + Cargo.lock | 1 + crates/cli/Cargo.toml | 6 +- crates/cli/harbor.db | Bin 32768 -> 36864 bytes .../up.sql | 8 +- crates/cli/src/db/models.rs | 7 ++ crates/cli/src/lib.rs | 92 +++++++++++++++--- crates/cli/src/main.rs | 78 +++++++++++++-- 8 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f73150f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["chrono"] +} diff --git a/Cargo.lock b/Cargo.lock index b314bc2..f0b28ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1748,6 +1748,7 @@ version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 3035f88..a376599 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,5 +20,9 @@ colored = "3.1.1" base64 = "0.22.1" chrono = "0.4.45" dotenvy = "0.15" -uuid = "1.23.5" +uuid = { version = "1.23.5", features = ["v4"] } hex = "0.4" + +[[bin]] +name = "harbor" +path = "src/main.rs" diff --git a/crates/cli/harbor.db b/crates/cli/harbor.db index 7552bbd7d029987ae170c1246d9b4160541643c7..cb108684b9fe8ea53bd7ecc5de9dd796c1375e26 100644 GIT binary patch delta 386 zcmZo@U}{*vG(lRBm4ShQ6Nq7ed!ml9EGvUv?=N2d9}FDadl~pn@Gs_F%r~7cje9R| z39swMMn`VBW>z+KacODB*2I#;q@2{^)a0Vnl42;uKG}#zZ*mMj|K>CvJw{dzHgQMk z$#eN6CO_ci;5ULQ;?!kF5*Og+Xmns^7nhY~Yzm(Io7WYjeX<*$|6~PTcEM0T&%jVu z1&zGK+*D02O?}qMGx=1xj0%eKvr>~wirF?#=ZhEQGEp!vv@$TZGPKyFz#_oL$bX1| z|IlVZfgSuFHjFHx{Km#6h6aXamL|F>28KquCg$cAx=BXnX}T7s=9VcIDT(GrDdver z21aJO2Ijhk5aTSZ3@y2Ufy2mugMt6XW{6^}`p`65+1=07Lf&cerL4)`F>0~*iwqPNIS2p%7Q|l; delta 211 zcmZozz|_#dG(lRBnSp_U1BhXOeWH%BG&6%HLUF7($b8r{w0Y?IjO~|$wjFp#ZZcE@@;Ot$-jB!HXHDWF|x9=i91S9 zp35g84wa8Lf-*RC**V3pmCv0T$-Dw^Ti8t87mkW XSs58y85nF*U=i5NlJG} { + pub id: &'a str, + pub name: &'a str, +} + #[derive(Debug, Queryable, Selectable)] #[diesel(table_name = secrets)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 5e23d93..e4c9a8f 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,10 +1,12 @@ use base64::{Engine, engine::general_purpose::STANDARD}; -use clap::{Args, Parser, Subcommand}; +use clap::{Parser, Subcommand}; use crypto::helper::gen_key; use keyring::Entry; use std::error::Error; +use std::io::{self, Write}; mod db; mod store; + #[derive(Parser)] #[clap( name = "Harbor", @@ -22,7 +24,13 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { - Inject(InjectArgs), + Inject { + #[arg(short, long)] + verbose: bool, + + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + after: Vec, + }, Shell {}, Add {}, Setup {}, @@ -37,30 +45,72 @@ pub enum Commands { }, } -#[derive(Args, Debug)] -pub struct InjectArgs { - #[arg(short, long)] - pub verbose: bool, - - #[arg(trailing_var_arg = true, allow_hyphen_values = true)] - pub after: Vec, -} - #[derive(Subcommand)] pub enum ProjectCommands { List {}, - Create { name: String }, + Create {}, Delete { name: String }, } #[derive(Subcommand)] pub enum ConfigCommands { List {}, - Create { project: String, name: String }, + Create { project: String }, Delete { project: String, name: String }, } -fn gen_or_get_key() -> Result> { +pub mod interactions { + use super::db::models::{NewProject, Project}; + use super::db::{ + establish_connection, + schema::projects::dsl::{name as project_name, projects}, + }; + use diesel::result::{DatabaseErrorKind, Error as DieselError}; + use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper}; + use std::error::Error; + use uuid::Uuid; + + fn construct_error(message: &str) -> Result<(), Box> { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + message.to_string(), + ))) + } + + pub fn create_project(proj_name: &str) -> Result<(), Box> { + let mut conn = establish_connection(); + + // 1. Check if the project already exists + let existing_project = projects + .filter(project_name.eq(proj_name)) + .select(Project::as_select()) + .first::(&mut conn) + .optional()?; + + if let Some(_) = existing_project { + return construct_error("Project already exists"); + } + + let project_id = Uuid::new_v4().to_string(); + let new_project = NewProject { + id: &project_id, + name: proj_name, + }; + + match diesel::insert_into(projects) + .values(&new_project) + .execute(&mut conn) + { + Ok(_) => Ok(()), + Err(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) => { + construct_error("Project already exists") + } + Err(err) => Err(Box::new(err)), + } + } +} + +pub fn gen_or_get_key() -> Result> { let entry = Entry::new("harbor", "encryption-key")?; match entry.get_password() { @@ -84,3 +134,17 @@ fn gen_or_get_key() -> Result> { Err(err) => Err(Box::new(err)), } } + +pub fn get_input(message: &str, prompt: char, new_line: bool) -> Result> { + let mut input = String::new(); + + if new_line { + print!("{}\n{} ", message, prompt); + } else { + print!("{}{} ", message, prompt); + } + + io::stdout().flush()?; + io::stdin().read_line(&mut input)?; + Ok(input.trim().to_string()) +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 25c8e25..5891477 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -3,6 +3,7 @@ use std::process; use clap::Parser; use clap::crate_version; use cli::Cli; +use cli::get_input; use colored::*; const GIT_VERSION: &str = env!("GIT_VERSION"); @@ -20,12 +21,75 @@ fn main() { process::exit(0); } - if cli.command.is_none() { - println!( - "{}\n{}", - "No command was entered.".red(), - "Exiting...".dimmed() - ); - process::exit(0); + match cli.command { + Some(c) => match c { + cli::Commands::Add {} => { + println!("Add command executed"); + } + cli::Commands::Config { command } => match command { + cli::ConfigCommands::List {} => { + println!("Config list command executed"); + } + cli::ConfigCommands::Create { project } => { + println!("Config create command executed for project: {}", project); + } + cli::ConfigCommands::Delete { project, name } => { + println!( + "Config delete command executed for project: {}, name: {}", + project, name + ); + } + }, + cli::Commands::Inject { verbose, after } => { + println!( + "Inject command executed with verbose: {}, after: {:?}", + verbose, after + ); + // for harbor inject -- bun dev + // after = ["bun", "dev"] + } + cli::Commands::List {} => { + println!("List command executed"); + } + cli::Commands::Project { command } => match command { + cli::ProjectCommands::List {} => { + println!("Project list command executed"); + } + cli::ProjectCommands::Create {} => { + let project_name = match get_input("Project name", ':', false) { + Ok(name) => name, + Err(e) => { + eprintln!("Error getting project name: {}", e); + process::exit(1); + } + }; + + match cli::interactions::create_project(&project_name) { + Ok(()) => println!("{}", "Project created successfully".blue()), + Err(e) => { + eprintln!("Error creating project: {}", e); + process::exit(1); + } + } + } + cli::ProjectCommands::Delete { name } => { + println!("Project delete command executed for name: {}", name); + } + }, + cli::Commands::Shell {} => { + println!("Shell command executed"); + } + cli::Commands::Setup {} => { + println!("Setup command executed"); + } + }, + None => { + println!( + "{}\n{}", + "No command was entered.".red(), + "Use --help for more information.".dimmed() + ); + process::exit(0); + } } }