// The `createAndAttach` action every Festipod collection handler uses (attendees, contacts), in place // of the one `PodCollectionsHandlerMixin` (@activitypods/app 2.3.2) gives them. // // The mixin's version calls `pod-collections.createAndAttach`, then derives a WAC group slug from the // collection URI it returns. That call returns undefined when it attaches nothing — the resource // already carries a value for the predicate (written by its creator or by another app), or the pod // refused to create the collection — and the mixin then runs `new URL(undefined)`, which throws. The // webhook call that runs the watcher is not awaited by the platform, so that throw ended the whole // backend process (observed 2026-10-12, an event POSTed with `apods:attendees` already set). Here, // nothing attached means no group to create. /** The action definition; runs bound to a service with the mixin's settings and `getGroupSlugFromCollectionUri`. */ const guardedCreateAndAttach = { async handler(ctx) { const { resourceUri, actorUri } = ctx.params; const { attachPredicate, collectionOptions, createWacGroup } = this.settings; const collectionUri = await ctx.call('pod-collections.createAndAttach', { resourceUri, attachPredicate, collectionOptions, actorUri }); if (!collectionUri) { this.logger.info(`No ${attachPredicate} collection attached to ${resourceUri} (already carries one, or creation refused)`); return undefined; } // The service's own follow-up for a collection it has just created (attendees: record it as // Festipod's, see public-read-ledger.js) — right after the attach, so a failure of what follows // (the WAC group) cannot skip it. if (typeof this.onCollectionAttached === 'function') { await this.onCollectionAttached(ctx, { resourceUri, collectionUri, actorUri }); } if (createWacGroup) { await ctx.call('pod-wac-groups.create', { groupSlug: this.getGroupSlugFromCollectionUri(collectionUri), actorUri }); } return collectionUri; } }; module.exports = { guardedCreateAndAttach };