Skip to content

Commit 4d757bb

Browse files
authored
feat(entry-handlers): add support for bulk publishing and unpublishing from the default publish/unpublish tools instead of the handlers
2 parents 48a16c9 + 561de7c commit 4d757bb

File tree

4 files changed

+363
-236
lines changed

4 files changed

+363
-236
lines changed

src/handlers/entry-handlers.ts

Lines changed: 259 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,34 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { getContentfulClient } from "../config/client.js"
33
import { summarizeData } from "../utils/summarizer.js"
4-
import { CreateEntryProps, EntryProps, QueryOptions } from "contentful-management"
4+
import {
5+
CreateEntryProps,
6+
EntryProps,
7+
QueryOptions,
8+
BulkActionProps,
9+
Link,
10+
Collection,
11+
} from "contentful-management"
12+
13+
// Define the interface for bulk action responses with succeeded property
14+
interface BulkActionResponse extends BulkActionProps<any> {
15+
succeeded?: Array<{
16+
sys: {
17+
id: string
18+
type: string
19+
}
20+
}>
21+
}
22+
23+
// Define the interface for versioned links
24+
interface VersionedLink {
25+
sys: {
26+
type: "Link"
27+
linkType: "Entry" | "Asset"
28+
id: string
29+
version: number
30+
}
31+
}
532

633
export const entryHandlers = {
734
searchEntries: async (args: { spaceId: string; environmentId: string; query: QueryOptions }) => {
@@ -19,13 +46,13 @@ export const entryHandlers = {
1946
query: {
2047
...args.query,
2148
limit: Math.min(args.query.limit || 3, 3),
22-
skip: args.query.skip || 0
49+
skip: args.query.skip || 0,
2350
},
2451
})
2552

2653
const summarized = summarizeData(entries, {
2754
maxItems: 3,
28-
remainingMessage: "To see more entries, please ask me to retrieve the next page."
55+
remainingMessage: "To see more entries, please ask me to retrieve the next page.",
2956
})
3057

3158
return {
@@ -121,35 +148,128 @@ export const entryHandlers = {
121148
}
122149
},
123150

124-
publishEntry: async (args: {
125-
spaceId: string;
126-
environmentId: string;
127-
entryId: string | string[]
151+
publishEntry: async (args: {
152+
spaceId: string
153+
environmentId: string
154+
entryId: string | string[]
128155
}) => {
129156
const spaceId = process.env.SPACE_ID || args.spaceId
130157
const environmentId = process.env.ENVIRONMENT_ID || args.environmentId
131158

132-
// If entryId is an array, use bulkPublish instead
133-
if (Array.isArray(args.entryId)) {
134-
const bulkActionHandlers = await import("./bulk-action-handlers.js").then(module => module.bulkActionHandlers)
135-
136-
// Map entry IDs to the expected format for bulkPublish
137-
const entities = args.entryId.map(id => ({
138-
sys: { id, type: "Entry" as const }
139-
}))
140-
141-
return bulkActionHandlers.bulkPublish({
142-
spaceId,
143-
environmentId,
144-
entities
145-
})
146-
}
147-
159+
// Handle case where entryId is a JSON string representation of an array
160+
let entryId = args.entryId
161+
if (typeof entryId === "string" && entryId.startsWith("[") && entryId.endsWith("]")) {
162+
try {
163+
entryId = JSON.parse(entryId)
164+
} catch (e) {
165+
// If parsing fails, keep it as string
166+
console.error("Failed to parse entryId as JSON array:", e)
167+
}
168+
}
169+
170+
// If entryId is an array, handle bulk publishing
171+
if (Array.isArray(entryId)) {
172+
try {
173+
const contentfulClient = await getContentfulClient()
174+
175+
// Get the current version of each entity
176+
const entryVersions = await Promise.all(
177+
entryId.map(async (id) => {
178+
try {
179+
// Get the current version of the entry
180+
const currentEntry = await contentfulClient.entry.get({
181+
spaceId,
182+
environmentId,
183+
entryId: id,
184+
})
185+
186+
// Create a versioned link according to the API docs
187+
const versionedLink: VersionedLink = {
188+
sys: {
189+
type: "Link",
190+
linkType: "Entry",
191+
id: id,
192+
version: currentEntry.sys.version,
193+
},
194+
}
195+
return versionedLink
196+
} catch (error) {
197+
console.error(`Error fetching entry ${id}: ${error}`)
198+
throw new Error(
199+
`Failed to get version for entry ${id}. All entries must have a version.`,
200+
)
201+
}
202+
}),
203+
)
204+
205+
// Create the correct entities format according to Contentful API docs
206+
const entities: {
207+
sys: { type: "Array" }
208+
items: VersionedLink[]
209+
} = {
210+
sys: {
211+
type: "Array",
212+
},
213+
items: entryVersions,
214+
}
215+
216+
// Create the bulk action
217+
const bulkAction = await contentfulClient.bulkAction.publish(
218+
{
219+
spaceId,
220+
environmentId,
221+
},
222+
{
223+
entities,
224+
},
225+
)
226+
227+
// Wait for the bulk action to complete
228+
let action = (await contentfulClient.bulkAction.get({
229+
spaceId,
230+
environmentId,
231+
bulkActionId: bulkAction.sys.id,
232+
})) as BulkActionResponse // Cast to our extended interface
233+
234+
while (action.sys.status === "inProgress" || action.sys.status === "created") {
235+
await new Promise((resolve) => setTimeout(resolve, 1000))
236+
action = (await contentfulClient.bulkAction.get({
237+
spaceId,
238+
environmentId,
239+
bulkActionId: bulkAction.sys.id,
240+
})) as BulkActionResponse // Cast to our extended interface
241+
}
242+
243+
return {
244+
content: [
245+
{
246+
type: "text",
247+
text: `Bulk publish completed with status: ${action.sys.status}. ${
248+
action.sys.status === "failed"
249+
? `Error: ${JSON.stringify(action.error)}`
250+
: `Successfully processed items.`
251+
}`,
252+
},
253+
],
254+
}
255+
} catch (error) {
256+
return {
257+
content: [
258+
{
259+
type: "text",
260+
text: `Error during bulk publish: ${error instanceof Error ? error.message : String(error)}`,
261+
},
262+
],
263+
isError: true,
264+
}
265+
}
266+
}
267+
148268
// Handle single entry publishing
149269
const params = {
150270
spaceId,
151271
environmentId,
152-
entryId: args.entryId,
272+
entryId: entryId as string,
153273
}
154274

155275
const contentfulClient = await getContentfulClient()
@@ -159,41 +279,134 @@ export const entryHandlers = {
159279
sys: currentEntry.sys,
160280
fields: currentEntry.fields,
161281
})
162-
282+
163283
return {
164284
content: [{ type: "text", text: JSON.stringify(entry, null, 2) }],
165285
}
166286
},
167287

168-
unpublishEntry: async (args: {
169-
spaceId: string;
170-
environmentId: string;
171-
entryId: string | string[]
288+
unpublishEntry: async (args: {
289+
spaceId: string
290+
environmentId: string
291+
entryId: string | string[]
172292
}) => {
173293
const spaceId = process.env.SPACE_ID || args.spaceId
174294
const environmentId = process.env.ENVIRONMENT_ID || args.environmentId
175295

176-
// If entryId is an array, use bulkUnpublish instead
177-
if (Array.isArray(args.entryId)) {
178-
const bulkActionHandlers = await import("./bulk-action-handlers.js").then(module => module.bulkActionHandlers)
179-
180-
// Map entry IDs to the expected format for bulkUnpublish
181-
const entities = args.entryId.map(id => ({
182-
sys: { id, type: "Entry" as const }
183-
}))
184-
185-
return bulkActionHandlers.bulkUnpublish({
186-
spaceId,
187-
environmentId,
188-
entities
189-
})
190-
}
191-
296+
// Handle case where entryId is a JSON string representation of an array
297+
let entryId = args.entryId
298+
if (typeof entryId === "string" && entryId.startsWith("[") && entryId.endsWith("]")) {
299+
try {
300+
entryId = JSON.parse(entryId)
301+
} catch (e) {
302+
// If parsing fails, keep it as string
303+
console.error("Failed to parse entryId as JSON array:", e)
304+
}
305+
}
306+
307+
// If entryId is an array, handle bulk unpublishing
308+
if (Array.isArray(entryId)) {
309+
try {
310+
const contentfulClient = await getContentfulClient()
311+
312+
// Get the current version of each entity
313+
const entryVersions = await Promise.all(
314+
entryId.map(async (id) => {
315+
try {
316+
// Get the current version of the entry
317+
const currentEntry = await contentfulClient.entry.get({
318+
spaceId,
319+
environmentId,
320+
entryId: id,
321+
})
322+
323+
// Create a versioned link according to the API docs
324+
const versionedLink: VersionedLink = {
325+
sys: {
326+
type: "Link",
327+
linkType: "Entry",
328+
id: id,
329+
version: currentEntry.sys.version,
330+
},
331+
}
332+
return versionedLink
333+
} catch (error) {
334+
console.error(`Error fetching entry ${id}: ${error}`)
335+
throw new Error(
336+
`Failed to get version for entry ${id}. All entries must have a version.`,
337+
)
338+
}
339+
}),
340+
)
341+
342+
// Create the correct entities format according to Contentful API docs
343+
const entities: {
344+
sys: { type: "Array" }
345+
items: VersionedLink[]
346+
} = {
347+
sys: {
348+
type: "Array",
349+
},
350+
items: entryVersions,
351+
}
352+
353+
// Create the bulk action
354+
const bulkAction = await contentfulClient.bulkAction.unpublish(
355+
{
356+
spaceId,
357+
environmentId,
358+
},
359+
{
360+
entities,
361+
},
362+
)
363+
364+
// Wait for the bulk action to complete
365+
let action = (await contentfulClient.bulkAction.get({
366+
spaceId,
367+
environmentId,
368+
bulkActionId: bulkAction.sys.id,
369+
})) as BulkActionResponse // Cast to our extended interface
370+
371+
while (action.sys.status === "inProgress" || action.sys.status === "created") {
372+
await new Promise((resolve) => setTimeout(resolve, 1000))
373+
action = (await contentfulClient.bulkAction.get({
374+
spaceId,
375+
environmentId,
376+
bulkActionId: bulkAction.sys.id,
377+
})) as BulkActionResponse // Cast to our extended interface
378+
}
379+
380+
return {
381+
content: [
382+
{
383+
type: "text",
384+
text: `Bulk unpublish completed with status: ${action.sys.status}. ${
385+
action.sys.status === "failed"
386+
? `Error: ${JSON.stringify(action.error)}`
387+
: `Successfully processed ${action.succeeded?.length || 0} items.`
388+
}`,
389+
},
390+
],
391+
}
392+
} catch (error) {
393+
return {
394+
content: [
395+
{
396+
type: "text",
397+
text: `Error during bulk unpublish: ${error instanceof Error ? error.message : String(error)}`,
398+
},
399+
],
400+
isError: true,
401+
}
402+
}
403+
}
404+
192405
// Handle single entry unpublishing
193406
const params = {
194407
spaceId,
195408
environmentId,
196-
entryId: args.entryId,
409+
entryId: entryId as string,
197410
}
198411

199412
const contentfulClient = await getContentfulClient()

0 commit comments

Comments
 (0)