Skip to main content

kindling_types/
pin.rs

1use serde::{Deserialize, Serialize};
2
3#[cfg(feature = "ts-rs")]
4use ts_rs::TS;
5
6use crate::common::{Id, ScopeIds, Timestamp};
7
8/// Type of entity that can be pinned.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
11#[serde(rename_all = "snake_case")]
12pub enum PinTargetType {
13    Observation,
14    Summary,
15}
16
17impl PinTargetType {
18    pub const ALL: &'static [PinTargetType] = &[PinTargetType::Observation, PinTargetType::Summary];
19}
20
21/// Pinned reference to an observation or summary.
22///
23/// Mirrors `Pin` in `packages/kindling-core/src/types/pin.ts`.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
26#[serde(rename_all = "camelCase")]
27pub struct Pin {
28    pub id: Id,
29    pub target_type: PinTargetType,
30    pub target_id: Id,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    #[cfg_attr(feature = "ts-rs", ts(optional))]
33    pub reason: Option<String>,
34    #[cfg_attr(feature = "ts-rs", ts(type = "number"))]
35    pub created_at: Timestamp,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    #[cfg_attr(feature = "ts-rs", ts(optional, type = "number"))]
38    pub expires_at: Option<Timestamp>,
39    pub scope_ids: ScopeIds,
40}
41
42/// Input for creating a new pin. Optional fields are auto-generated.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
45#[serde(rename_all = "camelCase")]
46pub struct PinInput {
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    #[cfg_attr(feature = "ts-rs", ts(optional))]
49    pub id: Option<Id>,
50    pub target_type: PinTargetType,
51    pub target_id: Id,
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    #[cfg_attr(feature = "ts-rs", ts(optional))]
54    pub reason: Option<String>,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    #[cfg_attr(feature = "ts-rs", ts(optional, type = "number"))]
57    pub created_at: Option<Timestamp>,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    #[cfg_attr(feature = "ts-rs", ts(optional, type = "number"))]
60    pub expires_at: Option<Timestamp>,
61    pub scope_ids: ScopeIds,
62}
63
64/// True iff the pin has not expired at `now` (epoch ms).
65pub fn is_pin_active(pin: &Pin, now: Timestamp) -> bool {
66    match pin.expires_at {
67        None => true,
68        Some(exp) => exp > now,
69    }
70}