@rmdes/indiekit-endpoint-conversations 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -33,10 +33,10 @@ export async function findCanonicalPost(application, syndicationUrl) {
|
|
|
33
33
|
export async function resolveCanonicalUrl(application, targetUrl, siteUrl) {
|
|
34
34
|
// If the target is already on our domain, it's likely canonical
|
|
35
35
|
if (targetUrl.startsWith(siteUrl)) {
|
|
36
|
-
return targetUrl;
|
|
36
|
+
return targetUrl.replace(/\/$/, "");
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Otherwise try to find via syndication reverse lookup
|
|
40
40
|
const canonical = await findCanonicalPost(application, targetUrl);
|
|
41
|
-
return canonical || targetUrl;
|
|
41
|
+
return (canonical || targetUrl).replace(/\/$/, "");
|
|
42
42
|
}
|
package/lib/polling/scheduler.js
CHANGED
|
@@ -95,6 +95,9 @@ export async function runPollCycle(indiekit, options) {
|
|
|
95
95
|
await pollActivityPub(indiekit, stateCollection, state);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Normalize trailing slashes in canonical_url (one-time migration)
|
|
99
|
+
await normalizeCanonicalUrls(indiekit, stateCollection);
|
|
100
|
+
|
|
98
101
|
// Backfill missing avatars from ap_notifications (one-time sweep per cycle)
|
|
99
102
|
await backfillMissingAvatars(indiekit, stateCollection);
|
|
100
103
|
|
|
@@ -102,6 +105,78 @@ export async function runPollCycle(indiekit, options) {
|
|
|
102
105
|
await backfillPlatformNames(indiekit, stateCollection);
|
|
103
106
|
}
|
|
104
107
|
|
|
108
|
+
/**
|
|
109
|
+
* One-time migration: normalize trailing slashes in canonical_url.
|
|
110
|
+
* Strips trailing slash from all canonical_url values, then removes
|
|
111
|
+
* duplicates that were created by the slash inconsistency.
|
|
112
|
+
*/
|
|
113
|
+
async function normalizeCanonicalUrls(indiekit, stateCollection) {
|
|
114
|
+
try {
|
|
115
|
+
const state = await stateCollection.findOne({ _id: "poll_cursors" });
|
|
116
|
+
if (state?.canonical_url_normalized) return;
|
|
117
|
+
|
|
118
|
+
const itemsCollection = indiekit.collections.get("conversation_items");
|
|
119
|
+
if (!itemsCollection) return;
|
|
120
|
+
|
|
121
|
+
// Find all items with trailing slash in canonical_url
|
|
122
|
+
const itemsWithSlash = await itemsCollection
|
|
123
|
+
.find({ canonical_url: /\/$/ })
|
|
124
|
+
.toArray();
|
|
125
|
+
|
|
126
|
+
if (itemsWithSlash.length === 0) {
|
|
127
|
+
await stateCollection.findOneAndUpdate(
|
|
128
|
+
{ _id: "poll_cursors" },
|
|
129
|
+
{ $set: { canonical_url_normalized: true } },
|
|
130
|
+
{ upsert: true },
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let updated = 0;
|
|
136
|
+
let deduped = 0;
|
|
137
|
+
|
|
138
|
+
for (const item of itemsWithSlash) {
|
|
139
|
+
const normalized = item.canonical_url.replace(/\/$/, "");
|
|
140
|
+
|
|
141
|
+
// Check if a non-slash version already exists with same platform_id
|
|
142
|
+
const existing = await itemsCollection.findOne({
|
|
143
|
+
canonical_url: normalized,
|
|
144
|
+
platform_id: item.platform_id,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (existing) {
|
|
148
|
+
// Duplicate — remove the trailing-slash version
|
|
149
|
+
await itemsCollection.deleteOne({ _id: item._id });
|
|
150
|
+
deduped++;
|
|
151
|
+
} else {
|
|
152
|
+
// No duplicate — just normalize the URL
|
|
153
|
+
await itemsCollection.updateOne(
|
|
154
|
+
{ _id: item._id },
|
|
155
|
+
{ $set: { canonical_url: normalized } },
|
|
156
|
+
);
|
|
157
|
+
updated++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (updated > 0 || deduped > 0) {
|
|
162
|
+
console.info(
|
|
163
|
+
`[Conversations] URL normalization: updated ${updated}, removed ${deduped} duplicates from ${itemsWithSlash.length} items with trailing slashes`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await stateCollection.findOneAndUpdate(
|
|
168
|
+
{ _id: "poll_cursors" },
|
|
169
|
+
{ $set: { canonical_url_normalized: true } },
|
|
170
|
+
{ upsert: true },
|
|
171
|
+
);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.warn(
|
|
174
|
+
"[Conversations] URL normalization error:",
|
|
175
|
+
error.message,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
105
180
|
/**
|
|
106
181
|
* Backfill empty author.photo fields in conversation_items.
|
|
107
182
|
* Tries four strategies in order:
|
|
@@ -22,6 +22,11 @@ function getCollection(application) {
|
|
|
22
22
|
export async function upsertConversationItem(application, item) {
|
|
23
23
|
const collection = getCollection(application);
|
|
24
24
|
|
|
25
|
+
// Normalize canonical_url — strip trailing slash for consistent deduplication
|
|
26
|
+
if (item.canonical_url) {
|
|
27
|
+
item.canonical_url = item.canonical_url.replace(/\/$/, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
const result = await collection.findOneAndUpdate(
|
|
26
31
|
{
|
|
27
32
|
canonical_url: item.canonical_url,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-conversations",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Conversation aggregation endpoint for Indiekit. Backend enrichment service that polls Mastodon/Bluesky notifications and serves JF2-compatible data for the interactions page.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|