Skip to content

Commit 24a0e65

Browse files
authored
Merge pull request #61 from microcmsio/feat/get-all-content-ids
feat: getAllContentIds
2 parents ec9c2aa + 3dc0535 commit 24a0e65

File tree

4 files changed

+294
-6
lines changed

4 files changed

+294
-6
lines changed

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,49 @@ client
106106
.catch((err) => console.error(err));
107107
```
108108

109+
#### Get all content ids
110+
111+
This function can be used to retrieve all content IDs only.
112+
Since `filters` and `draftKey` can also be specified, it is possible to retrieve only the content IDs for a specific category, or to include content from a specific draft. \
113+
The `target` property can also be used to address cases where the value of a field other than content ID is used in a URL, etc.
114+
115+
```javascript
116+
client
117+
.getAllContentIds({
118+
endpoint: 'endpoint',
119+
})
120+
.then((res) => console.log(res))
121+
.catch((err) => console.error(err));
122+
123+
// Get all content ids with filters
124+
client
125+
.getAllContentIds({
126+
endpoint: 'endpoint',
127+
filters: 'category[equals]uN28Folyn',
128+
})
129+
.then((res) => console.log(res))
130+
.catch((err) => console.error(err));
131+
132+
// Get all content ids with draftKey
133+
client
134+
.getAllContentIds({
135+
endpoint: 'endpoint',
136+
draftKey: 'draftKey',
137+
})
138+
.then((res) => console.log(res))
139+
.catch((err) => console.error(err));
140+
141+
// Get all content ids with target
142+
client
143+
.getAllContentIds({
144+
endpoint: 'endpoint',
145+
target: 'url',
146+
})
147+
.then((res) => console.log(res))
148+
.catch((err) => console.error(err));
149+
```
150+
```
151+
109152
#### CREATE API
110153

111154
The following is how to use the write system when making a request to the write system API.
@@ -253,6 +296,16 @@ client.getListDetail<Content>({ //other })
253296
client.getObject<Content>({ //other })
254297
```
255298

299+
The type of `getAllContentIds` is as follows.
300+
301+
```typescript
302+
/**
303+
* // getAllContentIds response type
304+
* string[] // This is array type of string
305+
*/
306+
client.getAllContentIds({ //other })
307+
```
308+
256309
Write functions can also be performed type-safely.
257310

258311
```typescript

src/createClient.ts

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
MicroCMSObjectContent,
1919
UpdateRequest,
2020
DeleteRequest,
21+
GetAllContentIdsRequest,
22+
MicroCMSQueries,
2123
} from './types';
2224
import {
2325
API_VERSION,
@@ -96,8 +98,8 @@ export const createClient = ({
9698
new Error(
9799
`fetch API response status: ${response.status}${
98100
message ? `\n message is \`${message}\`` : ''
99-
}`
100-
)
101+
}`,
102+
),
101103
);
102104
}
103105

@@ -109,8 +111,8 @@ export const createClient = ({
109111
new Error(
110112
`fetch API response status: ${response.status}${
111113
message ? `\n message is \`${message}\`` : ''
112-
}`
113-
)
114+
}`,
115+
),
114116
);
115117
}
116118

@@ -127,7 +129,7 @@ export const createClient = ({
127129
}
128130

129131
return Promise.reject(
130-
new Error(`Network Error.\n Details: ${error}`)
132+
new Error(`Network Error.\n Details: ${error}`),
131133
);
132134
}
133135
},
@@ -138,7 +140,7 @@ export const createClient = ({
138140
console.log(`Waiting for retry (${num}/${MAX_RETRY_COUNT})`);
139141
},
140142
minTimeout: MIN_TIMEOUT_MS,
141-
}
143+
},
142144
);
143145
};
144146

@@ -218,6 +220,63 @@ export const createClient = ({
218220
});
219221
};
220222

223+
const getAllContentIds = async ({
224+
endpoint,
225+
target,
226+
draftKey,
227+
filters,
228+
orders,
229+
customRequestInit,
230+
}: GetAllContentIdsRequest): Promise<string[]> => {
231+
const limit = 100;
232+
const defaultQueries: MicroCMSQueries = {
233+
draftKey,
234+
filters,
235+
orders,
236+
limit,
237+
fields: target ?? 'id',
238+
};
239+
240+
const { totalCount } = await makeRequest({
241+
endpoint,
242+
queries: { ...defaultQueries, limit: 0 },
243+
requestInit: customRequestInit,
244+
});
245+
246+
let contentIds: string[] = [];
247+
let offset = 0;
248+
249+
const sleep = (ms: number) =>
250+
new Promise((resolve) => setTimeout(resolve, ms));
251+
const isStringArray = (arr: unknown[]): arr is string[] =>
252+
arr.every((item) => typeof item === 'string');
253+
254+
while (contentIds.length < totalCount) {
255+
const { contents } = (await makeRequest({
256+
endpoint,
257+
queries: { ...defaultQueries, offset },
258+
requestInit: customRequestInit,
259+
})) as MicroCMSListResponse<Record<string, unknown>>;
260+
261+
const ids = contents.map((content) => content[target ?? 'id']);
262+
263+
if (!isStringArray(ids)) {
264+
throw new Error(
265+
'The value of the field specified by `target` is not a string.',
266+
);
267+
}
268+
269+
contentIds = [...contentIds, ...ids];
270+
271+
offset += limit;
272+
if (contentIds.length < totalCount) {
273+
await sleep(1000); // sleep for 1 second before the next request
274+
}
275+
}
276+
277+
return contentIds;
278+
};
279+
221280
/**
222281
* Create new content in the microCMS list API data
223282
*/
@@ -310,6 +369,7 @@ export const createClient = ({
310369
getList,
311370
getListDetail,
312371
getObject,
372+
getAllContentIds,
313373
create,
314374
update,
315375
delete: _delete,

src/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,20 @@ export interface GetObjectRequest {
115115
customRequestInit?: CustomRequestInit;
116116
}
117117

118+
export interface GetAllContentIdsRequest {
119+
endpoint: string;
120+
/**
121+
* @type {string} target
122+
* @example 'url'
123+
* If you are using a URL other than the content ID, for example, you can specify that value in the `target` field.
124+
*/
125+
target?: string;
126+
draftKey?: string;
127+
filters?: string;
128+
orders?: string;
129+
customRequestInit?: CustomRequestInit;
130+
}
131+
118132
export interface WriteApiRequestResult {
119133
id: string;
120134
}

tests/getAllContentIds.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { rest } from 'msw';
2+
import { createClient } from '../src/createClient';
3+
import { testBaseUrl } from './mocks/handlers';
4+
import { server } from './mocks/server';
5+
6+
const client = createClient({
7+
serviceDomain: 'serviceDomain',
8+
apiKey: 'apiKey',
9+
});
10+
11+
describe('getAllContentIds', () => {
12+
afterEach(() => {
13+
jest.resetAllMocks();
14+
});
15+
16+
test('should fetch all content ids', async () => {
17+
server.use(
18+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
19+
return res.once(
20+
ctx.status(200),
21+
ctx.json({
22+
totalCount: 100,
23+
}),
24+
);
25+
}),
26+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
27+
return res.once(
28+
ctx.status(200),
29+
ctx.json({
30+
contents: Array(100)
31+
.fill(null)
32+
.map((_, index) => ({ id: `id${index}` })),
33+
}),
34+
);
35+
}),
36+
);
37+
38+
const result = await client.getAllContentIds({
39+
endpoint: 'getAllContentIds-list-type',
40+
});
41+
42+
expect(result).toHaveLength(100);
43+
expect(result).toContain('id0');
44+
expect(result).toContain('id99');
45+
});
46+
47+
test('should handle pagination and fetch more than limit', async () => {
48+
server.use(
49+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
50+
return res.once(
51+
ctx.status(200),
52+
ctx.json({
53+
totalCount: 250,
54+
}),
55+
);
56+
}),
57+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
58+
return res.once(
59+
ctx.status(200),
60+
ctx.json({
61+
contents: Array(100)
62+
.fill(null)
63+
.map((_, index) => ({ id: `id${index}` })),
64+
}),
65+
);
66+
}),
67+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
68+
return res.once(
69+
ctx.status(200),
70+
ctx.json({
71+
contents: Array(100)
72+
.fill(null)
73+
.map((_, index) => ({ id: `id${index + 100}` })),
74+
}),
75+
);
76+
}),
77+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
78+
return res.once(
79+
ctx.status(200),
80+
ctx.json({
81+
contents: Array(50)
82+
.fill(null)
83+
.map((_, index) => ({ id: `id${index + 200}` })),
84+
}),
85+
);
86+
}),
87+
);
88+
89+
const result = await client.getAllContentIds({
90+
endpoint: 'getAllContentIds-list-type',
91+
});
92+
93+
expect(result).toHaveLength(250);
94+
expect(result).toContain('id0');
95+
expect(result).toContain('id249');
96+
});
97+
98+
test('should fetch all content ids with target field', async () => {
99+
server.use(
100+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
101+
return res.once(
102+
ctx.status(200),
103+
ctx.json({
104+
totalCount: 100,
105+
}),
106+
);
107+
}),
108+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
109+
return res.once(
110+
ctx.status(200),
111+
ctx.json({
112+
contents: Array(100)
113+
.fill(null)
114+
.map((_, index) => ({ url: `id${index}` })),
115+
}),
116+
);
117+
}),
118+
);
119+
120+
const result = await client.getAllContentIds({
121+
endpoint: 'getAllContentIds-list-type',
122+
target: 'url',
123+
});
124+
125+
expect(result).toHaveLength(100);
126+
expect(result).toContain('id0');
127+
expect(result).toContain('id99');
128+
});
129+
130+
test('should throw error when target field is not string', async () => {
131+
server.use(
132+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
133+
return res.once(
134+
ctx.status(200),
135+
ctx.json({
136+
totalCount: 100,
137+
}),
138+
);
139+
}),
140+
rest.get(`${testBaseUrl}/getAllContentIds-list-type`, (_, res, ctx) => {
141+
return res.once(
142+
ctx.status(200),
143+
ctx.json({
144+
contents: Array(100)
145+
.fill(null)
146+
.map(() => ({ image: { url: 'url', width: 100, height: 100 } })),
147+
}),
148+
);
149+
}),
150+
);
151+
152+
await expect(
153+
client.getAllContentIds({
154+
endpoint: 'getAllContentIds-list-type',
155+
target: 'image',
156+
}),
157+
).rejects.toThrowError(
158+
'The value of the field specified by `target` is not a string.',
159+
);
160+
});
161+
});

0 commit comments

Comments
 (0)