systemprompt_cli/commands/core/content/link/
generate.rs1use crate::cli_settings::CliConfig;
2use crate::commands::core::content::types::{GenerateLinkOutput, UtmParamsOutput};
3use crate::shared::CommandOutput;
4use anyhow::{Result, anyhow};
5use clap::{Args, ValueEnum};
6use systemprompt_content::models::{LinkType as DomainLinkType, UtmParams};
7use systemprompt_content::services::link::generation::{GenerateLinkParams, LinkGenerationService};
8use systemprompt_identifiers::{CampaignId, ContentId};
9use systemprompt_runtime::AppContext;
10
11#[derive(Debug, Clone, Copy, ValueEnum)]
12pub enum LinkType {
13 Redirect,
14 Utm,
15 Both,
16}
17
18impl From<LinkType> for DomainLinkType {
19 fn from(lt: LinkType) -> Self {
20 match lt {
21 LinkType::Redirect => Self::Redirect,
22 LinkType::Utm => Self::Utm,
23 LinkType::Both => Self::Both,
24 }
25 }
26}
27
28#[derive(Debug, Args)]
29pub struct GenerateArgs {
30 #[arg(long, help = "Target URL")]
31 pub url: String,
32
33 #[arg(long, help = "Campaign ID")]
34 pub campaign: Option<String>,
35
36 #[arg(long, help = "Campaign name")]
37 pub campaign_name: Option<String>,
38
39 #[arg(long, help = "Source content ID")]
40 pub content: Option<String>,
41
42 #[arg(long, help = "UTM source")]
43 pub utm_source: Option<String>,
44
45 #[arg(long, help = "UTM medium")]
46 pub utm_medium: Option<String>,
47
48 #[arg(long, help = "UTM campaign")]
49 pub utm_campaign: Option<String>,
50
51 #[arg(long, help = "UTM term")]
52 pub utm_term: Option<String>,
53
54 #[arg(long, help = "UTM content")]
55 pub utm_content: Option<String>,
56
57 #[arg(long, value_enum, default_value = "both", help = "Link type")]
58 pub link_type: LinkType,
59}
60
61const DEFAULT_BASE_URL: &str = "https://systemprompt.io";
62
63pub async fn execute(args: GenerateArgs, _config: &CliConfig) -> Result<CommandOutput> {
64 if args.url.is_empty() {
65 return Err(anyhow!("URL is required"));
66 }
67
68 let ctx = AppContext::new().await?;
69 let service = LinkGenerationService::new(ctx.db_pool())?;
70
71 let has_utm = args.utm_source.is_some()
72 || args.utm_medium.is_some()
73 || args.utm_campaign.is_some()
74 || args.utm_term.is_some()
75 || args.utm_content.is_some();
76
77 let utm_params = if has_utm {
78 Some(UtmParams {
79 source: args.utm_source.clone(),
80 medium: args.utm_medium.clone(),
81 campaign: args.utm_campaign.clone(),
82 term: args.utm_term.clone(),
83 content: args.utm_content.clone(),
84 })
85 } else {
86 None
87 };
88
89 let params = GenerateLinkParams {
90 target_url: args.url.clone(),
91 link_type: args.link_type.into(),
92 campaign_id: args.campaign.map(CampaignId::new),
93 campaign_name: args.campaign_name,
94 source_content_id: args.content.map(ContentId::new),
95 source_page: None,
96 utm_params: utm_params.clone(),
97 link_text: None,
98 link_position: None,
99 expires_at: None,
100 };
101
102 let link = service.generate_link(params).await?;
103
104 let short_url = format!("{}/r/{}", DEFAULT_BASE_URL, link.short_code);
105 let full_url = link.get_full_url();
106
107 let utm_output = utm_params.map(|p| UtmParamsOutput {
108 source: p.source,
109 medium: p.medium,
110 campaign: p.campaign,
111 term: p.term,
112 content: p.content,
113 });
114
115 let output = GenerateLinkOutput {
116 link_id: link.id,
117 short_code: link.short_code,
118 short_url,
119 target_url: link.target_url,
120 full_url,
121 link_type: link.link_type,
122 utm_params: utm_output,
123 };
124
125 Ok(CommandOutput::card_value("Generated Link", &output))
126}