feat: user-scoped history/delete/rollback, dashboard & login UI, ignore *.pem
- Filter history/rollback/delete by user_id in secrets-core - MCP tools/web pass user context; dashboard refresh; favicon static - .gitignore *.pem; vscode tasks tweaks - clippy: collapse else-if in rollback latest-history branch Made-with: Cursor
This commit is contained in:
@@ -33,7 +33,15 @@ pub async fn run(pool: &PgPool, params: DeleteParams<'_>) -> Result<DeleteResult
|
||||
let kind = params
|
||||
.kind
|
||||
.ok_or_else(|| anyhow::anyhow!("--kind is required when --name is specified"))?;
|
||||
delete_one(pool, params.namespace, kind, name, params.user_id).await
|
||||
delete_one(
|
||||
pool,
|
||||
params.namespace,
|
||||
kind,
|
||||
name,
|
||||
params.dry_run,
|
||||
params.user_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
delete_bulk(
|
||||
@@ -53,8 +61,48 @@ async fn delete_one(
|
||||
namespace: &str,
|
||||
kind: &str,
|
||||
name: &str,
|
||||
dry_run: bool,
|
||||
user_id: Option<Uuid>,
|
||||
) -> Result<DeleteResult> {
|
||||
if dry_run {
|
||||
let exists: bool = if let Some(uid) = user_id {
|
||||
sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM entries \
|
||||
WHERE user_id = $1 AND namespace = $2 AND kind = $3 AND name = $4)",
|
||||
)
|
||||
.bind(uid)
|
||||
.bind(namespace)
|
||||
.bind(kind)
|
||||
.bind(name)
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM entries \
|
||||
WHERE user_id IS NULL AND namespace = $1 AND kind = $2 AND name = $3)",
|
||||
)
|
||||
.bind(namespace)
|
||||
.bind(kind)
|
||||
.bind(name)
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
};
|
||||
|
||||
let deleted = if exists {
|
||||
vec![DeletedEntry {
|
||||
namespace: namespace.to_string(),
|
||||
kind: kind.to_string(),
|
||||
name: name.to_string(),
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
return Ok(DeleteResult {
|
||||
deleted,
|
||||
dry_run: true,
|
||||
});
|
||||
}
|
||||
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
let row: Option<EntryRow> = if let Some(uid) = user_id {
|
||||
@@ -88,7 +136,7 @@ async fn delete_one(
|
||||
});
|
||||
};
|
||||
|
||||
snapshot_and_delete(&mut tx, namespace, kind, name, &row).await?;
|
||||
snapshot_and_delete(&mut tx, namespace, kind, name, &row, user_id).await?;
|
||||
crate::audit::log_tx(&mut tx, "delete", namespace, kind, name, json!({})).await;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -186,7 +234,10 @@ async fn delete_bulk(
|
||||
metadata: row.metadata.clone(),
|
||||
};
|
||||
let mut tx = pool.begin().await?;
|
||||
snapshot_and_delete(&mut tx, namespace, &row.kind, &row.name, &entry_row).await?;
|
||||
snapshot_and_delete(
|
||||
&mut tx, namespace, &row.kind, &row.name, &entry_row, user_id,
|
||||
)
|
||||
.await?;
|
||||
crate::audit::log_tx(
|
||||
&mut tx,
|
||||
"delete",
|
||||
@@ -216,11 +267,13 @@ async fn snapshot_and_delete(
|
||||
kind: &str,
|
||||
name: &str,
|
||||
row: &EntryRow,
|
||||
user_id: Option<Uuid>,
|
||||
) -> Result<()> {
|
||||
if let Err(e) = db::snapshot_entry_history(
|
||||
tx,
|
||||
db::EntrySnapshotParams {
|
||||
entry_id: row.id,
|
||||
user_id,
|
||||
namespace,
|
||||
kind,
|
||||
name,
|
||||
|
||||
Reference in New Issue
Block a user