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 private_event_content::{private_event_content_delete, private_event_content_get},
24 },
25};
26
27#[derive(Deserialize)]
28pub(crate) struct ContentRecordQuery {
29 pub(crate) aturi: String,
30}
31
32pub(crate) async fn handle_admin_content_view(
33 admin_ctx: AdminRequestContext,
34 Query(query): Query<ContentRecordQuery>,
35) -> Result<impl IntoResponse, WebError> {
36 let canonical_url = format!(
37 "https://{}/admin/content/view",
38 admin_ctx.web_context.config.external_base
39 );
40 let default_context = admin_template_context(&admin_ctx, &canonical_url, "content");
41
42 let language = admin_ctx.language;
43 let web_context = admin_ctx.web_context;
44
45 let render_template = select_template!("admin_content_view", false, false, language);
46 let error_template = select_template!(false, false, language);
47
48 // Get the content record by AT-URI
49 let content = private_event_content_get(&web_context.pool, &query.aturi).await;
50 if let Err(err) = content {
51 return contextual_error!(
52 web_context,
53 language.0,
54 error_template,
55 default_context,
56 err
57 );
58 }
59 let content = content.unwrap();
60
61 if content.is_none() {
62 return contextual_error!(
63 web_context,
64 language.0,
65 error_template,
66 default_context,
67 "Content not found"
68 );
69 }
70 let content = content.unwrap();
71
72 // Convert to JSON for display
73 let content_json = serde_json::to_string_pretty(&content)
74 .unwrap_or_else(|_| "Error formatting JSON".to_string());
75
76 Ok(RenderHtml(
77 &render_template,
78 web_context.engine.clone(),
79 template_context! { ..default_context, ..template_context! {
80 aturi => query.aturi.clone(),
81 content => content,
82 content_json => content_json,
83 }},
84 )
85 .into_response())
86}
87
88#[derive(Deserialize)]
89pub(crate) struct NukeContentForm {
90 aturi: String,
91}
92
93pub(crate) async fn handle_admin_content_nuke(
94 admin_ctx: AdminRequestContext,
95 HxRequest(hx_request): HxRequest,
96 Form(form): Form<NukeContentForm>,
97) -> Result<impl IntoResponse, WebError> {
98 let error_template = select_template!(false, false, admin_ctx.language);
99
100 // Delete the content record
101 if let Err(err) = private_event_content_delete(&admin_ctx.web_context.pool, &form.aturi).await {
102 return contextual_error!(
103 admin_ctx.web_context,
104 admin_ctx.language,
105 error_template,
106 template_context! {},
107 err
108 );
109 }
110
111 // Add content AT-URI to denylist
112 let reason = format!(
113 "Content nuked by admin {}",
114 admin_ctx.admin_handle.did.replace('\'', "")
115 );
116
117 if let Err(err) = denylist_add_or_update(
118 &admin_ctx.web_context.pool,
119 Cow::Borrowed(&form.aturi),
120 Cow::Owned(reason),
121 )
122 .await
123 {
124 return contextual_error!(
125 admin_ctx.web_context,
126 admin_ctx.language,
127 error_template,
128 template_context! {},
129 err
130 );
131 }
132
133 if hx_request {
134 let hx_redirect = HxRedirect::from("/admin/content");
135 Ok((StatusCode::OK, hx_redirect, "").into_response())
136 } else {
137 Ok(Redirect::to("/admin/content").into_response())
138 }
139}