//! Per-workspace default placement node for new agent sandboxes.
use cm_domain::{AgentId, WorkspaceId};
use sqlx::{PgPool, Row};
use crate::DbError;
/// The placement node for the workspace that owns `agent_id` (None ⇒ local).
pub async fn for_agent(pool: &PgPool, agent_id: AgentId) -> Result, DbError> {
let row = sqlx::query(
"SELECT p.node_id FROM workspace_placement p
JOIN agents a ON a.workspace_id = p.workspace_id
WHERE a.id = $1",
)
.bind(agent_id.as_uuid())
.fetch_optional(pool)
.await?;
Ok(row.map(|r| r.get("node_id")))
}
/// A workspace's placement node (None ⇒ local).
pub async fn get(pool: &PgPool, workspace_id: WorkspaceId) -> Result , DbError> {
let row = sqlx::query("SELECT node_id FROM workspace_placement WHERE workspace_id = $1")
.bind(workspace_id.as_uuid())
.fetch_optional(pool)
.await?;
Ok(row.map(|r| r.get("node_id")))
}
/// Set the workspace's placement node.
pub async fn set(pool: &PgPool, workspace_id: WorkspaceId, node_id: &str) -> Result<(), DbError> {
sqlx::query(
"INSERT INTO workspace_placement (workspace_id, node_id, updated_at)
VALUES ($1, $2, now())
ON CONFLICT (workspace_id) DO UPDATE SET node_id = excluded.node_id, updated_at = now()",
)
.bind(workspace_id.as_uuid())
.bind(node_id)
.execute(pool)
.await?;
Ok(())
}
/// Clear the workspace's placement (revert to local).
pub async fn clear(pool: &PgPool, workspace_id: WorkspaceId) -> Result<(), DbError> {
sqlx::query("DELETE FROM workspace_placement WHERE workspace_id = $1")
.bind(workspace_id.as_uuid())
.execute(pool)
.await?;
Ok(())
}