The smokesignal.events web application

chore: formatting

Signed-off-by: Nick Gerakines <nick.gerakines@gmail.com>

+359 -276
+4 -3
src/atproto/lexicon/events_smokesignal_calendar_event.rs
··· 143 143 } = &event_response.value; 144 144 145 145 assert_eq!(name, "Pigeons Playing Ping Pong @ Neptune Theatre"); 146 - assert!(text 147 - .as_ref() 148 - .is_some_and(|value| value == "Pigeons Playing Ping Pong @ Neptune Theatre")); 146 + assert!( 147 + text.as_ref() 148 + .is_some_and(|value| value == "Pigeons Playing Ping Pong @ Neptune Theatre") 149 + ); 149 150 150 151 // Verify datetime fields are present and correctly parsed 151 152 assert!(starts_at.is_some(), "Expected starts_at to be present");
+8 -6
src/bin/crypto.rs
··· 1 1 use std::env; 2 2 3 - use base64::{engine::general_purpose, Engine as _}; 3 + use base64::{Engine as _, engine::general_purpose}; 4 4 use rand::RngCore; 5 5 6 6 fn main() { 7 7 let mut rng = rand::thread_rng(); 8 8 9 - env::args().for_each(|arg| if arg.as_str() == "key" { 10 - let mut key: [u8; 64] = [0; 64]; 11 - rng.fill_bytes(&mut key); 12 - let encoded: String = general_purpose::STANDARD_NO_PAD.encode(key); 13 - println!("{encoded}"); 9 + env::args().for_each(|arg| { 10 + if arg.as_str() == "key" { 11 + let mut key: [u8; 64] = [0; 64]; 12 + rng.fill_bytes(&mut key); 13 + let encoded: String = general_purpose::STANDARD_NO_PAD.encode(key); 14 + println!("{encoded}"); 15 + } 14 16 }); 15 17 }
+1 -1
src/bin/smokesignal.rs
··· 1 1 use anyhow::Result; 2 2 use atproto_identity::key::identify_key; 3 - use atproto_identity::resolve::{create_resolver, IdentityResolver, InnerIdentityResolver}; 3 + use atproto_identity::resolve::{IdentityResolver, InnerIdentityResolver, create_resolver}; 4 4 use atproto_oauth_axum::state::OAuthClientConfig; 5 5 use smokesignal::{ 6 6 http::{
+2 -2
src/config.rs
··· 1 1 use anyhow::{Context, Result}; 2 - use atproto_identity::key::{identify_key, to_public, KeyData, KeyType}; 2 + use atproto_identity::key::{KeyData, KeyType, identify_key, to_public}; 3 3 use axum_extra::extract::cookie::Key; 4 - use base64::{engine::general_purpose, Engine as _}; 4 + use base64::{Engine as _, engine::general_purpose}; 5 5 use ordermap::OrderMap; 6 6 7 7 use crate::config_errors::ConfigError;
+3 -1
src/config_errors.rs
··· 130 130 /// 131 131 /// This error occurs when oauth_backend is set to "aip" but 132 132 /// required AIP configuration values are missing. 133 - #[error("error-config-18 When oauth_backend is 'aip', AIP_HOSTNAME, AIP_CLIENT_ID, and AIP_CLIENT_SECRET must all be set")] 133 + #[error( 134 + "error-config-18 When oauth_backend is 'aip', AIP_HOSTNAME, AIP_CLIENT_ID, and AIP_CLIENT_SECRET must all be set" 135 + )] 134 136 AipConfigurationIncomplete, 135 137 136 138 /// Error when oauth_backend has an invalid value.
+1 -1
src/http/cache_countries.rs
··· 1 - use anyhow::{anyhow, Result}; 1 + use anyhow::{Result, anyhow}; 2 2 use once_cell::sync::OnceCell; 3 3 use std::{collections::BTreeMap, sync::Arc}; 4 4
-17
src/http/errors/middleware_errors.rs
··· 25 25 SerializeFailed(serde_json::Error), 26 26 } 27 27 28 - /// Represents errors that can occur during authentication middleware operations. 29 - /// 30 - /// These errors typically happen in the authentication middleware layer when 31 - /// processing requests, including cryptographic operations and session validation. 32 - #[derive(Debug, Error)] 33 - pub(crate) enum AuthMiddlewareError { 34 - /// Error when content signing fails. 35 - /// 36 - /// This error occurs when the authentication middleware attempts to 37 - /// cryptographically sign content but the operation fails. 38 - #[error("error-authmiddleware-1 Unable to sign content: {0:?}")] 39 - SigningFailed(anyhow::Error), 40 - } 41 - 42 28 #[derive(Debug, Error)] 43 29 pub(crate) enum MiddlewareAuthError { 44 30 #[error("error-middleware-auth-1 Access Denied: {0}")] ··· 49 35 50 36 #[error("error-middleware-auth-3 Unhandled Auth Error: {0:?}")] 51 37 Anyhow(#[from] anyhow::Error), 52 - 53 - #[error(transparent)] 54 - AuthError(#[from] AuthMiddlewareError), 55 38 } 56 39 57 40 impl IntoResponse for MiddlewareAuthError {
+3 -1
src/http/errors/migrate_rsvp_error.rs
··· 11 11 /// This error occurs when attempting to migrate an RSVP with a status 12 12 /// that doesn't match one of the expected values ('going', 'interested', 13 13 /// or 'notgoing'). 14 - #[error("error-migrate-rsvp-1 Invalid RSVP status: {0}. Expected 'going', 'interested', or 'notgoing'.")] 14 + #[error( 15 + "error-migrate-rsvp-1 Invalid RSVP status: {0}. Expected 'going', 'interested', or 'notgoing'." 16 + )] 15 17 InvalidRsvpStatus(String), 16 18 17 19 /// Error when a user is not authorized to migrate an RSVP.
+1 -1
src/http/errors/mod.rs
··· 21 21 pub(crate) use event_view_errors::EventViewError; 22 22 pub(crate) use import_error::ImportError; 23 23 pub(crate) use login_error::LoginError; 24 - pub(crate) use middleware_errors::{AuthMiddlewareError, WebSessionError}; 24 + pub(crate) use middleware_errors::WebSessionError; 25 25 pub(crate) use migrate_event_error::MigrateEventError; 26 26 pub(crate) use migrate_rsvp_error::MigrateRsvpError; 27 27 pub(crate) use rsvp_error::RSVPError;
+1 -1
src/http/event_view.rs
··· 17 17 }, 18 18 http::utils::truncate_text, 19 19 storage::{ 20 + StoragePool, 20 21 errors::StorageError, 21 22 event::{ 22 23 count_event_rsvps, extract_event_details, get_event_rsvp_counts, 23 24 model::{Event, EventWithRole}, 24 25 }, 25 26 identity_profile::{handles_by_did, model::IdentityProfile}, 26 - StoragePool, 27 27 }, 28 28 }; 29 29
+2 -2
src/http/handle_admin_denylist.rs
··· 1 1 use anyhow::Result; 2 2 use axum::{ 3 + Form, 3 4 extract::Query, 4 5 response::{IntoResponse, Redirect}, 5 - Form, 6 6 }; 7 7 use axum_template::RenderHtml; 8 8 use minijinja::context as template_context; ··· 12 12 use crate::{ 13 13 contextual_error, 14 14 http::{ 15 - context::{admin_template_context, AdminRequestContext}, 15 + context::{AdminRequestContext, admin_template_context}, 16 16 errors::WebError, 17 17 pagination::{Pagination, PaginationView}, 18 18 },
+1 -1
src/http/handle_admin_handles.rs
··· 11 11 use crate::{ 12 12 contextual_error, 13 13 http::{ 14 - context::{admin_template_context, AdminRequestContext}, 14 + context::{AdminRequestContext, admin_template_context}, 15 15 errors::WebError, 16 16 pagination::{Pagination, PaginationView}, 17 17 },
+1 -1
src/http/handle_admin_import_event.rs
··· 16 16 }, 17 17 contextual_error, 18 18 http::{ 19 - context::{admin_template_context, AdminRequestContext}, 19 + context::{AdminRequestContext, admin_template_context}, 20 20 errors::{AdminImportEventError, CommonError, LoginError, WebError}, 21 21 }, 22 22 select_template,
+4 -4
src/http/handle_admin_import_rsvp.rs
··· 13 13 use crate::{ 14 14 atproto::lexicon::{ 15 15 community::lexicon::calendar::rsvp::{ 16 - Rsvp as CommunityRsvpLexicon, RsvpStatus as CommunityRsvpStatusLexicon, 17 - NSID as COMMUNITY_RSVP_NSID, 16 + NSID as COMMUNITY_RSVP_NSID, Rsvp as CommunityRsvpLexicon, 17 + RsvpStatus as CommunityRsvpStatusLexicon, 18 18 }, 19 19 events::smokesignal::calendar::rsvp::{ 20 - Rsvp as SmokesignalRsvpLexicon, NSID as SMOKESIGNAL_RSVP_NSID, 20 + NSID as SMOKESIGNAL_RSVP_NSID, Rsvp as SmokesignalRsvpLexicon, 21 21 }, 22 22 }, 23 23 contextual_error, 24 24 http::{ 25 - context::{admin_template_context, AdminRequestContext}, 25 + context::{AdminRequestContext, admin_template_context}, 26 26 errors::{AdminImportRsvpError, CommonError, LoginError, WebError}, 27 27 }, 28 28 select_template,
+1 -1
src/http/handle_admin_index.rs
··· 3 3 use axum_template::RenderHtml; 4 4 use minijinja::context as template_context; 5 5 6 - use crate::http::context::{admin_template_context, AdminRequestContext}; 6 + use crate::http::context::{AdminRequestContext, admin_template_context}; 7 7 8 8 use super::errors::WebError; 9 9
+2 -2
src/http/handle_create_event.rs
··· 1 - use std::collections::HashMap; 2 1 use anyhow::Result; 3 2 use axum::extract::State; 4 3 use axum::response::IntoResponse; ··· 11 10 use http::Method; 12 11 use http::StatusCode; 13 12 use minijinja::context as template_context; 13 + use std::collections::HashMap; 14 14 15 15 use crate::atproto::auth::{ 16 16 create_dpop_auth_from_aip_session, create_dpop_auth_from_oauth_session, ··· 54 54 HxBoosted(hx_boosted): HxBoosted, 55 55 Form(mut build_event_form): Form<BuildEventForm>, 56 56 ) -> Result<impl IntoResponse, WebError> { 57 - let current_handle = auth.require(&web_context.config, "/event")?; 57 + let current_handle = auth.require("/event")?; 58 58 59 59 let is_development = cfg!(debug_assertions); 60 60
+8 -7
src/http/handle_create_rsvp.rs
··· 16 16 use crate::{ 17 17 atproto::lexicon::{ 18 18 com::atproto::repo::StrongRef, 19 - community::lexicon::calendar::rsvp::{Rsvp, RsvpStatus, NSID}, 19 + community::lexicon::calendar::rsvp::{NSID, Rsvp, RsvpStatus}, 20 20 }, 21 21 contextual_error, 22 22 http::{ ··· 28 28 utils::url_from_aturi, 29 29 }, 30 30 select_template, 31 - storage::{ 32 - event::{rsvp_insert_with_metadata, RsvpInsertParams} 33 - }, 31 + storage::event::{RsvpInsertParams, rsvp_insert_with_metadata}, 34 32 }; 35 - use atproto_client::com::atproto::repo::{put_record, PutRecordRequest, PutRecordResponse}; 33 + use atproto_client::com::atproto::repo::{PutRecordRequest, PutRecordResponse, put_record}; 36 34 37 35 pub(crate) async fn handle_create_rsvp( 38 36 method: Method, ··· 43 41 HxBoosted(hx_boosted): HxBoosted, 44 42 Form(mut build_rsvp_form): Form<BuildRSVPForm>, 45 43 ) -> Result<impl IntoResponse, WebError> { 46 - let current_handle = auth.require(&web_context.config, "/rsvp")?; 44 + let current_handle = auth.require("/rsvp")?; 47 45 48 46 // Check if user has email address set 49 - let identity_has_email = current_handle.email.as_ref().is_some_and(|value| !value.is_empty()); 47 + let identity_has_email = current_handle 48 + .email 49 + .as_ref() 50 + .is_some_and(|value| !value.is_empty()); 50 51 51 52 let default_context = template_context! { 52 53 current_handle,
+57 -57
src/http/handle_delete_event.rs
··· 6 6 use minijinja::context as template_context; 7 7 use serde::{Deserialize, Serialize}; 8 8 9 - use atproto_client::com::atproto::repo::{delete_record, DeleteRecordRequest}; 9 + use atproto_client::com::atproto::repo::{DeleteRecordRequest, delete_record}; 10 10 11 11 use crate::{ 12 12 atproto::{ 13 - auth::{create_dpop_auth_from_oauth_session, create_dpop_auth_from_aip_session}, 13 + auth::{create_dpop_auth_from_aip_session, create_dpop_auth_from_oauth_session}, 14 14 lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID, 15 15 }, 16 + config::OAuthBackendConfig, 16 17 contextual_error, 17 18 http::{context::UserRequestContext, errors::WebError, middleware_auth::Auth}, 18 19 select_template, 19 - storage::{event::{event_delete, event_exists}}, 20 - config::OAuthBackendConfig, 20 + storage::event::{event_delete, event_exists}, 21 21 }; 22 22 23 23 #[derive(Debug, Deserialize, Serialize)] ··· 80 80 if form.confirm.as_deref() == Some("true") { 81 81 // Create DPoP authentication based on auth type 82 82 let dpop_auth = match &ctx.auth { 83 - Auth::Pds { session, .. } => { 84 - match create_dpop_auth_from_oauth_session(session) { 85 - Ok(auth) => auth, 86 - Err(err) => { 87 - tracing::error!("Failed to create DPoP auth from OAuth session: {}", err); 88 - return contextual_error!( 89 - ctx.web_context, 90 - ctx.language, 91 - error_template, 92 - default_context, 93 - err, 94 - StatusCode::INTERNAL_SERVER_ERROR 95 - ); 96 - } 83 + Auth::Pds { session, .. } => match create_dpop_auth_from_oauth_session(session) { 84 + Ok(auth) => auth, 85 + Err(err) => { 86 + tracing::error!("Failed to create DPoP auth from OAuth session: {}", err); 87 + return contextual_error!( 88 + ctx.web_context, 89 + ctx.language, 90 + error_template, 91 + default_context, 92 + err, 93 + StatusCode::INTERNAL_SERVER_ERROR 94 + ); 97 95 } 98 - } 99 - Auth::Aip { .. } => { 100 - match &ctx.web_context.config.oauth_backend { 101 - OAuthBackendConfig::AIP { hostname, .. } => { 102 - let access_token = match &ctx.auth { 103 - Auth::Aip { access_token, .. } => access_token, 104 - _ => unreachable!("We already matched on Auth::Aip"), 105 - }; 106 - 107 - match create_dpop_auth_from_aip_session( 108 - &ctx.web_context.http_client, 109 - hostname, 110 - access_token, 111 - ).await { 112 - Ok(auth) => auth, 113 - Err(err) => { 114 - tracing::error!("Failed to create DPoP auth from AIP session: {}", err); 115 - return contextual_error!( 116 - ctx.web_context, 117 - ctx.language, 118 - error_template, 119 - default_context, 120 - err, 121 - StatusCode::INTERNAL_SERVER_ERROR 122 - ); 123 - } 96 + }, 97 + Auth::Aip { .. } => match &ctx.web_context.config.oauth_backend { 98 + OAuthBackendConfig::AIP { hostname, .. } => { 99 + let access_token = match &ctx.auth { 100 + Auth::Aip { access_token, .. } => access_token, 101 + _ => unreachable!("We already matched on Auth::Aip"), 102 + }; 103 + 104 + match create_dpop_auth_from_aip_session( 105 + &ctx.web_context.http_client, 106 + hostname, 107 + access_token, 108 + ) 109 + .await 110 + { 111 + Ok(auth) => auth, 112 + Err(err) => { 113 + tracing::error!("Failed to create DPoP auth from AIP session: {}", err); 114 + return contextual_error!( 115 + ctx.web_context, 116 + ctx.language, 117 + error_template, 118 + default_context, 119 + err, 120 + StatusCode::INTERNAL_SERVER_ERROR 121 + ); 124 122 } 125 123 } 126 - _ => { 127 - tracing::error!("AIP auth found but OAuth backend is not AIP"); 128 - return contextual_error!( 129 - ctx.web_context, 130 - ctx.language, 131 - error_template, 132 - default_context, 133 - anyhow!("Authentication configuration mismatch"), 134 - StatusCode::INTERNAL_SERVER_ERROR 135 - ); 136 - } 137 124 } 138 - } 125 + _ => { 126 + tracing::error!("AIP auth found but OAuth backend is not AIP"); 127 + return contextual_error!( 128 + ctx.web_context, 129 + ctx.language, 130 + error_template, 131 + default_context, 132 + anyhow!("Authentication configuration mismatch"), 133 + StatusCode::INTERNAL_SERVER_ERROR 134 + ); 135 + } 136 + }, 139 137 Auth::Unauthenticated => { 140 138 // This should not happen due to the check above 141 139 return Ok(StatusCode::FORBIDDEN.into_response()); ··· 156 154 &dpop_auth, 157 155 &current_handle.pds, 158 156 delete_record_request, 159 - ).await { 157 + ) 158 + .await 159 + { 160 160 Ok(_) => { 161 161 tracing::info!("Successfully deleted event from PDS: {}", lookup_aturi); 162 162 }
+5 -5
src/http/handle_edit_event.rs
··· 15 15 use crate::{ 16 16 atproto::{ 17 17 lexicon::community::lexicon::calendar::event::{ 18 - Event as LexiconCommunityEvent, EventLink, EventLocation, Mode, NamedUri, Status, 19 - NSID as LexiconCommunityEventNSID, 18 + Event as LexiconCommunityEvent, EventLink, EventLocation, Mode, 19 + NSID as LexiconCommunityEventNSID, NamedUri, Status, 20 20 }, 21 21 lexicon::community::lexicon::location::Address, 22 22 }, ··· 26 26 http::errors::{CommonError, WebError}, 27 27 http::event_form::BuildLocationForm, 28 28 http::event_form::{BuildEventContentState, BuildEventForm, BuildLinkForm, BuildStartsForm}, 29 - http::location_edit_status::{check_location_edit_status, LocationEditStatus}, 29 + http::location_edit_status::{LocationEditStatus, check_location_edit_status}, 30 30 http::timezones::supported_timezones, 31 31 http::utils::url_from_aturi, 32 32 select_template, ··· 35 35 identity_profile::{handle_for_did, handle_for_handle}, 36 36 }, 37 37 }; 38 - use atproto_client::com::atproto::repo::{put_record, PutRecordRequest, PutRecordResponse}; 38 + use atproto_client::com::atproto::repo::{PutRecordRequest, PutRecordResponse, put_record}; 39 39 40 40 pub(crate) async fn handle_edit_event( 41 41 ctx: UserRequestContext, ··· 45 45 Path((handle_slug, event_rkey)): Path<(String, String)>, 46 46 Form(mut build_event_form): Form<BuildEventForm>, 47 47 ) -> Result<impl IntoResponse, WebError> { 48 - let current_handle = ctx.auth.require(&ctx.web_context.config, "/")?; 48 + let current_handle = ctx.auth.require("/")?; 49 49 50 50 let default_context = template_context! { 51 51 current_handle,
+37 -23
src/http/handle_export_rsvps.rs
··· 2 2 3 3 use anyhow::Result; 4 4 use atproto_record::aturi::ATURI; 5 - use axum::{ 6 - extract::Path, 7 - response::IntoResponse, 8 - }; 9 - use http::{header::CONTENT_DISPOSITION, HeaderValue}; 5 + use axum::{extract::Path, response::IntoResponse}; 6 + use http::{HeaderValue, header::CONTENT_DISPOSITION}; 10 7 11 8 use crate::atproto::lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID; 12 9 use crate::http::context::UserRequestContext; ··· 16 13 /// Generate a CSV string from RSVP export data 17 14 fn generate_csv(rsvps: Vec<crate::storage::event::RsvpExportData>) -> String { 18 15 let mut csv = String::new(); 19 - 16 + 20 17 // Add CSV header 21 18 csv.push_str("event,rsvp,did,handle,status,created_at,email\n"); 22 - 19 + 23 20 // Add data rows 24 21 for rsvp in rsvps { 25 22 let created_at_str = match rsvp.created_at { 26 23 Some(dt) => dt.to_rfc3339(), 27 24 None => String::new(), 28 25 }; 29 - 26 + 30 27 let handle_str = rsvp.handle.unwrap_or_default(); 31 28 let email_str = rsvp.email.unwrap_or_default(); 32 - 29 + 33 30 // Escape CSV fields that might contain commas or quotes 34 31 let event = escape_csv_field(&rsvp.event_aturi); 35 32 let rsvp_aturi = escape_csv_field(&rsvp.rsvp_aturi); ··· 38 35 let status = escape_csv_field(&rsvp.status); 39 36 let created_at = escape_csv_field(&created_at_str); 40 37 let email = escape_csv_field(&email_str); 41 - 38 + 42 39 csv.push_str(&format!( 43 40 "{},{},{},{},{},{},{}\n", 44 41 event, rsvp_aturi, did, handle, status, created_at, email 45 42 )); 46 43 } 47 - 44 + 48 45 csv 49 46 } 50 47 ··· 63 60 // Extract the DID, collection, and record key from the AT-URI 64 61 // Format: at://did:plc:example/collection.nsid/recordkey 65 62 if let Ok(aturi) = ATURI::from_str(event_aturi) { 66 - return format!("{}-{}-{}.csv", aturi.authority, aturi.collection, aturi.record_key); 63 + return format!( 64 + "{}-{}-{}.csv", 65 + aturi.authority, aturi.collection, aturi.record_key 66 + ); 67 67 } 68 - 68 + 69 69 // Fallback if parsing fails 70 70 "rsvp-export.csv".to_string() 71 71 } ··· 75 75 Path((handle_slug, event_rkey)): Path<(String, String)>, 76 76 ) -> Result<impl IntoResponse, WebError> { 77 77 // Require authentication 78 - let current_handle = ctx.auth.require(&ctx.web_context.config, "/")?; 78 + let current_handle = ctx.auth.require("/")?; 79 79 80 80 // Check if the current user is the event organizer 81 81 if handle_slug != current_handle.did { 82 - return Err(WebError::from(crate::http::errors::CommonError::NotAuthorized)); 82 + return Err(WebError::from( 83 + crate::http::errors::CommonError::NotAuthorized, 84 + )); 83 85 } 84 86 85 87 let lookup_aturi = format!( ··· 90 92 // Get the event 91 93 let event_exists = event_exists(&ctx.web_context.pool, &lookup_aturi).await; 92 94 if let Err(_) = event_exists { 93 - return Err(WebError::from(crate::http::errors::CommonError::NotAuthorized)); 95 + return Err(WebError::from( 96 + crate::http::errors::CommonError::NotAuthorized, 97 + )); 94 98 } 95 99 96 100 if !event_exists.unwrap() { 97 - return Err(WebError::from(crate::http::errors::CommonError::NotAuthorized)); 101 + return Err(WebError::from( 102 + crate::http::errors::CommonError::NotAuthorized, 103 + )); 98 104 } 99 105 100 106 // Get all RSVPs for the event with detailed information ··· 113 119 114 120 let response = ( 115 121 [ 116 - (http::header::CONTENT_TYPE, HeaderValue::from_static("text/csv; charset=utf-8")), 117 - (CONTENT_DISPOSITION, HeaderValue::from_str(&content_disposition).unwrap()), 122 + ( 123 + http::header::CONTENT_TYPE, 124 + HeaderValue::from_static("text/csv; charset=utf-8"), 125 + ), 126 + ( 127 + CONTENT_DISPOSITION, 128 + HeaderValue::from_str(&content_disposition).unwrap(), 129 + ), 118 130 ], 119 131 csv_content, 120 132 ); ··· 138 150 139 151 #[test] 140 152 fn test_generate_filename() { 141 - let aturi = "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/community.lexicon.calendar.event/3ltbbcuygrc2c"; 142 - let expected = "did:plc:cbkjy5n7bk3ax2wplmtjofq2-community.lexicon.calendar.event-3ltbbcuygrc2c.csv"; 153 + let aturi = 154 + "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/community.lexicon.calendar.event/3ltbbcuygrc2c"; 155 + let expected = 156 + "did:plc:cbkjy5n7bk3ax2wplmtjofq2-community.lexicon.calendar.event-3ltbbcuygrc2c.csv"; 143 157 assert_eq!(generate_filename(aturi), expected); 144 158 } 145 159 ··· 169 183 170 184 let csv = generate_csv(rsvps); 171 185 let lines: Vec<&str> = csv.lines().collect(); 172 - 186 + 173 187 assert_eq!(lines.len(), 3); // Header + 2 data rows 174 188 assert_eq!(lines[0], "event,rsvp,did,handle,status,created_at,email"); 175 189 assert!(lines[1].contains("user1@example.com")); 176 190 assert!(lines[2].contains("interested")); 177 191 } 178 - } 192 + }
+11 -11
src/http/handle_import.rs
··· 18 18 community::lexicon::calendar::{ 19 19 event::{Event as LexiconCommunityEvent, NSID as LEXICON_COMMUNITY_EVENT_NSID}, 20 20 rsvp::{ 21 - Rsvp as LexiconCommunityRsvp, RsvpStatus as LexiconCommunityRsvpStatus, 22 - NSID as LEXICON_COMMUNITY_RSVP_NSID, 21 + NSID as LEXICON_COMMUNITY_RSVP_NSID, Rsvp as LexiconCommunityRsvp, 22 + RsvpStatus as LexiconCommunityRsvpStatus, 23 23 }, 24 24 }, 25 25 events::smokesignal::calendar::{ 26 26 event::{Event as SmokeSignalEvent, NSID as SMOKESIGNAL_EVENT_NSID}, 27 27 rsvp::{ 28 - Rsvp as SmokeSignalRsvp, RsvpStatus as SmokeSignalRsvpStatus, 29 - NSID as SMOKESIGNAL_RSVP_NSID, 28 + NSID as SMOKESIGNAL_RSVP_NSID, Rsvp as SmokeSignalRsvp, 29 + RsvpStatus as SmokeSignalRsvpStatus, 30 30 }, 31 31 }, 32 32 }, ··· 40 40 select_template, 41 41 storage::event::{event_insert_with_metadata, rsvp_insert_with_metadata}, 42 42 }; 43 - use atproto_client::com::atproto::repo::{list_records, ListRecordsParams}; 43 + use atproto_client::com::atproto::repo::{ListRecordsParams, list_records}; 44 44 45 45 pub(crate) async fn handle_import( 46 46 State(web_context): State<WebContext>, ··· 49 49 HxRequest(hx_request): HxRequest, 50 50 HxBoosted(hx_boosted): HxBoosted, 51 51 ) -> Result<impl IntoResponse, WebError> { 52 - let current_handle = auth.require(&web_context.config, "/import")?; 52 + let current_handle = auth.require("/import")?; 53 53 54 54 let default_context = template_context! { 55 55 current_handle, ··· 185 185 error_template, 186 186 template_context! {}, 187 187 ImportError::FailedToListCommunityEvents(err.to_string()) 188 - ) 188 + ); 189 189 } 190 190 } 191 191 } ··· 268 268 error_template, 269 269 template_context! {}, 270 270 ImportError::FailedToListCommunityRSVPs(err.to_string()) 271 - ) 271 + ); 272 272 } 273 273 } 274 274 } ··· 336 336 error_template, 337 337 template_context! {}, 338 338 ImportError::FailedToListSmokesignalEvents(err.to_string()) 339 - ) 339 + ); 340 340 } 341 341 } 342 342 } ··· 415 415 error_template, 416 416 template_context! {}, 417 417 ImportError::FailedToListSmokesignalRSVPs(err.to_string()) 418 - ) 418 + ); 419 419 } 420 420 } 421 421 } ··· 426 426 error_template, 427 427 template_context! {}, 428 428 ImportError::UnsupportedCollectionType(collection.clone()) 429 - ) 429 + ); 430 430 } 431 431 }; 432 432
+29 -18
src/http/handle_index.rs
··· 24 24 utils::url_from_aturi, 25 25 }, 26 26 select_template, 27 - storage::{ 28 - event::activity_list_recent, 29 - identity_profile::handles_by_did, 30 - }, 27 + storage::{event::activity_list_recent, identity_profile::handles_by_did}, 31 28 }; 32 29 33 30 #[derive(Deserialize, Serialize, PartialEq)] ··· 132 129 133 130 #[derive(Debug)] 134 131 enum GroupedActivity<'a> { 135 - Individual { 136 - did: String, 137 - activity: &'a crate::storage::event::model::ActivityItem 132 + Individual { 133 + did: String, 134 + activity: &'a crate::storage::event::model::ActivityItem, 138 135 }, 139 - GroupedRsvp { 140 - dids: Vec<String>, 136 + GroupedRsvp { 137 + dids: Vec<String>, 141 138 first_activity: &'a crate::storage::event::model::ActivityItem, 142 139 rsvp_statuses: Vec<String>, 143 - count: usize 140 + count: usize, 144 141 }, 145 142 } 146 143 ··· 155 152 let mut group_dids = vec![activity.did.clone()]; 156 153 let mut group_statuses = vec![activity.rsvp_status.clone().unwrap_or_default()]; 157 154 let mut j = i + 1; 158 - 155 + 159 156 while j < activity_list.len() { 160 157 let next_activity = activity_list[j]; 161 158 if next_activity.activity_type == "rsvp" ··· 207 204 let organizer_did = extract_did_from_aturi(&activity.event_aturi); 208 205 needed_dids.insert(organizer_did); 209 206 } 210 - GroupedActivity::GroupedRsvp { dids, first_activity, .. } => { 207 + GroupedActivity::GroupedRsvp { 208 + dids, 209 + first_activity, 210 + .. 211 + } => { 211 212 for did in dids { 212 213 needed_dids.insert(did.clone()); 213 214 } ··· 232 233 .map(|profile| profile.handle.clone()) 233 234 .unwrap_or_else(|| did.clone()); 234 235 235 - let event_url = url_from_aturi(&web_context.config.external_base, &activity.event_aturi) 236 - .unwrap_or_else(|_| format!("/event/{}", activity.event_aturi)); 236 + let event_url = 237 + url_from_aturi(&web_context.config.external_base, &activity.event_aturi) 238 + .unwrap_or_else(|_| format!("/event/{}", activity.event_aturi)); 237 239 238 240 let event_organizer_did = extract_did_from_aturi(&activity.event_aturi); 239 241 let event_organizer_handle = handles ··· 254 256 updated_at: activity.updated_at.to_rfc3339(), 255 257 })); 256 258 } 257 - GroupedActivity::GroupedRsvp { dids, first_activity, rsvp_statuses, count } => { 259 + GroupedActivity::GroupedRsvp { 260 + dids, 261 + first_activity, 262 + rsvp_statuses, 263 + count, 264 + } => { 258 265 let group_handles: Vec<String> = dids 259 266 .iter() 260 267 .map(|did| { ··· 265 272 }) 266 273 .collect(); 267 274 268 - let event_url = url_from_aturi(&web_context.config.external_base, &first_activity.event_aturi) 269 - .unwrap_or_else(|_| format!("/event/{}", first_activity.event_aturi)); 275 + let event_url = url_from_aturi( 276 + &web_context.config.external_base, 277 + &first_activity.event_aturi, 278 + ) 279 + .unwrap_or_else(|_| format!("/event/{}", first_activity.event_aturi)); 270 280 271 281 let event_organizer_did = extract_did_from_aturi(&first_activity.event_aturi); 272 282 let event_organizer_handle = handles ··· 301 311 302 312 let params: Vec<(&str, &str)> = vec![("tab", &tab_name)]; 303 313 304 - let pagination_view = PaginationView::new(page_size, activity_displays.len() as i64, page, params); 314 + let pagination_view = 315 + PaginationView::new(page_size, activity_displays.len() as i64, page, params); 305 316 306 317 if activity_displays.len() > page_size as usize { 307 318 activity_displays.truncate(page_size as usize);
+7 -7
src/http/handle_migrate_event.rs
··· 17 17 use crate::{ 18 18 atproto::lexicon::{ 19 19 community::lexicon::calendar::event::{ 20 - Event as CommunityEvent, EventLink, EventLocation as CommunityLocation, Mode, Status, 21 - NSID as COMMUNITY_NSID, 20 + Event as CommunityEvent, EventLink, EventLocation as CommunityLocation, Mode, 21 + NSID as COMMUNITY_NSID, Status, 22 22 }, 23 23 community::lexicon::location, 24 24 events::smokesignal::calendar::event::{ 25 - Event as SmokeSignalEvent, Location as SmokeSignalLocation, PlaceLocation, 26 - NSID as SMOKESIGNAL_NSID, 25 + Event as SmokeSignalEvent, Location as SmokeSignalLocation, NSID as SMOKESIGNAL_NSID, 26 + PlaceLocation, 27 27 }, 28 28 }, 29 29 contextual_error, ··· 37 37 identity_profile::{handle_for_did, handle_for_handle, model::IdentityProfile}, 38 38 }, 39 39 }; 40 - use atproto_client::com::atproto::repo::{put_record, PutRecordRequest, PutRecordResponse}; 40 + use atproto_client::com::atproto::repo::{PutRecordRequest, PutRecordResponse, put_record}; 41 41 42 42 pub(crate) async fn handle_migrate_event( 43 43 State(web_context): State<WebContext>, ··· 47 47 HxRequest(hx_request): HxRequest, 48 48 Path((handle_slug, event_rkey)): Path<(String, String)>, 49 49 ) -> Result<impl IntoResponse, WebError> { 50 - let current_handle = auth.require(&web_context.config, "/")?; 50 + let current_handle = auth.require("/")?; 51 51 52 52 // Configure templates 53 53 let default_context = template_context! { ··· 262 262 error_template, 263 263 default_context, 264 264 MigrateEventError::DestinationExists, 265 - StatusCode::CONFLICT 265 + StatusCode::OK 266 266 ); 267 267 } 268 268
+3 -6
src/http/handle_migrate_rsvp.rs
··· 17 17 use crate::{ 18 18 atproto::lexicon::{ 19 19 com::atproto::repo::StrongRef, 20 - community::lexicon::calendar::rsvp::{Rsvp, RsvpStatus, NSID as RSVP_COLLECTION}, 20 + community::lexicon::calendar::rsvp::{NSID as RSVP_COLLECTION, Rsvp, RsvpStatus}, 21 21 events::smokesignal::calendar::event::NSID as EVENT_COLLECTION, 22 22 }, 23 23 contextual_error, ··· 33 33 identity_profile::{handle_for_did, handle_for_handle, model::IdentityProfile}, 34 34 }, 35 35 }; 36 - use atproto_client::com::atproto::repo::{put_record, PutRecordRequest, PutRecordResponse}; 36 + use atproto_client::com::atproto::repo::{PutRecordRequest, PutRecordResponse, put_record}; 37 37 38 38 /// Migrates a user's RSVP from a legacy event to a standard event format. 39 39 /// ··· 55 55 Path((handle_slug, event_rkey)): Path<(String, String)>, 56 56 ) -> Result<impl IntoResponse, WebError> { 57 57 // Require user to be logged in 58 - let current_handle = auth.require( 59 - &web_context.config, 60 - "/{handle_slug}/{event_rkey}/migrate-rsvp", 61 - )?; 58 + let current_handle = auth.require("/{handle_slug}/{event_rkey}/migrate-rsvp")?; 62 59 63 60 let default_context = template_context! { 64 61 language => language.to_string(),
+18 -6
src/http/handle_oauth_aip_callback.rs
··· 1 1 use std::collections::HashMap; 2 2 3 - use crate::{config::OAuthBackendConfig, contextual_error, select_template, storage::identity_profile::{handle_for_did, identity_profile_set_email}}; 3 + use crate::{ 4 + config::OAuthBackendConfig, 5 + contextual_error, select_template, 6 + storage::identity_profile::{handle_for_did, identity_profile_set_email}, 7 + }; 4 8 use anyhow::{Context, Result, anyhow}; 5 9 use axum::{ 6 10 extract::State, ··· 126 130 127 131 let identity_profile = handle_for_did(&web_context.pool, &oauth_request.did).await?; 128 132 129 - let maybe_email = get_email_from_userinfo(&web_context.http_client, hostname, &identity_profile.did, &token_response.access_token).await; 133 + let maybe_email = get_email_from_userinfo( 134 + &web_context.http_client, 135 + hostname, 136 + &identity_profile.did, 137 + &token_response.access_token, 138 + ) 139 + .await; 130 140 let maybe_email = match maybe_email { 131 141 Ok(value) => value, 132 142 Err(err) => { ··· 138 148 // Write the email address to the database if it already isn't in the database. 139 149 // Only set if the identity_profile's email field is None (not even an empty string) 140 150 if identity_profile.email.is_none() { 141 - if let Err(err) = identity_profile_set_email(&web_context.pool, &oauth_request.did, Some(&email)).await { 151 + if let Err(err) = 152 + identity_profile_set_email(&web_context.pool, &oauth_request.did, Some(&email)) 153 + .await 154 + { 142 155 tracing::error!(error = ?err, "Failed to set email from OAuth userinfo"); 143 156 } 144 157 } ··· 163 176 let updated_jar = jar.add(cookie); 164 177 165 178 // Retrieve destination from OAuth request before deleting it 166 - let postgres_storage = crate::storage::atproto::PostgresOAuthRequestStorage::new( 167 - web_context.pool.clone() 168 - ); 179 + let postgres_storage = 180 + crate::storage::atproto::PostgresOAuthRequestStorage::new(web_context.pool.clone()); 169 181 let destination = match postgres_storage.get_destination(&callback_state).await { 170 182 Ok(Some(dest)) => dest, 171 183 Ok(None) => "/".to_string(),
+4 -4
src/http/handle_oauth_aip_login.rs
··· 1 - use anyhow::{anyhow, Result}; 1 + use anyhow::{Result, anyhow}; 2 2 use atproto_identity::resolve::IdentityResolver; 3 3 use atproto_oauth::pkce::generate; 4 4 use atproto_oauth::workflow::OAuthRequestState as AipOAuthRequestState; 5 5 use atproto_oauth_aip::{ 6 6 resources::oauth_authorization_server, 7 - workflow::{oauth_init, OAuthClient}, 7 + workflow::{OAuthClient, oauth_init}, 8 8 }; 9 9 use axum::response::Redirect; 10 10 use axum::{extract::State, response::IntoResponse}; ··· 13 13 use axum_template::RenderHtml; 14 14 use http::StatusCode; 15 15 use minijinja::context as template_context; 16 - use rand::{distributions::Alphanumeric, Rng}; 16 + use rand::{Rng, distributions::Alphanumeric}; 17 17 use serde::Deserialize; 18 18 19 19 use crate::{ ··· 248 248 if dest != "/" { 249 249 // Create a direct instance to access the set_destination method 250 250 let postgres_storage = crate::storage::atproto::PostgresOAuthRequestStorage::new( 251 - web_context.pool.clone() 251 + web_context.pool.clone(), 252 252 ); 253 253 if let Err(err) = postgres_storage.set_destination(&state, dest).await { 254 254 tracing::error!(?err, "set_destination");
+6 -7
src/http/handle_oauth_callback.rs
··· 1 - use anyhow::{anyhow, Result}; 1 + use anyhow::{Result, anyhow}; 2 2 use atproto_identity::{axum::state::KeyProviderExtractor, key::identify_key}; 3 - use atproto_oauth::workflow::{oauth_complete, OAuthClient}; 3 + use atproto_oauth::workflow::{OAuthClient, oauth_complete}; 4 4 use axum::{ 5 5 extract::State, 6 6 response::{IntoResponse, Redirect}, 7 7 }; 8 8 use axum_extra::extract::{ 9 - cookie::{Cookie, SameSite}, 10 9 Form, PrivateCookieJar, 10 + cookie::{Cookie, SameSite}, 11 11 }; 12 12 use minijinja::context as template_context; 13 13 use serde::{Deserialize, Serialize}; ··· 17 17 use super::{ 18 18 context::WebContext, 19 19 errors::{LoginError, WebError}, 20 - middleware_auth::{WebSession, AUTH_COOKIE_NAME}, 20 + middleware_auth::{AUTH_COOKIE_NAME, WebSession}, 21 21 middleware_i18n::Language, 22 22 }; 23 23 ··· 164 164 let token_response = token_response.unwrap(); 165 165 166 166 // Retrieve destination from OAuth request before deleting it 167 - let postgres_storage = crate::storage::atproto::PostgresOAuthRequestStorage::new( 168 - web_context.pool.clone() 169 - ); 167 + let postgres_storage = 168 + crate::storage::atproto::PostgresOAuthRequestStorage::new(web_context.pool.clone()); 170 169 let destination = match postgres_storage.get_destination(&callback_state).await { 171 170 Ok(Some(dest)) => dest, 172 171 Ok(None) => "/".to_string(),
+8 -5
src/http/handle_oauth_login.rs
··· 1 1 use anyhow::Result; 2 2 use atproto_identity::{ 3 - key::{generate_key, identify_key, KeyType}, 3 + key::{KeyType, generate_key, identify_key}, 4 4 resolve::IdentityResolver, 5 5 }; 6 6 use atproto_oauth::{ 7 7 pkce::generate, 8 8 resources::pds_resources, 9 - workflow::{oauth_init, OAuthClient, OAuthRequest, OAuthRequestState}, 9 + workflow::{OAuthClient, OAuthRequest, OAuthRequestState, oauth_init}, 10 10 }; 11 11 use axum::{extract::State, response::IntoResponse}; 12 12 use axum_extra::extract::{Cached, Form, Query}; ··· 14 14 use axum_template::RenderHtml; 15 15 use http::StatusCode; 16 16 use minijinja::context as template_context; 17 - use rand::{distributions::Alphanumeric, Rng}; 17 + use rand::{Rng, distributions::Alphanumeric}; 18 18 use serde::Deserialize; 19 19 20 20 use crate::{ ··· 277 277 if dest != "/" { 278 278 // Create a direct instance to access the set_destination method 279 279 let postgres_storage = crate::storage::atproto::PostgresOAuthRequestStorage::new( 280 - web_context.pool.clone() 280 + web_context.pool.clone(), 281 281 ); 282 - if let Err(err) = postgres_storage.set_destination(&oauth_request_state.state, dest).await { 282 + if let Err(err) = postgres_storage 283 + .set_destination(&oauth_request_state.state, dest) 284 + .await 285 + { 283 286 tracing::error!(?err, "set_destination"); 284 287 // Don't fail the login flow if we can't store the destination 285 288 }
+33 -12
src/http/handle_profile.rs
··· 200 200 && next_activity.event_aturi == activity.event_aturi 201 201 { 202 202 group_dids.push(next_activity.did.clone()); 203 - group_statuses.push(next_activity.rsvp_status.clone().unwrap_or_default()); 203 + group_statuses 204 + .push(next_activity.rsvp_status.clone().unwrap_or_default()); 204 205 j += 1; 205 206 } else { 206 207 break; ··· 241 242 let organizer_did = extract_did_from_aturi(&activity.event_aturi); 242 243 needed_dids.insert(organizer_did); 243 244 } 244 - GroupedActivity::GroupedRsvp { dids, first_activity, .. } => { 245 + GroupedActivity::GroupedRsvp { 246 + dids, 247 + first_activity, 248 + .. 249 + } => { 245 250 for did in dids { 246 251 needed_dids.insert(did.clone()); 247 252 } ··· 252 257 } 253 258 254 259 // Get handles for needed DIDs 255 - let handles = handles_by_did(&ctx.web_context.pool, needed_dids.into_iter().collect()).await?; 260 + let handles = 261 + handles_by_did(&ctx.web_context.pool, needed_dids.into_iter().collect()).await?; 256 262 257 263 // Create ActivityDisplay objects 258 264 let mut activity_displays: Vec<ActivityDisplay> = Vec::new(); ··· 265 271 .map(|profile| profile.handle.clone()) 266 272 .unwrap_or_else(|| did.clone()); 267 273 268 - let event_url = url_from_aturi(&ctx.web_context.config.external_base, &activity.event_aturi) 269 - .unwrap_or_else(|_| format!("/event/{}", activity.event_aturi)); 274 + let event_url = url_from_aturi( 275 + &ctx.web_context.config.external_base, 276 + &activity.event_aturi, 277 + ) 278 + .unwrap_or_else(|_| format!("/event/{}", activity.event_aturi)); 270 279 271 280 let event_organizer_did = extract_did_from_aturi(&activity.event_aturi); 272 281 let event_organizer_handle = handles ··· 287 296 updated_at: activity.updated_at.to_rfc3339(), 288 297 })); 289 298 } 290 - GroupedActivity::GroupedRsvp { dids, first_activity, rsvp_statuses, count } => { 299 + GroupedActivity::GroupedRsvp { 300 + dids, 301 + first_activity, 302 + rsvp_statuses, 303 + count, 304 + } => { 291 305 let group_handles: Vec<String> = dids 292 306 .iter() 293 307 .map(|did| { ··· 298 312 }) 299 313 .collect(); 300 314 301 - let event_url = url_from_aturi(&ctx.web_context.config.external_base, &first_activity.event_aturi) 302 - .unwrap_or_else(|_| format!("/event/{}", first_activity.event_aturi)); 315 + let event_url = url_from_aturi( 316 + &ctx.web_context.config.external_base, 317 + &first_activity.event_aturi, 318 + ) 319 + .unwrap_or_else(|_| format!("/event/{}", first_activity.event_aturi)); 303 320 304 - let event_organizer_did = extract_did_from_aturi(&first_activity.event_aturi); 321 + let event_organizer_did = 322 + extract_did_from_aturi(&first_activity.event_aturi); 305 323 let event_organizer_handle = handles 306 324 .get(&event_organizer_did) 307 325 .map(|profile| profile.handle.clone()) 308 326 .unwrap_or_else(|| event_organizer_did.clone()); 309 327 310 328 let first_status = &rsvp_statuses[0]; 311 - let all_same_status = rsvp_statuses.iter().all(|status| status == first_status); 329 + let all_same_status = 330 + rsvp_statuses.iter().all(|status| status == first_status); 312 331 let display_status = if all_same_status { 313 332 Some(first_status.clone()) 314 333 } else { ··· 394 413 } 395 414 }; 396 415 397 - let organizer_handlers = hydrate_event_organizers(&ctx.web_context.pool, &events).await?; 416 + let organizer_handlers = 417 + hydrate_event_organizers(&ctx.web_context.pool, &events).await?; 398 418 399 419 let mut events = events 400 420 .iter() ··· 410 430 .collect::<Vec<EventView>>(); 411 431 412 432 if let Err(err) = 413 - super::event_view::hydrate_event_rsvp_counts(&ctx.web_context.pool, &mut events).await 433 + super::event_view::hydrate_event_rsvp_counts(&ctx.web_context.pool, &mut events) 434 + .await 414 435 { 415 436 tracing::warn!("Failed to hydrate event counts: {}", err); 416 437 }
+2 -2
src/http/handle_set_language.rs
··· 4 4 response::{IntoResponse, Redirect}, 5 5 }; 6 6 use axum_extra::extract::{ 7 - cookie::{Cookie, CookieJar, SameSite}, 8 7 Cached, Form, 8 + cookie::{Cookie, CookieJar, SameSite}, 9 9 }; 10 10 use minijinja::context as template_context; 11 11 use serde::Deserialize; 12 12 use std::{borrow::Cow, str::FromStr}; 13 13 use unic_langid::LanguageIdentifier; 14 14 15 - use crate::storage::identity_profile::{handle_update_field, HandleField}; 15 + use crate::storage::identity_profile::{HandleField, handle_update_field}; 16 16 17 17 use super::{ 18 18 context::WebContext, errors::WebError, middleware_auth::Auth, middleware_i18n::COOKIE_LANG,
+9 -6
src/http/handle_settings.rs
··· 16 16 timezones::supported_timezones, 17 17 }, 18 18 select_template, 19 - storage::identity_profile::{handle_for_did, handle_update_field, identity_profile_set_email, HandleField}, 19 + storage::identity_profile::{ 20 + HandleField, handle_for_did, handle_update_field, identity_profile_set_email, 21 + }, 20 22 }; 21 23 22 24 #[derive(Deserialize, Clone, Debug)] ··· 41 43 HxBoosted(hx_boosted): HxBoosted, 42 44 ) -> Result<impl IntoResponse, WebError> { 43 45 // Require authentication 44 - let current_handle = auth.require(&web_context.config, "/settings")?; 46 + let current_handle = auth.require("/settings")?; 45 47 46 48 let default_context = template_context! { 47 49 current_handle => current_handle.clone(), ··· 251 253 }; 252 254 253 255 let error_template = select_template!(false, true, language); 254 - let render_template = format!("settings.{}.email.html", language.to_string().to_lowercase()); 256 + let render_template = format!( 257 + "settings.{}.email.html", 258 + language.to_string().to_lowercase() 259 + ); 255 260 256 261 // Update the email in the database 257 262 let update_result = match email_form.email { ··· 261 266 Some(email) => { 262 267 identity_profile_set_email(&web_context.pool, &current_handle.did, Some(&email)).await 263 268 } 264 - None => { 265 - identity_profile_set_email(&web_context.pool, &current_handle.did, Some("")).await 266 - } 269 + None => identity_profile_set_email(&web_context.pool, &current_handle.did, Some("")).await, 267 270 }; 268 271 269 272 if let Err(err) = update_result {
+17 -5
src/http/handle_view_event.rs
··· 17 17 use crate::http::context::UserRequestContext; 18 18 use crate::http::errors::ViewEventError; 19 19 use crate::http::errors::WebError; 20 - use crate::http::event_view::hydrate_event_rsvp_counts; 21 20 use crate::http::event_view::EventView; 21 + use crate::http::event_view::hydrate_event_rsvp_counts; 22 22 use crate::http::pagination::Pagination; 23 23 use crate::http::tab_selector::TabSelector; 24 24 use crate::http::utils::url_from_aturi; 25 25 use crate::select_template; 26 + use crate::storage::StoragePool; 26 27 use crate::storage::event::count_event_rsvps; 27 28 use crate::storage::event::event_exists; 28 29 use crate::storage::event::event_get; ··· 32 33 use crate::storage::identity_profile::handle_for_did; 33 34 use crate::storage::identity_profile::handle_for_handle; 34 35 use crate::storage::identity_profile::model::IdentityProfile; 35 - use crate::storage::StoragePool; 36 36 37 37 #[derive(Debug, Deserialize, Serialize, PartialEq)] 38 38 pub enum RSVPTab { ··· 127 127 128 128 let profile = profile.unwrap(); 129 129 130 - let identity_has_email = ctx.current_handle.as_ref().is_some_and(|handle| handle.email.as_ref().is_some_and(|value| !value.is_empty())); 130 + let identity_has_email = ctx 131 + .current_handle 132 + .as_ref() 133 + .is_some_and(|handle| handle.email.as_ref().is_some_and(|value| !value.is_empty())); 131 134 132 135 // We'll use TimeZoneSelector to implement the time zone selection logic 133 136 // The timezone selection will happen after we fetch the event ··· 284 287 let event_url = url_from_aturi(&ctx.web_context.config.external_base, &event.aturi)?; 285 288 286 289 // Create login URL with destination parameter for this event 287 - let login_url = format!("/oauth/login?destination={}", urlencoding::encode(&format!("/{}/{}", handle_slug, event_rkey))); 290 + let login_url = format!( 291 + "/oauth/login?destination={}", 292 + urlencoding::encode(&format!("/{}/{}", handle_slug, event_rkey)) 293 + ); 288 294 289 295 // Add Edit button link if the user is the event creator 290 296 let can_edit = ctx ··· 307 313 // Only fetch RSVP data for standard (non-legacy) events 308 314 // Get user's RSVP status and email sharing preference if logged in 309 315 let (user_rsvp, user_email_shared) = if let Some(current_entity) = &ctx.current_handle { 310 - match get_user_rsvp_with_email_shared(&ctx.web_context.pool, &lookup_aturi, &current_entity.did).await { 316 + match get_user_rsvp_with_email_shared( 317 + &ctx.web_context.pool, 318 + &lookup_aturi, 319 + &current_entity.did, 320 + ) 321 + .await 322 + { 311 323 Ok(Some((status, email_shared))) => (Some(status), email_shared), 312 324 Ok(None) => (None, false), 313 325 Err(err) => {
+2 -6
src/http/middleware_auth.rs
··· 1 1 use anyhow::Result; 2 - use atproto_oauth::jwt::{mint, Claims, Header, JoseClaims}; 3 2 use axum::{ 4 3 extract::{FromRef, FromRequestParts}, 5 4 http::request::Parts, ··· 10 9 use tracing::{debug, instrument, trace}; 11 10 12 11 use crate::{ 13 - config::Config, 14 - http::context::WebContext, 15 - http::errors::{AuthMiddlewareError, WebSessionError}, 12 + config::Config, http::context::WebContext, http::errors::WebSessionError, 16 13 storage::identity_profile::model::IdentityProfile, 17 14 }; 18 15 ··· 73 70 /// 74 71 /// This creates a redirect URL with a signed token containing the destination, 75 72 /// which the login handler can verify and redirect back to after successful authentication. 76 - #[instrument(level = "debug", skip(self, config), err)] 73 + #[instrument(level = "debug", skip(self), err)] 77 74 pub(crate) fn require( 78 75 &self, 79 - config: &crate::config::Config, 80 76 location: &str, 81 77 ) -> Result<IdentityProfile, MiddlewareAuthError> { 82 78 match self {
+1 -1
src/http/middleware_i18n.rs
··· 4 4 http::request::Parts, 5 5 response::Response, 6 6 }; 7 - use axum_extra::extract::{cookie::CookieJar, Cached}; 7 + use axum_extra::extract::{Cached, cookie::CookieJar}; 8 8 use std::{cmp::Ordering, str::FromStr}; 9 9 use tracing::{debug, instrument, trace}; 10 10 use unic_langid::LanguageIdentifier;
+9 -1
src/http/pagination.rs
··· 38 38 (page, page_size) 39 39 } 40 40 41 - pub(crate) fn clamped_with_options(&self, page_default: i64, page_min: i64, page_max: i64, page_size_default: i64, page_size_min: i64, page_size_max: i64) -> (i64, i64) { 41 + pub(crate) fn clamped_with_options( 42 + &self, 43 + page_default: i64, 44 + page_min: i64, 45 + page_max: i64, 46 + page_size_default: i64, 47 + page_size_min: i64, 48 + page_size_max: i64, 49 + ) -> (i64, i64) { 42 50 let page = self.page.unwrap_or(page_default).clamp(page_min, page_max); 43 51 let page_size = self 44 52 .page_size
+1 -1
src/http/rsvp_form.rs
··· 3 3 use crate::{ 4 4 errors::expand_error, 5 5 i18n::Locales, 6 - storage::{event::event_get_cid, StoragePool}, 6 + storage::{StoragePool, event::event_get_cid}, 7 7 }; 8 8 9 9 #[allow(dead_code)]
+13 -5
src/http/server.rs
··· 1 1 use std::time::Duration; 2 2 3 3 use axum::{ 4 + Router, 4 5 http::HeaderValue, 5 6 routing::{get, post}, 6 - Router, 7 7 }; 8 8 use axum_htmx::AutoVaryLayer; 9 9 use http::{ 10 - header::{ACCEPT, ACCEPT_LANGUAGE}, 11 10 Method, 11 + header::{ACCEPT, ACCEPT_LANGUAGE}, 12 12 }; 13 13 use tower_http::trace::TraceLayer; 14 14 use tower_http::{classify::ServerErrorsFailureClass, timeout::TimeoutLayer}; ··· 48 48 }, 49 49 handle_profile::handle_profile_view, 50 50 handle_set_language::handle_set_language, 51 - handle_settings::{handle_email_update, handle_language_update, handle_settings, handle_timezone_update}, 51 + handle_settings::{ 52 + handle_email_update, handle_language_update, handle_settings, handle_timezone_update, 53 + }, 52 54 handle_view_event::handle_view_event, 53 55 handle_view_feed::handle_view_feed, 54 56 handle_view_rsvp::handle_view_rsvp, ··· 128 130 .route("/event/links", post(handle_link_at_builder)) 129 131 .route("/{handle_slug}/{event_rkey}/edit", get(handle_edit_event)) 130 132 .route("/{handle_slug}/{event_rkey}/edit", post(handle_edit_event)) 131 - .route("/{handle_slug}/{event_rkey}/export-rsvps", get(handle_export_rsvps)) 132 - .route("/{handle_slug}/{event_rkey}/delete", post(handle_delete_event)) 133 + .route( 134 + "/{handle_slug}/{event_rkey}/export-rsvps", 135 + get(handle_export_rsvps), 136 + ) 137 + .route( 138 + "/{handle_slug}/{event_rkey}/delete", 139 + post(handle_delete_event), 140 + ) 133 141 .route( 134 142 "/{handle_slug}/{event_rkey}/migrate", 135 143 get(handle_migrate_event),
+1 -1
src/http/templates.rs
··· 21 21 pub mod reload_env { 22 22 use std::path::PathBuf; 23 23 24 - use minijinja::{path_loader, Environment}; 24 + use minijinja::{Environment, path_loader}; 25 25 use minijinja_autoreload::AutoReloader; 26 26 27 27 pub fn build_env(http_external: &str, version: &str) -> AutoReloader {
+1 -1
src/http/timezones.rs
··· 1 - use anyhow::{anyhow, Result}; 1 + use anyhow::{Result, anyhow}; 2 2 use chrono::{DateTime, NaiveDateTime, Utc}; 3 3 use itertools::Itertools; 4 4
+3 -3
src/i18n.rs
··· 1 1 use anyhow::Result; 2 - use fluent::{bundle::FluentBundle, FluentArgs, FluentResource}; 2 + use fluent::{FluentArgs, FluentResource, bundle::FluentBundle}; 3 3 use std::collections::HashMap; 4 4 use unic_langid::LanguageIdentifier; 5 5 ··· 105 105 use rust_embed::Embed; 106 106 use unic_langid::LanguageIdentifier; 107 107 108 - use crate::i18n::{errors::I18nError, Locales}; 108 + use crate::i18n::{Locales, errors::I18nError}; 109 109 110 110 #[derive(Embed)] 111 111 #[folder = "i18n/"] ··· 135 135 use std::path::PathBuf; 136 136 use unic_langid::LanguageIdentifier; 137 137 138 - use crate::i18n::{errors::I18nError, Locales}; 138 + use crate::i18n::{Locales, errors::I18nError}; 139 139 140 140 pub fn populate_locale( 141 141 supported_locales: &Vec<LanguageIdentifier>,
+8 -11
src/storage/atproto.rs
··· 6 6 use sqlx::FromRow; 7 7 use std::sync::Arc; 8 8 9 - use crate::storage::{errors::StorageError, StoragePool}; 9 + use crate::storage::{StoragePool, errors::StorageError}; 10 10 11 11 /// Database row representation of OAuthRequest 12 12 #[derive(FromRow)] ··· 131 131 destination: &str, 132 132 ) -> Result<(), StorageError> { 133 133 if oauth_state.trim().is_empty() { 134 - return Err(StorageError::UnableToExecuteQuery( 135 - sqlx::Error::Protocol("OAuth state cannot be empty".to_string()), 136 - )); 134 + return Err(StorageError::UnableToExecuteQuery(sqlx::Error::Protocol( 135 + "OAuth state cannot be empty".to_string(), 136 + ))); 137 137 } 138 138 139 139 let mut tx = self ··· 161 161 } 162 162 163 163 /// Get the destination for an OAuth request 164 - pub async fn get_destination( 165 - &self, 166 - oauth_state: &str, 167 - ) -> Result<Option<String>, StorageError> { 164 + pub async fn get_destination(&self, oauth_state: &str) -> Result<Option<String>, StorageError> { 168 165 if oauth_state.trim().is_empty() { 169 - return Err(StorageError::UnableToExecuteQuery( 170 - sqlx::Error::Protocol("OAuth state cannot be empty".to_string()), 171 - )); 166 + return Err(StorageError::UnableToExecuteQuery(sqlx::Error::Protocol( 167 + "OAuth state cannot be empty".to_string(), 168 + ))); 172 169 } 173 170 174 171 let mut tx = self
+1 -1
src/storage/denylist.rs
··· 6 6 7 7 use self::model::DenylistEntry; 8 8 9 - use crate::storage::{errors::StorageError, StoragePool}; 9 + use crate::storage::{StoragePool, errors::StorageError}; 10 10 11 11 pub(crate) mod model { 12 12 use chrono::{DateTime, Utc};
+21 -10
src/storage/event.rs
··· 11 11 Rsvp as RsvpLexicon, RsvpStatus as RsvpStatusLexicon, 12 12 }; 13 13 14 - use super::errors::StorageError; 15 14 use super::StoragePool; 15 + use super::errors::StorageError; 16 16 use model::{ActivityItem, Event, EventWithRole, Rsvp}; 17 17 18 18 pub mod model { ··· 1341 1341 ORDER BY r.updated_at ASC 1342 1342 "#; 1343 1343 1344 - let rsvps = sqlx::query_as::<_, (String, String, String, Option<String>, String, Option<chrono::DateTime<chrono::Utc>>, Option<String>)>(query) 1345 - .bind(event_aturi) 1346 - .fetch_all(tx.as_mut()) 1347 - .await 1348 - .map_err(StorageError::UnableToExecuteQuery)?; 1344 + let rsvps = sqlx::query_as::< 1345 + _, 1346 + ( 1347 + String, 1348 + String, 1349 + String, 1350 + Option<String>, 1351 + String, 1352 + Option<chrono::DateTime<chrono::Utc>>, 1353 + Option<String>, 1354 + ), 1355 + >(query) 1356 + .bind(event_aturi) 1357 + .fetch_all(tx.as_mut()) 1358 + .await 1359 + .map_err(StorageError::UnableToExecuteQuery)?; 1349 1360 1350 1361 tx.commit() 1351 1362 .await ··· 1353 1364 1354 1365 let export_data: Vec<RsvpExportData> = rsvps 1355 1366 .into_iter() 1356 - .map(|(event_aturi, rsvp_aturi, did, handle, status, created_at, email)| { 1357 - RsvpExportData { 1367 + .map( 1368 + |(event_aturi, rsvp_aturi, did, handle, status, created_at, email)| RsvpExportData { 1358 1369 event_aturi, 1359 1370 rsvp_aturi, 1360 1371 did, ··· 1362 1373 status, 1363 1374 created_at, 1364 1375 email, 1365 - } 1366 - }) 1376 + }, 1377 + ) 1367 1378 .collect(); 1368 1379 1369 1380 Ok(export_data)
+1 -1
src/storage/identity_profile.rs
··· 4 4 use cityhasher::HashMap; 5 5 use sqlx::{Postgres, QueryBuilder}; 6 6 7 + use crate::storage::StoragePool; 7 8 use crate::storage::denylist::denylist_add_or_update; 8 9 use crate::storage::errors::StorageError; 9 - use crate::storage::StoragePool; 10 10 use model::IdentityProfile; 11 11 12 12 pub mod model {
+1 -1
src/storage/oauth.rs
··· 2 2 3 3 use chrono::{DateTime, Utc}; 4 4 5 - use crate::storage::{errors::StorageError, identity_profile::model::IdentityProfile, StoragePool}; 5 + use crate::storage::{StoragePool, errors::StorageError, identity_profile::model::IdentityProfile}; 6 6 use model::OAuthSession; 7 7 8 8 pub async fn oauth_session_update(
+1 -1
src/task_identity_refresh.rs
··· 2 2 use atproto_identity::{resolve::IdentityResolver, storage::DidDocumentStorage}; 3 3 use chrono::Duration; 4 4 use sqlx::FromRow; 5 - use tokio::time::{sleep, Instant}; 5 + use tokio::time::{Instant, sleep}; 6 6 use tokio_util::sync::CancellationToken; 7 7 8 8 use crate::storage::StoragePool;
+1 -1
src/task_oauth_requests_cleanup.rs
··· 1 1 use anyhow::Result; 2 2 use chrono::Duration; 3 - use tokio::time::{sleep, Instant}; 3 + use tokio::time::{Instant, sleep}; 4 4 use tokio_util::sync::CancellationToken; 5 5 6 6 use crate::storage::StoragePool;
+5 -5
src/task_refresh_tokens.rs
··· 1 1 use anyhow::Result; 2 2 use atproto_identity::key::identify_key; 3 - use atproto_oauth::workflow::{oauth_refresh, OAuthClient}; 3 + use atproto_oauth::workflow::{OAuthClient, oauth_refresh}; 4 4 use chrono::{Duration, Utc}; 5 - use deadpool_redis::redis::{pipe, AsyncCommands}; 5 + use deadpool_redis::redis::{AsyncCommands, pipe}; 6 6 use std::borrow::Cow; 7 - use tokio::time::{sleep, Instant}; 7 + use tokio::time::{Instant, sleep}; 8 8 use tokio_util::sync::CancellationToken; 9 9 10 10 use crate::{ 11 11 config::SigningKeys, 12 12 refresh_tokens_errors::RefreshError, 13 13 storage::{ 14 - cache::{build_worker_queue, OAUTH_REFRESH_HEARTBEATS, OAUTH_REFRESH_QUEUE}, 15 - oauth::{oauth_session_delete, oauth_session_update, web_session_lookup}, 16 14 CachePool, StoragePool, 15 + cache::{OAUTH_REFRESH_HEARTBEATS, OAUTH_REFRESH_QUEUE, build_worker_queue}, 16 + oauth::{oauth_session_delete, oauth_session_update, web_session_lookup}, 17 17 }, 18 18 }; 19 19