Skip to main content

systemprompt_cli/commands/core/content/link/
generate.rs

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