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 super::*;
21

            
22
/// Navigation crumbs, e.g.: Home > Page > Subpage
23
///
24
/// # Example
25
///
26
/// ```rust
27
/// # use mailpot_web::utils::Crumb;
28
/// let crumbs = vec![Crumb {
29
///     label: "Home".into(),
30
///     url: "/".into(),
31
/// }];
32
/// println!("{} {}", crumbs[0].label, crumbs[0].url);
33
/// ```
34
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
35
pub struct Crumb {
36
    pub label: Cow<'static, str>,
37
    #[serde(serialize_with = "to_safe_string")]
38
    pub url: Cow<'static, str>,
39
}
40

            
41
/// Message urgency level or info.
42
#[derive(
43
29
    Debug, Default, Hash, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq,
44
)]
45
5
pub enum Level {
46
    Success,
47
    #[default]
48
2
    Info,
49
    Warning,
50
    Error,
51
}
52

            
53
/// UI message notifications.
54
43
#[derive(Debug, Hash, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
55
pub struct Message {
56
2
    pub message: Cow<'static, str>,
57
    #[serde(default)]
58
2
    pub level: Level,
59
}
60

            
61
impl Message {
62
    const MESSAGE_KEY: &'static str = "session-message";
63
}
64

            
65
/// Drain messages from session.
66
///
67
/// # Example
68
///
69
/// ```no_run
70
/// # use mailpot_web::utils::{Message, Level, SessionMessages};
71
/// struct Session(Vec<Message>);
72
///
73
/// impl SessionMessages for Session {
74
///     type Error = std::convert::Infallible;
75
///     fn drain_messages(&mut self) -> Vec<Message> {
76
///         std::mem::take(&mut self.0)
77
///     }
78
///
79
///     fn add_message(&mut self, m: Message) -> Result<(), std::convert::Infallible> {
80
///         self.0.push(m);
81
///         Ok(())
82
///     }
83
/// }
84
/// let mut s = Session(vec![]);
85
/// s.add_message(Message {
86
///     message: "foo".into(),
87
///     level: Level::default(),
88
/// })
89
/// .unwrap();
90
/// s.add_message(Message {
91
///     message: "bar".into(),
92
///     level: Level::Error,
93
/// })
94
/// .unwrap();
95
/// assert_eq!(
96
///     s.drain_messages().as_slice(),
97
///     [
98
///         Message {
99
///             message: "foo".into(),
100
///             level: Level::default(),
101
///         },
102
///         Message {
103
///             message: "bar".into(),
104
///             level: Level::Error
105
///         }
106
///     ]
107
///     .as_slice()
108
/// );
109
/// assert!(s.0.is_empty());
110
/// ```
111
pub trait SessionMessages {
112
    type Error;
113

            
114
    fn drain_messages(&mut self) -> Vec<Message>;
115
    fn add_message(&mut self, _: Message) -> Result<(), Self::Error>;
116
}
117

            
118
impl SessionMessages for WritableSession {
119
    type Error = ResponseError;
120

            
121
8
    fn drain_messages(&mut self) -> Vec<Message> {
122
8
        let ret = self.get(Message::MESSAGE_KEY).unwrap_or_default();
123
8
        self.remove(Message::MESSAGE_KEY);
124
        ret
125
8
    }
126

            
127
    #[allow(clippy::significant_drop_tightening)]
128
4
    fn add_message(&mut self, message: Message) -> Result<(), ResponseError> {
129
4
        let mut messages: Vec<Message> = self.get(Message::MESSAGE_KEY).unwrap_or_default();
130
4
        messages.push(message);
131
8
        self.insert(Message::MESSAGE_KEY, messages)?;
132
4
        Ok(())
133
4
    }
134
}
135

            
136
/// Deserialize a string integer into `i64`, because POST parameters are
137
/// strings.
138
4
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)]
139
#[repr(transparent)]
140
3
pub struct IntPOST(pub i64);
141

            
142
impl serde::Serialize for IntPOST {
143
1
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144
    where
145
        S: serde::Serializer,
146
    {
147
1
        serializer.serialize_i64(self.0)
148
1
    }
149
}
150

            
151
impl<'de> serde::Deserialize<'de> for IntPOST {
152
2
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153
    where
154
        D: serde::Deserializer<'de>,
155
    {
156
        struct IntVisitor;
157

            
158
        impl<'de> serde::de::Visitor<'de> for IntVisitor {
159
            type Value = IntPOST;
160

            
161
            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
162
                f.write_str("Int as a number or string")
163
            }
164

            
165
            fn visit_i64<E>(self, int: i64) -> Result<Self::Value, E>
166
            where
167
                E: serde::de::Error,
168
            {
169
                Ok(IntPOST(int))
170
            }
171

            
172
1
            fn visit_u64<E>(self, int: u64) -> Result<Self::Value, E>
173
            where
174
                E: serde::de::Error,
175
            {
176
1
                Ok(IntPOST(int.try_into().unwrap()))
177
1
            }
178

            
179
1
            fn visit_str<E>(self, int: &str) -> Result<Self::Value, E>
180
            where
181
                E: serde::de::Error,
182
            {
183
1
                int.parse().map(IntPOST).map_err(serde::de::Error::custom)
184
1
            }
185
        }
186

            
187
2
        deserializer.deserialize_any(IntVisitor)
188
2
    }
189
}
190

            
191
/// Deserialize a string integer into `bool`, because POST parameters are
192
/// strings.
193
4
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Hash)]
194
#[repr(transparent)]
195
3
pub struct BoolPOST(pub bool);
196

            
197
impl serde::Serialize for BoolPOST {
198
1
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
199
    where
200
        S: serde::Serializer,
201
    {
202
1
        serializer.serialize_bool(self.0)
203
1
    }
204
}
205

            
206
impl<'de> serde::Deserialize<'de> for BoolPOST {
207
2
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
208
    where
209
        D: serde::Deserializer<'de>,
210
    {
211
        struct BoolVisitor;
212

            
213
        impl<'de> serde::de::Visitor<'de> for BoolVisitor {
214
            type Value = BoolPOST;
215

            
216
            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
217
                f.write_str("Bool as a boolean or \"true\" \"false\"")
218
            }
219

            
220
1
            fn visit_bool<E>(self, val: bool) -> Result<Self::Value, E>
221
            where
222
                E: serde::de::Error,
223
            {
224
1
                Ok(BoolPOST(val))
225
2
            }
226

            
227
1
            fn visit_str<E>(self, val: &str) -> Result<Self::Value, E>
228
            where
229
                E: serde::de::Error,
230
            {
231
1
                val.parse().map(BoolPOST).map_err(serde::de::Error::custom)
232
1
            }
233
        }
234

            
235
2
        deserializer.deserialize_any(BoolVisitor)
236
2
    }
237
}
238

            
239
#[derive(Debug, Clone, serde::Deserialize)]
240
pub struct Next {
241
    #[serde(default, deserialize_with = "empty_string_as_none")]
242
    pub next: Option<String>,
243
}
244

            
245
impl Next {
246
    #[inline]
247
2
    pub fn or_else(self, cl: impl FnOnce() -> String) -> Redirect {
248
2
        self.next
249
2
            .map_or_else(|| Redirect::to(&cl()), |next| Redirect::to(&next))
250
2
    }
251
}
252

            
253
/// Serde deserialization decorator to map empty Strings to None,
254
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
255
where
256
    D: serde::Deserializer<'de>,
257
    T: std::str::FromStr,
258
    T::Err: std::fmt::Display,
259
{
260
    use serde::Deserialize;
261
    let opt = Option::<String>::deserialize(de)?;
262
    match opt.as_deref() {
263
        None | Some("") => Ok(None),
264
        Some(s) => std::str::FromStr::from_str(s)
265
            .map_err(serde::de::Error::custom)
266
            .map(Some),
267
    }
268
}
269

            
270
/// Serialize string to [`minijinja::value::Value`] with
271
/// [`minijinja::value::Value::from_safe_string`].
272
pub fn to_safe_string<S>(s: impl AsRef<str>, ser: S) -> Result<S::Ok, S::Error>
273
where
274
    S: serde::Serializer,
275
{
276
    use serde::Serialize;
277
    let s = s.as_ref();
278
    Value::from_safe_string(s.to_string()).serialize(ser)
279
}
280

            
281
/// Serialize an optional string to [`minijinja::value::Value`] with
282
/// [`minijinja::value::Value::from_safe_string`].
283
pub fn to_safe_string_opt<S>(s: &Option<String>, ser: S) -> Result<S::Ok, S::Error>
284
where
285
    S: serde::Serializer,
286
{
287
    use serde::Serialize;
288
    s.as_ref()
289
        .map(|s| Value::from_safe_string(s.to_string()))
290
        .serialize(ser)
291
}
292

            
293
#[derive(Debug, Clone)]
294
pub struct ThreadEntry {
295
    pub hash: melib::EnvelopeHash,
296
    pub depth: usize,
297
    pub thread_node: melib::ThreadNodeHash,
298
    pub thread: melib::ThreadHash,
299
    pub from: String,
300
    pub message_id: String,
301
    pub timestamp: u64,
302
    pub datetime: String,
303
}
304

            
305
pub fn thread(
306
    envelopes: &Arc<std::sync::RwLock<HashMap<melib::EnvelopeHash, melib::Envelope>>>,
307
    threads: &melib::Threads,
308
    root_env_hash: melib::EnvelopeHash,
309
) -> Vec<ThreadEntry> {
310
    let env_lock = envelopes.read().unwrap();
311
    let thread = threads.envelope_to_thread[&root_env_hash];
312
    let mut ret = vec![];
313
    for (depth, t) in threads.thread_iter(thread) {
314
        let hash = threads.thread_nodes[&t].message.unwrap();
315
        ret.push(ThreadEntry {
316
            hash,
317
            depth,
318
            thread_node: t,
319
            thread,
320
            message_id: env_lock[&hash].message_id().to_string(),
321
            from: env_lock[&hash].field_from_to_string(),
322
            datetime: env_lock[&hash].date_as_str().to_string(),
323
            timestamp: env_lock[&hash].timestamp,
324
        });
325
    }
326
    ret
327
}
328

            
329
2
pub fn thread_roots(
330
    envelopes: &Arc<std::sync::RwLock<HashMap<melib::EnvelopeHash, melib::Envelope>>>,
331
    threads: &melib::Threads,
332
) -> Vec<(ThreadEntry, usize, u64)> {
333
2
    let items = threads.roots();
334
2
    let env_lock = envelopes.read().unwrap();
335
2
    let mut ret = vec![];
336
4
    'items_for_loop: for thread in items {
337
2
        let mut iter_ptr = threads.thread_ref(thread).root();
338
2
        let thread_node = &threads.thread_nodes()[&iter_ptr];
339
2
        let root_env_hash = if let Some(h) = thread_node.message().or_else(|| {
340
            if thread_node.children().is_empty() {
341
                return None;
342
            }
343
            iter_ptr = thread_node.children()[0];
344
            while threads.thread_nodes()[&iter_ptr].message().is_none() {
345
                if threads.thread_nodes()[&iter_ptr].children().is_empty() {
346
                    return None;
347
                }
348
                iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
349
            }
350
            threads.thread_nodes()[&iter_ptr].message()
351
        }) {
352
2
            h
353
        } else {
354
            continue 'items_for_loop;
355
        };
356
2
        if !env_lock.contains_key(&root_env_hash) {
357
            panic!("key = {}", root_env_hash);
358
        }
359
2
        let envelope: &melib::Envelope = &env_lock[&root_env_hash];
360
2
        let tref = threads.thread_ref(thread);
361
2
        ret.push((
362
2
            ThreadEntry {
363
2
                hash: root_env_hash,
364
                depth: 0,
365
2
                thread_node: iter_ptr,
366
                thread,
367
2
                message_id: envelope.message_id().to_string(),
368
2
                from: envelope.field_from_to_string(),
369
2
                datetime: envelope.date_as_str().to_string(),
370
2
                timestamp: envelope.timestamp,
371
            },
372
2
            tref.len,
373
2
            tref.date,
374
        ));
375
    }
376
    // clippy: error: temporary with significant `Drop` can be early dropped
377
2
    drop(env_lock);
378
2
    ret.sort_by_key(|(_, _, key)| std::cmp::Reverse(*key));
379
2
    ret
380
2
}
381

            
382
#[cfg(test)]
383
mod tests {
384
    use super::*;
385

            
386
    #[test]
387
2
    fn test_session() {
388
        struct Session(Vec<Message>);
389

            
390
        impl SessionMessages for Session {
391
            type Error = std::convert::Infallible;
392
1
            fn drain_messages(&mut self) -> Vec<Message> {
393
1
                std::mem::take(&mut self.0)
394
1
            }
395

            
396
2
            fn add_message(&mut self, m: Message) -> Result<(), std::convert::Infallible> {
397
2
                self.0.push(m);
398
                Ok(())
399
2
            }
400
        }
401
1
        let mut s = Session(vec![]);
402
1
        s.add_message(Message {
403
1
            message: "foo".into(),
404
1
            level: Level::default(),
405
        })
406
        .unwrap();
407
1
        s.add_message(Message {
408
1
            message: "bar".into(),
409
1
            level: Level::Error,
410
        })
411
        .unwrap();
412
1
        assert_eq!(
413
1
            s.drain_messages().as_slice(),
414
1
            [
415
1
                Message {
416
1
                    message: "foo".into(),
417
1
                    level: Level::default(),
418
                },
419
1
                Message {
420
1
                    message: "bar".into(),
421
1
                    level: Level::Error
422
                }
423
            ]
424
            .as_slice()
425
        );
426
1
        assert!(s.0.is_empty());
427
2
    }
428

            
429
    #[test]
430
2
    fn test_post_serde() {
431
        use mailpot::serde_json::{self, json};
432
1
        assert_eq!(
433
            IntPOST(5),
434
1
            serde_json::from_str::<IntPOST>("\"5\"").unwrap()
435
        );
436
1
        assert_eq!(IntPOST(5), serde_json::from_str::<IntPOST>("5").unwrap());
437
1
        assert_eq!(&json! { IntPOST(5) }.to_string(), "5");
438

            
439
1
        assert_eq!(
440
            BoolPOST(true),
441
1
            serde_json::from_str::<BoolPOST>("true").unwrap()
442
        );
443
1
        assert_eq!(
444
            BoolPOST(true),
445
1
            serde_json::from_str::<BoolPOST>("\"true\"").unwrap()
446
        );
447
1
        assert_eq!(&json! { BoolPOST(false) }.to_string(), "false");
448
2
    }
449

            
450
    #[test]
451
2
    fn test_next() {
452
1
        let next = Next {
453
1
            next: Some("foo".to_string()),
454
        };
455
1
        assert_eq!(
456
1
            format!("{:?}", Redirect::to("foo")),
457
1
            format!("{:?}", next.or_else(|| "bar".to_string()))
458
        );
459
1
        let next = Next { next: None };
460
1
        assert_eq!(
461
1
            format!("{:?}", Redirect::to("bar")),
462
2
            format!("{:?}", next.or_else(|| "bar".to_string()))
463
        );
464
2
    }
465
}