torrust_index/services/
settings.rs

1//! Settings service.
2use std::fmt;
3use std::str::FromStr;
4use std::sync::Arc;
5
6use serde::{Deserialize, Serialize};
7use url::Url;
8
9use super::authorization::{self, ACTION};
10use crate::config::{self, Configuration, Settings};
11use crate::errors::ServiceError;
12use crate::models::user::UserId;
13
14pub struct Service {
15    configuration: Arc<Configuration>,
16    authorization_service: Arc<authorization::Service>,
17}
18
19impl Service {
20    #[must_use]
21    pub fn new(configuration: Arc<Configuration>, authorization_service: Arc<authorization::Service>) -> Service {
22        Service {
23            configuration,
24            authorization_service,
25        }
26    }
27
28    /// It gets all the settings.
29    ///
30    /// # Errors
31    ///
32    /// It returns an error if the user does not have the required permissions.
33    pub async fn get_all(&self, maybe_user_id: Option<UserId>) -> Result<Settings, ServiceError> {
34        self.authorization_service
35            .authorize(ACTION::GetSettings, maybe_user_id)
36            .await?;
37
38        let torrust_index_configuration = self.configuration.get_all().await;
39
40        Ok(torrust_index_configuration)
41    }
42
43    /// It gets all the settings making the secrets with asterisks.
44    ///
45    /// # Errors
46    ///
47    /// It returns an error if the user does not have the required permissions.
48    pub async fn get_all_masking_secrets(&self, maybe_user_id: Option<UserId>) -> Result<Settings, ServiceError> {
49        self.authorization_service
50            .authorize(ACTION::GetSettingsSecret, maybe_user_id)
51            .await?;
52
53        let mut torrust_index_configuration = self.configuration.get_all().await;
54
55        torrust_index_configuration.remove_secrets();
56
57        Ok(torrust_index_configuration)
58    }
59
60    /// It gets only the public settings.
61    ///
62    /// # Errors
63    ///
64    /// It returns an error if the user does not have the required permissions.
65    pub async fn get_public(&self, maybe_user_id: Option<UserId>) -> Result<ConfigurationPublic, ServiceError> {
66        self.authorization_service
67            .authorize(ACTION::GetPublicSettings, maybe_user_id)
68            .await?;
69
70        let settings_lock = self.configuration.get_all().await;
71        Ok(extract_public_settings(&settings_lock))
72    }
73
74    /// It gets the site name from the settings.
75    ///
76    /// # Errors
77    ///
78    /// It returns an error if the user does not have the required permissions.
79    pub async fn get_site_name(&self, maybe_user_id: Option<UserId>) -> Result<String, ServiceError> {
80        self.authorization_service
81            .authorize(ACTION::GetSiteName, maybe_user_id)
82            .await?;
83
84        Ok(self.configuration.get_site_name().await)
85    }
86}
87
88fn extract_public_settings(settings: &Settings) -> ConfigurationPublic {
89    let email_on_signup = match &settings.registration {
90        Some(registration) => match &registration.email {
91            Some(email) => {
92                if email.required {
93                    EmailOnSignup::Required
94                } else {
95                    EmailOnSignup::Optional
96                }
97            }
98            None => EmailOnSignup::NotIncluded,
99        },
100        None => EmailOnSignup::NotIncluded,
101    };
102
103    ConfigurationPublic {
104        website_name: settings.website.name.clone(),
105        tracker_url: settings.tracker.url.clone(),
106        tracker_listed: settings.tracker.listed,
107        tracker_private: settings.tracker.private,
108        email_on_signup,
109        website: settings.website.clone().into(),
110    }
111}
112
113/// The public index configuration.
114/// There is an endpoint to get this configuration.
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub struct ConfigurationPublic {
117    website_name: String,
118    tracker_url: Url,
119    tracker_listed: bool,
120    tracker_private: bool,
121    email_on_signup: EmailOnSignup,
122    website: Website,
123}
124
125/// Whether the email is required on signup or not.
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
127#[serde(rename_all = "lowercase")]
128pub enum EmailOnSignup {
129    /// The email is required on signup.
130    Required,
131    /// The email is optional on signup.
132    Optional,
133    /// The email is not allowed on signup. It will only be ignored if provided.
134    NotIncluded,
135}
136
137impl Default for EmailOnSignup {
138    fn default() -> Self {
139        Self::Optional
140    }
141}
142
143impl fmt::Display for EmailOnSignup {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        let display_str = match self {
146            EmailOnSignup::Required => "required",
147            EmailOnSignup::Optional => "optional",
148            EmailOnSignup::NotIncluded => "ignored",
149        };
150        write!(f, "{display_str}")
151    }
152}
153
154impl FromStr for EmailOnSignup {
155    type Err = String;
156
157    fn from_str(s: &str) -> Result<Self, Self::Err> {
158        match s.to_lowercase().as_str() {
159            "required" => Ok(EmailOnSignup::Required),
160            "optional" => Ok(EmailOnSignup::Optional),
161            "none" => Ok(EmailOnSignup::NotIncluded),
162            _ => Err(format!(
163                "Unknown config 'email_on_signup' option (required, optional, none): {s}"
164            )),
165        }
166    }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub struct Website {
171    pub name: String,
172    pub demo: Option<Demo>,
173    pub terms: Terms,
174}
175
176impl From<config::Website> for Website {
177    fn from(website: config::Website) -> Self {
178        Self {
179            name: website.name,
180            demo: website.demo.map(std::convert::Into::into),
181            terms: website.terms.into(),
182        }
183    }
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
187pub struct Demo {
188    pub warning: String,
189}
190
191impl From<config::Demo> for Demo {
192    fn from(demo: config::Demo) -> Self {
193        Self { warning: demo.warning }
194    }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
198pub struct Terms {
199    pub page: TermsPage,
200    pub upload: TermsUpload,
201}
202
203impl From<config::Terms> for Terms {
204    fn from(terms: config::Terms) -> Self {
205        Self {
206            page: terms.page.into(),
207            upload: terms.upload.into(),
208        }
209    }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
213pub struct TermsPage {
214    pub title: String,
215    pub content: Markdown,
216}
217
218impl From<config::TermsPage> for TermsPage {
219    fn from(terms_page: config::TermsPage) -> Self {
220        Self {
221            title: terms_page.title,
222            content: terms_page.content.into(),
223        }
224    }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
228pub struct TermsUpload {
229    pub content_upload_agreement: Markdown,
230}
231
232impl From<config::TermsUpload> for TermsUpload {
233    fn from(terms_upload: config::TermsUpload) -> Self {
234        Self {
235            content_upload_agreement: terms_upload.content_upload_agreement.into(),
236        }
237    }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
241pub struct Markdown(pub String);
242
243impl Markdown {
244    fn new(content: &str) -> Self {
245        Self(content.to_owned())
246    }
247}
248
249impl From<config::Markdown> for Markdown {
250    fn from(markdown: config::Markdown) -> Self {
251        Self::new(&markdown.source())
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use crate::config::Configuration;
258    use crate::services::settings::{extract_public_settings, ConfigurationPublic, EmailOnSignup};
259
260    #[tokio::test]
261    async fn configuration_should_return_only_public_settings() {
262        let configuration = Configuration::default();
263        let all_settings = configuration.get_all().await;
264
265        let email_on_signup = match &all_settings.registration {
266            Some(registration) => match &registration.email {
267                Some(email) => {
268                    if email.required {
269                        EmailOnSignup::Required
270                    } else {
271                        EmailOnSignup::Optional
272                    }
273                }
274                None => EmailOnSignup::NotIncluded,
275            },
276            None => EmailOnSignup::NotIncluded,
277        };
278
279        assert_eq!(
280            extract_public_settings(&all_settings),
281            ConfigurationPublic {
282                website_name: all_settings.website.name.clone(),
283                tracker_url: all_settings.tracker.url,
284                tracker_listed: all_settings.tracker.listed,
285                tracker_private: all_settings.tracker.private,
286                email_on_signup,
287                website: all_settings.website.into(),
288            }
289        );
290    }
291}