Skip to main content

systemprompt_content/models/builders/
link.rs

1//! Parameter builders for link and click mutations.
2//!
3//! [`CreateLinkParams`] describes a trackable campaign link, while
4//! [`RecordClickParams`] and [`TrackClickParams`] carry the click-event fields
5//! captured on redirect — the former for a fully-resolved click row, the latter
6//! for the inbound tracking request before the click id is minted.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use chrono::{DateTime, Utc};
12use systemprompt_identifiers::{
13    CampaignId, ContentId, ContextId, LinkClickId, LinkId, SessionId, TaskId, UserId,
14};
15
16#[derive(Debug, Clone)]
17pub struct CreateLinkParams {
18    pub short_code: String,
19    pub target_url: String,
20    pub link_type: String,
21    pub source_content_id: Option<ContentId>,
22    pub source_page: Option<String>,
23    pub campaign_id: Option<CampaignId>,
24    pub campaign_name: Option<String>,
25    pub utm_params: Option<String>,
26    pub link_text: Option<String>,
27    pub link_position: Option<String>,
28    pub destination_type: Option<String>,
29    pub is_active: bool,
30    pub expires_at: Option<DateTime<Utc>>,
31}
32
33impl CreateLinkParams {
34    pub const fn new(short_code: String, target_url: String, link_type: String) -> Self {
35        Self {
36            short_code,
37            target_url,
38            link_type,
39            source_content_id: None,
40            source_page: None,
41            campaign_id: None,
42            campaign_name: None,
43            utm_params: None,
44            link_text: None,
45            link_position: None,
46            destination_type: None,
47            is_active: true,
48            expires_at: None,
49        }
50    }
51
52    pub fn with_source_content_id(mut self, source_content_id: Option<ContentId>) -> Self {
53        self.source_content_id = source_content_id;
54        self
55    }
56
57    pub fn with_source_page(mut self, source_page: Option<String>) -> Self {
58        self.source_page = source_page;
59        self
60    }
61
62    pub fn with_campaign_id(mut self, campaign_id: Option<CampaignId>) -> Self {
63        self.campaign_id = campaign_id;
64        self
65    }
66
67    pub fn with_campaign_name(mut self, campaign_name: Option<String>) -> Self {
68        self.campaign_name = campaign_name;
69        self
70    }
71
72    pub fn with_utm_params(mut self, utm_params: Option<String>) -> Self {
73        self.utm_params = utm_params;
74        self
75    }
76
77    pub fn with_link_text(mut self, link_text: Option<String>) -> Self {
78        self.link_text = link_text;
79        self
80    }
81
82    pub fn with_link_position(mut self, link_position: Option<String>) -> Self {
83        self.link_position = link_position;
84        self
85    }
86
87    pub fn with_destination_type(mut self, destination_type: Option<String>) -> Self {
88        self.destination_type = destination_type;
89        self
90    }
91
92    pub const fn with_is_active(mut self, is_active: bool) -> Self {
93        self.is_active = is_active;
94        self
95    }
96
97    pub const fn with_expires_at(mut self, expires_at: Option<DateTime<Utc>>) -> Self {
98        self.expires_at = expires_at;
99        self
100    }
101}
102
103#[derive(Debug, Clone)]
104pub struct RecordClickParams {
105    pub click_id: LinkClickId,
106    pub link_id: LinkId,
107    pub session_id: SessionId,
108    pub user_id: Option<UserId>,
109    pub context_id: Option<ContextId>,
110    pub task_id: Option<TaskId>,
111    pub referrer_page: Option<String>,
112    pub referrer_url: Option<String>,
113    pub clicked_at: DateTime<Utc>,
114    pub user_agent: Option<String>,
115    pub ip_address: Option<String>,
116    pub device_type: Option<String>,
117    pub country: Option<String>,
118    pub is_first_click: bool,
119    pub is_conversion: bool,
120}
121
122impl RecordClickParams {
123    pub const fn new(
124        click_id: LinkClickId,
125        link_id: LinkId,
126        session_id: SessionId,
127        clicked_at: DateTime<Utc>,
128    ) -> Self {
129        Self {
130            click_id,
131            link_id,
132            session_id,
133            user_id: None,
134            context_id: None,
135            task_id: None,
136            referrer_page: None,
137            referrer_url: None,
138            clicked_at,
139            user_agent: None,
140            ip_address: None,
141            device_type: None,
142            country: None,
143            is_first_click: false,
144            is_conversion: false,
145        }
146    }
147
148    pub fn with_user_id(mut self, user_id: Option<UserId>) -> Self {
149        self.user_id = user_id;
150        self
151    }
152
153    pub fn with_context_id(mut self, context_id: Option<ContextId>) -> Self {
154        self.context_id = context_id;
155        self
156    }
157
158    pub fn with_task_id(mut self, task_id: Option<TaskId>) -> Self {
159        self.task_id = task_id;
160        self
161    }
162
163    pub fn with_referrer_page(mut self, referrer_page: Option<String>) -> Self {
164        self.referrer_page = referrer_page;
165        self
166    }
167
168    pub fn with_referrer_url(mut self, referrer_url: Option<String>) -> Self {
169        self.referrer_url = referrer_url;
170        self
171    }
172
173    pub fn with_user_agent(mut self, user_agent: Option<String>) -> Self {
174        self.user_agent = user_agent;
175        self
176    }
177
178    pub fn with_ip_address(mut self, ip_address: Option<String>) -> Self {
179        self.ip_address = ip_address;
180        self
181    }
182
183    pub fn with_device_type(mut self, device_type: Option<String>) -> Self {
184        self.device_type = device_type;
185        self
186    }
187
188    pub fn with_country(mut self, country: Option<String>) -> Self {
189        self.country = country;
190        self
191    }
192
193    pub const fn with_is_first_click(mut self, is_first_click: bool) -> Self {
194        self.is_first_click = is_first_click;
195        self
196    }
197
198    pub const fn with_is_conversion(mut self, is_conversion: bool) -> Self {
199        self.is_conversion = is_conversion;
200        self
201    }
202}
203
204#[derive(Debug, Clone)]
205pub struct TrackClickParams {
206    pub link_id: LinkId,
207    pub session_id: SessionId,
208    pub user_id: Option<UserId>,
209    pub context_id: Option<ContextId>,
210    pub task_id: Option<TaskId>,
211    pub referrer_page: Option<String>,
212    pub referrer_url: Option<String>,
213    pub user_agent: Option<String>,
214    pub ip_address: Option<String>,
215    pub device_type: Option<String>,
216    pub country: Option<String>,
217}
218
219impl TrackClickParams {
220    pub const fn new(link_id: LinkId, session_id: SessionId) -> Self {
221        Self {
222            link_id,
223            session_id,
224            user_id: None,
225            context_id: None,
226            task_id: None,
227            referrer_page: None,
228            referrer_url: None,
229            user_agent: None,
230            ip_address: None,
231            device_type: None,
232            country: None,
233        }
234    }
235
236    pub fn with_user_id(mut self, user_id: Option<UserId>) -> Self {
237        self.user_id = user_id;
238        self
239    }
240
241    pub fn with_context_id(mut self, context_id: Option<ContextId>) -> Self {
242        self.context_id = context_id;
243        self
244    }
245
246    pub fn with_task_id(mut self, task_id: Option<TaskId>) -> Self {
247        self.task_id = task_id;
248        self
249    }
250
251    pub fn with_referrer_page(mut self, referrer_page: Option<String>) -> Self {
252        self.referrer_page = referrer_page;
253        self
254    }
255
256    pub fn with_referrer_url(mut self, referrer_url: Option<String>) -> Self {
257        self.referrer_url = referrer_url;
258        self
259    }
260
261    pub fn with_user_agent(mut self, user_agent: Option<String>) -> Self {
262        self.user_agent = user_agent;
263        self
264    }
265
266    pub fn with_ip_address(mut self, ip_address: Option<String>) -> Self {
267        self.ip_address = ip_address;
268        self
269    }
270
271    pub fn with_device_type(mut self, device_type: Option<String>) -> Self {
272        self.device_type = device_type;
273        self
274    }
275
276    pub fn with_country(mut self, country: Option<String>) -> Self {
277        self.country = country;
278        self
279    }
280}