251 lines
8.5 KiB
Rust
251 lines
8.5 KiB
Rust
//! 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<AppState>,
|
|
headers: HeaderMap,
|
|
Query(query): Query<WorkspaceQuery>,
|
|
) -> 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<AppState>,
|
|
headers: HeaderMap,
|
|
Form(form): Form<ConfigurationForm>,
|
|
) -> 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<AppState>,
|
|
headers: HeaderMap,
|
|
Form(form): Form<PreviewForm>,
|
|
) -> 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<AppState>,
|
|
headers: HeaderMap,
|
|
Form(form): Form<EvidenceForm>,
|
|
) -> 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::<i64>() {
|
|
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<super::workspace_state::ExchangeRateWorkspaceState, LoadError>,
|
|
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(),
|
|
}
|
|
}
|