taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

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 }