1
/*
2
 * This file is part of mailpot
3
 *
4
 * Copyright 2020 - Manos Pitsidianakis
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 */
19

            
20
use std::borrow::Cow;
21

            
22
use chrono::{Datelike, Month};
23
use mailpot::{models::DbVal, *};
24
use minijinja::{
25
    value::{Object, Value},
26
    Environment, Error, Source, State,
27
};
28

            
29
lazy_static::lazy_static! {
30
    pub static ref TEMPLATES: Environment<'static> = {
31
        let mut env = Environment::new();
32
        env.add_function("calendarize", calendarize);
33
        env.set_source(Source::from_path("src/templates/"));
34

            
35
        env
36
    };
37
}
38

            
39
pub trait StripCarets {
40
    fn strip_carets(&self) -> &str;
41
}
42

            
43
impl StripCarets for &str {
44
    fn strip_carets(&self) -> &str {
45
        let mut self_ref = self.trim();
46
        if self_ref.starts_with('<') && self_ref.ends_with('>') {
47
            self_ref = &self_ref[1..self_ref.len().saturating_sub(1)];
48
        }
49
        self_ref
50
    }
51
}
52

            
53
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
54
pub struct MailingList {
55
    pub pk: i64,
56
    pub name: String,
57
    pub id: String,
58
    pub address: String,
59
    pub description: Option<String>,
60
    pub topics: Vec<String>,
61
    pub archive_url: Option<String>,
62
    pub inner: DbVal<mailpot::models::MailingList>,
63
}
64

            
65
impl From<DbVal<mailpot::models::MailingList>> for MailingList {
66
    fn from(val: DbVal<mailpot::models::MailingList>) -> Self {
67
        let DbVal(
68
            mailpot::models::MailingList {
69
                pk,
70
                name,
71
                id,
72
                address,
73
                description,
74
                topics,
75
                archive_url,
76
            },
77
            _,
78
        ) = val.clone();
79

            
80
        Self {
81
            pk,
82
            name,
83
            id,
84
            address,
85
            description,
86
            topics,
87
            archive_url,
88
            inner: val,
89
        }
90
    }
91
}
92

            
93
impl std::fmt::Display for MailingList {
94
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
95
        self.id.fmt(fmt)
96
    }
97
}
98

            
99
impl Object for MailingList {
100
    fn kind(&self) -> minijinja::value::ObjectKind {
101
        minijinja::value::ObjectKind::Struct(self)
102
    }
103

            
104
    fn call_method(
105
        &self,
106
        _state: &State,
107
        name: &str,
108
        _args: &[Value],
109
    ) -> std::result::Result<Value, Error> {
110
        match name {
111
            "subscription_mailto" => {
112
                Ok(Value::from_serializable(&self.inner.subscription_mailto()))
113
            }
114
            "unsubscription_mailto" => Ok(Value::from_serializable(
115
                &self.inner.unsubscription_mailto(),
116
            )),
117
            _ => Err(Error::new(
118
                minijinja::ErrorKind::UnknownMethod,
119
                format!("aaaobject has no method named {name}"),
120
            )),
121
        }
122
    }
123
}
124

            
125
impl minijinja::value::StructObject for MailingList {
126
    fn get_field(&self, name: &str) -> Option<Value> {
127
        match name {
128
            "pk" => Some(Value::from_serializable(&self.pk)),
129
            "name" => Some(Value::from_serializable(&self.name)),
130
            "id" => Some(Value::from_serializable(&self.id)),
131
            "address" => Some(Value::from_serializable(&self.address)),
132
            "description" => Some(Value::from_serializable(&self.description)),
133
            "topics" => Some(Value::from_serializable(&self.topics)),
134
            "archive_url" => Some(Value::from_serializable(&self.archive_url)),
135
            _ => None,
136
        }
137
    }
138

            
139
    fn static_fields(&self) -> Option<&'static [&'static str]> {
140
        Some(
141
            &[
142
                "pk",
143
                "name",
144
                "id",
145
                "address",
146
                "description",
147
                "topics",
148
                "archive_url",
149
            ][..],
150
        )
151
    }
152
}
153

            
154
pub fn calendarize(_state: &State, args: Value, hists: Value) -> std::result::Result<Value, Error> {
155
    macro_rules! month {
156
        ($int:expr) => {{
157
            let int = $int;
158
            match int {
159
                1 => Month::January.name(),
160
                2 => Month::February.name(),
161
                3 => Month::March.name(),
162
                4 => Month::April.name(),
163
                5 => Month::May.name(),
164
                6 => Month::June.name(),
165
                7 => Month::July.name(),
166
                8 => Month::August.name(),
167
                9 => Month::September.name(),
168
                10 => Month::October.name(),
169
                11 => Month::November.name(),
170
                12 => Month::December.name(),
171
                _ => unreachable!(),
172
            }
173
        }};
174
    }
175
    let month = args.as_str().unwrap();
176
    let hist = hists
177
        .get_item(&Value::from(month))?
178
        .as_seq()
179
        .unwrap()
180
        .iter()
181
        .map(|v| usize::try_from(v).unwrap())
182
        .collect::<Vec<usize>>();
183
    let sum: usize = hists
184
        .get_item(&Value::from(month))?
185
        .as_seq()
186
        .unwrap()
187
        .iter()
188
        .map(|v| usize::try_from(v).unwrap())
189
        .sum();
190
    let date = chrono::NaiveDate::parse_from_str(&format!("{}-01", month), "%F").unwrap();
191
    // Week = [Mon, Tue, Wed, Thu, Fri, Sat, Sun]
192
    Ok(minijinja::context! {
193
        month_name => month!(date.month()),
194
        month => month,
195
        month_int => date.month() as usize,
196
        year => date.year(),
197
        weeks => crate::cal::calendarize_with_offset(date, 1),
198
        hist => hist,
199
        sum => sum,
200
    })
201
}
202

            
203
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
204
pub struct Crumb {
205
    pub label: Cow<'static, str>,
206
    pub url: Cow<'static, str>,
207
}