1use 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 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 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 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 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 ®istration.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#[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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
127#[serde(rename_all = "lowercase")]
128pub enum EmailOnSignup {
129 Required,
131 Optional,
133 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 ®istration.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}