// 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, user: AuthUser, ) -> Result, (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, })) }