Skip to main content

aven_core/recurrence/
identity.rs

1use chrono::NaiveDate;
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5use crate::ids::{TaskId, WorkspaceId, encode_crockford};
6
7use super::schedule::{RecurrenceScheduleError, slot_values};
8use super::{RecurrenceSchedule, RecurrenceSeriesId};
9
10const TASK_ID_DOMAIN: &[u8] = b"aven recurrence task v1";
11const TASK_CHANGE_DOMAIN: &[u8] = b"aven recurrence task change v1";
12const OCCURRENCE_CHANGE_DOMAIN: &[u8] = b"aven recurrence occurrence change v1";
13const TASK_FIELD_VERSION_DOMAIN: &[u8] = b"aven recurrence task field version v1";
14const OCCURRENCE_FIELD_VERSION_DOMAIN: &[u8] = b"aven recurrence occurrence field version v1";
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct RecurrenceOccurrenceIdentity {
18    pub task_id: TaskId,
19    pub task_change_id: String,
20    pub occurrence_change_id: String,
21    pub created_at: String,
22    pub updated_at: String,
23    pub occurrence_link: RecurrenceOccurrenceLink,
24    pub field_version_seeds: RecurrenceFieldVersionSeeds,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct RecurrenceOccurrenceLink {
29    pub workspace_id: WorkspaceId,
30    pub series_id: RecurrenceSeriesId,
31    pub slot_on: NaiveDate,
32    pub task_id: TaskId,
33    pub projected_at: String,
34    pub change_id: String,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct RecurrenceFieldVersionSeeds {
39    pub task: String,
40    pub occurrence: String,
41}
42
43pub fn derive_occurrence_identity(
44    workspace_id: &WorkspaceId,
45    series_id: &RecurrenceSeriesId,
46    schedule: &RecurrenceSchedule,
47    slot_on: NaiveDate,
48) -> Result<RecurrenceOccurrenceIdentity, RecurrenceScheduleError> {
49    let slot = slot_values(schedule, slot_on)?;
50    let slot_text = slot_on.format("%Y-%m-%d").to_string();
51    let components = [
52        workspace_id.as_str(),
53        series_id.as_str(),
54        slot_text.as_str(),
55    ];
56    let task_id = derive_id(TASK_ID_DOMAIN, &components)
57        .parse::<TaskId>()
58        .expect("derived recurrence task ID is valid");
59    let task_change_id = derive_id(TASK_CHANGE_DOMAIN, &components);
60    let occurrence_change_id = derive_id(OCCURRENCE_CHANGE_DOMAIN, &components);
61    let field_version_seeds = RecurrenceFieldVersionSeeds {
62        task: derive_id(TASK_FIELD_VERSION_DOMAIN, &components),
63        occurrence: derive_id(OCCURRENCE_FIELD_VERSION_DOMAIN, &components),
64    };
65    let occurrence_link = RecurrenceOccurrenceLink {
66        workspace_id: workspace_id.clone(),
67        series_id: series_id.clone(),
68        slot_on,
69        task_id: task_id.clone(),
70        projected_at: slot.boundary_at.clone(),
71        change_id: occurrence_change_id.clone(),
72    };
73    Ok(RecurrenceOccurrenceIdentity {
74        task_id,
75        task_change_id,
76        occurrence_change_id,
77        created_at: slot.boundary_at.clone(),
78        updated_at: slot.boundary_at,
79        occurrence_link,
80        field_version_seeds,
81    })
82}
83
84fn derive_id(domain: &[u8], components: &[&str]) -> String {
85    let mut hasher = Sha256::new();
86    hasher.update(domain);
87    for component in components {
88        hasher.update(component.as_bytes());
89    }
90    let digest = hasher.finalize();
91    let mut bytes = [0u8; 10];
92    bytes.copy_from_slice(&digest[..10]);
93    encode_crockford(&bytes)
94}