forked from
smokesignal.events/smokesignal
i18n+filtering fork - fluent-templates v2
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}