taler-rust

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

api.rs (4157B)


      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 std::sync::Arc;
     18 
     19 use axum::{
     20     Json, Router,
     21     extract::{Path, State},
     22     response::{IntoResponse as _, NoContent},
     23     routing::{delete, get, post},
     24 };
     25 use jiff::Timestamp;
     26 use serde::{Deserialize, Serialize};
     27 use sqlx::PgPool;
     28 use taler_api::{error::ApiResult, json::Req};
     29 
     30 use crate::db;
     31 
     32 pub struct RelayApi {
     33     db: PgPool,
     34 }
     35 
     36 impl RelayApi {
     37     pub fn new(db: PgPool) -> Self {
     38         Self { db }
     39     }
     40 }
     41 
     42 #[derive(Debug, Deserialize, Serialize)]
     43 pub struct DeviceRegistrationRequest {
     44     pub token: String,
     45 }
     46 
     47 #[derive(Debug, Deserialize, Serialize)]
     48 pub struct DeviceUnregistrationRequest {
     49     pub token: String,
     50 }
     51 
     52 #[derive(Debug, Deserialize, Serialize)]
     53 pub struct ApnsRelayConfig<'a> {
     54     pub name: &'a str,
     55     pub version: &'a str,
     56     pub implementation: Option<&'a str>,
     57 }
     58 
     59 pub fn router(state: Arc<RelayApi>) -> Router {
     60     Router::new()
     61         .route(
     62             "/devices",
     63             post(
     64                 async |State(state): State<Arc<RelayApi>>,
     65                        Req(req): Req<DeviceRegistrationRequest>| {
     66                     db::register(&state.db, &req.token, &Timestamp::now()).await?;
     67                     ApiResult::Ok(NoContent)
     68                 },
     69             ),
     70         )
     71         .route(
     72             "/devices/{token}",
     73             delete(
     74                 async |State(state): State<Arc<RelayApi>>, Path(token): Path<String>| {
     75                     db::unregister(&state.db, &token, &Timestamp::now()).await?;
     76                     ApiResult::Ok(NoContent)
     77                 },
     78             ),
     79         )
     80         .route(
     81             "/config",
     82             get(async || {
     83                 Json(ApnsRelayConfig {
     84                     name: "taler-apns-relay",
     85                     version: "0:0:0",
     86                     implementation: Some("urn:net:taler:specs:taler-apns-relay:taler-rust"),
     87                 })
     88                 .into_response()
     89             }),
     90         )
     91         .with_state(state)
     92 }
     93 
     94 #[cfg(test)]
     95 mod test {
     96     use std::sync::Arc;
     97 
     98     use taler_api::api::TalerRouter as _;
     99     use taler_test_utils::{json, server::TestServer as _};
    100 
    101     use crate::{
    102         api::{ApnsRelayConfig, RelayApi, router},
    103         db::{all_registrations, test::setup},
    104     };
    105 
    106     #[tokio::test]
    107     async fn api() {
    108         let pool = setup().await;
    109         let api = Arc::new(RelayApi::new(pool.clone()));
    110         let server = router(api).finalize();
    111 
    112         server
    113             .get("/config")
    114             .await
    115             .assert_ok_json::<ApnsRelayConfig>();
    116 
    117         assert_eq!(all_registrations(&pool).await.unwrap(), &[""; 0]);
    118 
    119         server
    120             .post("/devices")
    121             .json(&json!({
    122                 "token": "device1"
    123             }))
    124             .await
    125             .assert_no_content();
    126         server
    127             .post("/devices")
    128             .json(&json!({
    129                 "token": "device1"
    130             }))
    131             .await
    132             .assert_no_content();
    133         server
    134             .post("/devices")
    135             .json(&json!({
    136                 "token": "device2"
    137             }))
    138             .await
    139             .assert_no_content();
    140 
    141         assert_eq!(
    142             all_registrations(&pool).await.unwrap(),
    143             &["device1", "device2"]
    144         );
    145 
    146         server.delete("/devices/device1").await.assert_no_content();
    147         server.delete("/devices/device3").await.assert_no_content();
    148         assert_eq!(all_registrations(&pool).await.unwrap(), &["device2"]);
    149     }
    150 }