Skip to main content

systemprompt_cli/commands/core/content/
search.rs

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