Skip to main content

batuta/serve/banco/
handlers_conversations.rs

1//! Conversation CRUD handlers.
2
3use axum::{extract::State, http::StatusCode, response::Json};
4
5use super::conversations::Conversation;
6use super::state::BancoState;
7use super::types::{
8    ConversationCreatedResponse, ConversationResponse, ConversationsListResponse,
9    CreateConversationRequest, ErrorResponse,
10};
11
12pub async fn create_conversation_handler(
13    State(state): State<BancoState>,
14    Json(request): Json<CreateConversationRequest>,
15) -> Json<ConversationCreatedResponse> {
16    let model = request.model.unwrap_or_else(|| "banco-echo".to_string());
17    let id = state.conversations.create(&model);
18
19    if let Some(title) = request.title {
20        if let Some(mut conv) = state.conversations.get(&id) {
21            conv.meta.title = title;
22        }
23    }
24
25    let conv = state.conversations.get(&id);
26    let title = conv.map(|c| c.meta.title).unwrap_or_else(|| "New conversation".to_string());
27    Json(ConversationCreatedResponse { id, title })
28}
29
30pub async fn list_conversations_handler(
31    State(state): State<BancoState>,
32) -> Json<ConversationsListResponse> {
33    Json(ConversationsListResponse { conversations: state.conversations.list() })
34}
35
36pub async fn get_conversation_handler(
37    State(state): State<BancoState>,
38    axum::extract::Path(id): axum::extract::Path<String>,
39) -> Result<Json<ConversationResponse>, (StatusCode, Json<ErrorResponse>)> {
40    state.conversations.get(&id).map(|c| Json(ConversationResponse { conversation: c })).ok_or((
41        StatusCode::NOT_FOUND,
42        Json(ErrorResponse::new(format!("Conversation {id} not found"), "not_found", 404)),
43    ))
44}
45
46pub async fn delete_conversation_handler(
47    State(state): State<BancoState>,
48    axum::extract::Path(id): axum::extract::Path<String>,
49) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
50    state.conversations.delete(&id).map(|()| StatusCode::NO_CONTENT).map_err(|_| {
51        (
52            StatusCode::NOT_FOUND,
53            Json(ErrorResponse::new(format!("Conversation {id} not found"), "not_found", 404)),
54        )
55    })
56}
57
58/// PATCH /api/v1/conversations/:id — rename a conversation.
59pub async fn rename_conversation_handler(
60    State(state): State<BancoState>,
61    axum::extract::Path(id): axum::extract::Path<String>,
62    Json(request): Json<RenameRequest>,
63) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
64    state.conversations.rename(&id, &request.title).map(|()| StatusCode::OK).map_err(|_| {
65        (
66            StatusCode::NOT_FOUND,
67            Json(ErrorResponse::new(format!("Conversation {id} not found"), "not_found", 404)),
68        )
69    })
70}
71
72#[derive(Debug, serde::Deserialize)]
73pub struct RenameRequest {
74    pub title: String,
75}
76
77/// GET /api/v1/conversations/search?q=query — search conversations by content.
78pub async fn search_conversations_handler(
79    State(state): State<BancoState>,
80    axum::extract::Query(params): axum::extract::Query<SearchParams>,
81) -> Json<ConversationsListResponse> {
82    let results = state.conversations.search(&params.q.unwrap_or_default());
83    Json(ConversationsListResponse { conversations: results })
84}
85
86#[derive(Debug, serde::Deserialize)]
87pub struct SearchParams {
88    pub q: Option<String>,
89}
90
91/// GET /api/v1/conversations/export — export all conversations as JSON.
92pub async fn export_conversations_handler(
93    State(state): State<BancoState>,
94) -> Json<Vec<Conversation>> {
95    Json(state.conversations.export_all())
96}
97
98/// POST /api/v1/conversations/import — import conversations from JSON.
99pub async fn import_conversations_handler(
100    State(state): State<BancoState>,
101    Json(conversations): Json<Vec<Conversation>>,
102) -> Json<serde_json::Value> {
103    let count = state.conversations.import_all(conversations);
104    Json(serde_json::json!({ "imported": count }))
105}