The smokesignal.events web application

refactor: linting and cleanup

+90 -108
-7
src/http/auth_utils.rs
··· 2 2 use atproto_client::client::get_dpop_json_with_headers; 3 3 use http::HeaderMap; 4 4 use reqwest::Client; 5 - use serde::Deserialize; 6 5 7 6 use crate::atproto::auth::create_dpop_auth_from_aip_session; 8 7 use crate::config::OAuthBackendConfig; ··· 10 9 use crate::http::errors::LoginError; 11 10 use crate::http::errors::web_error::WebError; 12 11 use crate::http::middleware_auth::Auth; 13 - 14 - #[derive(Deserialize)] 15 - struct GetServiceAuthResponse { 16 - #[allow(dead_code)] 17 - token: String, 18 - } 19 12 20 13 /// Result of checking if an AIP session is ready for AT Protocol operations. 21 14 pub(crate) enum AipSessionStatus {
+1 -8
src/http/errors/profile_import_error.rs
··· 5 5 /// These errors typically happen when attempting to import a user's Bluesky profile 6 6 /// (`app.bsky.actor.profile`) to create a Smokesignal profile on first login. 7 7 #[derive(Debug, Error)] 8 + #[allow(clippy::enum_variant_names)] 8 9 pub(crate) enum ProfileImportError { 9 - /// User already has a Smokesignal profile, import skipped. 10 - #[error("error-smokesignal-profile-import-1 User already has profile")] 11 - ProfileAlreadyExists, 12 - 13 10 /// Failed to fetch the Bluesky profile from the user's PDS. 14 11 #[error("error-smokesignal-profile-import-2 Failed to fetch Bluesky profile: {0}")] 15 12 FetchFailed(String), ··· 37 34 /// Failed to store the profile locally in the database. 38 35 #[error("error-smokesignal-profile-import-8 Failed to store profile locally: {0}")] 39 36 StorageFailed(String), 40 - 41 - /// No Bluesky profile exists for this user. 42 - #[error("error-smokesignal-profile-import-9 No Bluesky profile found")] 43 - NoBlueskyProfile, 44 37 }
+1
src/http/event_form.rs
··· 4 4 use crate::{errors::expand_error, i18n::Locales}; 5 5 6 6 #[derive(Debug, Error)] 7 + #[allow(clippy::enum_variant_names)] 7 8 pub(crate) enum BuildEventError { 8 9 #[error("error-smokesignal-event-builder-1 Invalid Name")] 9 10 InvalidName,
+4 -4
src/http/handle_admin_event_index.rs
··· 134 134 135 135 // Build query params for pagination 136 136 let mut params: Vec<(&str, String)> = vec![]; 137 - if let Some(ref q) = search_query.query { 138 - if !q.is_empty() { 139 - params.push(("query", q.clone())); 140 - } 137 + if let Some(ref q) = search_query.query 138 + && !q.is_empty() 139 + { 140 + params.push(("query", q.clone())); 141 141 } 142 142 143 143 let params_refs: Vec<(&str, &str)> = params
+4 -4
src/http/handle_admin_profile_index.rs
··· 134 134 135 135 // Build query params for pagination 136 136 let mut params: Vec<(&str, String)> = vec![]; 137 - if let Some(ref q) = search_query.query { 138 - if !q.is_empty() { 139 - params.push(("query", q.clone())); 140 - } 137 + if let Some(ref q) = search_query.query 138 + && !q.is_empty() 139 + { 140 + params.push(("query", q.clone())); 141 141 } 142 142 143 143 let params_refs: Vec<(&str, &str)> = params
+29 -31
src/http/handle_create_event.rs
··· 454 454 } 455 455 456 456 // Download and store header image from PDS if one was uploaded 457 - if let Some(ref header_cid) = build_event_form.header_cid { 458 - if let Err(err) = store_event_header_from_pds( 457 + if let Some(ref header_cid) = build_event_form.header_cid 458 + && let Err(err) = store_event_header_from_pds( 459 459 &web_context.http_client, 460 460 &web_context.content_storage, 461 461 &current_handle.pds, ··· 463 463 header_cid, 464 464 ) 465 465 .await 466 - { 467 - tracing::warn!( 468 - ?err, 469 - cid = %header_cid, 470 - "Failed to store event header image from PDS" 471 - ); 472 - // Don't fail the request - the event was created successfully 473 - // The header can be fetched later via jetstream or admin import 474 - } 466 + { 467 + tracing::warn!( 468 + ?err, 469 + cid = %header_cid, 470 + "Failed to store event header image from PDS" 471 + ); 472 + // Don't fail the request - the event was created successfully 473 + // The header can be fetched later via jetstream or admin import 475 474 } 476 475 477 476 let event_url = url_from_aturi( ··· 574 573 } 575 574 576 575 // Validate end is after start 577 - if let (Some(start), Some(end)) = (starts_at, ends_at) { 578 - if end <= start { 579 - return Ok(( 580 - StatusCode::BAD_REQUEST, 581 - Json(json!({ 582 - "error": "End time must be after start time" 583 - })) 584 - ).into_response()); 585 - } 576 + if let (Some(start), Some(end)) = (starts_at, ends_at) 577 + && end <= start 578 + { 579 + return Ok(( 580 + StatusCode::BAD_REQUEST, 581 + Json(json!({ 582 + "error": "End time must be after start time" 583 + })) 584 + ).into_response()); 586 585 } 587 586 588 587 // Parse mode ··· 792 791 } 793 792 794 793 // Download and store header image from PDS if one was uploaded 795 - if let Some(ref header_cid) = request.header_cid { 796 - if let Err(err) = store_event_header_from_pds( 794 + if let Some(ref header_cid) = request.header_cid 795 + && let Err(err) = store_event_header_from_pds( 797 796 &web_context.http_client, 798 797 &web_context.content_storage, 799 798 &current_handle.pds, ··· 801 800 header_cid, 802 801 ) 803 802 .await 804 - { 805 - tracing::warn!( 806 - ?err, 807 - cid = %header_cid, 808 - "Failed to store event header image from PDS" 809 - ); 810 - // Don't fail the request - the event was created successfully 811 - // The header can be fetched later via jetstream or admin import 812 - } 803 + { 804 + tracing::warn!( 805 + ?err, 806 + cid = %header_cid, 807 + "Failed to store event header image from PDS" 808 + ); 809 + // Don't fail the request - the event was created successfully 810 + // The header can be fetched later via jetstream or admin import 813 811 } 814 812 815 813 let event_url = url_from_aturi(
+17 -18
src/http/handle_edit_event.rs
··· 143 143 }; 144 144 145 145 // Validate end after start 146 - if let (Some(start), Some(end)) = (starts_at, ends_at) { 147 - if end <= start { 148 - return Ok(( 149 - StatusCode::BAD_REQUEST, 150 - Json(json!({"error": "End time must be after start time"})) 151 - ).into_response()); 152 - } 146 + if let (Some(start), Some(end)) = (starts_at, ends_at) 147 + && end <= start 148 + { 149 + return Ok(( 150 + StatusCode::BAD_REQUEST, 151 + Json(json!({"error": "End time must be after start time"})) 152 + ).into_response()); 153 153 } 154 154 155 155 // Parse mode and status ··· 363 363 } 364 364 365 365 // Download and store header image from PDS if one was uploaded 366 - if let Some(ref header_cid) = request.header_cid { 367 - if let Err(err) = store_event_header_from_pds( 366 + if let Some(ref header_cid) = request.header_cid 367 + && let Err(err) = store_event_header_from_pds( 368 368 &ctx.web_context.http_client, 369 369 &ctx.web_context.content_storage, 370 370 &current_handle.pds, ··· 372 372 header_cid, 373 373 ) 374 374 .await 375 - { 376 - tracing::warn!( 377 - ?err, 378 - cid = %header_cid, 379 - "Failed to store event header image from PDS" 380 - ); 381 - // Don't fail the request - the event was updated successfully 382 - // The header can be fetched later via jetstream or admin import 383 - } 375 + { 376 + tracing::warn!( 377 + ?err, 378 + cid = %header_cid, 379 + "Failed to store event header image from PDS" 380 + ); 381 + // Don't fail the request - the event was updated successfully 382 + // The header can be fetched later via jetstream or admin import 384 383 } 385 384 386 385 let event_url = crate::http::utils::url_from_aturi(
+1 -1
src/http/handle_oauth_aip_callback.rs
··· 232 232 &web_context.pool, 233 233 &identity_resolver, 234 234 &dpop_auth, 235 - &pds, 235 + pds, 236 236 &document.id, 237 237 handle, 238 238 &facet_limits,
+2 -8
src/http/handle_oauth_aip_login.rs
··· 105 105 106 106 let (pkce_verifier, code_challenge) = generate(); 107 107 108 - let aip_hostname = match &web_context.config.oauth_backend { 109 - OAuthBackendConfig::AIP { hostname, .. } => hostname.trim_start_matches("https://"), 110 - _ => unreachable!("AIP OAuth backend should have AIP configuration"), 111 - }; 112 - let scope = vec![ 108 + let scope = [ 113 109 "account:email", 114 110 "atproto", 115 111 "blob:image/*", ··· 121 117 "repo:events.smokesignal.calendar.acceptance", 122 118 "repo:events.smokesignal.profile", 123 119 "rpc:tools.graze.aip.ready?aud=*", 124 - // format!("rpc:tools.graze.aip.ready?aud=did:web:{}#aip", aip_hostname).as_str(), 125 120 ] 126 - .join(" ") 127 - .to_string(); 121 + .join(" "); 128 122 129 123 // Create AIP-specific OAuth request state with scope 130 124 let aip_oauth_request_state = AipOAuthRequestState {
+1 -1
src/http/handle_oauth_callback.rs
··· 251 251 &web_context.pool, 252 252 &identity_resolver, 253 253 &dpop_auth, 254 - &pds, 254 + pds, 255 255 &document.id, 256 256 handle, 257 257 &facet_limits,
+18 -16
src/http/import_utils.rs
··· 298 298 Ok(document) 299 299 } 300 300 301 + #[allow(clippy::too_many_arguments)] 301 302 async fn handle_event_import( 302 303 http_client: &reqwest::Client, 303 304 pool: &PgPool, ··· 424 425 Ok(()) 425 426 } 426 427 428 + #[allow(clippy::too_many_arguments)] 427 429 async fn handle_profile_import( 428 430 http_client: &reqwest::Client, 429 431 pool: &PgPool, ··· 465 467 profile_insert(pool, &aturi, cid, did, display_name, &profile_record).await?; 466 468 467 469 // Download avatar and banner blobs if present 468 - if let Some(ref avatar) = profile_record.avatar { 469 - if let Err(e) = download_avatar(http_client, content_storage, pds_endpoint, did, avatar).await { 470 - tracing::warn!( 471 - error = ?e, 472 - did = %did, 473 - "Failed to download avatar for profile" 474 - ); 475 - } 470 + if let Some(ref avatar) = profile_record.avatar 471 + && let Err(e) = download_avatar(http_client, content_storage, pds_endpoint, did, avatar).await 472 + { 473 + tracing::warn!( 474 + error = ?e, 475 + did = %did, 476 + "Failed to download avatar for profile" 477 + ); 476 478 } 477 479 478 - if let Some(ref banner) = profile_record.banner { 479 - if let Err(e) = download_banner(http_client, content_storage, pds_endpoint, did, banner).await { 480 - tracing::warn!( 481 - error = ?e, 482 - did = %did, 483 - "Failed to download banner for profile" 484 - ); 485 - } 480 + if let Some(ref banner) = profile_record.banner 481 + && let Err(e) = download_banner(http_client, content_storage, pds_endpoint, did, banner).await 482 + { 483 + tracing::warn!( 484 + error = ?e, 485 + did = %did, 486 + "Failed to download banner for profile" 487 + ); 486 488 } 487 489 488 490 tracing::info!("Successfully imported profile: {}", aturi);
+1
src/http/profile_import.rs
··· 52 52 /// * `Ok(true)` - Profile was successfully imported 53 53 /// * `Ok(false)` - Import was skipped (profile exists or no Bluesky profile) 54 54 /// * `Err(...)` - Import failed 55 + #[allow(clippy::too_many_arguments)] 55 56 pub(crate) async fn import_bluesky_profile( 56 57 http_client: &reqwest::Client, 57 58 content_storage: &Arc<dyn ContentStorage>,
+2 -2
src/http/server.rs
··· 7 7 }; 8 8 use axum_htmx::AutoVaryLayer; 9 9 use http::{ 10 - HeaderName, Method, 10 + HeaderName, Method, StatusCode, 11 11 header::{ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE}, 12 12 }; 13 13 use tower_http::trace::{self, TraceLayer}; ··· 339 339 tracing::error!(error = ?err, "Unhandled error: {err}"); 340 340 }, 341 341 ), 342 - TimeoutLayer::new(Duration::from_secs(30)), 342 + TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_secs(30)), 343 343 )) 344 344 .layer( 345 345 CorsLayer::new()
+3 -3
src/profile_index.rs
··· 132 132 133 133 let hits = body["hits"]["hits"] 134 134 .as_array() 135 - .ok_or_else(|| ProfileIndexError::InvalidSearchResponse)?; 135 + .ok_or(ProfileIndexError::InvalidSearchResponse)?; 136 136 137 137 let profiles: Vec<IndexedProfile> = hits 138 138 .iter() ··· 197 197 198 198 let hits = body["hits"]["hits"] 199 199 .as_array() 200 - .ok_or_else(|| ProfileIndexError::InvalidSearchResponse)?; 200 + .ok_or(ProfileIndexError::InvalidSearchResponse)?; 201 201 202 202 let profiles: Vec<IndexedProfile> = hits 203 203 .iter() ··· 323 323 "description": profile_record.description.unwrap_or_default(), 324 324 "tags": tags, 325 325 "created_at": json!(chrono::Utc::now()), 326 - "updated_at": json!(profile.updated_at.unwrap_or_else(|| chrono::Utc::now())) 326 + "updated_at": json!(profile.updated_at.unwrap_or_else(chrono::Utc::now)) 327 327 }); 328 328 329 329 let response = self
+6 -5
src/stats.rs
··· 12 12 const CACHE_TTL_SECONDS: u64 = 600; // 10 minutes 13 13 14 14 #[derive(Debug, Error)] 15 + #[allow(clippy::enum_variant_names)] 15 16 pub(crate) enum StatsError { 16 17 #[error("error-smokesignal-stats-1 Database query failed: {0}")] 17 18 DatabaseError(String), ··· 38 39 let chars: Vec<char> = num_str.chars().collect(); 39 40 40 41 for (i, ch) in chars.iter().enumerate() { 41 - if i > 0 && (chars.len() - i) % 3 == 0 { 42 + if i > 0 && (chars.len() - i).is_multiple_of(3) { 42 43 result.push(','); 43 44 } 44 45 result.push(*ch); ··· 63 64 } 64 65 65 66 fn get(&self) -> Option<NetworkStats> { 66 - if let (Some(stats), Some(cached_at)) = (&self.stats, &self.cached_at) { 67 - if cached_at.elapsed() < Duration::from_secs(CACHE_TTL_SECONDS) { 68 - return Some(stats.clone()); 69 - } 67 + if let (Some(stats), Some(cached_at)) = (&self.stats, &self.cached_at) 68 + && cached_at.elapsed() < Duration::from_secs(CACHE_TTL_SECONDS) 69 + { 70 + return Some(stats.clone()); 70 71 } 71 72 None 72 73 }