From 005759c84dc2a52a8b9a231fe61e9cbbdba49e9c Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Thu, 1 Jan 2026 20:02:30 -0600 Subject: [PATCH] feat: add check to CLI for diagnosing OAuth and indexing status * updated local dev docs --- .env.example | 16 +++++ Cargo.lock | 1 + crates/cli/Cargo.toml | 1 + crates/cli/src/main.rs | 145 +++++++++++++++++++++++++++++++++++++++++ docs/local-dev.md | 85 ++++++++++++++++++++---- justfile | 13 +--- 6 files changed, 240 insertions(+), 21 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7ff75ce --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Database (Required) +DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable" + +# OAuth Client Configuration (Optional - defaults shown) +APP_URL=http://localhost:3000 +APP_NAME=Malfestio + +# Server Configuration (Optional - defaults shown) +SERVER_HOST=127.0.0.1 +SERVER_PORT=8080 + +# Frontend Configuration (Optional - defaults shown) +VITE_API_URL=http://localhost:8080 + +# Logging (Optional) +RUST_LOG=info,malfestio_server=debug diff --git a/Cargo.lock b/Cargo.lock index 646da2a..6df3533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1654,6 +1654,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" name = "malfestio-cli" version = "0.1.0" dependencies = [ + "chrono", "clap", "dotenvy", "malfestio-core", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a9f03f5..297dbae 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +chrono = "0.4" clap = { version = "4.5.53", features = ["derive"] } dotenvy = "0.15.7" malfestio-core = { version = "0.1.0", path = "../core" } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 67ec737..b72a646 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -23,6 +23,11 @@ enum Commands { #[arg(long)] db_url: Option, }, + /// Check OAuth flow and database state for a Bluesky handle + Check { + /// Bluesky handle to test (e.g., alice.bsky.social) + handle: String, + }, } #[tokio::main] @@ -39,6 +44,9 @@ async fn main() -> malfestio_core::Result<()> { Commands::Migrate { db_url } => { run_migrations(db_url.as_deref()).await?; } + Commands::Check { handle } => { + check_flow(handle).await?; + } } Ok(()) @@ -144,3 +152,140 @@ async fn run_migrations(db_url: Option<&str>) -> malfestio_core::Result<()> { Ok(()) } + +async fn check_flow(handle: &str) -> malfestio_core::Result<()> { + println!("Checking OAuth flow for {}...\n", handle); + + // Get database URL + let db_url = std::env::var("DB_URL") + .or_else(|_| std::env::var("DATABASE_URL")) + .map_err(|_| malfestio_core::Error::InvalidArgument("DB_URL or DATABASE_URL not set".to_string()))?; + + // Test database connection + print!("• Testing database connection... "); + let (client, connection) = tokio_postgres::connect(&db_url, NoTls) + .await + .map_err(|e| malfestio_core::Error::Database(format!("Failed to connect: {}", e)))?; + + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("Database connection error: {}", e); + } + }); + + println!("✓ Connected"); + + let resolver = malfestio_server::oauth::resolver::IdentityResolver::new(); + + print!("• Resolving handle to DID... "); + let did = match resolver.resolve_handle(handle).await { + Ok(did) => { + println!("✓ {}", did); + did + } + Err(e) => { + println!("✗ Failed: {}", e); + return Err(malfestio_core::Error::Other(format!("Handle resolution failed: {}", e))); + } + }; + + print!("• Resolving DID to PDS... "); + let _resolved = match resolver.resolve_did(&did).await { + Ok(resolved) => { + println!("✓ {}", resolved.pds_url); + resolved + } + Err(e) => { + println!("✗ Failed: {}", e); + return Err(malfestio_core::Error::Other(format!("DID resolution failed: {}", e))); + } + }; + + print!("• Checking OAuth tokens... "); + let token_row = client + .query_opt( + "SELECT did, pds_url, created_at, updated_at FROM oauth_tokens WHERE did = $1", + &[&did], + ) + .await + .map_err(|e| malfestio_core::Error::Database(format!("Token query failed: {}", e)))?; + + if let Some(row) = token_row { + let updated_at: chrono::DateTime = row.get(3); + println!("✓ Found (last updated: {})", updated_at.format("%Y-%m-%d %H:%M:%S UTC")); + } else { + println!("✗ Not found"); + println!("\nℹ No OAuth tokens stored yet. Complete OAuth login first:"); + println!(" 1. Start server: just start"); + println!(" 2. Start frontend: just web-dev"); + println!(" 3. Navigate to http://localhost:3000/login"); + println!(" 4. Enter handle: {}", handle); + return Ok(()); + } + + print!("• Checking indexed decks... "); + let deck_rows = client + .query( + "SELECT at_uri, title, indexed_at FROM indexed_decks WHERE did = $1 ORDER BY indexed_at DESC LIMIT 5", + &[&did], + ) + .await + .map_err(|e| malfestio_core::Error::Database(format!("Deck query failed: {}", e)))?; + + if deck_rows.is_empty() { + println!("0 decks"); + } else { + println!("{} deck(s)", deck_rows.len()); + for row in &deck_rows { + let at_uri: String = row.get(0); + let title: Option = row.get(1); + let indexed_at: chrono::DateTime = row.get(2); + let time_ago = format_time_ago(indexed_at); + println!(" - {} ({})", title.unwrap_or_else(|| "Untitled".to_string()), time_ago); + println!(" {}", at_uri); + } + } + + print!("• Checking indexed cards... "); + let card_count: i64 = client + .query_one("SELECT COUNT(*) FROM indexed_cards WHERE did = $1", &[&did]) + .await + .map_err(|e| malfestio_core::Error::Database(format!("Card count query failed: {}", e)))? + .get(0); + + println!("{} card(s)", card_count); + + print!("• Checking indexed notes... "); + let note_count: i64 = client + .query_one("SELECT COUNT(*) FROM indexed_notes WHERE did = $1", &[&did]) + .await + .map_err(|e| malfestio_core::Error::Database(format!("Note count query failed: {}", e)))? + .get(0); + + println!("{} note(s)", note_count); + + println!("\n✓ Status: Ready for testing"); + println!("\nNext steps:"); + println!(" - Publish content via UI to see it indexed"); + println!(" - Check Bluesky profile: https://bsky.app/profile/{}", handle); + println!(" - Inspect records: https://pdsls.dev/at/{}", did); + + Ok(()) +} + +fn format_time_ago(timestamp: chrono::DateTime) -> String { + let now = chrono::Utc::now(); + let duration = now.signed_duration_since(timestamp); + + if duration.num_seconds() < 60 { + format!("{} seconds ago", duration.num_seconds()) + } else if duration.num_minutes() < 60 { + format!("{} minutes ago", duration.num_minutes()) + } else if duration.num_hours() < 24 { + format!("{} hours ago", duration.num_hours()) + } else if duration.num_days() < 30 { + format!("{} days ago", duration.num_days()) + } else { + format!("{} months ago", duration.num_days() / 30) + } +} diff --git a/docs/local-dev.md b/docs/local-dev.md index 7619403..50687f6 100644 --- a/docs/local-dev.md +++ b/docs/local-dev.md @@ -11,16 +11,18 @@ ### Bluesky Account Setup -1. Create a Bluesky account at -2. Generate an App Password (Settings → App Passwords) -3. Configure `.env` with your credentials: +1. Create a Bluesky account at (you'll use this for OAuth testing) + +### Environment Configuration + +Copy the template and configure for your environment: ```bash -APP_USERNAME=your-handle.bsky.social -APP_PASSWORD=your-app-password-here -DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable" +cp .env.example .env ``` +For local development, the defaults in `.env.example` work out of the box. You only need to ensure your PostgreSQL connection string is correct. + ## Testing OAuth Flow ### Step-by-Step @@ -82,30 +84,91 @@ After successful OAuth login: 3. Check your Bluesky profile at to see the published record 4. Verify record appears in your AT Protocol repository -## Environment Variables +## Verifying Your Setup + +### Check OAuth Tokens + +After successful login, verify tokens were stored: + +```sql +SELECT + did, + pds_url, + LEFT(access_token, 20) || '...' as token_preview, + created_at, + updated_at +FROM oauth_tokens +WHERE did = 'your-did-here'; +``` + +Replace `'your-did-here'` with the DID from your login success page. + +### Check Indexed Records + +After publishing content, verify firehose indexing: + +```sql +-- Check indexed decks +SELECT at_uri, title, indexed_at +FROM indexed_decks +WHERE did = 'your-did-here' +ORDER BY indexed_at DESC +LIMIT 10; + +-- Check indexed cards +SELECT at_uri, front_content, indexed_at +FROM indexed_cards +WHERE did = 'your-did-here' +ORDER BY indexed_at DESC +LIMIT 10; +``` + +Note: Indexing may take 5-10 seconds after publishing. + +### Diagnostic Command + +Run this command to check handle resolution and database state: + +```bash +just verify your-handle.bsky.social +``` + +This will verify: + +- Database connection +- Handle → DID resolution +- DID → PDS URL resolution +- OAuth token status +- Indexed content count + +## Environment Variables Reference ### Required ```bash -APP_USERNAME=your-handle.bsky.social -APP_PASSWORD=your-app-password DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable" ``` ### Optional ```bash -# Server configuration +# OAuth Client Configuration +APP_URL=http://localhost:3000 # OAuth callback URL +APP_NAME=Malfestio # App display name + +# Server Configuration SERVER_HOST=127.0.0.1 SERVER_PORT=8080 -# Frontend proxy +# Frontend Configuration VITE_API_URL=http://localhost:8080 # Logging RUST_LOG=info,malfestio_server=debug ``` +See `.env.example` for a complete template. + ## Additional Resources - [AT Protocol OAuth Guide](https://docs.bsky.app/blog/oauth-atproto) diff --git a/justfile b/justfile index dfbf18b..b8e81be 100644 --- a/justfile +++ b/justfile @@ -64,16 +64,9 @@ test-all: test web-test migrate: cargo run --bin malfestio-cli migrate -# Setup and test OAuth flow with real Bluesky account -test-oauth: - @echo "Testing OAuth with Bluesky account..." - @echo "1. Ensure PostgreSQL is running" - @echo "2. Running migrations..." - @just migrate - @echo "3. Start backend with: just start" - @echo "4. Start frontend with: just web-dev" - @echo "5. Navigate to http://localhost:3000/login" - @echo "6. Enter your Bluesky handle from .env" +# Test handle and DID resolution for a Bluesky account +verify HANDLE: + cargo run --bin malfestio-cli check {{HANDLE}} # Clean build artifacts clean: -- 2.51.2