Skip to main content

systemprompt_cli/commands/core/content/
show.rs

1use super::types::ContentDetailOutput;
2use crate::cli_settings::CliConfig;
3use crate::shared::CommandResult;
4use anyhow::{anyhow, Result};
5use clap::Args;
6use systemprompt_content::ContentRepository;
7use systemprompt_database::DbPool;
8use systemprompt_identifiers::{ContentId, SourceId};
9use systemprompt_runtime::AppContext;
10
11#[derive(Debug, Args)]
12pub struct ShowArgs {
13    #[arg(help = "Content ID or slug")]
14    pub identifier: String,
15
16    #[arg(long, help = "Source ID (required when using slug)")]
17    pub source: Option<String>,
18}
19
20pub async fn execute(
21    args: ShowArgs,
22    config: &CliConfig,
23) -> Result<CommandResult<ContentDetailOutput>> {
24    let ctx = AppContext::new().await?;
25    execute_with_pool(args, ctx.db_pool(), config).await
26}
27
28pub async fn execute_with_pool(
29    args: ShowArgs,
30    pool: &DbPool,
31    _config: &CliConfig,
32) -> Result<CommandResult<ContentDetailOutput>> {
33    let repo = ContentRepository::new(pool)?;
34
35    let content = if args.identifier.starts_with("content_")
36        || args.identifier.contains('-') && args.identifier.len() > 30
37    {
38        let id = ContentId::new(args.identifier.clone());
39        repo.get_by_id(&id)
40            .await?
41            .ok_or_else(|| anyhow!("Content not found: {}", args.identifier))?
42    } else {
43        let source_id = args
44            .source
45            .as_ref()
46            .ok_or_else(|| anyhow!("Source ID required when using slug"))?;
47        let source = SourceId::new(source_id.clone());
48        repo.get_by_source_and_slug(&source, &args.identifier)
49            .await?
50            .ok_or_else(|| {
51                anyhow!(
52                    "Content not found: {} in source {}",
53                    args.identifier,
54                    source_id
55                )
56            })?
57    };
58
59    let keywords: Vec<String> = content
60        .keywords
61        .split(',')
62        .map(|s| s.trim().to_string())
63        .filter(|s| !s.is_empty())
64        .collect();
65
66    let output = ContentDetailOutput {
67        id: content.id,
68        slug: content.slug,
69        title: content.title,
70        description: if content.description.is_empty() {
71            None
72        } else {
73            Some(content.description)
74        },
75        body: content.body,
76        author: if content.author.is_empty() {
77            None
78        } else {
79            Some(content.author)
80        },
81        published_at: Some(content.published_at),
82        keywords,
83        kind: content.kind,
84        image: content.image,
85        category_id: content.category_id,
86        source_id: content.source_id,
87        version_hash: content.version_hash,
88        is_public: content.public,
89        updated_at: content.updated_at,
90    };
91
92    Ok(CommandResult::card(output).with_title("Content Details"))
93}