transactions: Add JSON response format

Rocket makes it easy to dispatch requests for the same path to different
methods based on the `Accept` request header via the `format` argument
to the request attribute.

We'll use this JSON response format to populate a dropdown on the Add
Receipt form, which can be used to automatically fill the fields for
receipt data based on an existing transaction.
bugfix/ci-buildah
Dustin 2025-03-11 07:49:34 -05:00
parent c3c866fb8f
commit 1d5bdfe920
1 changed files with 58 additions and 4 deletions

View File

@ -2,11 +2,13 @@ mod config;
mod firefly;
mod receipts;
use chrono::{DateTime, FixedOffset};
use rocket::fairing::{self, AdHoc};
use rocket::form::Form;
use rocket::fs::{FileServer, TempFile};
use rocket::http::Status;
use rocket::response::Redirect;
use rocket::response::{Redirect, Responder};
use rocket::serde::json::Json;
use rocket::tokio::io::{AsyncReadExt, BufReader};
use rocket::{Rocket, State};
use rocket_db_pools::Database as RocketDatabase;
@ -49,7 +51,7 @@ pub struct Transaction {
pub id: String,
pub amount: f64,
pub description: String,
pub date: String,
pub date: DateTime<FixedOffset>,
}
#[derive(rocket::FromForm)]
@ -59,6 +61,27 @@ struct TransactionPostData<'r> {
photo: Vec<TempFile<'r>>,
}
#[derive(Serialize)]
struct ErrorBody {
error: String,
}
#[derive(Responder)]
#[response(status = 500)]
struct ErrorResponse {
error: Json<ErrorBody>,
}
impl ErrorResponse {
pub fn new<S: Into<String>>(error: S) -> Self {
Self {
error: Json(ErrorBody {
error: error.into(),
}),
}
}
}
impl TryFrom<TransactionRead> for Transaction {
type Error = &'static str;
@ -81,7 +104,7 @@ impl TryFrom<TransactionRead> for Transaction {
id: t.id,
amount,
description,
date: date.format("%A, %_d %B %Y").to_string(),
date,
})
}
}
@ -91,7 +114,7 @@ async fn index() -> Redirect {
Redirect::to("/receipts")
}
#[rocket::get("/transactions")]
#[rocket::get("/transactions", format = "html", rank = 2)]
async fn transaction_list(ctx: &State<Context>) -> (Status, Template) {
let result = ctx
.firefly
@ -136,6 +159,36 @@ async fn transaction_list(ctx: &State<Context>) -> (Status, Template) {
}
}
#[rocket::get("/transactions", format = "json")]
async fn transaction_list_json(
ctx: &State<Context>,
) -> Result<Json<Vec<Transaction>>, ErrorResponse> {
let result = ctx
.firefly
.search_transactions(&ctx.config.firefly.search_query)
.await
.map_err(|e| {
error!("Error fetching transaction list: {}", e);
ErrorResponse::new(
"Failed to fetch transaction list from Firefly III",
)
})?;
Ok(Json(
result
.data
.into_iter()
.filter_map(|t| match Transaction::try_from(t) {
Ok(t) => Some(t),
Err(e) => {
error!("Error parsing transaction details: {}", e);
None
},
})
.collect(),
))
}
#[rocket::get("/transactions/<id>")]
async fn get_transaction(id: &str, ctx: &State<Context>) -> Option<Template> {
match ctx.firefly.get_transaction(id).await {
@ -272,6 +325,7 @@ async fn rocket() -> _ {
rocket::routes![
index,
transaction_list,
transaction_list_json,
get_transaction,
update_transaction
],