backend/model/entity/
timeline.rs

1use chrono::NaiveDate;
2use diesel::{
3    sql_query,
4    sql_types::{Date, Integer},
5    QueryResult, QueryableByName,
6};
7use diesel_async::{AsyncPgConnection, RunQueryDsl};
8use std::collections::HashMap;
9
10use crate::model::dto::timeline::{TimelineDto, TimelineEntryDto, TimelineParameters};
11
12///Query that calculates the timeline
13const CALCULATE_TIMELINE_QUERY: &str = include_str!("../sql/calculate_timeline.sql");
14
15/// Stores the result of the timeline query. Dates and sum of additions and removals of plantings.
16#[derive(QueryableByName, Debug)]
17struct TimelineQeueryResult {
18    /// a date where at least one addition or removal of a planting took place
19    #[diesel(sql_type = Date)]
20    date: NaiveDate,
21    /// the sum of planting additions on this date
22    #[diesel(sql_type = Integer)]
23    additions: i32,
24    /// the sum of planting removals on this date
25    #[diesel(sql_type = Integer)]
26    removals: i32,
27}
28
29/// Summarize all plantings into a timeline.
30///
31/// # Errors
32/// * Unknown, diesel doesn't say why it might error.
33pub async fn calculate(
34    map_id: i32,
35    params: TimelineParameters,
36    conn: &mut AsyncPgConnection,
37) -> QueryResult<TimelineDto> {
38    let query = sql_query(CALCULATE_TIMELINE_QUERY)
39        .bind::<diesel::sql_types::Integer, _>(map_id)
40        .bind::<diesel::sql_types::Date, _>(params.start)
41        .bind::<diesel::sql_types::Date, _>(params.end);
42
43    let results = query.load::<TimelineQeueryResult>(conn).await?;
44
45    let mut years: HashMap<String, TimelineEntryDto> = HashMap::new();
46    let mut months: HashMap<String, TimelineEntryDto> = HashMap::new();
47    let mut dates: HashMap<String, TimelineEntryDto> = HashMap::new();
48
49    for result in results {
50        let date = result.date;
51        let date_string = date.format("%Y-%m-%d").to_string();
52        let month_string = date.format("%Y-%m").to_string();
53        let year_string = date.format("%Y").to_string();
54
55        dates.insert(
56            date_string,
57            TimelineEntryDto {
58                additions: result.additions,
59                removals: result.removals,
60            },
61        );
62
63        let (month_additions, month_removals) =
64            months
65                .get(&month_string)
66                .map_or((result.additions, result.removals), |entry| {
67                    (
68                        entry.additions + result.additions,
69                        entry.removals + result.removals,
70                    )
71                });
72        months.insert(
73            month_string,
74            TimelineEntryDto {
75                additions: month_additions,
76                removals: month_removals,
77            },
78        );
79
80        let (year_additions, year_removals) =
81            years
82                .get(&year_string)
83                .map_or((result.additions, result.removals), |entry| {
84                    (
85                        entry.additions + result.additions,
86                        entry.removals + result.removals,
87                    )
88                });
89        years.insert(
90            year_string,
91            TimelineEntryDto {
92                additions: year_additions,
93                removals: year_removals,
94            },
95        );
96    }
97
98    Ok(TimelineDto {
99        years,
100        months,
101        dates,
102    })
103}