api.rs (4180B)
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 sqlx::PgPool; 102 use taler_api::api::TalerRouter as _; 103 use taler_macros::db_test; 104 use taler_test_utils::{json, server::TestServer as _}; 105 106 use crate::{ 107 api::{ApnsRelayConfig, RelayApi, router}, 108 db::all_registrations, 109 }; 110 111 #[db_test] 112 async fn api(pool: PgPool) { 113 let api = Arc::new(RelayApi::new(pool.clone())); 114 let server = router(api).finalize(); 115 116 server 117 .get("/config") 118 .await 119 .assert_ok_json::<ApnsRelayConfig>(); 120 121 assert_eq!(all_registrations(&pool).await.unwrap(), &[""; 0]); 122 123 server 124 .post("/devices") 125 .json(json!({ 126 "token": "device1" 127 })) 128 .await 129 .assert_no_content(); 130 server 131 .post("/devices") 132 .json(json!({ 133 "token": "device1" 134 })) 135 .await 136 .assert_no_content(); 137 server 138 .post("/devices") 139 .json(json!({ 140 "token": "device2" 141 })) 142 .await 143 .assert_no_content(); 144 145 assert_eq!( 146 all_registrations(&pool).await.unwrap(), 147 &["device1", "device2"] 148 ); 149 150 server.delete("/devices/device1").await.assert_no_content(); 151 server.delete("/devices/device3").await.assert_no_content(); 152 assert_eq!(all_registrations(&pool).await.unwrap(), &["device2"]); 153 } 154 }