@aglyn/plugins-marketing 1.0.0-beta.285 → 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-marketing",
3
- "version": "1.0.0-beta.285",
3
+ "version": "1.0.0-beta.287",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,21 +25,21 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.285",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.285",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.285",
31
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.285",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.285",
33
- "@aglyn/shared-ui-jsx-forms": "1.0.0-beta.285",
34
- "@aglyn/shared-ui-next": "1.0.0-beta.285",
35
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.285",
36
- "@aglyn/shared-util-email": "1.0.0-beta.285",
37
- "@aglyn/shared-util-http": "1.0.0-beta.285",
38
- "@aglyn/shared-util-timestamp": "1.0.0-beta.285",
39
- "@aglyn/shared-util-tools": "1.0.0-beta.285",
40
- "@aglyn/tenant-data-admin": "1.0.0-beta.285",
41
- "@aglyn/tenant-feature-instance": "1.0.0-beta.285",
42
- "@aglyn/tenant-runtime": "1.0.0-beta.285",
28
+ "@aglyn/aglyn": "1.0.0-beta.287",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.287",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.287",
31
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.287",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.287",
33
+ "@aglyn/shared-ui-jsx-forms": "1.0.0-beta.287",
34
+ "@aglyn/shared-ui-next": "1.0.0-beta.287",
35
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.287",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.287",
37
+ "@aglyn/shared-util-http": "1.0.0-beta.287",
38
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.287",
39
+ "@aglyn/shared-util-tools": "1.0.0-beta.287",
40
+ "@aglyn/tenant-data-admin": "1.0.0-beta.287",
41
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.287",
42
+ "@aglyn/tenant-runtime": "1.0.0-beta.287",
43
43
  "@swc/helpers": "0.5.23"
44
44
  },
45
45
  "peerDependencies": {
@@ -50,7 +50,8 @@ type Firestore = FirebaseFirestore.Firestore;
50
50
  /** The resource names the writers are registered under. */
51
51
  export declare const OVERLAY_DRAFT_RESOURCE = "overlay";
52
52
  export declare const EXPERIMENT_DRAFT_RESOURCE = "experiment";
53
- export declare const OVERLAY_DRAFT_ROLE_REFUSAL = "Editing this site requires the editor role";
53
+ /** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */
54
+ export declare const OVERLAY_DRAFT_ROLE_REFUSAL: string;
54
55
  export declare const OVERLAY_DRAFT_PLAN_REFUSAL = "Announcement bars and popups are not included on this workspace\u2019s plan \u2014 see Billing to upgrade.";
55
56
  export declare const OVERLAY_DRAFT_ROOM_REFUSAL = "This site already has 50 overlays \u2014 delete the ones you have finished with first.";
56
57
  export declare const EXPERIMENT_DRAFT_PLAN_REFUSAL = "A/B testing is not included on this workspace\u2019s plan \u2014 see Billing to upgrade.";
@@ -14,8 +14,8 @@ 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 { 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 { registerPluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
20
20
  import firebaseAdmin from "@aglyn/tenant-data-admin/server/firebase-admin";
21
21
  import { isDocumentId } from "@aglyn/tenant-data-admin/server/document-id";
@@ -24,13 +24,12 @@ import { OVERLAY_LIST_CEILING, overlayDraftContentProblems, overlayDraftFromProp
24
24
  /** The resource names the writers are registered under. */ export const OVERLAY_DRAFT_RESOURCE = 'overlay';
25
25
  export const EXPERIMENT_DRAFT_RESOURCE = 'experiment';
26
26
  const MARKETING_PLUGIN_ID = 'marketing';
27
- export const OVERLAY_DRAFT_ROLE_REFUSAL = 'Editing this site requires the editor role';
27
+ /** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */ export const OVERLAY_DRAFT_ROLE_REFUSAL = siteKeyRefusal('site.edit');
28
28
  export const OVERLAY_DRAFT_PLAN_REFUSAL = 'Announcement bars and popups are not included on this workspace’s plan — see Billing to upgrade.';
29
29
  export const OVERLAY_DRAFT_ROOM_REFUSAL = `This site already has ${OVERLAY_LIST_CEILING} overlays — delete the ones you have finished with first.`;
30
30
  export const EXPERIMENT_DRAFT_PLAN_REFUSAL = 'A/B testing is not included on this workspace’s plan — see Billing to upgrade.';
31
31
  const str = (value)=>typeof value === 'string' ? value.trim() : '';
32
32
  /** The site, and the member's role on it, or the refusal that stands in for them. */ async function siteFor(firestore, context) {
33
- var _host_get;
34
33
  const host = await firestore.collection('hosts').doc(context.hostId).get();
35
34
  if (!host.exists || context.orgId && host.get('orgId') && host.get('orgId') !== context.orgId) {
36
35
  return {
@@ -38,8 +37,7 @@ const str = (value)=>typeof value === 'string' ? value.trim() : '';
38
37
  error: 'Unknown site'
39
38
  };
40
39
  }
41
- const role = ((_host_get = host.get('memberRoles')) != null ? _host_get : {})[context.uid];
42
- if (!hostRoleCanWrite(role)) return {
40
+ if (!holdsSiteKey(host, context.uid, 'site.edit')) return {
43
41
  status: 403,
44
42
  error: OVERLAY_DRAFT_ROLE_REFUSAL
45
43
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/server/marketing-drafts.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 { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n registerPluginResourceDraftWriter,\n type PluginDraftContext,\n type PluginDraftRecord,\n type PluginDraftRefusal,\n type PluginDraftWrite,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport firebaseAdmin from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { isDocumentId } from '@aglyn/tenant-data-admin/server/document-id'\nimport {\n EXPERIMENT_MAX_VARIANTS,\n validateExperiment,\n type ExperimentTarget,\n type HostExperiment,\n} from '../model/experiments'\nimport {\n OVERLAY_LIST_CEILING,\n overlayDraftContentProblems,\n overlayDraftFromProposal,\n type OverlayCopyProposal,\n} from '../model/overlay-drafts'\n\n/**\n * The overlay and A/B test draft writers this plugin registers on the core's\n * resource-drafts seam (AGL-3616), for another plugin — an AI build turning a\n * request into a plan — to make one without importing this plugin.\n *\n * ## What a draft is here\n *\n * - **An overlay is written SWITCHED OFF** (`enabled: false`) at\n * `hosts/{hostId}/overlays/{id}`: no visitor sees it until a member turns\n * it on in the Overlays section. Its copy is held to the editor's limits\n * and its trigger to the catalog the runtime opens a popup on; a link,\n * pages, a schedule and an order are never written.\n * - **An A/B test is written STOPPED** (`status: 'draft'`) at\n * `hosts/{hostId}/experiments/{id}`: assignment serves nothing for a test\n * that is not running. Its variants are drafts of copy — an email's subject\n * and body, a page's or section's name — and pin only versions that exist\n * on the page under test; the published page stays every variant's default.\n *\n * Neither write changes what a published page serves, so neither drops the\n * site's cache the way an edit to a live overlay does.\n *\n * ## The owner's rules\n *\n * `refusal` holds the member's role on the site (one that writes its content)\n * and the plan's entitlement (`marketingOverlays`, `abTesting`), and for an\n * overlay the room in a site's list, which reads and reorders at most\n * {@link OVERLAY_LIST_CEILING}. `check` is pure. `write` replays a draft\n * already written under its id, asks `refusal` again before it writes, and\n * CREATES the document, so two writes racing for one id cannot both land.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resource names the writers are registered under. */\nexport const OVERLAY_DRAFT_RESOURCE = 'overlay'\nexport const EXPERIMENT_DRAFT_RESOURCE = 'experiment'\n\nconst MARKETING_PLUGIN_ID = 'marketing'\n\nexport const OVERLAY_DRAFT_ROLE_REFUSAL = 'Editing this site requires the editor role'\nexport const OVERLAY_DRAFT_PLAN_REFUSAL =\n 'Announcement bars and popups are not included on this workspace’s plan — see Billing to upgrade.'\nexport const OVERLAY_DRAFT_ROOM_REFUSAL =\n `This site already has ${OVERLAY_LIST_CEILING} overlays — delete the ones you have finished with first.`\nexport const EXPERIMENT_DRAFT_PLAN_REFUSAL =\n 'A/B testing is not included on this workspace’s plan — see Billing to upgrade.'\n\nexport interface MarketingDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The site, and the member's role on it, or the refusal that stands in for them. */\nasync function siteFor(\n firestore: Firestore,\n context: Pick<PluginDraftContext, 'hostId' | 'uid' | 'orgId'>,\n): Promise<{ host: FirebaseFirestore.DocumentSnapshot } | PluginDraftRefusal> {\n const host = await firestore.collection('hosts').doc(context.hostId).get()\n if (!host.exists || (context.orgId && host.get('orgId') && host.get('orgId') !== context.orgId)) {\n return { status: 404, error: 'Unknown site' }\n }\n const role = ((host.get('memberRoles') ?? {}) as Record<string, unknown>)[context.uid]\n if (!hostRoleCanWrite(role)) return { status: 403, error: OVERLAY_DRAFT_ROLE_REFUSAL }\n return { host }\n}\n\nconst isRefusal = (value: object): value is PluginDraftRefusal => 'status' in value\n\nfunction overlayRecord(snapshot: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n return {\n id: snapshot.id,\n name: str(snapshot.get('name')),\n versionId: null,\n facts: { kind: snapshot.get('kind') ?? null, enabled: snapshot.get('enabled') !== false },\n }\n}\n\n/** The overlay content as the writer documents it: a proposal's fields and its kind. */\nfunction overlayProposal(content: Readonly<Record<string, unknown>>): OverlayCopyProposal {\n const proposal: OverlayCopyProposal = {}\n for (const field of ['name', 'text', 'headline', 'body', 'ctaLabel', 'trigger'] as const) {\n if (typeof content[field] === 'string') proposal[field] = content[field] as string\n }\n if (typeof content['triggerValue'] === 'number') proposal.triggerValue = content['triggerValue']\n return proposal\n}\n\n/**\n * The `overlay` writer. Content: `{ kind: 'bar' | 'popup', text?, headline?,\n * body?, ctaLabel?, trigger?, triggerValue? }` — a bar's text, a popup's\n * body, each within the overlay copy limits (`OVERLAY_COPY_LIMITS`).\n */\nexport function createOverlayDraftWriter(deps: MarketingDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const refusal = async (context: PluginDraftContext): Promise<PluginDraftRefusal | null> => {\n const site = await siteFor(firestore(), context)\n if (isRefusal(site)) return site\n if (!checkEntitlement(context.org as never, 'marketingOverlays')) {\n return { status: 403, error: OVERLAY_DRAFT_PLAN_REFUSAL }\n }\n const count = await site.host.ref.collection('overlays').count().get()\n if (Number(count.data().count) >= OVERLAY_LIST_CEILING) {\n return { status: 409, error: OVERLAY_DRAFT_ROOM_REFUSAL }\n }\n return null\n }\n return {\n refusal,\n check: (content) => {\n const problems = overlayDraftContentProblems(content)\n return problems.length\n ? { ok: false, problems }\n : { ok: true, facts: { kind: content['kind'], enabled: false } }\n },\n read: async ({ hostId, id }) => {\n if (!isDocumentId(id)) return null\n const snapshot = await firestore().collection('hosts').doc(hostId).collection('overlays').doc(id).get()\n return snapshot.exists ? overlayRecord(snapshot) : null\n },\n write: async (request): Promise<PluginDraftWrite> => {\n if (!isDocumentId(request.id)) return { ok: false, status: 400, error: 'Invalid overlay id' }\n const problems = overlayDraftContentProblems(request.content)\n if (problems.length) return { ok: false, status: 400, error: problems[0] }\n const kind = request.content['kind'] as 'bar' | 'popup'\n const draft = overlayDraftFromProposal(kind, {\n ...overlayProposal(request.content),\n ...(str(request.name) ? { name: str(request.name) } : {}),\n })\n if (!draft) return { ok: false, status: 400, error: 'There was no copy to save' }\n const ref = firestore().collection('hosts').doc(request.hostId).collection('overlays').doc(request.id)\n const earlier = await ref.get()\n if (earlier.exists) return { ok: true, replayed: true, ...overlayRecord(earlier) }\n const refused = await refusal(request)\n if (refused) return { ok: false, ...refused }\n const name = draft.name ?? null\n await ref.create({\n ...draft,\n // The list orders on `name`, which every overlay carries, null or not.\n name,\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: name ?? '',\n versionId: null,\n facts: { kind, enabled: false },\n }\n },\n }\n}\n\n/** One variant as the writer documents it. */\ninterface ExperimentDraftVariant {\n name: string\n subject?: string\n body?: string\n versionId?: string\n}\n\n/** The experiment content, read and checked; problems instead when it is not one. */\nexport function readExperimentDraftContent(\n content: Readonly<Record<string, unknown>>,\n name: string,\n): { ok: true; value: HostExperiment } | { ok: false; problems: string[] } {\n const target = content['target'] as ExperimentTarget\n const rawVariants = Array.isArray(content['variants']) ? content['variants'] : []\n const problems: string[] = []\n if (rawVariants.length > EXPERIMENT_MAX_VARIANTS) {\n problems.push(`Experiments are capped at ${EXPERIMENT_MAX_VARIANTS} variants`)\n }\n const variants = rawVariants.slice(0, EXPERIMENT_MAX_VARIANTS).map((raw, index) => {\n const variant = (raw ?? {}) as ExperimentDraftVariant\n const id = String.fromCharCode(97 + index)\n return {\n id,\n name: str(variant.name).slice(0, 80) || (index === 0 ? 'A (control)' : id.toUpperCase()),\n weight: 1,\n ...(target === 'email' && str(variant.subject) ? { subject: str(variant.subject).slice(0, 200) } : {}),\n ...(target === 'email' && str(variant.body) ? { body: str(variant.body).slice(0, 4_000) } : {}),\n ...(target !== 'email' && str(variant.versionId) ? { versionId: str(variant.versionId) } : {}),\n }\n })\n const goal = str(content['goal'])\n const value: HostExperiment = {\n name: str(name || content['name']).slice(0, 120),\n // STOPPED: assignment serves nothing for a test that is not running.\n status: 'draft',\n target,\n ...(str(content['screenId']) ? { screenId: str(content['screenId']) } : {}),\n ...(target === 'section' && str(content['nodeId']) ? { nodeId: str(content['nodeId']) } : {}),\n variants,\n goal: { event: goal || 'formSubmission' },\n }\n const invalid = validateExperiment(value)\n if (invalid) problems.push(invalid)\n if (variants.some((variant) => 'versionId' in variant && !isDocumentId(String(variant.versionId)))) {\n problems.push('A variant names a version that is not one')\n }\n return problems.length ? { ok: false, problems } : { ok: true, value }\n}\n\nfunction experimentRecord(snapshot: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n return {\n id: snapshot.id,\n name: str(snapshot.get('name')),\n versionId: null,\n facts: { status: snapshot.get('status') ?? null, target: snapshot.get('target') ?? null },\n }\n}\n\n/**\n * The `experiment` writer. Content: `{ target: 'screen' | 'section' | 'email',\n * screenId?, nodeId?, goal?, variants: [{ name, subject?, body?, versionId? }] }`,\n * two to four variants, the first the control.\n */\nexport function createExperimentDraftWriter(deps: MarketingDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const refusal = async (context: PluginDraftContext): Promise<PluginDraftRefusal | null> => {\n const site = await siteFor(firestore(), context)\n if (isRefusal(site)) return site\n if (!checkEntitlement(context.org as never, 'abTesting')) {\n return { status: 403, error: EXPERIMENT_DRAFT_PLAN_REFUSAL }\n }\n return null\n }\n return {\n refusal,\n check: (content) => {\n const read = readExperimentDraftContent(content, str(content['name']) || 'A/B test')\n return read.ok === false\n ? read\n : { ok: true, facts: { status: 'draft', target: read.value.target, variants: read.value.variants.length } }\n },\n read: async ({ hostId, id }) => {\n if (!isDocumentId(id)) return null\n const snapshot = await firestore().collection('hosts').doc(hostId).collection('experiments').doc(id).get()\n return snapshot.exists ? experimentRecord(snapshot) : null\n },\n write: async (request): Promise<PluginDraftWrite> => {\n if (!isDocumentId(request.id)) return { ok: false, status: 400, error: 'Invalid experiment id' }\n const read = readExperimentDraftContent(request.content, request.name || 'A/B test')\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const hostRef = firestore().collection('hosts').doc(request.hostId)\n const ref = hostRef.collection('experiments').doc(request.id)\n const earlier = await ref.get()\n if (earlier.exists) return { ok: true, replayed: true, ...experimentRecord(earlier) }\n const refused = await refusal(request)\n if (refused) return { ok: false, ...refused }\n const { value } = read\n // A page under test, and every version a variant pins, exist on this site.\n if (value.screenId) {\n const screenRef = hostRef.collection('screens').doc(value.screenId)\n const screen = await screenRef.get()\n if (!screen.exists || screen.get('deletedAt')) {\n return { ok: false, status: 400, error: 'The page under test does not exist' }\n }\n for (const variant of value.variants) {\n if (!variant.versionId) continue\n const version = await screenRef.collection('versions').doc(variant.versionId).get()\n if (!version.exists || version.get('deletedAt')) {\n return { ok: false, status: 400, error: 'A variant pins a version the page does not have' }\n }\n }\n }\n await ref.create({\n ...value,\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: value.name,\n versionId: null,\n facts: { status: 'draft', target: value.target },\n }\n },\n }\n}\n\n/**\n * Registers both writers; the console surface calls it, since only the\n * console runs the AI builds that write through them. Idempotent.\n */\nexport function registerMarketingDraftWriters(deps: MarketingDraftWriterDeps = {}): void {\n registerPluginResourceDraftWriter(OVERLAY_DRAFT_RESOURCE, createOverlayDraftWriter(deps), {\n pluginId: MARKETING_PLUGIN_ID,\n })\n registerPluginResourceDraftWriter(EXPERIMENT_DRAFT_RESOURCE, createExperimentDraftWriter(deps), {\n pluginId: MARKETING_PLUGIN_ID,\n })\n}\n"],"names":["hostRoleCanWrite","checkEntitlement","registerPluginResourceDraftWriter","firebaseAdmin","isDocumentId","EXPERIMENT_MAX_VARIANTS","validateExperiment","OVERLAY_LIST_CEILING","overlayDraftContentProblems","overlayDraftFromProposal","OVERLAY_DRAFT_RESOURCE","EXPERIMENT_DRAFT_RESOURCE","MARKETING_PLUGIN_ID","OVERLAY_DRAFT_ROLE_REFUSAL","OVERLAY_DRAFT_PLAN_REFUSAL","OVERLAY_DRAFT_ROOM_REFUSAL","EXPERIMENT_DRAFT_PLAN_REFUSAL","str","value","trim","siteFor","firestore","context","host","collection","doc","hostId","get","exists","orgId","status","error","role","uid","isRefusal","overlayRecord","snapshot","id","name","versionId","facts","kind","enabled","overlayProposal","content","proposal","field","triggerValue","createOverlayDraftWriter","deps","app","refusal","site","org","count","ref","Number","data","check","problems","length","ok","read","write","request","draft","earlier","replayed","refused","create","createdAt","now","updatedAt","createdBy","readExperimentDraftContent","target","rawVariants","Array","isArray","push","variants","slice","map","raw","index","variant","String","fromCharCode","toUpperCase","weight","subject","body","goal","screenId","nodeId","event","invalid","some","experimentRecord","createExperimentDraftWriter","hostRef","screenRef","screen","version","registerMarketingDraftWriters","pluginId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/server/marketing-drafts.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 {\n registerPluginResourceDraftWriter,\n type PluginDraftContext,\n type PluginDraftRecord,\n type PluginDraftRefusal,\n type PluginDraftWrite,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport firebaseAdmin from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { isDocumentId } from '@aglyn/tenant-data-admin/server/document-id'\nimport {\n EXPERIMENT_MAX_VARIANTS,\n validateExperiment,\n type ExperimentTarget,\n type HostExperiment,\n} from '../model/experiments'\nimport {\n OVERLAY_LIST_CEILING,\n overlayDraftContentProblems,\n overlayDraftFromProposal,\n type OverlayCopyProposal,\n} from '../model/overlay-drafts'\n\n/**\n * The overlay and A/B test draft writers this plugin registers on the core's\n * resource-drafts seam (AGL-3616), for another plugin — an AI build turning a\n * request into a plan — to make one without importing this plugin.\n *\n * ## What a draft is here\n *\n * - **An overlay is written SWITCHED OFF** (`enabled: false`) at\n * `hosts/{hostId}/overlays/{id}`: no visitor sees it until a member turns\n * it on in the Overlays section. Its copy is held to the editor's limits\n * and its trigger to the catalog the runtime opens a popup on; a link,\n * pages, a schedule and an order are never written.\n * - **An A/B test is written STOPPED** (`status: 'draft'`) at\n * `hosts/{hostId}/experiments/{id}`: assignment serves nothing for a test\n * that is not running. Its variants are drafts of copy — an email's subject\n * and body, a page's or section's name — and pin only versions that exist\n * on the page under test; the published page stays every variant's default.\n *\n * Neither write changes what a published page serves, so neither drops the\n * site's cache the way an edit to a live overlay does.\n *\n * ## The owner's rules\n *\n * `refusal` holds the member's role on the site (one that writes its content)\n * and the plan's entitlement (`marketingOverlays`, `abTesting`), and for an\n * overlay the room in a site's list, which reads and reorders at most\n * {@link OVERLAY_LIST_CEILING}. `check` is pure. `write` replays a draft\n * already written under its id, asks `refusal` again before it writes, and\n * CREATES the document, so two writes racing for one id cannot both land.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resource names the writers are registered under. */\nexport const OVERLAY_DRAFT_RESOURCE = 'overlay'\nexport const EXPERIMENT_DRAFT_RESOURCE = 'experiment'\n\nconst MARKETING_PLUGIN_ID = 'marketing'\n\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const OVERLAY_DRAFT_ROLE_REFUSAL = siteKeyRefusal('site.edit')\nexport const OVERLAY_DRAFT_PLAN_REFUSAL =\n 'Announcement bars and popups are not included on this workspace’s plan — see Billing to upgrade.'\nexport const OVERLAY_DRAFT_ROOM_REFUSAL =\n `This site already has ${OVERLAY_LIST_CEILING} overlays — delete the ones you have finished with first.`\nexport const EXPERIMENT_DRAFT_PLAN_REFUSAL =\n 'A/B testing is not included on this workspace’s plan — see Billing to upgrade.'\n\nexport interface MarketingDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The site, and the member's role on it, or the refusal that stands in for them. */\nasync function siteFor(\n firestore: Firestore,\n context: Pick<PluginDraftContext, 'hostId' | 'uid' | 'orgId'>,\n): Promise<{ host: FirebaseFirestore.DocumentSnapshot } | PluginDraftRefusal> {\n const host = await firestore.collection('hosts').doc(context.hostId).get()\n if (!host.exists || (context.orgId && host.get('orgId') && host.get('orgId') !== context.orgId)) {\n return { status: 404, error: 'Unknown site' }\n }\n if (!holdsSiteKey(host, context.uid, 'site.edit')) return { status: 403, error: OVERLAY_DRAFT_ROLE_REFUSAL }\n return { host }\n}\n\nconst isRefusal = (value: object): value is PluginDraftRefusal => 'status' in value\n\nfunction overlayRecord(snapshot: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n return {\n id: snapshot.id,\n name: str(snapshot.get('name')),\n versionId: null,\n facts: { kind: snapshot.get('kind') ?? null, enabled: snapshot.get('enabled') !== false },\n }\n}\n\n/** The overlay content as the writer documents it: a proposal's fields and its kind. */\nfunction overlayProposal(content: Readonly<Record<string, unknown>>): OverlayCopyProposal {\n const proposal: OverlayCopyProposal = {}\n for (const field of ['name', 'text', 'headline', 'body', 'ctaLabel', 'trigger'] as const) {\n if (typeof content[field] === 'string') proposal[field] = content[field] as string\n }\n if (typeof content['triggerValue'] === 'number') proposal.triggerValue = content['triggerValue']\n return proposal\n}\n\n/**\n * The `overlay` writer. Content: `{ kind: 'bar' | 'popup', text?, headline?,\n * body?, ctaLabel?, trigger?, triggerValue? }` — a bar's text, a popup's\n * body, each within the overlay copy limits (`OVERLAY_COPY_LIMITS`).\n */\nexport function createOverlayDraftWriter(deps: MarketingDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const refusal = async (context: PluginDraftContext): Promise<PluginDraftRefusal | null> => {\n const site = await siteFor(firestore(), context)\n if (isRefusal(site)) return site\n if (!checkEntitlement(context.org as never, 'marketingOverlays')) {\n return { status: 403, error: OVERLAY_DRAFT_PLAN_REFUSAL }\n }\n const count = await site.host.ref.collection('overlays').count().get()\n if (Number(count.data().count) >= OVERLAY_LIST_CEILING) {\n return { status: 409, error: OVERLAY_DRAFT_ROOM_REFUSAL }\n }\n return null\n }\n return {\n refusal,\n check: (content) => {\n const problems = overlayDraftContentProblems(content)\n return problems.length\n ? { ok: false, problems }\n : { ok: true, facts: { kind: content['kind'], enabled: false } }\n },\n read: async ({ hostId, id }) => {\n if (!isDocumentId(id)) return null\n const snapshot = await firestore().collection('hosts').doc(hostId).collection('overlays').doc(id).get()\n return snapshot.exists ? overlayRecord(snapshot) : null\n },\n write: async (request): Promise<PluginDraftWrite> => {\n if (!isDocumentId(request.id)) return { ok: false, status: 400, error: 'Invalid overlay id' }\n const problems = overlayDraftContentProblems(request.content)\n if (problems.length) return { ok: false, status: 400, error: problems[0] }\n const kind = request.content['kind'] as 'bar' | 'popup'\n const draft = overlayDraftFromProposal(kind, {\n ...overlayProposal(request.content),\n ...(str(request.name) ? { name: str(request.name) } : {}),\n })\n if (!draft) return { ok: false, status: 400, error: 'There was no copy to save' }\n const ref = firestore().collection('hosts').doc(request.hostId).collection('overlays').doc(request.id)\n const earlier = await ref.get()\n if (earlier.exists) return { ok: true, replayed: true, ...overlayRecord(earlier) }\n const refused = await refusal(request)\n if (refused) return { ok: false, ...refused }\n const name = draft.name ?? null\n await ref.create({\n ...draft,\n // The list orders on `name`, which every overlay carries, null or not.\n name,\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: name ?? '',\n versionId: null,\n facts: { kind, enabled: false },\n }\n },\n }\n}\n\n/** One variant as the writer documents it. */\ninterface ExperimentDraftVariant {\n name: string\n subject?: string\n body?: string\n versionId?: string\n}\n\n/** The experiment content, read and checked; problems instead when it is not one. */\nexport function readExperimentDraftContent(\n content: Readonly<Record<string, unknown>>,\n name: string,\n): { ok: true; value: HostExperiment } | { ok: false; problems: string[] } {\n const target = content['target'] as ExperimentTarget\n const rawVariants = Array.isArray(content['variants']) ? content['variants'] : []\n const problems: string[] = []\n if (rawVariants.length > EXPERIMENT_MAX_VARIANTS) {\n problems.push(`Experiments are capped at ${EXPERIMENT_MAX_VARIANTS} variants`)\n }\n const variants = rawVariants.slice(0, EXPERIMENT_MAX_VARIANTS).map((raw, index) => {\n const variant = (raw ?? {}) as ExperimentDraftVariant\n const id = String.fromCharCode(97 + index)\n return {\n id,\n name: str(variant.name).slice(0, 80) || (index === 0 ? 'A (control)' : id.toUpperCase()),\n weight: 1,\n ...(target === 'email' && str(variant.subject) ? { subject: str(variant.subject).slice(0, 200) } : {}),\n ...(target === 'email' && str(variant.body) ? { body: str(variant.body).slice(0, 4_000) } : {}),\n ...(target !== 'email' && str(variant.versionId) ? { versionId: str(variant.versionId) } : {}),\n }\n })\n const goal = str(content['goal'])\n const value: HostExperiment = {\n name: str(name || content['name']).slice(0, 120),\n // STOPPED: assignment serves nothing for a test that is not running.\n status: 'draft',\n target,\n ...(str(content['screenId']) ? { screenId: str(content['screenId']) } : {}),\n ...(target === 'section' && str(content['nodeId']) ? { nodeId: str(content['nodeId']) } : {}),\n variants,\n goal: { event: goal || 'formSubmission' },\n }\n const invalid = validateExperiment(value)\n if (invalid) problems.push(invalid)\n if (variants.some((variant) => 'versionId' in variant && !isDocumentId(String(variant.versionId)))) {\n problems.push('A variant names a version that is not one')\n }\n return problems.length ? { ok: false, problems } : { ok: true, value }\n}\n\nfunction experimentRecord(snapshot: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n return {\n id: snapshot.id,\n name: str(snapshot.get('name')),\n versionId: null,\n facts: { status: snapshot.get('status') ?? null, target: snapshot.get('target') ?? null },\n }\n}\n\n/**\n * The `experiment` writer. Content: `{ target: 'screen' | 'section' | 'email',\n * screenId?, nodeId?, goal?, variants: [{ name, subject?, body?, versionId? }] }`,\n * two to four variants, the first the control.\n */\nexport function createExperimentDraftWriter(deps: MarketingDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const refusal = async (context: PluginDraftContext): Promise<PluginDraftRefusal | null> => {\n const site = await siteFor(firestore(), context)\n if (isRefusal(site)) return site\n if (!checkEntitlement(context.org as never, 'abTesting')) {\n return { status: 403, error: EXPERIMENT_DRAFT_PLAN_REFUSAL }\n }\n return null\n }\n return {\n refusal,\n check: (content) => {\n const read = readExperimentDraftContent(content, str(content['name']) || 'A/B test')\n return read.ok === false\n ? read\n : { ok: true, facts: { status: 'draft', target: read.value.target, variants: read.value.variants.length } }\n },\n read: async ({ hostId, id }) => {\n if (!isDocumentId(id)) return null\n const snapshot = await firestore().collection('hosts').doc(hostId).collection('experiments').doc(id).get()\n return snapshot.exists ? experimentRecord(snapshot) : null\n },\n write: async (request): Promise<PluginDraftWrite> => {\n if (!isDocumentId(request.id)) return { ok: false, status: 400, error: 'Invalid experiment id' }\n const read = readExperimentDraftContent(request.content, request.name || 'A/B test')\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const hostRef = firestore().collection('hosts').doc(request.hostId)\n const ref = hostRef.collection('experiments').doc(request.id)\n const earlier = await ref.get()\n if (earlier.exists) return { ok: true, replayed: true, ...experimentRecord(earlier) }\n const refused = await refusal(request)\n if (refused) return { ok: false, ...refused }\n const { value } = read\n // A page under test, and every version a variant pins, exist on this site.\n if (value.screenId) {\n const screenRef = hostRef.collection('screens').doc(value.screenId)\n const screen = await screenRef.get()\n if (!screen.exists || screen.get('deletedAt')) {\n return { ok: false, status: 400, error: 'The page under test does not exist' }\n }\n for (const variant of value.variants) {\n if (!variant.versionId) continue\n const version = await screenRef.collection('versions').doc(variant.versionId).get()\n if (!version.exists || version.get('deletedAt')) {\n return { ok: false, status: 400, error: 'A variant pins a version the page does not have' }\n }\n }\n }\n await ref.create({\n ...value,\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: value.name,\n versionId: null,\n facts: { status: 'draft', target: value.target },\n }\n },\n }\n}\n\n/**\n * Registers both writers; the console surface calls it, since only the\n * console runs the AI builds that write through them. Idempotent.\n */\nexport function registerMarketingDraftWriters(deps: MarketingDraftWriterDeps = {}): void {\n registerPluginResourceDraftWriter(OVERLAY_DRAFT_RESOURCE, createOverlayDraftWriter(deps), {\n pluginId: MARKETING_PLUGIN_ID,\n })\n registerPluginResourceDraftWriter(EXPERIMENT_DRAFT_RESOURCE, createExperimentDraftWriter(deps), {\n pluginId: MARKETING_PLUGIN_ID,\n })\n}\n"],"names":["checkEntitlement","holdsSiteKey","siteKeyRefusal","registerPluginResourceDraftWriter","firebaseAdmin","isDocumentId","EXPERIMENT_MAX_VARIANTS","validateExperiment","OVERLAY_LIST_CEILING","overlayDraftContentProblems","overlayDraftFromProposal","OVERLAY_DRAFT_RESOURCE","EXPERIMENT_DRAFT_RESOURCE","MARKETING_PLUGIN_ID","OVERLAY_DRAFT_ROLE_REFUSAL","OVERLAY_DRAFT_PLAN_REFUSAL","OVERLAY_DRAFT_ROOM_REFUSAL","EXPERIMENT_DRAFT_PLAN_REFUSAL","str","value","trim","siteFor","firestore","context","host","collection","doc","hostId","get","exists","orgId","status","error","uid","isRefusal","overlayRecord","snapshot","id","name","versionId","facts","kind","enabled","overlayProposal","content","proposal","field","triggerValue","createOverlayDraftWriter","deps","app","refusal","site","org","count","ref","Number","data","check","problems","length","ok","read","write","request","draft","earlier","replayed","refused","create","createdAt","now","updatedAt","createdBy","readExperimentDraftContent","target","rawVariants","Array","isArray","push","variants","slice","map","raw","index","variant","String","fromCharCode","toUpperCase","weight","subject","body","goal","screenId","nodeId","event","invalid","some","experimentRecord","createExperimentDraftWriter","hostRef","screenRef","screen","version","registerMarketingDraftWriters","pluginId"],"mappings":"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"}