[object Object]

← back to AsSeenInMovies

asseeninmovies: GET /spotted/queue.csv — admin CSV export of pending submissions (token-gated, no-store). Attachment download with date in filename. 69/69.

2c2ee459d0a361635b8270c8b93a20f274828657 · 2026-05-13 08:14:11 -0700 · SteveStudio2

Files touched

Diff

commit 2c2ee459d0a361635b8270c8b93a20f274828657
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 08:14:11 2026 -0700

    asseeninmovies: GET /spotted/queue.csv — admin CSV export of pending submissions (token-gated, no-store). Attachment download with date in filename. 69/69.
---
 server.js | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/server.js b/server.js
index 4d25954..3a46932 100644
--- a/server.js
+++ b/server.js
@@ -834,6 +834,31 @@ app.get('/api/spotted/stats', async (_req, res) => {
 // stamps verified_by='rejected:<reason>' so the row is filtered out of
 // the public gallery WITHOUT being deleted — undoable from psql.
 // ────────────────────────────────────────────────────────────────────
+// /spotted/queue.csv — admin CSV export of pending submissions. Same gate
+// as /spotted/queue. Useful for moderators who want to review offline.
+app.get('/spotted/queue.csv', async (req, res) => {
+  res.setHeader('Cache-Control', 'no-store, must-revalidate');
+  if (!isAdmin(req)) return res.status(401).type('text/plain').send('admin token required');
+  try {
+    const r = await pool.query(`
+      SELECT s.id, s.product_name, s.product_category, s.brand, s.scene_timecode,
+             s.scene_description, s.shop_url, s.submitted_by_email, s.submitted_by_role,
+             s.created_at, m.title AS movie_title, m.release_year
+        FROM asim_spotted s JOIN asim_movies m ON m.id = s.movie_id
+       WHERE s.verified_at IS NULL ORDER BY s.created_at ASC`);
+    const escapeCsv = v => v == null ? '' : '"' + String(v).replace(/"/g, '""') + '"';
+    const cols = ['id','movie_title','release_year','product_name','product_category','brand','scene_timecode','scene_description','shop_url','submitted_by_email','submitted_by_role','created_at'];
+    const out = [cols.join(',')];
+    for (const row of r.rows) {
+      out.push(cols.map(c => escapeCsv(row[c])).join(','));
+    }
+    res.setHeader('Content-Disposition', `attachment; filename="spotted-queue-${new Date().toISOString().slice(0,10)}.csv"`);
+    res.type('text/csv').send(out.join('\n'));
+  } catch (e) {
+    res.status(500).type('text/plain').send(`error: ${e.message}`);
+  }
+});
+
 app.get('/spotted/queue', async (req, res) => {
   // Admin pages must never sit in any cache (shared or browser) — auth state
   // is token-gated and stale snapshots could leak privileged data.

← e8ce9a1 asim nav dice icon — +aria-label for screen readers ("Show a  ·  back to AsSeenInMovies  ·  asim +.deploy.conf — wire into /deploy skill (rsync→Kamatera 7d3963c →