+59
server/services/auto-backfill-follows.ts
+59
server/services/auto-backfill-follows.ts
···
20
20
// Track ongoing backfills to prevent duplicates
21
21
const ongoingBackfills = new Set<string>();
22
22
23
+
// Track ongoing new follow backfills to prevent duplicate cascading
24
+
const ongoingNewFollowBackfills = new Set<string>();
25
+
26
+
// Track recently backfilled users to prevent spam (DID -> timestamp)
27
+
const recentlyBackfilledUsers = new Map<string, number>();
28
+
const NEW_FOLLOW_BACKFILL_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour cooldown
29
+
23
30
export class AutoBackfillFollowsService {
31
+
constructor() {
32
+
// Periodically clean up old entries from recentlyBackfilledUsers to prevent memory leaks
33
+
setInterval(() => {
34
+
const now = Date.now();
35
+
let cleaned = 0;
36
+
const entries = Array.from(recentlyBackfilledUsers.entries());
37
+
for (const [did, timestamp] of entries) {
38
+
if (now - timestamp > NEW_FOLLOW_BACKFILL_COOLDOWN_MS) {
39
+
recentlyBackfilledUsers.delete(did);
40
+
cleaned++;
41
+
}
42
+
}
43
+
if (cleaned > 0) {
44
+
console.log(
45
+
`[AUTO_BACKFILL_FOLLOWS] Cleaned ${cleaned} expired cooldown entries`
46
+
);
47
+
}
48
+
}, 60 * 60 * 1000); // Run every hour
49
+
}
50
+
24
51
/**
25
52
* Check if a user needs follows backfilled and trigger it if needed
26
53
* Called automatically on login
···
768
795
* Backfill posts from a single user (called when following someone new)
769
796
*/
770
797
async backfillNewFollowPosts(followedDid: string): Promise<void> {
798
+
// Check if already backfilling this user
799
+
if (ongoingNewFollowBackfills.has(followedDid)) {
800
+
console.log(
801
+
`[AUTO_BACKFILL_FOLLOWS] Already backfilling posts for ${followedDid}, skipping duplicate request`
802
+
);
803
+
return;
804
+
}
805
+
806
+
// Check if recently backfilled (within cooldown period)
807
+
const lastBackfill = recentlyBackfilledUsers.get(followedDid);
808
+
if (lastBackfill) {
809
+
const timeSinceBackfill = Date.now() - lastBackfill;
810
+
if (timeSinceBackfill < NEW_FOLLOW_BACKFILL_COOLDOWN_MS) {
811
+
const minutesRemaining = Math.ceil(
812
+
(NEW_FOLLOW_BACKFILL_COOLDOWN_MS - timeSinceBackfill) / (60 * 1000)
813
+
);
814
+
console.log(
815
+
`[AUTO_BACKFILL_FOLLOWS] User ${followedDid} was backfilled ${Math.floor(timeSinceBackfill / 60000)} minutes ago, skipping (${minutesRemaining}m cooldown remaining)`
816
+
);
817
+
return;
818
+
}
819
+
}
820
+
771
821
console.log(
772
822
`[AUTO_BACKFILL_FOLLOWS] Backfilling posts from newly followed user: ${followedDid}`
773
823
);
824
+
825
+
// Mark as ongoing
826
+
ongoingNewFollowBackfills.add(followedDid);
774
827
775
828
try {
776
829
const eventProcessor = new EventProcessor(storage);
···
874
927
console.log(
875
928
`[AUTO_BACKFILL_FOLLOWS] Fetched ${postsFetched} posts from newly followed user ${followedDid}`
876
929
);
930
+
931
+
// Record timestamp of successful backfill
932
+
recentlyBackfilledUsers.set(followedDid, Date.now());
877
933
} catch (error) {
878
934
console.error(
879
935
`[AUTO_BACKFILL_FOLLOWS] Error backfilling new follow posts:`,
880
936
error
881
937
);
938
+
} finally {
939
+
// Always remove from ongoing set
940
+
ongoingNewFollowBackfills.delete(followedDid);
882
941
}
883
942
}
884
943
}