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 mailpot::models::{
21
    changesets::{AccountChangeset, ListSubscriptionChangeset},
22
    ListSubscription,
23
};
24

            
25
use super::*;
26

            
27
pub async fn settings(
28
    _: SettingsPath,
29
    mut session: WritableSession,
30
    Extension(user): Extension<User>,
31
    state: Arc<AppState>,
32
) -> Result<Html<String>, ResponseError> {
33
    let root_url_prefix = &state.root_url_prefix;
34
    let crumbs = vec![
35
        Crumb {
36
            label: "Home".into(),
37
            url: "/".into(),
38
        },
39
        Crumb {
40
            label: "Settings".into(),
41
            url: SettingsPath.to_crumb(),
42
        },
43
    ];
44
    let db = Connection::open_db(state.conf.clone())?;
45
    let acc = db
46
        .account_by_address(&user.address)
47
        .with_status(StatusCode::BAD_REQUEST)?
48
        .ok_or_else(|| {
49
            ResponseError::new("Account not found".to_string(), StatusCode::BAD_REQUEST)
50
        })?;
51
    let subscriptions = db
52
        .account_subscriptions(acc.pk())
53
        .with_status(StatusCode::BAD_REQUEST)?
54
        .into_iter()
55
        .filter_map(|s| match db.list(s.list) {
56
            Err(err) => Some(Err(err)),
57
            Ok(Some(list)) => Some(Ok((s, list))),
58
            Ok(None) => None,
59
        })
60
        .collect::<Result<
61
            Vec<(
62
                DbVal<mailpot::models::ListSubscription>,
63
                DbVal<mailpot::models::MailingList>,
64
            )>,
65
            mailpot::Error,
66
        >>()?;
67

            
68
    let context = minijinja::context! {
69
        title => state.site_title.as_ref(),
70
        page_title => "Account settings",
71
        description => "",
72
        root_url_prefix => &root_url_prefix,
73
        user => user,
74
        subscriptions => subscriptions,
75
        current_user => user,
76
        messages => session.drain_messages(),
77
        crumbs => crumbs,
78
    };
79
    Ok(Html(
80
        TEMPLATES.get_template("settings.html")?.render(context)?,
81
    ))
82
}
83

            
84
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
85
#[serde(tag = "type", rename_all = "kebab-case")]
86
pub enum ChangeSetting {
87
    Subscribe { list_pk: IntPOST },
88
    Unsubscribe { list_pk: IntPOST },
89
    ChangePassword { new: String },
90
    ChangePublicKey { new: String },
91
    // RemovePassword,
92
    RemovePublicKey,
93
    ChangeName { new: String },
94
}
95

            
96
pub async fn settings_post(
97
    _: SettingsPath,
98
    mut session: WritableSession,
99
    Extension(user): Extension<User>,
100
    Form(payload): Form<ChangeSetting>,
101
    state: Arc<AppState>,
102
) -> Result<Redirect, ResponseError> {
103
    let mut db = Connection::open_db(state.conf.clone())?;
104
    let acc = db
105
        .account_by_address(&user.address)
106
        .with_status(StatusCode::BAD_REQUEST)?
107
        .ok_or_else(|| {
108
            ResponseError::new("Account not found".to_string(), StatusCode::BAD_REQUEST)
109
        })?;
110

            
111
    match payload {
112
        ChangeSetting::Subscribe {
113
            list_pk: IntPOST(list_pk),
114
        } => {
115
            let subscriptions = db
116
                .account_subscriptions(acc.pk())
117
                .with_status(StatusCode::BAD_REQUEST)?;
118
            if subscriptions.iter().any(|s| s.list == list_pk) {
119
                session.add_message(Message {
120
                    message: "You are already subscribed to this list.".into(),
121
                    level: Level::Info,
122
                })?;
123
            } else {
124
                db.add_subscription(
125
                    list_pk,
126
                    ListSubscription {
127
                        pk: 0,
128
                        list: list_pk,
129
                        account: Some(acc.pk()),
130
                        address: acc.address.clone(),
131
                        name: acc.name.clone(),
132
                        digest: false,
133
                        enabled: true,
134
                        verified: true,
135
                        hide_address: false,
136
                        receive_duplicates: false,
137
                        receive_own_posts: false,
138
                        receive_confirmation: false,
139
                    },
140
                )?;
141
                session.add_message(Message {
142
                    message: "You have subscribed to this list.".into(),
143
                    level: Level::Success,
144
                })?;
145
            }
146
        }
147
        ChangeSetting::Unsubscribe {
148
            list_pk: IntPOST(list_pk),
149
        } => {
150
            let subscriptions = db
151
                .account_subscriptions(acc.pk())
152
                .with_status(StatusCode::BAD_REQUEST)?;
153
            if !subscriptions.iter().any(|s| s.list == list_pk) {
154
                session.add_message(Message {
155
                    message: "You are already not subscribed to this list.".into(),
156
                    level: Level::Info,
157
                })?;
158
            } else {
159
                let db = db.trusted();
160
                db.remove_subscription(list_pk, &acc.address)?;
161
                session.add_message(Message {
162
                    message: "You have unsubscribed from this list.".into(),
163
                    level: Level::Success,
164
                })?;
165
            }
166
        }
167
        ChangeSetting::ChangePassword { new } => {
168
            db.update_account(AccountChangeset {
169
                address: acc.address.clone(),
170
                name: None,
171
                public_key: None,
172
                password: Some(new.clone()),
173
                enabled: None,
174
            })
175
            .with_status(StatusCode::BAD_REQUEST)?;
176
            session.add_message(Message {
177
                message: "You have successfully updated your SSH public key.".into(),
178
                level: Level::Success,
179
            })?;
180
            let mut user = user.clone();
181
            user.password = new;
182
            state.insert_user(acc.pk(), user).await;
183
        }
184
        ChangeSetting::ChangePublicKey { new } => {
185
            db.update_account(AccountChangeset {
186
                address: acc.address.clone(),
187
                name: None,
188
                public_key: Some(Some(new.clone())),
189
                password: None,
190
                enabled: None,
191
            })
192
            .with_status(StatusCode::BAD_REQUEST)?;
193
            session.add_message(Message {
194
                message: "You have successfully updated your PGP public key.".into(),
195
                level: Level::Success,
196
            })?;
197
            let mut user = user.clone();
198
            user.public_key = Some(new);
199
            state.insert_user(acc.pk(), user).await;
200
        }
201
        ChangeSetting::RemovePublicKey => {
202
            db.update_account(AccountChangeset {
203
                address: acc.address.clone(),
204
                name: None,
205
                public_key: Some(None),
206
                password: None,
207
                enabled: None,
208
            })
209
            .with_status(StatusCode::BAD_REQUEST)?;
210
            session.add_message(Message {
211
                message: "You have successfully removed your PGP public key.".into(),
212
                level: Level::Success,
213
            })?;
214
            let mut user = user.clone();
215
            user.public_key = None;
216
            state.insert_user(acc.pk(), user).await;
217
        }
218
        ChangeSetting::ChangeName { new } => {
219
            let new = if new.trim().is_empty() {
220
                None
221
            } else {
222
                Some(new)
223
            };
224
            db.update_account(AccountChangeset {
225
                address: acc.address.clone(),
226
                name: Some(new.clone()),
227
                public_key: None,
228
                password: None,
229
                enabled: None,
230
            })
231
            .with_status(StatusCode::BAD_REQUEST)?;
232
            session.add_message(Message {
233
                message: "You have successfully updated your name.".into(),
234
                level: Level::Success,
235
            })?;
236
            let mut user = user.clone();
237
            user.name = new.clone();
238
            state.insert_user(acc.pk(), user).await;
239
        }
240
    }
241

            
242
    Ok(Redirect::to(&format!(
243
        "{}{}",
244
        &state.root_url_prefix,
245
        SettingsPath.to_uri()
246
    )))
247
}
248

            
249
pub async fn user_list_subscription(
250
    ListSettingsPath(id): ListSettingsPath,
251
    mut session: WritableSession,
252
    Extension(user): Extension<User>,
253
    State(state): State<Arc<AppState>>,
254
) -> Result<Html<String>, ResponseError> {
255
    let root_url_prefix = &state.root_url_prefix;
256
    let db = Connection::open_db(state.conf.clone())?;
257
    let Some(list) = (match id {
258
        ListPathIdentifier::Pk(id) => db.list(id)?,
259
        ListPathIdentifier::Id(id) => db.list_by_id(id)?,
260
    }) else {
261
        return Err(ResponseError::new(
262
            "List not found".to_string(),
263
            StatusCode::NOT_FOUND,
264
        ));
265
    };
266
    let acc = match db.account_by_address(&user.address)? {
267
        Some(v) => v,
268
        None => {
269
            return Err(ResponseError::new(
270
                "Account not found".to_string(),
271
                StatusCode::BAD_REQUEST,
272
            ))
273
        }
274
    };
275
    let mut subscriptions = db
276
        .account_subscriptions(acc.pk())
277
        .with_status(StatusCode::BAD_REQUEST)?;
278
    subscriptions.retain(|s| s.list == list.pk());
279
    let subscription = db
280
        .list_subscription(
281
            list.pk(),
282
            subscriptions
283
                .get(0)
284
                .ok_or_else(|| {
285
                    ResponseError::new(
286
                        "Subscription not found".to_string(),
287
                        StatusCode::BAD_REQUEST,
288
                    )
289
                })?
290
                .pk(),
291
        )
292
        .with_status(StatusCode::BAD_REQUEST)?;
293

            
294
    let crumbs = vec![
295
        Crumb {
296
            label: "Home".into(),
297
            url: "/".into(),
298
        },
299
        Crumb {
300
            label: "Settings".into(),
301
            url: SettingsPath.to_crumb(),
302
        },
303
        Crumb {
304
            label: "List Subscription".into(),
305
            url: ListSettingsPath(list.pk().into()).to_crumb(),
306
        },
307
    ];
308

            
309
    let context = minijinja::context! {
310
        title => state.site_title.as_ref(),
311
        page_title => "Subscription settings",
312
        description => "",
313
        root_url_prefix => &root_url_prefix,
314
        user => user,
315
        list => list,
316
        subscription => subscription,
317
        current_user => user,
318
        messages => session.drain_messages(),
319
        crumbs => crumbs,
320
    };
321
    Ok(Html(
322
        TEMPLATES
323
            .get_template("settings_subscription.html")?
324
            .render(context)?,
325
    ))
326
}
327

            
328
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)]
329
pub struct SubscriptionFormPayload {
330
    #[serde(default)]
331
    pub digest: bool,
332
    #[serde(default)]
333
    pub hide_address: bool,
334
    #[serde(default)]
335
    pub receive_duplicates: bool,
336
    #[serde(default)]
337
    pub receive_own_posts: bool,
338
    #[serde(default)]
339
    pub receive_confirmation: bool,
340
}
341

            
342
pub async fn user_list_subscription_post(
343
    ListSettingsPath(id): ListSettingsPath,
344
    mut session: WritableSession,
345
    Extension(user): Extension<User>,
346
    Form(payload): Form<SubscriptionFormPayload>,
347
    state: Arc<AppState>,
348
) -> Result<Redirect, ResponseError> {
349
    let mut db = Connection::open_db(state.conf.clone())?;
350

            
351
    let Some(list) = (match id {
352
        ListPathIdentifier::Pk(id) => db.list(id)?,
353
        ListPathIdentifier::Id(id) => db.list_by_id(id)?,
354
    }) else {
355
        return Err(ResponseError::new(
356
            "List not found".to_string(),
357
            StatusCode::NOT_FOUND,
358
        ));
359
    };
360

            
361
    let acc = match db.account_by_address(&user.address)? {
362
        Some(v) => v,
363
        None => {
364
            return Err(ResponseError::new(
365
                "Account with this address was not found".to_string(),
366
                StatusCode::BAD_REQUEST,
367
            ));
368
        }
369
    };
370
    let mut subscriptions = db
371
        .account_subscriptions(acc.pk())
372
        .with_status(StatusCode::BAD_REQUEST)?;
373

            
374
    subscriptions.retain(|s| s.list == list.pk());
375
    let mut s = db
376
        .list_subscription(list.pk(), subscriptions[0].pk())
377
        .with_status(StatusCode::BAD_REQUEST)?;
378

            
379
    let SubscriptionFormPayload {
380
        digest,
381
        hide_address,
382
        receive_duplicates,
383
        receive_own_posts,
384
        receive_confirmation,
385
    } = payload;
386

            
387
    let cset = ListSubscriptionChangeset {
388
        list: s.list,
389
        address: std::mem::take(&mut s.address),
390
        account: None,
391
        name: None,
392
        digest: Some(digest),
393
        hide_address: Some(hide_address),
394
        receive_duplicates: Some(receive_duplicates),
395
        receive_own_posts: Some(receive_own_posts),
396
        receive_confirmation: Some(receive_confirmation),
397
        enabled: None,
398
        verified: None,
399
    };
400

            
401
    db.update_subscription(cset)
402
        .with_status(StatusCode::BAD_REQUEST)?;
403

            
404
    session.add_message(Message {
405
        message: "Settings saved successfully.".into(),
406
        level: Level::Success,
407
    })?;
408

            
409
    Ok(Redirect::to(&format!(
410
        "{}{}",
411
        &state.root_url_prefix,
412
        ListSettingsPath(list.id.clone().into()).to_uri()
413
    )))
414
}