Files
Ricardo 6e63422c21 feat!: replace cursor-based status IDs with MongoDB ObjectId
BREAKING: Status IDs are now _id.toString() instead of
encodeCursor(published). This fixes the critical threading bug where
multiple posts sharing the same published second produced identical
IDs, causing findTimelineItemById to return the wrong document.

Changes:
- status.js: id = _id.toString() (unique, chronologically sortable)
- notification.js: same
- findTimelineItemById: ObjectId-only lookup (no cursor fallback)
- pagination.js: _id-based cursor pagination ($lt/$gt on ObjectId)
- resolve-reply-ids.js: returns _id.toString() for parent IDs
- Removed all encodeCursor/decodeCursor usage from API layer

ObjectIds have a 4-byte timestamp prefix so chronological sort via
_id: -1 works correctly. Pagination cursors are now ObjectId hex
strings in Link headers.
2026-03-31 09:57:37 +02:00

47 lines
1.6 KiB
JavaScript

/**
* Batch-resolve inReplyTo URLs to ObjectId strings and account IDs.
*
* Looks up parent posts in ap_timeline by uid/url and returns two Maps:
* - replyIdMap: inReplyTo URL → parent _id.toString()
* - replyAccountIdMap: inReplyTo URL → parent author account ID
*
* @param {object} collection - ap_timeline MongoDB collection
* @param {Array<object>} items - Timeline items with optional inReplyTo
* @returns {Promise<{replyIdMap: Map<string, string>, replyAccountIdMap: Map<string, string>}>}
*/
import { remoteActorId } from "./id-mapping.js";
export async function resolveReplyIds(collection, items) {
const replyIdMap = new Map();
const replyAccountIdMap = new Map();
if (!collection || !items?.length) return { replyIdMap, replyAccountIdMap };
const urls = [
...new Set(
items.map((item) => item.inReplyTo).filter(Boolean),
),
];
if (urls.length === 0) return { replyIdMap, replyAccountIdMap };
const parents = await collection
.find({ $or: [{ uid: { $in: urls } }, { url: { $in: urls } }] })
.project({ uid: 1, url: 1, "author.url": 1 })
.toArray();
for (const parent of parents) {
const parentId = parent._id.toString();
const authorUrl = parent.author?.url;
const authorAccountId = authorUrl ? remoteActorId(authorUrl) : null;
const setMaps = (key) => {
replyIdMap.set(key, parentId);
if (authorAccountId) replyAccountIdMap.set(key, authorAccountId);
};
if (parent.uid) setMaps(parent.uid);
if (parent.url && parent.url !== parent.uid) setMaps(parent.url);
}
return { replyIdMap, replyAccountIdMap };
}