setup.rs (5649B)
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 anyhow::Context; 18 use sqlx::PgPool; 19 use taler_common::config::Config; 20 use tracing::{debug, error, info, warn}; 21 22 use crate::{ 23 config::{SetupCfg, WiseBalance}, 24 db::{initialize_sync_state, reset_sync_state}, 25 wise_api::client::Client, 26 }; 27 28 pub async fn setup( 29 cfg: &Config, 30 pool: &PgPool, 31 client: &http_client::Client, 32 reset: bool, 33 ) -> anyhow::Result<()> { 34 let cfg = SetupCfg::parse(cfg)?; 35 let client = Client::new(client, cfg.api, &cfg.token); 36 37 info!(target: "setup", "Check API access and configuration"); 38 let mut ready = true; 39 40 // Check profile 41 let profiles = client.profiles().await?; 42 let profiles_fmt = std::fmt::from_fn(|w| { 43 for p in &profiles { 44 write!(w, "\n{} {:?} '{}'", p.id, p.ty, p.full_name)?; 45 } 46 Ok(()) 47 }); 48 if let Some(id) = cfg.profile_id { 49 if let Some(profile) = profiles.iter().find(|it| it.id == id) { 50 debug!(target: "setup", "PROFILE_ID {id} in config is one of:{profiles_fmt}"); 51 match &cfg.name { 52 Some(name) => { 53 if name != profile.full_name { 54 warn!( 55 target: "setup", 56 "Expected NAME '{name}' from config got '{}' from server", 57 profile.full_name 58 ) 59 } else { 60 debug!(target: "setup", "NAME {name} from config match server") 61 } 62 } 63 None => { 64 error!(target: "setup", "Missing NAME got '{}' from server", profile.full_name); 65 ready = false; 66 } 67 } 68 } else { 69 error!(target: "setup", "Unknown PROFILE_ID {id} in config, must be one of:{profiles_fmt}"); 70 ready = false; 71 } 72 } else { 73 error!(target: "setup", "Missing PROFILE_ID in config, must be one of:{profiles_fmt}"); 74 ready = false; 75 } 76 77 if let Some(profile_id) = cfg.profile_id { 78 // Check accounts 79 let accounts = client.borderless_accounts(profile_id).await?; 80 let account = accounts 81 .iter() 82 .find(|it| it.profile_id == profile_id) 83 .context("Missing Wise account for configured profile")?; 84 85 let balances_fmt = std::fmt::from_fn(|w| { 86 for balance in &account.balances { 87 if let Some(payto) = balance.bank_details.account() { 88 write!(w, "\n{} {} ({payto})", balance.id, balance.currency)?; 89 } 90 } 91 Ok(()) 92 }); 93 info!(target: "setup", "Supported balances are: {balances_fmt}"); 94 95 if cfg.balances.is_empty() { 96 error!(target: "setup", "No balances are configured"); 97 ready = false; 98 } 99 for WiseBalance { 100 id, 101 currency, 102 payto, 103 } in &cfg.balances 104 { 105 let Some(balance) = account.balances.iter().find(|b| b.id == u64::from(*id)) else { 106 error!(target: "setup", "Unknown balance {id} {currency}"); 107 ready = false; 108 continue; 109 }; 110 if balance.currency != *currency { 111 error!(target: "setup", "Balance {id} in config use currency {currency} expected {}", balance.currency); 112 ready = false; 113 } 114 match balance.bank_details.account() { 115 Some(account) if account == *payto => {} 116 Some(account) => { 117 error!(target: "setup", "Balance {id} in config use payto ({payto}) expected ({account})"); 118 ready = false; 119 } 120 None => { 121 error!(target: "setup", "Balance {id} in config use payto ({payto}) but it's not supported"); 122 ready = false; 123 } 124 } 125 } 126 } 127 128 if !ready { 129 error!(target: "setup", "Setup failed, config need editing"); 130 std::process::exit(6); 131 } 132 133 let profile_id = cfg.profile_id.context("Missing PROFILE_ID")?; 134 let balances = client.standard_balances(profile_id).await?; 135 for configured in &cfg.balances { 136 let remote = balances 137 .iter() 138 .find(|remote| { 139 remote.id == u64::from(configured.id) && remote.currency == configured.currency 140 }) 141 .with_context(|| { 142 format!( 143 "Missing Wise balance {} {}", 144 configured.id, configured.currency 145 ) 146 })?; 147 initialize_sync_state(pool, configured.id, remote.creation_time).await?; 148 } 149 if reset { 150 reset_sync_state(pool).await?; 151 info!(target: "setup", "Synchronization state reset to balance creation"); 152 } 153 info!(target: "setup", "Setup finished"); 154 Ok(()) 155 }