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,15 @@
[package]
name = "secrets-infrastructure-db"
version = "0.1.0"
edition.workspace = true
[lib]
name = "secrets_infrastructure_db"
path = "src/lib.rs"
[dependencies]
anyhow.workspace = true
dotenvy.workspace = true
sqlx.workspace = true
tracing.workspace = true
uuid.workspace = true

View File

@@ -0,0 +1,29 @@
mod migrate;
use anyhow::{Context, Result};
use sqlx::PgPool;
use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
use std::str::FromStr;
pub use migrate::migrate_current_schema;
pub fn load_database_url() -> Result<String> {
std::env::var("SECRETS_DATABASE_URL")
.context("SECRETS_DATABASE_URL is required for current services")
}
pub async fn create_pool(database_url: &str) -> Result<PgPool> {
let options =
PgConnectOptions::from_str(database_url).context("failed to parse SECRETS_DATABASE_URL")?;
let pool = PgPoolOptions::new()
.max_connections(
std::env::var("SECRETS_DATABASE_POOL_SIZE")
.ok()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(10),
)
.connect_with(options)
.await
.context("failed to connect to PostgreSQL")?;
Ok(pool)
}

View File

@@ -0,0 +1,108 @@
use anyhow::Result;
use sqlx::PgPool;
pub async fn migrate_current_schema(pool: &PgPool) -> Result<()> {
sqlx::raw_sql(
r#"
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuidv7(),
email VARCHAR(256),
name VARCHAR(256) NOT NULL DEFAULT '',
avatar_url TEXT,
key_salt BYTEA,
key_check BYTEA,
key_params JSONB,
key_version BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS oauth_accounts (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider VARCHAR(32) NOT NULL,
provider_id VARCHAR(256) NOT NULL,
email VARCHAR(256),
name VARCHAR(256),
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(provider, provider_id),
UNIQUE(user_id, provider)
);
CREATE TABLE IF NOT EXISTS devices (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
display_name VARCHAR(256) NOT NULL,
platform VARCHAR(64) NOT NULL,
client_version VARCHAR(64) NOT NULL,
device_fingerprint TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_devices_user_id ON devices(user_id);
CREATE TABLE IF NOT EXISTS device_login_tokens (
id UUID PRIMARY KEY DEFAULT uuidv7(),
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_device_login_tokens_device_id ON device_login_tokens(device_id);
CREATE TABLE IF NOT EXISTS auth_events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
device_name VARCHAR(256) NOT NULL,
platform VARCHAR(64) NOT NULL,
client_version VARCHAR(64) NOT NULL,
ip_addr TEXT,
forwarded_ip TEXT,
login_method VARCHAR(32) NOT NULL,
login_result VARCHAR(32) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_auth_events_user_id_created_at
ON auth_events(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_auth_events_device_id_created_at
ON auth_events(device_id, created_at DESC);
CREATE TABLE IF NOT EXISTS vault_objects (
object_id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
object_kind VARCHAR(32) NOT NULL,
revision BIGINT NOT NULL,
cipher_version INTEGER NOT NULL DEFAULT 1,
ciphertext BYTEA NOT NULL DEFAULT '\x',
content_hash TEXT NOT NULL DEFAULT '',
deleted_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by_device UUID REFERENCES devices(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_vault_objects_user_revision
ON vault_objects(user_id, revision ASC);
CREATE INDEX IF NOT EXISTS idx_vault_objects_user_deleted
ON vault_objects(user_id, deleted_at);
CREATE TABLE IF NOT EXISTS vault_object_revisions (
object_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
revision BIGINT NOT NULL,
cipher_version INTEGER NOT NULL DEFAULT 1,
ciphertext BYTEA NOT NULL DEFAULT '\x',
content_hash TEXT NOT NULL DEFAULT '',
deleted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (object_id, revision)
);
CREATE INDEX IF NOT EXISTS idx_vault_object_revisions_user_revision
ON vault_object_revisions(user_id, revision ASC);
"#,
)
.execute(pool)
.await?;
Ok(())
}