The smokesignal.events web application
1use anyhow::Result;
2use axum::{
3 Form,
4 extract::Query,
5 response::{IntoResponse, Redirect},
6};
7use axum_htmx::{HxRedirect, HxRequest};
8use axum_template::RenderHtml;
9use http::StatusCode;
10use minijinja::context as template_context;
11use serde::Deserialize;
12use std::borrow::Cow;
13
14use crate::{
15 contextual_error,
16 http::{
17 context::{AdminRequestContext, admin_template_context},
18 errors::WebError,
19 },
20 select_template,
21 storage::{
22 denylist::denylist_add_or_update,
23 identity_profile::{handle_nuke, identity_profile_get},
24 notification::notification_get,
25 },
26};
27
28#[derive(Deserialize)]
29pub(crate) struct IdentityProfileQuery {
30 pub(crate) did: String,
31}
32
33pub(crate) async fn handle_admin_identity_profile(
34 admin_ctx: AdminRequestContext,
35 Query(query): Query<IdentityProfileQuery>,
36) -> Result<impl IntoResponse, WebError> {
37 let canonical_url = format!(
38 "https://{}/admin/identity_profile",
39 admin_ctx.web_context.config.external_base
40 );
41 let default_context = admin_template_context(&admin_ctx, &canonical_url, "identity_profiles");
42
43 let language = admin_ctx.language;
44 let web_context = admin_ctx.web_context;
45
46 let render_template = select_template!("admin_identity_profile", false, false, language);
47 let error_template = select_template!(false, false, language);
48
49 // Get the identity profile
50 let profile = identity_profile_get(&web_context.pool, &query.did).await;
51 if let Err(err) = profile {
52 return contextual_error!(
53 web_context,
54 language.0,
55 error_template,
56 default_context,
57 err
58 );
59 }
60 let profile = profile.unwrap();
61
62 if profile.is_none() {
63 return contextual_error!(
64 web_context,
65 language.0,
66 error_template,
67 default_context,
68 "Identity profile not found"
69 );
70 }
71 let profile = profile.unwrap();
72
73 // Get notification settings (may not exist for all profiles)
74 let notifications = notification_get(&web_context.pool, &query.did).await;
75 let notifications = notifications.ok().flatten();
76
77 // Convert profile to JSON for display
78 let profile_json = serde_json::to_string_pretty(&profile)
79 .unwrap_or_else(|_| "Error formatting JSON".to_string());
80
81 // Convert notifications to JSON if available
82 let notifications_json = notifications.as_ref().map(|n| {
83 serde_json::to_string_pretty(n).unwrap_or_else(|_| "Error formatting JSON".to_string())
84 });
85
86 Ok(RenderHtml(
87 &render_template,
88 web_context.engine.clone(),
89 template_context! { ..default_context, ..template_context! {
90 did => query.did.clone(),
91 profile => profile,
92 profile_json => profile_json,
93 notifications => notifications,
94 notifications_json => notifications_json,
95 }},
96 )
97 .into_response())
98}
99
100#[derive(Deserialize)]
101pub(crate) struct BanDidForm {
102 did: String,
103}
104
105#[derive(Deserialize)]
106pub(crate) struct BanPdsForm {
107 did: String,
108 pds: String,
109}
110
111#[derive(Deserialize)]
112pub(crate) struct NukeIdentityForm {
113 did: String,
114}
115
116pub(crate) async fn handle_admin_identity_profile_ban_did(
117 admin_ctx: AdminRequestContext,
118 HxRequest(hx_request): HxRequest,
119 Form(form): Form<BanDidForm>,
120) -> Result<impl IntoResponse, WebError> {
121 let error_template = select_template!(false, false, admin_ctx.language);
122
123 if form.did == admin_ctx.admin_handle.did {
124 return contextual_error!(
125 admin_ctx.web_context,
126 admin_ctx.language,
127 error_template,
128 template_context! {
129 message => "You cannot ban your own identity."
130 },
131 "You cannot ban your own identity."
132 );
133 }
134
135 let reason = format!(
136 "DID banned by admin {}",
137 admin_ctx.admin_handle.did.replace('\'', "")
138 );
139
140 if let Err(err) = denylist_add_or_update(
141 &admin_ctx.web_context.pool,
142 Cow::Borrowed(&form.did),
143 Cow::Owned(reason),
144 )
145 .await
146 {
147 return contextual_error!(
148 admin_ctx.web_context,
149 admin_ctx.language,
150 error_template,
151 template_context! {},
152 err
153 );
154 }
155
156 let redirect_url = format!("/admin/identity_profile?did={}", form.did);
157 if hx_request {
158 let hx_redirect = HxRedirect::from(redirect_url.as_str());
159 Ok((StatusCode::OK, hx_redirect, "").into_response())
160 } else {
161 Ok(Redirect::to(&redirect_url).into_response())
162 }
163}
164
165pub(crate) async fn handle_admin_identity_profile_ban_pds(
166 admin_ctx: AdminRequestContext,
167 HxRequest(hx_request): HxRequest,
168 Form(form): Form<BanPdsForm>,
169) -> Result<impl IntoResponse, WebError> {
170 let error_template = select_template!(false, false, admin_ctx.language);
171
172 let reason = format!(
173 "PDS banned by admin {}",
174 admin_ctx.admin_handle.did.replace('\'', "")
175 );
176
177 if let Err(err) = denylist_add_or_update(
178 &admin_ctx.web_context.pool,
179 Cow::Borrowed(&form.pds),
180 Cow::Owned(reason),
181 )
182 .await
183 {
184 return contextual_error!(
185 admin_ctx.web_context,
186 admin_ctx.language,
187 error_template,
188 template_context! {},
189 err
190 );
191 }
192
193 let redirect_url = format!("/admin/identity_profile?did={}", form.did);
194 if hx_request {
195 let hx_redirect = HxRedirect::from(redirect_url.as_str());
196 Ok((StatusCode::OK, hx_redirect, "").into_response())
197 } else {
198 Ok(Redirect::to(&redirect_url).into_response())
199 }
200}
201
202pub(crate) async fn handle_admin_identity_profile_nuke(
203 admin_ctx: AdminRequestContext,
204 HxRequest(hx_request): HxRequest,
205 Form(form): Form<NukeIdentityForm>,
206) -> Result<impl IntoResponse, WebError> {
207 let error_template = select_template!(false, false, admin_ctx.language);
208
209 if form.did == admin_ctx.admin_handle.did {
210 return contextual_error!(
211 admin_ctx.web_context,
212 admin_ctx.language,
213 error_template,
214 template_context! {
215 message => "You cannot nuke your own identity."
216 },
217 "You cannot nuke your own identity."
218 );
219 }
220
221 if let Err(err) = handle_nuke(
222 &admin_ctx.web_context.pool,
223 &form.did,
224 &admin_ctx.admin_handle.did,
225 )
226 .await
227 {
228 return contextual_error!(
229 admin_ctx.web_context,
230 admin_ctx.language,
231 error_template,
232 template_context! {},
233 err
234 );
235 }
236
237 if hx_request {
238 let hx_redirect = HxRedirect::from("/admin/identity_profiles");
239 Ok((StatusCode::OK, hx_redirect, "").into_response())
240 } else {
241 Ok(Redirect::to("/admin/identity_profiles").into_response())
242 }
243}