feat(v3): migrate workspace to API, Tauri desktop, and v3 crates; remove legacy MCP stack
Some checks failed
Secrets v3 CI / 检查 (push) Has been cancelled
Some checks failed
Secrets v3 CI / 检查 (push) Has been cancelled
- Add apps/api, desktop Tauri shell, domain/application/crypto/device-auth/infrastructure-db - Replace desktop-daemon vault integration; drop secrets-core and secrets-mcp* - Ignore apps/desktop/dist and generated Tauri icons; document icon/dist steps in AGENTS.md - Apply rustfmt; fix clippy (collapsible_if, HTTP method as str)
This commit is contained in:
147
crates/application/src/vault_store.rs
Normal file
147
crates/application/src/vault_store.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use anyhow::{Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use secrets_domain::{VaultObjectEnvelope, VaultObjectKind, VaultTombstone};
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
struct VaultObjectRow {
|
||||
object_id: Uuid,
|
||||
_object_kind: String,
|
||||
revision: i64,
|
||||
cipher_version: i32,
|
||||
ciphertext: Vec<u8>,
|
||||
content_hash: String,
|
||||
deleted_at: Option<DateTime<Utc>>,
|
||||
updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl From<VaultObjectRow> for VaultObjectEnvelope {
|
||||
fn from(row: VaultObjectRow) -> Self {
|
||||
Self {
|
||||
object_id: row.object_id,
|
||||
object_kind: VaultObjectKind::Cipher,
|
||||
revision: row.revision,
|
||||
cipher_version: row.cipher_version,
|
||||
ciphertext: row.ciphertext,
|
||||
content_hash: row.content_hash,
|
||||
deleted_at: row.deleted_at,
|
||||
updated_at: row.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_objects_since(
|
||||
pool: &PgPool,
|
||||
user_id: Uuid,
|
||||
cursor: i64,
|
||||
limit: i64,
|
||||
) -> Result<Vec<VaultObjectEnvelope>> {
|
||||
let rows = sqlx::query_as::<_, VaultObjectRow>(
|
||||
r#"
|
||||
SELECT
|
||||
object_id,
|
||||
object_kind AS _object_kind,
|
||||
revision,
|
||||
cipher_version,
|
||||
ciphertext,
|
||||
content_hash,
|
||||
deleted_at,
|
||||
updated_at
|
||||
FROM vault_objects
|
||||
WHERE user_id = $1
|
||||
AND revision > $2
|
||||
ORDER BY revision ASC
|
||||
LIMIT $3
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(cursor)
|
||||
.bind(limit.max(1))
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.context("failed to list vault objects")?;
|
||||
|
||||
Ok(rows.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
pub async fn get_object(
|
||||
pool: &PgPool,
|
||||
user_id: Uuid,
|
||||
object_id: Uuid,
|
||||
) -> Result<Option<VaultObjectEnvelope>> {
|
||||
let row = sqlx::query_as::<_, VaultObjectRow>(
|
||||
r#"
|
||||
SELECT
|
||||
object_id,
|
||||
object_kind AS _object_kind,
|
||||
revision,
|
||||
cipher_version,
|
||||
ciphertext,
|
||||
content_hash,
|
||||
deleted_at,
|
||||
updated_at
|
||||
FROM vault_objects
|
||||
WHERE user_id = $1
|
||||
AND object_id = $2
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(object_id)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.context("failed to load vault object")?;
|
||||
|
||||
Ok(row.map(Into::into))
|
||||
}
|
||||
|
||||
pub async fn list_tombstones_since(
|
||||
pool: &PgPool,
|
||||
user_id: Uuid,
|
||||
cursor: i64,
|
||||
limit: i64,
|
||||
) -> Result<Vec<VaultTombstone>> {
|
||||
let rows = sqlx::query_as::<_, (Uuid, i64, DateTime<Utc>)>(
|
||||
r#"
|
||||
SELECT object_id, revision, deleted_at
|
||||
FROM vault_objects
|
||||
WHERE user_id = $1
|
||||
AND revision > $2
|
||||
AND deleted_at IS NOT NULL
|
||||
ORDER BY revision ASC
|
||||
LIMIT $3
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(cursor)
|
||||
.bind(limit.max(1))
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.context("failed to list tombstones")?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|(object_id, revision, deleted_at)| VaultTombstone {
|
||||
object_id,
|
||||
revision,
|
||||
deleted_at,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn max_server_revision(pool: &PgPool, user_id: Uuid) -> Result<i64> {
|
||||
let revision = sqlx::query_scalar::<_, Option<i64>>(
|
||||
r#"
|
||||
SELECT MAX(revision)
|
||||
FROM vault_objects
|
||||
WHERE user_id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.context("failed to load max server revision")?;
|
||||
|
||||
Ok(revision.unwrap_or(0))
|
||||
}
|
||||
Reference in New Issue
Block a user