@rmdes/indiekit-endpoint-conversations 2.3.2 → 2.4.0
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.
|
@@ -80,6 +80,19 @@ async function dashboard(request, response) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// Get item counts by channel (ingestion path)
|
|
84
|
+
let channelCounts = {};
|
|
85
|
+
if (itemsCollection) {
|
|
86
|
+
const counts = await itemsCollection
|
|
87
|
+
.aggregate([
|
|
88
|
+
{ $group: { _id: "$channel", count: { $sum: 1 } } },
|
|
89
|
+
])
|
|
90
|
+
.toArray();
|
|
91
|
+
for (const c of counts) {
|
|
92
|
+
channelCounts[c._id] = c.count;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
response.render("conversations", {
|
|
84
97
|
title: response.__
|
|
85
98
|
? response.__("conversations.title")
|
|
@@ -90,6 +103,7 @@ async function dashboard(request, response) {
|
|
|
90
103
|
summaries,
|
|
91
104
|
recentItems,
|
|
92
105
|
platformCounts,
|
|
106
|
+
channelCounts,
|
|
93
107
|
typeCounts,
|
|
94
108
|
baseUrl: config.mountPath || "/conversations",
|
|
95
109
|
});
|
|
@@ -103,6 +117,7 @@ async function dashboard(request, response) {
|
|
|
103
117
|
summaries: [],
|
|
104
118
|
recentItems: [],
|
|
105
119
|
platformCounts: {},
|
|
120
|
+
channelCounts: {},
|
|
106
121
|
typeCounts: {},
|
|
107
122
|
});
|
|
108
123
|
}
|
|
@@ -399,6 +414,7 @@ async function ingest(request, response) {
|
|
|
399
414
|
const item = {
|
|
400
415
|
canonical_url: canonicalUrl,
|
|
401
416
|
source: classification.source,
|
|
417
|
+
channel: "webhook",
|
|
402
418
|
type: classification.type,
|
|
403
419
|
author: webmention.author || {
|
|
404
420
|
name: "Unknown",
|
package/lib/polling/scheduler.js
CHANGED
|
@@ -103,6 +103,9 @@ export async function runPollCycle(indiekit, options) {
|
|
|
103
103
|
|
|
104
104
|
// Backfill platform names for items stored as "activitypub" (one-time)
|
|
105
105
|
await backfillPlatformNames(indiekit, stateCollection);
|
|
106
|
+
|
|
107
|
+
// Backfill channel field for items predating its introduction (one-time)
|
|
108
|
+
await backfillChannelField(indiekit, stateCollection);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
/**
|
|
@@ -377,6 +380,84 @@ async function backfillPlatformNames(indiekit, stateCollection) {
|
|
|
377
380
|
}
|
|
378
381
|
}
|
|
379
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Backfill the `channel` field for existing items that predate its introduction.
|
|
385
|
+
* Derives channel from platform_id prefix and bridgy_url presence.
|
|
386
|
+
* One-time operation — marks complete after first successful run.
|
|
387
|
+
*/
|
|
388
|
+
async function backfillChannelField(indiekit, stateCollection) {
|
|
389
|
+
try {
|
|
390
|
+
const itemsCollection = indiekit.collections.get("conversation_items");
|
|
391
|
+
if (!itemsCollection) return;
|
|
392
|
+
|
|
393
|
+
const state = await stateCollection.findOne({ _id: "poll_cursors" });
|
|
394
|
+
if (state?.channel_backfill_complete) return;
|
|
395
|
+
|
|
396
|
+
// Find all items missing the channel field
|
|
397
|
+
const itemsWithoutChannel = await itemsCollection
|
|
398
|
+
.find({ channel: { $exists: false } })
|
|
399
|
+
.project({ _id: 1, platform_id: 1, bridgy_url: 1 })
|
|
400
|
+
.toArray();
|
|
401
|
+
|
|
402
|
+
if (itemsWithoutChannel.length === 0) {
|
|
403
|
+
await stateCollection.findOneAndUpdate(
|
|
404
|
+
{ _id: "poll_cursors" },
|
|
405
|
+
{ $set: { channel_backfill_complete: true } },
|
|
406
|
+
{ upsert: true },
|
|
407
|
+
);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Group items by derived channel for batched updateMany
|
|
412
|
+
const groups = new Map();
|
|
413
|
+
|
|
414
|
+
for (const item of itemsWithoutChannel) {
|
|
415
|
+
const pid = item.platform_id || "";
|
|
416
|
+
const hasBridgy = !!item.bridgy_url;
|
|
417
|
+
let channel;
|
|
418
|
+
|
|
419
|
+
if (pid.startsWith("activitypub:")) {
|
|
420
|
+
channel = "activitypub_inbox";
|
|
421
|
+
} else if (hasBridgy) {
|
|
422
|
+
channel = "webhook";
|
|
423
|
+
} else if (pid.startsWith("mastodon:")) {
|
|
424
|
+
channel = "mastodon_api";
|
|
425
|
+
} else if (pid.startsWith("bluesky:")) {
|
|
426
|
+
channel = "bluesky_api";
|
|
427
|
+
} else {
|
|
428
|
+
channel = "webhook";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!groups.has(channel)) groups.set(channel, []);
|
|
432
|
+
groups.get(channel).push(item._id);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let updated = 0;
|
|
436
|
+
|
|
437
|
+
for (const [channel, ids] of groups) {
|
|
438
|
+
const result = await itemsCollection.updateMany(
|
|
439
|
+
{ _id: { $in: ids } },
|
|
440
|
+
{ $set: { channel } },
|
|
441
|
+
);
|
|
442
|
+
updated += result.modifiedCount;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (updated > 0) {
|
|
446
|
+
console.info(
|
|
447
|
+
`[Conversations] Channel backfill: set channel on ${updated} items`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
await stateCollection.findOneAndUpdate(
|
|
452
|
+
{ _id: "poll_cursors" },
|
|
453
|
+
{ $set: { channel_backfill_complete: true } },
|
|
454
|
+
{ upsert: true },
|
|
455
|
+
);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.warn("[Conversations] Channel backfill error:", error.message);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
380
461
|
/**
|
|
381
462
|
* Poll Mastodon notifications and store matching interactions
|
|
382
463
|
*/
|
|
@@ -431,6 +512,7 @@ async function pollMastodon(indiekit, stateCollection, state, credentials) {
|
|
|
431
512
|
await upsertConversationItem(indiekit, {
|
|
432
513
|
canonical_url: canonicalUrl,
|
|
433
514
|
source: "mastodon",
|
|
515
|
+
channel: "mastodon_api",
|
|
434
516
|
type: notification.type,
|
|
435
517
|
author: notification.author,
|
|
436
518
|
content: notification.content,
|
|
@@ -527,6 +609,7 @@ async function pollBluesky(indiekit, stateCollection, state, credentials) {
|
|
|
527
609
|
await upsertConversationItem(indiekit, {
|
|
528
610
|
canonical_url: canonicalUrl,
|
|
529
611
|
source: "bluesky",
|
|
612
|
+
channel: "bluesky_api",
|
|
530
613
|
type: notification.type,
|
|
531
614
|
author: notification.author,
|
|
532
615
|
content: notification.content,
|
|
@@ -644,6 +727,7 @@ async function pollActivityPub(indiekit, stateCollection, state) {
|
|
|
644
727
|
await upsertConversationItem(indiekit, {
|
|
645
728
|
canonical_url: interaction.canonical_url,
|
|
646
729
|
source: interaction.platform,
|
|
730
|
+
channel: "activitypub_inbox",
|
|
647
731
|
type: interaction.type,
|
|
648
732
|
author: interaction.author,
|
|
649
733
|
content: interaction.content,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-conversations",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
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",
|
package/views/conversations.njk
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</p>
|
|
38
38
|
{% endif %}
|
|
39
39
|
<p style="font-size: 0.85em; margin: 0.25rem 0">
|
|
40
|
-
{{
|
|
40
|
+
{{ channelCounts.mastodon_api or 0 }} {{ __("conversations.dashboard.itemsCollected") }}
|
|
41
41
|
</p>
|
|
42
42
|
{% else %}
|
|
43
43
|
<p style="font-size: 0.85em; color: #6b7280; margin: 0.25rem 0">
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
</p>
|
|
72
72
|
{% endif %}
|
|
73
73
|
<p style="font-size: 0.85em; margin: 0.25rem 0">
|
|
74
|
-
{{
|
|
74
|
+
{{ channelCounts.bluesky_api or 0 }} {{ __("conversations.dashboard.itemsCollected") }}
|
|
75
75
|
</p>
|
|
76
76
|
{% else %}
|
|
77
77
|
<p style="font-size: 0.85em; color: #6b7280; margin: 0.25rem 0">
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
</p>
|
|
106
106
|
{% endif %}
|
|
107
107
|
<p style="font-size: 0.85em; margin: 0.25rem 0">
|
|
108
|
-
{{
|
|
108
|
+
{{ channelCounts.activitypub_inbox or 0 }} {{ __("conversations.dashboard.itemsCollected") }}
|
|
109
109
|
</p>
|
|
110
110
|
{% else %}
|
|
111
111
|
<p style="font-size: 0.85em; color: #6b7280; margin: 0.25rem 0">
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
POST {{ baseUrl }}/ingest
|
|
131
131
|
</code>
|
|
132
132
|
<p style="font-size: 0.85em; margin: 0.25rem 0">
|
|
133
|
-
{{
|
|
133
|
+
{{ channelCounts.webhook or 0 }} {{ __("conversations.dashboard.itemsReceived") }}
|
|
134
134
|
</p>
|
|
135
135
|
</div>
|
|
136
136
|
</div>
|