@aglyn/plugins-events-calendar 1.0.0-beta.284 → 1.0.0-beta.287

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-events-calendar",
3
- "version": "1.0.0-beta.284",
3
+ "version": "1.0.0-beta.287",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,14 +25,14 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.284",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.284",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.284",
31
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.284",
32
- "@aglyn/shared-util-timestamp": "1.0.0-beta.284",
33
- "@aglyn/tenant-data-admin": "1.0.0-beta.284",
34
- "@aglyn/tenant-feature-instance": "1.0.0-beta.284",
35
- "@aglyn/tenant-runtime": "1.0.0-beta.284",
28
+ "@aglyn/aglyn": "1.0.0-beta.287",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.287",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.287",
31
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.287",
32
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.287",
33
+ "@aglyn/tenant-data-admin": "1.0.0-beta.287",
34
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.287",
35
+ "@aglyn/tenant-runtime": "1.0.0-beta.287",
36
36
  "@swc/helpers": "0.5.23"
37
37
  },
38
38
  "peerDependencies": {
@@ -52,7 +52,8 @@ import { type StockPhotoPicker } from '@aglyn/aglyn/plugin-manager/stock-photo-p
52
52
  */
53
53
  type Firestore = FirebaseFirestore.Firestore;
54
54
  /** The refusal for a member who may not write the site. */
55
- export declare const EVENT_DRAFT_ROLE_REFUSAL = "Editing requires the editor role";
55
+ /** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */
56
+ export declare const EVENT_DRAFT_ROLE_REFUSAL: string;
56
57
  /** The refusal for a workspace without the add-on, in the Events page's words. */
57
58
  export declare const EVENT_DRAFT_PLAN_REFUSAL = "The Event Calendar is a paid add-on: enable it from Billing \u2192 Add-ons";
58
59
  /** Why an event a member wrote was kept as a draft: they may write the site, not publish it. */
@@ -14,13 +14,13 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { hostRoleCanPublish, hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
18
- import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
17
+ */ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
18
+ import { holdsSiteKey, siteKeyRefusal } from "@aglyn/aglyn/app-utils/host-permissions";
19
19
  import { stockPhotoPicker } from "@aglyn/aglyn/plugin-manager/stock-photo-provider";
20
20
  import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
21
21
  import { checkEventDraftContent, EVENT_DRAFT_COVER_ADDRESS, eventDraftWallText, readEventDraftContent } from "../model/event-draft-content.js";
22
22
  import { EVENT_COVER_ALT_MAX_LENGTH, eventWrite } from "../model/event-write.js";
23
- /** The refusal for a member who may not write the site. */ export const EVENT_DRAFT_ROLE_REFUSAL = 'Editing requires the editor role';
23
+ /** The refusal for a member who may not write the site. */ /** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */ export const EVENT_DRAFT_ROLE_REFUSAL = siteKeyRefusal('site.edit');
24
24
  /** The refusal for a workspace without the add-on, in the Events page's words. */ export const EVENT_DRAFT_PLAN_REFUSAL = 'The Event Calendar is a paid add-on: enable it from Billing → Add-ons';
25
25
  /** Why an event a member wrote was kept as a draft: they may write the site, not publish it. */ export const EVENT_DRAFT_UNPUBLISHED_NOTICE = 'Kept as a draft: publishing an event takes the editor role, so someone who has it publishes it on the Events page.';
26
26
  /** The refusal for a start that is not after the write. */ export function eventDraftPastRefusal(wallText) {
@@ -55,9 +55,7 @@ function recordOf(event) {
55
55
  }, liveOf(published));
56
56
  }
57
57
  function roleRefusal(host, uid) {
58
- var _host_get;
59
- const role = ((_host_get = host.get('memberRoles')) != null ? _host_get : {})[uid];
60
- return hostRoleCanWrite(role) ? null : {
58
+ return holdsSiteKey(host, uid, 'site.edit') ? null : {
61
59
  status: 403,
62
60
  error: EVENT_DRAFT_ROLE_REFUSAL
63
61
  };
@@ -161,7 +159,7 @@ export function createEventDraftWriter(deps = {}) {
161
159
  }
162
160
  }
163
161
  return db.runTransaction(async (tx)=>{
164
- var _roleRefusal, _host_get;
162
+ var _roleRefusal;
165
163
  const [host, existing] = await Promise.all([
166
164
  tx.get(hostRef),
167
165
  tx.get(eventRef)
@@ -188,7 +186,8 @@ export function createEventDraftWriter(deps = {}) {
188
186
  ok: false
189
187
  }, early);
190
188
  // The Events page's publish: `status`, and only for the publishing role.
191
- const published = hostRoleCanPublish(((_host_get = host.get('memberRoles')) != null ? _host_get : {})[request.uid]);
189
+ // "Publish site content" (`site.publish`, AGL-4047) on this site.
190
+ const published = holdsSiteKey(host, request.uid, 'site.publish');
192
191
  const write = eventWrite(_extends({}, read.value, {
193
192
  status: published ? 'published' : 'draft'
194
193
  }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/events-calendar/src/lib/server/event-drafts.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { hostRoleCanPublish, hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type {\n PluginDraftContext,\n PluginDraftRecord,\n PluginDraftRefusal,\n PluginDraftRequest,\n PluginDraftWrite,\n PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n stockPhotoPicker,\n type StockPhotoPicker,\n} from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport {\n checkEventDraftContent,\n EVENT_DRAFT_COVER_ADDRESS,\n eventDraftWallText,\n readEventDraftContent,\n} from '../model/event-draft-content'\nimport { EVENT_COVER_ALT_MAX_LENGTH, eventWrite } from '../model/event-write'\n\n/**\n * AN EVENT ANOTHER PLUGIN ASKS FOR (AGL-3743).\n *\n * The draft writer this plugin registers for the `event` resource on the\n * core's resource-drafts seam. A plugin that sets a site up from a brief —\n * an AI build — asks for the writer by name and gets this plugin's rules:\n *\n * - THE DOCUMENT is a `hosts/{hostId}/events/{id}` event stored through\n * `eventWrite`, the one rule the Events page's editor and the events\n * import write through.\n * - PUBLISHED when the member may publish (AGL-3755): the Events page's\n * \"Set published\" is the event's `status` set to `published` and nothing\n * else, so that is all this writer changes. The member must hold the\n * publishing role (`hostRoleCanPublish`, admin or editor), the axis every\n * other publish asks. A member who may write but not publish (`author`)\n * gets the event as a draft, `live: false`, with a notice saying why, and\n * it waits on the Events page for someone who may.\n * - THE PLAN is the `eventCalendar` add-on the Events page is gated on, read\n * from the workspace's own document the way the listing and the import\n * read it. There is no allowance to count.\n * - THE ROLE is a member who may write the site's content.\n * - THE START is after the write. A draft that would open on the Events page\n * already over is a mistake in what was asked, so it is refused rather\n * than stored.\n * - THE COVER (AGL-3755), when the content names none: a photo of the\n * event's title from the deployment's stock photo picker\n * (`core.stock-photo-picker`), kept in the site's own media library once\n * and reused after, or a starter photo when the library is down. It is\n * the editor's own \"Cover image URL\" field, `coverImage`, holding the\n * photo's site-relative address. Photos of one act (the AI build) never\n * repeat. A process with no picker writes the event without a cover.\n *\n * The writer touches the one new document.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The refusal for a member who may not write the site. */\nexport const EVENT_DRAFT_ROLE_REFUSAL = 'Editing requires the editor role'\n\n/** The refusal for a workspace without the add-on, in the Events page's words. */\nexport const EVENT_DRAFT_PLAN_REFUSAL =\n 'The Event Calendar is a paid add-on: enable it from Billing → Add-ons'\n\n/** Why an event a member wrote was kept as a draft: they may write the site, not publish it. */\nexport const EVENT_DRAFT_UNPUBLISHED_NOTICE =\n 'Kept as a draft: publishing an event takes the editor role, so someone who has it publishes it on the Events page.'\n\n/** The refusal for a start that is not after the write. */\nexport function eventDraftPastRefusal(wallText: string): string {\n return `A drafted event starts in the future: ${wallText} has already passed`\n}\n\nfunction isDeleted(data: Readonly<Record<string, unknown>> | undefined): boolean {\n return Boolean(data?.['deletedAt']) || data?.['status'] === 'deleted'\n}\n\n/** What a written event reports: published or a draft, and why a draft when it is one. */\nfunction liveOf(published: boolean): Pick<PluginDraftRecord, 'live' | 'notice'> {\n return published ? { live: true } : { live: false, notice: EVENT_DRAFT_UNPUBLISHED_NOTICE }\n}\n\nfunction recordOf(event: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n const data = (event.data() ?? {}) as Record<string, unknown>\n const published = data['status'] === 'published'\n return {\n id: event.id,\n name: String(data['title'] ?? ''),\n versionId: null,\n facts: {\n status: published ? 'published' : 'draft',\n startsAtMs: Number(data['startsAtMs'] ?? 0),\n endsAtMs: Number(data['endsAtMs'] ?? 0),\n ...(typeof data['coverImage'] === 'string' ? { coverImage: data['coverImage'] } : {}),\n },\n ...liveOf(published),\n }\n}\n\nfunction roleRefusal(host: FirebaseFirestore.DocumentSnapshot, uid: string): PluginDraftRefusal | null {\n const role = (host.get('memberRoles') ?? {})[uid]\n return hostRoleCanWrite(role) ? null : { status: 403, error: EVENT_DRAFT_ROLE_REFUSAL }\n}\n\nfunction planRefusal(org: PluginDraftContext['org']): PluginDraftRefusal | null {\n return checkEntitlement(org as never, 'eventCalendar')\n ? null\n : { status: 403, error: EVENT_DRAFT_PLAN_REFUSAL }\n}\n\nexport interface EventDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n /** The stock photo picker a cover comes from; specs hand in a double. */\n stockPhotos?: () => StockPhotoPicker | null\n}\n\n/**\n * A cover for an event the content gave none: the picker's, held to what the\n * editor's field takes, with the words that describe it (AGL-3856): a cover\n * the event list drew with `alt=\"\"` hid a yoga pose under \"Open gym strength\n * session\" from a screen reader.\n */\nasync function coverFor(\n picker: StockPhotoPicker | null,\n request: PluginDraftRequest,\n title: string,\n): Promise<{ src: string; alt: string | null } | null> {\n if (!picker) return null\n try {\n const pick = await picker.pick({\n hostId: request.hostId,\n uid: request.uid,\n subject: title,\n ...(request.act?.business ? { business: request.act.business } : {}),\n orientation: 'horizontal',\n ...(request.act?.id ? { scope: request.act.id } : {}),\n // The draft's id: asked again, the same photo.\n seed: request.id,\n })\n if (!pick || !EVENT_DRAFT_COVER_ADDRESS.test(pick.url)) return null\n const alt = pick.alt?.replace(/\\s+/g, ' ').trim().slice(0, EVENT_COVER_ALT_MAX_LENGTH) ?? ''\n return { src: pick.url, alt: alt || null }\n } catch (error) {\n console.warn('event draft: no cover photo', { error: String(error) })\n return null\n }\n}\n\nexport function createEventDraftWriter(deps: EventDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const stockPhotos = deps.stockPhotos ?? stockPhotoPicker\n return {\n refusal: async (context) => {\n const host = await firestore().collection('hosts').doc(context.hostId).get()\n if (!host.exists) return { status: 404, error: 'Unknown site' }\n return roleRefusal(host, context.uid) ?? planRefusal(context.org)\n },\n\n check: (content) => checkEventDraftContent(content),\n\n read: async ({ hostId, id }) => {\n const event = await firestore().collection('hosts').doc(hostId).collection('events').doc(id).get()\n return event.exists && !isDeleted(event.data()) ? recordOf(event) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n // The request's name is the one asked for; the content's stands in.\n const asked = request.name.replace(/[\\p{Cc}\\s]+/gu, ' ').trim()\n const read = readEventDraftContent(asked ? { ...request.content, title: asked } : request.content)\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n if (read.value.startsAtMs <= request.now.getTime()) {\n return {\n ok: false,\n status: 400,\n error: eventDraftPastRefusal(eventDraftWallText(read.value.startsAtMs, read.timezone)),\n }\n }\n const db = firestore()\n const hostRef = db.collection('hosts').doc(request.hostId)\n const eventRef = hostRef.collection('events').doc(request.id)\n // A cover is fetched only for a new event a member may write, before\n // the transaction: a photo's copy is too slow to hold one open.\n if (!read.value.coverImage) {\n const [host, existing] = await Promise.all([hostRef.get(), eventRef.get()])\n if (host.exists && !existing.exists && !roleRefusal(host, request.uid) && !planRefusal(request.org)) {\n const cover = await coverFor(stockPhotos(), request, read.value.title)\n if (cover) {\n read.value.coverImage = cover.src\n if (cover.alt && !read.value.coverImageAlt) read.value.coverImageAlt = cover.alt\n }\n }\n }\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n const [host, existing] = await Promise.all([tx.get(hostRef), tx.get(eventRef)])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n // Asked again under the same id: the draft it already wrote — unless\n // a person deleted it since, which a replay never brings back.\n return isDeleted(existing.data())\n ? { ok: false, status: 409, error: 'That event was deleted on the Events page' }\n : { ok: true, replayed: true, ...recordOf(existing) }\n }\n const early = roleRefusal(host, request.uid) ?? planRefusal(request.org)\n if (early) return { ok: false, ...early }\n // The Events page's publish: `status`, and only for the publishing role.\n const published = hostRoleCanPublish((host.get('memberRoles') ?? {})[request.uid])\n const write = eventWrite({ ...read.value, status: published ? 'published' : 'draft' })\n tx.create(eventRef, {\n ...write.fields,\n createdAt: request.now,\n updatedAt: request.now,\n createdBy: request.uid,\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name: write.fields.title,\n versionId: null,\n facts: {\n status: write.fields.status,\n startsAtMs: write.fields.startsAtMs,\n endsAtMs: write.fields.endsAtMs,\n ...(write.fields.coverImage ? { coverImage: write.fields.coverImage } : {}),\n },\n ...liveOf(published),\n }\n })\n },\n }\n}\n\nexport const eventDraftWriter = createEventDraftWriter()\n"],"names":["hostRoleCanPublish","hostRoleCanWrite","checkEntitlement","stockPhotoPicker","firebaseAdmin","checkEventDraftContent","EVENT_DRAFT_COVER_ADDRESS","eventDraftWallText","readEventDraftContent","EVENT_COVER_ALT_MAX_LENGTH","eventWrite","EVENT_DRAFT_ROLE_REFUSAL","EVENT_DRAFT_PLAN_REFUSAL","EVENT_DRAFT_UNPUBLISHED_NOTICE","eventDraftPastRefusal","wallText","isDeleted","data","Boolean","liveOf","published","live","notice","recordOf","event","id","name","String","versionId","facts","status","startsAtMs","Number","endsAtMs","coverImage","roleRefusal","host","uid","role","get","error","planRefusal","org","coverFor","picker","request","title","pick","hostId","subject","act","business","orientation","scope","seed","test","url","alt","replace","trim","slice","src","console","warn","createEventDraftWriter","deps","firestore","app","stockPhotos","refusal","context","collection","doc","exists","check","content","read","write","asked","ok","problems","value","now","getTime","timezone","db","hostRef","eventRef","existing","Promise","all","cover","coverImageAlt","runTransaction","tx","replayed","early","create","fields","createdAt","updatedAt","createdBy","eventDraftWriter"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/events-calendar/src/lib/server/event-drafts.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { holdsSiteKey, siteKeyRefusal } from '@aglyn/aglyn/app-utils/host-permissions'\nimport type {\n PluginDraftContext,\n PluginDraftRecord,\n PluginDraftRefusal,\n PluginDraftRequest,\n PluginDraftWrite,\n PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n stockPhotoPicker,\n type StockPhotoPicker,\n} from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport {\n checkEventDraftContent,\n EVENT_DRAFT_COVER_ADDRESS,\n eventDraftWallText,\n readEventDraftContent,\n} from '../model/event-draft-content'\nimport { EVENT_COVER_ALT_MAX_LENGTH, eventWrite } from '../model/event-write'\n\n/**\n * AN EVENT ANOTHER PLUGIN ASKS FOR (AGL-3743).\n *\n * The draft writer this plugin registers for the `event` resource on the\n * core's resource-drafts seam. A plugin that sets a site up from a brief —\n * an AI build — asks for the writer by name and gets this plugin's rules:\n *\n * - THE DOCUMENT is a `hosts/{hostId}/events/{id}` event stored through\n * `eventWrite`, the one rule the Events page's editor and the events\n * import write through.\n * - PUBLISHED when the member may publish (AGL-3755): the Events page's\n * \"Set published\" is the event's `status` set to `published` and nothing\n * else, so that is all this writer changes. The member must hold the\n * publishing role (`hostRoleCanPublish`, admin or editor), the axis every\n * other publish asks. A member who may write but not publish (`author`)\n * gets the event as a draft, `live: false`, with a notice saying why, and\n * it waits on the Events page for someone who may.\n * - THE PLAN is the `eventCalendar` add-on the Events page is gated on, read\n * from the workspace's own document the way the listing and the import\n * read it. There is no allowance to count.\n * - THE ROLE is a member who may write the site's content.\n * - THE START is after the write. A draft that would open on the Events page\n * already over is a mistake in what was asked, so it is refused rather\n * than stored.\n * - THE COVER (AGL-3755), when the content names none: a photo of the\n * event's title from the deployment's stock photo picker\n * (`core.stock-photo-picker`), kept in the site's own media library once\n * and reused after, or a starter photo when the library is down. It is\n * the editor's own \"Cover image URL\" field, `coverImage`, holding the\n * photo's site-relative address. Photos of one act (the AI build) never\n * repeat. A process with no picker writes the event without a cover.\n *\n * The writer touches the one new document.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The refusal for a member who may not write the site. */\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const EVENT_DRAFT_ROLE_REFUSAL = siteKeyRefusal('site.edit')\n\n/** The refusal for a workspace without the add-on, in the Events page's words. */\nexport const EVENT_DRAFT_PLAN_REFUSAL =\n 'The Event Calendar is a paid add-on: enable it from Billing → Add-ons'\n\n/** Why an event a member wrote was kept as a draft: they may write the site, not publish it. */\nexport const EVENT_DRAFT_UNPUBLISHED_NOTICE =\n 'Kept as a draft: publishing an event takes the editor role, so someone who has it publishes it on the Events page.'\n\n/** The refusal for a start that is not after the write. */\nexport function eventDraftPastRefusal(wallText: string): string {\n return `A drafted event starts in the future: ${wallText} has already passed`\n}\n\nfunction isDeleted(data: Readonly<Record<string, unknown>> | undefined): boolean {\n return Boolean(data?.['deletedAt']) || data?.['status'] === 'deleted'\n}\n\n/** What a written event reports: published or a draft, and why a draft when it is one. */\nfunction liveOf(published: boolean): Pick<PluginDraftRecord, 'live' | 'notice'> {\n return published ? { live: true } : { live: false, notice: EVENT_DRAFT_UNPUBLISHED_NOTICE }\n}\n\nfunction recordOf(event: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n const data = (event.data() ?? {}) as Record<string, unknown>\n const published = data['status'] === 'published'\n return {\n id: event.id,\n name: String(data['title'] ?? ''),\n versionId: null,\n facts: {\n status: published ? 'published' : 'draft',\n startsAtMs: Number(data['startsAtMs'] ?? 0),\n endsAtMs: Number(data['endsAtMs'] ?? 0),\n ...(typeof data['coverImage'] === 'string' ? { coverImage: data['coverImage'] } : {}),\n },\n ...liveOf(published),\n }\n}\n\nfunction roleRefusal(host: FirebaseFirestore.DocumentSnapshot, uid: string): PluginDraftRefusal | null {\n return holdsSiteKey(host, uid, 'site.edit') ? null : { status: 403, error: EVENT_DRAFT_ROLE_REFUSAL }\n}\n\nfunction planRefusal(org: PluginDraftContext['org']): PluginDraftRefusal | null {\n return checkEntitlement(org as never, 'eventCalendar')\n ? null\n : { status: 403, error: EVENT_DRAFT_PLAN_REFUSAL }\n}\n\nexport interface EventDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n /** The stock photo picker a cover comes from; specs hand in a double. */\n stockPhotos?: () => StockPhotoPicker | null\n}\n\n/**\n * A cover for an event the content gave none: the picker's, held to what the\n * editor's field takes, with the words that describe it (AGL-3856): a cover\n * the event list drew with `alt=\"\"` hid a yoga pose under \"Open gym strength\n * session\" from a screen reader.\n */\nasync function coverFor(\n picker: StockPhotoPicker | null,\n request: PluginDraftRequest,\n title: string,\n): Promise<{ src: string; alt: string | null } | null> {\n if (!picker) return null\n try {\n const pick = await picker.pick({\n hostId: request.hostId,\n uid: request.uid,\n subject: title,\n ...(request.act?.business ? { business: request.act.business } : {}),\n orientation: 'horizontal',\n ...(request.act?.id ? { scope: request.act.id } : {}),\n // The draft's id: asked again, the same photo.\n seed: request.id,\n })\n if (!pick || !EVENT_DRAFT_COVER_ADDRESS.test(pick.url)) return null\n const alt = pick.alt?.replace(/\\s+/g, ' ').trim().slice(0, EVENT_COVER_ALT_MAX_LENGTH) ?? ''\n return { src: pick.url, alt: alt || null }\n } catch (error) {\n console.warn('event draft: no cover photo', { error: String(error) })\n return null\n }\n}\n\nexport function createEventDraftWriter(deps: EventDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const stockPhotos = deps.stockPhotos ?? stockPhotoPicker\n return {\n refusal: async (context) => {\n const host = await firestore().collection('hosts').doc(context.hostId).get()\n if (!host.exists) return { status: 404, error: 'Unknown site' }\n return roleRefusal(host, context.uid) ?? planRefusal(context.org)\n },\n\n check: (content) => checkEventDraftContent(content),\n\n read: async ({ hostId, id }) => {\n const event = await firestore().collection('hosts').doc(hostId).collection('events').doc(id).get()\n return event.exists && !isDeleted(event.data()) ? recordOf(event) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n // The request's name is the one asked for; the content's stands in.\n const asked = request.name.replace(/[\\p{Cc}\\s]+/gu, ' ').trim()\n const read = readEventDraftContent(asked ? { ...request.content, title: asked } : request.content)\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n if (read.value.startsAtMs <= request.now.getTime()) {\n return {\n ok: false,\n status: 400,\n error: eventDraftPastRefusal(eventDraftWallText(read.value.startsAtMs, read.timezone)),\n }\n }\n const db = firestore()\n const hostRef = db.collection('hosts').doc(request.hostId)\n const eventRef = hostRef.collection('events').doc(request.id)\n // A cover is fetched only for a new event a member may write, before\n // the transaction: a photo's copy is too slow to hold one open.\n if (!read.value.coverImage) {\n const [host, existing] = await Promise.all([hostRef.get(), eventRef.get()])\n if (host.exists && !existing.exists && !roleRefusal(host, request.uid) && !planRefusal(request.org)) {\n const cover = await coverFor(stockPhotos(), request, read.value.title)\n if (cover) {\n read.value.coverImage = cover.src\n if (cover.alt && !read.value.coverImageAlt) read.value.coverImageAlt = cover.alt\n }\n }\n }\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n const [host, existing] = await Promise.all([tx.get(hostRef), tx.get(eventRef)])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n // Asked again under the same id: the draft it already wrote — unless\n // a person deleted it since, which a replay never brings back.\n return isDeleted(existing.data())\n ? { ok: false, status: 409, error: 'That event was deleted on the Events page' }\n : { ok: true, replayed: true, ...recordOf(existing) }\n }\n const early = roleRefusal(host, request.uid) ?? planRefusal(request.org)\n if (early) return { ok: false, ...early }\n // The Events page's publish: `status`, and only for the publishing role.\n // \"Publish site content\" (`site.publish`, AGL-4047) on this site.\n const published = holdsSiteKey(host, request.uid, 'site.publish')\n const write = eventWrite({ ...read.value, status: published ? 'published' : 'draft' })\n tx.create(eventRef, {\n ...write.fields,\n createdAt: request.now,\n updatedAt: request.now,\n createdBy: request.uid,\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name: write.fields.title,\n versionId: null,\n facts: {\n status: write.fields.status,\n startsAtMs: write.fields.startsAtMs,\n endsAtMs: write.fields.endsAtMs,\n ...(write.fields.coverImage ? { coverImage: write.fields.coverImage } : {}),\n },\n ...liveOf(published),\n }\n })\n },\n }\n}\n\nexport const eventDraftWriter = createEventDraftWriter()\n"],"names":["checkEntitlement","holdsSiteKey","siteKeyRefusal","stockPhotoPicker","firebaseAdmin","checkEventDraftContent","EVENT_DRAFT_COVER_ADDRESS","eventDraftWallText","readEventDraftContent","EVENT_COVER_ALT_MAX_LENGTH","eventWrite","EVENT_DRAFT_ROLE_REFUSAL","EVENT_DRAFT_PLAN_REFUSAL","EVENT_DRAFT_UNPUBLISHED_NOTICE","eventDraftPastRefusal","wallText","isDeleted","data","Boolean","liveOf","published","live","notice","recordOf","event","id","name","String","versionId","facts","status","startsAtMs","Number","endsAtMs","coverImage","roleRefusal","host","uid","error","planRefusal","org","coverFor","picker","request","title","pick","hostId","subject","act","business","orientation","scope","seed","test","url","alt","replace","trim","slice","src","console","warn","createEventDraftWriter","deps","firestore","app","stockPhotos","refusal","context","collection","doc","get","exists","check","content","read","write","asked","ok","problems","value","now","getTime","timezone","db","hostRef","eventRef","existing","Promise","all","cover","coverImageAlt","runTransaction","tx","replayed","early","create","fields","createdAt","updatedAt","createdBy","eventDraftWriter"],"mappings":"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"}