ReviewOS

also looking at this

stacks/postline

Sync Bluesky engagement metrics into analytics

#12
Merged glennmichael123 wants to merge feat/metrics-sync into main
10 files +200 -4
app/Actions/Postline/AnalyticsAction.tsmodified+19-3
Changes to app/Actions/Postline/AnalyticsAction.ts
@@ -34,7 +34,7 @@ export default new Action({
3434
3535 const targets = await database
3636 .selectFrom('post_targets')
37 .select(['provider', 'status', 'updated_at'])
37 .select(['provider', 'status', 'created_at', 'metrics'])
3838 .where('status', '=', 'published')
3939 .execute()
4040
@@ -77,10 +77,11 @@ export default new Action({
7777 share: totalTargets ? Math.round((count / totalTargets) * 100) : 0,
7878 }))
7979
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.
8182 const byHour = new Map<number, number>()
8283 for (const target of targets) {
83 const date = parseUtc(target.updated_at)
84 const date = parseUtc(target.created_at)
8485 if (date) byHour.set(date.getUTCHours(), (byHour.get(date.getUTCHours()) || 0) + 1)
8586 }
8687 const windows = [...byHour.entries()]
@@ -88,6 +89,20 @@ export default new Action({
8889 .slice(0, 3)
8990 .map(([hour, count]) => ({ hour, count }))
9091
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
91106 return response.json({
92107 ok: true,
93108 data: {
@@ -96,6 +111,7 @@ export default new Action({
96111 postsLastWeek,
97112 bestChannel: channelMix[0] || null,
98113 bestHour: windows[0] || null,
114 engagement,
99115 },
100116 series,
101117 channelMix,