← 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 →