i18n+filtering fork - fluent-templates v2
at main 268 lines 8.3 kB view raw
1use anyhow::Result; 2use axum::{ 3 extract::Form, 4 response::{IntoResponse, Redirect}, 5}; 6use serde::Deserialize; 7 8use crate::{ 9 atproto::{ 10 lexicon::{ 11 community::lexicon::calendar::event::Event as CommunityEventLexicon, 12 events::smokesignal::calendar::event::{Event as SmokeSignalEvent, EventResponse}, 13 }, 14 uri::parse_aturi, 15 }, 16 contextual_error, 17 http::{ 18 context::{admin_template_context, AdminRequestContext}, 19 errors::{AdminImportEventError, CommonError, LoginError, WebError}, 20 }, 21 resolve::{parse_input, resolve_subject, InputType}, 22 select_template, 23 storage::{event::event_insert_with_metadata, handle::handle_warm_up}, 24}; 25 26#[derive(Deserialize)] 27pub struct ImportEventForm { 28 pub aturi: String, 29} 30 31pub async fn handle_admin_import_event( 32 admin_ctx: AdminRequestContext, 33 Form(form): Form<ImportEventForm>, 34) -> Result<impl IntoResponse, WebError> { 35 // Admin access is already verified by the extractor 36 let canonical_url = format!( 37 "https://{}/admin/events", 38 admin_ctx.web_context.config.external_base 39 ); 40 let default_context = admin_template_context(&admin_ctx, &canonical_url); 41 42 let error_template = select_template!(false, false, admin_ctx.language); 43 44 // Parse the AT-URI 45 let aturi = form.aturi.trim(); 46 let (repository, collection, rkey) = match parse_aturi(aturi) { 47 Ok(parsed) => parsed, 48 Err(_err) => { 49 return contextual_error!( 50 admin_ctx.web_context, 51 admin_ctx.language, 52 error_template, 53 default_context, 54 CommonError::InvalidAtUri 55 ); 56 } 57 }; 58 59 // Determine event type based on collection 60 let event_format = match collection.as_str() { 61 "events.smokesignal.calendar.event" => "smokesignal", 62 "community.lexicon.calendar.event" => "community", 63 _ => { 64 return contextual_error!( 65 admin_ctx.web_context, 66 admin_ctx.language, 67 error_template, 68 default_context, 69 CommonError::UnsupportedEventType 70 ); 71 } 72 }; 73 74 // Resolve the DID for the repository 75 let input_type = match parse_input(&repository) { 76 Ok(input) => input, 77 Err(_err) => { 78 return contextual_error!( 79 admin_ctx.web_context, 80 admin_ctx.language, 81 error_template, 82 default_context, 83 CommonError::FailedToParse 84 ); 85 } 86 }; 87 88 let did = match input_type { 89 InputType::Handle(handle) => { 90 match resolve_subject( 91 &admin_ctx.web_context.http_client, 92 &admin_ctx.web_context.dns_resolver, 93 &handle, 94 ) 95 .await 96 { 97 Ok(did) => did, 98 Err(_err) => { 99 return contextual_error!( 100 admin_ctx.web_context, 101 admin_ctx.language, 102 error_template, 103 default_context, 104 CommonError::FailedToParse 105 ); 106 } 107 } 108 } 109 InputType::Plc(did) | InputType::Web(did) => did, 110 }; 111 112 // Get the DID document to find the PDS endpoint 113 let did_doc = match crate::did::plc::query( 114 &admin_ctx.web_context.http_client, 115 &admin_ctx.web_context.config.plc_hostname, 116 &did, 117 ) 118 .await 119 { 120 Ok(doc) => doc, 121 Err(_err) => { 122 return contextual_error!( 123 admin_ctx.web_context, 124 admin_ctx.language, 125 error_template, 126 default_context, 127 CommonError::FailedToParse 128 ); 129 } 130 }; 131 132 // Insert the handle if it doesn't exist 133 if let Some(handle) = did_doc.primary_handle() { 134 if let Some(pds) = did_doc.pds_endpoint() { 135 if let Err(err) = handle_warm_up(&admin_ctx.web_context.pool, &did, handle, pds).await { 136 tracing::warn!("Failed to insert handle: {}", err); 137 } 138 } 139 } 140 141 // Get the PDS endpoint 142 let pds_endpoint = match did_doc.pds_endpoint() { 143 Some(endpoint) => endpoint, 144 None => { 145 return contextual_error!( 146 admin_ctx.web_context, 147 admin_ctx.language, 148 error_template, 149 default_context, 150 WebError::Login(LoginError::NoPDS) 151 ); 152 } 153 }; 154 155 // Construct the XRPC request to get the record 156 let url = format!( 157 "{}/xrpc/com.atproto.repo.getRecord?repo={}&collection={}&rkey={}", 158 pds_endpoint, did, collection, rkey 159 ); 160 161 let response = match admin_ctx.web_context.http_client.get(&url).send().await { 162 Ok(resp) => resp, 163 Err(_err) => { 164 return contextual_error!( 165 admin_ctx.web_context, 166 admin_ctx.language, 167 error_template, 168 default_context, 169 CommonError::RecordNotFound 170 ); 171 } 172 }; 173 174 // Parse the record based on collection type 175 if event_format == "smokesignal" { 176 // Handle SmokeSignal event format 177 let record = match response.json::<EventResponse>().await { 178 Ok(record) => record, 179 Err(_err) => { 180 return contextual_error!( 181 admin_ctx.web_context, 182 admin_ctx.language, 183 error_template, 184 default_context, 185 CommonError::FailedToParse 186 ); 187 } 188 }; 189 190 // Get name from SmokeSignal event format 191 let name = match &record.value { 192 SmokeSignalEvent::Current { name, .. } => name.clone(), 193 }; 194 195 // Store event using the generic event_insert_with_metadata 196 match event_insert_with_metadata( 197 &admin_ctx.web_context.pool, 198 aturi, 199 &record.cid, 200 &did, 201 "events.smokesignal.calendar.event", 202 &record.value, 203 &name, 204 ) 205 .await 206 { 207 Ok(_) => Ok(Redirect::to("/admin/events").into_response()), 208 Err(err) => { 209 contextual_error!( 210 admin_ctx.web_context, 211 admin_ctx.language, 212 error_template, 213 default_context, 214 AdminImportEventError::InsertFailed(err.to_string()) 215 ) 216 } 217 } 218 } else { 219 // Handle Community Lexicon event format 220 #[derive(serde::Deserialize)] 221 struct CommunityResponse { 222 cid: String, 223 value: CommunityEventLexicon, 224 } 225 226 let record = match response.json::<CommunityResponse>().await { 227 Ok(record) => record, 228 Err(_err) => { 229 return contextual_error!( 230 admin_ctx.web_context, 231 admin_ctx.language, 232 error_template, 233 default_context, 234 CommonError::FailedToParse 235 ); 236 } 237 }; 238 239 // Get name from Community event format 240 let name = match &record.value { 241 CommunityEventLexicon::Current { name, .. } => name.clone(), 242 }; 243 244 // Store event using the generic event_insert_with_metadata 245 match event_insert_with_metadata( 246 &admin_ctx.web_context.pool, 247 aturi, 248 &record.cid, 249 &did, 250 "community.lexicon.calendar.event", 251 &record.value, 252 &name, 253 ) 254 .await 255 { 256 Ok(_) => Ok(Redirect::to("/admin/events").into_response()), 257 Err(err) => { 258 contextual_error!( 259 admin_ctx.web_context, 260 admin_ctx.language, 261 error_template, 262 default_context, 263 AdminImportEventError::InsertFailed(err.to_string()) 264 ) 265 } 266 } 267 } 268}