diff --git a/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/down.sql b/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/down.sql index d9ddc46..9937a64 100644 --- a/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/down.sql +++ b/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/down.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS inbox; -DROP TABLE IF EXISTS movement; -DROP TABLE IF EXISTS account; -DROP TABLE IF EXISTS attachment; -DROP TABLE IF EXISTS token; +DROP TABLE IF EXISTS movements; +DROP TABLE IF EXISTS accounts; +DROP TABLE IF EXISTS attachments; +DROP TABLE IF EXISTS tokens; DROP TABLE IF EXISTS users; \ No newline at end of file diff --git a/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/up.sql b/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/up.sql index 04107cd..ca8bd49 100644 --- a/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/up.sql +++ b/moneymgr_backend/migrations/2025-03-17-173101_initial_structure/up.sql @@ -7,7 +7,7 @@ CREATE TABLE users time_update BIGINT NOT NULL ); -CREATE TABLE token +CREATE TABLE tokens ( id SERIAL PRIMARY KEY, name VARCHAR(150) NOT NULL, @@ -25,7 +25,7 @@ CREATE TABLE token right_auth BOOLEAN NOT NULL DEFAULT false ); -CREATE TABLE attachment +CREATE TABLE attachments ( id SERIAL PRIMARY KEY, time_create BIGINT NOT NULL, @@ -35,7 +35,7 @@ CREATE TABLE attachment user_id INTEGER NOT NULL REFERENCES users ON DELETE SET NULL ); -CREATE TABLE account +CREATE TABLE accounts ( id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, @@ -45,13 +45,13 @@ CREATE TABLE account default_account BOOLEAN NOT NULL DEFAULT false ); -CREATE TABLE movement +CREATE TABLE movements ( id SERIAL PRIMARY KEY, - account_id INTEGER NOT NULL REFERENCES account ON DELETE CASCADE, + account_id INTEGER NOT NULL REFERENCES accounts ON DELETE CASCADE, time BIGINT NOT NULL, label VARCHAR(200) NOT NULL, - attachment_id INT REFERENCES attachment ON DELETE SET NULL, + attachment_id INT REFERENCES attachments ON DELETE SET NULL, amount REAL NOT NULL, checked BOOLEAN NOT NULL DEFAULT false, time_create BIGINT NOT NULL, @@ -61,9 +61,9 @@ CREATE TABLE movement CREATE TABLE inbox ( id SERIAL PRIMARY KEY, - attachment_id INTEGER NOT NULL REFERENCES attachment ON DELETE CASCADE, + attachment_id INTEGER NOT NULL REFERENCES attachments ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, - account_id INTEGER REFERENCES account ON DELETE CASCADE, + account_id INTEGER REFERENCES accounts ON DELETE CASCADE, time_create BIGINT NOT NULL, time_update BIGINT NOT NULL ); \ No newline at end of file diff --git a/moneymgr_backend/src/controllers/accounts_controller.rs b/moneymgr_backend/src/controllers/accounts_controller.rs new file mode 100644 index 0000000..c6b1787 --- /dev/null +++ b/moneymgr_backend/src/controllers/accounts_controller.rs @@ -0,0 +1,35 @@ +use crate::controllers::HttpResult; +use crate::controllers::server_controller::ServerConstraints; +use crate::extractors::auth_extractor::AuthExtractor; +use crate::services::accounts_service; +use crate::services::accounts_service::UpdateAccountQuery; +use actix_web::{HttpResponse, web}; + +#[derive(serde::Deserialize)] +pub struct CreateAccountRequest { + name: String, +} + +/// Create a new account +pub async fn create(auth: AuthExtractor, req: web::Json) -> HttpResult { + let constraints = ServerConstraints::default(); + + if !constraints.account_name.check_str(&req.name) { + return Ok(HttpResponse::BadRequest().json("Invalid account name length!")); + } + + accounts_service::create( + auth.user_id(), + &UpdateAccountQuery { + name: req.name.clone(), + }, + ) + .await?; + + Ok(HttpResponse::Created().finish()) +} + +/// Get the list of accounts of the user +pub async fn get_list(auth: AuthExtractor) -> HttpResult { + Ok(HttpResponse::Ok().json(accounts_service::get_list_user(auth.user_id()).await?)) +} diff --git a/moneymgr_backend/src/controllers/mod.rs b/moneymgr_backend/src/controllers/mod.rs index 9b6f8a7..90ee21e 100644 --- a/moneymgr_backend/src/controllers/mod.rs +++ b/moneymgr_backend/src/controllers/mod.rs @@ -2,6 +2,7 @@ use actix_web::http::StatusCode; use actix_web::{HttpResponse, ResponseError}; use std::error::Error; +pub mod accounts_controller; pub mod auth_controller; pub mod server_controller; pub mod static_controller; diff --git a/moneymgr_backend/src/controllers/server_controller.rs b/moneymgr_backend/src/controllers/server_controller.rs index 22683ee..319e811 100644 --- a/moneymgr_backend/src/controllers/server_controller.rs +++ b/moneymgr_backend/src/controllers/server_controller.rs @@ -39,6 +39,7 @@ pub struct ServerConstraints { pub token_name: LenConstraints, pub token_ip_net: LenConstraints, pub token_max_inactivity: LenConstraints, + pub account_name: LenConstraints, } impl Default for ServerConstraints { @@ -47,6 +48,7 @@ impl Default for ServerConstraints { token_name: LenConstraints::new(5, 255), token_ip_net: LenConstraints::max_only(44), token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365), + account_name: LenConstraints::not_empty(50), } } } diff --git a/moneymgr_backend/src/extractors/auth_extractor.rs b/moneymgr_backend/src/extractors/auth_extractor.rs index 9351269..d714bc6 100644 --- a/moneymgr_backend/src/extractors/auth_extractor.rs +++ b/moneymgr_backend/src/extractors/auth_extractor.rs @@ -141,10 +141,10 @@ impl FromRequest for AuthExtractor { // Check for authorization let uri = req.uri().to_string(); - let authorized = (uri.starts_with("/api/account/") && token.right_account) - || (uri.starts_with("/api/movement/") && token.right_movement) + let authorized = (uri.starts_with("/api/accounts/") && token.right_account) + || (uri.starts_with("/api/movements/") && token.right_movement) || (uri.starts_with("/api/inbox/") && token.right_inbox) - || (uri.starts_with("/api/attachment/") && token.right_attachment) + || (uri.starts_with("/api/attachments/") && token.right_attachment) || (uri.starts_with("/api/auth/") && token.right_auth); if !authorized { diff --git a/moneymgr_backend/src/main.rs b/moneymgr_backend/src/main.rs index 8cb0e37..e79167c 100644 --- a/moneymgr_backend/src/main.rs +++ b/moneymgr_backend/src/main.rs @@ -100,6 +100,19 @@ async fn main() -> std::io::Result<()> { "/api/tokens/{id}", web::delete().to(tokens_controller::delete), ) + // Accounts controller + .route("/api/accounts", web::post().to(accounts_controller::create)) + .route( + "/api/accounts/list", + web::get().to(accounts_controller::get_list), + ) + // TODO : update account + //TODO + /*.route( + "/api/accounts/{id}", + web::delete().to(accounts_controller::delete), + )*/ + // TODO : set as default // Static assets .route("/", web::get().to(static_controller::root_index)) .route( diff --git a/moneymgr_backend/src/models/accounts.rs b/moneymgr_backend/src/models/accounts.rs new file mode 100644 index 0000000..e47fa71 --- /dev/null +++ b/moneymgr_backend/src/models/accounts.rs @@ -0,0 +1,35 @@ +use crate::models::users::UserID; +use crate::schema::*; +use diesel::prelude::*; + +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)] +pub struct AccountID(pub i32); + +#[derive(Queryable, Debug, Clone, serde::Serialize)] +pub struct Account { + id: i32, + pub name: String, + user_id: i32, + pub time_create: i64, + pub time_update: i64, + pub default_account: bool, +} + +impl Account { + pub fn id(&self) -> AccountID { + AccountID(self.id) + } + + pub fn user_id(&self) -> UserID { + UserID(self.user_id) + } +} + +#[derive(Insertable)] +#[diesel(table_name = accounts)] +pub struct NewAccount<'a> { + pub name: &'a str, + pub user_id: i32, + pub time_create: i64, + pub time_update: i64, +} diff --git a/moneymgr_backend/src/models/mod.rs b/moneymgr_backend/src/models/mod.rs index bf3b714..626d891 100644 --- a/moneymgr_backend/src/models/mod.rs +++ b/moneymgr_backend/src/models/mod.rs @@ -1,2 +1,3 @@ +pub mod accounts; pub mod tokens; pub mod users; diff --git a/moneymgr_backend/src/models/tokens.rs b/moneymgr_backend/src/models/tokens.rs index 6c9f28b..44a1470 100644 --- a/moneymgr_backend/src/models/tokens.rs +++ b/moneymgr_backend/src/models/tokens.rs @@ -63,7 +63,7 @@ impl Token { } #[derive(Insertable)] -#[diesel(table_name = token)] +#[diesel(table_name = tokens)] pub struct NewToken<'a> { pub name: &'a str, pub user_id: i32, diff --git a/moneymgr_backend/src/schema.rs b/moneymgr_backend/src/schema.rs index f8cb76c..b830fc1 100644 --- a/moneymgr_backend/src/schema.rs +++ b/moneymgr_backend/src/schema.rs @@ -1,7 +1,7 @@ // @generated automatically by Diesel CLI. diesel::table! { - account (id) { + accounts (id) { id -> Int4, #[max_length = 50] name -> Varchar, @@ -13,7 +13,7 @@ diesel::table! { } diesel::table! { - attachment (id) { + attachments (id) { id -> Int4, time_create -> Int8, #[max_length = 150] @@ -37,7 +37,7 @@ diesel::table! { } diesel::table! { - movement (id) { + movements (id) { id -> Int4, account_id -> Int4, time -> Int8, @@ -52,7 +52,7 @@ diesel::table! { } diesel::table! { - token (id) { + tokens (id) { id -> Int4, #[max_length = 150] name -> Varchar, @@ -85,13 +85,20 @@ diesel::table! { } } -diesel::joinable!(account -> users (user_id)); -diesel::joinable!(attachment -> users (user_id)); -diesel::joinable!(inbox -> account (account_id)); -diesel::joinable!(inbox -> attachment (attachment_id)); +diesel::joinable!(accounts -> users (user_id)); +diesel::joinable!(attachments -> users (user_id)); +diesel::joinable!(inbox -> accounts (account_id)); +diesel::joinable!(inbox -> attachments (attachment_id)); diesel::joinable!(inbox -> users (user_id)); -diesel::joinable!(movement -> account (account_id)); -diesel::joinable!(movement -> attachment (attachment_id)); -diesel::joinable!(token -> users (user_id)); +diesel::joinable!(movements -> accounts (account_id)); +diesel::joinable!(movements -> attachments (attachment_id)); +diesel::joinable!(tokens -> users (user_id)); -diesel::allow_tables_to_appear_in_same_query!(account, attachment, inbox, movement, token, users,); +diesel::allow_tables_to_appear_in_same_query!( + accounts, + attachments, + inbox, + movements, + tokens, + users, +); diff --git a/moneymgr_backend/src/services/accounts_service.rs b/moneymgr_backend/src/services/accounts_service.rs new file mode 100644 index 0000000..8884904 --- /dev/null +++ b/moneymgr_backend/src/services/accounts_service.rs @@ -0,0 +1,64 @@ +use crate::connections::db_connection::db; +use crate::models::accounts::{Account, AccountID, NewAccount}; +use crate::models::users::UserID; +use crate::schema::accounts; +use crate::utils::time_utils::time; +use diesel::RunQueryDsl; +use diesel::prelude::*; + +#[derive(serde::Deserialize)] +pub struct UpdateAccountQuery { + pub name: String, +} + +/// Create a new account +pub async fn create(user_id: UserID, query: &UpdateAccountQuery) -> anyhow::Result { + let new_account = NewAccount { + name: query.name.as_str(), + user_id: user_id.0, + time_create: time() as i64, + time_update: time() as i64, + }; + + let res: Account = diesel::insert_into(accounts::table) + .values(&new_account) + .get_result(&mut db()?)?; + + update(res.id(), query).await?; + + Ok(res) +} + +/// Update an account +pub async fn update(id: AccountID, q: &UpdateAccountQuery) -> anyhow::Result<()> { + diesel::update(accounts::dsl::accounts.filter(accounts::dsl::id.eq(id.0))) + .set(( + accounts::dsl::time_update.eq(time() as i64), + accounts::dsl::name.eq(&q.name), + )) + .execute(&mut db()?)?; + + Ok(()) +} + +/// Get a single account by its id +pub async fn get_by_id(account_id: AccountID) -> anyhow::Result { + Ok(accounts::table + .filter(accounts::dsl::id.eq(account_id.0)) + .get_result(&mut db()?)?) +} + +/// Get the accounts of a user +pub async fn get_list_user(id: UserID) -> anyhow::Result> { + Ok(accounts::table + .filter(accounts::dsl::user_id.eq(id.0)) + .get_results(&mut db()?)?) +} + +/// Delete an account +pub async fn delete(id: AccountID) -> anyhow::Result<()> { + diesel::delete(accounts::dsl::accounts.filter(accounts::dsl::id.eq(id.0))) + .execute(&mut db()?)?; + + Ok(()) +} diff --git a/moneymgr_backend/src/services/mod.rs b/moneymgr_backend/src/services/mod.rs index ff9dfc0..2539ab1 100644 --- a/moneymgr_backend/src/services/mod.rs +++ b/moneymgr_backend/src/services/mod.rs @@ -1,2 +1,3 @@ +pub mod accounts_service; pub mod tokens_service; pub mod users_service; diff --git a/moneymgr_backend/src/services/tokens_service.rs b/moneymgr_backend/src/services/tokens_service.rs index b4c5108..33a9c4c 100644 --- a/moneymgr_backend/src/services/tokens_service.rs +++ b/moneymgr_backend/src/services/tokens_service.rs @@ -4,7 +4,7 @@ use crate::connections::db_connection::db; use crate::constants; use crate::models::tokens::{NewToken, Token, TokenID}; use crate::models::users::UserID; -use crate::schema::token; +use crate::schema::tokens; use crate::utils::rand_utils::rand_string; use crate::utils::time_utils::time; @@ -41,7 +41,7 @@ pub async fn create(new_token: NewTokenInfo) -> anyhow::Result { right_attachment: new_token.right_attachment, }; - let res = diesel::insert_into(token::table) + let res = diesel::insert_into(tokens::table) .values(&t) .get_result(&mut db()?)?; @@ -50,36 +50,36 @@ pub async fn create(new_token: NewTokenInfo) -> anyhow::Result { /// Get a single token by its id pub async fn get_by_id(token_id: TokenID) -> anyhow::Result { - Ok(token::table - .filter(token::dsl::id.eq(token_id.0)) + Ok(tokens::table + .filter(tokens::dsl::id.eq(token_id.0)) .get_result(&mut db()?)?) } /// Get a single token by its name pub fn get_by_name(name: &str) -> anyhow::Result { - Ok(token::table - .filter(token::dsl::name.eq(name)) + Ok(tokens::table + .filter(tokens::dsl::name.eq(name)) .get_result(&mut db()?)?) } /// Get a single token by its value pub async fn get_by_value(value: &str) -> anyhow::Result { - Ok(token::table - .filter(token::dsl::token_value.eq(value)) + Ok(tokens::table + .filter(tokens::dsl::token_value.eq(value)) .get_result(&mut db()?)?) } /// Get the token of a user pub async fn get_list_user(id: UserID) -> anyhow::Result> { - Ok(token::table - .filter(token::dsl::user_id.eq(id.0)) + Ok(tokens::table + .filter(tokens::dsl::user_id.eq(id.0)) .get_results(&mut db()?)?) } /// Update last used value of a token pub async fn update_time_used(token: &Token) -> anyhow::Result<()> { - diesel::update(token::dsl::token.filter(token::dsl::id.eq(token.id().0))) - .set(token::dsl::time_used.eq(time() as i64)) + diesel::update(tokens::dsl::tokens.filter(tokens::dsl::id.eq(token.id().0))) + .set(tokens::dsl::time_used.eq(time() as i64)) .execute(&mut db()?)?; Ok(()) } @@ -87,10 +87,10 @@ pub async fn update_time_used(token: &Token) -> anyhow::Result<()> { /// Delete the token of a user pub async fn delete(user_id: UserID, token_id: TokenID) -> anyhow::Result<()> { diesel::delete( - token::dsl::token.filter( - token::dsl::id + tokens::dsl::tokens.filter( + tokens::dsl::id .eq(token_id.0) - .and(token::dsl::user_id.eq(user_id.0)), + .and(tokens::dsl::user_id.eq(user_id.0)), ), ) .execute(&mut db()?)?;