-1

I am hitting the google calendar api, and I have a lambda setup in a async try catch. I have tried adding await to every function, tried moving the return to after the if(err) but that gives me a 500. What I need to do is pass the array data from the google calendar api function to the message so I can get it in my front end. Here is the lambda so far. Any help would be greatly appreciated. Thanks


const { google } = require("googleapis")
const { OAuth2 } = google.auth
const faunadb = require("faunadb") /* Import faunaDB sdk */

// Docs on event and context https://www.netlify.com/docs/functions/#the-handler-method
exports.handler = async (event, context) => {
  try {
    const OAuth2Client = new OAuth2(
      "FDSAF",
      "FDSAF"
    )

    // Connect to the database
    const q = faunadb.query
    const client = new faunadb.Client({
      secret: "FDSAFA",
    })

    let refreshToken

    await client
      .query(q.Get(q.Ref(q.Collection("AuthUrl"), "fdsa")))
      .then(ret => (refreshToken = ret.data.title.refresh_token))

    console.log(refreshToken)

    OAuth2Client.setCredentials({
      refresh_token: refreshToken,
    })

    // Create a new calender instance.
    const calendar = google.calendar({ version: "v3", auth: OAuth2Client })



    let ok
    function listEvents(callback) {
      let array = []

      calendar.events.list(
        {
          calendarId: "primary",
          // timeMin: new Date().toISOString(),
          maxResults: 100000,
          singleEvents: true,
          orderBy: "startTime",
        },
        (err, res) => {
          if (err) return console.log("The API returned an error: " + err)

          var date = new Date()
          var firstDay = new Date(date.getFullYear(), date.getMonth(), 1)
          var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0)

          //console.log(res.data.items)
          const events = res.data.items
          if (events.length) {
            //   console.log("Upcoming 10 events:")
            events.map((event, i) => {
              const start = new Date(event.start.dateTime || event.start.date)
              const end = new Date(event.end.dateTime || event.end.date)

              if (start >= firstDay && end <= lastDay) {
                console.log(start, end, event.summary)
                //ok = "test"
                array.push(start)
                callback(array)
              }

              //     const start = event.start.dateTime || event.start.date
              //     console.log(`${start} - ${event.summary}`)
            })
          } else {
            console.log("No upcoming events found.")
          }
        }
      )
    }

    let array

    listEvents(function (eventList) {
      array = eventList
    })



    return {
      statusCode: 200,
      body: JSON.stringify({ message: array }),
      // // more keys you can return:
      // headers: { "headerName": "headerValue", ... },
      // isBase64Encoded: true,
    }
  } catch (err) {
    return { statusCode: 500, body: err.toString() }
  }
}

This is the fetch I am doing for it on the front end and it returns an empty object

const IndexPage = () => {
  fetch("/functions/list-calendar")
    .then(response => response.json())
    .then(response => {
      console.log(response)
    })
Ersoy
  • 8,816
  • 6
  • 34
  • 48
Anders Kitson
  • 1,413
  • 6
  • 38
  • 98

1 Answers1

1

It looks to me that you're returning your response before the callback has been executed. If you're not familiar with async and await, you should read this documentation. Basically you need to wait for the callback to happen before you return, and you can either do this using callback functions, .then chains or async functions.

I also noticed in your code that you're calling the callback everytime you're doing a push into array. I think that it's simpler to push all your items into array and then callback the array.

Rather than do a push in the map (which is confusing), this returns a new array of event.start.dateTime and because if (start >= firstDay && end <= lastDay) is false, it adds a null to the array, so .filter(x => Boolean(x)); filters them out so you just get an array of datetimes.

let array = events.map((event, i) => {
    const start = new Date(event.start.dateTime || event.start.date)
    const end = new Date(event.end.dateTime || event.end.date)

    if (start >= firstDay && end <= lastDay) {
      return start;
    else
      return null;

      //     const start = event.start.dateTime || event.start.date
      //     console.log(`${start} - ${event.summary}`)
    })
    .filter(x => Boolean(x));

Simple Version

You could move your return into the callback.

listEvents(function (eventList) {
  array = eventList;

  return {
    statusCode: 200,
    body: JSON.stringify({ message: array }),
    // // more keys you can return:
    // headers: { "headerName": "headerValue", ... },
    // isBase64Encoded: true,
  }
});

Promise Version

My node is a bit rusty but you could change your method to return a promise.

function listEvents(callback) {
  return new Promise((resolve, reject) => {


    calendar.events.list(
    {
      calendarId: "primary",
      // timeMin: new Date().toISOString(),
      maxResults: 100000,
      singleEvents: true,
      orderBy: "startTime",
    },
    (err, res) => {
      if (err) reject("The API returned an error: " + err));

      var date = new Date()
      var firstDay = new Date(date.getFullYear(), date.getMonth(), 1)
      var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0)

      //console.log(res.data.items)
      const events = res.data.items
      if (events.length) {
        //   console.log("Upcoming 10 events:")
        let array = events.map((event, i) => {
          const start = new Date(event.start.dateTime || event.start.date)
          const end = new Date(event.end.dateTime || event.end.date)

          if (start >= firstDay && end <= lastDay) {
            return start;
          else
            return null;

          //     const start = event.start.dateTime || event.start.date
          //     console.log(`${start} - ${event.summary}`)
        }).filter(x => Boolean(x));

        resolve(array);
      } else {
        resolve([]);
      }
    })
  });
}

and then, since you're already using async,

let array = await listEvents();

Updated Version

If you want to return array data in a different format.

  let array = events.filter(event => {
      const start = new Date(event.start.dateTime || event.start.date)
      const end = new Date(event.end.dateTime || event.end.date)

      if (start >= firstDay && end <= lastDay) {
        return true;
      else
        return false;
    })
    .map(event => {
        startDate: new Date(event.start.dateTime || event.start.date),
        endDate: new Date(event.end.dateTime || event.end.date),
        summary: event.summary
    });

If you want to group by a property, you can search StackOverflow for answers, like this one: Most efficient method to groupby on an array of objects, the below groupByArray function was taken from the comments on the accepted answer.

function groupByArray(xs, key) { return xs.reduce(function (rv, x) { let v = key instanceof Function ? key(x) : x[key]; let el = rv.find((r) => r && r.key === v); if (el) { el.values.push(x); } else { rv.push({ key: v, values: [x] }); } return rv; }, []); }

let groupedAndFilteredEvents = groupByArray(events.filter(event => {
  const start = new Date(event.start.dateTime || event.start.date)
  const end = new Date(event.end.dateTime || event.end.date)

  if (start >= firstDay && end <= lastDay) {
    return true;
  else
    return false;
})
.map(event => {
    event.groupByKey = event.start.dateTime;
    return event;
}), "groupByKey");
Shoejep
  • 4,414
  • 4
  • 22
  • 26
  • ok I will give both a try and come back and select this answer thanks – Anders Kitson May 23 '20 at 13:39
  • You should also look out for that you only call your callback once to make it simpler, i.e. push all your items into `array` and then callback outside the loop. – Shoejep May 23 '20 at 13:41
  • Glad it worked. I'll add more of an explanation to my answer :) – Shoejep May 23 '20 at 13:44
  • yeah I was curious what was happening at the Boolean(x) part – Anders Kitson May 23 '20 at 13:45
  • It just filters the `null`s that may have been added during the map. – Shoejep May 23 '20 at 13:56
  • dumb question how would I add the start, end and event.summary to the return so the first (start,end, event.summary) were in and array and then the second ones were in a separate array so on. Or how do I add this data I am grabbing into this single array. Because I am just returning the start, but I can't return multiple variables, and If I push them into an array they repeat the same thing however many times the map loops – Anders Kitson May 23 '20 at 14:00
  • got it this worked how I wanted it to return { start, end, eventz } – Anders Kitson May 23 '20 at 14:04
  • 1
    Not sure I understand what you're after, but I've added the Updated Version to the answer which returns the summary as well. Did you want to group events on the same day into one object? – Shoejep May 23 '20 at 14:10
  • yeah that's what I was looking for – Anders Kitson May 23 '20 at 14:10
  • 1
    Updated my answer with a `groupByArray` function that should help you achieve what you want. – Shoejep May 23 '20 at 14:30