Add route to create inbox entries
This commit is contained in:
parent
68bc15cccc
commit
b6281be349
@ -67,7 +67,10 @@ CREATE TABLE inbox
|
||||
id SERIAL PRIMARY KEY,
|
||||
file_id INTEGER NOT NULL REFERENCES files ON DELETE RESTRICT,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
account_id INTEGER REFERENCES accounts ON DELETE CASCADE,
|
||||
movement_id INTEGER NULL REFERENCES movements ON DELETE CASCADE,
|
||||
time BIGINT NOT NULL,
|
||||
label VARCHAR(200) NULL,
|
||||
amount REAL NULL,
|
||||
time_create BIGINT NOT NULL,
|
||||
time_update BIGINT NOT NULL
|
||||
);
|
16
moneymgr_backend/src/controllers/inbox_controller.rs
Normal file
16
moneymgr_backend/src/controllers/inbox_controller.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use crate::controllers::HttpResult;
|
||||
use crate::extractors::auth_extractor::AuthExtractor;
|
||||
use crate::services::inbox_service;
|
||||
use crate::services::inbox_service::UpdateInboxEntryQuery;
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
/// Create a new inbox entry
|
||||
pub async fn create(auth: AuthExtractor, req: web::Json<UpdateInboxEntryQuery>) -> HttpResult {
|
||||
if let Some(err) = req.check_error(auth.user_id()).await? {
|
||||
return Ok(HttpResponse::BadRequest().json(err));
|
||||
}
|
||||
|
||||
inbox_service::create(auth.user_id(), &req).await?;
|
||||
|
||||
Ok(HttpResponse::Created().finish())
|
||||
}
|
@ -8,6 +8,7 @@ pub mod accounts_controller;
|
||||
pub mod auth_controller;
|
||||
pub mod backup_controller;
|
||||
pub mod files_controller;
|
||||
pub mod inbox_controller;
|
||||
pub mod movement_controller;
|
||||
pub mod server_controller;
|
||||
pub mod static_controller;
|
||||
|
@ -42,6 +42,7 @@ pub struct ServerConstraints {
|
||||
pub token_max_inactivity: LenConstraints,
|
||||
pub account_name: LenConstraints,
|
||||
pub movement_label: LenConstraints,
|
||||
pub inbox_entry_label: LenConstraints,
|
||||
pub file_allowed_types: &'static [&'static str],
|
||||
}
|
||||
|
||||
@ -53,6 +54,7 @@ impl Default for ServerConstraints {
|
||||
token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365),
|
||||
account_name: LenConstraints::not_empty(50),
|
||||
movement_label: LenConstraints::not_empty(200),
|
||||
inbox_entry_label: LenConstraints::not_empty(200),
|
||||
file_allowed_types: &[
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
|
@ -155,6 +155,8 @@ async fn main() -> std::io::Result<()> {
|
||||
"/api/movement/{movement_id}",
|
||||
web::delete().to(movement_controller::delete),
|
||||
)
|
||||
// Inbox controller
|
||||
.route("/api/inbox", web::post().to(inbox_controller::create))
|
||||
// Statistics controller
|
||||
.route("/api/stats/global", web::get().to(stats_controller::global))
|
||||
.route(
|
||||
|
66
moneymgr_backend/src/models/inbox.rs
Normal file
66
moneymgr_backend/src/models/inbox.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use crate::models::files::FileID;
|
||||
use crate::models::movements::MovementID;
|
||||
use crate::models::users::UserID;
|
||||
use crate::schema::*;
|
||||
use diesel::{Insertable, Queryable};
|
||||
|
||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
pub struct InboxID(pub i32);
|
||||
|
||||
/// Single inbox entry information
|
||||
#[derive(Queryable, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Inbox {
|
||||
/// The ID of the inbox entry
|
||||
id: i32,
|
||||
/// ID of the file attached to this inbox entry
|
||||
file_id: i32,
|
||||
/// The ID this inbox entry belongs to
|
||||
user_id: i32,
|
||||
/// The ID of the movement this inbox entry is attached to (if any)
|
||||
movement_id: Option<i32>,
|
||||
/// The time this inbox entry happened
|
||||
pub time: i64,
|
||||
/// The label associated to this inbox entry
|
||||
pub label: Option<String>,
|
||||
/// The amount of the inbox entry
|
||||
pub amount: Option<f32>,
|
||||
/// The time this inbox entry was created in the database
|
||||
pub time_create: i64,
|
||||
/// The time this inbox entry was last updated in the database
|
||||
pub time_update: i64,
|
||||
}
|
||||
|
||||
impl Inbox {
|
||||
/// Get the ID of the inbox entry
|
||||
pub fn id(&self) -> InboxID {
|
||||
InboxID(self.id)
|
||||
}
|
||||
|
||||
/// The id of the user owning this inbox entry
|
||||
pub fn user_id(&self) -> UserID {
|
||||
UserID(self.user_id)
|
||||
}
|
||||
|
||||
/// The ID of the movement attached to the movement (if any)
|
||||
pub fn movement_id(&self) -> Option<MovementID> {
|
||||
self.movement_id.map(MovementID)
|
||||
}
|
||||
|
||||
/// The ID of the file attached to the movement, if any
|
||||
pub fn file_id(&self) -> FileID {
|
||||
FileID(self.file_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable, Debug)]
|
||||
#[diesel(table_name = inbox)]
|
||||
pub struct NewInboxEntry<'a> {
|
||||
pub file_id: i32,
|
||||
pub user_id: i32,
|
||||
pub movement_id: Option<i32>,
|
||||
pub time: i64,
|
||||
pub label: Option<&'a str>,
|
||||
pub amount: Option<f32>,
|
||||
pub time_create: i64,
|
||||
pub time_update: i64,
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod accounts;
|
||||
pub mod files;
|
||||
pub mod inbox;
|
||||
pub mod movements;
|
||||
pub mod tokens;
|
||||
pub mod users;
|
||||
|
@ -35,7 +35,11 @@ diesel::table! {
|
||||
id -> Int4,
|
||||
file_id -> Int4,
|
||||
user_id -> Int4,
|
||||
account_id -> Nullable<Int4>,
|
||||
movement_id -> Nullable<Int4>,
|
||||
time -> Int8,
|
||||
#[max_length = 200]
|
||||
label -> Nullable<Varchar>,
|
||||
amount -> Nullable<Float4>,
|
||||
time_create -> Int8,
|
||||
time_update -> Int8,
|
||||
}
|
||||
@ -94,8 +98,8 @@ diesel::table! {
|
||||
|
||||
diesel::joinable!(accounts -> users (user_id));
|
||||
diesel::joinable!(files -> users (user_id));
|
||||
diesel::joinable!(inbox -> accounts (account_id));
|
||||
diesel::joinable!(inbox -> files (file_id));
|
||||
diesel::joinable!(inbox -> movements (movement_id));
|
||||
diesel::joinable!(inbox -> users (user_id));
|
||||
diesel::joinable!(movements -> accounts (account_id));
|
||||
diesel::joinable!(movements -> files (file_id));
|
||||
|
93
moneymgr_backend/src/services/inbox_service.rs
Normal file
93
moneymgr_backend/src/services/inbox_service.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::connections::db_connection::db;
|
||||
use crate::controllers::server_controller::ServerConstraints;
|
||||
use crate::models::files::FileID;
|
||||
use crate::models::inbox::{Inbox, InboxID, NewInboxEntry};
|
||||
use crate::models::movements::MovementID;
|
||||
use crate::models::users::UserID;
|
||||
use crate::schema::inbox;
|
||||
use crate::services::{accounts_service, files_service, movements_service};
|
||||
use crate::utils::time_utils::time;
|
||||
use diesel::prelude::*;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct UpdateInboxEntryQuery {
|
||||
pub file_id: FileID,
|
||||
pub movement_id: Option<MovementID>,
|
||||
pub time: u64,
|
||||
pub label: Option<String>,
|
||||
pub amount: Option<f32>,
|
||||
}
|
||||
|
||||
impl UpdateInboxEntryQuery {
|
||||
pub async fn check_error(&self, user_id: UserID) -> anyhow::Result<Option<&'static str>> {
|
||||
let constraints = ServerConstraints::default();
|
||||
|
||||
// Check inbox entry label
|
||||
if let Some(label) = &self.label {
|
||||
if !constraints.inbox_entry_label.check_str(label) {
|
||||
return Ok(Some("Invalid inbox entry label length!"));
|
||||
}
|
||||
}
|
||||
|
||||
// Check the referenced movement
|
||||
if let Some(movement_id) = self.movement_id {
|
||||
let Ok(movement) = movements_service::get_by_id(movement_id).await else {
|
||||
return Ok(Some("Movement not found"));
|
||||
};
|
||||
|
||||
let account = accounts_service::get_by_id(movement.account_id()).await?;
|
||||
|
||||
if account.user_id() != user_id {
|
||||
return Ok(Some("The account does not own this movement!"));
|
||||
}
|
||||
}
|
||||
|
||||
// Check the file
|
||||
let Ok(file) = files_service::get_file_with_id(self.file_id) else {
|
||||
return Ok(Some("The account does not own this file!"));
|
||||
};
|
||||
if file.user_id() != user_id {
|
||||
return Ok(Some("The user does not own the referenced file!"));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new inbox entry
|
||||
pub async fn create(user_id: UserID, query: &UpdateInboxEntryQuery) -> anyhow::Result<Inbox> {
|
||||
let new_entry = NewInboxEntry {
|
||||
time: query.time as i64,
|
||||
label: query.label.as_deref(),
|
||||
file_id: query.file_id.0,
|
||||
user_id: user_id.0,
|
||||
amount: query.amount,
|
||||
time_create: time() as i64,
|
||||
time_update: time() as i64,
|
||||
movement_id: None,
|
||||
};
|
||||
|
||||
let res: Inbox = diesel::insert_into(inbox::table)
|
||||
.values(&new_entry)
|
||||
.get_result(&mut db()?)?;
|
||||
|
||||
update(res.id(), query).await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Update a inbox entry
|
||||
pub async fn update(id: InboxID, 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),
|
||||
inbox::dsl::movement_id.eq(q.movement_id.map(|m| m.0)),
|
||||
inbox::dsl::time.eq(q.time as i64),
|
||||
inbox::dsl::label.eq(&q.label),
|
||||
inbox::dsl::file_id.eq(&q.file_id.0),
|
||||
inbox::dsl::amount.eq(q.amount),
|
||||
))
|
||||
.execute(&mut db()?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod accounts_service;
|
||||
pub mod files_service;
|
||||
pub mod inbox_service;
|
||||
pub mod movements_service;
|
||||
pub mod tokens_service;
|
||||
pub mod users_service;
|
||||
|
Loading…
x
Reference in New Issue
Block a user