This post will focus on a specific problem solution around the use of SavvyCal for developers but each step will use standard tech (for Elixir developers) and should be understandable and generalizable to other problems.
Context
For some context, SavvyCal is a calendar booking application that allows users to publicly post availability windows and allow sign-ups. When a user books a session it goes through their workflow and ultimately ends up on whichever calendar they already use. For example, it allows anyone to book time with me for a coffee chat that will then show up on my existing Google calendar. (Try it! https://savvycal.com/byronsalty/coffee)
Problem
While Savvy is rather full featured in their domain of calendar booking, you may want to use a webbhook to be notified of events in other systems and build other integrations.
Let's say every time someone books you for an event you want to store their name and email in a "contacts" list that you setup. After you create that it's going to work perfectly (of course!) going forward but what about all of the bookings that happened before the webhook was operational?
Let's figure out how to replay all the events that happened before the webhook was running by manually sending them back into the webhook
Outline
- Connect to SavvyCal API to get events to "replay"
- Create an Elixir project to act as our replay engine.
- Convert API results into new posts to our webhook
- Success
1. Connect to SavvyCal API to get events to "replay"
Savvy allows for two types of integrations - apps and personal use. We are going to use the much simpler "personal use" route.
Go to:
Savvy Cal > Settings > Developers > Personal Tokens > Create
Create one and write down the values that should look like:
pt_01GSJPE5R6N<snip>
pt_secret_d69cc128<snip>
Now let's test it using curl
.
The me
api will give you some basic info on yourself:
curl -H 'Authorization: Bearer <private key>' https://api.savvycal.com/v1/me
We will use the events
api to get the events that we want to replay:
curl https://api.savvycal.com/v1/events \
-H 'Authorization: Bearer <private key>' \
-H 'Content-Type: application/json'
So far so good?
2. Create an Elixir project to act as our replay engine.
We'll use mix
to generate a base project called replay.
Create project:
$ mix new replay
$ cd replay
Add HTTPoison
and Poison
dependencies for http calls and json manipulation respectively:
# edit mix.exs
defp deps do
[
{:httpoison, "~> 1.8"},
{:poison, "~> 5.0"}
]
end
Install the dependencies:
mix deps.get
mix deps.compile
Test that everything is working so far:
$ iex -S mix
iex> response = HTTPoison.get!("http://ip.jsontest.com/")
%HTTPoison.Response{
status_code: 200,
...
}
iex> json = Poison.decode!(response.body)
%{"ip" => "..."}
That's all the setup. Now let's put in the replay code.
3. Use API to get entries that we'll send to webhook
Lets a new function that we'll call from the command line to actually run our project.
Edit lib/replay.ex
and add a start
function:
def start do
IO.inspect("Starting Replay...")
end
Testing that everything works so far:
$ mix run -e "Replay.start()"
Compiling 1 file (.ex)
"Starting Replay..."
Now let's add the HTTPoison
call in to retrieve events. Note that we're going to pass in period=all
because the API defaults to only upcoming events.
# adding to lib/replay.ex > start()
savvy_api_url = "https://api.savvycal.com/v1/events?limit=100&period=all"
headers = [
{"Authorization", "Bearer <add private key here>"},
{"Content-type", "application/json"}
]
response = HTTPoison.get!(savvy_api_url, headers)
json = Poison.decode!(response.body)
entries = json["entries"]
Send those entries over to the webhook url that we have setup with SavvyCal:
# continuing in lib/replay.ex > start()
webook_url = "<enter your webhook url>"
webhook_headers = [{"Content-type", "application/json"}]
Enum.map(entries, fn entry ->
body = Poison.encode!(%{
type: "event.created",
payload: entry
})
{:ok, _} = HTTPoison.post(webook_url, body, webhook_headers)
end)
Note: We are going to send these events through our webhook as if they were new events (See: event.created
).
IMPORTANT: Make sure your webhook doesn't create duplicate results. In general, you want your code to be idempotent so handling this is best practice in any case and will allow you to rerun this replay as many times as necessary.
4. Success!
For fun, let's add in some timing and then run the whole thing. I bumped the number of entries up to the max at 100. Let's see how it goes.
We could just run our start()
function now but I want to get some timing so I'll add a new timed()
function that will then call our start()
and wrap it with timing like this:
#adding to lib/replay.ex > timed()
def timed do
{micro_secs, results} = :timer.tc( fn ->
start()
end)
IO.inspect(results, label: "Num of events replayed")
IO.inspect(micro_secs, label: "micro seconds")
end
Results?
$ mix run -e "Replay.timed()"
Compiling 1 file (.ex)
"Starting Replay..."
Num of events replayed: 100
micro seconds: 6613082
6.6 seconds to replay 100 events sounds pretty good to me.
Top comments (0)