Skip to content

Commit e5cb482

Browse files
committed
feat: extractValuesFromProperties, cover property 추가 기능 add
1 parent 4e8b690 commit e5cb482

File tree

4 files changed

+215
-22
lines changed

4 files changed

+215
-22
lines changed

packages/notion-to-utils/src/client/getPageProperties.spec.ts

Lines changed: 132 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,47 @@ const getPagePropertiesMock = {
2424
},
2525
};
2626

27+
const getPagePropertiesMockWithCover = {
28+
object: 'page',
29+
id: TEST_ID,
30+
properties: {
31+
Category: { id: '1', type: 'text', text: { content: 'Category A' } },
32+
Slug: { id: '2', type: 'text', text: { content: 'slug-value' } },
33+
Date: { id: '3', type: 'date', date: { start: '2023-05-01' } },
34+
},
35+
cover: {
36+
type: 'file',
37+
file: {
38+
url: 'https://notion-image.url/test.jpg',
39+
},
40+
},
41+
};
42+
2743
describe('getPageProperties', () => {
28-
it('유효한 pageId가 제공되고 keys가 없을 때 모든 속성을 반환한다', async () => {
44+
it('유효한 pageId가 제공되고 keys가 없으며, extractValues가 false일 때 모든 속성을 반환한다', async () => {
2945
notionClient.pages.retrieve = vi
3046
.fn()
3147
.mockResolvedValue(getPagePropertiesMock);
3248

33-
const properties = await notionClient.getPageProperties(TEST_ID);
49+
// extractValues를 false로 명시적으로 설정
50+
const properties = await notionClient.getPageProperties(TEST_ID, [], false);
3451
expect(properties).toEqual(getPagePropertiesMock.properties);
3552
expect(notionClient.pages.retrieve).toHaveBeenCalledWith({
3653
page_id: TEST_ID,
3754
});
3855
});
3956

40-
it('유효한 pageId와 keys가 제공될 때 지정된 속성만 반환한다', async () => {
57+
it('유효한 pageId와 keys가 제공될 때, extractValues가 false일 때 지정된 속성만 반환한다', async () => {
4158
notionClient.pages.retrieve = vi
4259
.fn()
4360
.mockResolvedValue(getPagePropertiesMock);
4461

45-
const filteredProperties = await notionClient.getPageProperties(TEST_ID, [
46-
'Category',
47-
'Date',
48-
]);
62+
// extractValues를 false로 명시적으로 설정
63+
const filteredProperties = await notionClient.getPageProperties(
64+
TEST_ID,
65+
['Category', 'Date'],
66+
false
67+
);
4968

5069
expect(filteredProperties).toEqual({
5170
Category: getPagePropertiesMock.properties.Category,
@@ -59,9 +78,11 @@ describe('getPageProperties', () => {
5978
it('유효하지 않은 pageId가 제공될 때 undefined를 반환한다', async () => {
6079
notionClient.pages.retrieve = vi.fn().mockResolvedValue(null);
6180

62-
const filteredProperties = await notionClient.getPageProperties(TEST_ID, [
63-
'Category',
64-
]);
81+
const filteredProperties = await notionClient.getPageProperties(
82+
TEST_ID,
83+
['Category'],
84+
false
85+
);
6586

6687
expect(filteredProperties).toBeUndefined();
6788
expect(notionClient.pages.retrieve).toHaveBeenCalledWith({
@@ -74,9 +95,11 @@ describe('getPageProperties', () => {
7495
.fn()
7596
.mockResolvedValue(getPagePropertiesMock);
7697

77-
const filteredProperties = await notionClient.getPageProperties(TEST_ID, [
78-
'NonExistentKey',
79-
]);
98+
const filteredProperties = await notionClient.getPageProperties(
99+
TEST_ID,
100+
['NonExistentKey'],
101+
false
102+
);
80103

81104
expect(filteredProperties).toEqual({});
82105
expect(notionClient.pages.retrieve).toHaveBeenCalledWith({
@@ -96,4 +119,100 @@ describe('getPageProperties', () => {
96119
page_id: TEST_ID,
97120
});
98121
});
122+
123+
// 커버 이미지 테스트 케이스 추가
124+
it('파일 타입의 커버 이미지가 있을 때 coverUrl 속성을 추가한다', async () => {
125+
notionClient.pages.retrieve = vi
126+
.fn()
127+
.mockResolvedValue(getPagePropertiesMockWithCover);
128+
129+
// 테스트 실행
130+
const properties = await notionClient.getPageProperties(TEST_ID, [], false);
131+
console.log(properties);
132+
// 결과 검증
133+
expect(properties).not.toBeUndefined();
134+
expect(properties).toHaveProperty('coverUrl');
135+
expect((properties as Record<string, any>).coverUrl).toEqual({
136+
type: 'url',
137+
url: 'https://www.notion.so/image/https%3A%2F%2Fnotion-image.url%2Ftest.jpg?table=block&id=TEMP&cache=v2',
138+
id: `${TEST_ID}-coverUrl`,
139+
});
140+
});
141+
142+
it('외부 타입의 커버 이미지가 있을 때 coverUrl 속성을 추가한다', async () => {
143+
const mockWithExternalCover = {
144+
...getPagePropertiesMock,
145+
cover: {
146+
type: 'external',
147+
external: {
148+
url: 'https://external-image.url/test.jpg',
149+
},
150+
},
151+
};
152+
153+
notionClient.pages.retrieve = vi
154+
.fn()
155+
.mockResolvedValue(mockWithExternalCover);
156+
157+
// 테스트 실행
158+
const properties = await notionClient.getPageProperties(TEST_ID, [], false);
159+
160+
// 결과 검증
161+
expect(properties).not.toBeUndefined();
162+
expect(properties).toHaveProperty('coverUrl');
163+
expect((properties as Record<string, any>).coverUrl).toEqual({
164+
type: 'url',
165+
url: mockWithExternalCover.cover.external.url,
166+
id: `${TEST_ID}-coverUrl`,
167+
});
168+
});
169+
170+
it('커버 이미지가 없을 때 coverUrl 속성을 추가하지 않는다', async () => {
171+
const mockWithoutCover = {
172+
object: 'page',
173+
id: TEST_ID,
174+
properties: {
175+
Category: { id: '1', type: 'text', text: { content: 'Category A' } },
176+
Slug: { id: '2', type: 'text', text: { content: 'slug-value' } },
177+
Date: { id: '3', type: 'date', date: { start: '2023-05-01' } },
178+
},
179+
// cover 속성 없음
180+
};
181+
182+
notionClient.pages.retrieve = vi.fn().mockResolvedValue(mockWithoutCover);
183+
// 테스트 실행
184+
const properties = await notionClient.getPageProperties(TEST_ID, [], false);
185+
186+
// 결과 검증
187+
expect(properties).not.toBeUndefined();
188+
expect(properties as Record<string, any>).not.toHaveProperty('coverUrl');
189+
});
190+
191+
it('extractValues가 true일 때 커버 이미지 URL을 추출한다', async () => {
192+
// 커버 이미지가 있는 mock 데이터
193+
const mockWithExternalCover = {
194+
...getPagePropertiesMock,
195+
cover: {
196+
type: 'external',
197+
external: {
198+
url: 'https://external-image.url/test.jpg',
199+
},
200+
},
201+
};
202+
203+
// pages.retrieve만 모킹
204+
notionClient.pages.retrieve = vi
205+
.fn()
206+
.mockResolvedValue(mockWithExternalCover);
207+
208+
// 테스트 실행 - extractValues를 true로 설정
209+
const properties = await notionClient.getPageProperties(TEST_ID, [], true);
210+
211+
// 결과 검증
212+
expect(properties).not.toBeUndefined();
213+
expect(properties).toHaveProperty('coverUrl');
214+
expect((properties as Record<string, any>).coverUrl).toBe(
215+
mockWithExternalCover.cover.external.url
216+
);
217+
});
99218
});
Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,52 @@
11
import { Client } from '@notionhq/client';
22
import { isPageObjectResponse } from './utils/isPageObjectResponse';
3+
import { extractValuesFromProperties } from './utils/extractValuesFromProperties';
4+
import { formatNotionImageUrl } from './formatNotionImageUrl';
35

46
/**
57
* Retrieves properties of a Notion page.
68
*
79
* @param {Client} client - The Notion client instance.
810
* @param {string} pageId - The ID of the Notion page.
911
* @param {string[]} [keys=[]] - Optional array of property keys to filter. If empty, all properties are returned.
10-
* @returns {Promise<PageObjectResponse['properties'] | undefined>} The requested page properties or undefined if the page is not found or is not a PageObjectResponse.
12+
* @param {boolean} [extractValues=true] - Whether to extract values from properties.
1113
*/
1214
export async function getPageProperties(
1315
client: Client,
1416
pageId: string,
15-
keys: string[] = []
17+
keys: string[] = [],
18+
extractValues = true
1619
) {
1720
const page = await client.pages.retrieve({
1821
page_id: pageId,
1922
});
20-
2123
if (!isPageObjectResponse(page)) return;
22-
const { properties } = page;
24+
const { properties, cover } = page;
25+
26+
// cover 이미지 URL 포맷팅
27+
let coverUrl = '';
28+
if (cover) {
29+
if (cover.type === 'file') {
30+
const url = cover.file?.url;
31+
if (url) {
32+
coverUrl = formatNotionImageUrl(url, pageId);
33+
}
34+
} else if (cover.type === 'external') {
35+
coverUrl = cover.external?.url;
36+
}
37+
}
38+
39+
// cover 이미지 URL 추가
40+
if (coverUrl) {
41+
properties.coverUrl = {
42+
type: 'url',
43+
url: coverUrl,
44+
id: `${pageId}-coverUrl`,
45+
};
46+
}
2347

2448
if (keys.length === 0) {
25-
return properties;
49+
return extractValues ? extractValuesFromProperties(properties) : properties;
2650
}
2751

2852
const filteredProperties: Record<string, any> = {};
@@ -33,5 +57,7 @@ export async function getPageProperties(
3357
}
3458
});
3559

36-
return filteredProperties;
60+
return extractValues
61+
? extractValuesFromProperties(filteredProperties)
62+
: filteredProperties;
3763
}

packages/notion-to-utils/src/client/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ export class Client extends NotionClient {
1010
super(options);
1111
}
1212

13-
getPageProperties = (pageId: string, keys?: string[]) =>
14-
getPagePropertiesFunc(this, pageId, keys);
13+
getPageProperties = (
14+
pageId: string,
15+
keys: string[] = [],
16+
extractValues = true
17+
) => getPagePropertiesFunc(this, pageId, keys, extractValues);
18+
getPageBlocks = (pageId: string) => getPageBlocksFunc(this, pageId);
19+
20+
//? 25.4.7 getFileUrl 외부에서 미사용
1521
getFileUrl = (pageId: string, propertyKey: string) =>
1622
getFileUrlFunc(this, pageId, propertyKey);
17-
getPageBlocks = (pageId: string) => getPageBlocksFunc(this, pageId);
23+
//? 25.4.7 formatNotionImageUrl 외부에서 미사용
1824
formatNotionImageUrl = (url: string, blockId?: string) =>
1925
formatNotionImageUrlFunc(url, blockId);
2026
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export const extractValuesFromProperties = (
2+
properties: Record<string, any>
3+
) => {
4+
const extractedValues: Record<string, any> = {};
5+
Object.entries(properties).forEach(([key, property]) => {
6+
switch (property.type) {
7+
case 'date':
8+
extractedValues[key] = property.date;
9+
break;
10+
case 'multi_select':
11+
if (property.multi_select.length > 0) {
12+
extractedValues[key] = property.multi_select[0];
13+
} else {
14+
extractedValues[key] = null;
15+
}
16+
break;
17+
case 'rich_text':
18+
if (property.rich_text.length > 0) {
19+
extractedValues[key] = property.rich_text[0].plain_text;
20+
} else {
21+
extractedValues[key] = '';
22+
}
23+
break;
24+
case 'checkbox':
25+
extractedValues[key] = property.checkbox;
26+
break;
27+
case 'title':
28+
if (property.title.length > 0) {
29+
extractedValues[key] = property.title[0].plain_text;
30+
} else {
31+
extractedValues[key] = '';
32+
}
33+
break;
34+
case 'url':
35+
extractedValues[key] = property.url;
36+
break;
37+
default:
38+
extractedValues[key] = property;
39+
}
40+
});
41+
return extractedValues;
42+
};

0 commit comments

Comments
 (0)