headless_lms_models/
page_visit_datum_daily_visit_hashing_keys.rs

1use chrono::NaiveDate;
2use headless_lms_utils::page_visit_hasher::hash_anonymous_identifier;
3
4use crate::prelude::*;
5
6pub struct GenerateAnonymousIdentifierInput {
7    pub course_id: Uuid,
8    pub user_agent: String,
9    pub ip_address: String,
10}
11pub async fn generate_anonymous_identifier(
12    conn: &mut PgConnection,
13    input: GenerateAnonymousIdentifierInput,
14) -> ModelResult<String> {
15    let key_for_the_day = get_key_for_the_day(conn).await?;
16    let hash = hash_anonymous_identifier(
17        input.course_id,
18        key_for_the_day,
19        input.user_agent,
20        input.ip_address,
21    )
22    .map_err(|e| ModelError::new(ModelErrorType::Generic, e.to_string(), Some(e)))?;
23    Ok(hash)
24}
25
26pub async fn get_key_for_the_day(conn: &mut PgConnection) -> ModelResult<Vec<u8>> {
27    let now = Utc::now();
28    let valid_for_date = now.date_naive();
29    let res = try_get_key_for_the_day_internal(conn, valid_for_date).await?;
30    match res {
31        Some(hashing_key) => Ok(hashing_key),
32        None => {
33            try_insert_key_for_the_day_internal(conn, valid_for_date).await?;
34            let second_try = try_get_key_for_the_day_internal(conn, valid_for_date).await?;
35            match second_try {
36                Some(hashing_key) => Ok(hashing_key),
37                None => Err(ModelError::new(
38                    ModelErrorType::Generic,
39                    "Failed to get hashing key for the day".to_string(),
40                    None,
41                )),
42            }
43        }
44    }
45}
46
47async fn try_get_key_for_the_day_internal(
48    conn: &mut PgConnection,
49    valid_for_date: NaiveDate,
50) -> ModelResult<Option<Vec<u8>>> {
51    let res = sqlx::query!(
52        "
53SELECT hashing_key FROM page_visit_datum_daily_visit_hashing_keys
54WHERE valid_for_date = $1
55    ",
56        valid_for_date
57    )
58    .fetch_optional(conn)
59    .await?;
60    Ok(res.map(|r| r.hashing_key))
61}
62
63async fn try_insert_key_for_the_day_internal(
64    conn: &mut PgConnection,
65    valid_for_date: NaiveDate,
66) -> ModelResult<()> {
67    sqlx::query!(
68        "
69INSERT INTO page_visit_datum_daily_visit_hashing_keys(valid_for_date)
70VALUES ($1)
71ON CONFLICT (valid_for_date) DO NOTHING
72    ",
73        valid_for_date
74    )
75    .execute(&mut *conn)
76    .await?;
77
78    // We no longer need the keys from the previous days, so lets delete them.
79    sqlx::query!(
80        "
81DELETE FROM page_visit_datum_daily_visit_hashing_keys WHERE valid_for_date < $1
82    ",
83        valid_for_date
84    )
85    .execute(&mut *conn)
86    .await?;
87    Ok(())
88}