diff options
author | Joris Guyonvarch | 2025-08-30 09:32:27 +0200 |
---|---|---|
committer | Joris Guyonvarch | 2025-08-30 09:32:27 +0200 |
commit | 2a6bcee45086bca9128489de19908984ea1be0da (patch) | |
tree | 2e5e1b0e50109385b5d3daaabed00efb450f4b62 | |
parent | 164538a57a5b7c19c993862a6badc85fd723380e (diff) |
Remove weekly report jobmain
-rw-r--r-- | README.md | 1 | ||||
-rwxr-xr-x | bin/dev-server | 2 | ||||
-rw-r--r-- | config.json | 1 | ||||
-rw-r--r-- | sql/fixtures.sql | 3 | ||||
-rw-r--r-- | src/db/incomes.rs | 61 | ||||
-rw-r--r-- | src/db/jobs.rs | 1 | ||||
-rw-r--r-- | src/db/migrations/06-remove-weekly-report-job.sql | 1 | ||||
-rw-r--r-- | src/db/mod.rs | 1 | ||||
-rw-r--r-- | src/db/payments.rs | 56 | ||||
-rw-r--r-- | src/db/utils.rs | 12 | ||||
-rw-r--r-- | src/jobs/mod.rs | 16 | ||||
-rw-r--r-- | src/jobs/weekly_report.rs | 65 | ||||
-rw-r--r-- | src/mail.rs | 107 | ||||
-rw-r--r-- | src/main.rs | 7 | ||||
-rw-r--r-- | src/model/config.rs | 4 | ||||
-rw-r--r-- | src/model/job.rs | 1 | ||||
-rw-r--r-- | src/model/mod.rs | 1 | ||||
-rw-r--r-- | src/model/report.rs | 40 | ||||
-rw-r--r-- | templates/report/list.j2 | 14 | ||||
-rw-r--r-- | templates/report/report.j2 | 50 |
20 files changed, 5 insertions, 439 deletions
@@ -3,7 +3,6 @@ - Pay according to your income - Configure monthly payments - View statistics -- Get weekly activity by email # Getting started diff --git a/bin/dev-server b/bin/dev-server index 197b94c..de05d29 100755 --- a/bin/dev-server +++ b/bin/dev-server @@ -7,8 +7,6 @@ CMD=${1:-run} export RUST_LOG="budget=info" export AUTH_SECRET="pMkW8PR7bjhc24bbvZLIEHfcooLzrGIh" export DB_PATH="database.db" -export MAILS_MOCK="true" -export MAILS_FROM="budget@localhost" export SECURE_COOKIES="false" export SOCKET_ADDRESS="0.0.0.0:3000" diff --git a/config.json b/config.json index 6be6ab2..20bad2e 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,4 @@ { "secure_cookies": false, - "mock_mails": true, "auth_secret": "1QAZa8RSgogsakXSa0hgEeyA7xqnvo91" } diff --git a/sql/fixtures.sql b/sql/fixtures.sql index 74e91b7..913c3ba 100644 --- a/sql/fixtures.sql +++ b/sql/fixtures.sql @@ -42,5 +42,4 @@ VALUES INSERT INTO jobs(name) VALUES - ('MonthlyPayment'), - ('WeeklyReport'); + ('MonthlyPayment'); diff --git a/src/db/incomes.rs b/src/db/incomes.rs index 688e9e1..d33cbcb 100644 --- a/src/db/incomes.rs +++ b/src/db/incomes.rs @@ -5,7 +5,6 @@ use tokio_rusqlite::{named_params, Connection, Row}; use crate::db::utils; use crate::model::income::{Create, Form, Stat, Table, Update}; -use crate::model::report::Report; fn row_to_table(row: &Row) -> Result<Table, rusqlite::Error> { Ok(Table { @@ -487,63 +486,3 @@ pub async fn total_each_month(conn: &Connection) -> Vec<Stat> { } } } - -pub async fn last_week(conn: &Connection) -> Vec<Report> { - let query = r#" - SELECT - strftime('%m/%Y', incomes.date) AS date, - users.name AS name, - incomes.amount AS amount, - (CASE - WHEN - incomes.deleted_at IS NOT NULL - THEN - 'Deleted' - WHEN - incomes.updated_at IS NOT NULL - AND incomes.created_at < date('now', 'weekday 0', '-13 days') - THEN - 'Updated' - ELSE - 'Created' - END) AS action - FROM - incomes - INNER JOIN - users - ON - incomes.user_id = users.id - WHERE - ( - incomes.created_at >= date('now', 'weekday 0', '-13 days') - AND incomes.created_at < date('now', 'weekday 0', '-6 days') - ) OR ( - incomes.updated_at >= date('now', 'weekday 0', '-13 days') - AND incomes.updated_at < date('now', 'weekday 0', '-6 days') - ) OR ( - incomes.deleted_at >= date('now', 'weekday 0', '-13 days') - AND incomes.deleted_at < date('now', 'weekday 0', '-6 days') - ) - ORDER BY - incomes.date - "#; - - let res = conn - .call(move |conn| { - let mut stmt = conn.prepare(query)?; - let xs = stmt - .query_map([], utils::row_to_report)? - .collect::<Result<Vec<Report>, _>>()?; - - Ok(xs) - }) - .await; - - match res { - Ok(xs) => xs, - Err(err) => { - log::error!("Error listing payments for report: {:?}", err); - vec![] - } - } -} diff --git a/src/db/jobs.rs b/src/db/jobs.rs index 1d00408..0080339 100644 --- a/src/db/jobs.rs +++ b/src/db/jobs.rs @@ -4,7 +4,6 @@ use crate::model::job::Job; pub async fn should_run(conn: &Connection, job: Job) -> bool { let run_from = match job { - Job::WeeklyReport => "date('now', 'weekday 0', '-6 days')", Job::MonthlyPayment => "date('now', 'start of month')", }; diff --git a/src/db/migrations/06-remove-weekly-report-job.sql b/src/db/migrations/06-remove-weekly-report-job.sql new file mode 100644 index 0000000..415ea76 --- /dev/null +++ b/src/db/migrations/06-remove-weekly-report-job.sql @@ -0,0 +1 @@ +DELETE FROM jobs WHERE name = 'WeeklyReport'; diff --git a/src/db/mod.rs b/src/db/mod.rs index b1fdeea..4894e95 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -27,6 +27,7 @@ async fn apply_migrations(conn: &Connection) -> Result<()> { M::up(include_str!("migrations/03-sign-in-token.sql")), M::up(include_str!("migrations/04-plural-naming.sql")), M::up(include_str!("migrations/05-strict-tables.sql")), + M::up(include_str!("migrations/06-remove-weekly-report-job.sql")), ]); Ok(conn diff --git a/src/db/payments.rs b/src/db/payments.rs index c3013d2..23b4d2f 100644 --- a/src/db/payments.rs +++ b/src/db/payments.rs @@ -7,7 +7,6 @@ use tokio_rusqlite::{ use crate::db::utils; use crate::model::frequency::Frequency; use crate::model::payment; -use crate::model::report::Report; use crate::queries; use crate::utils::text; @@ -567,58 +566,3 @@ pub async fn create_monthly_payments(conn: &Connection) { Err(err) => log::error!("Error creating monthly payments: {:?}", err), } } - -pub async fn last_week(conn: &Connection) -> Vec<Report> { - let query = r#" - SELECT - strftime('%d/%m/%Y', payments.date) AS date, - (payments.name || ' (' || users.name || ')') AS name, - payments.cost AS amount, - (CASE - WHEN payments.deleted_at IS NOT NULL - THEN 'Deleted' - WHEN - payments.updated_at IS NOT NULL - AND payments.created_at < date('now', 'weekday 0', '-13 days') - THEN 'Updated' - ELSE 'Created' - END) AS action - FROM payments - INNER JOIN users - ON payments.user_id = users.id - WHERE - payments.frequency = 'Punctual' - AND ( - ( - payments.created_at >= date('now', 'weekday 0', '-13 days') - AND payments.created_at < date('now', 'weekday 0', '-6 days') - ) OR ( - payments.updated_at >= date('now', 'weekday 0', '-13 days') - AND payments.updated_at < date('now', 'weekday 0', '-6 days') - ) OR ( - payments.deleted_at >= date('now', 'weekday 0', '-13 days') - AND payments.deleted_at < date('now', 'weekday 0', '-6 days') - ) - ) - ORDER BY payments.date - "#; - - let res = conn - .call(move |conn| { - let mut stmt = conn.prepare(query)?; - let xs = stmt - .query_map([], utils::row_to_report)? - .collect::<Result<Vec<Report>, _>>()?; - - Ok(xs) - }) - .await; - - match res { - Ok(payments) => payments, - Err(err) => { - log::error!("Error listing payments for report: {:?}", err); - vec![] - } - } -} diff --git a/src/db/utils.rs b/src/db/utils.rs index 2ff0f13..8f8a31d 100644 --- a/src/db/utils.rs +++ b/src/db/utils.rs @@ -1,6 +1,3 @@ -use crate::model::report::Report; -use tokio_rusqlite::Row; - pub fn format_key_for_search(value: &str) -> String { // Lower doesn’t work on accentuated letters, hence the need to remove manually accents for // uppercase letters as well. @@ -18,12 +15,3 @@ pub fn one<A, I: Iterator<Item = Result<A, rusqlite::Error>>>( )), } } - -pub fn row_to_report(row: &Row) -> Result<Report, rusqlite::Error> { - Ok(Report { - date: row.get(0)?, - name: row.get(1)?, - amount: row.get(2)?, - action: row.get(3)?, - }) -} diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index 3bfca71..0a903c4 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -1,25 +1,11 @@ -mod weekly_report; - use tokio::time::{sleep, Duration}; use tokio_rusqlite::Connection; use crate::db; -use crate::model::config::Config; use crate::model::job::Job; -pub async fn start( - config: Config, - db_conn: Connection, - templates: minijinja::Environment<'_>, -) { +pub async fn start(db_conn: Connection) { loop { - if db::jobs::should_run(&db_conn, Job::WeeklyReport).await { - log::info!("Starting weekly report job"); - if weekly_report::send(&config, &db_conn, &templates).await { - db::jobs::actualize_last_execution(&db_conn, Job::WeeklyReport) - .await; - } - } if db::jobs::should_run(&db_conn, Job::MonthlyPayment).await { log::info!("Starting monthly payment job"); db::payments::create_monthly_payments(&db_conn).await; diff --git a/src/jobs/weekly_report.rs b/src/jobs/weekly_report.rs deleted file mode 100644 index 35bf5af..0000000 --- a/src/jobs/weekly_report.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use tokio_rusqlite::Connection; - -use crate::db; -use crate::mail; -use crate::model::config::Config; -use crate::payer; - -pub async fn send( - config: &Config, - db_conn: &Connection, - env: &minijinja::Environment<'_>, -) -> bool { - match get_weekly_report(db_conn, env).await { - Ok(report) => { - let users = db::users::list(db_conn).await; - mail::send( - config, - users - .into_iter() - .map(|u| mail::Recipient { - name: u.name, - address: u.email, - }) - .collect(), - "Rapport hebdomadaire", - &report, - ) - .await - } - Err(err) => { - log::error!( - "Error preparing weekly report from template: {:?}", - err - ); - false - } - } -} - -async fn get_weekly_report( - db_conn: &Connection, - env: &minijinja::Environment<'_>, -) -> Result<String, minijinja::Error> { - let users = db::users::list(db_conn).await; - let incomes_from = db::incomes::defined_for_all(db_conn).await; - let user_incomes = match incomes_from { - Some(from) => db::incomes::cumulative(db_conn, from).await, - None => HashMap::new(), - }; - let user_payments = db::payments::repartition(db_conn).await; - let exceeding_payers = - payer::exceeding(&users, &user_incomes, &user_payments); - - let last_week_payments = db::payments::last_week(db_conn).await; - let last_week_incomes = db::incomes::last_week(db_conn).await; - - let template = env.get_template("report/report.j2")?; - template.render(minijinja::context!( - name => "John", - exceeding_payers => exceeding_payers, - payments => last_week_payments, - incomes => last_week_incomes - )) -} diff --git a/src/mail.rs b/src/mail.rs deleted file mode 100644 index d7df246..0000000 --- a/src/mail.rs +++ /dev/null @@ -1,107 +0,0 @@ -use chrono::Utc; -use std::io::{Error, ErrorKind}; -use std::process::{Output, Stdio}; -use tokio::io::AsyncWriteExt; -use tokio::process::Command; - -use crate::model::config::Config; - -static FROM_NAME: &str = "Budget"; - -#[derive(Clone)] -pub struct Recipient { - pub name: String, - pub address: String, -} - -pub async fn send( - config: &Config, - recipients: Vec<Recipient>, - subject: &str, - message: &str, -) -> bool { - let headers = format_headers(config, recipients.clone(), subject); - - log::info!( - "Sending mail{}\n{}", - if config.mails_mock { " (MOCK)" } else { "" }, - headers.clone() - ); - - if config.mails_mock { - true - } else { - let recipient_addresses = recipients - .clone() - .into_iter() - .map(|r| r.address) - .collect::<Vec<String>>(); - - // https://github.com/NixOS/nixpkgs/issues/90248 - let mut command = Command::new("/run/wrappers/bin/sendmail"); - command.kill_on_drop(true); - command.arg("-f").arg(config.mails_from.clone()); - command.arg("--").args(recipient_addresses); - command - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - let message = format!("{}\n\n{}", headers, message); - match spawn(command, &message.into_bytes()).await { - Ok(output) => { - if output.status.success() { - log::info!("Mail sent"); - true - } else { - match String::from_utf8(output.stderr) { - Ok(error) => { - log::error!("Error sending email: {}", error) - } - _ => log::error!("Error sending email"), - }; - false - } - } - Err(err) => { - log::error!("Error spawning command: {:?}", err); - false - } - } - } -} - -fn format_headers( - config: &Config, - recipients: Vec<Recipient>, - subject: &str, -) -> String { - let recipients = recipients - .into_iter() - .map(|r| format_address(&r.name, &r.address)) - .collect::<Vec<String>>() - .join(", "); - - format!( - "Date: {}\nFrom: {}\nTo: {}\nSubject: {}", - Utc::now().to_rfc2822(), - format_address(FROM_NAME, &config.mails_from), - recipients, - subject, - ) -} - -fn format_address(name: &str, address: &str) -> String { - format!("{name} <{address}>") -} - -async fn spawn(mut command: Command, stdin: &[u8]) -> Result<Output, Error> { - let mut process = command.spawn()?; - process - .stdin - .as_mut() - .ok_or(Error::new(ErrorKind::Other, "Getting mutable stdin"))? - .write_all(stdin) - .await?; - process.wait_with_output().await -} diff --git a/src/main.rs b/src/main.rs index 30832d3..70bac81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ mod controller; mod crypto; mod db; mod jobs; -mod mail; mod model; mod payer; mod queries; @@ -33,11 +32,7 @@ async fn main() -> Result<()> { let templates = templates::get()?; - tokio::spawn(jobs::start( - config.clone(), - db_conn.clone(), - templates.clone(), - )); + tokio::spawn(jobs::start(db_conn.clone())); let listener = TcpListener::bind(config.socket_address).await?; log::info!("Starting server at {}", config.socket_address); diff --git a/src/model/config.rs b/src/model/config.rs index e0267d5..f40b0fb 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -6,8 +6,6 @@ use std::str::FromStr; pub struct Config { pub auth_secret: String, pub db_path: String, - pub mails_mock: bool, - pub mails_from: String, pub secure_cookies: bool, pub socket_address: SocketAddr, } @@ -16,8 +14,6 @@ pub fn from_env() -> Result<Config, String> { Ok(Config { auth_secret: read_string("AUTH_SECRET")?, db_path: read_string("DB_PATH")?, - mails_mock: read_bool("MAILS_MOCK")?, - mails_from: read_string("MAILS_FROM")?, secure_cookies: read_bool("SECURE_COOKIES")?, socket_address: read_socket_address("SOCKET_ADDRESS")?, }) diff --git a/src/model/job.rs b/src/model/job.rs index f31cfa0..b10b2df 100644 --- a/src/model/job.rs +++ b/src/model/job.rs @@ -3,7 +3,6 @@ use std::fmt; #[derive(Debug)] pub enum Job { MonthlyPayment, - WeeklyReport, } impl fmt::Display for Job { diff --git a/src/model/mod.rs b/src/model/mod.rs index fb07721..55adadd 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,5 +5,4 @@ pub mod income; pub mod job; pub mod login; pub mod payment; -pub mod report; pub mod user; diff --git a/src/model/report.rs b/src/model/report.rs deleted file mode 100644 index e944745..0000000 --- a/src/model/report.rs +++ /dev/null @@ -1,40 +0,0 @@ -use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef}; -use std::fmt; - -#[derive(Debug, serde::Serialize)] -pub struct Report { - pub date: String, - pub name: String, - pub amount: i64, - pub action: Action, -} - -#[derive(Debug, PartialEq, serde::Serialize)] -pub enum Action { - Created, - Updated, - Deleted, -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl FromSql for Action { - fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { - match value { - ValueRef::Text(text) => match std::str::from_utf8(text) { - Ok("Created") => Ok(Action::Created), - Ok("Updated") => Ok(Action::Updated), - Ok("Deleted") => Ok(Action::Deleted), - Ok(str) => Err(FromSqlError::Other( - format!("Unknown action: {str}").into(), - )), - Err(err) => Err(FromSqlError::Other(err.into())), - }, - _ => Err(FromSqlError::InvalidType), - } - } -} diff --git a/templates/report/list.j2 b/templates/report/list.j2 deleted file mode 100644 index d683879..0000000 --- a/templates/report/list.j2 +++ /dev/null @@ -1,14 +0,0 @@ -{% macro list(resource, action, xs) -%} - -{% if xs -%} - - {% set l = xs | length %} - - {{ xs | length }} {{ pluralize(l, resource) }} {{ pluralize(l, action) }} : - - {% for x in xs -%} - - {{ x.date }} {{ x.name }} {{ x.amount | euros() }} - {% endfor %} -{% endif -%} - -{% endmacro %} diff --git a/templates/report/report.j2 b/templates/report/report.j2 deleted file mode 100644 index 8711184..0000000 --- a/templates/report/report.j2 +++ /dev/null @@ -1,50 +0,0 @@ -{% import "report/list.j2" as list %} - -{% if exceeding_payers -%} - - Équilibre : - - {% for exceeding_payer in exceeding_payers -%} - - {{ exceeding_payer[0] }} : +{{ exceeding_payer[1] | euros() }} - {% endfor %} -{% else -%} - - Les paiements sont équilibrés. - -{% endif %}{# - -#}{{ list.list( - resource="paiement", - action="créé", - xs=payments | filter("action", "Created") -) }}{# - -#}{{ list.list( - resource="paiement", - action="modifié", - xs=payments | filter("action", "Updated") -) }}{# - -#}{{ list.list( - resource="paiement", - action="supprimé", - xs=payments | filter("action", "Deleted") -) }}{# - -#}{{ list.list( - resource="revenu", - action="créé", - xs=incomes | filter("action", "Created") -) }}{# - -#}{{ list.list( - resource="revenu", - action="modifié", - xs=incomes | filter("action", "Updated") -) }}{# - -#}{{ list.list( - resource="revenu", - action="supprimé", - xs=incomes | filter("action", "Deleted") -) }} |