taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

db.rs (4182B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use jiff::Timestamp;
     18 use sqlx::PgPool;
     19 use taler_api::db::BindHelper;
     20 use taler_common::config::Config;
     21 
     22 use crate::config::parse_db_cfg;
     23 
     24 const SCHEMA: &str = "apns_relay";
     25 
     26 pub async fn pool(cfg: &Config) -> anyhow::Result<PgPool> {
     27     let db = parse_db_cfg(cfg)?;
     28     let pool = taler_common::db::pool(db.cfg, SCHEMA).await?;
     29     Ok(pool)
     30 }
     31 
     32 pub async fn dbinit(cfg: &Config, reset: bool) -> anyhow::Result<PgPool> {
     33     let db_cfg = parse_db_cfg(cfg)?;
     34     let pool = taler_common::db::pool(db_cfg.cfg, SCHEMA).await?;
     35     let mut db = pool.acquire().await?;
     36     taler_common::db::dbinit(&mut db, db_cfg.sql_dir.as_ref(), "apns-relay", reset).await?;
     37     Ok(pool)
     38 }
     39 
     40 /// Register a new device
     41 pub async fn register(db: &PgPool, token: &str, now: &Timestamp) -> sqlx::Result<()> {
     42     sqlx::query(
     43         "
     44         INSERT INTO devices (token, registered_at)
     45         VALUES ($1, $2)
     46         ON CONFLICT (token) DO NOTHING
     47     ",
     48     )
     49     .bind(token)
     50     .bind_timestamp(now)
     51     .execute(db)
     52     .await?;
     53     Ok(())
     54 }
     55 
     56 /// Unregister
     57 pub async fn unregister(db: &PgPool, token: &str, before: &Timestamp) -> sqlx::Result<()> {
     58     sqlx::query(
     59         "
     60         DELETE FROM devices
     61         WHERE token = $1 AND registered_at <= $2
     62     ",
     63     )
     64     .bind(token)
     65     .bind_timestamp(before)
     66     .execute(db)
     67     .await?;
     68     Ok(())
     69 }
     70 
     71 /// List all registered devices
     72 pub async fn all_registrations(db: &PgPool) -> sqlx::Result<Vec<String>> {
     73     sqlx::query_scalar("SELECT token FROM devices")
     74         .fetch_all(db)
     75         .await
     76 }
     77 
     78 /// Remove all registered devices
     79 pub async fn clear_registration(db: &PgPool) -> sqlx::Result<()> {
     80     sqlx::query("TRUNCATE TABLE devices").execute(db).await?;
     81     Ok(())
     82 }
     83 
     84 #[cfg(test)]
     85 pub mod test {
     86     use jiff::{SignedDuration, Timestamp};
     87     use sqlx::PgPool;
     88     use taler_test_utils::db::db_test_setup;
     89 
     90     use crate::{
     91         constants::CONFIG_SOURCE,
     92         db::{all_registrations, clear_registration, register, unregister},
     93     };
     94 
     95     pub async fn setup() -> PgPool {
     96         db_test_setup(CONFIG_SOURCE).await.1
     97     }
     98 
     99     #[tokio::test]
    100     async fn registration() {
    101         let db = setup().await;
    102         let token1 = "device_token1";
    103         let token2 = "device_token2";
    104         let now = Timestamp::now();
    105 
    106         // Empty
    107         assert_eq!(all_registrations(&db).await.unwrap(), &[""; 0]);
    108         clear_registration(&db).await.unwrap();
    109 
    110         // Register
    111         register(&db, token1, &now).await.unwrap();
    112         assert_eq!(all_registrations(&db).await.unwrap(), &[token1]);
    113 
    114         // Idempotent
    115         register(&db, token1, &now).await.unwrap();
    116         assert_eq!(all_registrations(&db).await.unwrap(), &[token1]);
    117 
    118         // Many
    119         register(&db, token2, &(now + SignedDuration::from_mins(1)))
    120             .await
    121             .unwrap();
    122         assert_eq!(all_registrations(&db).await.unwrap(), &[token1, token2]);
    123 
    124         // Unregister
    125         unregister(&db, token1, &now).await.unwrap();
    126         assert_eq!(all_registrations(&db).await.unwrap(), &[token2]);
    127 
    128         // Idempotent
    129         unregister(&db, token1, &now).await.unwrap();
    130         assert_eq!(all_registrations(&db).await.unwrap(), &[token2]);
    131 
    132         // Skip reregistered
    133         unregister(&db, token2, &now).await.unwrap();
    134         assert_eq!(all_registrations(&db).await.unwrap(), &[token2]);
    135 
    136         clear_registration(&db).await.unwrap();
    137         assert_eq!(all_registrations(&db).await.unwrap(), &[""; 0]);
    138     }
    139 }