ūüéČ Pipedream raises $20M Series A ūüéČ
Read the blog post and TC Techcrunch article.
STREET CLEANING #2 - Create street cleaning calendar reminder
‚ÄĘ@dylan‚ÄĘ
‚ÄĘcode:
‚ÄĘdata:private‚ÄĘlast updated:1 year ago
today
Build integrations remarkably fast!
You're viewing a public workflow template.
Sign up to customize, add steps, modify code and more.
Join 250,000+ developers using the Pipedream platform
steps.
trigger
HTTP API
Deploy to generate unique URL
This workflow runs on Pipedream's servers and is triggered by HTTP / Webhook requests.
steps.
filter_test_requests
auth
to use OAuth tokens and API keys in code via theauths object
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across invocations with$checkpoint.
async (event, steps) => {
1
2
3
4
}
5
if (!('instances' in event.body)) {
  $end("Event doesn't contain instances")
}
if (!('instances' in event.body)) {
  $end("Event doesn't contain instances")
}
steps.
parse_time_from_text
auth
to use OAuth tokens and API keys in code via theauths object
params
Tz
string ·params.tz
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across invocations with$checkpoint.
async (event, steps, params) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
}
54
const chrono = require('chrono-node');
const { convertToLocalTime } = require('date-fns-timezone')

// iOS Shortcuts sends arrays of JSON object in JSONLines format, 
// so we convert to an array
const instances = event.body.instances.split('\n').map(JSON.parse)

// Find the closest instance to today, and put that on the calendar
this.closestCleaningStartTime = Infinity
for (const instance of instances) {
  const { fromhour, tohour, weekday } = instance
  const fromStr = convert24HourTimeTo12HourTime(fromhour)
  const toStr = convert24HourTimeTo12HourTime(tohour)

  console.log(`${fromStr} ${weekday}`)
  const cleaningTime = chrono.parseDate(`${fromStr} ${weekday}`, new Date(), { forwardDate: true })
  console.log(`Cleaning time: ${cleaningTime}`)
  if (+cleaningTime < this.closestCleaningStartTime) {
    console.log(`Closest cleaning time so far: ${cleaningTime}`)
    this.closestCleaningStartTime = cleaningTime
    console.log(`End time: ${toStr} ${weekday}`)
    this.closestCleaningEndTime = chrono.parseDate(`${toStr} ${weekday} `, new Date(), { forwardDate: true })
  }
}

// Get the start time and end time for the closest instance in local time
this.cleaningStartTimeLocal = convertToLocalTime(this.closestCleaningStartTime, { timeZone: params.tz })
this.cleaningEndTimeLocal = convertToLocalTime(this.closestCleaningEndTime, { timeZone: params.tz })

// We want to get notifications 24 and 12 hours 
// prior to the start time of the event
this.reminders = [
  {
    method: "email",
    minutes:  60 * 12,
  },
  {
    method: "email",
    minutes:  60 * 24,
  },
]

function convert24HourTimeTo12HourTime(stringHour) {
  const hour = parseInt(stringHour)
  if (hour >= 0 && hour <= 11) {
    return `${hour}AM`
  }
  if (hour === 12) {
    return `${hour}PM`
  }
  return `${hour - 12}PM`
}
const chrono = require('chrono-node');
const { convertToLocalTime } = require('date-fns-timezone')

// iOS Shortcuts sends arrays of JSON object in JSONLines format, 
// so we convert to an array
const instances = event.body.instances.split('\n').map(JSON.parse)

// Find the closest instance to today, and put that on the calendar
this.closestCleaningStartTime = Infinity
for (const instance of instances) {
  const { fromhour, tohour, weekday } = instance
  const fromStr = convert24HourTimeTo12HourTime(fromhour)
  const toStr = convert24HourTimeTo12HourTime(tohour)

  console.log(`${fromStr} ${weekday}`)
  const cleaningTime = chrono.parseDate(`${fromStr} ${weekday}`, new Date(), { forwardDate: true })
  console.log(`Cleaning time: ${cleaningTime}`)
  if (+cleaningTime < this.closestCleaningStartTime) {
    console.log(`Closest cleaning time so far: ${cleaningTime}`)
    this.closestCleaningStartTime = cleaningTime
    console.log(`End time: ${toStr} ${weekday}`)
    this.closestCleaningEndTime = chrono.parseDate(`${toStr} ${weekday} `, new Date(), { forwardDate: true })
  }
}

// Get the start time and end time for the closest instance in local time
this.cleaningStartTimeLocal = convertToLocalTime(this.closestCleaningStartTime, { timeZone: params.tz })
this.cleaningEndTimeLocal = convertToLocalTime(this.closestCleaningEndTime, { timeZone: params.tz })

// We want to get notifications 24 and 12 hours 
// prior to the start time of the event
this.reminders = [
  {
    method: "email",
    minutes:  60 * 12,
  },
  {
    method: "email",
    minutes:  60 * 24,
  },
]

function convert24HourTimeTo12HourTime(stringHour) {
  const hour = parseInt(stringHour)
  if (hour >= 0 && hour <= 11) {
    return `${hour}AM`
  }
  if (hour === 12) {
    return `${hour}PM`
  }
  return `${hour - 12}PM`
}
steps.
delete_existing_street_cleaning_reminders
auth
to use OAuth tokens and API keys in code via theauths object
(auths.google_calendar)
params
Optional
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across invocations with$checkpoint.
async (event, steps, params, auths) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
}
35
const axios = require('axios')

const calendarId = params.calendarId || "primary"

// If we move our car, old street cleaning reminders are irrelevant,
// so we find and delete all old reminders
const { items } = await require("@pipedreamhq/platform").axios(this, {
  method: 'GET',
  url: `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`,
  headers: {
    Authorization: `Bearer ${auths.google_calendar.oauth_access_token}`,
  },
  params: {
    privateExtendedProperty: "streetCleaning=true",
  },
})

// Export for observability
this.existingStreetCleaningReminders = items

for (const item of items) {
  console.log(`Deleting street cleaning reminder for ${item.start.dateTime}`)
  await require("@pipedreamhq/platform").axios(this, {
    method: 'DELETE',
    url: `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events/${item.id}`,
    headers: {
      Authorization: `Bearer ${auths.google_calendar.oauth_access_token}`,
    },
    params: {
      sendUpdates: "none",
    }
  })
}
const axios = require('axios')

const calendarId = params.calendarId || "primary"

// If we move our car, old street cleaning reminders are irrelevant,
// so we find and delete all old reminders
const { items } = await require("@pipedreamhq/platform").axios(this, {
  method: 'GET',
  url: `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`,
  headers: {
    Authorization: `Bearer ${auths.google_calendar.oauth_access_token}`,
  },
  params: {
    privateExtendedProperty: "streetCleaning=true",
  },
})

// Export for observability
this.existingStreetCleaningReminders = items

for (const item of items) {
  console.log(`Deleting street cleaning reminder for ${item.start.dateTime}`)
  await require("@pipedreamhq/platform").axios(this, {
    method: 'DELETE',
    url: `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events/${item.id}`,
    headers: {
      Authorization: `Bearer ${auths.google_calendar.oauth_access_token}`,
    },
    params: {
      sendUpdates: "none",
    }
  })
}
steps.
create_street_cleaning_reminder
auth
to use OAuth tokens and API keys in code via theauths object
(auths.google_calendar)
params
Attendees
[0]:
array ·params.attendees
Start dateTime

The time, as a combined date-time value (formatted according to RFC3339). A time zone offset is required unless a time zone is explicitly specified in timeZone.

{{steps.parse_time_from_text.cleaningStartTimeLocal}}
{{steps.parse_time_from_text.cleaningStartTimeLocal}}
string ·params.start_dateTime
End dateTime

The time, as a combined date-time value (formatted according to RFC3339). A time zone offset is required unless a time zone is explicitly specified in timeZone.

{{steps.parse_time_from_text.cleaningEndTimeLocal}}
{{steps.parse_time_from_text.cleaningEndTimeLocal}}
string ·params.end_dateTime
Summary

Title of the event.

STREET CLEANING
STREET CLEANING
string ·params.summary
Location

Geographic location of the event as free-form text.

{{event.body.lat}},{{event.body.long}}
{{event.body.lat}},{{event.body.long}}
string ·params.location
Optional
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across invocations with$checkpoint.
async (event, steps, params, auths) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
}
84
const axios = require('axios') 

// Defaults to the authed calendar (primary)
calendarId = params.calendarId || "primary"

const calendarParams = ["conferenceDataVersion", "maxAttendees", "sendNotifications", "sendUpdates", "supportsAttachments"]
const {conferenceDataVersion, maxAttendees, sendNotifications, sendUpdates, supportsAttachments} = params
p = params

const queryString = calendarParams.filter(param => p[param]).map(param => `${param}=${p[param]}`).join("&")
const { attendees } = params
data = {
  attendees: attendees.map(email => { return { email }}),
  'anyoneCanAddSelf': params.anyoneCanAddSelf,
  'colorId': params.colorId,
  'summary': params.summary,
  'location': params.location,
  'description': params.description,
  'start': {
    'dateTime': params.start_dateTime,
    'timeZone': params.start_timeZone,
  },
  'end': {
    'dateTime': params.end_dateTime,
    'timeZone': params.end_timeZone,
  },
  extendedProperties: {
    private: {
      streetCleaning: true,
    }
  },
  'recurrence': params.recurrence,
  'gadget': {
    'display': params.gadget_display,
    'height': params.gadget_height,
    'iconLink': params.gadget_iconLink,
    'link': params.gadget_link,
    'preferences': params.gadget_preferences,
    'title': params.gadget_title,
    'type': params.gadget_type,
    'width': params.gadget_width
  },
  'guestsCanInviteOthers': params.guestsCanInviteOthers,
  'guestsCanModify': params.guestsCanModify,
  'guestsCanSeeOtherGuests': params.guestsCanSeeOtherGuests,
  reminders: {
    overrides: params.reminders,
    useDefault: false,
  },
  'sequence': params.sequence,
  'source': {
    'title': params.source_title,
    'url': params.source_url
  },
  'status': params.status,
  'transparency': params.transparency,
  'visibility': params.visibility
}

// remove empty parameters from data object
const removeEmpty = (obj) => {
  Object.keys(obj).forEach(key => {
    if (obj[key] && typeof obj[key] === 'object') removeEmpty(obj[key]);
    else if (obj[key] === undefined) delete obj[key];
  });
  return obj;
};
data = removeEmpty(data);
if (!Object.keys(data.gadget).length)
  delete data.gadget;
if (!Object.keys(data.source).length)
  delete data.source
  

return await require("@pipedreamhq/platform").axios(this, {
  url: `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events?${queryString}`,
  headers: {
    Authorization: `Bearer ${auths.google_calendar.oauth_access_token}`,
  },
  method: 'POST',
  data: data
})
const axios = require('axios') 

// Defaults to the authed calendar (primary)
calendarId = params.calendarId || "primary"

const calendarParams = ["conferenceDataVersion", "maxAttendees", "sendNotifications", "sendUpdates", "supportsAttachments"]
const {conferenceDataVersion, maxAttendees, sendNotifications, sendUpdates, supportsAttachments} = params
p = params

const queryString = calendarParams.filter(param => p[param]).map(param => `${param}=${p[param]}`).join("&")
const { attendees } = params
data = {
  attendees: attendees.map(email => { return { email }}),
  'anyoneCanAddSelf': params.anyoneCanAddSelf,
  'colorId': params.colorId,
  'summary': params.summary,
  'location': params.location,
  'description': params.description,
  'start': {
    'dateTime': params.start_dateTime,
    'timeZone': params.start_timeZone,
  },
  'end': {
    'dateTime': params.end_dateTime,
    'timeZone': params.end_timeZone,
  },
  extendedProperties: {
    private: {
      streetCleaning: true,
    }
  },
  'recurrence': params.recurrence,
  'gadget': {
    'display': params.gadget_display,
    'height': params.gadget_height,
    'iconLink': params.gadget_iconLink,
    'link': params.gadget_link,
    'preferences': params.gadget_preferences,
    'title': params.gadget_title,
    'type': params.gadget_type,
    'width': params.gadget_width
  },
  'guestsCanInviteOthers': params.guestsCanInviteOthers,
  'guestsCanModify': params.guestsCanModify,
  'guestsCanSeeOtherGuests': params.guestsCanSeeOtherGuests,
  reminders: {
    overrides: params.reminders,
    useDefault: false,
  },
  'sequence': params.sequence,
  'source': {
    'title': params.source_title,
    'url': params.source_url
  },
  'status': params.status,
  'transparency': params.transparency,
  'visibility': params.visibility
}

// remove empty parameters from data object
const removeEmpty = (obj) => {
  Object.keys(obj).forEach(key => {
    if (obj[key] && typeof obj[key] === 'object') removeEmpty(obj[key]);
    else if (obj[key] === undefined) delete obj[key];
  });
  return obj;
};
data = removeEmpty(data);
if (!Object.keys(data.gadget).length)
  delete data.gadget;
if (!Object.keys(data.source).length)
  delete data.source
  

return await require("@pipedreamhq/platform").axios(this, {
  url: `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events?${queryString}`,
  headers: {
    Authorization: `Bearer ${auths.google_calendar.oauth_access_token}`,
  },
  method: 'POST',
  data: data
})