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

            
67
    let context = minijinja::context! {
68
        page_title => "Account settings",
69
        user => user,
70
        subscriptions => subscriptions,
71
        current_user => user,
72
        messages => session.drain_messages(),
73
        crumbs => crumbs,
74
    };
75
    Ok(Html(
76
        TEMPLATES.get_template("settings.html")?.render(context)?,
77
    ))
78
2
}
79

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

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

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

            
239
    Ok(Redirect::to(&format!(
240
        "{}{}",
241
        &state.root_url_prefix,
242
        SettingsPath.to_uri()
243
    )))
244
2
}
245

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

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

            
305
    let list_owners = db.list_owners(list.pk)?;
306
    let mut list = crate::minijinja_utils::MailingList::from(list);
307
    list.set_safety(list_owners.as_slice(), &state.conf.administrators);
308
    let context = minijinja::context! {
309
        page_title => "Subscription settings",
310
        user => user,
311
        list => list,
312
        subscription => subscription,
313
        current_user => user,
314
        messages => session.drain_messages(),
315
        crumbs => crumbs,
316
    };
317
    Ok(Html(
318
        TEMPLATES
319
            .get_template("settings_subscription.html")?
320
            .render(context)?,
321
    ))
322
}
323

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

            
338
#[allow(non_snake_case)]
339
pub async fn user_list_subscription_POST(
340
    ListSettingsPath(id): ListSettingsPath,
341
    mut session: WritableSession,
342
    Extension(user): Extension<User>,
343
    Form(payload): Form<SubscriptionFormPayload>,
344
    state: Arc<AppState>,
345
) -> Result<Redirect, ResponseError> {
346
    let db = Connection::open_db(state.conf.clone())?;
347

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

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

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

            
376
    let SubscriptionFormPayload {
377
        digest,
378
        hide_address,
379
        receive_duplicates,
380
        receive_own_posts,
381
        receive_confirmation,
382
    } = payload;
383

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

            
398
    db.update_subscription(cset)
399
        .with_status(StatusCode::BAD_REQUEST)?;
400

            
401
    session.add_message(Message {
402
        message: "Settings saved successfully.".into(),
403
        level: Level::Success,
404
    })?;
405

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