SpacetimeDB var brukt som «instant feedback»-lag mellom portvokteren og frontend. Nå som PG NOTIFY-triggere og WebSocket er på plass (oppgave 22.1–22.2), er STDB-skrivestien overflødig. Endringer: - intentions.rs: Alle CRUD-operasjoner (create/update/delete node/edge) skriver nå synkront til PG i stedet for STDB-først + async PG-jobbkø. PG NOTIFY-triggere gir umiddelbar sanntidsoppdatering til klienter. Tilgangsgivende edges (owner/admin/member_of/reader) bruker transaksjon med recompute_access direkte i handleren. - maintenance.rs: Fjernet StdbClient fra alle funksjoner. Varsler opprettes/oppdateres/slettes direkte i PG. - agent.rs, audio.rs, tts.rs, ai_process.rs: Fjernet STDB-synk etter CLI-verktøy-kjøring. PG NOTIFY dekker sanntidsvisning. - pg_writes.rs: Fjernet sync_node_access_to_stdb. access_changed NOTIFY-trigger håndterer dette. - workspace.rs: Synkrone PG-skrivinger med recompute_access. - summarize.rs, ai_edges.rs: Fjernet StdbClient fra signaturer. - jobs.rs: Fjernet StdbClient fra dispatch og start_worker. - main.rs: Fjernet STDB-initialisering, warmup, stdb_monitor. StdbClient fjernet fra AppState. stdb.rs beholdt som død kode (fjernes i oppgave 22.4). - health.rs: Fjernet STDB-helsesjekk fra dashboard. - Slettet warmup.rs og stdb_monitor.rs (PG→STDB-synk ikke lenger relevant). - docs/retninger/datalaget.md: Markert fase M3 som fullført.
147 lines
4.1 KiB
Rust
147 lines
4.1 KiB
Rust
// Personlig arbeidsflate — oppgave 19.6
|
|
//
|
|
// GET /my/workspace — finn eller opprett brukerens personlige workspace-node.
|
|
//
|
|
// Hvert bruker-node har én workspace-node (node_kind='workspace') koblet via
|
|
// en owner-edge. Ved første kall opprettes noden automatisk.
|
|
//
|
|
// Ref: docs/retninger/arbeidsflaten.md § "Tre lag"
|
|
|
|
use axum::extract::State;
|
|
use axum::http::StatusCode;
|
|
use axum::Json;
|
|
use serde::Serialize;
|
|
use uuid::Uuid;
|
|
|
|
use crate::auth::AuthUser;
|
|
use crate::AppState;
|
|
|
|
#[derive(Serialize)]
|
|
pub struct WorkspaceResponse {
|
|
pub node_id: Uuid,
|
|
pub title: String,
|
|
pub metadata: serde_json::Value,
|
|
pub created: bool,
|
|
}
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
struct WorkspaceRow {
|
|
id: Uuid,
|
|
title: String,
|
|
metadata: String,
|
|
}
|
|
|
|
/// GET /my/workspace — finn eller opprett brukerens personlige workspace.
|
|
pub async fn my_workspace(
|
|
State(state): State<AppState>,
|
|
user: AuthUser,
|
|
) -> Result<Json<WorkspaceResponse>, (StatusCode, String)> {
|
|
// Søk etter eksisterende workspace-node der brukeren er eier
|
|
let existing = sqlx::query_as::<_, WorkspaceRow>(
|
|
r#"
|
|
SELECT n.id, n.title, n.metadata
|
|
FROM nodes n
|
|
INNER JOIN edges e ON e.target_id = n.id
|
|
WHERE e.source_id = $1
|
|
AND e.edge_type = 'owner'
|
|
AND n.node_kind = 'workspace'
|
|
LIMIT 1
|
|
"#,
|
|
)
|
|
.bind(user.node_id)
|
|
.fetch_optional(&state.db)
|
|
.await
|
|
.map_err(|e| {
|
|
tracing::error!("PG-feil ved workspace-oppslag: {e}");
|
|
(StatusCode::INTERNAL_SERVER_ERROR, "Databasefeil".to_string())
|
|
})?;
|
|
|
|
if let Some(row) = existing {
|
|
let metadata: serde_json::Value =
|
|
serde_json::from_str(&row.metadata).unwrap_or(serde_json::json!({}));
|
|
return Ok(Json(WorkspaceResponse {
|
|
node_id: row.id,
|
|
title: row.title,
|
|
metadata,
|
|
created: false,
|
|
}));
|
|
}
|
|
|
|
// Ingen workspace funnet — opprett ny
|
|
let node_id = Uuid::now_v7();
|
|
let title = "Min arbeidsflate".to_string();
|
|
let metadata = serde_json::json!({
|
|
"workspace_layout": {
|
|
"panels": []
|
|
}
|
|
});
|
|
|
|
// Skriv workspace-node til PG
|
|
sqlx::query(
|
|
r#"INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
|
|
VALUES ($1, 'workspace', $2, '', 'hidden'::visibility, $3, $4)
|
|
ON CONFLICT (id) DO NOTHING"#,
|
|
)
|
|
.bind(node_id)
|
|
.bind(&title)
|
|
.bind(&metadata)
|
|
.bind(user.node_id)
|
|
.execute(&state.db)
|
|
.await
|
|
.map_err(|e| {
|
|
tracing::error!("PG-feil ved workspace-opprettelse: {e}");
|
|
(StatusCode::INTERNAL_SERVER_ERROR, "Databasefeil".to_string())
|
|
})?;
|
|
|
|
// Opprett owner-edge med recompute_access
|
|
let edge_id = Uuid::now_v7();
|
|
|
|
let mut tx = state.db.begin().await.map_err(|e| {
|
|
tracing::error!("PG begin: {e}");
|
|
(StatusCode::INTERNAL_SERVER_ERROR, "Databasefeil".to_string())
|
|
})?;
|
|
|
|
sqlx::query(
|
|
r#"INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by)
|
|
VALUES ($1, $2, $3, 'owner', '{}', false, $2)
|
|
ON CONFLICT (id) DO NOTHING"#,
|
|
)
|
|
.bind(edge_id)
|
|
.bind(user.node_id)
|
|
.bind(node_id)
|
|
.execute(&mut *tx)
|
|
.await
|
|
.map_err(|e| {
|
|
tracing::error!("PG-feil ved workspace owner-edge: {e}");
|
|
(StatusCode::INTERNAL_SERVER_ERROR, "Databasefeil".to_string())
|
|
})?;
|
|
|
|
sqlx::query("SELECT recompute_access($1, $2, 'owner'::access_level, $3)")
|
|
.bind(user.node_id)
|
|
.bind(node_id)
|
|
.bind(edge_id)
|
|
.execute(&mut *tx)
|
|
.await
|
|
.map_err(|e| {
|
|
tracing::error!("recompute_access: {e}");
|
|
(StatusCode::INTERNAL_SERVER_ERROR, "Databasefeil".to_string())
|
|
})?;
|
|
|
|
tx.commit().await.map_err(|e| {
|
|
tracing::error!("PG commit: {e}");
|
|
(StatusCode::INTERNAL_SERVER_ERROR, "Databasefeil".to_string())
|
|
})?;
|
|
|
|
tracing::info!(
|
|
node_id = %node_id,
|
|
user_id = %user.node_id,
|
|
"Personlig workspace opprettet"
|
|
);
|
|
|
|
Ok(Json(WorkspaceResponse {
|
|
node_id,
|
|
title,
|
|
metadata,
|
|
created: true,
|
|
}))
|
|
}
|