use serde::{Deserialize, Serialize};
use crate::kanban::KanbanCard;
use crate::rpc::error::RpcError;
use crate::state::AppState;
use super::shared::{
default_workspace_id, ensure_workspace_exists, resolve_board, tasks_for_board,
};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchCardsParams {
#[serde(default = "default_workspace_id")]
pub workspace_id: String,
pub query: String,
pub board_id: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct SearchCardsResult {
pub cards: Vec<KanbanCard>,
}
pub async fn search_cards(
state: &AppState,
params: SearchCardsParams,
) -> Result<SearchCardsResult, RpcError> {
ensure_workspace_exists(state, ¶ms.workspace_id).await?;
let query = params.query.trim().to_ascii_lowercase();
if query.is_empty() {
return Err(RpcError::BadRequest("query cannot be blank".to_string()));
}
let tasks = state
.task_store
.list_by_workspace(¶ms.workspace_id)
.await?;
let cards = tasks
.into_iter()
.filter(|task| {
if let Some(board_id) = params.board_id.as_deref() {
if task.board_id.as_deref() != Some(board_id) {
return false;
}
}
task.board_id.is_some()
&& (task.title.to_ascii_lowercase().contains(&query)
|| task
.labels
.iter()
.any(|label| label.to_ascii_lowercase().contains(&query))
|| task
.assignee
.as_ref()
.map(|assignee| assignee.to_ascii_lowercase().contains(&query))
.unwrap_or(false))
})
.map(|task| crate::kanban::task_to_card(&task))
.collect();
Ok(SearchCardsResult { cards })
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListCardsByColumnParams {
#[serde(default = "default_workspace_id")]
pub workspace_id: String,
pub board_id: Option<String>,
pub column_id: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListCardsByColumnResult {
pub board_id: String,
pub column_id: String,
pub column_name: String,
pub cards: Vec<KanbanCard>,
}
pub async fn list_cards_by_column(
state: &AppState,
params: ListCardsByColumnParams,
) -> Result<ListCardsByColumnResult, RpcError> {
let board = resolve_board(state, ¶ms.workspace_id, params.board_id.as_deref()).await?;
let column = board
.columns
.iter()
.find(|column| column.id == params.column_id)
.ok_or_else(|| RpcError::NotFound(format!("Column {} not found", params.column_id)))?;
let mut tasks = tasks_for_board(state, &board).await?;
tasks.retain(|task| task.column_id.as_deref().unwrap_or("backlog") == params.column_id);
tasks.sort_by_key(|task| task.position);
Ok(ListCardsByColumnResult {
board_id: board.id,
column_id: params.column_id,
column_name: column.name.clone(),
cards: tasks
.into_iter()
.map(|task| crate::kanban::task_to_card(&task))
.collect(),
})
}