Notion API์ ํจ๊ป ์ ์ ํ์ด์ง๋ก์ ์ฌ์ โ ํํด ๋ธ๋ก๊ทธ | ๊ธฐ์ ๋ธ๋ก๊ทธ
๊ด๋ฆฌ์๊ฐ ์ฐ๊ธฐ ์ฌ์ด ํด
์ฐ๋ฆฌ ํ์ฌ๋ ๋ ธ์ ์ ์ด๋ฏธ ์ฌ์ฉ ํ๊ณ ์๋ค. ์ค์ผ์ฅด๋ถํฐ ๊ณต์ ๋ฌธ์ ๋ฑ์์ ๋ชจ๋ ๋ถ์๊ฐ ๋ ธ์ ์ ์ฌ์ฉํ๊ณ ์์์ผ๋ฉฐ, ๊ทธ๋ ๊ธฐ์ ๋ ธ์ ์ฌ์ฉ์ด ์ต์ํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค. ๊ทธ๋ ์ง๋ง ์ด๋ฏธ ๋ฐฑ์คํผ์ค์์ ๊ณต์ง์ฌํญ ์ ๋ ฅ ํผ์ ๋ง๋ค์ด ๋์์๋ค.
๊ทธ๋ฌ๋ ์์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ณต์ง์ฌํญ์ ์ ํ ํด๋์ง ์์๊ณ , ๋ด์ฉ ์ ๋ ฅ์์๋ ์์ง ๋ฐ๋ก ํ ์คํธ ์ ๋ ฅ๊ธฐ ๋ฑ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก ๊ธ์์ ์คํ์ผ ์ ์ฉ์ด ๋ถ๊ฐํ๋ค. ํ ์คํธ ์ ๋ ฅ๊ธฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ์ฉํ๋ฉด ๋์์? ์ถ์ง๋ง ํ ์ด๋ธ ์ ํ ์ด ์์ง ์๋๊ธฐ์ ๋ ธ์ API๋ฅผ ์ ์ฉํด๋ณด๊ธฐ๋ก ํ๋ค.
๊ณํ
์ ์์ ํํด ๋ธ๋ก๊ทธ์ ๋๋ฌด๋ ์์ธํ ์จ์ ธ ์์ผ๋ฏ๋ก ๊ทธ๋๋ก ๋ฐ๋ผ๊ฐ๊ธฐ๋ก ํ๋ค.
- ๋ ธ์ integration ์ถ๊ฐ
- ์ํ๋ ํ์ด์ง์ connection ์ถ๊ฐ
- database ํ์ด์ง ์ถ๊ฐ
- properties ์ธํ
- ๋ฐ์ดํฐ ์ถ๊ฐ
- API๋ฅผ ํตํด ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
- ๋งํฌ๋ค์ด์ผ๋ก ๋ณ๊ฒฝ
- HTML ์ฝ๋๋ก ๋ณ๊ฒฝ
- ์์๊ฒ ๋ณด์ฌ์ฃผ๊ธฐ-๐ซ
๋ ธ์ API๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ
@notionhq/client
๋ฅผ ์ฌ์ฉํ๋ค.
const notion = new Client({ auth: token });
์ฌ๊ธฐ์ token์ integration ์ถ๊ฐ ์ ๋ฐ๋ secret์ด๋ค.
์ฃผ์ํ ์
๋ ธ์ API ํธ์ถ์ ์๋ฒ ์ฌ์ด๋์์๋ง ๊ฐ๋ฅํ๋ค.
ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์์ ํธ์ถ์ CORS ERROR๋ฅผ ์ผ์ผํจ๋ค!
๊ณต์ง์ฌํญ ๋ฆฌ์คํธ ๊ฐ์ ธ์ค๊ธฐ
๊ณต์ง์ฌํญ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ฌ ๋๋ ํํฐํด์ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ด ํ์ํ๋ค.
- ๋ด๊ฐ ๋ณด์ฌ์ค ์ฌ์ดํธ์ ๊ดํ ๋ฐ์ดํฐ
- ๊ณต์ง์ฌํญ ๋ฐ์ดํฐ
- ๋ ธ์ถ ๊ฐ๋ฅํ ์ํ์ ๋ฐ์ดํฐ
- ๋ ธ์ถ์ผ์์์ ๋ด๋ฆผ์ฐจ์์ผ๋ก๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ผ๋ฉด ์ข๊ฒ ๋ค.
๋ ธ์ ๋ฆฌ์คํธ์์๋ properties๋ฅผ ์ด์ฉํด์ ์ฟผ๋ฆฌ๋ฅผ ํ ์ ์๋๋ฐ, ๊ทธ ํ์ ์ ๋ฐ๋ผ์ ์ฟผ๋ฆฌ ํ๋ ๋ฐฉ๋ฒ์ด ๋ค๋ฅด๋ค.
// ๋จ์ผ ํํฐ
filter: {
property: '์ด๋ฆ',
[property type]: {
[๋น๊ต๋ฌธ]: '๋ด์ฉ',
},
}
// ๋ค์ค ํํฐ
filter: {
and: [
{
property: '์ด๋ฆ',
[property type]: {
[๋น๊ต๋ฌธ]: '๋ด์ฉ',
}
},
],
}
๋น๊ต๋ฌธ์ property์ ํ์ ์ ๋ฐ๋ผ์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ๋ค๋ฅธ ๋ฏ ํ๋ค.
๋ ธ์ ๊ฐ์ด๋์์๋ ์ฐพ์ง ๋ชปํ์๊ณ , ์ฝํ์ผ๋ฟ์ด๋ ChatGPT์์ ๋์์ ๋ฐ๋๋ค๋ฉด ์ฝ๊ฒ ํด๊ฒฐ ํ ์ ์๋ค.
sortํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
sorts: [
{
property: 'property ์ด๋ฆ',
direction: 'ascending or descending',
},
],
limit๋ฅผ ์ง์ ํ ์ ์๋ค.
page_size: number | undefined
ํ์ํ ๊ฒ๋ค์ ์ ์ฉํ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
const response = await notion.databases.query({
database_id: databaseId,
page_size: !isNaN(Number(limit)) ? Number(limit) : undefined,
filter: {
and: [
{
property: '์ฌ์ดํธ',
select: {
equals: '๋ฐฑ์คํผ์ค',
},
},
{
property: 'ํ๊ทธ',
select: {
equals: '๊ณต์ง์ฌํญ',
},
},
{
property: '์ํ',
status: {
equals: '๋
ธ์ถ',
},
},
{
property: '๋
ธ์ถ์ผ์',
date: {
on_or_before: dayjs().format('YYYY-MM-DD'),
},
},
],
},
sorts: [
{
property: '๋
ธ์ถ์ผ์',
direction: 'descending',
},
],
});
databaseId
๋ ํด๋น ํ์ด์ง์
https://www.notion.so/name/[databaseId]?.. ์์ ํ์ธ ํ ์ ์๋ค.
์ฃผ์ํ ์
property์ ์ด๋ฆ์ด ๋ฌ๋ผ์ง๊ฑฐ๋ ํ์ ์ด ๋ฌ๋ผ์ง๊ฒ ๋๋ฉด ์ฟผ๋ฆฌ์์ ์๋ฌ๊ฐ ๋๋ค.
๋ญ ์ด๋ ๊ฒ ์ฝ๊ฒ ์๋ฌ๊ฐ ๋์ง? ์ถ์์ง๋ง, SQL์์ ์ปฌ๋ผ ์ด๋ฆ์ด ๋ณ๊ฒฝ๋๊ฑฐ๋ ํ์ ์ด ๋ณ๊ฒฝ๋๋ฉด ์๋ฌ๊ฐ ๋๋ ๊ฒ๊ณผ ๋์ผํ๋ค.
response๋ฅผ ์ฝ์์ ์ฐ์ด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
// response
{
object: 'list',
results: [
{
object: 'page',
id: 'page-id',
created_time: '2024-05-23T02:30:00.000Z',
last_edited_time: '2024-05-23T09:23:00.000Z',
created_by: [Object],
last_edited_by: [Object],
cover: null,
icon: null,
parent: [Object],
archived: false,
in_trash: false,
properties: [Object],
url: 'https://www.notion.so/',
public_url: null
},
],
}
response๋ง ์ฐ์ด ๋ด์๋ ๊ณต์ง์ฌํญ ๋ฆฌ์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ธ๋งํ ๋ฐ์ดํฐ๊ฐ ํ๋๋ ์๋ค.
์ฌ๊ธฐ์๋ ๊ณต์ง์ฌํญ ์ธ๋ถ ๋ด์ฉ์ ๋ณด๊ธฐ ์ํด ํ์ํ id๋ง ๊ฑด์ง ์ ์๋ค.
์ด์ธ์ ์ง์ ํ properties(์ปฌ๋ผ๋ค)๋ฅผ ๋ณด๋ ค๋ฉด properties๋ฅผ ๋ณด์์ผ ํ๋ค.
ํ์ ๋ณ๋ก ๊ตฌ์ฑ๋์ด ์๋ ๋ฐ์ดํฐ๊ฐ ๋ชจ๋ ๋ค๋ฅด๋ ๊ผญ ์ฐ์ด๋ณด๊ธฐ๋ฅผ ๊ถํ๋ค.
๊ณต์ง์ฌํญ ๋ฆฌ์คํธ๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํ ๋ฐ์ดํฐ๋ค์ ์ป์๋ค!
const notices = response.results.map((result: any) => {
return {
id: result.id,
title: result.properties['์ ๋ชฉ'].title[0].plain_text,
category: result.properties['์นดํ
๊ณ ๋ฆฌ'].multi_select.map(
(category: { name: string }) => category.name,
),
displayedAt: result.properties['๋
ธ์ถ์ผ์'].date.start,
};
});
๊ณต์ง์ฌํญ ์ธ๋ถ ๋ด์ฉ ๊ฐ์ ธ์ค๊ธฐ
ํ์ด์ง ์ธ๋ถ ๋ด์ฉ์ ๊ฐ์ ธ์๋ณด๋ฉด ๋์ฐํ JSON ๋ฐ์ดํฐ๋ฅผ ๋ฐ์๋ณผ ์ ์๋ค.
const blocks = await notion.blocks.children.list({ block_id: pageId });
{
object: 'list',
results: [
{
object: 'block',
id: '',
parent: [Object],
created_time: '2024-05-23T02:48:00.000Z',
last_edited_time: '2024-05-23T10:51:00.000Z',
created_by: [Object],
last_edited_by: [Object],
has_children: false,
archived: false,
in_trash: false,
type: 'heading_2',
heading_2: [Object]
},
{
object: 'block',
id: '',
parent: [Object],
created_time: '2024-05-23T02:48:00.000Z',
last_edited_time: '2024-05-23T02:48:00.000Z',
created_by: [Object],
last_edited_by: [Object],
has_children: false,
archived: false,
in_trash: false,
type: 'heading_1',
heading_1: [Object]
},
{
object: 'block',
id: '',
parent: [Object],
created_time: '2024-05-23T02:49:00.000Z',
last_edited_time: '2024-05-23T02:49:00.000Z',
created_by: [Object],
last_edited_by: [Object],
has_children: true,
archived: false,
in_trash: false,
type: 'table',
table: [Object]
},
{
object: 'block',
id: '',
parent: [Object],
created_time: '2024-05-23T02:48:00.000Z',
last_edited_time: '2024-05-23T10:26:00.000Z',
created_by: [Object],
last_edited_by: [Object],
has_children: false,
archived: false,
in_trash: false,
type: 'code',
code: [Object]
},
{
object: 'block',
id: '',
parent: [Object],
created_time: '2024-05-23T10:27:00.000Z',
last_edited_time: '2024-05-23T10:27:00.000Z',
created_by: [Object],
last_edited_by: [Object],
has_children: false,
archived: false,
in_trash: false,
type: 'paragraph',
paragraph: [Object]
}
],
next_cursor: null,
has_more: false,
type: 'block',
block: {},
request_id: ''
}
๋ ์ด์ ์๊ณ ์ถ์ง ์์์ก๋ค.
๊ทธ ํ์ด์ง์ ์ด๋ค ๋ด์ฉ์ด ์ฐ์ฌ์ง์ง ๋ชจ๋ฅด๋๋ฐ paragraph, code, heading ๋ฑ์ ์ง์ ์ ๋ฆฌํด์ฃผ๊ณ ์ถ์ง ์์๋ค.
๊ทธ๋์ notion-to-md
๋ฅผ ์ฌ์ฉํ๋ค.
์ฐ๋ฆฌ๋ ์ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ํ์๊ฐ ์์ด ์ด๊ฒ ๋งํฌ๋ค์ด์ผ๋ก ๋ง๋ค์ด ์ค ๊ฒ์ด๋ค.
const mdBlocks = await n2m.pageToMarkdown(pageId);
const mdContent = n2m.toMarkdownString(mdBlocks).parent;
์ํ๊น๊ฒ๋ ์ ๋ชฉ ๋ฑ properties๋ค์ ๋ฆฌํดํด์ฃผ์ง ์๋๋ค.
๊ทธ๋์ ๋ค์ ๊ฒ์ํด์ค๋ค.
const page = (await notion.pages.retrieve({ page_id: pageId })) as any;
์์๊ฒ(?) ๋ณด์ฌ์ฃผ๊ธฐ
๋งํฌ๋ค์ด์ ๊ทธ๋๋ก ๋ณด์ฌ์ค ์ ์์ผ๋ฏ๋ก HTML ์ฝ๋๋ก ๋ง๋ค์ด์ค๋ค.
marked
๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํ๋ค.
const [content, setContent] = useState<string>('');
useEffect(() => {
const convertMarkdownToHtml = async () => {
const htmlString = await marked(notice.mdContent);
setContent(htmlString);
};
convertMarkdownToHtml();
}, []);
return (
<div dangerouslySetInnerHTML={{ __html: content }} />
);
tailwind๋ฅผ ์ฌ์ฉํ๊ณ ์์ด์ ๊ทธ๋ฐ์ง ์คํ์ผ์ด ์ ์ฉ๋์ง ์์์ css ํ์ผ๋ ์ถ๊ฐํด์ฃผ์๋ค.
.markdown-body {
font-family: Arial, sans-serif;
line-height: 1.6;
padding: 20px;
}
.markdown-body h1 {
font-size: 2em;
margin-bottom: 0.5em;
}
.markdown-body h2 {
font-size: 1.75em;
margin-bottom: 0.5em;
}
.markdown-body h3 {
font-size: 1.5em;
margin-bottom: 0.5em;
}
.markdown-body h4 {
font-size: 1.25em;
margin-bottom: 0.5em;
}
.markdown-body p {
margin-bottom: 0.8em;
}
.markdown-body ul,
.markdown-body ol {
margin: 0 0 1em 1.5em;
}
.markdown-body li {
margin-bottom: 0.5em;
}
.markdown-body a {
color: #0366d6;
text-decoration: none;
}
.markdown-body a:hover {
text-decoration: underline;
}
.markdown-body code {
background-color: #f6f8fa;
border-radius: 3px;
font-size: 85%;
padding: 0.2em 0.4em;
}
.markdown-body pre {
background-color: #f6f8fa;
border-radius: 3px;
padding: 1em;
overflow: auto;
}
.markdown-body blockquote {
border-left: 0.25em solid #dfe2e5;
padding: 0.5em 1em;
color: #6a737d;
background-color: #f6f8fa;
}
.markdown-body table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1em;
}
.markdown-body th,
.markdown-body td {
border: 1px solid #dfe2e5;
padding: 0.5em 1em;
}
.markdown-body th {
background-color: #f6f8fa;
font-weight: bold;
}
.markdown-body img {
max-width: 100%;
height: auto;
}
์์ฑ
์ฌ์ฉ ๊ฐ๋ฅ์ฑ
๋ฐฑ์คํผ์ค์ ๋ณ๋ค๋ฅธ ๊ตฌํ ์์ด ๋ ธ์ ํ์ด์ง๋ฅผ ๊ทธ๋๋ก ์ฝ์ด ์ค๋ ๊ฒ์ ์ฐธ ๋งค๋ ฅ์ ์ธ ๋ฐฉ์์ธ ๊ฒ ๊ฐ๋ค. ์๋ ๋ฉด์์๋ ์์ฌ์ด ๋ถ๋ถ์ ์์๊ณ , ์ฌ์ฉ์๊ฐ ์ต์ํ ํด๋ก ์ฝ๊ฒ ์ ๋ ฅ ๊ฐ๋ฅํ๋ค๋๊ฒ ์ ์ผ ํฐ ์ฅ์ ์ผ๋ก ์๊ฐ๋๋ค. ๋ํ ์ฌ์ด ์คํ์ผ ๋ณ๊ฒฝ๋ ์ฅ์ ์ด๋ค.
๊ทธ๋ฌ๋ ๋จ์ ๋ ๋๊ปด์ก๋ค.
๋จ์
- ์ฌ์ฉ์์๊ฒ ์กฐ์ฌ์ฑ์ ๋ฐ๋ผ์ผ ํ๋ค.
- property๊ฐ ๋ฐ๋๋ฉด ๋ฐ๋ก ์๋ฌ๊ฐ ๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ์๋ฌ๊ฐ ๋๋ ๊ฒ์ด ๋น์ฐํ์ง๋ง, SQL์ฒ๋ผ ์ด๋ ํ ๋ช ๋ น๋ฌธ์ ์ณ์ ๋ฐ๋๋๊ฒ ์๋๊ธฐ ๋๋ฌธ์ ๋๋ฌด ์์ฝ๊ฒ ๋ฐ๋ ์ ์๋ ์ํ์ฑ์ด ์๋ค.
- property ๋ณ๊ฒฝ์ ๊ผญ ๊ฐ๋ฐ๋ถ์์ ํ์ธ ํ ๋ณ๊ฒฝ์ด ํ์ํ๋ค๋ ์ธ์ง๊ฐ ํ์ํ๋ค.
- ์์ ์ค์ธ ๊ธ์ธ์ง ํ์ธ ํ ์ ์๋ค.
- ์ฐพ์๋ณธ ๋ฐ๋ก๋ ๋ ธ์ ์ ์๋ ์ ์ฅ ๊ธฐ๋ฅ์ด ๊ฐ์ฅ ๋ํ์ ์ธ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์ ๊ทธ๊ฑธ ๋๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ์ง ์๋๋ค๊ณ ํ๋ค.
- Formula ํจ์๋ฅผ ์ด์ฉ ํ ์๋ ์์ผ๋ ์๋ฒฝํ๊ฒ ์ํ๋ ํจ์๋ฅผ ๊ตฌํ ํ ์ ์๋ค.
- ์)
if(now() > prop("๋ ธ์ถ์ผ์") and prop("์ํ") == "๋ ธ์ถ", prop("Last edited time").dateAdd(1, "hours") > now(), false)
: ์์ ํ 1์๊ฐ ๋ค ๋ ธ์ถ ๋ฑ..
- ์)
- property๊ฐ ๋ฐ๋๋ฉด ๋ฐ๋ก ์๋ฌ๊ฐ ๋๋ค.
- ํ ์ฟผ๋ฆฌ์ MAX LIMIT๊ฐ ์๋ค.
- page_size์ undefined๋ฅผ ๋ฃ์ผ๋ฉด 100๊ฐ๊น์ง์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์จ๋ค๊ณ ํ๋ค.
- next_cursor๋ฑ์ผ๋ก ํ์ด์ง๋ค์ด์
์ ๊ตฌํ ํ ์ ์์ผ๋, API ์์์ ์ด์ ํ์ด์ง ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๊ธฐ๋ ์ง์ํ์ง ์์ ๋ฐ๋ก ๊ตฌํ์ด ํ์ํ๋ค.
- ๊ทธ๋ฌ๋ 3ํ์ด์ง์์ 5ํ์ด์ง๋ก ์ด๋์ด๋ 7ํ์ด์ง์์ 4ํ์ด์ง์ ์ด๋ ๋ฑ์ ๊ตฌํํ๊ธฐ ์ด๋ ต๋ค.
- ๋งํฌ๋ค์ด์ด๋ HTML์ ์์์ผ ์๋ฒฝํ ์ํ๋ ํ์ด์ง๋ฅผ ๋ง๋ค ์ ์๋ค.
- ๊ธ์ ์์ ๋ณ๊ฒฝ ๋ฑ์ ๋งํฌ๋ค์ด์์ ์ง์ํ์ง ์์ผ๋ฏ๋ก HTML์ผ๋ก ์์ฑํด์ผ ํ๋ค.
<span style="color:red;">๊ธ์</span>
๋ฑ..
- ๋ค์ค ์ํฐ ๋ฑ..
- ๊ธ์ ์์ ๋ณ๊ฒฝ ๋ฑ์ ๋งํฌ๋ค์ด์์ ์ง์ํ์ง ์์ผ๋ฏ๋ก HTML์ผ๋ก ์์ฑํด์ผ ํ๋ค.
์์ ๋จ์ ๋ค์ ์ด๊ฒจ๋ผ ์ ์๋ค๋ฉด ์ฌ์ฉ ํ ์ ์์ ๊ฒ ๊ฐ๋ค.
๋ง์ ์ฌ๋๋ค์ด ์ฌ์ฉํ ์์ ์ธ ๊ณต์ง์ฌํญ๋ณด๋ค๋ ์ผ๋ถ ์ฌ๋๋ค๋ง ์ฌ์ฉํ๊ณ ๋ ์กฐ์ฌ์ค๋ฝ๊ฒ ๋ค๋ฃฐ ๋ด์ฉ๊ฐ์ ์ฝ๊ด ๋ฑ์ ์ ํฉํ๋ค๊ณ ๋๊ผ๋ค.
'TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Next.js ์ Dynamic server usage ๋น๋ ์๋ฌ (0) | 2024.08.19 |
---|---|
too many clients์์ ๋ฒ์ด๋๋ณด์ (0) | 2024.08.19 |
token์ ๊ด๋ฆฌํด๋ณด์! (0) | 2024.08.19 |
x-api-key authorization (0) | 2024.08.19 |
supertest์ :id๊ฐ 500์ผ๋ก ๋จ์ด์ง๋ค๋ฉด? (0) | 2024.08.19 |