summaryrefslogtreecommitdiff
path: root/makima/src/daemon/api
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-01 01:35:18 +0000
committersoryu <soryu@soryu.co>2026-02-01 01:35:18 +0000
commitc2750f86ebd6ac5c04b70dd8249501262d6dd07c (patch)
treeecd4a32d74adc3479cdc30c94843a6447bf44649 /makima/src/daemon/api
parent7567153e6281b94e39e52be5d060b381ed69597d (diff)
downloadsoryu-c2750f86ebd6ac5c04b70dd8249501262d6dd07c.tar.gz
soryu-c2750f86ebd6ac5c04b70dd8249501262d6dd07c.zip
[WIP] Heartbeat checkpoint - 2026-02-01 01:35:18 UTC
Diffstat (limited to 'makima/src/daemon/api')
-rw-r--r--makima/src/daemon/api/contract.rs178
1 files changed, 177 insertions, 1 deletions
diff --git a/makima/src/daemon/api/contract.rs b/makima/src/daemon/api/contract.rs
index 7c76b40..119c0ba 100644
--- a/makima/src/daemon/api/contract.rs
+++ b/makima/src/daemon/api/contract.rs
@@ -1,6 +1,6 @@
//! Contract API methods.
-use serde::Serialize;
+use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::client::{ApiClient, ApiError};
@@ -281,3 +281,179 @@ struct AddRemoteRepositoryRequest {
repository_url: String,
is_primary: bool,
}
+
+// ============================================================================
+// Contracts cleanup types
+// ============================================================================
+
+/// Request for batch contract operations (cleanup, archive, etc.).
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BatchOperationRequest {
+ /// The operation to perform: "archive", "delete_archived", "cleanup_worktrees"
+ pub operation: String,
+ /// Age threshold in seconds (for archive and delete operations)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub older_than_seconds: Option<u64>,
+ /// Status filter for the operation
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub status_filter: Option<Vec<String>>,
+ /// If true, only show what would be affected without making changes
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub dry_run: Option<bool>,
+}
+
+/// Response from a batch operation.
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BatchOperationResponse {
+ /// The operation that was performed
+ pub operation: String,
+ /// Number of items affected
+ pub affected_count: usize,
+ /// IDs of affected items
+ #[serde(default)]
+ pub affected_ids: Vec<Uuid>,
+ /// Any errors that occurred
+ #[serde(default)]
+ pub errors: Vec<String>,
+ /// Whether this was a dry run
+ #[serde(default)]
+ pub dry_run: bool,
+}
+
+/// Summary of contracts that would be affected by cleanup.
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CleanupPreviewResponse {
+ /// Contracts that would be archived
+ #[serde(default)]
+ pub to_archive: Vec<ContractSummary>,
+ /// Archived contracts that would be deleted
+ #[serde(default)]
+ pub to_delete: Vec<ContractSummary>,
+ /// Orphaned worktrees that would be cleaned up
+ #[serde(default)]
+ pub orphaned_worktrees: Vec<String>,
+}
+
+/// Brief summary of a contract for cleanup operations.
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractSummary {
+ pub id: Uuid,
+ pub name: String,
+ pub status: String,
+ #[serde(default)]
+ pub updated_at: Option<String>,
+}
+
+impl ApiClient {
+ // ========================================================================
+ // Contracts cleanup operations
+ // ========================================================================
+
+ /// Preview cleanup operations (dry run).
+ /// Returns what would be affected by archive, delete, and worktree cleanup.
+ pub async fn cleanup_preview(
+ &self,
+ older_than_seconds: u64,
+ archive: bool,
+ delete_archived: bool,
+ worktrees: bool,
+ ) -> Result<CleanupPreviewResponse, ApiError> {
+ let mut params = vec![
+ format!("older_than_seconds={}", older_than_seconds),
+ "dry_run=true".to_string(),
+ ];
+ if archive {
+ params.push("archive=true".to_string());
+ }
+ if delete_archived {
+ params.push("delete_archived=true".to_string());
+ }
+ if worktrees {
+ params.push("worktrees=true".to_string());
+ }
+ let query = params.join("&");
+ let result = self.get(&format!("/api/v1/contracts/cleanup?{}", query)).await?;
+ serde_json::from_value(result.0)
+ .map_err(|e| ApiError::Other(format!("Failed to parse cleanup preview: {}", e)))
+ }
+
+ /// Archive completed/failed contracts older than the threshold.
+ pub async fn archive_contracts(
+ &self,
+ older_than_seconds: u64,
+ dry_run: bool,
+ ) -> Result<BatchOperationResponse, ApiError> {
+ let req = BatchOperationRequest {
+ operation: "archive".to_string(),
+ older_than_seconds: Some(older_than_seconds),
+ status_filter: Some(vec!["completed".to_string(), "failed".to_string()]),
+ dry_run: Some(dry_run),
+ };
+ let result = self.post("/api/v1/contracts/batch", &req).await?;
+ serde_json::from_value(result.0)
+ .map_err(|e| ApiError::Other(format!("Failed to parse archive response: {}", e)))
+ }
+
+ /// Delete archived contracts older than the threshold.
+ pub async fn delete_archived_contracts(
+ &self,
+ older_than_seconds: u64,
+ dry_run: bool,
+ ) -> Result<BatchOperationResponse, ApiError> {
+ let req = BatchOperationRequest {
+ operation: "delete_archived".to_string(),
+ older_than_seconds: Some(older_than_seconds),
+ status_filter: Some(vec!["archived".to_string()]),
+ dry_run: Some(dry_run),
+ };
+ let result = self.post("/api/v1/contracts/batch", &req).await?;
+ serde_json::from_value(result.0)
+ .map_err(|e| ApiError::Other(format!("Failed to parse delete response: {}", e)))
+ }
+
+ /// Clean up orphaned worktrees.
+ pub async fn cleanup_worktrees(&self, dry_run: bool) -> Result<BatchOperationResponse, ApiError> {
+ let req = BatchOperationRequest {
+ operation: "cleanup_worktrees".to_string(),
+ older_than_seconds: None,
+ status_filter: None,
+ dry_run: Some(dry_run),
+ };
+ let result = self.post("/api/v1/contracts/batch", &req).await?;
+ serde_json::from_value(result.0)
+ .map_err(|e| ApiError::Other(format!("Failed to parse worktree cleanup response: {}", e)))
+ }
+
+ /// List contracts with filtering options.
+ pub async fn list_contracts_filtered(
+ &self,
+ status: Option<&str>,
+ phase: Option<&str>,
+ stale: bool,
+ stale_threshold_seconds: Option<u64>,
+ ) -> Result<JsonValue, ApiError> {
+ let mut params = Vec::new();
+ if let Some(s) = status {
+ params.push(format!("status={}", s));
+ }
+ if let Some(p) = phase {
+ params.push(format!("phase={}", p));
+ }
+ if stale {
+ params.push("stale=true".to_string());
+ if let Some(threshold) = stale_threshold_seconds {
+ params.push(format!("stale_threshold={}", threshold));
+ }
+ }
+ let query_string = if params.is_empty() {
+ String::new()
+ } else {
+ format!("?{}", params.join("&"))
+ };
+ self.get(&format!("/api/v1/contracts{}", query_string)).await
+ }
+}