forked from
smokesignal.events/smokesignal
i18n+filtering fork - fluent-templates v2
1use anyhow::Result;
2use axum::{extract::State, response::IntoResponse};
3use axum_extra::extract::{Cached, Form};
4use axum_htmx::HxBoosted;
5use http::StatusCode;
6use minijinja::context as template_context;
7use serde::Deserialize;
8use std::borrow::Cow;
9use unic_langid::LanguageIdentifier;
10
11use crate::{
12 contextual_error, create_renderer,
13 http::{
14 context::WebContext, errors::WebError, middleware_auth::Auth, middleware_i18n::Language,
15 timezones::supported_timezones,
16 },
17 storage::handle::{handle_for_did, handle_update_field, HandleField},
18};
19
20#[derive(Deserialize, Clone, Debug)]
21pub struct TimezoneForm {
22 timezone: String,
23}
24
25#[derive(Deserialize, Clone, Debug)]
26pub struct LanguageForm {
27 language: String,
28}
29
30pub async fn handle_settings(
31 State(web_context): State<WebContext>,
32 Language(language): Language,
33 Cached(auth): Cached<Auth>,
34 HxBoosted(hx_boosted): HxBoosted,
35) -> Result<impl IntoResponse, WebError> {
36 // Require authentication
37 let current_handle = auth.require(&web_context.config.destination_key, "/settings")?;
38
39 // Create the template renderer with enhanced context
40 let renderer = create_renderer!(web_context.clone(), Language(language), hx_boosted, false);
41
42 let canonical_url = format!("https://{}/settings", web_context.config.external_base);
43
44 // Get available timezones
45 let (_, timezones) = supported_timezones(Some(¤t_handle));
46
47 // Get the list of supported languages
48 let supported_languages = web_context
49 .i18n_context
50 .supported_languages
51 .iter()
52 .map(|lang| lang.to_string())
53 .collect::<Vec<String>>();
54
55 // Render the form
56 Ok((
57 StatusCode::OK,
58 renderer.render_template(
59 "settings",
60 template_context! {
61 timezones => timezones,
62 languages => supported_languages,
63 },
64 Some(¤t_handle),
65 &canonical_url,
66 ),
67 )
68 .into_response())
69}
70
71#[tracing::instrument(skip_all, err)]
72pub async fn handle_timezone_update(
73 State(web_context): State<WebContext>,
74 Language(language): Language,
75 Cached(auth): Cached<Auth>,
76 Form(timezone_form): Form<TimezoneForm>,
77) -> Result<impl IntoResponse, WebError> {
78 let current_handle = auth.require_flat()?;
79
80 // Create the template renderer for HTMX content replacement
81 let renderer = create_renderer!(web_context.clone(), Language(language), false, false);
82
83 let canonical_url = format!("https://{}/settings", web_context.config.external_base);
84
85 let (_, timezones) = supported_timezones(Some(¤t_handle));
86
87 if timezone_form.timezone.is_empty()
88 || timezone_form.timezone == current_handle.tz
89 || !timezones.contains(&timezone_form.timezone.as_str())
90 {
91 return contextual_error!(renderer: renderer, "Invalid timezone", template_context!{});
92 }
93
94 if let Err(err) = handle_update_field(
95 &web_context.pool,
96 ¤t_handle.did,
97 HandleField::Timezone(Cow::Owned(timezone_form.timezone)),
98 )
99 .await
100 {
101 return contextual_error!(renderer: renderer, err, template_context!{});
102 }
103
104 let current_handle = match handle_for_did(&web_context.pool, ¤t_handle.did).await {
105 Ok(value) => value,
106 Err(err) => {
107 return contextual_error!(renderer: renderer, err, template_context!{});
108 }
109 };
110
111 Ok((
112 StatusCode::OK,
113 renderer.render_template(
114 "settings.tz",
115 template_context! {
116 timezone_updated => true,
117 timezones,
118 },
119 Some(¤t_handle),
120 &canonical_url,
121 ),
122 )
123 .into_response())
124}
125
126#[tracing::instrument(skip_all, err)]
127pub async fn handle_language_update(
128 State(web_context): State<WebContext>,
129 Language(language): Language,
130 Cached(auth): Cached<Auth>,
131 Form(language_form): Form<LanguageForm>,
132) -> Result<impl IntoResponse, WebError> {
133 let current_handle = auth.require_flat()?;
134
135 // Create the template renderer for HTMX content replacement
136 let renderer = create_renderer!(web_context.clone(), Language(language), false, false);
137
138 let canonical_url = format!("https://{}/settings", web_context.config.external_base);
139
140 // Get the list of supported languages
141 let supported_languages = web_context
142 .i18n_context
143 .supported_languages
144 .iter()
145 .map(|lang| lang.to_string())
146 .collect::<Vec<String>>();
147
148 if language_form.language.is_empty() || language_form.language == current_handle.language {
149 return contextual_error!(renderer: renderer, "Invalid language", template_context!{});
150 }
151
152 let lang_id = match language_form.language.parse::<LanguageIdentifier>() {
153 Ok(value) => value,
154 Err(err) => {
155 return contextual_error!(renderer: renderer, err, template_context!{});
156 }
157 };
158
159 if !web_context
160 .i18n_context
161 .supported_languages
162 .iter()
163 .any(|l| l == &lang_id)
164 {
165 return contextual_error!(renderer: renderer, "Invalid language", template_context!{});
166 }
167
168 if let Err(err) = handle_update_field(
169 &web_context.pool,
170 ¤t_handle.did,
171 HandleField::Language(Cow::Owned(language_form.language)),
172 )
173 .await
174 {
175 return contextual_error!(renderer: renderer, err, template_context!{});
176 }
177
178 let current_handle = match handle_for_did(&web_context.pool, ¤t_handle.did).await {
179 Ok(value) => value,
180 Err(err) => {
181 return contextual_error!(renderer: renderer, err, template_context!{});
182 }
183 };
184
185 // Create a new renderer with the updated language for proper template selection
186 let new_renderer = create_renderer!(web_context.clone(), Language(lang_id), false, false);
187
188 Ok((
189 StatusCode::OK,
190 new_renderer.render_template(
191 "settings.language",
192 template_context! {
193 language_updated => true,
194 languages => supported_languages,
195 },
196 Some(¤t_handle),
197 &canonical_url,
198 ),
199 )
200 .into_response())
201}