summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/templates.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/handlers/templates.rs')
-rw-r--r--makima/src/server/handlers/templates.rs419
1 files changed, 6 insertions, 413 deletions
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<ContractTypeTemplate>,
}
-/// 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<SharedState>,
- 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<SharedState>,
- Authenticated(auth): Authenticated,
- Json(req): Json<CreateTemplateRequest>,
-) -> 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<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> 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<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<UpdateTemplateRequest>,
-) -> 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<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> 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,
- }
-}