feat(secrets-mcp): Web 条目编辑 API 与 Notes 列表展示优化(0.3.6)
- secrets-core: EntryWriteRow;按 id 更新/删除(含并发冲突与唯一键)
- Web: PATCH/DELETE /api/entries/{id};列表编辑/删除与错误映射
- entries 模板:Notes 限高滚动;空 Notes 不显示占位框
- 版本 0.3.5 → 0.3.6,同步 Cargo.lock
Made-with: Cursor
This commit is contained in:
@@ -4,7 +4,7 @@ use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db;
|
||||
use crate::models::{EntryRow, SecretFieldRow};
|
||||
use crate::models::{EntryRow, EntryWriteRow, SecretFieldRow};
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct DeletedEntry {
|
||||
@@ -31,6 +31,62 @@ pub struct DeleteParams<'a> {
|
||||
pub user_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// Delete a single entry by id (multi-tenant: `user_id` must match). Cascades `secrets` via FK.
|
||||
pub async fn delete_by_id(pool: &PgPool, entry_id: Uuid, user_id: Uuid) -> Result<DeleteResult> {
|
||||
let mut tx = pool.begin().await?;
|
||||
let row: Option<EntryWriteRow> = sqlx::query_as(
|
||||
"SELECT id, version, folder, type, name, tags, metadata, notes FROM entries \
|
||||
WHERE id = $1 AND user_id = $2 FOR UPDATE",
|
||||
)
|
||||
.bind(entry_id)
|
||||
.bind(user_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let row = match row {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
tx.rollback().await?;
|
||||
anyhow::bail!("Entry not found");
|
||||
}
|
||||
};
|
||||
|
||||
let folder = row.folder.clone();
|
||||
let entry_type = row.entry_type.clone();
|
||||
let name = row.name.clone();
|
||||
let entry_row: EntryRow = (&row).into();
|
||||
|
||||
snapshot_and_delete(
|
||||
&mut tx,
|
||||
&folder,
|
||||
&entry_type,
|
||||
&name,
|
||||
&entry_row,
|
||||
Some(user_id),
|
||||
)
|
||||
.await?;
|
||||
crate::audit::log_tx(
|
||||
&mut tx,
|
||||
Some(user_id),
|
||||
"delete",
|
||||
&folder,
|
||||
&entry_type,
|
||||
&name,
|
||||
json!({ "source": "web", "entry_id": entry_id }),
|
||||
)
|
||||
.await;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(DeleteResult {
|
||||
deleted: vec![DeletedEntry {
|
||||
name,
|
||||
folder,
|
||||
entry_type,
|
||||
}],
|
||||
dry_run: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(pool: &PgPool, params: DeleteParams<'_>) -> Result<DeleteResult> {
|
||||
match params.name {
|
||||
Some(name) => delete_one(pool, name, params.folder, params.dry_run, params.user_id).await,
|
||||
|
||||
Reference in New Issue
Block a user