feat: added config auth setup
ef7acd60
4 file(s) · +104 −5
| 27 | 27 | reqwest = { version = "0.12", features = ["json", "blocking"] } |
|
| 28 | 28 | serde_json = "1" |
|
| 29 | 29 | clap = { version = "4", features = ["derive", "env"] } |
|
| 30 | + | toml = "0.8" |
|
| 31 | + | rpassword = "5" |
| 1 | 1 | use arboard::Clipboard; |
|
| 2 | - | use clap::Parser; |
|
| 2 | + | use clap::{Parser, Subcommand}; |
|
| 3 | 3 | use crossterm::event::{self, Event, KeyCode, KeyModifiers}; |
|
| 4 | 4 | use ratatui::{ |
|
| 5 | 5 | DefaultTerminal, |
|
| 9 | 9 | widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Widget}, |
|
| 10 | 10 | }; |
|
| 11 | 11 | use sipp_rust::backend::Backend; |
|
| 12 | + | use sipp_rust::config; |
|
| 12 | 13 | use sipp_rust::db::Snippet; |
|
| 13 | 14 | use std::io::Cursor; |
|
| 14 | 15 | use std::time::{Duration, Instant}; |
|
| 27 | 28 | /// API key for authenticated operations |
|
| 28 | 29 | #[arg(short = 'k', long, env = "SIPP_API_KEY")] |
|
| 29 | 30 | api_key: Option<String>, |
|
| 31 | + | ||
| 32 | + | #[command(subcommand)] |
|
| 33 | + | command: Option<Commands>, |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | #[derive(Subcommand)] |
|
| 37 | + | enum Commands { |
|
| 38 | + | /// Save remote URL and API key to config file |
|
| 39 | + | Auth, |
|
| 30 | 40 | } |
|
| 31 | 41 | ||
| 32 | 42 | enum Focus { |
|
| 272 | 282 | } |
|
| 273 | 283 | } |
|
| 274 | 284 | ||
| 285 | + | fn run_auth() -> Result<(), Box<dyn std::error::Error>> { |
|
| 286 | + | use std::io::{self, Write}; |
|
| 287 | + | ||
| 288 | + | print!("Remote URL: "); |
|
| 289 | + | io::stdout().flush()?; |
|
| 290 | + | let mut remote_url = String::new(); |
|
| 291 | + | io::stdin().read_line(&mut remote_url)?; |
|
| 292 | + | let remote_url = remote_url.trim().to_string(); |
|
| 293 | + | ||
| 294 | + | print!("API Key: "); |
|
| 295 | + | io::stdout().flush()?; |
|
| 296 | + | let api_key = rpassword::read_password()?; |
|
| 297 | + | let api_key = api_key.trim().to_string(); |
|
| 298 | + | ||
| 299 | + | let cfg = config::Config { |
|
| 300 | + | remote_url: if remote_url.is_empty() { |
|
| 301 | + | None |
|
| 302 | + | } else { |
|
| 303 | + | Some(remote_url) |
|
| 304 | + | }, |
|
| 305 | + | api_key: if api_key.is_empty() { |
|
| 306 | + | None |
|
| 307 | + | } else { |
|
| 308 | + | Some(api_key) |
|
| 309 | + | }, |
|
| 310 | + | }; |
|
| 311 | + | ||
| 312 | + | config::save_config(&cfg)?; |
|
| 313 | + | println!("Config saved to {}", config::config_path().display()); |
|
| 314 | + | Ok(()) |
|
| 315 | + | } |
|
| 316 | + | ||
| 317 | + | fn resolve_backend(cli: &Cli) -> (Backend, bool, Option<String>) { |
|
| 318 | + | // 1. CLI flags / env vars take highest priority |
|
| 319 | + | if let Some(url) = &cli.remote { |
|
| 320 | + | return ( |
|
| 321 | + | Backend::remote(url.clone(), cli.api_key.clone()), |
|
| 322 | + | true, |
|
| 323 | + | Some(url.clone()), |
|
| 324 | + | ); |
|
| 325 | + | } |
|
| 326 | + | ||
| 327 | + | // 2. If no local DB exists, try config file |
|
| 328 | + | if !std::path::Path::new("sipp.sqlite").exists() { |
|
| 329 | + | let cfg = config::load_config(); |
|
| 330 | + | if let Some(url) = cfg.remote_url { |
|
| 331 | + | return (Backend::remote(url.clone(), cfg.api_key), true, Some(url)); |
|
| 332 | + | } |
|
| 333 | + | } |
|
| 334 | + | ||
| 335 | + | // 3. Fallback to local DB (creates it if needed) |
|
| 336 | + | (Backend::local(), false, None) |
|
| 337 | + | } |
|
| 338 | + | ||
| 275 | 339 | fn main() -> Result<(), Box<dyn std::error::Error>> { |
|
| 276 | 340 | let cli = Cli::parse(); |
|
| 277 | 341 | ||
| 278 | - | let (backend, is_remote, remote_url) = match cli.remote { |
|
| 279 | - | Some(url) => (Backend::remote(url.clone(), cli.api_key), true, Some(url)), |
|
| 280 | - | None => (Backend::local(), false, None), |
|
| 281 | - | }; |
|
| 342 | + | if let Some(Commands::Auth) = &cli.command { |
|
| 343 | + | return run_auth(); |
|
| 344 | + | } |
|
| 345 | + | ||
| 346 | + | let (backend, is_remote, remote_url) = resolve_backend(&cli); |
|
| 282 | 347 | ||
| 283 | 348 | let snippets = match backend.list_snippets() { |
|
| 284 | 349 | Ok(s) => s, |
|
| 1 | + | use serde::{Deserialize, Serialize}; |
|
| 2 | + | use std::path::PathBuf; |
|
| 3 | + | ||
| 4 | + | #[derive(Debug, Default, Serialize, Deserialize)] |
|
| 5 | + | pub struct Config { |
|
| 6 | + | pub remote_url: Option<String>, |
|
| 7 | + | pub api_key: Option<String>, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | pub fn config_path() -> PathBuf { |
|
| 11 | + | let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); |
|
| 12 | + | PathBuf::from(home).join(".config/sipp/config.toml") |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | pub fn load_config() -> Config { |
|
| 16 | + | let path = config_path(); |
|
| 17 | + | match std::fs::read_to_string(&path) { |
|
| 18 | + | Ok(contents) => toml::from_str(&contents).unwrap_or_default(), |
|
| 19 | + | Err(_) => Config::default(), |
|
| 20 | + | } |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | pub fn save_config(config: &Config) -> Result<(), Box<dyn std::error::Error>> { |
|
| 24 | + | let path = config_path(); |
|
| 25 | + | if let Some(parent) = path.parent() { |
|
| 26 | + | std::fs::create_dir_all(parent)?; |
|
| 27 | + | } |
|
| 28 | + | let contents = toml::to_string_pretty(config)?; |
|
| 29 | + | std::fs::write(&path, contents)?; |
|
| 30 | + | Ok(()) |
|
| 31 | + | } |
| 1 | 1 | pub mod backend; |
|
| 2 | + | pub mod config; |
|
| 2 | 3 | pub mod db; |
|
| 3 | 4 | pub mod highlight; |