aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris Guyonvarch2025-08-30 09:32:27 +0200
committerJoris Guyonvarch2025-08-30 09:32:27 +0200
commit2a6bcee45086bca9128489de19908984ea1be0da (patch)
tree2e5e1b0e50109385b5d3daaabed00efb450f4b62
parent164538a57a5b7c19c993862a6badc85fd723380e (diff)
Remove weekly report jobmain
-rw-r--r--README.md1
-rwxr-xr-xbin/dev-server2
-rw-r--r--config.json1
-rw-r--r--sql/fixtures.sql3
-rw-r--r--src/db/incomes.rs61
-rw-r--r--src/db/jobs.rs1
-rw-r--r--src/db/migrations/06-remove-weekly-report-job.sql1
-rw-r--r--src/db/mod.rs1
-rw-r--r--src/db/payments.rs56
-rw-r--r--src/db/utils.rs12
-rw-r--r--src/jobs/mod.rs16
-rw-r--r--src/jobs/weekly_report.rs65
-rw-r--r--src/mail.rs107
-rw-r--r--src/main.rs7
-rw-r--r--src/model/config.rs4
-rw-r--r--src/model/job.rs1
-rw-r--r--src/model/mod.rs1
-rw-r--r--src/model/report.rs40
-rw-r--r--templates/report/list.j214
-rw-r--r--templates/report/report.j250
20 files changed, 5 insertions, 439 deletions
diff --git a/README.md b/README.md
index 8e02d05..b499812 100644
--- a/README.md
+++ b/README.md
@@ -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")
-) }}