use axum::{ Form, extract::State, http::{HeaderMap, StatusCode}, response::{Html, IntoResponse, Redirect, Response}, }; use crate::{AppState, services::authenticated_request}; use super::{ loader::{LoadError, load_page, load_set_page}, state::{ValidationForm, ValidationSetForm}, ui, }; pub(crate) async fn new_field_validation_page(State(state): State, headers: HeaderMap) -> Response { render_loaded(load_page(state, &headers, ValidationForm::default(), false, None).await) } pub(crate) async fn new_validation_rule_page(State(state): State, headers: HeaderMap) -> Response { render_loaded(load_page(state, &headers, ValidationForm::default(), true, None).await) } pub(crate) async fn new_validation_set_page(State(state): State, headers: HeaderMap) -> Response { render_set_loaded(load_set_page(state, &headers, ValidationSetForm::default(), None).await) } pub(crate) async fn save_field_validation( State(state): State, headers: HeaderMap, Form(form): Form, ) -> Response { let submitted = form.clone(); if let Err(error) = load_page(state.clone(), &headers, submitted.clone(), false, None).await { return render_loaded(Err(error)); } if cross_site(&headers) { return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response(); } if !form.validation_set_name.trim().is_empty() { let request = match form.apply_set_request() { Ok(request) => request, Err(message) => return render_loaded(load_page(state, &headers, submitted, false, Some(message)).await), }; let mut validations = state.validations; let request = match authenticated_request(&headers, request) { Ok(request) => request, Err(_) => return Redirect::to("/login").into_response(), }; return match validations.apply_validation_set(request).await { Ok(response) if response.get_ref().success => Html(ui::render_success(&response.get_ref().message)).into_response(), Ok(response) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(&response.get_ref().message))).into_response(), Err(error) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(error.message()))).into_response(), }; } let request = match form.field_request() { Ok(request) => request, Err(message) => return render_loaded(load_page(state, &headers, submitted, false, Some(message)).await), }; let mut validations = state.validations; let request = match authenticated_request(&headers, request) { Ok(request) => request, Err(_) => return Redirect::to("/login").into_response(), }; match validations.update_field_validation(request).await { Ok(response) if response.get_ref().success => Html(ui::render_success(&response.get_ref().message)).into_response(), Ok(response) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(&response.get_ref().message))).into_response(), Err(error) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(error.message()))).into_response(), } } pub(crate) async fn save_validation_rule( State(state): State, headers: HeaderMap, Form(form): Form, ) -> Response { let submitted = form.clone(); if let Err(error) = load_page(state.clone(), &headers, submitted.clone(), true, None).await { return render_loaded(Err(error)); } if cross_site(&headers) { return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response(); } let request = match form.rule_request() { Ok(request) => request, Err(message) => return render_loaded(load_page(state, &headers, submitted, true, Some(message)).await), }; let mut validations = state.validations; let request = match authenticated_request(&headers, request) { Ok(request) => request, Err(_) => return Redirect::to("/login").into_response(), }; match validations.upsert_validation_rule(request).await { Ok(response) if response.get_ref().success => Html(ui::render_success(&response.get_ref().message)).into_response(), Ok(response) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(&response.get_ref().message))).into_response(), Err(error) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(error.message()))).into_response(), } } pub(crate) async fn save_validation_set( State(state): State, headers: HeaderMap, Form(form): Form, ) -> Response { let submitted = form.clone(); if let Err(error) = load_set_page(state.clone(), &headers, submitted.clone(), None).await { return render_set_loaded(Err(error)); } if cross_site(&headers) { return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response(); } let request = match form.request() { Ok(request) => request, Err(message) => return render_set_loaded(load_set_page(state, &headers, submitted, Some(message)).await), }; let mut validations = state.validations; let request = match authenticated_request(&headers, request) { Ok(request) => request, Err(_) => return Redirect::to("/login").into_response(), }; match validations.upsert_validation_set(request).await { Ok(response) if response.get_ref().success => Html(ui::render_success(&response.get_ref().message)).into_response(), Ok(response) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(&response.get_ref().message))).into_response(), Err(error) => (StatusCode::UNPROCESSABLE_ENTITY, Html(ui::render_error(error.message()))).into_response(), } } fn render_set_loaded( result: Result<(crate::ui::Nav, ValidationSetForm, Option), LoadError>, ) -> Response { match result { Ok((nav, form, error)) => { Html(ui::render_set_page(nav, &form, error.as_deref())).into_response() } Err(LoadError::Unauthenticated) => Redirect::to("/login").into_response(), Err(LoadError::Forbidden) => (StatusCode::FORBIDDEN, Html(ui::render_error("Validation-management permission is required."))).into_response(), Err(LoadError::Backend(message)) => (StatusCode::BAD_GATEWAY, Html(ui::render_error(&message))).into_response(), } } fn render_loaded(result: Result) -> Response { match result { Ok(page) => Html(ui::render_page(&page)).into_response(), Err(LoadError::Unauthenticated) => Redirect::to("/login").into_response(), Err(LoadError::Forbidden) => (StatusCode::FORBIDDEN, Html(ui::render_error("Validation-management permission is required."))).into_response(), Err(LoadError::Backend(message)) => (StatusCode::BAD_GATEWAY, Html(ui::render_error(&message))).into_response(), } } fn cross_site(headers: &HeaderMap) -> bool { headers.get("sec-fetch-site").is_some_and(|value| value == "cross-site") }