api.rs (4343B)
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::State, 22 response::{IntoResponse as _, NoContent}, 23 routing::{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 .delete( 71 async |State(state): State<Arc<RelayApi>>, 72 Req(req): Req<DeviceRegistrationRequest>| { 73 db::unregister(&state.db, &req.token, &Timestamp::now()).await?; 74 ApiResult::Ok(NoContent) 75 }, 76 ), 77 ) 78 .route( 79 "/config", 80 get(async || { 81 Json(ApnsRelayConfig { 82 name: "taler-apns-relay", 83 version: "0:0:0", 84 implementation: Some("urn:net:taler:specs:taler-apns-relay:taler-rust"), 85 }) 86 .into_response() 87 }), 88 ) 89 .with_state(state) 90 } 91 92 #[cfg(test)] 93 mod test { 94 use std::sync::Arc; 95 96 use taler_api::api::TalerRouter as _; 97 use taler_test_utils::{json, server::TestServer as _}; 98 99 use crate::{ 100 api::{ApnsRelayConfig, RelayApi, router}, 101 db::{all_registrations, test::setup}, 102 }; 103 104 #[tokio::test] 105 async fn api() { 106 let pool = setup().await; 107 let api = Arc::new(RelayApi::new(pool.clone())); 108 let server = router(api).finalize(); 109 110 server 111 .get("/config") 112 .await 113 .assert_ok_json::<ApnsRelayConfig>(); 114 115 assert_eq!(all_registrations(&pool).await.unwrap(), &[""; 0]); 116 117 server 118 .post("/devices") 119 .json(&json!({ 120 "token": "device1" 121 })) 122 .await 123 .assert_no_content(); 124 server 125 .post("/devices") 126 .json(&json!({ 127 "token": "device1" 128 })) 129 .await 130 .assert_no_content(); 131 server 132 .post("/devices") 133 .json(&json!({ 134 "token": "device2" 135 })) 136 .await 137 .assert_no_content(); 138 139 assert_eq!( 140 all_registrations(&pool).await.unwrap(), 141 &["device1", "device2"] 142 ); 143 144 server 145 .delete("/devices") 146 .json(&json!({ 147 "token": "device1" 148 })) 149 .await 150 .assert_no_content(); 151 server 152 .delete("/devices") 153 .json(&json!({ 154 "token": "device3" 155 })) 156 .await 157 .assert_no_content(); 158 assert_eq!(all_registrations(&pool).await.unwrap(), &["device2"]); 159 } 160 }