···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 }
22482249 // Guard against invalid or missing dates in upstream records
02250 private safeDate(value: string | Date | undefined): Date {
2251 if (!value) return new Date();
2252 const d = value instanceof Date ? value : new Date(value);
2253- return isNaN(d.getTime()) ? new Date() : d;
00000000000000002254 }
22552256 private async processDelete(uri: string, collection: string) {
···2247 }
22482249 // Guard against invalid or missing dates in upstream records
2250+ // Also prevents future-dated posts from appearing at the top of timelines
2251 private safeDate(value: string | Date | undefined): Date {
2252 if (!value) return new Date();
2253 const d = value instanceof Date ? value : new Date(value);
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;
2271 }
22722273 private async processDelete(uri: string, collection: string) {