Skip to main content

systemprompt_cli/commands/core/content/
search.rs

1use super::types::{SearchOutput, SearchResultRow};
2use crate::cli_settings::CliConfig;
3use crate::shared::CommandResult;
4use anyhow::Result;
5use clap::Args;
6use systemprompt_content::{SearchFilters, SearchRequest, SearchService};
7use systemprompt_database::DbPool;
8use systemprompt_identifiers::CategoryId;
9use systemprompt_runtime::AppContext;
10
11#[derive(Debug, Args)]
12pub struct SearchArgs {
13    #[arg(help = "Search query")]
14    pub query: String,
15
16    #[arg(long, help = "Filter by source ID")]
17    pub source: Option<String>,
18
19    #[arg(long, help = "Filter by category ID")]
20    pub category: Option<String>,
21
22    #[arg(long, default_value = "20")]
23    pub limit: i64,
24}
25
26pub async fn execute(args: SearchArgs, config: &CliConfig) -> Result<CommandResult<SearchOutput>> {
27    let ctx = AppContext::new().await?;
28    execute_with_pool(args, ctx.db_pool(), config).await
29}
30
31pub async fn execute_with_pool(
32    args: SearchArgs,
33    pool: &DbPool,
34    _config: &CliConfig,
35) -> Result<CommandResult<SearchOutput>> {
36    let service = SearchService::new(pool)?;
37
38    let filters = args.category.as_ref().map(|cat| SearchFilters {
39        category_id: Some(CategoryId::new(cat.clone())),
40    });
41
42    let request = SearchRequest {
43        query: args.query.clone(),
44        filters,
45        limit: Some(args.limit),
46    };
47
48    let response = service.search(&request).await?;
49
50    let results: Vec<SearchResultRow> = response
51        .results
52        .into_iter()
53        .filter(|r| {
54            args.source
55                .as_ref()
56                .is_none_or(|src| r.source_id.as_str() == src)
57        })
58        .map(|r| SearchResultRow {
59            id: r.id,
60            slug: r.slug,
61            title: r.title,
62            description: if r.description.is_empty() {
63                None
64            } else {
65                Some(r.description)
66            },
67            image: r.image,
68            source_id: r.source_id,
69            category_id: r.category_id,
70        })
71        .collect();
72
73    let total = results.len() as i64;
74
75    let output = SearchOutput {
76        results,
77        total,
78        query: args.query,
79    };
80
81    Ok(CommandResult::table(output)
82        .with_title("Search Results")
83        .with_columns(vec![
84            "id".to_string(),
85            "title".to_string(),
86            "slug".to_string(),
87            "source_id".to_string(),
88        ]))
89}