synops/maskinrommet/src/workspace.rs
vegard 09f69d1fdb Fiks workspace JSONB type-mismatch: metadata leses som serde_json::Value
WorkspaceRow.metadata var String, men PG-kolonnen er JSONB.
Første opprettelse fungerte (inserter Value), men oppslag
feilet ved andre besøk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:09:39 +00:00

145 lines
4 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: serde_json::Value,
}
/// 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 {
return Ok(Json(WorkspaceResponse {
node_id: row.id,
title: row.title,
metadata: row.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,
}))
}