chore: align sqlite control plane architecture
- replace default Convex control-plane wording with Rust SQLite control-plane across architecture, AGENTS, Reasonix, and design docs - retire root Convex functions source and deploy script into recycle while keeping explicit cloud/compat/sync-replica boundaries - add control-plane migration guard/docs and keep CodeGraph refreshed after the SQLite control-plane cutover
This commit is contained in:
@@ -5,6 +5,7 @@ use crate::local_folder_watcher_registry::LocalFolderWatcherRegistry;
|
||||
use crate::middleware::request_context::inject_request_context;
|
||||
use crate::routes::build_router;
|
||||
use axum::Router;
|
||||
use control_plane::{ControlPlaneStore, SqliteControlPlaneStore};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
@@ -137,7 +138,7 @@ fn read_env_or_dotenv(key: &str) -> Option<String> {
|
||||
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
config: Arc<AppConfig>,
|
||||
local_folder_watcher_registry: LocalFolderWatcherRegistry,
|
||||
@@ -146,6 +147,7 @@ pub struct AppState {
|
||||
pub stream_delta_tx: broadcast::Sender<serde_json::Value>,
|
||||
pub acp_runtime: Arc<AcpRuntimeManager>,
|
||||
pub buffer_store: BufferStore,
|
||||
control_plane: Arc<SqliteControlPlaneStore>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -155,6 +157,7 @@ impl AppState {
|
||||
let actor = EditorRuntimeActor::new();
|
||||
actor.set_block_delta_tx(block_delta_tx.clone());
|
||||
let buffer_store = BufferStore::new();
|
||||
let control_plane = Arc::new(open_control_plane_store());
|
||||
Self {
|
||||
config: Arc::new(config),
|
||||
local_folder_watcher_registry: LocalFolderWatcherRegistry::new(buffer_store.clone()),
|
||||
@@ -163,6 +166,7 @@ impl AppState {
|
||||
stream_delta_tx,
|
||||
acp_runtime: Arc::new(AcpRuntimeManager::from_env()),
|
||||
buffer_store,
|
||||
control_plane,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +177,27 @@ impl AppState {
|
||||
pub fn local_folder_watcher_registry(&self) -> &LocalFolderWatcherRegistry {
|
||||
&self.local_folder_watcher_registry
|
||||
}
|
||||
|
||||
pub fn control_plane(&self) -> &dyn ControlPlaneStore {
|
||||
self.control_plane.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn open_control_plane_store() -> SqliteControlPlaneStore {
|
||||
SqliteControlPlaneStore::in_memory().expect("初始化测试 SQLite 控制面")
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn open_control_plane_store() -> SqliteControlPlaneStore {
|
||||
let db_path = env::var("MNOTE_CONTROL_PLANE_DB_PATH")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or_else(|| "/mnt/Data1T/Mnote_data/control-plane/control-plane.db".to_string());
|
||||
if let Some(parent) = std::path::Path::new(&db_path).parent() {
|
||||
fs::create_dir_all(parent).expect("创建 SQLite 控制面目录");
|
||||
}
|
||||
SqliteControlPlaneStore::open(&db_path).expect("初始化 SQLite 控制面")
|
||||
}
|
||||
|
||||
pub fn build_app(state: AppState) -> Router {
|
||||
@@ -180,3 +205,61 @@ pub fn build_app(state: AppState) -> Router {
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(axum::middleware::from_fn(inject_request_context))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use control_plane::UpsertUserInput;
|
||||
|
||||
fn test_config() -> AppConfig {
|
||||
AppConfig {
|
||||
service_name: "mnote-web".into(),
|
||||
service_version: "0.1.0".into(),
|
||||
bind_addr: "127.0.0.1:0".into(),
|
||||
public_bind_addr: "127.0.0.1:3000".into(),
|
||||
legacy_next_base_url: None,
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
convex_admin_key: None,
|
||||
allow_dev_fixtures: false,
|
||||
query_fixtures_json: None,
|
||||
mutation_fixtures_json: None,
|
||||
dev_user_id: "dev-user".into(),
|
||||
dev_user_name: "开发用户".into(),
|
||||
dev_user_email: "dev@mnote.local".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_state_initializes_sqlite_control_plane_store() {
|
||||
let state = AppState::new(test_config());
|
||||
|
||||
state
|
||||
.control_plane()
|
||||
.upsert_user(UpsertUserInput {
|
||||
id: Some("shujuan".into()),
|
||||
email: Some("shujuan@163.com".into()),
|
||||
username: "shujuan".into(),
|
||||
display_name: "shujuan".into(),
|
||||
role: None,
|
||||
password_hash: None,
|
||||
})
|
||||
.expect("upsert user");
|
||||
|
||||
let workspace = state
|
||||
.control_plane()
|
||||
.ensure_default_workspace("shujuan")
|
||||
.expect("default workspace");
|
||||
assert_eq!(workspace.name, "shujuan 的空间");
|
||||
|
||||
let access = state
|
||||
.control_plane()
|
||||
.resolve_access("shujuan", &workspace.root_uri)
|
||||
.expect("owner access");
|
||||
assert_eq!(access.permission, "write");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user