synops/maskinrommet/src/warmup.rs
vegard 8fa2849f0c Legg til node_access i STDB + synk fra maskinrommet
Visibility-filtrering (oppgave 4.3, del 1/2):
- Ny node_access-tabell i STDB-modulen som speiler PG
- Reducers: upsert_node_access, delete_node_access, delete_node_access_for_subject
- STDB-klient i maskinrommet: metoder for node_access
- Warmup synker node_access fra PG til STDB ved oppstart
- Tilgangsgivende edges synker node_access til STDB etter PG-commit
- clear_all tømmer også node_access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:09:55 +01:00

141 lines
3.7 KiB
Rust

// Warmup: last hele grafen fra PG til SpacetimeDB ved oppstart.
//
// Sekvens: clear_all → noder → edges.
// Edges refererer til noder, så noder må lastes først.
//
// Ref: docs/infra/synkronisering.md
use sqlx::PgPool;
use crate::stdb::StdbClient;
/// Last hele grafen fra PG til SpacetimeDB.
pub async fn run(db: &PgPool, stdb: &StdbClient) -> Result<WarmupStats, Box<dyn std::error::Error>> {
tracing::info!("Warmup: starter (PG → SpacetimeDB)");
// 1. Tøm STDB for å unngå duplikater ved restart
stdb.clear_all().await?;
tracing::info!("Warmup: STDB tømt");
// 2. Last alle noder
let nodes = sqlx::query_as::<_, PgNode>(
"SELECT id, node_kind::text, COALESCE(title, '') as title, \
COALESCE(content, '') as content, visibility::text, \
COALESCE(metadata::text, '{}') as metadata, \
created_at, COALESCE(created_by::text, '') as created_by \
FROM nodes ORDER BY created_at"
)
.fetch_all(db)
.await?;
let node_count = nodes.len();
for node in &nodes {
stdb.create_node(
&node.id.to_string(),
&node.node_kind,
&node.title,
&node.content,
&node.visibility,
&node.metadata,
&node.created_by,
)
.await?;
}
tracing::info!("Warmup: {node_count} noder lastet");
// 3. Last alle edges
let edges = sqlx::query_as::<_, PgEdge>(
"SELECT id, source_id, target_id, edge_type, \
COALESCE(metadata::text, '{}') as metadata, \
system, created_at, COALESCE(created_by::text, '') as created_by \
FROM edges ORDER BY created_at"
)
.fetch_all(db)
.await?;
let edge_count = edges.len();
for edge in &edges {
stdb.create_edge(
&edge.id.to_string(),
&edge.source_id.to_string(),
&edge.target_id.to_string(),
&edge.edge_type,
&edge.metadata,
edge.system,
&edge.created_by,
)
.await?;
}
tracing::info!("Warmup: {edge_count} edges lastet");
// 4. Last alle node_access-rader
let access_rows = sqlx::query_as::<_, PgNodeAccess>(
"SELECT subject_id, object_id, access::text, \
COALESCE(via_edge::text, '') as via_edge \
FROM node_access"
)
.fetch_all(db)
.await?;
let access_count = access_rows.len();
for row in &access_rows {
stdb.upsert_node_access(
&row.subject_id.to_string(),
&row.object_id.to_string(),
&row.access,
&row.via_edge,
)
.await?;
}
tracing::info!("Warmup: {access_count} node_access-rader lastet");
let stats = WarmupStats {
nodes: node_count,
edges: edge_count,
access: access_count,
};
tracing::info!("Warmup: ferdig ({} noder, {} edges, {} access)", stats.nodes, stats.edges, stats.access);
Ok(stats)
}
pub struct WarmupStats {
pub nodes: usize,
pub edges: usize,
pub access: usize,
}
// PG-radtyper for sqlx
#[derive(sqlx::FromRow)]
#[allow(dead_code)]
struct PgNode {
id: uuid::Uuid,
node_kind: String,
title: String,
content: String,
visibility: String,
metadata: String,
created_at: chrono::DateTime<chrono::Utc>,
created_by: String,
}
#[derive(sqlx::FromRow)]
#[allow(dead_code)]
struct PgNodeAccess {
subject_id: uuid::Uuid,
object_id: uuid::Uuid,
access: String,
via_edge: String,
}
#[derive(sqlx::FromRow)]
#[allow(dead_code)]
struct PgEdge {
id: uuid::Uuid,
source_id: uuid::Uuid,
target_id: uuid::Uuid,
edge_type: String,
metadata: String,
system: bool,
created_at: chrono::DateTime<chrono::Utc>,
created_by: String,
}