interactive intro to open social

fix: improve URL validation with GET fallback for HEAD method errors

some servers (like bsky.app) return HTTP 405 for HEAD requests but work fine with GET. this commit adds a fallback strategy:
- try HEAD first (faster, less bandwidth)
- if HEAD returns error status (405, etc), fall back to GET
- if HEAD fails completely, also try GET
- mark as valid only if either succeeds

this ensures valid URLs aren't incorrectly grayed out while still catching truly invalid domains.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+22 -3
src
+22 -3
src/routes.rs
··· 356 356 pub async fn validate_url(query: web::Query<ValidateUrlQuery>) -> HttpResponse { 357 357 let url = &query.url; 358 358 359 - // Try to make a HEAD request with a short timeout 359 + // Build client with redirect following and timeout 360 360 let client = reqwest::Client::builder() 361 361 .timeout(std::time::Duration::from_secs(3)) 362 + .redirect(reqwest::redirect::Policy::limited(5)) 362 363 .build() 363 364 .unwrap(); 364 365 366 + // Try HEAD first, fall back to GET if HEAD doesn't succeed 365 367 let is_valid = match client.head(url).send().await { 366 - Ok(response) => response.status().is_success() || response.status().is_redirection(), 367 - Err(_) => false, 368 + Ok(response) => { 369 + let status = response.status(); 370 + if status.is_success() || status.is_redirection() { 371 + true 372 + } else { 373 + // HEAD returned error status (like 405), try GET 374 + match client.get(url).send().await { 375 + Ok(get_response) => get_response.status().is_success(), 376 + Err(_) => false, 377 + } 378 + } 379 + } 380 + Err(_) => { 381 + // HEAD request failed completely, try GET as fallback 382 + match client.get(url).send().await { 383 + Ok(response) => response.status().is_success(), 384 + Err(_) => false, 385 + } 386 + } 368 387 }; 369 388 370 389 HttpResponse::Ok().json(serde_json::json!({