From 18eea7e743315762aeda8121fcd58600d5d3a235 Mon Sep 17 00:00:00 2001
From: mheavey2 <85061867+mheavey2@users.noreply.github.com>
Date: Wed, 22 Oct 2025 15:14:53 +0100
Subject: [PATCH 1/6] fix: migrate Foursquare API to new Places API endpoints
(#1474)
* fix: migrate Foursquare API to new Places API endpoints
* docs: update readme
* feat: add fetch error handling to the Foursquare API example
---------
Co-authored-by: Yashar Fakhari <6448697+YasharF@users.noreply.github.com>
---
.env.example | 3 +-
README.md | 16 ++++----
controllers/api.js | 44 +++++++++++++--------
views/api/foursquare.pug | 84 ++++++++++++++++++----------------------
4 files changed, 76 insertions(+), 71 deletions(-)
diff --git a/.env.example b/.env.example
index ddb75f3f4a..27030223c0 100644
--- a/.env.example
+++ b/.env.example
@@ -37,8 +37,7 @@ FACEBOOK_SECRET=41860e58c256a3d7ad8267d3c1939a4a
# FB Pixel ID is optional if you are trying to do customer rtracking
FACEBOOK_PIXEL_ID=
-FOURSQUARE_ID=2STROLSFBMZLAHG3IBA141EM2HGRF0IRIBB4KXMOGA2EH3JG
-FOURSQUARE_SECRET=UAABFAWTIHIUFBL0PDC3TDMSXJF2GTGWLD3BES1QHXKAIYQB
+FOURSQUARE_APIKEY=foursquare-service-key
GITHUB_ID=cb448b1d4f0c743a1e36
GITHUB_SECRET=815aa4606f476444691c5f1c16b9c70da6714dc6
diff --git a/README.md b/README.md
index f9e0065631..a3df4e5b73 100644
--- a/README.md
+++ b/README.md
@@ -220,13 +220,15 @@ Obtain SMTP credentials from a provider for transactional emails. Set the SMTP_U
-- Go to Foursquare for Developers and log in
-- Click on **My Apps** in the top menu
-- Click the **Create A New App** button
-- Enter _App Name_, _Welcome page url_,
-- For **Redirect URI**: your BASE_URL value followed by /auth/foursquare/callback (i.e. `http://localhost:8080/auth/foursquare/callback` )
-- Click **Save Changes**
-- Copy and paste _Client ID_ and _Client Secret_ keys into `.env` file
+- Go to Foursquare for Developers and log in
+
+- Click on **Create a new project** button
+- Enter your _Organization_ and _Project Name_
+- Click **Create**
+- Navigate to your project
+- Click **Settings** in the left-hand-side menu
+- Generate a Service API Key
+- Copy and paste the Service API Key as `FOURSQUARE_APIKEY` in your `.env` file
diff --git a/controllers/api.js b/controllers/api.js
index 60c51d84e9..f7f3d2559c 100644
--- a/controllers/api.js
+++ b/controllers/api.js
@@ -32,29 +32,41 @@ exports.getApi = (req, res) => {
*/
exports.getFoursquare = async (req, res, next) => {
try {
- const headers = {
- Authorization: `${process.env.FOURSQUARE_APIKEY}`,
+ const options = {
+ method: 'GET',
+ headers: {
+ accept: 'application/json',
+ 'X-Places-Api-Version': '2025-06-17',
+ authorization: `Bearer ${process.env.FOURSQUARE_APIKEY}`,
+ },
+ };
+
+ const fetchJson = async (url, fetchOptions, label) => {
+ const res = await fetch(url, fetchOptions);
+ if (!res.ok) {
+ const text = await res.text().catch(() => '');
+ throw new Error(`${label} failed: ${res.status} ${res.statusText} - ${text}`);
+ }
+ return res.json();
};
- const [trendingVenuesRes, venueDetailRes, venuePhotosRes] = await Promise.all([
- fetch('https://api.foursquare.com/v3/places/search?ll=47.609657,-122.342148&limit=10', {
- headers,
- }).then((res) => res.json()),
- fetch('https://api.foursquare.com/v3/places/427ea800f964a520b1211fe3', {
- headers,
- }).then((res) => res.json()),
- fetch('https://api.foursquare.com/v3/places/427ea800f964a520b1211fe3/photos', {
- headers,
- }).then((res) => res.json()),
+ const [trendingVenuesRes, venueDetailRes] = await Promise.all([
+ fetchJson('https://places-api.foursquare.com/places/search?ll=47.609657,-122.342148&limit=10', options, 'Foursquare search'),
+ fetchJson('https://places-api.foursquare.com/places/427ea800f964a520b1211fe3', options, 'Foursquare venue detail'),
]);
res.render('api/foursquare', {
- title: 'Foursquare API (v3)',
- trendingVenues: trendingVenuesRes.results,
+ title: 'Foursquare Places API',
+ trendingVenues: trendingVenuesRes.results || [],
venueDetail: venueDetailRes,
- venuePhotos: venuePhotosRes.slice(0, 9), // Limit the photos to 9
});
} catch (error) {
- next(error);
+ console.error('Foursquare API Error:', error);
+ return res.status(500).render('api/foursquare', {
+ title: 'Foursquare Places API',
+ trendingVenues: [],
+ venueDetail: null,
+ error: 'Failed to fetch Foursquare data',
+ });
}
};
diff --git a/views/api/foursquare.pug b/views/api/foursquare.pug
index ee5bc088e9..989369393a 100644
--- a/views/api/foursquare.pug
+++ b/views/api/foursquare.pug
@@ -7,56 +7,48 @@ block content
| Foursquare API
.btn-group.mb-4.d-flex(role='group')
- a.btn.btn-primary.w-100(href='https://location.foursquare.com/developer', target='_blank')
+ a.btn.btn-primary.w-100(href='https://foursquare.com/developer', target='_blank')
i.far.fa-check-square.fa-sm.me-2
| Developer Info
a.btn.btn-primary.w-100(href='https://docs.foursquare.com/', target='_blank')
i.fas.fa-code-branch.fa-sm.me-2
| API Docs
- h3.text-primary Trending Venues
- p Near longitude: -122.342148, latitude: 47.609657
- table.table.table-striped.table-bordered
- thead
- tr
- th.d-xs
- th Name
- th.d-xs.d-sm Category
- th.d-xs Address
- th.d-xs Distance (meters)
- th.d-xs Open Now ?
- tbody
- each venue in trendingVenues
+ if error
+ .alert.alert-danger.mt-3 #{ error }
+ else
+ h3.text-primary Trending Venues
+ p Near longitude: -122.342148, latitude: 47.609657
+ table.table.table-striped.table-bordered
+ thead
tr
- td.d-xs
- if venue.categories && venue.categories.length > 0
- img(src=venue.categories[0].icon.prefix + '32' + venue.categories[0].icon.suffix, alt=venue.categories[0].name, width='32', height='32')
- else
- | N/A
- td= venue.name
- td.d-xs.d-sm= venue.categories && venue.categories.length > 0 ? venue.categories[0].name : 'N/A'
- td.d-xs= venue.location.formatted_address || 'N/A'
- td.d-xs= venue.distance
- td.d-xs= venue.closed_bucket || 'Unknown'
- br
- h3.text-primary Venue Details
- p
- i
- u #{ venueDetail.name }
- if venueDetail.categories && venueDetail.categories.length > 0
+ th.d-xs
+ th Name
+ th.d-xs.d-sm Category
+ th.d-xs Address
+ th.d-xs Distance (meters)
+ tbody
+ each venue in trendingVenues
+ tr
+ td.d-xs
+ if venue.categories && venue.categories.length > 0
+ img(src=venue.categories[0].icon.prefix + '32' + venue.categories[0].icon.suffix, alt=venue.categories[0].name, width='32', height='32')
+ else
+ | N/A
+ td= venue.name
+ td.d-xs.d-sm= venue.categories && venue.categories.length > 0 ? venue.categories[0].name : 'N/A'
+ td.d-xs= venue.location.formatted_address || 'N/A'
+ td.d-xs= venue.distance
+ br
+ h3.text-primary Venue Details
+ p
+ i
+ u #{ venueDetail.name }
+ if venueDetail.categories && venueDetail.categories.length > 0
+ |
+ | is a #{ venueDetail.categories[0].name }
|
- | is a #{ venueDetail.categories[0].name }
- |
- | located at #{ venueDetail.location.address || 'N/A' }, #{ venueDetail.location.locality || 'N/A' }, #{ venueDetail.location.region || 'N/A' }. (longitude: #{ venueDetail.geocodes.main.longitude }, latitude: #{ venueDetail.geocodes.main.latitude })
- if venueDetail.related_places
- if venueDetail.related_places.children && venueDetail.related_places.children.length > 0
- p Related venues or businesses to #{ venueDetail.name }, which are mostly in the same building or the immediate area are:
- p(style='margin-left: 20px; white-space: pre-wrap') #{ venueDetail.related_places.children.map(place => place.name).join(', ') }
- if venuePhotos && venuePhotos.length > 0
- h4.text-secondary Photo Gallery
- | #{ venueDetail.name }
- .row
- each photo in venuePhotos
- .col-md-4
- img.img-thumbnail(src=photo.prefix + '400x400' + photo.suffix, alt=venueDetail.name, width='100%')
- else
- p No photos available.
+ | located at #{ venueDetail.location.address || 'N/A' }, #{ venueDetail.location.locality || 'N/A' }, #{ venueDetail.location.region || 'N/A' }. (longitude: #{ venueDetail.longitude }, latitude: #{ venueDetail.latitude })
+ if venueDetail.related_places
+ if venueDetail.related_places.children && venueDetail.related_places.children.length > 0
+ p Related venues or businesses to #{ venueDetail.name }, which are mostly in the same building or the immediate area are:
+ p(style='margin-left: 20px; white-space: pre-wrap') #{ venueDetail.related_places.children.map(place => place.name).join(', ') }
From 09b5229c2b74dc38d6bd21727d47231b1d995a65 Mon Sep 17 00:00:00 2001
From: "gemini-code-assist-test[bot]"
<200674850+gemini-code-assist-test[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 11:43:48 +0000
Subject: [PATCH 2/6] fix: remove unused next parameter from getFoursquare
---
controllers/api.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/controllers/api.js b/controllers/api.js
index f7f3d2559c..c0eb743d3d 100644
--- a/controllers/api.js
+++ b/controllers/api.js
@@ -30,7 +30,7 @@ exports.getApi = (req, res) => {
* GET /api/foursquare
* Foursquare API example.
*/
-exports.getFoursquare = async (req, res, next) => {
+exports.getFoursquare = async (req, res) => {
try {
const options = {
method: 'GET',
From fcac3378915bedae71b2dc0ba1f610f208998831 Mon Sep 17 00:00:00 2001
From: "gemini-code-assist-test[bot]"
<200674850+gemini-code-assist-test[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 12:20:25 +0000
Subject: [PATCH 3/6] Fix: Remove unused next parameter in getFoursquare
The 'next' parameter in the getFoursquare function was unused because the catch block handles the error response directly. This commit removes the unused parameter to improve code clarity.
From 9ca1ed26054cf510a8507af18f9032b4fd7d853e Mon Sep 17 00:00:00 2001
From: "gemini-code-assist-test[bot]"
<200674850+gemini-code-assist-test[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 12:32:04 +0000
Subject: [PATCH 4/6] Remove unused next parameter from getFoursquare in
controllers/api.js
From c873aa41c26e3fd15a9bd338757ac64deba63680 Mon Sep 17 00:00:00 2001
From: "gemini-code-assist-test[bot]"
<200674850+gemini-code-assist-test[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 12:33:10 +0000
Subject: [PATCH 5/6] Remove unused next parameter from getFoursquare in
controllers/api.js (v2)
From 548d50cde8b3caf5cd0d108236cf12c10d41f119 Mon Sep 17 00:00:00 2001
From: "gemini-code-assist-test[bot]"
<200674850+gemini-code-assist-test[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 12:38:45 +0000
Subject: [PATCH 6/6] Style: Ensure code clarity in getFoursquare