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 }