systemprompt_cli/commands/core/content/
show.rs1use super::types::ContentDetailOutput;
2use crate::cli_settings::CliConfig;
3use crate::shared::CommandOutput;
4use anyhow::{Result, anyhow};
5use clap::Args;
6use systemprompt_content::ContentRepository;
7use systemprompt_database::DbPool;
8use systemprompt_identifiers::{ContentId, LocaleCode, 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(
17 long,
18 help = "Source ID — only required when the slug exists in more than one source"
19 )]
20 pub source: Option<String>,
21}
22
23pub async fn execute(args: ShowArgs, config: &CliConfig) -> Result<CommandOutput> {
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<CommandOutput> {
33 let repo = ContentRepository::new(pool)?;
34 let locale = LocaleCode::new("en");
35
36 let content = if args.identifier.starts_with("content_")
37 || args.identifier.contains('-') && args.identifier.len() > 30
38 {
39 let id = ContentId::new(args.identifier.clone());
40 repo.get_by_id(&id)
41 .await?
42 .ok_or_else(|| anyhow!("Content not found: {}", args.identifier))?
43 } else if let Some(source_id) = args.source.as_ref() {
44 let source = SourceId::new(source_id.clone());
45 repo.get_by_source_and_slug(&source, &args.identifier, &locale)
46 .await?
47 .ok_or_else(|| {
48 anyhow!(
49 "Content not found: slug '{}' in source '{}'",
50 args.identifier,
51 source_id
52 )
53 })?
54 } else {
55 let sources = repo.find_sources_by_slug(&args.identifier, &locale).await?;
56 match sources.as_slice() {
57 [] => return Err(anyhow!("No content with slug '{}' found", args.identifier)),
58 [only] => repo
59 .get_by_source_and_slug(only, &args.identifier, &locale)
60 .await?
61 .ok_or_else(|| anyhow!("Content not found: {}", args.identifier))?,
62 many => {
63 let list = many
64 .iter()
65 .map(SourceId::as_str)
66 .collect::<Vec<_>>()
67 .join(", ");
68 return Err(anyhow!(
69 "Slug '{}' exists in multiple sources: [{}]. Re-run with --source <SOURCE> to \
70 disambiguate.",
71 args.identifier,
72 list
73 ));
74 },
75 }
76 };
77
78 let keywords: Vec<String> = content
79 .keywords
80 .split(',')
81 .map(|s| s.trim().to_owned())
82 .filter(|s| !s.is_empty())
83 .collect();
84
85 let output = ContentDetailOutput {
86 id: content.id,
87 slug: content.slug,
88 title: content.title,
89 description: if content.description.is_empty() {
90 None
91 } else {
92 Some(content.description)
93 },
94 body: content.body,
95 author: if content.author.is_empty() {
96 None
97 } else {
98 Some(content.author)
99 },
100 published_at: Some(content.published_at),
101 keywords,
102 kind: content.kind,
103 image: content.image,
104 category_id: content.category_id,
105 source_id: content.source_id,
106 version_hash: content.version_hash,
107 is_public: content.public,
108 updated_at: content.updated_at,
109 };
110
111 Ok(CommandOutput::card_value("Content Details", &output))
112}