From b6af889dc582c3efe1dbdf0e84346052f3575f1e Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 8 May 2025 16:47:32 +0200 Subject: [PATCH] Add a route to get a single inbox entry --- .../src/controllers/inbox_controller.rs | 6 ++ .../src/extractors/inbox_entry_extractor.rs | 64 +++++++++++++++++++ moneymgr_backend/src/extractors/mod.rs | 1 + moneymgr_backend/src/main.rs | 4 ++ moneymgr_backend/src/models/inbox.rs | 6 +- .../src/services/inbox_service.rs | 11 +++- 6 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 moneymgr_backend/src/extractors/inbox_entry_extractor.rs diff --git a/moneymgr_backend/src/controllers/inbox_controller.rs b/moneymgr_backend/src/controllers/inbox_controller.rs index fe0e18f..c5a6908 100644 --- a/moneymgr_backend/src/controllers/inbox_controller.rs +++ b/moneymgr_backend/src/controllers/inbox_controller.rs @@ -1,5 +1,6 @@ use crate::controllers::HttpResult; use crate::extractors::auth_extractor::AuthExtractor; +use crate::extractors::inbox_entry_extractor::InboxEntryInPath; use crate::services::inbox_service; use crate::services::inbox_service::UpdateInboxEntryQuery; use actix_web::{HttpResponse, web}; @@ -35,3 +36,8 @@ pub async fn get_list(auth: AuthExtractor, query: web::Query) -> Ok(HttpResponse::Ok().json(list)) } + +/// Get a single inbox entry +pub async fn get_single(entry: InboxEntryInPath) -> HttpResult { + Ok(HttpResponse::Ok().json(entry.as_ref())) +} diff --git a/moneymgr_backend/src/extractors/inbox_entry_extractor.rs b/moneymgr_backend/src/extractors/inbox_entry_extractor.rs new file mode 100644 index 0000000..2a48fa4 --- /dev/null +++ b/moneymgr_backend/src/extractors/inbox_entry_extractor.rs @@ -0,0 +1,64 @@ +use crate::extractors::auth_extractor::AuthExtractor; +use crate::models::inbox::{InboxEntry, InboxEntryID}; +use crate::services::inbox_service; +use actix_web::dev::Payload; +use actix_web::{FromRequest, HttpRequest}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct InboxEntryIdInPath { + inbox_id: InboxEntryID, +} + +#[derive(thiserror::Error, Debug)] +enum InboxEntryExtractorError { + #[error("Current user does not own the entry!")] + UserDoesNotOwnInboxEntry, +} + +pub struct InboxEntryInPath(InboxEntry); + +impl InboxEntryInPath { + pub async fn load_inbox_entry_from_path( + auth: &AuthExtractor, + id: InboxEntryID, + ) -> anyhow::Result { + let entry = inbox_service::get_by_id(id).await?; + + if entry.user_id() != auth.user_id() { + return Err(InboxEntryExtractorError::UserDoesNotOwnInboxEntry.into()); + } + + Ok(Self(entry)) + } +} + +impl FromRequest for InboxEntryInPath { + type Error = actix_web::Error; + type Future = futures_util::future::LocalBoxFuture<'static, Result>; + + fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { + let req = req.clone(); + Box::pin(async move { + let auth = AuthExtractor::extract(&req).await?; + + let entry_id = + actix_web::web::Path::::from_request(&req, &mut Payload::None) + .await? + .inbox_id; + + Self::load_inbox_entry_from_path(&auth, entry_id) + .await + .map_err(|e| { + log::error!("Failed to extract inbox entry ID from URL! {}", e); + actix_web::error::ErrorNotFound("Could not fetch inbox entry information!") + }) + }) + } +} + +impl AsRef for InboxEntryInPath { + fn as_ref(&self) -> &InboxEntry { + &self.0 + } +} diff --git a/moneymgr_backend/src/extractors/mod.rs b/moneymgr_backend/src/extractors/mod.rs index f584e3a..d3c58d6 100644 --- a/moneymgr_backend/src/extractors/mod.rs +++ b/moneymgr_backend/src/extractors/mod.rs @@ -2,5 +2,6 @@ pub mod account_extractor; pub mod auth_extractor; pub mod file_extractor; pub mod file_id_extractor; +pub mod inbox_entry_extractor; pub mod money_session; pub mod movement_extractor; diff --git a/moneymgr_backend/src/main.rs b/moneymgr_backend/src/main.rs index b40d987..b81ea9c 100644 --- a/moneymgr_backend/src/main.rs +++ b/moneymgr_backend/src/main.rs @@ -158,6 +158,10 @@ async fn main() -> std::io::Result<()> { // Inbox controller .route("/api/inbox", web::post().to(inbox_controller::create)) .route("/api/inbox", web::get().to(inbox_controller::get_list)) + .route( + "/api/inbox/{inbox_id}", + web::get().to(inbox_controller::get_single), + ) // Statistics controller .route("/api/stats/global", web::get().to(stats_controller::global)) .route( diff --git a/moneymgr_backend/src/models/inbox.rs b/moneymgr_backend/src/models/inbox.rs index 086d1c3..861e058 100644 --- a/moneymgr_backend/src/models/inbox.rs +++ b/moneymgr_backend/src/models/inbox.rs @@ -5,7 +5,7 @@ use crate::schema::*; use diesel::{Insertable, Queryable}; #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)] -pub struct InboxID(pub i32); +pub struct InboxEntryID(pub i32); /// Single inbox entry information #[derive(Queryable, Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -32,8 +32,8 @@ pub struct InboxEntry { impl InboxEntry { /// Get the ID of the inbox entry - pub fn id(&self) -> InboxID { - InboxID(self.id) + pub fn id(&self) -> InboxEntryID { + InboxEntryID(self.id) } /// The id of the user owning this inbox entry diff --git a/moneymgr_backend/src/services/inbox_service.rs b/moneymgr_backend/src/services/inbox_service.rs index 6ee2d1e..6fd610e 100644 --- a/moneymgr_backend/src/services/inbox_service.rs +++ b/moneymgr_backend/src/services/inbox_service.rs @@ -1,7 +1,7 @@ use crate::connections::db_connection::db; use crate::controllers::server_controller::ServerConstraints; use crate::models::files::FileID; -use crate::models::inbox::{InboxEntry, InboxID, NewInboxEntry}; +use crate::models::inbox::{InboxEntry, InboxEntryID, NewInboxEntry}; use crate::models::movements::MovementID; use crate::models::users::UserID; use crate::schema::inbox; @@ -77,7 +77,7 @@ pub async fn create(user_id: UserID, query: &UpdateInboxEntryQuery) -> anyhow::R } /// Update a inbox entry -pub async fn update(id: InboxID, q: &UpdateInboxEntryQuery) -> anyhow::Result<()> { +pub async fn update(id: InboxEntryID, q: &UpdateInboxEntryQuery) -> anyhow::Result<()> { diesel::update(inbox::dsl::inbox.filter(inbox::dsl::id.eq(id.0))) .set(( inbox::dsl::time_update.eq(time() as i64), @@ -98,3 +98,10 @@ pub async fn get_list_user(user_id: UserID) -> anyhow::Result> { .filter(inbox::dsl::user_id.eq(user_id.0)) .get_results(&mut db()?)?) } + +/// Get a single inbox entry by its ID +pub async fn get_by_id(entry_id: InboxEntryID) -> anyhow::Result { + Ok(inbox::table + .filter(inbox::dsl::id.eq(entry_id.0)) + .get_result(&mut db()?)?) +}