web/pages/transaction/
validate.rs1use axum::{Extension, Json, extract::State, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use server::command::{CmdResult, FinanceEntity, account::ListAccounts};
6use sqlx::types::Uuid;
7use std::sync::Arc;
8
9use crate::{
10 AppState, jwt_auth::JWTAuthMiddleware, pages::HtmlTemplate,
11 pages::validation::feedback::ValidationFeedback,
12};
13
14#[derive(Deserialize, Debug)]
15pub struct ValidateAmountRequest {
16 pub amount: Option<String>,
17}
18
19#[derive(Deserialize, Debug)]
20pub struct ValidateAccountRequest {
21 pub account_id: Option<String>,
22}
23
24#[derive(Deserialize, Debug)]
25pub struct ValidateFromAccountRequest {
26 pub from_account: Option<String>,
27}
28
29#[derive(Deserialize, Debug)]
30pub struct ValidateToAccountRequest {
31 pub to_account: Option<String>,
32}
33
34#[derive(Deserialize, Debug)]
35pub struct ValidateNoteRequest {
36 pub note: Option<String>,
37}
38
39#[derive(Debug)]
41pub enum ValidationResult {
42 Success(String),
43 Error(String),
44}
45
46impl ValidationResult {
47 #[must_use]
48 pub fn to_html_response(self) -> impl IntoResponse {
49 match self {
50 ValidationResult::Success(msg) => HtmlTemplate(ValidationFeedback::success(msg)),
51 ValidationResult::Error(msg) => HtmlTemplate(ValidationFeedback::error(msg)),
52 }
53 }
54
55 pub fn to_json_response(
56 self,
57 ) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
58 match self {
59 ValidationResult::Success(msg) => Ok(msg),
60 ValidationResult::Error(msg) => {
61 let error_response = serde_json::json!({
62 "status": "fail",
63 "message": msg,
64 });
65 Err((StatusCode::BAD_REQUEST, Json(error_response)))
66 }
67 }
68 }
69}
70
71#[must_use]
73pub fn validate_amount_logic(amount_str: Option<&str>) -> ValidationResult {
74 let amount_str = match amount_str {
75 Some(s) if !s.trim().is_empty() => s.trim(),
76 _ => return ValidationResult::Error(t!("Amount is required").to_string()),
77 };
78
79 match amount_str.parse::<f64>() {
80 Ok(num) => {
81 if num <= 0.0 {
82 ValidationResult::Error(t!("Amount must be greater than zero").to_string())
83 } else {
84 ValidationResult::Success(t!("Valid amount").to_string())
85 }
86 }
87 Err(_) => ValidationResult::Error(t!("Please enter a valid number").to_string()),
88 }
89}
90
91pub async fn validate_account_logic(
93 user_id: Uuid,
94 account_id_str: Option<&str>,
95 field_name: &str,
96) -> ValidationResult {
97 let account_id_str = match account_id_str {
98 Some(s) if !s.trim().is_empty() => s.trim(),
99 _ => return ValidationResult::Error(format!("{field_name} is required")),
100 };
101
102 let account_uuid = match Uuid::parse_str(account_id_str) {
104 Ok(uuid) => uuid,
105 Err(_) => return ValidationResult::Error(t!("Invalid account ID format").to_string()),
106 };
107
108 match ListAccounts::new().user_id(user_id).run().await {
110 Ok(Some(CmdResult::TaggedEntities { entities, .. })) => {
111 for (entity, _) in entities {
112 if let FinanceEntity::Account(account) = entity
113 && account.id == account_uuid
114 {
115 return ValidationResult::Success(t!("Valid account").to_string());
116 }
117 }
118 ValidationResult::Error(t!("Account not found or not accessible").to_string())
119 }
120 _ => ValidationResult::Error(t!("Error validating account").to_string()),
121 }
122}
123
124#[must_use]
126pub fn validate_note_logic(note: Option<&str>) -> ValidationResult {
127 match note {
128 Some(note_str) if note_str.len() > 500 => {
129 ValidationResult::Error(t!("Note is too long (max 500 characters)").to_string())
130 }
131 _ => ValidationResult::Success(t!("Note is valid").to_string()),
132 }
133}
134
135pub async fn validate_amount_html(Json(form): Json<ValidateAmountRequest>) -> impl IntoResponse {
137 let result = validate_amount_logic(form.amount.as_deref());
138 result.to_html_response()
139}
140
141pub async fn validate_from_account_html(
142 State(_data): State<Arc<AppState>>,
143 Extension(jwt_auth): Extension<JWTAuthMiddleware>,
144 Json(form): Json<ValidateFromAccountRequest>,
145) -> impl IntoResponse {
146 let user = &jwt_auth.user;
147 let result =
148 validate_account_logic(user.id, form.from_account.as_deref(), "Source account").await;
149 result.to_html_response()
150}
151
152pub async fn validate_to_account_html(
153 State(_data): State<Arc<AppState>>,
154 Extension(jwt_auth): Extension<JWTAuthMiddleware>,
155 Json(form): Json<ValidateToAccountRequest>,
156) -> impl IntoResponse {
157 let user = &jwt_auth.user;
158 let result =
159 validate_account_logic(user.id, form.to_account.as_deref(), "Destination account").await;
160 result.to_html_response()
161}
162
163pub async fn validate_note_html(Json(form): Json<ValidateNoteRequest>) -> impl IntoResponse {
164 let result = validate_note_logic(form.note.as_deref());
165 result.to_html_response()
166}
167
168pub async fn validate_amount_json(
170 State(_data): State<Arc<AppState>>,
171 Extension(_jwt_auth): Extension<JWTAuthMiddleware>,
172 Json(form): Json<ValidateAmountRequest>,
173) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
174 let result = validate_amount_logic(form.amount.as_deref());
175 result.to_json_response()
176}
177
178pub async fn validate_from_account_json(
179 State(_data): State<Arc<AppState>>,
180 Extension(jwt_auth): Extension<JWTAuthMiddleware>,
181 Json(form): Json<ValidateAccountRequest>,
182) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
183 let user = &jwt_auth.user;
184 let result = validate_account_logic(user.id, form.account_id.as_deref(), "From account").await;
185 result.to_json_response()
186}
187
188pub async fn validate_to_account_json(
189 State(_data): State<Arc<AppState>>,
190 Extension(jwt_auth): Extension<JWTAuthMiddleware>,
191 Json(form): Json<ValidateAccountRequest>,
192) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
193 let user = &jwt_auth.user;
194 let result = validate_account_logic(user.id, form.account_id.as_deref(), "To account").await;
195 result.to_json_response()
196}
197
198pub async fn validate_note_json(
199 State(_data): State<Arc<AppState>>,
200 Extension(_jwt_auth): Extension<JWTAuthMiddleware>,
201 Json(form): Json<ValidateNoteRequest>,
202) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
203 let result = validate_note_logic(form.note.as_deref());
204 result.to_json_response()
205}