refactor: workspace secrets-core + secrets-mcp MCP SaaS
- Split library (db/crypto/service) and MCP/Web/OAuth binary - Add deploy examples and CI/docs updates Made-with: Cursor
This commit is contained in:
123
crates/secrets-core/src/service/import.rs
Normal file
123
crates/secrets-core/src/service/import.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use anyhow::Result;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::ExportFormat;
|
||||
use crate::service::add::{AddParams, run as add_run};
|
||||
use crate::service::export::{build_meta_entries, build_secret_entries};
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ImportSummary {
|
||||
pub total: usize,
|
||||
pub inserted: usize,
|
||||
pub skipped: usize,
|
||||
pub failed: usize,
|
||||
pub dry_run: bool,
|
||||
}
|
||||
|
||||
pub struct ImportParams<'a> {
|
||||
pub file: &'a str,
|
||||
pub force: bool,
|
||||
pub dry_run: bool,
|
||||
pub user_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
pool: &PgPool,
|
||||
params: ImportParams<'_>,
|
||||
master_key: &[u8; 32],
|
||||
) -> Result<ImportSummary> {
|
||||
let format = ExportFormat::from_extension(params.file)?;
|
||||
let content = std::fs::read_to_string(params.file)
|
||||
.map_err(|e| anyhow::anyhow!("Cannot read file '{}': {}", params.file, e))?;
|
||||
let data = format.deserialize(&content)?;
|
||||
|
||||
if data.version != 1 {
|
||||
anyhow::bail!(
|
||||
"Unsupported export version {}. Only version 1 is supported.",
|
||||
data.version
|
||||
);
|
||||
}
|
||||
|
||||
let total = data.entries.len();
|
||||
let mut inserted = 0usize;
|
||||
let mut skipped = 0usize;
|
||||
let mut failed = 0usize;
|
||||
|
||||
for entry in &data.entries {
|
||||
let exists: bool = sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM entries \
|
||||
WHERE namespace = $1 AND kind = $2 AND name = $3 AND user_id IS NOT DISTINCT FROM $4)",
|
||||
)
|
||||
.bind(&entry.namespace)
|
||||
.bind(&entry.kind)
|
||||
.bind(&entry.name)
|
||||
.bind(params.user_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
|
||||
if exists && !params.force {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Import aborted: conflict on [{}/{}/{}]",
|
||||
entry.namespace,
|
||||
entry.kind,
|
||||
entry.name
|
||||
));
|
||||
}
|
||||
|
||||
if params.dry_run {
|
||||
if exists {
|
||||
skipped += 1;
|
||||
} else {
|
||||
inserted += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let secret_entries = build_secret_entries(entry.secrets.as_ref());
|
||||
let meta_entries = build_meta_entries(&entry.metadata);
|
||||
|
||||
match add_run(
|
||||
pool,
|
||||
AddParams {
|
||||
namespace: &entry.namespace,
|
||||
kind: &entry.kind,
|
||||
name: &entry.name,
|
||||
tags: &entry.tags,
|
||||
meta_entries: &meta_entries,
|
||||
secret_entries: &secret_entries,
|
||||
user_id: params.user_id,
|
||||
},
|
||||
master_key,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
inserted += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
namespace = entry.namespace,
|
||||
kind = entry.kind,
|
||||
name = entry.name,
|
||||
error = %e,
|
||||
"failed to import entry"
|
||||
);
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if failed > 0 {
|
||||
return Err(anyhow::anyhow!("{} record(s) failed to import", failed));
|
||||
}
|
||||
|
||||
Ok(ImportSummary {
|
||||
total,
|
||||
inserted,
|
||||
skipped,
|
||||
failed,
|
||||
dry_run: params.dry_run,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user