← back to AsSeenInMovies
asseeninmovies: /person/:id — surface 'SPOTTED in their films' section (person ⇄ movie ⇄ spotted graph; renders only when at least one verified placement matches person's filmography)
4f8629cd18a26e9f350af1a5f01fc281d960e251 · 2026-05-12 16:25:29 -0700 · SteveStudio2
Files touched
Diff
commit 4f8629cd18a26e9f350af1a5f01fc281d960e251
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 16:25:29 2026 -0700
asseeninmovies: /person/:id — surface 'SPOTTED in their films' section (person ⇄ movie ⇄ spotted graph; renders only when at least one verified placement matches person's filmography)
---
server.js | 30 +++++++++++++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 09e0fa2..9ea2f47 100644
--- a/server.js
+++ b/server.js
@@ -775,6 +775,20 @@ app.get('/person/:id', async (req, res) => {
m.id AS movie_id, m.title, m.release_year, m.poster_url, m.poster_source
FROM asim_credits c JOIN asim_movies m ON m.id = c.movie_id
WHERE c.person_id=$1 ORDER BY m.release_year DESC NULLS LAST`, [id]);
+ // Spotted placements from films this person worked on — surfaces the
+ // person ⇄ movie ⇄ spotted graph. Today this is mostly empty (only 14
+ // verified spotted rows), but the join is structurally correct so it
+ // lights up as submissions accumulate.
+ const personSpotted = await pool.query(`
+ SELECT DISTINCT s.id AS spot_id, s.product_name, s.product_category, s.brand,
+ s.scene_timecode, s.shop_url,
+ m.id AS movie_id, m.title, m.release_year
+ FROM asim_spotted s
+ JOIN asim_movies m ON m.id = s.movie_id
+ JOIN asim_credits c ON c.movie_id = m.id
+ WHERE c.person_id = $1 AND s.verified_at IS NOT NULL
+ ORDER BY m.release_year DESC NULLS LAST, s.product_name
+ LIMIT 24`, [id]);
const isPremium = person.claimed_at && (!person.premium_expires_at || new Date(person.premium_expires_at) > new Date());
const heroHtml = `
@@ -806,7 +820,21 @@ app.get('/person/:id', async (req, res) => {
</div>`).join('')}
</section>` : '<div class="empty">No credits loaded yet.</div>';
- res.type('html').send(htmlShell(person.full_name, heroHtml + filmHtml));
+ const personSpottedHtml = personSpotted.rows.length ? `
+ <section>
+ <h2>SPOTTED in their films · ${personSpotted.rows.length}</h2>
+ ${personSpotted.rows.map(s => `
+ <div class="spotted-row">
+ <div class="badge-col">${esc(s.product_category || 'dressing')}</div>
+ <div>
+ <div style="font-weight:500">${esc(s.product_name)}${s.brand ? ' <span style="color:var(--ink-faint);font-weight:400">— '+esc(s.brand)+'</span>' : ''}</div>
+ <div class="desc"><a href="/movie/${esc(s.movie_id)}#spotted">${esc(s.title)}${s.release_year ? ' ('+esc(s.release_year)+')' : ''}</a>${s.scene_timecode ? ' · ' + esc(s.scene_timecode) : ''}</div>
+ ${s.shop_url ? `<a class="shop" href="${esc(s.shop_url)}" target="_blank" rel="nofollow noopener">Where to find it ↗</a>` : ''}
+ </div>
+ </div>`).join('')}
+ </section>` : '';
+
+ res.type('html').send(htmlShell(person.full_name, heroHtml + filmHtml + personSpottedHtml));
} catch (e) {
res.status(500).type('html').send(htmlShell('Error', `<div class="empty">${esc(e.message)}</div>`));
}
← 3a3c2c7 asseeninmovies: home page — third rail 'Recent SPOTTED' (6 v
·
back to AsSeenInMovies
·
asseeninmovies: /spotted/queue admin moderation — token-gate 0206a11 →