Can create a movement

This commit is contained in:
Pierre HUBERT 2025-04-15 22:39:42 +02:00
parent 5a51dee8b0
commit b4cf6624f7
9 changed files with 160 additions and 0 deletions

View File

@ -5,6 +5,7 @@ use std::error::Error;
pub mod accounts_controller;
pub mod auth_controller;
pub mod files_controller;
pub mod movement_controller;
pub mod server_controller;
pub mod static_controller;
pub mod tokens_controller;

View File

@ -0,0 +1,16 @@
use crate::controllers::HttpResult;
use crate::extractors::auth_extractor::AuthExtractor;
use crate::services::movements_service;
use crate::services::movements_service::UpdateMovementQuery;
use actix_web::{HttpResponse, web};
/// Create a new movement
pub async fn create(auth: AuthExtractor, req: web::Json<UpdateMovementQuery>) -> HttpResult {
if let Some(err) = req.check_error(auth.user_id()).await? {
return Ok(HttpResponse::BadRequest().json(err));
}
movements_service::create(&req).await?;
Ok(HttpResponse::Created().finish())
}

View File

@ -41,6 +41,7 @@ pub struct ServerConstraints {
pub token_ip_net: LenConstraints,
pub token_max_inactivity: LenConstraints,
pub account_name: LenConstraints,
pub movement_label: LenConstraints,
}
impl Default for ServerConstraints {
@ -50,6 +51,7 @@ impl Default for ServerConstraints {
token_ip_net: LenConstraints::max_only(44),
token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365),
account_name: LenConstraints::not_empty(50),
movement_label: LenConstraints::not_empty(200),
}
}
}

View File

@ -129,6 +129,8 @@ async fn main() -> std::io::Result<()> {
"/api/file/{file_id}",
web::delete().to(files_controller::delete),
)
// Movement controller
.route("/api/movement", web::post().to(movement_controller::create))
// Static assets
.route("/", web::get().to(static_controller::root_index))
.route(

View File

@ -1,4 +1,5 @@
pub mod accounts;
pub mod files;
pub mod movements;
pub mod tokens;
pub mod users;

View File

@ -0,0 +1,47 @@
use crate::models::accounts::AccountID;
use crate::models::files::FileID;
use crate::schema::*;
use diesel::{Insertable, Queryable};
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct MovementID(pub i32);
#[derive(Queryable, Debug, Clone, serde::Serialize)]
pub struct Movement {
id: i32,
account_id: i32,
pub time: i64,
pub label: String,
file_id: Option<i32>,
pub amount: f32,
pub checked: bool,
pub time_create: i64,
pub time_update: i64,
}
impl Movement {
pub fn id(&self) -> MovementID {
MovementID(self.id)
}
pub fn account_id(&self) -> AccountID {
AccountID(self.account_id)
}
pub fn file_id(&self) -> Option<FileID> {
self.file_id.map(FileID)
}
}
#[derive(Insertable, Debug)]
#[diesel(table_name = movements)]
pub struct NewMovement<'a> {
pub account_id: i32,
pub time: i64,
pub label: &'a str,
pub file_id: Option<i32>,
pub amount: f32,
pub checked: bool,
pub time_create: i64,
pub time_update: i64,
}

View File

@ -1,4 +1,5 @@
pub mod accounts_service;
pub mod files_service;
pub mod movements_service;
pub mod tokens_service;
pub mod users_service;

View File

@ -0,0 +1,89 @@
use crate::connections::db_connection::db;
use crate::controllers::server_controller::ServerConstraints;
use crate::models::accounts::AccountID;
use crate::models::files::FileID;
use crate::models::movements::{Movement, MovementID, NewMovement};
use crate::models::users::UserID;
use crate::schema::movements;
use crate::services::{accounts_service, files_service};
use crate::utils::time_utils::time;
use diesel::RunQueryDsl;
use diesel::prelude::*;
#[derive(serde::Deserialize)]
pub struct UpdateMovementQuery {
pub account_id: AccountID,
pub time: u64,
pub label: String,
pub file_id: Option<FileID>,
pub amount: f32,
pub checked: bool,
}
impl UpdateMovementQuery {
pub async fn check_error(&self, user_id: UserID) -> anyhow::Result<Option<&'static str>> {
let constraints = ServerConstraints::default();
// Check movement label
if !constraints.movement_label.check_str(&self.label) {
return Ok(Some("Invalid movement label length!"));
}
// Check the account
let account = accounts_service::get_by_id(self.account_id).await?;
if account.user_id() != user_id {
return Ok(Some("The user does not own the account!"));
}
// Check the file
if let Some(file_id) = self.file_id {
let file = files_service::get_file_with_id(file_id)?;
if file.user_id() != user_id {
return Ok(Some("The user does not own the referenced file!"));
}
}
// TODO : check for conflict
Ok(None)
}
}
/// Create a new movement
pub async fn create(query: &UpdateMovementQuery) -> anyhow::Result<Movement> {
let new_account = NewMovement {
account_id: query.account_id.0,
time: query.time as i64,
label: &query.label,
file_id: query.file_id.map(|f| f.0),
amount: query.amount,
checked: query.checked,
time_create: time() as i64,
time_update: time() as i64,
};
let res: Movement = diesel::insert_into(movements::table)
.values(&new_account)
.get_result(&mut db()?)?;
update(res.id(), query).await?;
Ok(res)
}
/// Update a movement
pub async fn update(id: MovementID, q: &UpdateMovementQuery) -> anyhow::Result<()> {
diesel::update(movements::dsl::movements.filter(movements::dsl::id.eq(id.0)))
.set((
movements::dsl::time_update.eq(time() as i64),
movements::dsl::account_id.eq(q.account_id.0),
movements::dsl::time.eq(q.time as i64),
movements::dsl::label.eq(&q.label),
movements::dsl::file_id.eq(&q.file_id.map(|f| f.0)),
movements::dsl::amount.eq(q.amount),
))
.execute(&mut db()?)?;
Ok(())
}

View File

@ -17,6 +17,7 @@ export interface ServerConstraints {
token_name: LenConstraint;
token_ip_net: LenConstraint;
token_max_inactivity: LenConstraint;
movement_label: LenConstraint;
}
export interface LenConstraint {