29
How I Sync Daily LeetCoding Challenge to Todoist
If you have ever been on a job hunt for a software developer position, you would have come across the so-called LeetCode style interviews.
Despite the fact that most of us don’t have to invert a Binary Tree at our actual job, that is how most coding/technical interviews are conducted at big tech companies like Google and Microsoft. Yes, even at the likes of unicorns (except for Stripe, because they are cool) and startups.
In this post, I’ll be writing about the thought process of how I came about building and deploying a very simple JavaScript app for free with Cloudflare Worker. If you simply want to deploy and use the app on your own, check out the repository here.
- How to get the Daily LeetCoding Challenge question using LeetCode API
- Creating a Todoist task using Todoist API
- Schedule our app to run daily using Cloudflare Worker ScheduledEvent API
- How to test a Cloudflare Worker Cron trigger locally with Miniflare
Since a year ago, I have been trying to make it a habit to solve the Daily LeetCoding Challenge (which I’m still struggling with). As I am using Todoist as my main productivity tool of choice, I have a daily task that looks just like this:
My Todoist habitual tasks
As a lazy person, having to check leetcode.com every time I want to practice is too much of a hassle. So then I thought, why not just sync the Daily LeetCoding Challenge to my Todoist every day?
Let’s start by defining what I want the app to do:
- Get Daily LeetCoding Challenge question
- Ability to create a new task on my Todoist account
- Sync new Daily LeetCoding Challenge question on time
- The app has to sync on time for free every day
Let’s start!
Like any sane person would do, the first thing I did was to do some research. By research, I meant I started to Google for information.
The first thing I did was to immediately Google for “leetcode API”, looking for the official API documentation.
To my surprise, there wasn’t any official LeetCode API documentation available. While there is a couple of unofficial LeetCode API repositories on GitHub, I would rather not use any unofficial API due to reliability concerns (poorly maintained, outdated, etc.).
The second thing that immediately came to my mind was to inspect the network request being made while visiting the site https://leetcode.com/problemset/all/.
With this, I was able to figure out the exact API called to query for the Daily LeetCoding Challenge — done.
Since LeetCode is using GraphQL, you would need to check out the “Payload” tab to see the GraphQL body
Here’s the GraphQL request body:
# HTTP POST to https://leetcode.com/graphql
query questionOfToday {
activeDailyCodingChallengeQuestion {
date
userStatus
link
question {
acRate
difficulty
freqBar
frontendQuestionId: questionFrontendId
isFavor
paidOnly: isPaidOnly
status
title
titleSlug
hasVideoSolution
hasSolution
topicTags {
name
id
slug
}
}
}
}
You can use the curl
command below to try it out yourself:
curl --request POST \
--url https://leetcode.com/graphql \
--header 'Content-Type: application/json' \
--data '{"query":"query questionOfToday {\n\tactiveDailyCodingChallengeQuestion {\n\t\tdate\n\t\tuserStatus\n\t\tlink\n\t\tquestion {\n\t\t\tacRate\n\t\t\tdifficulty\n\t\t\tfreqBar\n\t\t\tfrontendQuestionId: questionFrontendId\n\t\t\tisFavor\n\t\t\tpaidOnly: isPaidOnly\n\t\t\tstatus\n\t\t\ttitle\n\t\t\ttitleSlug\n\t\t\thasVideoSolution\n\t\t\thasSolution\n\t\t\ttopicTags {\n\t\t\t\tname\n\t\t\t\tid\n\t\t\t\tslug\n\t\t\t}\n\t\t}\n\t}\n}\n","operationName":"questionOfToday"}'
Code
Enough talking, let’s start to write some code that does exactly what we went through:
// Just some constants
const LEETCODE_API_ENDPOINT = 'https://leetcode.com/graphql'
const DAILY_CODING_CHALLENGE_QUERY = `
query questionOfToday {
activeDailyCodingChallengeQuestion {
date
userStatus
link
question {
acRate
difficulty
freqBar
frontendQuestionId: questionFrontendId
isFavor
paidOnly: isPaidOnly
status
title
titleSlug
hasVideoSolution
hasSolution
topicTags {
name
id
slug
}
}
}
}`
// We can pass the JSON response as an object to our createTodoistTask later.
const fetchDailyCodingChallenge = async () => {
console.log(`Fetching daily coding challenge from LeetCode API.`)
const init = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: DAILY_CODING_CHALLENGE_QUERY }),
}
const response = await fetch(LEETCODE_API_ENDPOINT, init)
return response.json()
}
Task “Get Daily LeetCoding Challenge question” — checked.
Like what we did in the previous step, I was able to find the official API documentation for Todoist. Typically, the first section that I always look for in API documentation is the Authorization section, especially when you want to perform create/update/delete operations on an app.
In short, authorization was pretty straightforward for Todoist:
- Get your API token
- Whenever you make a request, attach
Authorization: Bearer xxx-your-todoist-api-token-xxx
to your HTTP request header
Here’s an example of what the curl command to create a new task on Todoist would look like:
curl --request POST \
--url 'https://api.todoist.com/rest/v1/tasks?=' \
--header 'Authorization: Bearer xxx-your-todoist-api-token-xxx' \
--header 'Content-Type: application/json' \
--data '{
"content": "Buy a jar of peanut butter",
"due_string": "Today"
}'
Writing a function that does what we said is relatively easy, it looks something like this:
const TODOIST_API_ENDPOINT = "https://api.todoist.com/rest/v1";
// Passing in the `question` object from fetchDailyCodingChallenge function
const createTodoistTask = async (question) => {
const questionInfo = question.data.activeDailyCodingChallengeQuestion;
const questionTitle = questionInfo.question.title;
const questionDifficulty = questionInfo.question.difficulty;
const questionLink = `https://leetcode.com${questionInfo.link}`;
console.log(`Creating Todoist task with title ${questionTitle}.`);
const body = {
content: `[${questionTitle}](${questionLink})`,
description: `Difficulty: ${questionDifficulty}`,
due_string: "Today",
priority: 4,
};
const init = {
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${TODOIST_API_TOKEN}`, // Set at environment variable
},
};
const response = await fetch(`${TODOIST_API_ENDPOINT}/tasks`, init);
return response.json();
};
Task “Create a new task on my Todoist account” — done.
And we’re down to our one final task — running/automating the 2 tasks above every day, for free.
The first thing that came to my mind was Cron job. So, I immediately started looking for free solutions on the Internet. After spending a couple of hours doing some homework, I came across Cloudflare Worker, and I figured to give them a try.
This is probably one of the most common misconceptions about Cloudflare Worker. As the worker’s environment is not in Node.js, a lot of packages (e.g. npm install some-node-package
) that are running on Node.js would simply not work.
Tip: Check out the supported packages and libraries here.
Lucky for us, we only need to use the JavaScript built-in fetch
API.
Starting a Cloudflare Worker project is dead simple (reference), basically:
- Install the Wrangler CLI using
npm install -g @cloudflare/wrangler
- Run wrangler generate
- The entry point is
addEventListener
function. For our use case, we will be using the ScheduledEvent API where we simply have to change our event from"fetch"
to"scheduled"
Let’s stitch everything together:
// Move the constants to const.js
const syncLeetCodeCodingChallenge = async (event) => {
const question = await fetchDailyCodingChallenge();
await createTodoistTask(question);
};
addEventListener("scheduled", (event) => {
// Change 'fetch' to 'scheduled'
event.waitUntil(syncLeetCodeCodingChallenge(event));
});
Next, we would simply need to modify the wrangler.toml as below:
name = "<your-project-name>"
type = "webpack"
...
[triggers]
crons = ["1 0 * * *"]
With the setting above, the worker will run every day at 00:01 UTC and sync the Daily LeetCoding Challenge to your Todoist.
That’s all! Moving on to testing.
In order to try out the Cron triggers locally, we would need to install the Miniflare CLI. After installation, you may run your worker using:
# At terminal 1
miniflare
# At terminal 2
curl "http://localhost:8787/.mf/scheduled"
If you see a new task is created on your Todoist, you have made it!
No side project is ever done without hosting it.
To deploy the app on your own immediately, check out the project repository and use the “Deploy with Workers” button. If you’re interested in the know-how:
- Create a Cloudflare account.
- Add
TODOIST_API_TOKEN
usingwrangler secret put TODOIST_API_TOKEN
. You may find the newly added secret under 'Cloudflare Worker' → 'Settings' → 'Variables'. You can get your Todoist API token here. - Optional: This is only required for Wrangler actions. Add
CF_API_TOKEN
into your GitHub repository secrets. You can create your API token from https://dash.cloudflare.com/profile/api-tokens using theEdit Cloudflare Workers
template. - Finally, to publish any new changes to your Cloudflare Worker, run
wrangler publish
And we are finally done!
Finally, there’s a lot more that we could have done, e.g.:
- Handling unanswered questions from previous days
- Making the app configurable/customizable for users
- Add tags to our task
- Allowing users to create a random LeetCode question as a task based on question tag
I’m going to leave these features out for another day.
While there is a lot of hate on coding interviews as such, I personally look at it this way — by learning some brain teasers, you probably get paid a lot more, so why not? It is really a matter of perspective. If you happen to enjoy doing them, that is even better.
Personally, I don’t find as much joy doing LeetCode questions. Rather, I work on LeetCode problems as if I am lifting weights at the gym. While I don’t enjoy lifting weights, I do like reaping the benefits of it.
That’s all for today. Let me know what are you building with Cloudflare Worker!
29