also looking at this
Sync Bluesky engagement metrics into analytics
#12
10 files
+200
-4
| @@ -34,7 +34,7 @@ export default new Action({ | ||
| 34 | 34 | |
| 35 | 35 | const targets = await database |
| 36 | 36 | .selectFrom('post_targets') |
| 37 | .select(['provider', 'status', 'updated_at']) | |
| 37 | .select(['provider', 'status', 'created_at', 'metrics']) | |
| 38 | 38 | .where('status', '=', 'published') |
| 39 | 39 | .execute() |
| 40 | 40 | |
| @@ -77,10 +77,11 @@ export default new Action({ | ||
| 77 | 77 | share: totalTargets ? Math.round((count / totalTargets) * 100) : 0, |
| 78 | 78 | })) |
| 79 | 79 | |
| 80 | // Publish-hour histogram (UTC) → best posting windows. | |
| 80 | // Publish-hour histogram (UTC) → best posting windows. Uses | |
| 81 | // created_at: metric syncs bump updated_at, which would skew it. | |
| 81 | 82 | const byHour = new Map<number, number>() |
| 82 | 83 | for (const target of targets) { |
| 83 | const date = parseUtc(target.updated_at) | |
| 84 | const date = parseUtc(target.created_at) | |
| 84 | 85 | if (date) byHour.set(date.getUTCHours(), (byHour.get(date.getUTCHours()) || 0) + 1) |
| 85 | 86 | } |
| 86 | 87 | const windows = [...byHour.entries()] |
| @@ -88,6 +89,20 @@ export default new Action({ | ||
| 88 | 89 | .slice(0, 3) |
| 89 | 90 | .map(([hour, count]) => ({ hour, count })) |
| 90 | 91 | |
| 92 | // Engagement totals from synced post_targets.metrics. | |
| 93 | const engagement = { likes: 0, reposts: 0, replies: 0, syncedTargets: 0 } | |
| 94 | for (const target of targets) { | |
| 95 | if (!target.metrics) continue | |
| 96 | try { | |
| 97 | const metrics = JSON.parse(String(target.metrics)) | |
| 98 | engagement.likes += Number(metrics.likes) || 0 | |
| 99 | engagement.reposts += Number(metrics.reposts) || 0 | |
| 100 | engagement.replies += Number(metrics.replies) || 0 | |
| 101 | engagement.syncedTargets += 1 | |
| 102 | } | |
| 103 | catch {} | |
| 104 | } | |
| 105 | ||
| 91 | 106 | return response.json({ |
| 92 | 107 | ok: true, |
| 93 | 108 | data: { |
| @@ -96,6 +111,7 @@ export default new Action({ | ||
| 96 | 111 | postsLastWeek, |
| 97 | 112 | bestChannel: channelMix[0] || null, |
| 98 | 113 | bestHour: windows[0] || null, |
| 114 | engagement, | |
| 99 | 115 | }, |
| 100 | 116 | series, |
| 101 | 117 | channelMix, |