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