changes
All checks were successful
CI / ci (push) Successful in 19m38s

This commit is contained in:
2026-08-26 20:55:30 +02:00
parent a557c183e9
commit 23d052278a
523 changed files with 24448 additions and 2005 deletions

View File

@@ -0,0 +1,26 @@
use domain::entry::Date;
use domain::metric::{MetricKind, MetricValue, Source};
use domain::user::UserId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetricChange {
Stated(MetricValue),
Cleared(MetricKind),
}
impl MetricChange {
pub fn kind(&self) -> MetricKind {
match self {
Self::Stated(value) => value.kind(),
Self::Cleared(kind) => *kind,
}
}
}
#[derive(Debug)]
pub struct SetDailyMetricsCommand {
pub user_id: UserId,
pub date: Date,
pub changes: Vec<MetricChange>,
pub source: Source,
}

View File

@@ -0,0 +1,2 @@
pub mod commands;
pub mod use_cases;

View File

@@ -0,0 +1,21 @@
use std::sync::Arc;
use domain::entry::DateSpan;
use domain::metric::DailyMetric;
use domain::ports::DailyMetricQueryPort;
use domain::user::UserId;
use crate::errors::ApplicationError;
pub struct Deps {
pub metrics: Arc<dyn DailyMetricQueryPort>,
}
#[tracing::instrument(skip(deps))]
pub async fn execute(
user_id: UserId,
span: DateSpan,
deps: &Deps,
) -> Result<Vec<DailyMetric>, ApplicationError> {
Ok(deps.metrics.find_by_span(&user_id, &span).await?)
}

View File

@@ -0,0 +1,2 @@
pub mod list_daily_metrics;
pub mod set_daily_metrics;

View File

@@ -0,0 +1,101 @@
use std::sync::Arc;
use domain::metric::{DailyMetric, MetricKind};
use domain::ports::{DailyMetricCommandPort, UserQueryPort};
use crate::day::{timezone_for, today_in};
use crate::errors::ApplicationError;
use crate::metric::commands::{MetricChange, SetDailyMetricsCommand};
pub struct Deps {
pub metrics: Arc<dyn DailyMetricCommandPort>,
pub users: Arc<dyn UserQueryPort>,
}
#[tracing::instrument(skip(deps))]
pub async fn execute(command: SetDailyMetricsCommand, deps: &Deps) -> Result<(), ApplicationError> {
let timezone = timezone_for(&command.user_id, &deps.users).await?;
if command.date > today_in(&timezone) {
return Err(ApplicationError::Validation(format!(
"cannot record a metric for {}, which has not happened yet",
command.date
)));
}
reject_a_kind_named_twice(&command.changes)?;
reject_clearing_by_anyone_but_the_user(&command)?;
let cleared: Vec<MetricKind> = command
.changes
.iter()
.filter_map(|change| match change {
MetricChange::Cleared(kind) => Some(*kind),
MetricChange::Stated(_) => None,
})
.collect();
let stated: Vec<DailyMetric> = command
.changes
.iter()
.filter_map(|change| match change {
MetricChange::Stated(value) => Some(DailyMetric::new(
command.user_id.clone(),
command.date,
*value,
command.source.clone(),
)),
MetricChange::Cleared(_) => None,
})
.collect();
if !cleared.is_empty() {
deps.metrics
.delete(&command.user_id, &command.date, &cleared)
.await?;
}
if !stated.is_empty() {
deps.metrics.save(&stated).await?;
}
Ok(())
}
fn reject_clearing_by_anyone_but_the_user(
command: &SetDailyMetricsCommand,
) -> Result<(), ApplicationError> {
if command.source.is_manual() {
return Ok(());
}
let clears = command
.changes
.iter()
.any(|change| matches!(change, MetricChange::Cleared(_)));
if clears {
return Err(ApplicationError::Validation(
"a provider cannot clear a reading: only the user can".into(),
));
}
Ok(())
}
fn reject_a_kind_named_twice(changes: &[MetricChange]) -> Result<(), ApplicationError> {
for (position, change) in changes.iter().enumerate() {
let named_again = changes[position + 1..]
.iter()
.any(|later| later.kind() == change.kind());
if named_again {
return Err(ApplicationError::Validation(format!(
"{} appears more than once in the same request",
change.kind().name()
)));
}
}
Ok(())
}