summaryrefslogblamecommitdiff
path: root/makima/src/server/handlers/templates.rs
blob: 868d5b4323a08f3b374c283826d1821212d1e3eb (plain) (tree)










































































































                                                                                                        
//! Templates API handler.

use axum::{extract::Query, http::StatusCode, response::IntoResponse, Json};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::llm::templates;

/// Query parameters for listing templates
#[derive(Debug, Deserialize, ToSchema)]
pub struct ListTemplatesQuery {
    /// Filter by contract phase (research, specify, plan, execute, review)
    pub phase: Option<String>,
}

/// Template summary for API response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TemplateSummary {
    /// Template identifier
    pub id: String,
    /// Display name
    pub name: String,
    /// Contract phase this template is designed for
    pub phase: String,
    /// Brief description
    pub description: String,
    /// Number of body elements in the template
    pub element_count: usize,
}

/// Response for listing templates
#[derive(Debug, Serialize, ToSchema)]
pub struct ListTemplatesResponse {
    pub templates: Vec<TemplateSummary>,
}

/// List available file templates
#[utoipa::path(
    get,
    path = "/api/v1/templates",
    params(
        ("phase" = Option<String>, Query, description = "Filter by contract phase")
    ),
    responses(
        (status = 200, description = "Templates retrieved successfully", body = ListTemplatesResponse)
    ),
    tag = "templates"
)]
pub async fn list_templates(
    Query(query): Query<ListTemplatesQuery>,
) -> impl IntoResponse {
    let template_list = match query.phase.as_deref() {
        Some(phase) => templates::templates_for_phase(phase),
        None => templates::all_templates(),
    };

    let summaries: Vec<TemplateSummary> = template_list
        .iter()
        .map(|t| TemplateSummary {
            id: t.id.clone(),
            name: t.name.clone(),
            phase: t.phase.clone(),
            description: t.description.clone(),
            element_count: t.suggested_body.len(),
        })
        .collect();

    (
        StatusCode::OK,
        Json(ListTemplatesResponse {
            templates: summaries,
        }),
    )
        .into_response()
}

/// Get a specific template by ID
#[utoipa::path(
    get,
    path = "/api/v1/templates/{id}",
    params(
        ("id" = String, Path, description = "Template ID")
    ),
    responses(
        (status = 200, description = "Template retrieved successfully", body = templates::FileTemplate),
        (status = 404, description = "Template not found")
    ),
    tag = "templates"
)]
pub async fn get_template(
    axum::extract::Path(id): axum::extract::Path<String>,
) -> impl IntoResponse {
    let all = templates::all_templates();
    let template = all.into_iter().find(|t| t.id == id);

    match template {
        Some(t) => (StatusCode::OK, Json(serde_json::json!(t))).into_response(),
        None => (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({
                "error": format!("Template '{}' not found", id)
            })),
        )
            .into_response(),
    }
}