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

- 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:
agent
2026-04-13 08:49:57 +08:00
parent cb5865b958
commit 0374899dab
130 changed files with 20447 additions and 21577 deletions

View 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))
}