Merge pull request #3780 from omnivore-app/fix/notion
sync items and highlights to user created database in notion
This commit is contained in:
@ -40,6 +40,7 @@ import {
|
||||
saveIntegration,
|
||||
updateIntegration,
|
||||
} from '../../services/integrations'
|
||||
import { NotionClient } from '../../services/integrations/notion'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import {
|
||||
deleteTask,
|
||||
@ -57,15 +58,14 @@ export const setIntegrationResolver = authorized<
|
||||
...input,
|
||||
user: { id: uid },
|
||||
id: input.id || undefined,
|
||||
type: input.type || IntegrationType.Export,
|
||||
type: input.type || undefined,
|
||||
syncedAt: input.syncedAt ? new Date(input.syncedAt) : undefined,
|
||||
importItemState:
|
||||
input.type === IntegrationType.Import
|
||||
? input.importItemState || ImportItemState.Unarchived // default to unarchived
|
||||
: undefined,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
settings: input.settings,
|
||||
}
|
||||
|
||||
if (input.id) {
|
||||
// Update
|
||||
const existingIntegration = await findIntegration({ id: input.id }, uid)
|
||||
@ -96,6 +96,22 @@ export const setIntegrationResolver = authorized<
|
||||
if (integration.name.toLowerCase() === 'readwise') {
|
||||
// create a task to export all the items for readwise temporarily
|
||||
await enqueueExportToIntegration(integration.id, uid)
|
||||
} else if (
|
||||
integration.name.toLowerCase() === 'notion' &&
|
||||
integration.settings
|
||||
) {
|
||||
const settings = integration.settings as { parentDatabaseId?: string }
|
||||
if (settings.parentDatabaseId) {
|
||||
// update notion database properties
|
||||
const notion = new NotionClient(integration.token, integration)
|
||||
try {
|
||||
await notion.updateDatabase(settings.parentDatabaseId)
|
||||
} catch (error) {
|
||||
return {
|
||||
errorCodes: [SetIntegrationErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analytics.capture({
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Client } from '@notionhq/client'
|
||||
import axios from 'axios'
|
||||
import { updateIntegration } from '.'
|
||||
import { Integration } from '../../entity/integration'
|
||||
import { LibraryItem } from '../../entity/library_item'
|
||||
import { env } from '../../env'
|
||||
@ -111,7 +110,7 @@ type Property = 'highlights'
|
||||
interface Settings {
|
||||
parentPageId: string
|
||||
parentDatabaseId: string
|
||||
properties: Property[]
|
||||
properties?: Property[]
|
||||
}
|
||||
|
||||
export class NotionClient implements IntegrationClient {
|
||||
@ -244,7 +243,7 @@ export class NotionClient implements IntegrationClient {
|
||||
: undefined,
|
||||
},
|
||||
children:
|
||||
settings.properties.includes('highlights') && item.highlights
|
||||
settings.properties?.includes('highlights') && item.highlights
|
||||
? item.highlights
|
||||
.filter(
|
||||
(highlight) => !lastSync || highlight.updatedAt > lastSync // only new highlights
|
||||
@ -315,103 +314,92 @@ export class NotionClient implements IntegrationClient {
|
||||
return false
|
||||
}
|
||||
|
||||
const pageId = settings.parentPageId
|
||||
if (!pageId) {
|
||||
logger.error('Notion parent page id not found')
|
||||
return false
|
||||
}
|
||||
|
||||
let databaseId = settings.parentDatabaseId
|
||||
const databaseId = settings.parentDatabaseId
|
||||
if (!databaseId) {
|
||||
// create a database for the items
|
||||
const database = await this.client.databases.create({
|
||||
parent: {
|
||||
page_id: pageId,
|
||||
},
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: 'Library',
|
||||
},
|
||||
},
|
||||
],
|
||||
description: [
|
||||
{
|
||||
text: {
|
||||
content: 'Library of saved items from Omnivore',
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
Title: {
|
||||
title: {},
|
||||
},
|
||||
Author: {
|
||||
rich_text: {},
|
||||
},
|
||||
'Original URL': {
|
||||
url: {},
|
||||
},
|
||||
'Omnivore URL': {
|
||||
url: {},
|
||||
},
|
||||
'Saved At': {
|
||||
date: {},
|
||||
},
|
||||
'Last Updated': {
|
||||
date: {},
|
||||
},
|
||||
Tags: {
|
||||
multi_select: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// save the database id
|
||||
databaseId = database.id
|
||||
settings.parentDatabaseId = databaseId
|
||||
await updateIntegration(
|
||||
this.integrationData.id,
|
||||
{
|
||||
settings,
|
||||
},
|
||||
this.integrationData.user.id
|
||||
)
|
||||
logger.error('Notion database id not found')
|
||||
return false
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
items.map(async (item) => {
|
||||
const notionPage = this.itemToNotionPage(
|
||||
item,
|
||||
settings,
|
||||
this.integrationData?.syncedAt
|
||||
)
|
||||
const url = notionPage.properties['Omnivore URL'].url
|
||||
try {
|
||||
const notionPage = this.itemToNotionPage(
|
||||
item,
|
||||
settings,
|
||||
this.integrationData?.syncedAt
|
||||
)
|
||||
const url = notionPage.properties['Omnivore URL'].url
|
||||
|
||||
const existingPage = await this.findPage(url, databaseId)
|
||||
if (existingPage) {
|
||||
// update the page
|
||||
await this.client.pages.update({
|
||||
page_id: existingPage.id,
|
||||
properties: notionPage.properties,
|
||||
})
|
||||
|
||||
// append the children incrementally
|
||||
if (notionPage.children && notionPage.children.length > 0) {
|
||||
await this.client.blocks.children.append({
|
||||
block_id: existingPage.id,
|
||||
children: notionPage.children,
|
||||
const existingPage = await this.findPage(url, databaseId)
|
||||
if (existingPage) {
|
||||
// update the page
|
||||
await this.client.pages.update({
|
||||
page_id: existingPage.id,
|
||||
properties: notionPage.properties,
|
||||
})
|
||||
|
||||
// append the children incrementally
|
||||
if (notionPage.children && notionPage.children.length > 0) {
|
||||
await this.client.blocks.children.append({
|
||||
block_id: existingPage.id,
|
||||
children: notionPage.children,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
// create the page
|
||||
return this.createPage(notionPage)
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return false
|
||||
}
|
||||
|
||||
// create the page
|
||||
return this.createPage(notionPage)
|
||||
})
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private findDatabase = async (databaseId: string) => {
|
||||
return this.client.databases.retrieve({
|
||||
database_id: databaseId,
|
||||
})
|
||||
}
|
||||
|
||||
updateDatabase = async (databaseId: string) => {
|
||||
const database = await this.findDatabase(databaseId)
|
||||
// find the title property and update it
|
||||
const titleProperty = Object.entries(database.properties).find(
|
||||
([, property]) => property.type === 'title'
|
||||
)
|
||||
const title = titleProperty ? titleProperty[0] : 'Name'
|
||||
|
||||
await this.client.databases.update({
|
||||
database_id: database.id,
|
||||
properties: {
|
||||
[title]: {
|
||||
name: 'Title',
|
||||
},
|
||||
Author: {
|
||||
rich_text: {},
|
||||
},
|
||||
'Original URL': {
|
||||
url: {},
|
||||
},
|
||||
'Omnivore URL': {
|
||||
url: {},
|
||||
},
|
||||
'Saved At': {
|
||||
date: {},
|
||||
},
|
||||
'Last Updated': {
|
||||
date: {},
|
||||
},
|
||||
Tags: {
|
||||
multi_select: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,8 +30,7 @@ import { applyStoredTheme } from '../../../lib/themeUpdater'
|
||||
import { showSuccessToast } from '../../../lib/toastHelpers'
|
||||
|
||||
type FieldType = {
|
||||
parentPageId?: string
|
||||
parentDatabaseId?: string
|
||||
parentDatabaseId: string
|
||||
properties?: string[]
|
||||
}
|
||||
|
||||
@ -47,7 +46,6 @@ export default function Notion(): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
parentPageId: notion.settings?.parentPageId,
|
||||
parentDatabaseId: notion.settings?.parentDatabaseId,
|
||||
properties: notion.settings?.properties,
|
||||
})
|
||||
@ -72,6 +70,28 @@ export default function Notion(): JSX.Element {
|
||||
})
|
||||
}
|
||||
|
||||
const normalizeDatabaseId = useCallback(
|
||||
(value: string) => {
|
||||
// check if database id is in UUIDv4 format
|
||||
const uuidRegex =
|
||||
/^[0-9a-fA-F]{8}[0-9a-fA-F]{4}[0-9a-fA-F]{4}[0-9a-fA-F]{4}[0-9a-fA-F]{12}$/
|
||||
if (uuidRegex.test(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
// extract the database id from the URL
|
||||
// https://www.notion.so/ec460c235baa4da5bb412971a12e9dbe?v=8f4e324c0b584b67b8b7cfe9a2f996d7 -> ec460c235baa4da5bb412971a12e9dbe
|
||||
const urlRegex = /https:\/\/www.notion.so\/([a-f0-9]{32})\?*/
|
||||
const match = value.match(urlRegex)
|
||||
if (!match || match.length < 2) {
|
||||
messageApi.error('Invalid Notion Database ID.')
|
||||
return value
|
||||
}
|
||||
return match[1]
|
||||
},
|
||||
[messageApi]
|
||||
)
|
||||
|
||||
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
|
||||
try {
|
||||
await updateNotion(values)
|
||||
@ -168,27 +188,39 @@ export default function Notion(): JSX.Element {
|
||||
onFinishFailed={onFinishFailed}
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label="Notion Page Id"
|
||||
name="parentPageId"
|
||||
help="The id of the Notion page where the items will be exported to. You can find it in the URL of the page."
|
||||
label="Notion Database ID"
|
||||
name="parentDatabaseId"
|
||||
help="The ID of the Notion database where the items will be exported to. You can find it in the URL of the database."
|
||||
normalize={normalizeDatabaseId}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please input your Notion Page Id!',
|
||||
message: 'Please input your Notion Database ID!',
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
// check if database id is in UUIDv4 format
|
||||
const uuidRegex = /^[0-9a-fA-F]{8}[0-9a-fA-F]{4}[0-9a-fA-F]{4}[0-9a-fA-F]{4}[0-9a-fA-F]{12}$/
|
||||
if (uuidRegex.test(value)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
// extract the database id from the URL
|
||||
const urlRegex =
|
||||
/https:\/\/www.notion.so\/([a-f0-9]{32})\?*/
|
||||
const match = value.match(urlRegex)
|
||||
if (match && match.length >= 2) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error('Invalid Notion Database ID.')
|
||||
)
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
label="Notion Database Id"
|
||||
name="parentDatabaseId"
|
||||
hidden
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
label="Properties to Export"
|
||||
name="properties"
|
||||
|
||||
Reference in New Issue
Block a user