Kontekst-arv: automatisk belongs_to-edge ved input i kommunikasjonsnode
Legger til context_id-parameter på create_node-intensjonen. Når context_id er satt (og peker på en kommunikasjonsnode), opprettes automatisk en belongs_to-edge fra den nye noden til kontekstnoden. Dette er kjernen i kontekst-arv: si noe i et møte → noden tilhører møtet automatisk. Backend: Validerer at context_id eksisterer og er communication-node, oppretter belongs_to-edge i STDB+PG etter node-opprettelse. Frontend: Oppdaterer API-typer med context_id og belongs_to_edge_id. Ref: docs/retninger/universell_input.md (kontekst arves automatisk) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4ef0b31b3c
commit
b55705d12c
2 changed files with 95 additions and 1 deletions
|
|
@ -12,10 +12,14 @@ export interface CreateNodeRequest {
|
|||
content?: string;
|
||||
visibility?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
/** Kontekst-node (kommunikasjonsnode). Gir automatisk belongs_to-edge. */
|
||||
context_id?: string;
|
||||
}
|
||||
|
||||
export interface CreateNodeResponse {
|
||||
node_id: string;
|
||||
/** Edge-ID for automatisk belongs_to-edge (kun ved context_id). */
|
||||
belongs_to_edge_id?: string;
|
||||
}
|
||||
|
||||
export interface CreateEdgeRequest {
|
||||
|
|
|
|||
|
|
@ -136,17 +136,28 @@ pub struct CreateNodeRequest {
|
|||
pub visibility: Option<String>,
|
||||
/// Typespesifikk metadata (JSON-objekt).
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
/// Kontekst-node (f.eks. kommunikasjonsnode). Hvis satt, opprettes
|
||||
/// automatisk en `belongs_to`-edge fra den nye noden til kontekstnoden.
|
||||
/// Ref: docs/retninger/universell_input.md (kontekst-arv).
|
||||
pub context_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CreateNodeResponse {
|
||||
pub node_id: Uuid,
|
||||
/// Edge-ID for automatisk opprettet `belongs_to`-edge (kun ved context_id).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub belongs_to_edge_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// POST /intentions/create_node
|
||||
///
|
||||
/// Validerer input, skriver til STDB (instant), spawner async PG-skriving.
|
||||
/// Returnerer node_id umiddelbart.
|
||||
///
|
||||
/// Hvis `context_id` er satt, opprettes automatisk en `belongs_to`-edge
|
||||
/// fra den nye noden til kontekstnoden. Kontekstnoden må eksistere og
|
||||
/// være en kommunikasjonsnode. Ref: docs/retninger/universell_input.md
|
||||
pub async fn create_node(
|
||||
State(state): State<AppState>,
|
||||
user: AuthUser,
|
||||
|
|
@ -165,6 +176,31 @@ pub async fn create_node(
|
|||
)));
|
||||
}
|
||||
|
||||
// -- Valider context_id hvis satt --
|
||||
if let Some(ctx_id) = req.context_id {
|
||||
let ctx_node = sqlx::query_as::<_, NodeKindRow>(
|
||||
"SELECT node_kind FROM nodes WHERE id = $1",
|
||||
)
|
||||
.bind(ctx_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("PG-feil ved context_id-sjekk: {e}");
|
||||
internal_error("Databasefeil ved validering av context_id")
|
||||
})?;
|
||||
|
||||
match ctx_node {
|
||||
None => return Err(bad_request(&format!("context_id {} finnes ikke", ctx_id))),
|
||||
Some(row) if row.node_kind != "communication" => {
|
||||
return Err(bad_request(&format!(
|
||||
"context_id {} er en '{}'-node, ikke en kommunikasjonsnode",
|
||||
ctx_id, row.node_kind
|
||||
)));
|
||||
}
|
||||
_ => {} // OK — kommunikasjonsnode
|
||||
}
|
||||
}
|
||||
|
||||
let title = req.title.unwrap_or_default();
|
||||
let content = req.content.unwrap_or_default();
|
||||
let metadata = req
|
||||
|
|
@ -196,6 +232,7 @@ pub async fn create_node(
|
|||
node_id = %node_id,
|
||||
node_kind = %node_kind,
|
||||
created_by = %user.node_id,
|
||||
context_id = ?req.context_id,
|
||||
"Node opprettet i STDB"
|
||||
);
|
||||
|
||||
|
|
@ -211,7 +248,54 @@ pub async fn create_node(
|
|||
user.node_id,
|
||||
);
|
||||
|
||||
Ok(Json(CreateNodeResponse { node_id }))
|
||||
// -- Kontekst-arv: automatisk belongs_to-edge --
|
||||
let belongs_to_edge_id = if let Some(ctx_id) = req.context_id {
|
||||
let edge_id = Uuid::now_v7();
|
||||
let edge_id_str = edge_id.to_string();
|
||||
let ctx_id_str = ctx_id.to_string();
|
||||
let bt_metadata = serde_json::json!({});
|
||||
let bt_metadata_str = bt_metadata.to_string();
|
||||
|
||||
state
|
||||
.stdb
|
||||
.create_edge(
|
||||
&edge_id_str,
|
||||
&node_id_str, // source = ny node
|
||||
&ctx_id_str, // target = kommunikasjonsnoden
|
||||
"belongs_to",
|
||||
&bt_metadata_str,
|
||||
false,
|
||||
&created_by_str,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| stdb_error("create_edge (belongs_to)", e))?;
|
||||
|
||||
tracing::info!(
|
||||
edge_id = %edge_id,
|
||||
node_id = %node_id,
|
||||
context_id = %ctx_id,
|
||||
"belongs_to-edge opprettet i STDB (kontekst-arv)"
|
||||
);
|
||||
|
||||
// belongs_to er ikke tilgangsgivende — enkel PG-insert
|
||||
spawn_pg_insert_edge(
|
||||
state.db.clone(),
|
||||
state.stdb.clone(),
|
||||
edge_id,
|
||||
node_id,
|
||||
ctx_id,
|
||||
"belongs_to".to_string(),
|
||||
bt_metadata,
|
||||
false,
|
||||
user.node_id,
|
||||
);
|
||||
|
||||
Some(edge_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Json(CreateNodeResponse { node_id, belongs_to_edge_id }))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -705,6 +789,12 @@ struct NodeRow {
|
|||
metadata: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Enkel rad for å sjekke node_kind (brukes ved context_id-validering).
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct NodeKindRow {
|
||||
node_kind: String,
|
||||
}
|
||||
|
||||
/// Spawner en tokio-task som skriver noden til PostgreSQL i bakgrunnen.
|
||||
fn spawn_pg_insert_node(
|
||||
db: PgPool,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue