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