The smokesignal.events web application
at main 139 lines 3.9 kB view raw
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}