Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 0 additions & 44 deletions src/api/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,48 +159,4 @@ impl ApiClient {

self.handle_response(response)
}

pub fn post_file<T: DeserializeOwned>(
&self,
path: &str,
file_path: &std::path::Path,
) -> Result<T, ApiError> {
use reqwest::blocking::multipart::{Form, Part};
use std::fs::File;
use std::io::Read;

let url = format!("{}{}", self.base_url, path);

let mut file = File::open(file_path)
.map_err(|e| ApiError::Other(format!("Failed to open file: {}", e)))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|e| ApiError::Other(format!("Failed to read file: {}", e)))?;

let file_name = file_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("file.sql")
.to_string();

let part = Part::bytes(buffer)
.file_name(file_name)
.mime_str("application/octet-stream")
.map_err(|e| ApiError::Other(format!("Failed to set mime type: {}", e)))?;

let form = Form::new().part("file", part);

let mut headers = self.headers()?;
headers.remove(CONTENT_TYPE);

let response = self
.client
.post(&url)
.headers(headers)
.multipart(form)
.send()
.map_err(ApiError::NetworkError)?;

self.handle_response(response)
}
}
104 changes: 26 additions & 78 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "vector")]
Expand Down Expand Up @@ -395,30 +394,6 @@ pub enum EnvSecretCommands {

#[derive(Subcommand)]
pub enum EnvDbCommands {
/// Import a SQL file directly (files under 50MB)
Import {
/// Environment ID
env_id: String,
/// Path to SQL file
file: PathBuf,
/// Drop all existing tables before import
#[arg(long)]
drop_tables: bool,
/// Disable foreign key checks during import
#[arg(long)]
disable_foreign_keys: bool,
/// Search string for search-and-replace during import
#[arg(long)]
search_replace_from: Option<String>,
/// Replace string for search-and-replace during import
#[arg(long)]
search_replace_to: Option<String>,
},
/// Manage import sessions for large files
ImportSession {
#[command(subcommand)]
command: EnvDbImportSessionCommands,
},
/// Promote dev database to this environment
Promote {
/// Environment ID
Expand All @@ -437,46 +412,26 @@ pub enum EnvDbCommands {
/// Promote ID
promote_id: String,
},
/// Manage domain changes
DomainChange {
#[command(subcommand)]
command: EnvDomainChangeCommands,
},
}

#[derive(Subcommand)]
pub enum EnvDbImportSessionCommands {
/// Create an import session
pub enum EnvDomainChangeCommands {
/// Create a domain change
Create {
/// Environment ID
env_id: String,
/// Filename
#[arg(long)]
filename: Option<String>,
/// Content length in bytes
#[arg(long)]
content_length: Option<u64>,
/// Drop all existing tables before import
#[arg(long)]
drop_tables: bool,
/// Disable foreign key checks during import
#[arg(long)]
disable_foreign_keys: bool,
/// Search string for search-and-replace during import
#[arg(long)]
search_replace_from: Option<String>,
/// Replace string for search-and-replace during import
#[arg(long)]
search_replace_to: Option<String>,
},
/// Run an import session
Run {
/// Environment ID
env_id: String,
/// Import ID
import_id: String,
},
/// Check import session status
/// Check domain change status
Status {
/// Environment ID
env_id: String,
/// Import ID
import_id: String,
/// Domain change ID
domain_change_id: String,
},
}

Expand Down Expand Up @@ -538,26 +493,7 @@ pub enum SslCommands {

#[derive(Subcommand)]
pub enum DbCommands {
/// Import a SQL file directly (files under 50MB)
Import {
/// Site ID
site_id: String,
/// Path to SQL file
file: PathBuf,
/// Drop all existing tables before import
#[arg(long)]
drop_tables: bool,
/// Disable foreign key checks during import
#[arg(long)]
disable_foreign_keys: bool,
/// Search string for search-and-replace during import
#[arg(long)]
search_replace_from: Option<String>,
/// Replace string for search-and-replace during import
#[arg(long)]
search_replace_to: Option<String>,
},
/// Manage import sessions for large files
/// Manage archive import sessions
ImportSession {
#[command(subcommand)]
command: DbImportSessionCommands,
Expand All @@ -571,7 +507,7 @@ pub enum DbCommands {

#[derive(Subcommand)]
pub enum DbImportSessionCommands {
/// Create an import session
/// Create an archive import session
Create {
/// Site ID
site_id: String,
Expand All @@ -594,14 +530,14 @@ pub enum DbImportSessionCommands {
#[arg(long)]
search_replace_to: Option<String>,
},
/// Run an import session
/// Run an archive import session
Run {
/// Site ID
site_id: String,
/// Import ID
import_id: String,
},
/// Check import session status
/// Check archive import session status
Status {
/// Site ID
site_id: String,
Expand Down Expand Up @@ -1063,6 +999,18 @@ pub enum RestoreCommands {
/// Restore scope (full, database, files)
#[arg(long, default_value = "full")]
scope: String,
/// Drop all existing tables before restore
#[arg(long)]
drop_tables: bool,
/// Disable foreign key checks during restore
#[arg(long)]
disable_foreign_keys: bool,
/// Search string for search-and-replace during restore
#[arg(long)]
search_replace_from: Option<String>,
/// Replace string for search-and-replace during restore
#[arg(long)]
search_replace_to: Option<String>,
},
}

Expand Down
76 changes: 4 additions & 72 deletions src/commands/db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use serde::Serialize;
use serde_json::Value;
use std::path::Path;

use crate::api::{ApiClient, ApiError};
use crate::output::{OutputFormat, format_option, print_json, print_key_value, print_message};
Expand Down Expand Up @@ -39,71 +38,6 @@ struct CreateExportRequest {
format: Option<String>,
}

#[allow(clippy::too_many_arguments)]
pub fn import_direct(
client: &ApiClient,
site_id: &str,
file_path: &Path,
drop_tables: bool,
disable_foreign_keys: bool,
search_replace_from: Option<String>,
search_replace_to: Option<String>,
format: OutputFormat,
) -> Result<(), ApiError> {
// Check file size - direct import only supports files under 50MB
let metadata = std::fs::metadata(file_path)
.map_err(|e| ApiError::Other(format!("Failed to read file: {}", e)))?;

if metadata.len() > 50 * 1024 * 1024 {
return Err(ApiError::Other(
"File too large for direct import. Use 'import-session' for files over 50MB."
.to_string(),
));
}

let mut path = format!("/api/v1/vector/sites/{}/db/import", site_id);
let mut params = vec![];
if drop_tables {
params.push("drop_tables=true".to_string());
}
if disable_foreign_keys {
params.push("disable_foreign_keys=true".to_string());
}
if let Some(ref from) = search_replace_from {
params.push(format!("search_replace_from={}", from));
}
if let Some(ref to) = search_replace_to {
params.push(format!("search_replace_to={}", to));
}
if !params.is_empty() {
path = format!("{}?{}", path, params.join("&"));
}

let response: Value = client.post_file(&path, file_path)?;

if format == OutputFormat::Json {
print_json(&response);
return Ok(());
}

let data = &response["data"];
if data["success"].as_bool().unwrap_or(false) {
print_message(&format!(
"Database imported successfully ({}ms).",
data["duration_ms"].as_u64().unwrap_or(0)
));
} else {
return Err(ApiError::Other(
data["error"]
.as_str()
.unwrap_or("Import failed")
.to_string(),
));
}

Ok(())
}

#[allow(clippy::too_many_arguments)]
pub fn import_session_create(
client: &ApiClient,
Expand Down Expand Up @@ -138,10 +72,8 @@ pub fn import_session_create(
options,
};

let response: Value = client.post(
&format!("/api/v1/vector/sites/{}/db/imports", site_id),
&body,
)?;
let response: Value =
client.post(&format!("/api/v1/vector/sites/{}/imports", site_id), &body)?;

if format == OutputFormat::Json {
print_json(&response);
Expand Down Expand Up @@ -179,7 +111,7 @@ pub fn import_session_run(
format: OutputFormat,
) -> Result<(), ApiError> {
let response: Value = client.post_empty(&format!(
"/api/v1/vector/sites/{}/db/imports/{}/run",
"/api/v1/vector/sites/{}/imports/{}/run",
site_id, import_id
))?;

Expand All @@ -205,7 +137,7 @@ pub fn import_session_status(
format: OutputFormat,
) -> Result<(), ApiError> {
let response: Value = client.get(&format!(
"/api/v1/vector/sites/{}/db/imports/{}",
"/api/v1/vector/sites/{}/imports/{}",
site_id, import_id
))?;

Expand Down
Loading