Merge pull request #3780 from omnivore-app/fix/notion

sync items and highlights to user created database in notion
This commit is contained in:
Hongbo Wu
2024-04-04 11:58:59 +08:00
committed by GitHub
3 changed files with 141 additions and 105 deletions

View File

@ -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({

View File

@ -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: {},
},
},
})
}
}

View File

@ -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"