Wanderlust Photos

Hy. I work on the Wanderlust project at step 41: For a real challenge, try fetching venue photos! This will require an additional request for venue details for each venue, as the photo information is not returned in the initial request.

This is my code:
https://www.codecademy.com/paths/web-development/tracks/webdev-intermediate-javascript/modules/intermediate-javascript-requests/projects/wanderlust

// Foursquare API Info
const clientId = '5HKXAQMDQPKMQV2LI150HUFLL4CY1JKZIX0SUOYKQ5CZP2EZ';
const clientSecret = 'AJ10MUNT0F1NRFLNLAH1OIMGVBGTLCLWLII1EYAPOW3W44CC';
const url = 'https://api.foursquare.com/v2/venues/explore?near=';
const urlPhoto = 'https://api.foursquare.com/v2/venues/VENUE_ID';

// OpenWeather Info
const openWeatherKey = '759d8604da41b217757e15f0dc068ad3';
const weatherUrl = 'https://api.openweathermap.org/data/2.5/weather';

// Page Elements
const $input = $('#city');
const $submit = $('#button');
const $destination = $('#destination');
const $container = $('.container');
const $venueDivs = [$("#venue1"), $("#venue2"), $("#venue3"), $("#venue4"), $("#venue5")];
const $weatherDiv = $("#weather1");
const weekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

// Add AJAX functions here:
const getVenues = async () => {
  const city = $input.val();
  const urlToFetch = url + city + '&limit=10&client_id=' + clientId + '&client_secret=' + clientSecret + '&v=20200710';
  try{
    const response = await fetch(urlToFetch);
    if(response.ok){
      const jsonResponse = await response.json();
      const venues = jsonResponse.response.groups[0].items.map(item => item.venue);
      console.log(venues);
      return venues;
    }
  }catch(error){
    console.log(error);
  }
}

const getVenuePhoto = async () => {
  const urlToFetch = urlPhoto + '&client_id=' + clientId + '&client_secret=' + clientSecret + '&v=20200710';
 try {
    const response = await fetch(urlToFetch);
    console.log(response);
    if(response.ok) {
      const jsonResponse = await response.json();
      return jsonResponse;
    }
  } catch(error) {
    console.log(error);
  }
}


const getForecast = async () => {
  const urlToFetch = weatherUrl + '?&q=' + $input.val() + '&APPID=' + openWeatherKey;
  try {
    const response = await fetch(urlToFetch);
    if(response.ok) {
      const jsonResponse = await response.json();
      return jsonResponse;
    }
  } catch(error) {
    console.log(error);
  }
}


// Render functions
const renderVenues = (venues) => {
  $venueDivs.forEach(($venue, index) => {
    // Add your code here:
    //Update for p.39 of Challenges.
    let randomIndex = Math.floor(Math.random() * 10);
    const venue = venues[randomIndex];
    const venueIcon = venue.categories[0].icon;
    const venueIconSrc = venueIcon.prefix + 'bg_64' + venueIcon.suffix;
    const venueImg = venue.photos;
    const venueImgSrc = venueImg.prefix + '300x500' + venueImg.suffix;
    let venueContent = createVenueHTML(venue.name, venue.location, venueIconSrc, venueImgSrc);
    $venue.append(venueContent);
  });
  $destination.append(`<h2>${venues[0].location.city}</h2>`);
}


const renderForecast = (day) => {
  // Add your code here:
  let weatherContent = createWeatherHTML(day);
  $weatherDiv.append(weatherContent);
}

const executeSearch = () => {
  $venueDivs.forEach(venue => venue.empty());
  $weatherDiv.empty();
  $destination.empty();
  $container.css("visibility", "visible");
  getVenues().then(venues => renderVenues(venues));
  getForecast().then(forecast => renderForecast(forecast));
  return false;
}

$submit.click(executeSearch)

The problem is that I’m getting this error in the console: Failed to load resource: the server responded with a status of 404 ()

On the web page where it should be the photo, it appears only an icon representing the image.

Am I getting this error because in the Venue details description on https://developer.foursquare.com/docs/api/venues/details
it says: " This is a Premium endpoint. Restrictions apply based on your account type. Please see our rate limits page."?
Or I’ve made other mistakes on the code?

HTTP 404 is Not Found, suggesting that the URI you are attempting to fetch is incorrect.

If you look at the documentation for the Foursquare API, you’ll notice that the VENUE_ID part of the URI is a parameter value:

You have, however, hard-coded the literal text VENUE_ID into the URI you’re attempting to fetch:

https://api.foursquare.com/v2/venues/VENUE_ID&client_id=val&client_secret=val&v=20200710

You should be passing in the venue ID that you received back from the original call to the venues API endpoint. :slight_smile:

1 Like

A post was split to a new topic: Help with wanderlust

I understand. But I don’t know where to take from the VENUE ID.
The response from API endpoint is this:

The venue id shouldn’t be in the response? like in the Foursquare API documentation example?

Here I made this adjustment:

// Foursquare API Info
const clientId = '5HKXAQMDQPKMQV2LI150HUFLL4CY1JKZIX0SUOYKQ5CZP2EZ';
const clientSecret = 'AJ10MUNT0F1NRFLNLAH1OIMGVBGTLCLWLII1EYAPOW3W44CC';
const url = 'https://api.foursquare.com/v2/venues/explore?near=';
const urlPhoto = 'https://api.foursquare.com/v2/venues/';

and I understand that here:

const getVenuePhoto = async () => {
  const urlToFetch = urlPhoto + '&client_id=' + clientId + '&client_secret=' + clientSecret + '&v=20200710';

it has to be something like this, after a take the venue id:

const getVenuePhoto = async () => {
  const urlToFetch = urlPhoto + '&venue_id=' + venueId + '&client_id=' + clientId + '&client_secret=' + clientSecret + '&v=20200710';

Reading the documentation is often a good start.

When you’re making the call to the Venue Recommendation endpoint (https://api.foursquare.com/v2/venues/explore as defined in your variable url) the response you get back from Foursquare includes an id value for each venue it returns. This is the venue ID that you need to pass to the API when retrieving photos.

Your request link is incorrect…

If you look at the Foursquare link:

https://api.foursquare.com/v2/venues/VENUE_ID/photos

  1. You have to replace VENUE_ID with venue’s id number **XXXXXXXX** followed by /photos. You can access venue id in your venue JSON.

  2. To signal the beginning of your query search section you have to use ? in front of your first parameter.

const urlPhoto = 'https://api.foursquare.com/v2/venues/**XXXXXXXX**/photos?client_id=.......';

Took me ages to get this working. Unfortunately the API documentation can be quite vague and doesn’t offer very clear examples.

Could you elaborate a little please? I understand where the ID value comes from and that we need it to construct the URL to fetch the venue’s photos, but I don’t understand how to access that ID from within the new request to get the photos and incorporate it in the URL.

At the moment I have made a new request to get the photos and made an attempt to build the URL to fetch, and I think most of it is correct based on several responses I’ve read on the forum, but that venue ID is a puzzle. I was thinking of calling getVenues() from inside getPhotos() in order to access the ID value of each venue, but it feels somewhat convoluted (and I can’t figure out how to store the ID value for each location so that I could use venueID as a place holder in the URL). I feel like I’m missing something really obvious here :frowning:

My full code looks like this for now:

// Foursquare API Info
const clientId = 'N5KDGDGACPESEDBSDIUP2B5Y3RB5MSLB4VADJTQ3051FK2NP';
const clientSecret = 'T2I44UXUE2XBLYH5T4CPLPM5SPHZA0Y5CUXASSALXJMN5DVB';
const url = 'https://api.foursquare.com/v2/venues/explore?near=';

// OpenWeather Info
const openWeatherKey = 'a0f5649664de61ad7c59a6ecccd822cb';
const weatherUrl = 'https://api.openweathermap.org/data/2.5/weather';

// Page Elements
const $input = $('#city');
const $submit = $('#button');
const $destination = $('#destination');
const $container = $('.container');
const $venueDivs = [$("#venue1"), $("#venue2"), $("#venue3"), $("#venue4")];
const $weatherDiv = $("#weather1");
const weekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

// Add AJAX functions here:
const getVenues = async () => {
  const city = $input.val();
  let limit = 20;
  const urlToFetch = `${url}${city}&limit=${limit}&client_id=${clientId}&client_secret=${clientSecret}&v=20200901`;
  try {
    const response = await fetch(urlToFetch);
    if (response.ok) {
      //console.log(response);
      const jsonResponse = await response.json();
      console.log('This is the jsonResponse for getVenues:');
      console.log(jsonResponse);
      const venues = jsonResponse.response.groups[0].items.map(parameter => parameter.venue); // accesses an array with number of obj selected in the limit param. .map() is used to return only the venue property inside these obj.
      console.log('Venues property from the jsonResponse:');
      console.log(venues);
      // Challenge: Show 4 unique venues at random
      let randomVenues = [];
      while (randomVenues.length < 4) {
        let randNum = Math.floor(Math.random() * venues.length)
        if (!randomVenues.includes(venues[randNum])) {
            randomVenues.push(venues[randNum]);
        }
      }
      console.log('These are the 4 venues randomly chosen:');
      console.log(randomVenues);
      return randomVenues;
    }
  } catch (error) {
    console.log(error);
  }
}

const getPhotos = async() => {
  let venueId = getVenues().forEach() // something something?...
  const urlToFetch = `https://api.foursquare.com/v2/venues/${venueId}/photos?client_id=${clientId}&client_secret=${clientSecret}&v=20200901`;
  try {
    const response = await fetch(urlToFetch);
    console.log('This is the response for getPhotos:');
    console.log(response); // this is not logging anything to the console
    if (response.ok) {
      const jsonResponse = await jsonResponse.json();
      console.log('This is the jsonResponse from getPhotos:'); 
      console.log(jsonResponse); // this is not logging anything to the console
    }
  } catch (error) {
    console.log(error)
  }
}

const getForecast = async() => {
  const urlToFetch = `${weatherUrl}?&q=${$input.val()}&APPID=${openWeatherKey}`
  try {
    const response = await fetch(urlToFetch);
    if (response.ok) {
      const jsonResponse = await response.json()
      //jsonResponse is logged to the console via the helper function 
      return jsonResponse;
    }
  } catch(error) {
    console.log(error);
  }
}


// Render functions
const renderVenues = (venues) => {
  $venueDivs.forEach(($venue, index) => {
    const venue = venues[index];
    const venueIcon = venue.categories[0].icon;
    const venueImgSrc = `${venueIcon.prefix}bg_64${venueIcon.suffix}`;

    let venueContent = createVenueHTML(venue.name, venue.location, venueImgSrc);
    /*Without the helper function:
    let venueContent = `<h2>${venue.name}</h2>
<img class="venueimage" src="${venueImgSrc}"/>
<h3>Address:</h3>
<p>${venue.location.address}</p>
<p>${venue.location.city}</p>
<p>${venue.location.country}</p>`;*/

    $venue.append(venueContent);
  });
  $destination.append(`<h2>${venues[0].location.city}</h2>`);
}

const renderForecast = (day) => {
	let weatherContent = createWeatherHTML(day);
  $weatherDiv.append(weatherContent);
}

const executeSearch = () => {
  $venueDivs.forEach(venue => venue.empty());
  $weatherDiv.empty();
  $destination.empty();
  $container.css("visibility", "visible");
  getVenues().then(venues => renderVenues(venues));
  //getPhotos();
  getForecast().then(forecast => renderForecast(forecast));
  return false;
}

$submit.click(executeSearch)

Thanks for any input - I’m so anxious to make it work!

1 Like

Sure.

Your original request for venues returns a response that you then turn into a json object.

const jsonResponse = await response.json();

then you access ‘items’ array inside and using map() method you save all the array elements (in this case objects) to a variable. Those elements are your individual venues. To understand where this comes from you really have to log your ‘jsonResponse’.

const venues = jsonResponse.response.groups[0].items.map(parameter => parameter.venue);  // to understand where this comes from you really have to log your 'jsonResponse' 

You can do the following steps in a different way, but here’s what I did. By ‘tapping’ into the forEach() method later in the code I was able to isolate individual array members (objects) and access their properties. ID is one of the first properties within each venue object. This is what I did.

const renderVenues = (venues) => {
  $venueDivs.forEach(async ($venue, index) => {
    // Add your code here:
    const venue = venues[index];       //this is your individual venue object, if you log 'venue' you will be able to see all properties, including ID
    const photoData = await getPhoto(venue.id);      // venue.id is your venue's id; function getPhoto() sends photo request and returns an array with photo's prefix and suffix
    const venueIcon = venue.categories[0].icon; 
    const venueImgSrc = `${venueIcon.prefix}bg_64${venueIcon.suffix}`;
    let venueContent = `<h2>${venue.name}</h2>
                        <img class="venueimage" src="${venueImgSrc}"/>
                        <h3>Address:</h3>
                        <p>${venue.location.address}</p>
                        <p>${venue.location.city}</p>
                        <p>${venue.location.country}</p>
                        <img class="venueimage" src="${photoData[0]}cap140${photoData[1]}"/>`;  
     $venue.append(venueContent);
  });
  $destination.append(`<h2>${venues[0].location.city}</h2>`);
}

I created a separate function ‘getPhoto()’ (basically a copy of your original getVenues()), which deals with sending photo request. 'venue.id is passed to the function as the argument. Function getPhoto() extract required prefix and suffix from the returned response, and returns an array with with two elements (prefix and suffix) saved with a variable ‘photoData’.

const getPhoto = async (venueId) => {
  const photosUrl = venueByIdUrl + venueId + '/photos?client_id=' + clientId + '&client_secret=' + clientSecret + '&v=20200909';
  try {
    let response = await fetch(photosUrl);
    if(response.ok) {
      let jsonResponse = await response.json();
      const prefix = jsonResponse.response.photos.items[0].prefix;
      const suffix = jsonResponse.response.photos.items[0].suffix;
      return [prefix, suffix];  
    }
    throw new Error('Request failed!');
    } catch(error) {
        console.log(error);
    }
};

Then, back in my renderVenues() function I just concatenate and insert those elements as new HTML line:

 <img class="venueimage" src="${photoData[0]}cap140${photoData[1]}"/>`;  // cap140 caps the size - refer to API documentation

I also had to add async to my forEach() function and then await for the results of calling getPhoto(). This is because my getPhoto() returns a promise.

3 Likes

I didn’t have a chance yet to look back at my code taking into consideration your post, but I just wanted to thank you for the detailed walkthrough! This weekend I will get back to it with a clear head again :slight_smile:

2 Likes

All right, so here I am after having carefully reviewed your explanation and it’s definitely starting to fall into place. I have implemented your code in mine. I am running into a few other errors now, and I hit the limit rate of requests for today so I can’t keep on working on it yet, but I made a screenshot of the errors I was getting:

  • The URL in the response: if I paste it in the browser, I get this
{
* meta: {
  * code: 400,
  * errorType: "invalid_auth",
  * errorDetail: "Missing access credentials. See https://developer.foursquare.com/docs/api/configuration/authentication for details.",
  * requestId: "5f5cc8abce204e43767c62d4"},
* response: { }
}

That being said, since the status is 200 and it’s the ‘raw’ response (not yet the json object), I’m not sure it’s actually (going to be) a problem. The rest of the code is ran regardless.

  • The ReferenceError: now that seems to be the main issue. I understand what the error means, but I don’t see how and where I would have tried to access the jsonResponse variable for getPhotos() before initialising it. I’ve tried removing all the console.log(), tracking back in which order all the functions are getting executed, checked the scope, everything seems to be where it should be.

  • The TypeError (concerning photoData[0] is the added HTML line): my guess is this one stems from the ReferenceError, so I need to fix that one first.

My getPhotos() function now looks like this:

const venueByIdUrl = 'https://api.foursquare.com/v2/venues/';

const getPhotos = async(venueId) => {
  const urlToFetch = `${venueByIdUrl}${venueId}/photos?client_id=${clientId}&client_secret=${clientSecret}&v=20200901`;
  try {
    const response = await fetch(urlToFetch);
    console.log('This is the response from getPhotos:');
    console.log(response);
    if (response.ok) {
      const jsonResponse = await jsonResponse.json();
      const prefix = jsonResponse.response.photos.items[0].prefix;
      const suffix = jsonResponse.response.photos.items[0].suffix;
      console.log('This is the jsonResponse from getPhotos:');
      console.log(jsonResponse);
      return [prefix, suffix];
    }
  } catch (error) {
    console.log(error);
  }
}

And my renderVenues() function looks like this:

const renderVenues = (venues) => {
  $venueDivs.forEach(async ($venue, index) => {
    const venue = venues[index];
    const photoData = await getPhotos(venue.id);
    const venueIcon = venue.categories[0].icon;
    const venueImgSrc = `${venueIcon.prefix}bg_64${venueIcon.suffix}`;

    let venueContent = `<h2>${venue.name}</h2>
<img class="venueimage" src="${venueImgSrc}"/>
<h3>Address:</h3>
<p>${venue.location.address}</p>
<p>${venue.location.city}</p>
<p>${venue.location.country}</p>
<img class="venueimage" src="${photoData[0]}cap140${photoData[1]}"/>` 

    $venue.append(venueContent);
  });
  $destination.append(`<h2>${venues[0].location.city}</h2>`);
}

The rest of my code is unchanged.
I’ll keep investigating…

2 Likes

Hi Amelie,

Regarding JsonResponse ReferenceError I spotted one mistake in your code:

I think it’s a typo? The right hand side of the code will not resolve as you are trying to perform a specific operation on JsonResponse’s value, which is undefined, hence the ReferenceError.
Did you mean:

Try changing this and see if you are still getting the TypeError thrown at you.

Regarding access authorisation issues, I’m not sure what to advise. I’ve hit the same problem several times at the very beginning (with getVenues() function). Initially I assumed that this was caused by the API credentials not working yet (apparently with some API vendors the api keys/client credentials take an hour or so to get validated). Then I moved my credential details to the beginning of my search query, which at the time seemed to have fixed the issue. I didn’t come across this problem with the photo fetch query.
Maybe re-check your getPhotos() fetch url. Compare how it logs to the console and how it looks in your response. Perhaps you’ll spot a typo or some other gremlin.

Good luck!

2 Likes

Hi Michal!
I’m a bit embarrassed a typo (almost) got the better of me again on this project! lol
I made the corrections and now I get zero errors in the console and all photos display successfully :ok_hand:t2:
I’m going to review everything once more so that it all hopefully sinks in.

Many thanks again for the time you took to help me out!

2 Likes