[object Object]

← back to Dw Launches

server: add /api/streams (per-channel segmented board) + /api/dashboard (overview counts)

c49590af13ba1e9fa3fb727ac15b1f61efca6fac · 2026-06-19 10:34:28 -0700 · SteveStudio2

Files touched

Diff

commit c49590af13ba1e9fa3fb727ac15b1f61efca6fac
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Fri Jun 19 10:34:28 2026 -0700

    server: add /api/streams (per-channel segmented board) + /api/dashboard (overview counts)
---
 server.js | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 89 insertions(+)

diff --git a/server.js b/server.js
index 90a8e0b..b2b7717 100644
--- a/server.js
+++ b/server.js
@@ -204,6 +204,95 @@ app.get('/api/calendar', (_req, res) => {
   res.json({ events });
 });
 
+// Per-channel STREAMS feed — for the Hootsuite-style board. Each channel gets
+// its posts bucketed by status (scheduled / pending / published / drafts).
+// A launch appears in every channel it targets.
+app.get('/api/streams', (_req, res) => {
+  const db = loadStore();
+  const cols = {};
+  CHANNELS.forEach(c => {
+    cols[c.id] = { channel: c.id, name: c.name, connected: c.connected,
+      scheduled: [], pending: [], published: [], drafts: [] };
+  });
+  db.launches.forEach(l => {
+    const chans = (l.channelsSelected && l.channelsSelected.length)
+      ? l.channelsSelected : Object.keys(l.channels || {});
+    chans.forEach(ch => {
+      if (!cols[ch]) return;
+      const cs = (l.channels && l.channels[ch] && l.channels[ch].schedule) || l.schedule || {};
+      const body = (l.channels && l.channels[ch] && l.channels[ch].body) || l.global?.body || '';
+      const item = {
+        launchId: l.id, title: l.title, channel: ch,
+        body: body.slice(0, 220), status: l.status,
+        published: !!l.published, datetime: cs.datetime || null,
+        mode: cs.mode || l.schedule?.mode || null,
+        approved: !!(l.approval && l.approval.approved),
+        created_at: l.created_at, updated_at: l.updated_at
+      };
+      // Bucket: published wins, then explicit status, then schedule mode.
+      if (l.published || l.status === 'published') cols[ch].published.push(item);
+      else if (l.status === 'pending_approval') cols[ch].pending.push(item);
+      else if (l.status === 'approved') cols[ch].pending.push(item); // approved = awaiting publish, sits in pending lane
+      else if (item.datetime || cs.mode === 'schedule' || cs.mode === 'queue' || cs.mode === 'now') cols[ch].scheduled.push(item);
+      else cols[ch].drafts.push(item);
+    });
+  });
+  // sort each lane: scheduled by datetime asc, others by updated desc
+  Object.values(cols).forEach(col => {
+    col.scheduled.sort((a, b) => new Date(a.datetime || 0) - new Date(b.datetime || 0));
+    ['pending', 'published', 'drafts'].forEach(k =>
+      col[k].sort((a, b) => new Date(b.updated_at || b.created_at || 0) - new Date(a.updated_at || a.created_at || 0)));
+  });
+  res.json({ columns: CHANNELS.map(c => cols[c.id]) });
+});
+
+// Dashboard / overview counts for the command-center landing.
+app.get('/api/dashboard', (_req, res) => {
+  const db = loadStore();
+  const now = new Date();
+  const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+  const endOfToday = new Date(startOfToday); endOfToday.setDate(endOfToday.getDate() + 1);
+  const weekAgo = new Date(now); weekAgo.setDate(weekAgo.getDate() - 7);
+
+  let scheduledToday = 0, pendingApproval = 0, publishedThisWeek = 0;
+  let drafts = 0, approved = 0, totalLaunches = db.launches.length;
+  const perChannel = {};
+  CHANNELS.forEach(c => { perChannel[c.id] = { name: c.name, scheduled: 0, pending: 0, published: 0, drafts: 0, total: 0 }; });
+
+  db.launches.forEach(l => {
+    if (l.status === 'pending_approval') pendingApproval++;
+    if (l.status === 'approved') approved++;
+    if (l.status === 'draft') drafts++;
+    const chans = (l.channelsSelected && l.channelsSelected.length)
+      ? l.channelsSelected : Object.keys(l.channels || {});
+    chans.forEach(ch => {
+      if (!perChannel[ch]) return;
+      perChannel[ch].total++;
+      const cs = (l.channels && l.channels[ch] && l.channels[ch].schedule) || l.schedule || {};
+      const dt = cs.datetime ? new Date(cs.datetime) : null;
+      if (l.published || l.status === 'published') {
+        perChannel[ch].published++;
+        const pAt = l.publishedSimulatedAt ? new Date(l.publishedSimulatedAt) : (l.updated_at ? new Date(l.updated_at) : null);
+        if (pAt && pAt >= weekAgo) publishedThisWeek++;
+      } else if (l.status === 'pending_approval' || l.status === 'approved') {
+        perChannel[ch].pending++;
+        if (dt && dt >= startOfToday && dt < endOfToday) scheduledToday++;
+      } else if (dt) {
+        perChannel[ch].scheduled++;
+        if (dt >= startOfToday && dt < endOfToday) scheduledToday++;
+      } else {
+        perChannel[ch].drafts++;
+      }
+    });
+  });
+
+  res.json({
+    counts: { scheduledToday, pendingApproval, publishedThisWeek, drafts, approved, totalLaunches },
+    perChannel: CHANNELS.map(c => Object.assign({ id: c.id, connected: c.connected }, perChannel[c.id])),
+    generatedAt: now.toISOString()
+  });
+});
+
 app.get('/api/health', (_req, res) => res.json({ ok: true, port: PORT }));
 
 app.listen(PORT, () => {

← 8db4dc5 add modal-rig.js drop-in (vanilla drag/resize/maximize/persi  ·  back to Dw Launches  ·  command-center suite: view switcher (Dashboard/Compose/Strea a2037e7 →