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 }