diff options
Diffstat (limited to 'makima/src/db')
| -rw-r--r-- | makima/src/db/models.rs | 10 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 104 |
2 files changed, 107 insertions, 7 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 291fad7..1eaaf58 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -441,6 +441,12 @@ pub struct Task { #[serde(default)] pub is_supervisor: bool, + // Anonymous task flag + /// True for ephemeral one-off tasks that don't belong to a contract. + /// Anonymous tasks are automatically cleaned up after a period of inactivity. + #[serde(default)] + pub is_anonymous: bool, + // Daemon/container info pub daemon_id: Option<Uuid>, pub container_id: Option<String>, @@ -558,6 +564,9 @@ pub struct TaskSummary { /// True for contract supervisor tasks #[serde(default)] pub is_supervisor: bool, + /// True for ephemeral anonymous tasks + #[serde(default)] + pub is_anonymous: bool, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc>, } @@ -580,6 +589,7 @@ impl From<Task> for TaskSummary { subtask_count: 0, // Would need separate query version: task.version, is_supervisor: task.is_supervisor, + is_anonymous: task.is_anonymous, created_at: task.created_at, updated_at: task.updated_at, } diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 536bc9b..d7f2e69 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -739,7 +739,7 @@ pub async fn list_tasks(pool: &PgPool) -> Result<Vec<TaskSummary>, sqlx::Error> t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.parent_task_id IS NULL @@ -760,7 +760,7 @@ pub async fn list_subtasks(pool: &PgPool, parent_id: Uuid) -> Result<Vec<TaskSum t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.parent_task_id = $1 @@ -1135,7 +1135,7 @@ pub async fn list_tasks_for_owner( t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.owner_id = $1 AND t.parent_task_id IS NULL @@ -1161,7 +1161,7 @@ pub async fn list_subtasks_for_owner( t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.owner_id = $1 AND t.parent_task_id = $2 @@ -1679,7 +1679,7 @@ pub async fn list_sibling_tasks( t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.parent_task_id = $1 AND t.id != $2 @@ -1701,7 +1701,7 @@ pub async fn list_sibling_tasks( t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.parent_task_id IS NULL AND t.id != $1 @@ -2679,7 +2679,7 @@ pub async fn list_tasks_in_contract( t.parent_task_id, t.depth, t.name, t.status, t.priority, t.progress_summary, (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, - t.version, t.is_supervisor, t.created_at, t.updated_at + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at FROM tasks t LEFT JOIN contracts c ON t.contract_id = c.id WHERE t.contract_id = $1 AND t.owner_id = $2 @@ -3678,3 +3678,93 @@ pub async fn get_supervisor_conversation_full( ) -> Result<Option<SupervisorState>, sqlx::Error> { get_supervisor_state(pool, contract_id).await } + +// ============================================================================= +// Anonymous Task Cleanup Functions +// ============================================================================= + +/// Get anonymous tasks that are completed/failed and older than the specified threshold. +/// Used by the background cleanup job to find stale anonymous tasks. +pub async fn get_stale_anonymous_tasks( + pool: &PgPool, + older_than_days: i32, +) -> Result<Vec<Task>, sqlx::Error> { + sqlx::query_as::<_, Task>( + r#" + SELECT * FROM tasks + WHERE is_anonymous = TRUE + AND status IN ('done', 'failed', 'merged') + AND completed_at < NOW() - INTERVAL '1 day' * $1 + ORDER BY completed_at ASC + "#, + ) + .bind(older_than_days) + .fetch_all(pool) + .await +} + +/// Delete a task and its associated data (cascade delete). +/// Related records (task_events, conversation_snapshots, etc.) are automatically +/// deleted via ON DELETE CASCADE foreign key constraints. +pub async fn delete_task_cascade( + pool: &PgPool, + task_id: Uuid, +) -> Result<bool, sqlx::Error> { + let result = sqlx::query( + r#" + DELETE FROM tasks + WHERE id = $1 + "#, + ) + .bind(task_id) + .execute(pool) + .await?; + + Ok(result.rows_affected() > 0) +} + +/// List anonymous tasks for a specific owner. +/// Used by the API endpoint to let users view their ephemeral tasks. +pub async fn list_anonymous_tasks_for_owner( + pool: &PgPool, + owner_id: Uuid, +) -> Result<Vec<TaskSummary>, sqlx::Error> { + sqlx::query_as::<_, TaskSummary>( + r#" + SELECT + t.id, t.contract_id, NULL as contract_name, NULL as contract_phase, + NULL as contract_status, + t.parent_task_id, t.depth, t.name, t.status, t.priority, + t.progress_summary, + (SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count, + t.version, t.is_supervisor, t.is_anonymous, t.created_at, t.updated_at + FROM tasks t + WHERE t.owner_id = $1 AND t.is_anonymous = TRUE + ORDER BY t.created_at DESC + "#, + ) + .bind(owner_id) + .fetch_all(pool) + .await +} + +/// Count anonymous tasks that will be cleaned up. +/// Useful for monitoring and logging purposes. +pub async fn count_stale_anonymous_tasks( + pool: &PgPool, + older_than_days: i32, +) -> Result<i64, sqlx::Error> { + let result: (i64,) = sqlx::query_as( + r#" + SELECT COUNT(*) FROM tasks + WHERE is_anonymous = TRUE + AND status IN ('done', 'failed', 'merged') + AND completed_at < NOW() - INTERVAL '1 day' * $1 + "#, + ) + .bind(older_than_days) + .fetch_one(pool) + .await?; + + Ok(result.0) +} |
