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(', ') }