//! HTTP handlers for the provider-neutral exchange-rate administration workspace. use axum::{ extract::{Query, State}, http::{HeaderMap, StatusCode}, response::{Html, IntoResponse, Redirect, Response}, }; use axum_extra::extract::Form; use crate::{ AppState, definitions::exchange_rates::{ AddProfileCurrencySourceRequest, ExchangeRateDateRule, ExchangeRateSource, ListConversionEvidenceRequest, }, services::{authenticated_request, reject_cross_site}, }; use super::{ state::LoadError, workspace_loader::load_workspace, workspace_state::{ ConfigurationForm, EvidenceForm, EvidenceView, PreviewForm, PreviewView, WorkspaceInputs, WorkspaceQuery, }, workspace_ui, }; pub(crate) async fn legacy_ecb() -> Redirect { Redirect::permanent("/admin/exchange-rates#providers") } pub(crate) async fn page( State(state): State, headers: HeaderMap, Query(query): Query, ) -> Response { render_loaded( &headers, load_workspace(state, &headers, WorkspaceInputs::for_profile(query.profile)).await, StatusCode::OK, ) } pub(crate) async fn add_configuration( State(state): State, headers: HeaderMap, Form(form): Form, ) -> Response { if let Some(rejection) = reject_cross_site(&headers) { return rejection; } let mut inputs = WorkspaceInputs::for_profile(form.profile.clone()); inputs.configuration = form.clone(); let added_source = form.source_id.trim().to_string(); let request = AddProfileCurrencySourceRequest { profile_name: form.profile, currency: form.currency.trim().to_ascii_uppercase(), source_id: form.source_id.trim().to_string(), make_default: form.make_default, }; let Ok(request) = authenticated_request(&headers, request) else { return Redirect::to("/login").into_response(); }; let mut exchange_rates = state.exchange_rates.clone(); match exchange_rates.add_profile_currency_source(request).await { Ok(currency) => { let currency = currency.into_inner(); inputs.configuration = ConfigurationForm::default(); inputs.status = Some(crate::tr!( crate::i18n::Locale::from_headers(&headers), "rates-configuration-added", "currency" => currency.currency, "source" => added_source, )); render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::OK, ) } Err(error) => { inputs.error = Some(error.message().to_string()); render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::UNPROCESSABLE_ENTITY, ) } } } pub(crate) async fn preview( State(state): State, headers: HeaderMap, Form(form): Form, ) -> Response { if let Some(rejection) = reject_cross_site(&headers) { return rejection; } let mut inputs = WorkspaceInputs::for_profile(form.profile.clone()); inputs.preview_form = form.clone(); let request = match form.request() { Ok(request) => request, Err(key) => { inputs.error = Some(crate::tr!(crate::i18n::Locale::from_headers(&headers), key)); return render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::UNPROCESSABLE_ENTITY, ); } }; let Ok(request) = authenticated_request(&headers, request) else { return Redirect::to("/login").into_response(); }; let mut exchange_rates = state.exchange_rates.clone(); match exchange_rates.preview_direct_conversion(request).await { Ok(response) => { let response = response.into_inner(); let locale = crate::i18n::Locale::from_headers(&headers); inputs.preview = Some(PreviewView { applied_method: crate::tr!(locale, rate_method_key(response.applied_source)), applied_date_rule: crate::tr!(locale, date_rule_key(response.applied_date_rule)), response, }); render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::OK, ) } Err(error) => { inputs.error = Some(error.message().to_string()); render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::UNPROCESSABLE_ENTITY, ) } } } pub(crate) async fn evidence( State(state): State, headers: HeaderMap, Form(form): Form, ) -> Response { if let Some(rejection) = reject_cross_site(&headers) { return rejection; } let mut inputs = WorkspaceInputs::for_profile(form.profile.clone()); inputs.evidence_form = form.clone(); let record_id = match form.record_id.trim().parse::() { Ok(id) if id > 0 => id, _ => { inputs.error = Some(crate::tr!( crate::i18n::Locale::from_headers(&headers), "rates-err-record-id" )); return render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::UNPROCESSABLE_ENTITY, ); } }; let request = ListConversionEvidenceRequest { profile_name: form.profile, table_name: form.table_name.trim().to_string(), record_id, column_name: form.column_name.trim().to_string(), limit: 50, before_evidence_id: None, }; let Ok(request) = authenticated_request(&headers, request) else { return Redirect::to("/login").into_response(); }; let mut exchange_rates = state.exchange_rates.clone(); match exchange_rates.list_conversion_evidence(request).await { Ok(response) => { let response = response.into_inner(); inputs.evidence_has_exchange = Some(response.has_exchange); inputs.evidence_has_more = response.has_more; inputs.evidence = response .evidence .into_iter() .map(|evidence| EvidenceView { evidence }) .collect(); render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::OK, ) } Err(error) => { inputs.error = Some(error.message().to_string()); render_loaded( &headers, load_workspace(state, &headers, inputs).await, StatusCode::UNPROCESSABLE_ENTITY, ) } } } fn rate_method_key(value: i32) -> &'static str { match ExchangeRateSource::try_from(value).ok() { Some(ExchangeRateSource::Official) => "rates-source-official", Some(ExchangeRateSource::SavedCustom) => "rates-source-saved", Some(ExchangeRateSource::Manual) => "rates-source-manual", _ => "rates-unknown", } } fn date_rule_key(value: i32) -> &'static str { match ExchangeRateDateRule::try_from(value).ok() { Some(ExchangeRateDateRule::PreviousPublication) => "rates-rule-previous", Some(ExchangeRateDateRule::OnOrBeforeDate) => "rates-rule-on-or-before", Some(ExchangeRateDateRule::SpecificPublicationDate) => "rates-rule-specific", _ => "rates-unknown", } } fn render_loaded( headers: &HeaderMap, result: Result, success_status: StatusCode, ) -> Response { match result { Ok(page) => (success_status, Html(workspace_ui::render_page(&page))).into_response(), Err(LoadError::Unauthenticated) => Redirect::to("/login").into_response(), Err(LoadError::Forbidden) => ( StatusCode::FORBIDDEN, Html(workspace_ui::render_error( crate::i18n::Locale::from_headers(headers), &crate::tr!(crate::i18n::Locale::from_headers(headers), "ecb-forbidden"), )), ) .into_response(), Err(LoadError::Backend(message)) => ( StatusCode::BAD_GATEWAY, Html(workspace_ui::render_error( crate::i18n::Locale::from_headers(headers), &message, )), ) .into_response(), } }