From 151e9d87e117b7980e6aad522ac8f3633eeca87a Mon Sep 17 00:00:00 2001 From: soryu Date: Mon, 2 Feb 2026 02:34:50 +0000 Subject: Make makima more opinionated and structured --- makima/src/server/handlers/templates.rs | 419 +------------------------------- 1 file changed, 6 insertions(+), 413 deletions(-) (limited to 'makima/src/server/handlers/templates.rs') diff --git a/makima/src/server/handlers/templates.rs b/makima/src/server/handlers/templates.rs index 0cc5657..aa97876 100644 --- a/makima/src/server/handlers/templates.rs +++ b/makima/src/server/handlers/templates.rs @@ -1,27 +1,19 @@ //! Contract types API handler. +//! Only returns built-in contract types (simple, specification, execute). use axum::{ - extract::{Path, State}, http::StatusCode, response::IntoResponse, Json, }; use serde::Serialize; use utoipa::ToSchema; -use uuid::Uuid; -use crate::db::models::{ - ContractTypeTemplateRecord, ContractTypeTemplateSummary, CreateTemplateRequest, - UpdateTemplateRequest, -}; -use crate::db::repository; use crate::llm::templates; use crate::llm::templates::ContractTypeTemplate; -use crate::server::auth::{Authenticated, MaybeAuthenticated}; -use crate::server::state::SharedState; // ============================================================================= -// Contract Type Templates (Workflow Definitions) +// Contract Type Templates (Built-in Only) // ============================================================================= /// Response for listing contract types @@ -31,14 +23,7 @@ pub struct ListContractTypesResponse { pub contract_types: Vec, } -/// Response for a single custom template -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct TemplateResponse { - pub template: ContractTypeTemplateSummary, -} - -/// List all available contract type templates (built-in + custom) +/// List all available contract type templates (built-in only) #[utoipa::path( get, path = "/api/v1/contract-types", @@ -47,404 +32,12 @@ pub struct TemplateResponse { ), tag = "templates" )] -pub async fn list_contract_types( - State(state): State, - MaybeAuthenticated(auth): MaybeAuthenticated, -) -> impl IntoResponse { - // Start with built-in types - let mut contract_types = templates::all_contract_types(); - - // If authenticated, also fetch custom templates for this owner - if let Some(user) = auth { - if let Some(ref pool) = state.db_pool { - if let Ok(custom_templates) = - repository::list_templates_for_owner(pool, user.owner_id).await - { - for template in custom_templates { - contract_types.push(template_record_to_api(&template)); - } - } - } - } - +pub async fn list_contract_types() -> impl IntoResponse { + // Only return built-in types (simple, specification, execute) + let contract_types = templates::all_contract_types(); ( StatusCode::OK, Json(ListContractTypesResponse { contract_types }), ) .into_response() } - -/// Create a new custom contract type template -#[utoipa::path( - post, - path = "/api/v1/contract-types", - request_body = CreateTemplateRequest, - responses( - (status = 201, description = "Template created successfully", body = TemplateResponse), - (status = 400, description = "Invalid request"), - (status = 401, description = "Unauthorized"), - (status = 409, description = "Template with this name already exists") - ), - tag = "templates" -)] -pub async fn create_template( - State(state): State, - Authenticated(auth): Authenticated, - Json(req): Json, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": "Database not configured" - })), - ) - .into_response(); - }; - - // Validate request - if req.name.trim().is_empty() { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "code": "INVALID_REQUEST", - "message": "Template name cannot be empty" - })), - ) - .into_response(); - } - - if req.phases.is_empty() { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "code": "INVALID_REQUEST", - "message": "Template must have at least one phase" - })), - ) - .into_response(); - } - - // Validate default_phase is in the phases list - if !req.phases.iter().any(|p| p.id == req.default_phase) { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "code": "INVALID_REQUEST", - "message": format!("Default phase '{}' is not in the phases list", req.default_phase) - })), - ) - .into_response(); - } - - // Check that template name doesn't conflict with built-in types - let builtin_names = ["simple", "specification", "execute"]; - if builtin_names.contains(&req.name.to_lowercase().as_str()) { - return ( - StatusCode::CONFLICT, - Json(serde_json::json!({ - "code": "NAME_CONFLICT", - "message": "Cannot create a template with the same name as a built-in type" - })), - ) - .into_response(); - } - - match repository::create_template_for_owner(pool, auth.owner_id, req).await { - Ok(template) => ( - StatusCode::CREATED, - Json(serde_json::json!({ - "template": template_record_to_summary(&template) - })), - ) - .into_response(), - Err(e) => { - // Check for unique constraint violation - let error_str = e.to_string(); - if error_str.contains("unique") || error_str.contains("duplicate") { - return ( - StatusCode::CONFLICT, - Json(serde_json::json!({ - "code": "NAME_CONFLICT", - "message": "A template with this name already exists" - })), - ) - .into_response(); - } - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": format!("Failed to create template: {}", e) - })), - ) - .into_response() - } - } -} - -/// Get a specific contract type template by ID -#[utoipa::path( - get, - path = "/api/v1/contract-types/{id}", - params( - ("id" = Uuid, Path, description = "Template ID") - ), - responses( - (status = 200, description = "Template retrieved successfully", body = TemplateResponse), - (status = 401, description = "Unauthorized"), - (status = 404, description = "Template not found") - ), - tag = "templates" -)] -pub async fn get_template( - State(state): State, - Authenticated(auth): Authenticated, - Path(id): Path, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": "Database not configured" - })), - ) - .into_response(); - }; - - match repository::get_template_for_owner(pool, id, auth.owner_id).await { - Ok(Some(template)) => ( - StatusCode::OK, - Json(serde_json::json!({ - "template": template_record_to_summary(&template) - })), - ) - .into_response(), - Ok(None) => ( - StatusCode::NOT_FOUND, - Json(serde_json::json!({ - "code": "NOT_FOUND", - "message": "Template not found" - })), - ) - .into_response(), - Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": format!("Failed to get template: {}", e) - })), - ) - .into_response(), - } -} - -/// Update a contract type template -#[utoipa::path( - put, - path = "/api/v1/contract-types/{id}", - params( - ("id" = Uuid, Path, description = "Template ID") - ), - request_body = UpdateTemplateRequest, - responses( - (status = 200, description = "Template updated successfully", body = TemplateResponse), - (status = 400, description = "Invalid request"), - (status = 401, description = "Unauthorized"), - (status = 404, description = "Template not found"), - (status = 409, description = "Version conflict") - ), - tag = "templates" -)] -pub async fn update_template( - State(state): State, - Authenticated(auth): Authenticated, - Path(id): Path, - Json(req): Json, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": "Database not configured" - })), - ) - .into_response(); - }; - - // Validate phases if provided - if let Some(ref phases) = req.phases { - if phases.is_empty() { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "code": "INVALID_REQUEST", - "message": "Template must have at least one phase" - })), - ) - .into_response(); - } - - // If default_phase is also provided, validate it's in the phases - if let Some(ref default_phase) = req.default_phase { - if !phases.iter().any(|p| &p.id == default_phase) { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "code": "INVALID_REQUEST", - "message": format!("Default phase '{}' is not in the phases list", default_phase) - })), - ) - .into_response(); - } - } - } - - // Check that template name doesn't conflict with built-in types - if let Some(ref name) = req.name { - let builtin_names = ["simple", "specification", "execute"]; - if builtin_names.contains(&name.to_lowercase().as_str()) { - return ( - StatusCode::CONFLICT, - Json(serde_json::json!({ - "code": "NAME_CONFLICT", - "message": "Cannot rename template to a built-in type name" - })), - ) - .into_response(); - } - } - - match repository::update_template_for_owner(pool, id, auth.owner_id, req).await { - Ok(Some(template)) => ( - StatusCode::OK, - Json(serde_json::json!({ - "template": template_record_to_summary(&template) - })), - ) - .into_response(), - Ok(None) => ( - StatusCode::NOT_FOUND, - Json(serde_json::json!({ - "code": "NOT_FOUND", - "message": "Template not found" - })), - ) - .into_response(), - Err(repository::RepositoryError::VersionConflict { expected, actual }) => ( - StatusCode::CONFLICT, - Json(serde_json::json!({ - "code": "VERSION_CONFLICT", - "message": format!("Version conflict: expected {}, found {}", expected, actual), - "expectedVersion": expected, - "actualVersion": actual - })), - ) - .into_response(), - Err(e) => { - let error_str = e.to_string(); - if error_str.contains("unique") || error_str.contains("duplicate") { - return ( - StatusCode::CONFLICT, - Json(serde_json::json!({ - "code": "NAME_CONFLICT", - "message": "A template with this name already exists" - })), - ) - .into_response(); - } - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": format!("Failed to update template: {}", e) - })), - ) - .into_response() - } - } -} - -/// Delete a contract type template -#[utoipa::path( - delete, - path = "/api/v1/contract-types/{id}", - params( - ("id" = Uuid, Path, description = "Template ID") - ), - responses( - (status = 204, description = "Template deleted successfully"), - (status = 401, description = "Unauthorized"), - (status = 404, description = "Template not found") - ), - tag = "templates" -)] -pub async fn delete_template( - State(state): State, - Authenticated(auth): Authenticated, - Path(id): Path, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": "Database not configured" - })), - ) - .into_response(); - }; - - match repository::delete_template_for_owner(pool, id, auth.owner_id).await { - Ok(true) => StatusCode::NO_CONTENT.into_response(), - Ok(false) => ( - StatusCode::NOT_FOUND, - Json(serde_json::json!({ - "code": "NOT_FOUND", - "message": "Template not found" - })), - ) - .into_response(), - Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({ - "code": "DB_ERROR", - "message": format!("Failed to delete template: {}", e) - })), - ) - .into_response(), - } -} - -// ============================================================================= -// Helper Functions -// ============================================================================= - -/// Convert a database template record to the API template format -fn template_record_to_api(template: &ContractTypeTemplateRecord) -> ContractTypeTemplate { - ContractTypeTemplate { - id: template.id.to_string(), - name: template.name.clone(), - description: template.description.clone().unwrap_or_default(), - phases: template.phases.iter().map(|p| p.id.clone()).collect(), - default_phase: template.default_phase.clone(), - is_builtin: false, - } -} - -/// Convert a database template record to the summary format -fn template_record_to_summary(template: &ContractTypeTemplateRecord) -> ContractTypeTemplateSummary { - ContractTypeTemplateSummary { - id: template.id, - name: template.name.clone(), - description: template.description.clone(), - phases: template.phases.clone(), - default_phase: template.default_phase.clone(), - is_builtin: false, - version: template.version, - created_at: template.created_at, - } -} -- cgit v1.2.3