171 lines
4.9 KiB
Rust
171 lines
4.9 KiB
Rust
use crate::app::AppConfig;
|
|||
|
|
use crate::context::RequestContext;
|
||
|
|
use crate::error::WebError;
|
||
|
|
use crate::routes::query_support::{
|
||
|
|
execute_runtime_query_via_convex, resolve_effective_workspace_id,
|
||
|
|
};
|
||
|
|
use crate::routes::snapshot_support::{
|
||
|
|
execute_kernel_query, load_projection_snapshot, load_sidebar_dataset, subtree_query,
|
||
|
|
ProjectionSnapshotSpec,
|
||
|
|
};
|
||
|
|
use bridge_runtime::RuntimeQueryEnvelopeWire;
|
||
|
|
use core_protocol::KernelProjectionKind;
|
||
|
|
use serde::Deserialize;
|
||
|
|
use serde_json::{json, Value};
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Deserialize, Default)]
|
||
|
|
#[serde(rename_all = "camelCase")]
|
||
|
|
pub struct StreamSnapshotQuery {
|
||
|
|
pub workspace_id: Option<String>,
|
||
|
|
pub root_node_id: Option<String>,
|
||
|
|
pub depth: Option<u32>,
|
||
|
|
pub cursor: Option<String>,
|
||
|
|
pub limit: Option<u32>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum StreamSnapshotScope {
|
||
|
|
Workspace,
|
||
|
|
Subtree,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl StreamSnapshotScope {
|
||
|
|
pub fn as_str(self) -> &'static str {
|
||
|
|
match self {
|
||
|
|
Self::Workspace => "workspace",
|
||
|
|
Self::Subtree => "subtree",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn resolve_stream_scope(query: &StreamSnapshotQuery) -> StreamSnapshotScope {
|
||
|
|
if query
|
||
|
|
.root_node_id
|
||
|
|
.as_deref()
|
||
|
|
.map(str::trim)
|
||
|
|
.filter(|value| !value.is_empty())
|
||
|
|
.is_some()
|
||
|
|
{
|
||
|
|
StreamSnapshotScope::Subtree
|
||
|
|
} else {
|
||
|
|
StreamSnapshotScope::Workspace
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn workspace_overview_query(
|
||
|
|
workspace_id: &str,
|
||
|
|
query: &StreamSnapshotQuery,
|
||
|
|
) -> RuntimeQueryEnvelopeWire {
|
||
|
|
RuntimeQueryEnvelopeWire {
|
||
|
|
name: "bridge.workspace.overview".into(),
|
||
|
|
payload: json!({
|
||
|
|
"workspaceId": workspace_id,
|
||
|
|
"cursor": query.cursor,
|
||
|
|
"limit": query.limit.unwrap_or(20),
|
||
|
|
"commandStatus": Value::Null,
|
||
|
|
"eventStatus": Value::Null,
|
||
|
|
"targetPageId": Value::Null,
|
||
|
|
"targetBlockId": Value::Null,
|
||
|
|
"aggregateType": Value::Null,
|
||
|
|
"aggregateId": query.root_node_id,
|
||
|
|
}),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn load_stream_snapshot(
|
||
|
|
config: &AppConfig,
|
||
|
|
context: &RequestContext,
|
||
|
|
query: &StreamSnapshotQuery,
|
||
|
|
) -> Result<Value, WebError> {
|
||
|
|
let effective_workspace_id =
|
||
|
|
resolve_effective_workspace_id(context, query.workspace_id.as_deref(), true)?
|
||
|
|
.expect("workspace_required 已确保存在");
|
||
|
|
let scope = resolve_stream_scope(query);
|
||
|
|
|
||
|
|
let snapshot = match scope {
|
||
|
|
StreamSnapshotScope::Workspace => {
|
||
|
|
let loaded = load_projection_snapshot(
|
||
|
|
config,
|
||
|
|
context,
|
||
|
|
&ProjectionSnapshotSpec {
|
||
|
|
workspace_id: &effective_workspace_id,
|
||
|
|
root_node_id: None,
|
||
|
|
depth: query.depth,
|
||
|
|
projection: KernelProjectionKind::SidebarTree,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
.await?;
|
||
|
|
|
||
|
|
json!({
|
||
|
|
"dataset": loaded.dataset,
|
||
|
|
"tree": loaded.projection,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
StreamSnapshotScope::Subtree => {
|
||
|
|
let root_node_id = query
|
||
|
|
.root_node_id
|
||
|
|
.as_deref()
|
||
|
|
.map(str::trim)
|
||
|
|
.filter(|value| !value.is_empty())
|
||
|
|
.expect("subtree scope 已确保 rootNodeId 存在");
|
||
|
|
let dataset = load_sidebar_dataset(config, context, &effective_workspace_id).await?;
|
||
|
|
let tree = execute_kernel_query(
|
||
|
|
context,
|
||
|
|
&effective_workspace_id,
|
||
|
|
subtree_query(&effective_workspace_id, root_node_id, query.depth),
|
||
|
|
dataset.clone(),
|
||
|
|
)?;
|
||
|
|
|
||
|
|
json!({
|
||
|
|
"dataset": dataset,
|
||
|
|
"tree": tree,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
let overview = execute_runtime_query_via_convex(
|
||
|
|
config,
|
||
|
|
context,
|
||
|
|
Some(&effective_workspace_id),
|
||
|
|
workspace_overview_query(&effective_workspace_id, query),
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
.ok();
|
||
|
|
|
||
|
|
Ok(json!({
|
||
|
|
"kind": "snapshot",
|
||
|
|
"scope": scope.as_str(),
|
||
|
|
"requestId": context.trace.request_id,
|
||
|
|
"traceId": context.trace.trace_id,
|
||
|
|
"workspaceId": effective_workspace_id,
|
||
|
|
"rootNodeId": query.root_node_id,
|
||
|
|
"depth": query.depth,
|
||
|
|
"snapshot": snapshot,
|
||
|
|
"overview": overview,
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::{resolve_stream_scope, StreamSnapshotQuery, StreamSnapshotScope};
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn stream_scope_defaults_to_workspace() {
|
||
|
|
assert_eq!(
|
||
|
|
resolve_stream_scope(&StreamSnapshotQuery::default()),
|
||
|
|
StreamSnapshotScope::Workspace
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn stream_scope_switches_to_subtree_when_root_exists() {
|
||
|
|
assert_eq!(
|
||
|
|
resolve_stream_scope(&StreamSnapshotQuery {
|
||
|
|
root_node_id: Some("page_root".into()),
|
||
|
|
..StreamSnapshotQuery::default()
|
||
|
|
}),
|
||
|
|
StreamSnapshotScope::Subtree
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|