learn and share notes on atproto (wip) 🦉 malfestio.stormlightlabs.org/
readability solid axum atproto srs
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add check to CLI for diagnosing OAuth and indexing status

* updated local dev docs

+240 -21
+16
.env.example
··· 1 + # Database (Required) 2 + DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable" 3 + 4 + # OAuth Client Configuration (Optional - defaults shown) 5 + APP_URL=http://localhost:3000 6 + APP_NAME=Malfestio 7 + 8 + # Server Configuration (Optional - defaults shown) 9 + SERVER_HOST=127.0.0.1 10 + SERVER_PORT=8080 11 + 12 + # Frontend Configuration (Optional - defaults shown) 13 + VITE_API_URL=http://localhost:8080 14 + 15 + # Logging (Optional) 16 + RUST_LOG=info,malfestio_server=debug
+1
Cargo.lock
··· 1654 1654 name = "malfestio-cli" 1655 1655 version = "0.1.0" 1656 1656 dependencies = [ 1657 + "chrono", 1657 1658 "clap", 1658 1659 "dotenvy", 1659 1660 "malfestio-core",
+1
crates/cli/Cargo.toml
··· 4 4 edition = "2024" 5 5 6 6 [dependencies] 7 + chrono = "0.4" 7 8 clap = { version = "4.5.53", features = ["derive"] } 8 9 dotenvy = "0.15.7" 9 10 malfestio-core = { version = "0.1.0", path = "../core" }
+145
crates/cli/src/main.rs
··· 23 23 #[arg(long)] 24 24 db_url: Option<String>, 25 25 }, 26 + /// Check OAuth flow and database state for a Bluesky handle 27 + Check { 28 + /// Bluesky handle to test (e.g., alice.bsky.social) 29 + handle: String, 30 + }, 26 31 } 27 32 28 33 #[tokio::main] ··· 38 43 } 39 44 Commands::Migrate { db_url } => { 40 45 run_migrations(db_url.as_deref()).await?; 46 + } 47 + Commands::Check { handle } => { 48 + check_flow(handle).await?; 41 49 } 42 50 } 43 51 ··· 144 152 145 153 Ok(()) 146 154 } 155 + 156 + async fn check_flow(handle: &str) -> malfestio_core::Result<()> { 157 + println!("Checking OAuth flow for {}...\n", handle); 158 + 159 + // Get database URL 160 + let db_url = std::env::var("DB_URL") 161 + .or_else(|_| std::env::var("DATABASE_URL")) 162 + .map_err(|_| malfestio_core::Error::InvalidArgument("DB_URL or DATABASE_URL not set".to_string()))?; 163 + 164 + // Test database connection 165 + print!("• Testing database connection... "); 166 + let (client, connection) = tokio_postgres::connect(&db_url, NoTls) 167 + .await 168 + .map_err(|e| malfestio_core::Error::Database(format!("Failed to connect: {}", e)))?; 169 + 170 + tokio::spawn(async move { 171 + if let Err(e) = connection.await { 172 + eprintln!("Database connection error: {}", e); 173 + } 174 + }); 175 + 176 + println!("✓ Connected"); 177 + 178 + let resolver = malfestio_server::oauth::resolver::IdentityResolver::new(); 179 + 180 + print!("• Resolving handle to DID... "); 181 + let did = match resolver.resolve_handle(handle).await { 182 + Ok(did) => { 183 + println!("✓ {}", did); 184 + did 185 + } 186 + Err(e) => { 187 + println!("✗ Failed: {}", e); 188 + return Err(malfestio_core::Error::Other(format!("Handle resolution failed: {}", e))); 189 + } 190 + }; 191 + 192 + print!("• Resolving DID to PDS... "); 193 + let _resolved = match resolver.resolve_did(&did).await { 194 + Ok(resolved) => { 195 + println!("✓ {}", resolved.pds_url); 196 + resolved 197 + } 198 + Err(e) => { 199 + println!("✗ Failed: {}", e); 200 + return Err(malfestio_core::Error::Other(format!("DID resolution failed: {}", e))); 201 + } 202 + }; 203 + 204 + print!("• Checking OAuth tokens... "); 205 + let token_row = client 206 + .query_opt( 207 + "SELECT did, pds_url, created_at, updated_at FROM oauth_tokens WHERE did = $1", 208 + &[&did], 209 + ) 210 + .await 211 + .map_err(|e| malfestio_core::Error::Database(format!("Token query failed: {}", e)))?; 212 + 213 + if let Some(row) = token_row { 214 + let updated_at: chrono::DateTime<chrono::Utc> = row.get(3); 215 + println!("✓ Found (last updated: {})", updated_at.format("%Y-%m-%d %H:%M:%S UTC")); 216 + } else { 217 + println!("✗ Not found"); 218 + println!("\nℹ No OAuth tokens stored yet. Complete OAuth login first:"); 219 + println!(" 1. Start server: just start"); 220 + println!(" 2. Start frontend: just web-dev"); 221 + println!(" 3. Navigate to http://localhost:3000/login"); 222 + println!(" 4. Enter handle: {}", handle); 223 + return Ok(()); 224 + } 225 + 226 + print!("• Checking indexed decks... "); 227 + let deck_rows = client 228 + .query( 229 + "SELECT at_uri, title, indexed_at FROM indexed_decks WHERE did = $1 ORDER BY indexed_at DESC LIMIT 5", 230 + &[&did], 231 + ) 232 + .await 233 + .map_err(|e| malfestio_core::Error::Database(format!("Deck query failed: {}", e)))?; 234 + 235 + if deck_rows.is_empty() { 236 + println!("0 decks"); 237 + } else { 238 + println!("{} deck(s)", deck_rows.len()); 239 + for row in &deck_rows { 240 + let at_uri: String = row.get(0); 241 + let title: Option<String> = row.get(1); 242 + let indexed_at: chrono::DateTime<chrono::Utc> = row.get(2); 243 + let time_ago = format_time_ago(indexed_at); 244 + println!(" - {} ({})", title.unwrap_or_else(|| "Untitled".to_string()), time_ago); 245 + println!(" {}", at_uri); 246 + } 247 + } 248 + 249 + print!("• Checking indexed cards... "); 250 + let card_count: i64 = client 251 + .query_one("SELECT COUNT(*) FROM indexed_cards WHERE did = $1", &[&did]) 252 + .await 253 + .map_err(|e| malfestio_core::Error::Database(format!("Card count query failed: {}", e)))? 254 + .get(0); 255 + 256 + println!("{} card(s)", card_count); 257 + 258 + print!("• Checking indexed notes... "); 259 + let note_count: i64 = client 260 + .query_one("SELECT COUNT(*) FROM indexed_notes WHERE did = $1", &[&did]) 261 + .await 262 + .map_err(|e| malfestio_core::Error::Database(format!("Note count query failed: {}", e)))? 263 + .get(0); 264 + 265 + println!("{} note(s)", note_count); 266 + 267 + println!("\n✓ Status: Ready for testing"); 268 + println!("\nNext steps:"); 269 + println!(" - Publish content via UI to see it indexed"); 270 + println!(" - Check Bluesky profile: https://bsky.app/profile/{}", handle); 271 + println!(" - Inspect records: https://pdsls.dev/at/{}", did); 272 + 273 + Ok(()) 274 + } 275 + 276 + fn format_time_ago(timestamp: chrono::DateTime<chrono::Utc>) -> String { 277 + let now = chrono::Utc::now(); 278 + let duration = now.signed_duration_since(timestamp); 279 + 280 + if duration.num_seconds() < 60 { 281 + format!("{} seconds ago", duration.num_seconds()) 282 + } else if duration.num_minutes() < 60 { 283 + format!("{} minutes ago", duration.num_minutes()) 284 + } else if duration.num_hours() < 24 { 285 + format!("{} hours ago", duration.num_hours()) 286 + } else if duration.num_days() < 30 { 287 + format!("{} days ago", duration.num_days()) 288 + } else { 289 + format!("{} months ago", duration.num_days() / 30) 290 + } 291 + }
+74 -11
docs/local-dev.md
··· 11 11 12 12 ### Bluesky Account Setup 13 13 14 - 1. Create a Bluesky account at <https://bsky.app> 15 - 2. Generate an App Password (Settings → App Passwords) 16 - 3. Configure `.env` with your credentials: 14 + 1. Create a Bluesky account at <https://bsky.app> (you'll use this for OAuth testing) 15 + 16 + ### Environment Configuration 17 + 18 + Copy the template and configure for your environment: 17 19 18 20 ```bash 19 - APP_USERNAME=your-handle.bsky.social 20 - APP_PASSWORD=your-app-password-here 21 - DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable" 21 + cp .env.example .env 22 22 ``` 23 + 24 + For local development, the defaults in `.env.example` work out of the box. You only need to ensure your PostgreSQL connection string is correct. 23 25 24 26 ## Testing OAuth Flow 25 27 ··· 82 84 3. Check your Bluesky profile at <https://bsky.app> to see the published record 83 85 4. Verify record appears in your AT Protocol repository 84 86 85 - ## Environment Variables 87 + ## Verifying Your Setup 88 + 89 + ### Check OAuth Tokens 90 + 91 + After successful login, verify tokens were stored: 92 + 93 + ```sql 94 + SELECT 95 + did, 96 + pds_url, 97 + LEFT(access_token, 20) || '...' as token_preview, 98 + created_at, 99 + updated_at 100 + FROM oauth_tokens 101 + WHERE did = 'your-did-here'; 102 + ``` 103 + 104 + Replace `'your-did-here'` with the DID from your login success page. 105 + 106 + ### Check Indexed Records 107 + 108 + After publishing content, verify firehose indexing: 109 + 110 + ```sql 111 + -- Check indexed decks 112 + SELECT at_uri, title, indexed_at 113 + FROM indexed_decks 114 + WHERE did = 'your-did-here' 115 + ORDER BY indexed_at DESC 116 + LIMIT 10; 117 + 118 + -- Check indexed cards 119 + SELECT at_uri, front_content, indexed_at 120 + FROM indexed_cards 121 + WHERE did = 'your-did-here' 122 + ORDER BY indexed_at DESC 123 + LIMIT 10; 124 + ``` 125 + 126 + Note: Indexing may take 5-10 seconds after publishing. 127 + 128 + ### Diagnostic Command 129 + 130 + Run this command to check handle resolution and database state: 131 + 132 + ```bash 133 + just verify your-handle.bsky.social 134 + ``` 135 + 136 + This will verify: 137 + 138 + - Database connection 139 + - Handle → DID resolution 140 + - DID → PDS URL resolution 141 + - OAuth token status 142 + - Indexed content count 143 + 144 + ## Environment Variables Reference 86 145 87 146 ### Required 88 147 89 148 ```bash 90 - APP_USERNAME=your-handle.bsky.social 91 - APP_PASSWORD=your-app-password 92 149 DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable" 93 150 ``` 94 151 95 152 ### Optional 96 153 97 154 ```bash 98 - # Server configuration 155 + # OAuth Client Configuration 156 + APP_URL=http://localhost:3000 # OAuth callback URL 157 + APP_NAME=Malfestio # App display name 158 + 159 + # Server Configuration 99 160 SERVER_HOST=127.0.0.1 100 161 SERVER_PORT=8080 101 162 102 - # Frontend proxy 163 + # Frontend Configuration 103 164 VITE_API_URL=http://localhost:8080 104 165 105 166 # Logging 106 167 RUST_LOG=info,malfestio_server=debug 107 168 ``` 169 + 170 + See `.env.example` for a complete template. 108 171 109 172 ## Additional Resources 110 173
+3 -10
justfile
··· 64 64 migrate: 65 65 cargo run --bin malfestio-cli migrate 66 66 67 - # Setup and test OAuth flow with real Bluesky account 68 - test-oauth: 69 - @echo "Testing OAuth with Bluesky account..." 70 - @echo "1. Ensure PostgreSQL is running" 71 - @echo "2. Running migrations..." 72 - @just migrate 73 - @echo "3. Start backend with: just start" 74 - @echo "4. Start frontend with: just web-dev" 75 - @echo "5. Navigate to http://localhost:3000/login" 76 - @echo "6. Enter your Bluesky handle from .env" 67 + # Test handle and DID resolution for a Bluesky account 68 + verify HANDLE: 69 + cargo run --bin malfestio-cli check {{HANDLE}} 77 70 78 71 # Clean build artifacts 79 72 clean: