A third party ATProto appview

fixes

+4 -26
.claude/settings.local.json
··· 1 1 { 2 2 "permissions": { 3 3 "allow": [ 4 - "Bash(cat:*)", 5 - "Bash(git add:*)", 6 - "Bash(git commit:*)", 7 - "Bash(git push)", 8 - "Bash(npm run lint:*)", 9 - "Bash(npm run check:*)", 10 - "Bash(npm run lint:fix:*)", 11 - "Bash(npm run db:generate:*)", 12 - "Bash(npm run:*)", 13 - "Bash(npm audit:*)", 14 - "Bash(npm install)", 15 - "Bash(curl:*)", 16 - "Bash(npx tsc:*)", 17 - "Bash(findstr:*)", 18 - "Bash(yarn install)", 19 - "Bash(node --version:*)", 20 - "Bash(npm:*)", 21 - "Bash(docker-compose restart:*)", 22 - "Bash(echo \"# Bluesky Branding Removal Checklist\n\n## 1. App Icons & Images\n- assets/app-icons/*.png - App icons (replace with Aurora Prism branding)\n- assets/favicon.png - Browser favicon\n- assets/icon-android-*.png - Android icons\n- assets/default-avatar.png - Default avatar image\n\n## 2. App Metadata\n- app.json - App name, slug, description\n- package.json - App name and description\n\n## 3. Text References (276 occurrences)\n- Onboarding screens (src/screens/Onboarding/)\n- Signup screens (src/screens/Signup/)\n- Settings/About screens\n- Terms of Service / Privacy Policy references\n- Help text and tooltips\n- Error messages mentioning Bluesky\n\n## 4. URLs\n- bsky.app references (feed URLs, profile URLs)\n- bsky.social references\n- Links to Bluesky support/help\n\n## 5. Service Names\n- Bluesky Moderation Service references\n- Default feed generator names\n\nTotal: 276 text references found\")", 23 - "Bash(psql \"$DATABASE_URL\" -c \"SELECT \n (SELECT COUNT(*) FROM users) as users,\n (SELECT COUNT(*) FROM posts) as posts,\n (SELECT COUNT(*) FROM likes) as likes,\n (SELECT COUNT(*) FROM reposts) as reposts,\n (SELECT COUNT(*) FROM follows) as follows,\n (SELECT COUNT(*) FROM blocks) as blocks;\")", 24 - "Bash(dig +short TXT _atproto.spacelawshitpost.me)", 25 - "Bash(python3 -m json.tool)", 26 - "Bash(npx --yes @expo/cli export:web)", 27 - "Bash(magick favicon.png -define icon:auto-resize=16,32,48 favicon.ico)", 28 - "Bash(convert favicon.png -define icon:auto-resize=16,32,48 favicon.ico)", 29 - "Bash(npx --yes @fiahfy/ico-cli -i assets/favicon.png -o assets/favicon.ico)" 4 + "Bash(node -e \"const {db} = require(''''./dist/server/db''''); db.execute(''''SELECT COUNT(*) as feed_count FROM feed_generators;'''').then(r => console.log(r.rows[0])).catch(e => console.error(e))\")", 5 + "Bash(node -e \"const data = require(''''fs'''').readFileSync(0, ''''utf-8''''); const json = JSON.parse(data); console.log(''''Service DID:'''', json.view.did); console.log(''''Service endpoint (from DID doc):'''', json.view.did)\")", 6 + "Bash(node -e \"const data = require(''''fs'''').readFileSync(0, ''''utf-8''''); const json = JSON.parse(data); const feedgenService = json.service?.find(s => s.id === ''''#bsky_fg''''); console.log(''''Feed Generator Endpoint:'''', feedgenService?.serviceEndpoint || ''''Not found'''')\")", 7 + "Bash(node -e \"const data = require(''''fs'''').readFileSync(0, ''''utf-8''''); console.log(''''Response:'''', data.substring(0, 1000))\")" 30 8 ], 31 9 "deny": [], 32 10 "ask": []
+82
server/scripts/fix-future-dated-posts.ts
··· 1 + /** 2 + * One-time script to fix future-dated posts in the database 3 + * 4 + * This fixes posts that were created with timestamps in the future, 5 + * which causes them to appear at the top of timelines incorrectly. 6 + * 7 + * Run with: npx tsx server/scripts/fix-future-dated-posts.ts 8 + */ 9 + 10 + import { db } from '../db'; 11 + import { posts, feedItems } from '../../shared/schema'; 12 + import { sql } from 'drizzle-orm'; 13 + 14 + async function fixFutureDatedPosts() { 15 + console.log('[FIX] Starting fix for future-dated posts...'); 16 + 17 + try { 18 + // Find all posts with createdAt in the future (with 5 min grace period) 19 + const gracePeriod = sql`INTERVAL '5 minutes'`; 20 + const futurePosts = await db.execute(sql` 21 + SELECT uri, "authorDid", "createdAt", "indexedAt", text 22 + FROM ${posts} 23 + WHERE "createdAt" > NOW() + ${gracePeriod} 24 + ORDER BY "createdAt" DESC 25 + `); 26 + 27 + console.log(`[FIX] Found ${futurePosts.rows.length} future-dated posts`); 28 + 29 + if (futurePosts.rows.length === 0) { 30 + console.log('[FIX] No future-dated posts found. Nothing to fix.'); 31 + return; 32 + } 33 + 34 + // Log the posts we're about to fix 35 + for (const post of futurePosts.rows) { 36 + console.log(`[FIX] Post: ${post.uri}`); 37 + console.log(` Author: ${post.authorDid}`); 38 + console.log(` Original createdAt: ${post.createdAt}`); 39 + console.log(` Text: ${(post.text as string).substring(0, 100)}...`); 40 + } 41 + 42 + // Update posts: set createdAt to indexedAt (when we first saw it) 43 + const updateResult = await db.execute(sql` 44 + UPDATE ${posts} 45 + SET "createdAt" = "indexedAt" 46 + WHERE "createdAt" > NOW() + ${gracePeriod} 47 + `); 48 + 49 + console.log(`[FIX] Updated ${updateResult.rowCount} posts`); 50 + 51 + // Also update feedItems that reference these posts 52 + const feedItemsResult = await db.execute(sql` 53 + UPDATE ${feedItems} 54 + SET "sortAt" = "createdAt" 55 + WHERE "originatorDid" IN ( 56 + SELECT "authorDid" 57 + FROM ${posts} 58 + WHERE "createdAt" > NOW() + ${gracePeriod} 59 + ) 60 + `); 61 + 62 + console.log(`[FIX] Updated ${feedItemsResult.rowCount} feed items`); 63 + 64 + console.log('[FIX] ✓ Future-dated posts have been fixed!'); 65 + console.log('[FIX] These posts will now appear in the correct chronological order.'); 66 + 67 + } catch (error) { 68 + console.error('[FIX] Error fixing future-dated posts:', error); 69 + throw error; 70 + } 71 + } 72 + 73 + // Run the fix 74 + fixFutureDatedPosts() 75 + .then(() => { 76 + console.log('[FIX] Script completed successfully'); 77 + process.exit(0); 78 + }) 79 + .catch((error) => { 80 + console.error('[FIX] Script failed:', error); 81 + process.exit(1); 82 + });
+18 -1
server/services/event-processor.ts
··· 2247 2247 } 2248 2248 2249 2249 // Guard against invalid or missing dates in upstream records 2250 + // Also prevents future-dated posts from appearing at the top of timelines 2250 2251 private safeDate(value: string | Date | undefined): Date { 2251 2252 if (!value) return new Date(); 2252 2253 const d = value instanceof Date ? value : new Date(value); 2253 - return isNaN(d.getTime()) ? new Date() : d; 2254 + 2255 + // If date is invalid, return current time 2256 + if (isNaN(d.getTime())) return new Date(); 2257 + 2258 + const now = new Date(); 2259 + 2260 + // If date is in the future (with 5 minute grace period for clock skew), 2261 + // clamp it to current time to prevent timeline manipulation 2262 + const gracePeriod = 5 * 60 * 1000; // 5 minutes in milliseconds 2263 + if (d.getTime() > now.getTime() + gracePeriod) { 2264 + console.warn( 2265 + `[EVENT_PROCESSOR] Future-dated record detected (${d.toISOString()}), clamping to current time` 2266 + ); 2267 + return now; 2268 + } 2269 + 2270 + return d; 2254 2271 } 2255 2272 2256 2273 private async processDelete(uri: string, collection: string) {
+21 -5
server/services/feed-generator-client.ts
··· 83 83 } 84 84 85 85 const data = await response.json(); 86 - const skeleton = feedSkeletonResponseSchema.parse(data); 86 + 87 + // Try to parse the response - if it fails, log the actual response for debugging 88 + try { 89 + const skeleton = feedSkeletonResponseSchema.parse(data); 87 90 88 - console.log( 89 - `[FeedGenClient] Received ${skeleton.feed.length} posts from feed generator` 90 - ); 91 + console.log( 92 + `[FeedGenClient] Received ${skeleton.feed.length} posts from feed generator` 93 + ); 91 94 92 - return skeleton; 95 + return skeleton; 96 + } catch (parseError) { 97 + console.error( 98 + `[FeedGenClient] Invalid response format from feed generator ${serviceEndpoint}:` 99 + ); 100 + console.error('[FeedGenClient] Response data:', JSON.stringify(data).substring(0, 500)); 101 + console.error('[FeedGenClient] Parse error:', parseError); 102 + 103 + // Throw a more user-friendly error 104 + throw new Error( 105 + `Feed generator at ${serviceEndpoint} returned an invalid response format. ` + 106 + `This may indicate the feed generator is out of date or misconfigured.` 107 + ); 108 + } 93 109 } catch (error) { 94 110 console.error('[FeedGenClient] Error fetching skeleton:', error); 95 111 throw error;
+22
server/services/xrpc-api.ts
··· 1650 1650 return []; 1651 1651 } 1652 1652 1653 + // Check which users exist in database 1654 + const existingUsers = await storage.getUsers(uniqueDids); 1655 + const existingDids = new Set(existingUsers.map(u => u.did)); 1656 + const missingDids = uniqueDids.filter(did => !existingDids.has(did)); 1657 + 1658 + // Fetch missing users from their PDSes 1659 + if (missingDids.length > 0) { 1660 + console.log(`[XRPC] Fetching ${missingDids.length} missing user(s) from their PDSes`); 1661 + 1662 + await Promise.all( 1663 + missingDids.map(async (did) => { 1664 + try { 1665 + const { pdsDataFetcher } = await import('./pds-data-fetcher'); 1666 + await pdsDataFetcher.fetchUser(did); 1667 + console.log(`[XRPC] Successfully fetched user ${did} from their PDS`); 1668 + } catch (error) { 1669 + console.error(`[XRPC] Failed to fetch user ${did} from PDS:`, error); 1670 + } 1671 + }) 1672 + ); 1673 + } 1674 + 1653 1675 const [ 1654 1676 users, 1655 1677 followersCounts,