Skip to main content

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

1//! `core content link generate` command minting tracked links.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use 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}