+4
-26
.claude/settings.local.json
+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
+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
+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
+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
+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,