Skip to content

fix(og): news permalink resolves share image via rollup parent POI (#475)#479

Draft
fatherlinux wants to merge 1 commit into
masterfrom
fix/issue-475-ogshareimages
Draft

fix(og): news permalink resolves share image via rollup parent POI (#475)#479
fatherlinux wants to merge 1 commit into
masterfrom
fix/issue-475-ogshareimages

Conversation

@fatherlinux

Copy link
Copy Markdown
Member

Fixes #475.

Root cause

findItemBySlugs filtered news with a direct WHERE n.poi_id = $1, while /api/pois/:id/news expands boundary/org parents via getRollupPoiIds. After the 2026-06-09 seed refresh, the first POI in the test loop with both media and news is a boundary/org POI whose news exists only via rollup from child POIs. So findItemBySlugs returned null, the news-permalink middleware fell through to next(), Express served the static index.html (absolute brand-fallback og:image), and ogImages[0].endsWith(FALLBACK_IMAGE_PATH) was true — failing the assertion at line 125.

Changes (backend/server.js, 4 lines)

  1. findItemBySlugs (news branch): expand to getRollupPoiIds(pool, poi.id) and WHERE n.poi_id = ANY($1), matching the news API.
  2. News-permalink middleware: resolve og:image against item._poi.id (slug-resolved parent POI) instead of item.poi_id.

Provenance

🤖 Maiden autonomous run of Ashigaru — Kagetora dispatched Claude (Sonnet) in an unprivileged sandbox; diagnosed by code-reading only (no DB). Not verified locally — relying on this PR's CI to run ogShareImages. Draft, pending CI + human review.

)

findItemBySlugs filtered news by exact poi_id, but the news API expands boundary/org parents via getRollupPoiIds. After the 2026-06-09 seed refresh the first POI with media+news is a boundary/org POI whose news exists only via child rollup, so the lookup returned null, the middleware fell through, and Express served the static index.html brand-fallback og:image. Expand the news query to poi_id = ANY(getRollupPoiIds(...)) and resolve og:image against the slug-resolved parent POI (item._poi.id).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the findItemBySlugs function in backend/server.js to support rolled-up POIs for news items by fetching rollup POI IDs and querying with ANY. It also changes the Open Graph image resolution to use the parent POI ID (item._poi.id) instead of the child POI ID (item.poi_id). The reviewer suggests extending the rollup POI support to the event branch of findItemBySlugs to prevent similar failures. Additionally, they recommend a tiered fallback strategy for resolving the Open Graph image to prioritize the child POI's image before falling back to the parent POI's image.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread backend/server.js
`, [poi.id]);
rows = q.rows;
} else {
const poiIds = await getRollupPoiIds(pool, poi.id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The event branch of findItemBySlugs (lines 2623–2636) suffers from the exact same issue as the news branch. If an event is rolled up to a parent POI (boundary/org), trying to resolve its permalink via the parent POI's slug will fail because the query strictly filters on WHERE e.poi_id = $1.

To match the events API and support rolled-up events, the event branch should also be updated to use getRollupPoiIds and check (e.poi_id = ANY($1) OR e.venue_poi_id = ANY($1)).

Here is how the event branch should be refactored:

if (type === 'event') {
  const poiIds = await getRollupPoiIds(pool, poi.id);
  const q = await pool.query(`
    SELECT e.id, e.title, e.description, e.start_date, e.end_date, e.event_type,
           e.location_details, e.source_url, e.publication_date, e.collection_date, e.image_url,
           p.name AS poi_name, p.id AS poi_id,
           COALESCE(json_agg(json_build_object('url', u.url, 'source_name', u.source_name)) FILTER (WHERE u.id IS NOT NULL), '[]'::json) AS additional_urls
    FROM poi_events e
    JOIN pois p ON e.poi_id = p.id
    LEFT JOIN poi_event_urls u ON u.event_id = e.id
    WHERE (e.poi_id = ANY($1) OR e.venue_poi_id = ANY($1)) AND e.moderation_status IN ('published', 'auto_approved')
    GROUP BY e.id, p.name, p.id
    ORDER BY e.start_date DESC
  `, [poiIds]);
  rows = q.rows;
}

Comment thread backend/server.js
// Image priority: source article image, then POI primary photo, then brand.
const sourceImage = isUsableSourceImage(item.image_url) ? item.image_url : null;
const ogImage = escapeHtml(sourceImage || await resolvePoiOgImage(item.poi_id, baseUrl));
const ogImage = escapeHtml(sourceImage || await resolvePoiOgImage(item._poi.id, baseUrl));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While falling back to the parent POI's image (item._poi.id) ensures that we don't get a generic brand fallback when the parent has media, it completely bypasses the child POI's specific image (item.poi_id) if one exists. News and events are often highly specific to the child POI (e.g., a specific trailhead or waterfall), so using the child POI's image is much more relevant for social sharing.

We can implement a tiered fallback strategy:

  1. Use the specific child POI's image (item.poi_id) if it has one.
  2. If the child POI has no image (returns the brand fallback), fall back to the parent POI's image (item._poi.id).
  3. Finally, fall back to the brand image.
Suggested change
const ogImage = escapeHtml(sourceImage || await resolvePoiOgImage(item._poi.id, baseUrl));
let resolvedImage = sourceImage;
if (!resolvedImage) {
const childImage = await resolvePoiOgImage(item.poi_id, baseUrl);
if (childImage && !childImage.endsWith(OG_FALLBACK_IMAGE)) {
resolvedImage = childImage;
} else {
resolvedImage = await resolvePoiOgImage(item._poi.id, baseUrl);
}
}
const ogImage = escapeHtml(resolvedImage);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ogShareImages test fails deterministically: news permalink serves brand fallback despite available images

1 participant