auths
objectEnter your notion API Token here. Do not share this with anyone!
Enter the target Notion database ID here.
The name for a task's "Due Date" property in Notion.
Type: Date
The name for a task's "Interval" property in Notion.
Type: Select
return
or this.key = 'value'
, pass input data to your code viaparams
, and maintain state across executions with$checkpoint.async
(event, steps, params) => {
}
/*******************************************************************************/
/*** DO NOT EDIT ANY CODE. ENTER YOUR CONFIGURATION VALUES IN THE FORM ABOVE ***/
/*******************************************************************************/
// Collect and assign user input
this.API_TOKEN = params.notionApiToken
this.DB_ID = params.taskDatabaseId
this.PROPS = {
dueDate: params.dueDatePropertyName, // Type: Date
interval: params.intervalPropertyName // Type: Select
}
this.INTERVALS = params.intervals && Object.keys(params.intervals).length > 0 ? params.intervals : {
"Daily" : [1, 'days'],
"Weekly" : [1, 'weeks'],
"Biweekly" : [2, 'weeks'],
"Monthly" : [1, 'months'],
"Quarterly" : [1, 'quarters'],
"Semi-Annually" : [6, 'months'],
"Annually" : [1, 'years']
}
// Validation
console.log('Validating user input...')
if (!/^secret_[\d|a-z|A-Z]+$/.test(this.API_TOKEN)) { throw new Error('Invalid Notion API token format')}
if (!/^[\d|a-f]{8}-?([\d|a-f]{4}-?){3}[\d|a-f]{12}$/.test(this.DB_ID)) { throw new Error('Invalid database ID format')}
const validIntervalUnits = ['days', 'weeks', 'months', 'quarters', 'years']
for (const interval in this.INTERVALS) {
if (!(Array.isArray(this.INTERVALS[interval]) && this.INTERVALS[interval].length === 2)) { throw new Error(`Invalid recurring interval configuration for option: ${interval}.`)}
if (!Number.isInteger(this.INTERVALS[interval][0])) { throw new Error(`Recurring interval must be an integer for option: ${interval}.`)}
if (!(typeof this.INTERVALS[interval][1] === 'string' || this.INTERVALS[interval][1] instanceof String)) { throw new Error(`Recurring unit must be a string for option: ${interval}.`)}
if (!validIntervalUnits.includes(this.INTERVALS[interval][1])) { throw new Error(`Recurring unit for option ${interval} must be one of: ${validIntervalUnits.join(', ')}.`)}
}
console.log('Validation complete!')
auths
objectreturn
or this.key = 'value'
, pass input data to your code viaparams
, and maintain state across executions with$checkpoint.async
(event, steps) => {
}
const axios = require("axios")
const moment = require("moment")
const respOne = await axios({
method: "POST",
url: `https://api.notion.com/v1/databases/${steps.config.DB_ID}/query`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${steps.config.API_TOKEN}`, // API KEY
"Notion-Version": "2021-05-13"
},
data : {
filter : {
and : [
{
property : 'Category',
multi_select : {
contains : 'Recurring'
}
},
{
property : steps.config.PROPS.interval,
select : {
is_not_empty : true
}
},
{
property : steps.config.PROPS.dueDate,
date : {
is_not_empty : true
}
},
{
property : 'Stop',
select : {
is_empty : true
}
},
{
property : 'hash',
rich_text : {
is_empty : true
}
}
]
}
}
}).catch(err => {
console.log(err.response.data)
throw err
})
this.tasksOne = respOne.data.results
const respTwo = await axios({
method: "POST",
url: `https://api.notion.com/v1/databases/${steps.config.DB_ID}/query`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${steps.config.API_TOKEN}`, // API KEY
"Notion-Version": "2021-05-13"
},
data : {
filter : {
and : [
{
property : 'Category',
multi_select : {
contains : 'Recurring'
}
},
{
property : steps.config.PROPS.interval,
select : {
is_not_empty : true
}
},
{
property : steps.config.PROPS.dueDate,
date : {
after : moment().format("YYYY-MM-DDTHH:mm:ss.SSSZ")
}
},
{
property : 'hash',
rich_text : {
is_not_empty : true
}
}
]
}
}
}).catch(err => {
console.log(err.response.data)
throw err
})
this.tasksTwo = respTwo.data.results
auths
objectreturn
or this.key = 'value'
, pass input data to your code viaparams
, and maintain state across executions with$checkpoint.async
(event, steps) => {
}
const axios = require("axios")
const moment = require("moment")
const DATE_TIME_REGEX = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?(-[0-9]{2}:[0-9]{2})?$/
if (steps.fetchCompletedRecurringTasks.tasksOne.length > 0) {
for (const task of steps.fetchCompletedRecurringTasks.tasksOne) {
let hash = (()=>([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16)))()
let patch = { properties : {} }
patch.properties['hash'] = { "rich_text": [ { "type": "text", "text" : { "content" : hash } } ] }
await axios({
method: "PATCH",
url: `https://api.notion.com/v1/pages/${task.id}`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${steps.config.API_TOKEN}`, // API KEY
"Notion-Version": "2021-05-13"
},
data : JSON.stringify(patch)
}).catch(err => {
console.log(err.response.data)
throw err
})
let next = moment().add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
let startDate = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.start)
if (task.properties[steps.config.PROPS.dueDate].date.end) {
var endDate = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.end)
}
let newStart = startDate
let newEnd = endDate
while (next.isBefore(moment().add(2, 'y'))) {
let props = task.properties
props['hash'] = { "rich_text": [ { "type": "text", "text" : { "content" : hash } } ] }
props[steps.config.PROPS.dueDate] = { date : {} }
newStart.add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
let startDateFormat = DATE_TIME_REGEX.test(newStart) ? "YYYY-MM-DDTHH:mm:ss.SSSZ" : "YYYY-MM-DD"
props[steps.config.PROPS.dueDate].date.start = newStart.format(startDateFormat)
if (task.properties[steps.config.PROPS.dueDate].date.end) {
newEnd.add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
let endDateFormat = DATE_TIME_REGEX.test(newEnd) ? "YYYY-MM-DDTHH:mm:ss.SSSZ" : "YYYY-MM-DD"
props[steps.config.PROPS.dueDate].date.end = newEnd.format(endDateFormat)
}
let post = { parent : { "database_id" : steps.config.DB_ID }, properties : props }
await axios({
method: "POST",
url: `https://api.notion.com/v1/pages`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${steps.config.API_TOKEN}`, // API KEY
"Notion-Version": "2022-06-28"
},
data : JSON.stringify(post)
}).catch(err => {
console.log(err.response.data)
throw err
})
next.add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
}
}
}
if (steps.fetchCompletedRecurringTasks.tasksTwo.length > 0) {
var latests = {}
let deletedHashes = []
for (const task of steps.fetchCompletedRecurringTasks.tasksTwo) {
try {
if (task.properties['Stop'].select.name === "YES") {
deletedHashes.push(task.properties['hash'].rich_text[0].text.content)
}
} catch (e) {}
let date = moment()
if (task.properties[steps.config.PROPS.dueDate].date.end) {
date = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.end)
} else date = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.start)
let hash = task.properties['hash'].rich_text[0].text.content
if (!deletedHashes.includes(hash)) {
if (!latests[hash]) {
latests[hash] = date
} else if (latests[hash].isBefore(date)) {
latests[hash] = date
}
}
}
if (deletedHashes.length > 0) {
for (const task of steps.fetchCompletedRecurringTasks.tasksTwo) {
for (const hash of deletedHashes) {
if (task.properties['hash'].rich_text[0].text.content === hash) {
await axios({
method: "PATCH",
url: `https://api.notion.com/v1/pages/${task.id}`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${steps.config.API_TOKEN}`, // API KEY
"Notion-Version": "2021-05-13"
},
data : JSON.stringify({archived : true})
}).catch(err => {
console.log(err.response.data)
throw err
})
}
}
}
}
if (Object.keys(latests).length > 0) {
for (const task of steps.fetchCompletedRecurringTasks.tasksTwo) {
let hash = task.properties['hash'].rich_text[0].text.content
if (!deletedHashes.includes(hash)) {
if (task.properties[steps.config.PROPS.dueDate].date.end) {
var date = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.end)
} else var date = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.start)
var realStart = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.start)
var start = task.properties[steps.config.PROPS.dueDate].date.start
var realEnd = moment.parseZone(task.properties[steps.config.PROPS.dueDate].date.end)
var end = task.properties[steps.config.PROPS.dueDate].date.end
if (latests[hash].isSame(date)) {
let next = date.add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
if (next.isBefore(moment().add(2, 'y'))) {
let props = task.properties
props[steps.config.PROPS.dueDate] = { date : {} }
realStart.add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
let startDateFormat = DATE_TIME_REGEX.test(start) ? "YYYY-MM-DDTHH:mm:ss.SSSZ" : "YYYY-MM-DD"
props[steps.config.PROPS.dueDate].date.start = realStart.format(startDateFormat)
if (task.properties[steps.config.PROPS.dueDate].date.end) {
realEnd.add(...steps.config.INTERVALS[task.properties[steps.config.PROPS.interval].select.name])
let endDateFormat = DATE_TIME_REGEX.test(end) ? "YYYY-MM-DDTHH:mm:ss.SSSZ" : "YYYY-MM-DD"
props[steps.config.PROPS.dueDate].date.end = realEnd.format(endDateFormat)
}
let post = { parent : { "database_id" : steps.config.DB_ID }, properties : props }
await axios({
method: "POST",
url: `https://api.notion.com/v1/pages`,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${steps.config.API_TOKEN}`, // API KEY
"Notion-Version": "2022-06-28"
},
data : JSON.stringify(post)
}).catch(err => {
console.log(err.response.data)
throw err
})
}
}
}
}
}
}
console.log("Update complete!")
console.log('Workflow finished')