diff --git a/client/src/components/Planner/PlaceDetailsColumn.tsx b/client/src/components/Planner/PlaceDetailsColumn.tsx index 4afd5c2b6..524598957 100644 --- a/client/src/components/Planner/PlaceDetailsColumn.tsx +++ b/client/src/components/Planner/PlaceDetailsColumn.tsx @@ -402,6 +402,8 @@ function descriptionSourceLabel( return 'Wikivoyage' case 'wikipedia': return 'Wikipedia' + case 'community': + return 'TREK Community' case 'website': try { return new URL(sourceUrl || '').hostname.replace(/^www\./, '') diff --git a/server/src/nest/maps/community-overlay.ts b/server/src/nest/maps/community-overlay.ts index cf85df53e..2692c96b7 100644 --- a/server/src/nest/maps/community-overlay.ts +++ b/server/src/nest/maps/community-overlay.ts @@ -36,7 +36,11 @@ export function withCommunityValues( out.opening_periods = hours.opening_periods; } } - if (values.description?.text) out.summary = values.description.text; + if (values.description?.text) { + out.summary = values.description.text; + // The details panel credits it to the community, not to OpenStreetMap. + out.summary_source = 'community'; + } if (typeof values.community.version === 'number') out.community_version = values.community.version; return out; } diff --git a/server/src/nest/place-enrichment/place-enrichment.service.ts b/server/src/nest/place-enrichment/place-enrichment.service.ts index 6844e6ef4..b488e8845 100644 --- a/server/src/nest/place-enrichment/place-enrichment.service.ts +++ b/server/src/nest/place-enrichment/place-enrichment.service.ts @@ -705,10 +705,14 @@ export class PlaceEnrichmentService { if (!this.maps.trekPlacesEnabled()) return null; try { const got = ( - (await trekPlacesById(placeId.slice(5))) as { description?: { text?: string; sourceUrl?: string } } | null + (await trekPlacesById(placeId.slice(5))) as { + description?: { text?: string; sourceUrl?: string; source?: string }; + } | null )?.description; const text = typeof got?.text === 'string' ? got.text.trim() : ''; if (!text) return null; + // A description installations shared and review published, laid over the index row. + if (got?.source === 'community') return { text, source: 'community', sourceUrl: null, license: 'CC BY 4.0' }; // Through the same allow-list a place's website goes through: this // becomes an href on the client, and the value comes from whatever the // configured index answered with. Anything but http(s) loses the link and @@ -739,6 +743,9 @@ export class PlaceEnrichmentService { // OpenStreetMap first: it costs nothing, it is already fetched, and a // description someone wrote into the map data beats a generated blurb. const osmSummary = typeof details?.summary === 'string' ? details.summary.trim() : ''; + if (osmSummary && details?.summary_source === 'community') { + return { text: osmSummary, source: 'community', sourceUrl: null, license: 'CC BY 4.0' }; + } if (osmSummary && details?.source === 'openstreetmap') { return { text: osmSummary, diff --git a/server/tests/unit/nest/maps.community-overlay.test.ts b/server/tests/unit/nest/maps.community-overlay.test.ts index 7c9f7f2f5..b1fd2a5db 100644 --- a/server/tests/unit/nest/maps.community-overlay.test.ts +++ b/server/tests/unit/nest/maps.community-overlay.test.ts @@ -41,6 +41,7 @@ describe('community values on OSM details', () => { expect(got).toMatchObject({ phone: '+81 6 6440 3855', summary: 'A tower of two linked blocks.', + summary_source: 'community', community_version: 3, name: 'Umeda Sky Building', }); diff --git a/server/tests/unit/nest/place-enrichment.description.test.ts b/server/tests/unit/nest/place-enrichment.description.test.ts index f50874b7d..44643a067 100644 --- a/server/tests/unit/nest/place-enrichment.description.test.ts +++ b/server/tests/unit/nest/place-enrichment.description.test.ts @@ -281,6 +281,29 @@ describe('description source order', () => { expect(bare.description).toMatchObject({ source: 'google', sourceUrl: 'https://maps.google.com/x' }); }); + it('ENRICH-075b: a description installations shared is credited to the TREK community, not to OpenStreetMap', async () => { + const maps = mapsStub({ + details: vi.fn(async () => ({ + place: { + source: 'openstreetmap', + summary: 'An Asian restaurant in Evershagen.', + summary_source: 'community', + osm_url: 'https://osm.org/n/1', + }, + })), + fetchWikiExtractFor: vi.fn(async () => EXTRACT), + }); + + const out = await make(maps).enrich(1, OSM_REQ); + + expect(out.description).toEqual({ + text: 'An Asian restaurant in Evershagen.', + source: 'community', + sourceUrl: null, + license: 'CC BY 4.0', + }); + }); + it('ENRICH-075: keeps the OpenStreetMap description ahead of everything', async () => { const maps = mapsStub({ details: vi.fn(async () => ({ diff --git a/shared/src/maps/maps.schema.ts b/shared/src/maps/maps.schema.ts index 5f89acdf9..b43cbdb19 100644 --- a/shared/src/maps/maps.schema.ts +++ b/shared/src/maps/maps.schema.ts @@ -185,6 +185,8 @@ export const placeDescriptionSourceSchema = z.enum([ 'wikipedia', /** Quoted from the place's own site, via the TREK Places API. */ 'website', + /** Written by a TREK user, shared by an installation and checked before it was published. */ + 'community', ]); export type PlaceDescriptionSource = z.infer;