Skip to main content

mas_storage_pg/user/
terms.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use async_trait::async_trait;
9use mas_data_model::{Clock, UlidExt as _, User};
10use mas_storage::user::UserTermsRepository;
11use rand::Rng;
12use sqlx::PgConnection;
13use ulid::Ulid;
14use url::Url;
15use uuid::Uuid;
16
17use crate::{DatabaseError, tracing::ExecuteExt};
18
19/// An implementation of [`UserTermsRepository`] for a PostgreSQL connection
20pub struct PgUserTermsRepository<'c> {
21    conn: &'c mut PgConnection,
22}
23
24impl<'c> PgUserTermsRepository<'c> {
25    /// Create a new [`PgUserTermsRepository`] from an active PostgreSQL
26    /// connection
27    pub fn new(conn: &'c mut PgConnection) -> Self {
28        Self { conn }
29    }
30}
31
32#[async_trait]
33impl UserTermsRepository for PgUserTermsRepository<'_> {
34    type Error = DatabaseError;
35
36    #[tracing::instrument(
37        name = "db.user_terms.accept_terms",
38        skip_all,
39        fields(
40            db.query.text,
41            %user.id,
42            user_terms.id,
43            %user_terms.url = terms_url.as_str(),
44        ),
45        err,
46    )]
47    async fn accept_terms(
48        &mut self,
49        rng: &mut (dyn Rng + Send),
50        clock: &dyn Clock,
51        user: &User,
52        terms_url: Url,
53    ) -> Result<(), Self::Error> {
54        let created_at = clock.now();
55        let id = Ulid::from_datetime_with_rng(created_at, rng);
56        tracing::Span::current().record("user_terms.id", tracing::field::display(id));
57
58        sqlx::query!(
59            r#"
60            INSERT INTO user_terms (user_terms_id, user_id, terms_url, created_at)
61            VALUES ($1, $2, $3, $4)
62            ON CONFLICT (user_id, terms_url) DO NOTHING
63            "#,
64            Uuid::from(id),
65            Uuid::from(user.id),
66            terms_url.as_str(),
67            created_at,
68        )
69        .traced()
70        .execute(&mut *self.conn)
71        .await?;
72
73        Ok(())
74    }
75}