use anyhow::Result; use axum::{extract::Path, response::IntoResponse}; use axum_extra::extract::Form; use axum_template::RenderHtml; use http::StatusCode; use minijinja::context as template_context; use serde::{Deserialize, Serialize}; use atproto_client::com::atproto::repo::{DeleteRecordRequest, delete_record}; use crate::{ atproto::auth::create_dpop_auth_from_session, contextual_error, http::{ context::UserRequestContext, errors::{DeleteEventError, WebError}, middleware_auth::Auth, }, select_template, storage::event::{event_delete, event_exists}, }; use atproto_record::lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID; #[derive(Debug, Deserialize, Serialize)] pub struct DeleteEventForm { pub confirm: Option, } pub(crate) async fn handle_delete_event( ctx: UserRequestContext, Path((handle_slug, event_rkey)): Path<(String, String)>, Form(form): Form, ) -> Result { let (current_handle, session) = match &ctx.auth { Auth::Authenticated { profile, session } => (profile.clone(), session), Auth::Unauthenticated => { return Ok(StatusCode::FORBIDDEN.into_response()); } }; let default_context = template_context! { current_handle, language => ctx.language.to_string(), canonical_url => format!("https://{}/{}/{}/delete", ctx.web_context.config.external_base, handle_slug, event_rkey), event_url => format!("/{}/{}", handle_slug, event_rkey), }; let error_template = select_template!(false, false, ctx.language); let render_template = select_template!("delete_event", false, false, ctx.language); let lookup_aturi = format!( "at://{}/{}/{}", current_handle.did, LexiconCommunityEventNSID, event_rkey ); // Get the event let event_exists = event_exists(&ctx.web_context.pool, &lookup_aturi).await; if let Err(err) = event_exists { return contextual_error!( ctx.web_context, ctx.language, error_template, default_context, err ); } if !event_exists.unwrap() { return contextual_error!( ctx.web_context, ctx.language, error_template, default_context, DeleteEventError::EventNotFound { aturi: lookup_aturi.clone() } ); } // If form is submitted with confirmation or it's a non-HTMX POST, proceed with deletion if form.confirm.as_deref() == Some("true") { // Create DPoP authentication from session let dpop_auth = match create_dpop_auth_from_session(session) { Ok(auth) => auth, Err(err) => { tracing::error!("Failed to create DPoP auth from OAuth session: {}", err); return contextual_error!( ctx.web_context, ctx.language, error_template, default_context, err, StatusCode::INTERNAL_SERVER_ERROR ); } }; // Delete from PDS first let delete_record_request = DeleteRecordRequest { repo: current_handle.did.clone(), collection: LexiconCommunityEventNSID.to_string(), record_key: event_rkey.clone(), swap_commit: None, swap_record: None, }; match delete_record( &ctx.web_context.http_client, &atproto_client::client::Auth::DPoP(dpop_auth), ¤t_handle.pds, delete_record_request, ) .await { Ok(_) => {} Err(err) => { tracing::error!("Failed to delete event from PDS: {}", err); return contextual_error!( ctx.web_context, ctx.language, error_template, default_context, err, StatusCode::INTERNAL_SERVER_ERROR ); } } // Delete from local storage if let Err(err) = event_delete(&ctx.web_context.pool, &lookup_aturi).await { tracing::error!("Failed to delete event from local storage: {}", err); return contextual_error!( ctx.web_context, ctx.language, error_template, default_context, err, StatusCode::INTERNAL_SERVER_ERROR ); } let render_template = select_template!("alert", false, false, ctx.language); // TODO: Localize these strings. return Ok(RenderHtml( &render_template, ctx.web_context.engine.clone(), template_context! { ..default_context, ..template_context! { message_type => "info", message_title => "Event Deleted Successfully", message => "The event has been deleted from your Personal Data Server (PDS) and removed this Smoke Signal instance.", }}, ) .into_response()); } Ok(RenderHtml( &render_template, ctx.web_context.engine.clone(), template_context! { ..default_context, ..template_context! { show_confirm => true, event_url => format!("/{}/{}", handle_slug, event_rkey), }}, ) .into_response()) }