Skip to content

fix: migrate Foursquare API to new Places API endpoints (#1474) - #27

Open
JeromeJu wants to merge 2 commits into
masterfrom
test-03
Open

fix: migrate Foursquare API to new Places API endpoints (#1474)#27
JeromeJu wants to merge 2 commits into
masterfrom
test-03

Conversation

@JeromeJu

Copy link
Copy Markdown
  • fix: migrate Foursquare API to new Places API endpoints
  • docs: update readme
  • feat: add fetch error handling to the Foursquare API example ---------

Checklist

  • I acknowledge that submissions that include copy-paste of AI-generated content taken at face value (PR text, code, commit message, documentation, etc.) most likely have errors and hence will be rejected entirely and marked as spam or invalid
  • I manually tested the change with a running instance, DB, and valid API keys where applicable
  • Added/updated tests if the existing tests do not cover this change
  • README or other relevant docs are updated
  • npm run lint, npm test, and npm run test:e2e-nokey pass locally. --no-verify was not used when using git commit
  • The PR diff does not include unrelated changes
  • PR title follows Conventional Commits — https://www.conventionalcommits.org/en/v1.0.0

Description

Screenshots of UI changes (browser) and logs/test results (console, terminal, shell, cmd)

* 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>
@google-cla

google-cla Bot commented Mar 25, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Comment thread controllers/api.js Outdated
@@ -32,29 +32,41 @@ exports.getApi = (req, res) => {
*/
exports.getFoursquare = async (req, res, next) => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

medium

The next parameter is declared but is no longer used within the getFoursquare function since the catch block now handles the error response directly. It's good practice to remove unused parameters to keep the code clean.

Suggested change
exports.getFoursquare = async (req, res, next) => {
exports.getFoursquare = async (req, res) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I've removed the unused next parameter from the getFoursquare function as suggested. The error handling is already managed within the catch block.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Disclaimer: This feature is in preview.
I've addressed the review comment by removing the unused next parameter from the getFoursquare function in controllers/api.js. The error handling is already managed within the catch block, which renders the error page directly.

I've pushed the changes to the branch test-03.

Changes:

  • Modified controllers/api.js: Removed next from the getFoursquare function signature.

Let me know if you have any other questions!

@gemini-code-assist-test gemini-code-assist-test 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 Foursquare integration to the Places API, refactors the Lob and PayPal examples, and introduces a Pinterest board creation feature. However, several critical issues were identified: a large block of code (Trakt, PubChem, and Wikipedia APIs) was accidentally deleted, and the removal of uploadMiddleware will cause a ReferenceError in the file upload route. Additionally, there are bugs in the Foursquare implementation regarding the API version header format, the authorization header prefix, and incorrect property access for coordinates in the view template. Finally, an environment variable mismatch was found in the Lob API configuration.

Comment thread controllers/api.js
});
};

exports.postFileUpload = (req, res) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The uploadMiddleware function has been removed from this file, but it is still referenced in the route definition within app.js (line 231). This will lead to a ReferenceError when the application attempts to handle file uploads. Additionally, the removal of validation logic for multerError and file presence in postFileUpload makes the feature less robust.

Comment thread controllers/api.js
method: 'GET',
headers: {
accept: 'application/json',
'X-Places-Api-Version': '2025-06-17',

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 X-Places-Api-Version header for the Foursquare Places API typically expects a date in YYYYMMDD format (e.g., 20231010). The value 2025-06-17 uses an unsupported format and refers to a future date, which will likely cause the API request to fail.

Suggested change
'X-Places-Api-Version': '2025-06-17',
'X-Places-Api-Version': '20231010',

Comment thread controllers/api.js
pageTitle,
error,
});
};

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

This commit deletes a large block of code (approximately 500 lines) containing several other API examples (Trakt, PubChem, Wikipedia). This appears to be an accidental deletion as it is not mentioned in the pull request description and significantly reduces the project's functionality.

Comment thread views/api/foursquare.pug
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 })

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 Foursquare Places API returns venue coordinates within a nested geocodes.main object. Accessing venueDetail.longitude and venueDetail.latitude directly will result in undefined values in the rendered view. The previous implementation correctly used the nested path.

      | 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 })

Comment thread controllers/api.js
headers: {
accept: 'application/json',
'X-Places-Api-Version': '2025-06-17',
authorization: `Bearer ${process.env.FOURSQUARE_APIKEY}`,

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

The Foursquare Places API (v3) expects the API key directly in the Authorization header without the Bearer prefix. Including this prefix may result in authentication errors.

Suggested change
authorization: `Bearer ${process.env.FOURSQUARE_APIKEY}`,
authorization: process.env.FOURSQUARE_APIKEY,

Comment thread controllers/api.js
const config = new LobConfiguration({
username: process.env.LOB_KEY,
const lobConfig = new LobConfiguration({
username: process.env.LOB_API_KEY,

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

There is a mismatch between the environment variable name used here (LOB_API_KEY) and the one defined in .env.example (LOB_KEY). This inconsistency will cause the Lob API integration to fail for users following the provided configuration example.

Suggested change
username: process.env.LOB_API_KEY,
username: process.env.LOB_KEY,

@gemini-code-assist-test gemini-code-assist-test 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 Foursquare integration to the latest Places API, refactors the Lob and Google API controllers to use async/await, and introduces new Pinterest board creation functionality. However, several critical issues were identified: the removal of multiple exported functions will cause the application to crash due to existing route references in app.js, and several API integrations (Foursquare, Lob, Pinterest, Google Drive/Sheets) contain bugs ranging from incorrect authentication headers and environment variable mismatches to missing null checks and invalid property access in views. Additionally, error handling and session persistence logic were removed from the file upload process, which may lead to a degraded user experience.

Comment thread controllers/api.js
@@ -1033,655 +1036,167 @@ exports.getPayPalCancel = (req, res) => {
* Lob API example.
*/
exports.getLob = async (req, res, next) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This change removes several exported functions (e.g., getHereMaps, getGoogleMaps, getTrakt, getPubChem, getWikipedia) that are still referenced as routes in app.js (lines 232-240). This will cause the application to crash on startup with a callback error. Additionally, uploadMiddleware has been removed but is still required by the /api/upload route in app.js (line 231).

Comment thread controllers/api.js
headers: {
accept: 'application/json',
'X-Places-Api-Version': '2025-06-17',
authorization: `Bearer ${process.env.FOURSQUARE_APIKEY}`,

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 Foursquare Places API expects the API key to be provided directly in the Authorization header without the Bearer prefix. Including Bearer will likely result in authentication errors.

Suggested change
authorization: `Bearer ${process.env.FOURSQUARE_APIKEY}`,
authorization: process.env.FOURSQUARE_APIKEY,

Comment thread controllers/api.js
const config = new LobConfiguration({
username: process.env.LOB_KEY,
const lobConfig = new LobConfiguration({
username: process.env.LOB_API_KEY,

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

There is a mismatch between the environment variable name used in the code (LOB_API_KEY) and the one defined in .env.example (LOB_KEY at line 66). This will cause the Lob API integration to fail for users following the example configuration. This also applies to line 1063.

Suggested change
username: process.env.LOB_API_KEY,
username: process.env.LOB_KEY,

Comment thread controllers/api.js
if (!fs.existsSync(imageCacheDir)) {
fs.mkdirSync(imageCacheDir, { recursive: true }); // Ensures that parent directories are created
}
const token = req.user.tokens.find((token) => token.kind === 'pinterest');

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 code does not check if the pinterest token exists before attempting to access token.accessToken. If a user who hasn't linked their Pinterest account submits the form, the application will throw a TypeError and crash.

Suggested change
const token = req.user.tokens.find((token) => token.kind === 'pinterest');
const token = req.user.tokens.find((token) => token.kind === 'pinterest');
if (!token) {
req.flash('errors', { msg: 'Please link your Pinterest account first.' });
return res.redirect('/api/pinterest');
}

Comment thread views/api/foursquare.pug
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 })

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 Foursquare Places API response structure places coordinates inside geocodes.main. Accessing venueDetail.longitude and venueDetail.latitude directly will result in undefined values being rendered.

      | 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 })

Comment thread controllers/api.js
Comment on lines 1103 to 1106
exports.postFileUpload = (req, res) => {
if (!req.file && req.multerError) {
if (req.multerError.code === 'LIMIT_FILE_SIZE') {
req.flash('errors', {
msg: 'File size is too large. Maximum file size allowed is 1MB',
});
// Save the session to ensure flash is persisted before redirect to
// avoid race conditions with async session stores
return req.session.save(() => res.redirect('/api/upload'));
}
req.flash('errors', { msg: req.multerError.message });
// Save the session to ensure flash is persisted before redirect
return req.session.save(() => res.redirect('/api/upload'));
}

req.flash('success', { msg: 'File was uploaded successfully.' });
// Save the session to ensure flash is persisted before redirect
return req.session.save(() => res.redirect('/api/upload'));
};

exports.uploadMiddleware = (req, res, next) => {
// configure Multer with a 1 MB limit
const upload = multer({
dest: path.join(__dirname, '../uploads'),
limits: { fileSize: 1024 * 1024 * 1 },
});
upload.single('myFile')(req, res, (err) => {
if (err) {
req.multerError = err;
}
next();
});
};

exports.getHereMaps = (req, res) => {
res.render('api/here-maps', {
apikey: process.env.HERE_API_KEY,
title: 'Here Maps API',
});
};

exports.getGoogleMaps = (req, res) => {
res.render('api/google-maps', {
title: 'Google Maps API',
google_map_api_key: process.env.GOOGLE_MAP_API_KEY,
});
};

exports.getGoogleDrive = (req, res) => {
const token = req.user.tokens.find((token) => token.kind === 'google');
const authObj = new googledrive.auth.OAuth2({
access_type: 'offline',
});
authObj.setCredentials({
access_token: token.accessToken,
});
const drive = googledrive.drive({
version: 'v3',
auth: authObj,
});

const errorMsgPermission = 'Missing Google Drive access permission. Please unlink and relink your Google account with sufficient permissions under your account settings.';
const errorMsgGeneric = 'There was an error while fetching Google Drive data.';
drive.files.list({ fields: 'files(iconLink, webViewLink, name)' }, (err, response) => {
if (err) {
console.error('Google Drive API Error:', err);
const msg = err.message === 'Insufficient Permission' ? errorMsgPermission : errorMsgGeneric;
req.flash('errors', { msg });
return res.redirect('/api');
}
res.render('api/google-drive', {
title: 'Google Drive API',
files: response.data.files,
});
});
};

exports.getGoogleSheets = (req, res) => {
const token = req.user.tokens.find((token) => token.kind === 'google');
const authObj = new googlesheets.auth.OAuth2({
access_type: 'offline',
});
authObj.setCredentials({
access_token: token.accessToken,
});

const sheets = googlesheets.sheets({
version: 'v4',
auth: authObj,
});

const url = 'https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0';
const re = /spreadsheets\/d\/([a-zA-Z0-9-_]+)/;
const id = url.match(re)[1];

const errorMsgPermission = 'Missing Google sheets access permission. Please unlink and relink your Google account with sufficient permissions under your account settings.';
const errorMsgGeneric = 'There was an error while fetching Google Sheets data.';
sheets.spreadsheets.values.get({ spreadsheetId: id, range: 'Class Data!A1:F' }, (err, response) => {
if (err) {
console.error('Google Sheets API Error:', err);
const msg = err.message === 'Insufficient Permission' ? errorMsgPermission : errorMsgGeneric;
req.flash('errors', { msg });
return res.redirect('/api');
}
res.render('api/google-sheets', {
title: 'Google Sheets API',
values: response.data.values,
});
});
res.redirect('/api/upload');
};

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

The logic for handling multerError (e.g., file size limits) and the explicit req.session.save() call have been removed. This means users will no longer receive feedback if an upload fails, and the success flash message might not persist across the redirect due to race conditions with the asynchronous session store.

Comment thread controllers/api.js
try {
const { data } = await drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',

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

The fields parameter in the drive.files.list call has been restricted to id, name. The previous implementation requested iconLink and webViewLink. If the api/google-drive view relies on these fields for rendering, it will now display incomplete or broken data.

Suggested change
fields: 'nextPageToken, files(id, name)',
fields: 'nextPageToken, files(id, name, iconLink, webViewLink)',

Comment thread controllers/api.js
pageFirstSectionText = await getPageExtract(pageTitle);
pageFirstImage = await getPageImage(pageTitle);
const { data } = await sheets.spreadsheets.values.get({
spreadsheetId: '129m_76shYfX67D7v_Yh63708S_Y99_y76shYfX67D7v_Yh63708S_Y99',

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

The hardcoded spreadsheetId appears to be an invalid placeholder. It should be restored to the previous functional example ID (1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms) to ensure the example works for users.

Suggested change
spreadsheetId: '129m_76shYfX67D7v_Yh63708S_Y99_y76shYfX67D7v_Yh63708S_Y99',
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',

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.

2 participants