diff options
Diffstat (limited to 'makima/src/db')
| -rw-r--r-- | makima/src/db/models.rs | 64 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 189 |
2 files changed, 224 insertions, 29 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 4e569ec..30e1603 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -2652,16 +2652,40 @@ pub struct Chain { pub loop_current_iteration: Option<i32>, /// Progress check prompt/criteria for evaluating loop completion pub loop_progress_check: Option<String>, - /// Repository URL for contracts in this chain (optional) - pub repository_url: Option<String>, - /// Local path for contracts in this chain (optional) - pub local_path: Option<String>, /// Version for optimistic locking pub version: i32, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc>, } +/// Chain repository record from the database +#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct ChainRepository { + pub id: Uuid, + pub chain_id: Uuid, + pub name: String, + pub repository_url: Option<String>, + pub local_path: Option<String>, + pub source_type: String, + pub status: String, + pub is_primary: bool, + pub created_at: DateTime<Utc>, + pub updated_at: DateTime<Utc>, +} + +impl ChainRepository { + /// Parse source_type string to RepositorySourceType enum + pub fn source_type_enum(&self) -> Result<RepositorySourceType, String> { + self.source_type.parse() + } + + /// Parse status string to RepositoryStatus enum + pub fn status_enum(&self) -> Result<RepositoryStatus, String> { + self.status.parse() + } +} + impl Chain { /// Parse status string to ChainStatus enum pub fn status_enum(&self) -> Result<ChainStatus, String> { @@ -2724,6 +2748,7 @@ pub struct ChainWithContracts { #[serde(flatten)] pub chain: Chain, pub contracts: Vec<ChainContractDetail>, + pub repositories: Vec<ChainRepository>, } /// Contract detail within a chain (includes contract info + chain link info) @@ -2790,10 +2815,8 @@ pub struct CreateChainRequest { pub name: String, /// Optional description pub description: Option<String>, - /// Repository URL for contracts in this chain - pub repository_url: Option<String>, - /// Local path for contracts in this chain - pub local_path: Option<String>, + /// Repositories for this chain + pub repositories: Option<Vec<AddChainRepositoryRequest>>, /// Enable loop mode for iterative execution #[serde(default)] pub loop_enabled: Option<bool>, @@ -2805,6 +2828,28 @@ pub struct CreateChainRequest { pub contracts: Option<Vec<CreateChainContractRequest>>, } +/// Request to add a repository to a chain +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct AddChainRepositoryRequest { + /// Display name for the repository + pub name: String, + /// Remote repository URL (for remote repos) + pub repository_url: Option<String>, + /// Local filesystem path (for local repos) + pub local_path: Option<String>, + /// Source type: remote, local, or managed + #[serde(default = "default_source_type")] + pub source_type: String, + /// Whether this is the primary repository + #[serde(default)] + pub is_primary: bool, +} + +fn default_source_type() -> String { + "remote".to_string() +} + /// Request to create a contract within a chain #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] @@ -2934,8 +2979,7 @@ pub struct ChainEditorData { pub id: Option<Uuid>, pub name: String, pub description: Option<String>, - pub repository_url: Option<String>, - pub local_path: Option<String>, + pub repositories: Vec<ChainRepository>, pub loop_enabled: bool, pub loop_max_iterations: Option<i32>, pub loop_progress_check: Option<String>, diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index ec233ba..2b595b5 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -6,20 +6,20 @@ use sqlx::PgPool; use uuid::Uuid; use super::models::{ - AddContractDefinitionRequest, AddContractToChainRequest, Chain, ChainContract, - ChainContractDefinition, ChainContractDetail, ChainDefinitionGraphNode, + AddChainRepositoryRequest, AddContractDefinitionRequest, AddContractToChainRequest, Chain, + ChainContract, ChainContractDefinition, ChainContractDetail, ChainDefinitionGraphNode, ChainDefinitionGraphResponse, ChainEditorContract, ChainEditorData, ChainEditorDeliverable, ChainEditorEdge, ChainEditorNode, ChainEditorTask, ChainEvent, ChainGraphEdge, ChainGraphNode, - ChainGraphResponse, ChainSummary, ChainWithContracts, CheckpointPatch, CheckpointPatchInfo, - Contract, ContractChatConversation, ContractChatMessageRecord, ContractEvent, - ContractRepository, ContractSummary, ContractTypeTemplateRecord, ConversationMessage, - ConversationSnapshot, CreateChainRequest, CreateContractRequest, CreateFileRequest, - CreateTaskRequest, CreateTemplateRequest, Daemon, DaemonTaskAssignment, DaemonWithCapacity, - DeliverableDefinition, File, FileSummary, FileVersion, HistoryEvent, HistoryQueryFilters, - MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult, PhaseConfig, PhaseDefinition, - SupervisorHeartbeatRecord, SupervisorState, Task, TaskCheckpoint, TaskEvent, TaskSummary, - UpdateChainRequest, UpdateContractDefinitionRequest, UpdateContractRequest, UpdateFileRequest, - UpdateTaskRequest, UpdateTemplateRequest, + ChainGraphResponse, ChainRepository, ChainSummary, ChainWithContracts, CheckpointPatch, + CheckpointPatchInfo, Contract, ContractChatConversation, ContractChatMessageRecord, + ContractEvent, ContractRepository, ContractSummary, ContractTypeTemplateRecord, + ConversationMessage, ConversationSnapshot, CreateChainRequest, CreateContractRequest, + CreateFileRequest, CreateTaskRequest, CreateTemplateRequest, Daemon, DaemonTaskAssignment, + DaemonWithCapacity, DeliverableDefinition, File, FileSummary, FileVersion, HistoryEvent, + HistoryQueryFilters, MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult, + PhaseConfig, PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState, Task, TaskCheckpoint, + TaskEvent, TaskSummary, UpdateChainRequest, UpdateContractDefinitionRequest, + UpdateContractRequest, UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest, }; /// Repository error types. @@ -4917,16 +4917,14 @@ pub async fn create_chain_for_owner( sqlx::query_as::<_, Chain>( r#" - INSERT INTO chains (owner_id, name, description, repository_url, local_path, loop_enabled, loop_max_iterations, loop_progress_check) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + INSERT INTO chains (owner_id, name, description, loop_enabled, loop_max_iterations, loop_progress_check) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING * "#, ) .bind(owner_id) .bind(&req.name) .bind(&req.description) - .bind(&req.repository_url) - .bind(&req.local_path) .bind(loop_enabled) .bind(loop_max_iterations) .bind(&req.loop_progress_check) @@ -5181,12 +5179,165 @@ pub async fn get_chain_with_contracts( match chain { Some(chain) => { let contracts = list_chain_contracts(pool, chain_id).await?; - Ok(Some(ChainWithContracts { chain, contracts })) + let repositories = list_chain_repositories(pool, chain_id).await?; + Ok(Some(ChainWithContracts { + chain, + contracts, + repositories, + })) } None => Ok(None), } } +// ============================================================================= +// Chain Repository Operations +// ============================================================================= + +/// List all repositories for a chain. +pub async fn list_chain_repositories( + pool: &PgPool, + chain_id: Uuid, +) -> Result<Vec<ChainRepository>, sqlx::Error> { + sqlx::query_as::<_, ChainRepository>( + r#" + SELECT * + FROM chain_repositories + WHERE chain_id = $1 + ORDER BY is_primary DESC, created_at ASC + "#, + ) + .bind(chain_id) + .fetch_all(pool) + .await +} + +/// Get a chain repository by ID. +pub async fn get_chain_repository( + pool: &PgPool, + chain_id: Uuid, + repository_id: Uuid, +) -> Result<Option<ChainRepository>, sqlx::Error> { + sqlx::query_as::<_, ChainRepository>( + r#" + SELECT * + FROM chain_repositories + WHERE id = $1 AND chain_id = $2 + "#, + ) + .bind(repository_id) + .bind(chain_id) + .fetch_optional(pool) + .await +} + +/// Add a repository to a chain. +pub async fn add_chain_repository( + pool: &PgPool, + chain_id: Uuid, + req: &AddChainRepositoryRequest, +) -> Result<ChainRepository, sqlx::Error> { + // If is_primary, clear other primaries first + if req.is_primary { + sqlx::query( + r#" + UPDATE chain_repositories + SET is_primary = false, updated_at = NOW() + WHERE chain_id = $1 AND is_primary = true + "#, + ) + .bind(chain_id) + .execute(pool) + .await?; + } + + sqlx::query_as::<_, ChainRepository>( + r#" + INSERT INTO chain_repositories (chain_id, name, repository_url, local_path, source_type, status, is_primary) + VALUES ($1, $2, $3, $4, $5, 'ready', $6) + RETURNING * + "#, + ) + .bind(chain_id) + .bind(&req.name) + .bind(&req.repository_url) + .bind(&req.local_path) + .bind(&req.source_type) + .bind(req.is_primary) + .fetch_one(pool) + .await +} + +/// Delete a repository from a chain. +pub async fn delete_chain_repository( + pool: &PgPool, + chain_id: Uuid, + repository_id: Uuid, +) -> Result<bool, sqlx::Error> { + let result = sqlx::query( + r#" + DELETE FROM chain_repositories + WHERE id = $1 AND chain_id = $2 + "#, + ) + .bind(repository_id) + .bind(chain_id) + .execute(pool) + .await?; + + Ok(result.rows_affected() > 0) +} + +/// Set a repository as primary for a chain. +pub async fn set_chain_repository_primary( + pool: &PgPool, + chain_id: Uuid, + repository_id: Uuid, +) -> Result<ChainRepository, sqlx::Error> { + // Clear existing primary + sqlx::query( + r#" + UPDATE chain_repositories + SET is_primary = false, updated_at = NOW() + WHERE chain_id = $1 AND is_primary = true + "#, + ) + .bind(chain_id) + .execute(pool) + .await?; + + // Set new primary + sqlx::query_as::<_, ChainRepository>( + r#" + UPDATE chain_repositories + SET is_primary = true, updated_at = NOW() + WHERE id = $1 AND chain_id = $2 + RETURNING * + "#, + ) + .bind(repository_id) + .bind(chain_id) + .fetch_one(pool) + .await +} + +/// Get the primary repository for a chain. +pub async fn get_chain_primary_repository( + pool: &PgPool, + chain_id: Uuid, +) -> Result<Option<ChainRepository>, sqlx::Error> { + sqlx::query_as::<_, ChainRepository>( + r#" + SELECT * + FROM chain_repositories + WHERE chain_id = $1 AND is_primary = true + "#, + ) + .bind(chain_id) + .fetch_optional(pool) + .await +} + /// Get chain graph structure for visualization. pub async fn get_chain_graph( pool: &PgPool, @@ -5381,6 +5532,7 @@ pub async fn get_chain_editor_data( match chain { Some(chain) => { let contracts = list_chain_contracts(pool, chain_id).await?; + let repositories = list_chain_repositories(pool, chain_id).await?; // Build nodes let nodes: Vec<ChainEditorNode> = contracts @@ -5415,8 +5567,7 @@ pub async fn get_chain_editor_data( id: Some(chain.id), name: chain.name, description: chain.description, - repository_url: chain.repository_url, - local_path: chain.local_path, + repositories, loop_enabled: chain.loop_enabled, loop_max_iterations: chain.loop_max_iterations, loop_progress_check: chain.loop_progress_check, |
