Skip to main content

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