//! SQLite 控制面 Phase 1 的最小数据模型。 use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub type EntityId = String; pub type Timestamp = String; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UserRecord { pub id: EntityId, pub email: Option, pub username: String, pub display_name: String, pub role: String, pub status: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } /// 会话令牌哈希(领域前缀,避免与 share/password 等 token 类型混淆)。 /// 注意:改前缀会使既有 session 行失效,需用户重新登录。 pub fn session_token_hash(raw_token: &str) -> String { let mut hasher = Sha256::new(); hasher.update(b"mnote-session-token-v1:"); hasher.update(raw_token.as_bytes()); format!("sha256-v1:{}", hex::encode(hasher.finalize())) } /// 密码哈希 v1:SHA-256 + 固定领域前缀(兼容既有 `sha256-v1:` 存档)。 /// 技术债:生产应迁移 Argon2id + 每用户随机盐;在此保持算法兼容以免批量锁死账号。 pub fn password_hash_v1(password: &str) -> String { let mut hasher = Sha256::new(); hasher.update(b"mnote-password-v1:"); hasher.update(password.as_bytes()); format!("sha256-v1:{}", hex::encode(hasher.finalize())) } pub fn share_token_hash_v1(token: &str) -> String { let mut hasher = Sha256::new(); hasher.update(b"mnote-share-token-v1:"); hasher.update(token.as_bytes()); format!("sha256-v1:{}", hex::encode(hasher.finalize())) } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertUserInput { pub id: Option, pub email: Option, pub username: String, pub display_name: String, pub role: Option, /// 密码哈希;Debug 脱敏,避免日志泄露。 pub password_hash: Option, } impl std::fmt::Debug for UpsertUserInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UpsertUserInput") .field("id", &self.id) .field("email", &self.email) .field("username", &self.username) .field("display_name", &self.display_name) .field("role", &self.role) .field( "password_hash", &self .password_hash .as_ref() .map(|_| "") .unwrap_or("None"), ) .finish() } } #[derive(Clone, PartialEq, Eq, Deserialize)] pub struct CreatePasswordIdentityInput { pub user_id: EntityId, pub email: Option, pub username: String, /// 明文密码仅用于创建身份;Debug/Serialize 必须脱敏。 pub password: String, } impl Serialize for CreatePasswordIdentityInput { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { use serde::ser::SerializeStruct; let mut state = serializer.serialize_struct("CreatePasswordIdentityInput", 4)?; state.serialize_field("user_id", &self.user_id)?; state.serialize_field("email", &self.email)?; state.serialize_field("username", &self.username)?; state.serialize_field("password", "")?; state.end() } } impl std::fmt::Debug for CreatePasswordIdentityInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CreatePasswordIdentityInput") .field("user_id", &self.user_id) .field("email", &self.email) .field("username", &self.username) .field("password", &"") .finish() } } #[derive(Clone, PartialEq, Eq, Deserialize)] pub struct AuthenticatePasswordInput { pub account: String, /// 明文密码仅用于鉴权;Debug/Serialize 脱敏。 pub password: String, pub session_id: Option, pub token_hash: String, pub user_agent: Option, pub ip_hash: Option, pub expires_at: Option, } impl Serialize for AuthenticatePasswordInput { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { use serde::ser::SerializeStruct; let mut state = serializer.serialize_struct("AuthenticatePasswordInput", 7)?; state.serialize_field("account", &self.account)?; state.serialize_field("password", "")?; state.serialize_field("session_id", &self.session_id)?; state.serialize_field("token_hash", "")?; state.serialize_field("user_agent", &self.user_agent)?; state.serialize_field("ip_hash", &self.ip_hash)?; state.serialize_field("expires_at", &self.expires_at)?; state.end() } } impl std::fmt::Debug for AuthenticatePasswordInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AuthenticatePasswordInput") .field("account", &self.account) .field("password", &"") .field("session_id", &self.session_id) .field("token_hash", &"") .field("user_agent", &self.user_agent) .field("ip_hash", &self.ip_hash) .field("expires_at", &self.expires_at) .finish() } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthSessionRecord { pub id: EntityId, pub user_id: EntityId, pub token_hash: String, pub user_agent: Option, pub ip_hash: Option, pub created_at: Timestamp, pub expires_at: Timestamp, pub revoked_at: Option, pub last_seen_at: Timestamp, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CreateSessionInput { pub id: Option, pub user_id: EntityId, pub token_hash: String, pub user_agent: Option, pub ip_hash: Option, pub expires_at: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ResolvedAuthSession { pub session: AuthSessionRecord, pub user: UserRecord, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct WorkspaceRecord { pub id: EntityId, pub owner_user_id: EntityId, pub name: String, pub kind: String, pub root_uri: String, pub root_path: String, pub source_kind: String, pub status: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertWorkspaceInput { pub id: Option, pub owner_user_id: EntityId, pub name: String, pub kind: Option, pub root_uri: String, pub root_path: String, pub source_kind: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DirectoryGrantRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub root_uri: String, pub root_path: String, pub permission: String, pub recursive: bool, pub capabilities_json: String, pub source: String, pub status: String, pub created_by: Option, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DirectoryGrantLookup { pub grant_id: Option, pub user_id: Option, pub root_uri: Option, pub include_revoked: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DirectoryGrantInput { pub user_id: EntityId, pub workspace_id: Option, pub root_uri: String, pub root_path: String, pub permission: String, pub recursive: bool, pub capabilities: Vec, pub source: String, pub created_by: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ResolvedAccess { pub user_id: EntityId, pub root_uri: String, pub permission: String, pub grant_ids: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct OutboxEventRecord { pub id: EntityId, pub topic: String, pub event_type: String, pub payload_json: String, pub created_at: Timestamp, pub delivered_at: Option, pub attempts: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct OutboxEventInput { pub topic: String, pub event_type: String, pub payload_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SidebarShortcutRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: EntityId, pub root_uri: Option, pub kind: String, pub source_kind: String, pub target_id: String, pub relative_path: Option, pub document_id: Option, pub title: String, pub icon: Option, pub sort_order: i64, pub status: String, pub metadata_json: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertSidebarShortcutInput { pub id: Option, pub user_id: EntityId, pub workspace_id: EntityId, pub root_uri: Option, pub kind: String, pub source_kind: String, pub target_id: String, pub relative_path: Option, pub document_id: Option, pub title: String, pub icon: Option, pub sort_order: i64, pub metadata_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UserUiPreferenceRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub source_kind: Option, pub scope_kind: String, pub scope_id: String, pub key: String, pub value_json: String, pub status: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertUserUiPreferenceInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub source_kind: Option, pub scope_kind: String, pub scope_id: String, pub key: String, pub value_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiAgentProfileRecord { pub id: EntityId, pub agent_id: String, pub profile_kind: String, pub owner_user_id: Option, pub base_profile_name: String, pub isolated_profile_name: String, pub display_name: String, pub status: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiAgentProfileGrantRecord { pub id: EntityId, pub profile_id: EntityId, pub user_id: Option, pub role: String, pub can_run: bool, pub can_manage_skills: bool, pub can_manage_config: bool, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiAgentProfileAccessRecord { pub profile: AiAgentProfileRecord, pub grant: AiAgentProfileGrantRecord, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct NavigationRecentRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub kind: String, pub source_kind: String, pub root_uri: String, pub target_key: String, pub relative_path: Option, pub document_id: Option, pub title: String, pub status: String, pub metadata_json: String, pub visited_at: Timestamp, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertNavigationRecentInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub kind: String, pub source_kind: String, pub root_uri: String, pub relative_path: Option, pub document_id: Option, pub title: String, pub metadata_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct KnowledgeBaseRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub root_uri: Option, pub name: String, pub description: Option, pub provider: String, pub provider_kb_id: String, pub default_tool_enabled: bool, pub can_write: bool, pub source_count: i64, pub chunk_count: i64, pub status: String, pub metadata_json: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertKnowledgeBaseInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub root_uri: Option, pub name: String, pub description: Option, pub provider: String, pub provider_kb_id: String, pub default_tool_enabled: bool, pub can_write: bool, pub source_count: i64, pub chunk_count: i64, pub status: String, pub metadata_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiPolicyRecord { pub id: EntityId, pub user_id: Option, pub workspace_id: Option, pub allowed_roots_json: String, pub model_policy_json: String, pub quota_json: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertAiPolicyInput { pub id: Option, pub user_id: Option, pub workspace_id: Option, pub allowed_roots_json: String, pub model_policy_json: String, pub quota_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SyncStateRecord { pub id: EntityId, pub workspace_id: EntityId, pub remote_kind: String, pub remote_id: Option, pub cursor: Option, pub last_synced_at: Option, pub status: String, pub error_json: Option, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertSyncStateInput { pub id: Option, pub workspace_id: EntityId, pub remote_kind: String, pub remote_id: Option, pub cursor: Option, pub last_synced_at: Option, pub status: String, pub error_json: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiRuntimeRunRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub document_id: Option, pub session_id: EntityId, pub run_id: EntityId, pub title: Option, pub profile: String, pub acp_runtime: String, pub trace_id: Option, pub status: String, pub runtime_json: String, pub payload_json: String, pub deleted_at: Option, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertAiRuntimeRunInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub document_id: Option, pub session_id: EntityId, pub run_id: EntityId, pub title: Option, pub profile: String, pub acp_runtime: String, pub trace_id: Option, pub status: String, pub runtime_json: String, pub payload_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiRuntimeEventRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub document_id: Option, pub session_id: EntityId, pub run_id: EntityId, pub profile: String, pub acp_runtime: String, pub event_type: String, pub payload_json: String, pub created_at: Timestamp, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiRuntimeJournalEventRecord { pub seq: i64, pub event: AiRuntimeEventRecord, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AppendAiRuntimeEventInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub document_id: Option, pub session_id: EntityId, pub run_id: EntityId, pub profile: String, pub acp_runtime: String, pub event_type: String, pub payload_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiExternalConversationBindingRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub mnote_session_id: EntityId, pub acp_session_id: Option, pub agent_id: String, pub profile: String, pub provider: String, pub remote_conversation_id: String, pub remote_url: Option, pub status: String, pub metadata_json: String, pub created_at: Timestamp, pub updated_at: Timestamp, pub deleted_at: Option, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct UpsertAiExternalConversationBindingInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub mnote_session_id: EntityId, pub acp_session_id: Option, pub agent_id: String, pub profile: String, pub provider: String, pub remote_conversation_id: String, pub remote_url: Option, pub status: String, pub metadata_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ShareLinkRecord { pub id: EntityId, pub workspace_id: EntityId, pub resource_kind: String, pub resource_id: String, pub token_hash: String, pub permission: String, pub created_by: EntityId, pub expires_at: Option, pub revoked_at: Option, pub created_at: Timestamp, pub updated_at: Timestamp, pub revision: i64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CreateShareLinkInput { pub id: Option, pub workspace_id: EntityId, pub resource_kind: String, pub resource_id: String, pub token: Option, pub permission: String, pub created_by: EntityId, pub expires_at: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CreatedShareLink { pub link: ShareLinkRecord, pub token: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AuditLogRecord { pub id: EntityId, pub actor_user_id: Option, pub action: String, pub target_kind: String, pub target_id: Option, pub metadata_json: String, pub created_at: Timestamp, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AppendAuditInput { pub actor_user_id: Option, pub action: String, pub target_kind: String, pub target_id: Option, pub metadata_json: String, } // --------------------------------------------------------------------------- // P2: AI tool events (receipts) and file patches — 7-71 // --------------------------------------------------------------------------- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiToolEventRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub session_id: EntityId, pub run_id: Option, pub provider: String, pub provider_session_id: Option, pub tool_name: String, pub allowed: bool, pub deny_reason: Option, pub root_uri: String, pub page_path: Option, pub normalized_file_path: Option, pub diff_summary: Option, pub citation_count: i64, pub before_file_version: Option, pub after_file_version: Option, pub payload_json: String, pub created_at: Timestamp, pub deleted_at: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AppendAiToolEventInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub session_id: EntityId, pub run_id: Option, pub provider: String, pub provider_session_id: Option, pub tool_name: String, pub allowed: bool, pub deny_reason: Option, pub root_uri: String, pub page_path: Option, pub normalized_file_path: Option, pub diff_summary: Option, pub citation_count: i64, pub before_file_version: Option, pub after_file_version: Option, pub payload_json: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AiFilePatchRecord { pub id: EntityId, pub user_id: EntityId, pub workspace_id: Option, pub session_id: EntityId, pub run_id: Option, pub tool_event_id: EntityId, pub root_uri: String, pub relative_path: String, pub before_file_version: Option, pub after_file_version: Option, pub patch_summary_json: String, pub created_at: Timestamp, pub deleted_at: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AppendAiFilePatchInput { pub id: Option, pub user_id: EntityId, pub workspace_id: Option, pub session_id: EntityId, pub run_id: Option, pub tool_event_id: EntityId, pub root_uri: String, pub relative_path: String, pub before_file_version: Option, pub after_file_version: Option, pub patch_summary_json: String, }