Skip to main content

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

1use crate::cli_settings::CliConfig;
2use crate::commands::core::content::types::{GenerateLinkOutput, UtmParamsOutput};
3use crate::shared::CommandResult;
4use anyhow::{anyhow, Result};
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(
64    args: GenerateArgs,
65    _config: &CliConfig,
66) -> Result<CommandResult<GenerateLinkOutput>> {
67    if args.url.is_empty() {
68        return Err(anyhow!("URL is required"));
69    }
70
71    let ctx = AppContext::new().await?;
72    let service = LinkGenerationService::new(ctx.db_pool())?;
73
74    let has_utm = args.utm_source.is_some()
75        || args.utm_medium.is_some()
76        || args.utm_campaign.is_some()
77        || args.utm_term.is_some()
78        || args.utm_content.is_some();
79
80    let utm_params = if has_utm {
81        Some(UtmParams {
82            source: args.utm_source.clone(),
83            medium: args.utm_medium.clone(),
84            campaign: args.utm_campaign.clone(),
85            term: args.utm_term.clone(),
86            content: args.utm_content.clone(),
87        })
88    } else {
89        None
90    };
91
92    let params = GenerateLinkParams {
93        target_url: args.url.clone(),
94        link_type: args.link_type.into(),
95        campaign_id: args.campaign.map(CampaignId::new),
96        campaign_name: args.campaign_name,
97        source_content_id: args.content.map(ContentId::new),
98        source_page: None,
99        utm_params: utm_params.clone(),
100        link_text: None,
101        link_position: None,
102        expires_at: None,
103    };
104
105    let link = service.generate_link(params).await?;
106
107    let short_url = format!("{}/r/{}", DEFAULT_BASE_URL, link.short_code);
108    let full_url = link.get_full_url();
109
110    let utm_output = utm_params.map(|p| UtmParamsOutput {
111        source: p.source,
112        medium: p.medium,
113        campaign: p.campaign,
114        term: p.term,
115        content: p.content,
116    });
117
118    let output = GenerateLinkOutput {
119        link_id: link.id,
120        short_code: link.short_code,
121        short_url,
122        target_url: link.target_url,
123        full_url,
124        link_type: link.link_type,
125        utm_params: utm_output,
126    };
127
128    Ok(CommandResult::card(output).with_title("Generated Link"))
129}