@aglyn/tenant-data-admin 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/jest.integration.config.d.ts +39 -0
- package/jest.integration.setup.d.ts +17 -0
- package/package.json +51 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/lib/render-cache.d.ts +184 -0
- package/src/lib/render-cache.js +198 -0
- package/src/lib/render-cache.js.map +1 -0
- package/src/lib/server/account-addresses.d.ts +160 -0
- package/src/lib/server/account-addresses.js +311 -0
- package/src/lib/server/account-addresses.js.map +1 -0
- package/src/lib/server/account-emails.d.ts +219 -0
- package/src/lib/server/account-emails.js +640 -0
- package/src/lib/server/account-emails.js.map +1 -0
- package/src/lib/server/admin-audit.d.ts +180 -0
- package/src/lib/server/admin-audit.js +233 -0
- package/src/lib/server/admin-audit.js.map +1 -0
- package/src/lib/server/analytics-retention.d.ts +63 -0
- package/src/lib/server/analytics-retention.js +66 -0
- package/src/lib/server/analytics-retention.js.map +1 -0
- package/src/lib/server/api-http.d.ts +159 -0
- package/src/lib/server/api-http.js +230 -0
- package/src/lib/server/api-http.js.map +1 -0
- package/src/lib/server/api-keys.d.ts +117 -0
- package/src/lib/server/api-keys.js +298 -0
- package/src/lib/server/api-keys.js.map +1 -0
- package/src/lib/server/auth-handoff.d.ts +165 -0
- package/src/lib/server/auth-handoff.js +482 -0
- package/src/lib/server/auth-handoff.js.map +1 -0
- package/src/lib/server/auth-pools.d.ts +246 -0
- package/src/lib/server/auth-pools.js +551 -0
- package/src/lib/server/auth-pools.js.map +1 -0
- package/src/lib/server/billing-addons.d.ts +171 -0
- package/src/lib/server/billing-addons.js +277 -0
- package/src/lib/server/billing-addons.js.map +1 -0
- package/src/lib/server/campaign-attribution-store.d.ts +55 -0
- package/src/lib/server/campaign-attribution-store.js +95 -0
- package/src/lib/server/campaign-attribution-store.js.map +1 -0
- package/src/lib/server/campaign-conversion-attribution.d.ts +205 -0
- package/src/lib/server/campaign-conversion-attribution.js +279 -0
- package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
- package/src/lib/server/client-error-report.d.ts +301 -0
- package/src/lib/server/client-error-report.js +755 -0
- package/src/lib/server/client-error-report.js.map +1 -0
- package/src/lib/server/connect-account-status.d.ts +74 -0
- package/src/lib/server/connect-account-status.js +94 -0
- package/src/lib/server/connect-account-status.js.map +1 -0
- package/src/lib/server/connect-payout-failure.d.ts +103 -0
- package/src/lib/server/connect-payout-failure.js +127 -0
- package/src/lib/server/connect-payout-failure.js.map +1 -0
- package/src/lib/server/console-api-rate-limit.d.ts +81 -0
- package/src/lib/server/console-api-rate-limit.js +111 -0
- package/src/lib/server/console-api-rate-limit.js.map +1 -0
- package/src/lib/server/console-domains.d.ts +326 -0
- package/src/lib/server/console-domains.js +779 -0
- package/src/lib/server/console-domains.js.map +1 -0
- package/src/lib/server/consume-once.d.ts +91 -0
- package/src/lib/server/consume-once.js +96 -0
- package/src/lib/server/consume-once.js.map +1 -0
- package/src/lib/server/contact-company-link.d.ts +85 -0
- package/src/lib/server/contact-company-link.js +105 -0
- package/src/lib/server/contact-company-link.js.map +1 -0
- package/src/lib/server/contact-email-engagement.d.ts +32 -0
- package/src/lib/server/contact-email-engagement.js +145 -0
- package/src/lib/server/contact-email-engagement.js.map +1 -0
- package/src/lib/server/contact-email-index.d.ts +56 -0
- package/src/lib/server/contact-email-index.js +158 -0
- package/src/lib/server/contact-email-index.js.map +1 -0
- package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
- package/src/lib/server/contact-lifecycle-floor.js +88 -0
- package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
- package/src/lib/server/contact-merge.d.ts +55 -0
- package/src/lib/server/contact-merge.js +279 -0
- package/src/lib/server/contact-merge.js.map +1 -0
- package/src/lib/server/contact-suppression.d.ts +153 -0
- package/src/lib/server/contact-suppression.js +259 -0
- package/src/lib/server/contact-suppression.js.map +1 -0
- package/src/lib/server/crm-booking-activity.d.ts +86 -0
- package/src/lib/server/crm-booking-activity.js +251 -0
- package/src/lib/server/crm-booking-activity.js.map +1 -0
- package/src/lib/server/crm-email-activity.d.ts +112 -0
- package/src/lib/server/crm-email-activity.js +143 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.d.ts +145 -0
- package/src/lib/server/crm-inbound-email.js +287 -0
- package/src/lib/server/crm-inbound-email.js.map +1 -0
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- package/src/lib/server/crm-records.d.ts +144 -0
- package/src/lib/server/crm-records.js +218 -0
- package/src/lib/server/crm-records.js.map +1 -0
- package/src/lib/server/csp-aggregate.d.ts +62 -0
- package/src/lib/server/csp-aggregate.js +276 -0
- package/src/lib/server/csp-aggregate.js.map +1 -0
- package/src/lib/server/data-storage-gate.d.ts +66 -0
- package/src/lib/server/data-storage-gate.js +48 -0
- package/src/lib/server/data-storage-gate.js.map +1 -0
- package/src/lib/server/dataset-live-pages.d.ts +179 -0
- package/src/lib/server/dataset-live-pages.js +421 -0
- package/src/lib/server/dataset-live-pages.js.map +1 -0
- package/src/lib/server/dns-probe.d.ts +53 -0
- package/src/lib/server/dns-probe.js +184 -0
- package/src/lib/server/dns-probe.js.map +1 -0
- package/src/lib/server/document-id.d.ts +29 -0
- package/src/lib/server/document-id.js +100 -0
- package/src/lib/server/document-id.js.map +1 -0
- package/src/lib/server/domain-provider-vercel.d.ts +33 -0
- package/src/lib/server/domain-provider-vercel.js +298 -0
- package/src/lib/server/domain-provider-vercel.js.map +1 -0
- package/src/lib/server/domain-provider-webhook.d.ts +73 -0
- package/src/lib/server/domain-provider-webhook.js +223 -0
- package/src/lib/server/domain-provider-webhook.js.map +1 -0
- package/src/lib/server/domain-provider-wildcard.d.ts +54 -0
- package/src/lib/server/domain-provider-wildcard.js +163 -0
- package/src/lib/server/domain-provider-wildcard.js.map +1 -0
- package/src/lib/server/domain-provider.d.ts +197 -0
- package/src/lib/server/domain-provider.js +221 -0
- package/src/lib/server/domain-provider.js.map +1 -0
- package/src/lib/server/duplicate-activity.d.ts +54 -0
- package/src/lib/server/duplicate-activity.js +67 -0
- package/src/lib/server/duplicate-activity.js.map +1 -0
- package/src/lib/server/duplicate-resource.d.ts +55 -0
- package/src/lib/server/duplicate-resource.js +474 -0
- package/src/lib/server/duplicate-resource.js.map +1 -0
- package/src/lib/server/dynamic-list-materialize.d.ts +153 -0
- package/src/lib/server/dynamic-list-materialize.js +581 -0
- package/src/lib/server/dynamic-list-materialize.js.map +1 -0
- package/src/lib/server/edit-access-authz.d.ts +56 -0
- package/src/lib/server/edit-access-authz.js +107 -0
- package/src/lib/server/edit-access-authz.js.map +1 -0
- package/src/lib/server/edit-access-token.d.ts +72 -0
- package/src/lib/server/edit-access-token.js +116 -0
- package/src/lib/server/edit-access-token.js.map +1 -0
- package/src/lib/server/edit-hint-token.d.ts +83 -0
- package/src/lib/server/edit-hint-token.js +119 -0
- package/src/lib/server/edit-hint-token.js.map +1 -0
- package/src/lib/server/email-delivery-log.d.ts +509 -0
- package/src/lib/server/email-delivery-log.js +1043 -0
- package/src/lib/server/email-delivery-log.js.map +1 -0
- package/src/lib/server/email-flow-gate.d.ts +80 -0
- package/src/lib/server/email-flow-gate.js +153 -0
- package/src/lib/server/email-flow-gate.js.map +1 -0
- package/src/lib/server/email-marketing-gate.d.ts +333 -0
- package/src/lib/server/email-marketing-gate.js +639 -0
- package/src/lib/server/email-marketing-gate.js.map +1 -0
- package/src/lib/server/email-metering.d.ts +293 -0
- package/src/lib/server/email-metering.js +359 -0
- package/src/lib/server/email-metering.js.map +1 -0
- package/src/lib/server/email-revenue-attribution.d.ts +185 -0
- package/src/lib/server/email-revenue-attribution.js +300 -0
- package/src/lib/server/email-revenue-attribution.js.map +1 -0
- package/src/lib/server/email-send-rate.d.ts +232 -0
- package/src/lib/server/email-send-rate.js +417 -0
- package/src/lib/server/email-send-rate.js.map +1 -0
- package/src/lib/server/email-sender-reputation.d.ts +317 -0
- package/src/lib/server/email-sender-reputation.js +464 -0
- package/src/lib/server/email-sender-reputation.js.map +1 -0
- package/src/lib/server/email-suppression.d.ts +490 -0
- package/src/lib/server/email-suppression.js +648 -0
- package/src/lib/server/email-suppression.js.map +1 -0
- package/src/lib/server/email-topic-confirmation.d.ts +150 -0
- package/src/lib/server/email-topic-confirmation.js +272 -0
- package/src/lib/server/email-topic-confirmation.js.map +1 -0
- package/src/lib/server/email-unsubscribe-link.d.ts +174 -0
- package/src/lib/server/email-unsubscribe-link.js +236 -0
- package/src/lib/server/email-unsubscribe-link.js.map +1 -0
- package/src/lib/server/erase-person.d.ts +116 -0
- package/src/lib/server/erase-person.js +272 -0
- package/src/lib/server/erase-person.js.map +1 -0
- package/src/lib/server/erase.d.ts +458 -0
- package/src/lib/server/erase.js +1506 -0
- package/src/lib/server/erase.js.map +1 -0
- package/src/lib/server/firebase-admin.d.ts +71 -0
- package/src/lib/server/firebase-admin.js +343 -0
- package/src/lib/server/firebase-admin.js.map +1 -0
- package/src/lib/server/form-dataset-binding-token.d.ts +35 -0
- package/src/lib/server/form-dataset-binding-token.js +107 -0
- package/src/lib/server/form-dataset-binding-token.js.map +1 -0
- package/src/lib/server/free-workspace-cap.d.ts +201 -0
- package/src/lib/server/free-workspace-cap.js +404 -0
- package/src/lib/server/free-workspace-cap.js.map +1 -0
- package/src/lib/server/ga4-measurement-protocol.d.ts +256 -0
- package/src/lib/server/ga4-measurement-protocol.js +558 -0
- package/src/lib/server/ga4-measurement-protocol.js.map +1 -0
- package/src/lib/server/host-email-tokens.d.ts +47 -0
- package/src/lib/server/host-email-tokens.js +72 -0
- package/src/lib/server/host-email-tokens.js.map +1 -0
- package/src/lib/server/host-memberships.d.ts +54 -0
- package/src/lib/server/host-memberships.js +194 -0
- package/src/lib/server/host-memberships.js.map +1 -0
- package/src/lib/server/host-sending-domain.d.ts +292 -0
- package/src/lib/server/host-sending-domain.js +451 -0
- package/src/lib/server/host-sending-domain.js.map +1 -0
- package/src/lib/server/host-visitor-records.d.ts +150 -0
- package/src/lib/server/host-visitor-records.js +262 -0
- package/src/lib/server/host-visitor-records.js.map +1 -0
- package/src/lib/server/id-token-refusal.d.ts +28 -0
- package/src/lib/server/id-token-refusal.js +111 -0
- package/src/lib/server/id-token-refusal.js.map +1 -0
- package/src/lib/server/legal-acceptance.d.ts +214 -0
- package/src/lib/server/legal-acceptance.js +305 -0
- package/src/lib/server/legal-acceptance.js.map +1 -0
- package/src/lib/server/list-members.d.ts +211 -0
- package/src/lib/server/list-members.js +176 -0
- package/src/lib/server/list-members.js.map +1 -0
- package/src/lib/server/live-page-usage.d.ts +202 -0
- package/src/lib/server/live-page-usage.js +300 -0
- package/src/lib/server/live-page-usage.js.map +1 -0
- package/src/lib/server/lockdown.d.ts +313 -0
- package/src/lib/server/lockdown.js +643 -0
- package/src/lib/server/lockdown.js.map +1 -0
- package/src/lib/server/media-cdn-rate-limit.d.ts +188 -0
- package/src/lib/server/media-cdn-rate-limit.js +204 -0
- package/src/lib/server/media-cdn-rate-limit.js.map +1 -0
- package/src/lib/server/media-delivery.d.ts +267 -0
- package/src/lib/server/media-delivery.js +485 -0
- package/src/lib/server/media-delivery.js.map +1 -0
- package/src/lib/server/media-download-tokens.d.ts +161 -0
- package/src/lib/server/media-download-tokens.js +386 -0
- package/src/lib/server/media-download-tokens.js.map +1 -0
- package/src/lib/server/media-quarantine.d.ts +127 -0
- package/src/lib/server/media-quarantine.js +170 -0
- package/src/lib/server/media-quarantine.js.map +1 -0
- package/src/lib/server/media-signing.d.ts +126 -0
- package/src/lib/server/media-signing.js +159 -0
- package/src/lib/server/media-signing.js.map +1 -0
- package/src/lib/server/media-storage-path.d.ts +96 -0
- package/src/lib/server/media-storage-path.js +111 -0
- package/src/lib/server/media-storage-path.js.map +1 -0
- package/src/lib/server/media-strong-digest.d.ts +85 -0
- package/src/lib/server/media-strong-digest.js +190 -0
- package/src/lib/server/media-strong-digest.js.map +1 -0
- package/src/lib/server/media-tombstone.d.ts +276 -0
- package/src/lib/server/media-tombstone.js +421 -0
- package/src/lib/server/media-tombstone.js.map +1 -0
- package/src/lib/server/media-variants.d.ts +309 -0
- package/src/lib/server/media-variants.js +428 -0
- package/src/lib/server/media-variants.js.map +1 -0
- package/src/lib/server/member-email-aliases.d.ts +180 -0
- package/src/lib/server/member-email-aliases.js +420 -0
- package/src/lib/server/member-email-aliases.js.map +1 -0
- package/src/lib/server/member-photo.d.ts +88 -0
- package/src/lib/server/member-photo.js +185 -0
- package/src/lib/server/member-photo.js.map +1 -0
- package/src/lib/server/membership-recover-throttle.d.ts +211 -0
- package/src/lib/server/membership-recover-throttle.js +231 -0
- package/src/lib/server/membership-recover-throttle.js.map +1 -0
- package/src/lib/server/notifications.d.ts +55 -0
- package/src/lib/server/notifications.js +134 -0
- package/src/lib/server/notifications.js.map +1 -0
- package/src/lib/server/org-billing.d.ts +97 -0
- package/src/lib/server/org-billing.js +236 -0
- package/src/lib/server/org-billing.js.map +1 -0
- package/src/lib/server/organizations.d.ts +837 -0
- package/src/lib/server/organizations.js +1814 -0
- package/src/lib/server/organizations.js.map +1 -0
- package/src/lib/server/password-reset-throttle.d.ts +84 -0
- package/src/lib/server/password-reset-throttle.js +102 -0
- package/src/lib/server/password-reset-throttle.js.map +1 -0
- package/src/lib/server/personal-data-export.d.ts +241 -0
- package/src/lib/server/personal-data-export.js +858 -0
- package/src/lib/server/personal-data-export.js.map +1 -0
- package/src/lib/server/platform-domain-names.d.ts +62 -0
- package/src/lib/server/platform-domain-names.js +197 -0
- package/src/lib/server/platform-domain-names.js.map +1 -0
- package/src/lib/server/platform-marketing-consent.d.ts +133 -0
- package/src/lib/server/platform-marketing-consent.js +209 -0
- package/src/lib/server/platform-marketing-consent.js.map +1 -0
- package/src/lib/server/rate-limit-store.d.ts +702 -0
- package/src/lib/server/rate-limit-store.js +1220 -0
- package/src/lib/server/rate-limit-store.js.map +1 -0
- package/src/lib/server/realm-plugins.d.ts +67 -0
- package/src/lib/server/realm-plugins.js +250 -0
- package/src/lib/server/realm-plugins.js.map +1 -0
- package/src/lib/server/recaptcha-allowlist.d.ts +90 -0
- package/src/lib/server/recaptcha-allowlist.js +409 -0
- package/src/lib/server/recaptcha-allowlist.js.map +1 -0
- package/src/lib/server/release-flags.d.ts +105 -0
- package/src/lib/server/release-flags.js +183 -0
- package/src/lib/server/release-flags.js.map +1 -0
- package/src/lib/server/replay-erasures.d.ts +126 -0
- package/src/lib/server/replay-erasures.js +217 -0
- package/src/lib/server/replay-erasures.js.map +1 -0
- package/src/lib/server/resolve-people.d.ts +66 -0
- package/src/lib/server/resolve-people.js +112 -0
- package/src/lib/server/resolve-people.js.map +1 -0
- package/src/lib/server/safe-equal.d.ts +45 -0
- package/src/lib/server/safe-equal.js +58 -0
- package/src/lib/server/safe-equal.js.map +1 -0
- package/src/lib/server/sending-domain-debt.d.ts +127 -0
- package/src/lib/server/sending-domain-debt.js +268 -0
- package/src/lib/server/sending-domain-debt.js.map +1 -0
- package/src/lib/server/sending-domain-recheck.d.ts +151 -0
- package/src/lib/server/sending-domain-recheck.js +253 -0
- package/src/lib/server/sending-domain-recheck.js.map +1 -0
- package/src/lib/server/sending-domains.d.ts +364 -0
- package/src/lib/server/sending-domains.js +683 -0
- package/src/lib/server/sending-domains.js.map +1 -0
- package/src/lib/server/serve-media-cdn.d.ts +433 -0
- package/src/lib/server/serve-media-cdn.js +1370 -0
- package/src/lib/server/serve-media-cdn.js.map +1 -0
- package/src/lib/server/serve-plugin-fetch.d.ts +61 -0
- package/src/lib/server/serve-plugin-fetch.js +294 -0
- package/src/lib/server/serve-plugin-fetch.js.map +1 -0
- package/src/lib/server/sms-keywords.d.ts +63 -0
- package/src/lib/server/sms-keywords.js +146 -0
- package/src/lib/server/sms-keywords.js.map +1 -0
- package/src/lib/server/sso-break-glass-owners.d.ts +91 -0
- package/src/lib/server/sso-break-glass-owners.js +227 -0
- package/src/lib/server/sso-break-glass-owners.js.map +1 -0
- package/src/lib/server/sso-domain-policy.d.ts +140 -0
- package/src/lib/server/sso-domain-policy.js +239 -0
- package/src/lib/server/sso-domain-policy.js.map +1 -0
- package/src/lib/server/sso-drift-logic.d.ts +106 -0
- package/src/lib/server/sso-drift-logic.js +95 -0
- package/src/lib/server/sso-drift-logic.js.map +1 -0
- package/src/lib/server/sso-enforcement.d.ts +204 -0
- package/src/lib/server/sso-enforcement.js +309 -0
- package/src/lib/server/sso-enforcement.js.map +1 -0
- package/src/lib/server/sso-provisioning.d.ts +320 -0
- package/src/lib/server/sso-provisioning.js +715 -0
- package/src/lib/server/sso-provisioning.js.map +1 -0
- package/src/lib/server/staff-alert-email.d.ts +41 -0
- package/src/lib/server/staff-alert-email.js +63 -0
- package/src/lib/server/staff-alert-email.js.map +1 -0
- package/src/lib/server/stripe-account-mode.d.ts +156 -0
- package/src/lib/server/stripe-account-mode.js +162 -0
- package/src/lib/server/stripe-account-mode.js.map +1 -0
- package/src/lib/server/tenant-write-lockdown.d.ts +131 -0
- package/src/lib/server/tenant-write-lockdown.js +176 -0
- package/src/lib/server/tenant-write-lockdown.js.map +1 -0
- package/src/lib/server/test-firestore.d.ts +45 -0
- package/src/lib/server/test-firestore.js +124 -0
- package/src/lib/server/test-firestore.js.map +1 -0
- package/src/lib/server/token-revocation.d.ts +140 -0
- package/src/lib/server/token-revocation.js +255 -0
- package/src/lib/server/token-revocation.js.map +1 -0
- package/src/lib/server/update-existing.d.ts +32 -0
- package/src/lib/server/update-existing.js +73 -0
- package/src/lib/server/update-existing.js.map +1 -0
- package/src/lib/server/upload-cors-reconcile.d.ts +118 -0
- package/src/lib/server/upload-cors-reconcile.js +234 -0
- package/src/lib/server/upload-cors-reconcile.js.map +1 -0
- package/src/lib/server/upsert-contact.d.ts +262 -0
- package/src/lib/server/upsert-contact.js +546 -0
- package/src/lib/server/upsert-contact.js.map +1 -0
- package/src/lib/server/usage-invoice.d.ts +203 -0
- package/src/lib/server/usage-invoice.js +397 -0
- package/src/lib/server/usage-invoice.js.map +1 -0
- package/src/lib/server/user-profiles.d.ts +151 -0
- package/src/lib/server/user-profiles.js +364 -0
- package/src/lib/server/user-profiles.js.map +1 -0
- package/src/lib/server/vercel-drain-signature.d.ts +30 -0
- package/src/lib/server/vercel-drain-signature.js +61 -0
- package/src/lib/server/vercel-drain-signature.js.map +1 -0
- package/src/lib/server/vercel-log-drain.d.ts +259 -0
- package/src/lib/server/vercel-log-drain.js +479 -0
- package/src/lib/server/vercel-log-drain.js.map +1 -0
- package/src/lib/server/verify-email-cooldown.d.ts +102 -0
- package/src/lib/server/verify-email-cooldown.js +114 -0
- package/src/lib/server/verify-email-cooldown.js.map +1 -0
- package/src/lib/server/visitor-write-rate-limit.d.ts +87 -0
- package/src/lib/server/visitor-write-rate-limit.js +117 -0
- package/src/lib/server/visitor-write-rate-limit.js.map +1 -0
- package/src/lib/server/workspace-domains.d.ts +156 -0
- package/src/lib/server/workspace-domains.js +271 -0
- package/src/lib/server/workspace-domains.js.map +1 -0
- package/src/tenancy.d.ts +115 -0
- package/src/tenancy.js +142 -0
- package/src/tenancy.js.map +1 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /**
|
|
18
|
+
* Duplicating a site resource whole, as a draft (AGL-2936).
|
|
19
|
+
*
|
|
20
|
+
* One module for every kind that lives under a site — screens and email
|
|
21
|
+
* designs, reusable components, layouts, templates, forms and workflows —
|
|
22
|
+
* so the console route, the AI runtime's `duplicate_resource` tool and any
|
|
23
|
+
* later door make the same copy: the same fields, the same uniqueness
|
|
24
|
+
* rules, the same band arithmetic, the same activity rows. A caller that
|
|
25
|
+
* has verified who is asking and that they may write the site hands over
|
|
26
|
+
* the ids and gets back the copy's id, or a refusal it can answer with.
|
|
27
|
+
*
|
|
28
|
+
* WHAT MAKES THE COPY A DRAFT is different per kind, and each is the
|
|
29
|
+
* omission of exactly one thing the create paths would otherwise do:
|
|
30
|
+
*
|
|
31
|
+
* - a screen is reachable only through the host's routing map, so the
|
|
32
|
+
* copy gets no entry there — it has a slug of its own (`-copy`) but no
|
|
33
|
+
* address until somebody publishes it;
|
|
34
|
+
* - a collection's entry template renders only for the collection that
|
|
35
|
+
* points at it, and nothing points at the copy;
|
|
36
|
+
* - a layout, component or form is served only where a page places it,
|
|
37
|
+
* and nothing places the copy;
|
|
38
|
+
* - a workflow runs only when its trigger fires, and the copy's trigger
|
|
39
|
+
* is cleared;
|
|
40
|
+
* - a template is inert until it is used.
|
|
41
|
+
*
|
|
42
|
+
* COUNTED LIKE A CREATE. The copy is one more document in a collection the
|
|
43
|
+
* plan bands, so it meets the same counter the create route meets — for a
|
|
44
|
+
* screen that is not a page, the flat platform ceiling rather than a band —
|
|
45
|
+
* and meets it INSIDE the transaction that writes the copy (AGL-2231): a count
|
|
46
|
+
* taken before the write is a count N concurrent copies all pass. The
|
|
47
|
+
* siblings are read once and answer three questions — the count, the name
|
|
48
|
+
* the copy may take, and the slug — because three reads of one collection
|
|
49
|
+
* is the reason a route and its readout come to disagree.
|
|
50
|
+
*
|
|
51
|
+
* THE VERSION TRAVELS WITH THE DOCUMENT. A besigner resource is opened by
|
|
52
|
+
* its version, so the copy of a screen with no version is a screen nobody
|
|
53
|
+
* can open. The source's most recently saved version is copied into the
|
|
54
|
+
* copy's own `versions` as its first — no `versioning` entitlement is
|
|
55
|
+
* owed for a first version — with its stored node bytes carried across
|
|
56
|
+
* untouched: they are msgpack at rest, and re-encoding them is the
|
|
57
|
+
* double-encode `encodeStoredNodes` exists to avoid.
|
|
58
|
+
*
|
|
59
|
+
* THE ONE TREE THAT IS REWRITTEN is a form's (AGL-3024). A form's design
|
|
60
|
+
* names its own form inside itself, so carrying those bytes across hands the
|
|
61
|
+
* copy a design that files every submission under the SOURCE — silently,
|
|
62
|
+
* because the copy still renders and the row still lands. That binding is
|
|
63
|
+
* moved onto the copy below. No other kind embeds its own id in its design,
|
|
64
|
+
* so no other kind is decoded.
|
|
65
|
+
*/ import { billableScreenIds, checkEntitlement, checkQuota, claimAttempt, createResourceUid, decodeStoredNodes, encodeStoredNodes, nameSearchKey, NON_PAGE_SCREEN_MAX_PER_HOST, nonPageScreenIds, SCREEN_KIND_EMAIL, SCREEN_KIND_TEMPLATE, screenClaimsToBeAPage } from "@aglyn/aglyn/server";
|
|
66
|
+
import { DUPLICABLE_RESOURCE_NOUNS, DUPLICATE_BUSY_MESSAGE, duplicateDisplayName, duplicateVersionNote, uniqueDuplicateName, uniqueDuplicateSlug } from "@aglyn/aglyn/app-utils/duplicate-resource";
|
|
67
|
+
import { formDesignReboundTo } from "@aglyn/aglyn/app-utils/forms";
|
|
68
|
+
import { Timestamp } from "firebase-admin/firestore";
|
|
69
|
+
import { logResourceDuplicated } from "./duplicate-activity.js";
|
|
70
|
+
import firebaseAdmin from "./firebase-admin.js";
|
|
71
|
+
/**
|
|
72
|
+
* Per kind, the same fields the create route's allow-list admits, less the
|
|
73
|
+
* ones a copy must not inherit: a screen's `versionId` (the copy mints its
|
|
74
|
+
* own), a template's `source` (stamped `authored` below — a copy of a
|
|
75
|
+
* marketplace template is the customer's own), a workflow's `trigger`
|
|
76
|
+
* (cleared, so the copy runs nothing until armed), and anything the server
|
|
77
|
+
* stamps on every create.
|
|
78
|
+
*/ const RECIPES = {
|
|
79
|
+
screen: {
|
|
80
|
+
collection: 'screens',
|
|
81
|
+
nameField: 'displayName',
|
|
82
|
+
fields: [
|
|
83
|
+
'description',
|
|
84
|
+
'seo',
|
|
85
|
+
'kind',
|
|
86
|
+
'visibility',
|
|
87
|
+
'locale',
|
|
88
|
+
'protection',
|
|
89
|
+
// The shared-layout binding. A version inherits it from its screen, so
|
|
90
|
+
// a copy without it renders with no header or footer (AGL-3120).
|
|
91
|
+
'layoutId'
|
|
92
|
+
],
|
|
93
|
+
versionParentField: 'screenId',
|
|
94
|
+
quotaKey: 'screensPerHost',
|
|
95
|
+
slug: true
|
|
96
|
+
},
|
|
97
|
+
// No `quotaKey`: an email design is not a page, so it meets the flat
|
|
98
|
+
// platform ceiling on non-page screens below rather than the page band.
|
|
99
|
+
emailDesign: {
|
|
100
|
+
collection: 'screens',
|
|
101
|
+
nameField: 'displayName',
|
|
102
|
+
fields: [
|
|
103
|
+
'description',
|
|
104
|
+
'kind'
|
|
105
|
+
],
|
|
106
|
+
versionParentField: 'screenId'
|
|
107
|
+
},
|
|
108
|
+
component: {
|
|
109
|
+
collection: 'components',
|
|
110
|
+
nameField: 'displayName',
|
|
111
|
+
fields: [
|
|
112
|
+
'description',
|
|
113
|
+
'icon',
|
|
114
|
+
'rootId',
|
|
115
|
+
'nodes',
|
|
116
|
+
'props'
|
|
117
|
+
],
|
|
118
|
+
versionParentField: 'componentId',
|
|
119
|
+
entitlement: 'reusableComponents'
|
|
120
|
+
},
|
|
121
|
+
layout: {
|
|
122
|
+
collection: 'layouts',
|
|
123
|
+
nameField: 'displayName',
|
|
124
|
+
fields: [
|
|
125
|
+
'description'
|
|
126
|
+
],
|
|
127
|
+
versionParentField: 'layoutId',
|
|
128
|
+
quotaKey: 'sharedLayoutsPerHost'
|
|
129
|
+
},
|
|
130
|
+
template: {
|
|
131
|
+
collection: 'templates',
|
|
132
|
+
nameField: 'displayName',
|
|
133
|
+
fields: [
|
|
134
|
+
'kind',
|
|
135
|
+
'description',
|
|
136
|
+
'category',
|
|
137
|
+
'placeholders',
|
|
138
|
+
'nodes',
|
|
139
|
+
'rootId',
|
|
140
|
+
'props',
|
|
141
|
+
'slug',
|
|
142
|
+
'seo',
|
|
143
|
+
'theme'
|
|
144
|
+
],
|
|
145
|
+
quotaKey: 'templatesPerHost',
|
|
146
|
+
stamps: {
|
|
147
|
+
source: {
|
|
148
|
+
type: 'authored'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
form: {
|
|
153
|
+
collection: 'forms',
|
|
154
|
+
nameField: 'displayName',
|
|
155
|
+
fields: [
|
|
156
|
+
'fields',
|
|
157
|
+
'consentFieldName',
|
|
158
|
+
'routing',
|
|
159
|
+
'legacyMatch',
|
|
160
|
+
'rootId',
|
|
161
|
+
'nodes'
|
|
162
|
+
],
|
|
163
|
+
versionParentField: 'formId',
|
|
164
|
+
quotaKey: 'formsPerHost',
|
|
165
|
+
entitlement: 'reusableComponents',
|
|
166
|
+
slug: true
|
|
167
|
+
},
|
|
168
|
+
workflow: {
|
|
169
|
+
collection: 'workflows',
|
|
170
|
+
nameField: 'name',
|
|
171
|
+
fields: [
|
|
172
|
+
'steps',
|
|
173
|
+
'returnValue'
|
|
174
|
+
],
|
|
175
|
+
quotaKey: 'workflowsPerHost',
|
|
176
|
+
entitlement: 'workflows',
|
|
177
|
+
stamps: {
|
|
178
|
+
trigger: null
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const asRecord = (value)=>value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
183
|
+
/**
|
|
184
|
+
* Makes the copy. The caller has authenticated `uid` and verified that they
|
|
185
|
+
* may write content on `hostId`; this decides everything after that.
|
|
186
|
+
*/ export async function duplicateResource(kind, options) {
|
|
187
|
+
var _options_sourceId, _options_attemptKey;
|
|
188
|
+
const recipe = RECIPES[kind];
|
|
189
|
+
const noun = DUPLICABLE_RESOURCE_NOUNS[kind];
|
|
190
|
+
const sourceId = String((_options_sourceId = options.sourceId) != null ? _options_sourceId : '').trim().slice(0, 64);
|
|
191
|
+
if (!sourceId) return {
|
|
192
|
+
ok: false,
|
|
193
|
+
status: 400,
|
|
194
|
+
error: `Missing the ${noun} to copy`
|
|
195
|
+
};
|
|
196
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
197
|
+
const attemptKey = String((_options_attemptKey = options.attemptKey) != null ? _options_attemptKey : '').trim().slice(0, 200);
|
|
198
|
+
const claimed = attemptKey ? await claimAttempt(firestore, {
|
|
199
|
+
kind: `duplicate-${kind}`,
|
|
200
|
+
scopeId: `${options.hostId}:${sourceId}`,
|
|
201
|
+
orgId: options.orgId,
|
|
202
|
+
key: attemptKey,
|
|
203
|
+
busyMessage: DUPLICATE_BUSY_MESSAGE
|
|
204
|
+
}) : null;
|
|
205
|
+
if (claimed && 'replay' in claimed) {
|
|
206
|
+
return claimed.replay.body;
|
|
207
|
+
}
|
|
208
|
+
const claim = claimed && 'claim' in claimed ? claimed.claim : null;
|
|
209
|
+
try {
|
|
210
|
+
const result = await copy(firestore, kind, recipe, _extends({}, options, {
|
|
211
|
+
sourceId
|
|
212
|
+
}));
|
|
213
|
+
if (result.ok) {
|
|
214
|
+
var _options_email;
|
|
215
|
+
await logResourceDuplicated(kind, {
|
|
216
|
+
uid: options.uid,
|
|
217
|
+
email: (_options_email = options.email) != null ? _options_email : null
|
|
218
|
+
}, {
|
|
219
|
+
orgId: options.orgId,
|
|
220
|
+
hostId: options.hostId,
|
|
221
|
+
source: {
|
|
222
|
+
id: sourceId,
|
|
223
|
+
name: result.sourceName
|
|
224
|
+
},
|
|
225
|
+
target: {
|
|
226
|
+
id: result.id,
|
|
227
|
+
name: result.name,
|
|
228
|
+
versionId: result.versionId
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
const answer = {
|
|
232
|
+
ok: true,
|
|
233
|
+
id: result.id,
|
|
234
|
+
versionId: result.versionId,
|
|
235
|
+
name: result.name
|
|
236
|
+
};
|
|
237
|
+
await (claim == null ? void 0 : claim.record(200, answer));
|
|
238
|
+
return answer;
|
|
239
|
+
}
|
|
240
|
+
// A refusal releases the key: the person fixes the cause — frees a slot,
|
|
241
|
+
// picks another name — and presses the same button again.
|
|
242
|
+
await (claim == null ? void 0 : claim.release());
|
|
243
|
+
return result;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
await (claim == null ? void 0 : claim.release());
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function copy(firestore, kind, recipe, options) {
|
|
250
|
+
var _options_org, _data;
|
|
251
|
+
const { hostId, sourceId, uid } = options;
|
|
252
|
+
const noun = DUPLICABLE_RESOURCE_NOUNS[kind];
|
|
253
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
254
|
+
const collectionRef = hostRef.collection(recipe.collection);
|
|
255
|
+
const sourceRef = collectionRef.doc(sourceId);
|
|
256
|
+
const org = (_options_org = options.org) != null ? _options_org : (_data = (await firestore.collection('orgs').doc(options.orgId).get()).data()) != null ? _data : null;
|
|
257
|
+
if (recipe.entitlement && !checkEntitlement(org, recipe.entitlement)) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
status: 403,
|
|
261
|
+
error: 'This feature is not included in your plan — see Billing'
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const id = createResourceUid();
|
|
265
|
+
const versionId = recipe.versionParentField ? createResourceUid() : null;
|
|
266
|
+
return firestore.runTransaction(async (tx)=>{
|
|
267
|
+
var _data_recipe_nameField, _options_name, _recipe_stamps;
|
|
268
|
+
// ALL READS BEFORE ANY WRITE, which Firestore requires. The host is read
|
|
269
|
+
// inside the transaction rather than passed in because its routing map
|
|
270
|
+
// is an input to the screen count and is ordinary client-writable.
|
|
271
|
+
const siblingFields = [
|
|
272
|
+
recipe.nameField,
|
|
273
|
+
'slug',
|
|
274
|
+
'kind',
|
|
275
|
+
'deletedAt',
|
|
276
|
+
'source.type'
|
|
277
|
+
];
|
|
278
|
+
const [hostSnapshot, source, siblingsSnapshot] = await Promise.all([
|
|
279
|
+
tx.get(hostRef),
|
|
280
|
+
tx.get(sourceRef),
|
|
281
|
+
tx.get(collectionRef.select(...siblingFields))
|
|
282
|
+
]);
|
|
283
|
+
if (!hostSnapshot.exists) return {
|
|
284
|
+
ok: false,
|
|
285
|
+
status: 404,
|
|
286
|
+
error: 'Unknown site'
|
|
287
|
+
};
|
|
288
|
+
if (!source.exists || source.get('deletedAt') != null) {
|
|
289
|
+
return {
|
|
290
|
+
ok: false,
|
|
291
|
+
status: 404,
|
|
292
|
+
error: `Unknown ${noun}`
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const data = asRecord(source.data());
|
|
296
|
+
// The two screen kinds share a collection and are told apart by `kind`:
|
|
297
|
+
// an email design copied as a page would land on the Screens list, and
|
|
298
|
+
// the other way round would put a page on the Emails page.
|
|
299
|
+
//
|
|
300
|
+
// Of the screens that are not pages, only a collection's entry template
|
|
301
|
+
// may be copied as a screen (AGL-3102), and the copy stays a template:
|
|
302
|
+
// `kind` rides across with the recipe's fields, so it composes nothing
|
|
303
|
+
// until a collection points at it — which is how a second collection
|
|
304
|
+
// starts from the first one's design. An error screen is refused:
|
|
305
|
+
// `kind: 'error'` is the slot assignment's to stamp, because that write
|
|
306
|
+
// is what bounds how many a site holds, and a copy would stamp one
|
|
307
|
+
// outside it.
|
|
308
|
+
const sourceKind = data['kind'];
|
|
309
|
+
const nonPageScreen = recipe.collection === 'screens' && !screenClaimsToBeAPage({
|
|
310
|
+
kind: sourceKind
|
|
311
|
+
});
|
|
312
|
+
if (recipe.collection === 'screens') {
|
|
313
|
+
if (kind === 'emailDesign' && sourceKind !== SCREEN_KIND_EMAIL) {
|
|
314
|
+
return {
|
|
315
|
+
ok: false,
|
|
316
|
+
status: 400,
|
|
317
|
+
error: 'That screen is not an email design'
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (kind === 'screen' && nonPageScreen && sourceKind !== SCREEN_KIND_TEMPLATE) {
|
|
321
|
+
return {
|
|
322
|
+
ok: false,
|
|
323
|
+
status: 400,
|
|
324
|
+
error: 'Only a page or a collection entry template can be duplicated as a screen'
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const siblings = siblingsSnapshot.docs.map((row)=>{
|
|
329
|
+
var _row_get, _row_get1;
|
|
330
|
+
return {
|
|
331
|
+
id: row.id,
|
|
332
|
+
name: String((_row_get = row.get(recipe.nameField)) != null ? _row_get : ''),
|
|
333
|
+
slug: String((_row_get1 = row.get('slug')) != null ? _row_get1 : ''),
|
|
334
|
+
kind: row.get('kind'),
|
|
335
|
+
deletedAt: row.get('deletedAt'),
|
|
336
|
+
sourceType: row.get('source.type')
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
const live = siblings.filter((row)=>row.deletedAt == null);
|
|
340
|
+
// THE BAND, met inside the transaction (AGL-2231) with the create route's
|
|
341
|
+
// own arithmetic: screens count what the routing map makes billable,
|
|
342
|
+
// templates exclude the platform's starters, everything else counts its
|
|
343
|
+
// live documents. A screen copy that is not a page — an email design or
|
|
344
|
+
// an entry template — meets the flat platform ceiling on non-page screens
|
|
345
|
+
// instead, the one an email design's create meets and a demoted template
|
|
346
|
+
// fills, and never the page band: it spends none of it.
|
|
347
|
+
const screenRows = siblings.map((row)=>({
|
|
348
|
+
id: row.id,
|
|
349
|
+
kind: row.kind,
|
|
350
|
+
deletedAt: row.deletedAt
|
|
351
|
+
}));
|
|
352
|
+
const routingMap = hostSnapshot.get('screens');
|
|
353
|
+
if (nonPageScreen) {
|
|
354
|
+
if (nonPageScreenIds(screenRows, routingMap).size >= NON_PAGE_SCREEN_MAX_PER_HOST) {
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
status: 403,
|
|
358
|
+
error: 'This site is at its limit of ' + `${NON_PAGE_SCREEN_MAX_PER_HOST} email and template screens — ` + 'delete some to make room'
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
} else if (recipe.quotaKey) {
|
|
362
|
+
const used = kind === 'screen' ? billableScreenIds(screenRows, routingMap).size : kind === 'template' ? live.filter((row)=>row.sourceType !== 'starter').length : live.length;
|
|
363
|
+
const quota = checkQuota(org, recipe.quotaKey, used);
|
|
364
|
+
if (!quota.allowed) {
|
|
365
|
+
return {
|
|
366
|
+
ok: false,
|
|
367
|
+
status: 403,
|
|
368
|
+
error: `Your plan includes ${quota.limit} ${noun}s — ` + 'upgrade in Billing for more'
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// THE NAME AND THE SLUG, unique among the live siblings. A deleted
|
|
373
|
+
// sibling's name is free: a person who deleted "Home" and copies
|
|
374
|
+
// "About" as "Home" is not confused by a tombstone.
|
|
375
|
+
const sourceName = String((_data_recipe_nameField = data[recipe.nameField]) != null ? _data_recipe_nameField : '').trim();
|
|
376
|
+
const requested = String((_options_name = options.name) != null ? _options_name : '').trim() || duplicateDisplayName(sourceName);
|
|
377
|
+
const name = uniqueDuplicateName(requested, live.map((row)=>row.name));
|
|
378
|
+
const slug = recipe.slug ? uniqueDuplicateSlug(data['slug'], live.map((row)=>row.slug)) : undefined;
|
|
379
|
+
// THE VERSION: the most recently saved one, else the one the document
|
|
380
|
+
// points at. Read inside the transaction so the copy is of what was
|
|
381
|
+
// saved when the count was taken.
|
|
382
|
+
let versionData = null;
|
|
383
|
+
let sourceVersionNumber = null;
|
|
384
|
+
if (recipe.versionParentField) {
|
|
385
|
+
var _newest_docs_, _data_versionId;
|
|
386
|
+
const versionsRef = sourceRef.collection('versions');
|
|
387
|
+
const [newest, all] = await Promise.all([
|
|
388
|
+
tx.get(versionsRef.orderBy('updatedAt', 'desc').limit(1)),
|
|
389
|
+
tx.get(versionsRef.select())
|
|
390
|
+
]);
|
|
391
|
+
let found = (_newest_docs_ = newest.docs[0]) != null ? _newest_docs_ : null;
|
|
392
|
+
const pointer = String((_data_versionId = data['versionId']) != null ? _data_versionId : '');
|
|
393
|
+
if (!found && pointer) {
|
|
394
|
+
const pointed = await tx.get(versionsRef.doc(pointer));
|
|
395
|
+
if (pointed.exists) found = pointed;
|
|
396
|
+
}
|
|
397
|
+
if (found) {
|
|
398
|
+
versionData = asRecord(found.data());
|
|
399
|
+
sourceVersionNumber = all.size;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// THE DOCUMENT: the recipe's fields, the name, the slug, the stamps.
|
|
403
|
+
const doc = {};
|
|
404
|
+
for (const field of recipe.fields){
|
|
405
|
+
if (data[field] !== undefined) doc[field] = data[field];
|
|
406
|
+
}
|
|
407
|
+
doc[recipe.nameField] = name;
|
|
408
|
+
if (recipe.slug) {
|
|
409
|
+
if (slug) doc['slug'] = slug;
|
|
410
|
+
else delete doc['slug'];
|
|
411
|
+
}
|
|
412
|
+
/*
|
|
413
|
+
* THE FORM'S BINDING, moved onto the copy (AGL-3024).
|
|
414
|
+
*
|
|
415
|
+
* A form's design names its own form in a prop, and that name is what
|
|
416
|
+
* `/api/forms/submit` files a submission under. Copied verbatim it still
|
|
417
|
+
* names the SOURCE, so the copy renders, collects, and files everything
|
|
418
|
+
* it collects under the form it was copied from — while its own list
|
|
419
|
+
* stays empty and nothing anywhere reports an error. `checkFormContract`
|
|
420
|
+
* is what the console's form page banners it with; `formDesignReboundTo`
|
|
421
|
+
* is the write side of the same fact.
|
|
422
|
+
*
|
|
423
|
+
* This is the one place a copied tree is not carried across untouched,
|
|
424
|
+
* and the exception is narrow on purpose: no other kind here embeds its
|
|
425
|
+
* own id in its design. `formDesignReboundTo` answers `null` when there
|
|
426
|
+
* is nothing to move, so every other kind — and a form whose design is
|
|
427
|
+
* already correct — pays nothing for this.
|
|
428
|
+
*/ const rebound = (raw)=>{
|
|
429
|
+
var _formDesignReboundTo;
|
|
430
|
+
if (kind !== 'form') return raw;
|
|
431
|
+
return (_formDesignReboundTo = formDesignReboundTo(decodeStoredNodes(raw), {
|
|
432
|
+
formId: id,
|
|
433
|
+
formName: name
|
|
434
|
+
})) != null ? _formDesignReboundTo : raw;
|
|
435
|
+
};
|
|
436
|
+
// The stored tree is msgpack at rest; `encodeStoredNodes` passes encoded
|
|
437
|
+
// bytes through and packs a plain map, so a legacy plain source lands
|
|
438
|
+
// compressed like every create does — and so does a rebound one, which
|
|
439
|
+
// comes back decoded.
|
|
440
|
+
const packed = encodeStoredNodes(rebound(doc['nodes']));
|
|
441
|
+
if (packed) doc['nodes'] = Buffer.from(packed);
|
|
442
|
+
tx.create(collectionRef.doc(id), _extends({}, doc, (_recipe_stamps = recipe.stamps) != null ? _recipe_stamps : {}, recipe.collection === 'screens' ? {
|
|
443
|
+
nameLower: nameSearchKey(name)
|
|
444
|
+
} : {}, versionData && versionId ? {
|
|
445
|
+
versionId
|
|
446
|
+
} : {}, {
|
|
447
|
+
createdAt: Timestamp.now(),
|
|
448
|
+
updatedAt: Timestamp.now(),
|
|
449
|
+
createdBy: uid
|
|
450
|
+
}));
|
|
451
|
+
if (versionData && versionId && recipe.versionParentField) {
|
|
452
|
+
const versionNodes = encodeStoredNodes(rebound(versionData['nodes']));
|
|
453
|
+
tx.create(collectionRef.doc(id).collection('versions').doc(versionId), _extends({}, versionData, versionNodes ? {
|
|
454
|
+
nodes: Buffer.from(versionNodes)
|
|
455
|
+
} : {}, {
|
|
456
|
+
[recipe.versionParentField]: id,
|
|
457
|
+
hostId,
|
|
458
|
+
displayName: duplicateVersionNote(sourceName, sourceVersionNumber),
|
|
459
|
+
createdAt: Timestamp.now(),
|
|
460
|
+
updatedAt: Timestamp.now(),
|
|
461
|
+
createdBy: uid
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
ok: true,
|
|
466
|
+
id,
|
|
467
|
+
versionId: versionData && versionId ? versionId : null,
|
|
468
|
+
name,
|
|
469
|
+
sourceName
|
|
470
|
+
};
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
//# sourceMappingURL=duplicate-resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/duplicate-resource.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\n/**\n * Duplicating a site resource whole, as a draft (AGL-2936).\n *\n * One module for every kind that lives under a site — screens and email\n * designs, reusable components, layouts, templates, forms and workflows —\n * so the console route, the AI runtime's `duplicate_resource` tool and any\n * later door make the same copy: the same fields, the same uniqueness\n * rules, the same band arithmetic, the same activity rows. A caller that\n * has verified who is asking and that they may write the site hands over\n * the ids and gets back the copy's id, or a refusal it can answer with.\n *\n * WHAT MAKES THE COPY A DRAFT is different per kind, and each is the\n * omission of exactly one thing the create paths would otherwise do:\n *\n * - a screen is reachable only through the host's routing map, so the\n * copy gets no entry there — it has a slug of its own (`-copy`) but no\n * address until somebody publishes it;\n * - a collection's entry template renders only for the collection that\n * points at it, and nothing points at the copy;\n * - a layout, component or form is served only where a page places it,\n * and nothing places the copy;\n * - a workflow runs only when its trigger fires, and the copy's trigger\n * is cleared;\n * - a template is inert until it is used.\n *\n * COUNTED LIKE A CREATE. The copy is one more document in a collection the\n * plan bands, so it meets the same counter the create route meets — for a\n * screen that is not a page, the flat platform ceiling rather than a band —\n * and meets it INSIDE the transaction that writes the copy (AGL-2231): a count\n * taken before the write is a count N concurrent copies all pass. The\n * siblings are read once and answer three questions — the count, the name\n * the copy may take, and the slug — because three reads of one collection\n * is the reason a route and its readout come to disagree.\n *\n * THE VERSION TRAVELS WITH THE DOCUMENT. A besigner resource is opened by\n * its version, so the copy of a screen with no version is a screen nobody\n * can open. The source's most recently saved version is copied into the\n * copy's own `versions` as its first — no `versioning` entitlement is\n * owed for a first version — with its stored node bytes carried across\n * untouched: they are msgpack at rest, and re-encoding them is the\n * double-encode `encodeStoredNodes` exists to avoid.\n *\n * THE ONE TREE THAT IS REWRITTEN is a form's (AGL-3024). A form's design\n * names its own form inside itself, so carrying those bytes across hands the\n * copy a design that files every submission under the SOURCE — silently,\n * because the copy still renders and the row still lands. That binding is\n * moved onto the copy below. No other kind embeds its own id in its design,\n * so no other kind is decoded.\n */\n\nimport {\n billableScreenIds,\n checkEntitlement,\n checkQuota,\n claimAttempt,\n createResourceUid,\n decodeStoredNodes,\n encodeStoredNodes,\n nameSearchKey,\n NON_PAGE_SCREEN_MAX_PER_HOST,\n nonPageScreenIds,\n SCREEN_KIND_EMAIL,\n SCREEN_KIND_TEMPLATE,\n screenClaimsToBeAPage,\n type BillableScreenSource,\n type OrgEntitlements,\n type OrgFeatureFlags,\n} from '@aglyn/aglyn/server'\nimport {\n DUPLICABLE_RESOURCE_NOUNS,\n DUPLICATE_BUSY_MESSAGE,\n duplicateDisplayName,\n duplicateVersionNote,\n uniqueDuplicateName,\n uniqueDuplicateSlug,\n type DuplicableHostResourceKind,\n} from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { formDesignReboundTo } from '@aglyn/aglyn/app-utils/forms'\nimport { Timestamp } from 'firebase-admin/firestore'\nimport { logResourceDuplicated } from './duplicate-activity'\nimport firebaseAdmin from './firebase-admin'\n\n/** How the copy of each kind is made. */\ninterface KindRecipe {\n collection: string\n /** The field the name lives in — `name` on a workflow, `displayName` elsewhere. */\n nameField: 'displayName' | 'name'\n /** The source fields carried onto the copy; everything else stays behind. */\n fields: readonly string[]\n /** The back-pointer a copied version carries, when the kind versions at all. */\n versionParentField?: 'screenId' | 'layoutId' | 'componentId' | 'formId'\n /** The plan counter the copy is charged to. */\n quotaKey?: keyof OrgEntitlements & string\n /** The feature the plan must include for the kind to exist at all. */\n entitlement?: keyof OrgFeatureFlags\n /** Whether a `slug` on the source becomes a unique `-copy` slug. */\n slug?: boolean\n /** What the copy carries beyond the source's fields. */\n stamps?: Record<string, unknown>\n}\n\n/**\n * Per kind, the same fields the create route's allow-list admits, less the\n * ones a copy must not inherit: a screen's `versionId` (the copy mints its\n * own), a template's `source` (stamped `authored` below — a copy of a\n * marketplace template is the customer's own), a workflow's `trigger`\n * (cleared, so the copy runs nothing until armed), and anything the server\n * stamps on every create.\n */\nconst RECIPES: Record<DuplicableHostResourceKind, KindRecipe> = {\n screen: {\n collection: 'screens',\n nameField: 'displayName',\n fields: [\n 'description',\n 'seo',\n 'kind',\n 'visibility',\n 'locale',\n 'protection',\n // The shared-layout binding. A version inherits it from its screen, so\n // a copy without it renders with no header or footer (AGL-3120).\n 'layoutId',\n ],\n versionParentField: 'screenId',\n quotaKey: 'screensPerHost',\n slug: true,\n },\n // No `quotaKey`: an email design is not a page, so it meets the flat\n // platform ceiling on non-page screens below rather than the page band.\n emailDesign: {\n collection: 'screens',\n nameField: 'displayName',\n fields: ['description', 'kind'],\n versionParentField: 'screenId',\n },\n component: {\n collection: 'components',\n nameField: 'displayName',\n fields: ['description', 'icon', 'rootId', 'nodes', 'props'],\n versionParentField: 'componentId',\n entitlement: 'reusableComponents',\n },\n layout: {\n collection: 'layouts',\n nameField: 'displayName',\n fields: ['description'],\n versionParentField: 'layoutId',\n quotaKey: 'sharedLayoutsPerHost',\n },\n template: {\n collection: 'templates',\n nameField: 'displayName',\n fields: [\n 'kind',\n 'description',\n 'category',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n 'theme',\n ],\n quotaKey: 'templatesPerHost',\n stamps: { source: { type: 'authored' } },\n },\n form: {\n collection: 'forms',\n nameField: 'displayName',\n fields: [\n 'fields',\n 'consentFieldName',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n versionParentField: 'formId',\n quotaKey: 'formsPerHost',\n entitlement: 'reusableComponents',\n slug: true,\n },\n workflow: {\n collection: 'workflows',\n nameField: 'name',\n fields: ['steps', 'returnValue'],\n quotaKey: 'workflowsPerHost',\n entitlement: 'workflows',\n stamps: { trigger: null },\n },\n}\n\nexport interface DuplicateResourceOptions {\n orgId: string\n hostId: string\n sourceId: string\n /** The name the copy takes; blank means `Copy of <source>`. */\n name?: string | null\n /** The verified person making the copy. */\n uid: string\n email?: string | null\n /**\n * The owning org's document, when the caller already holds it; read\n * here otherwise. The bands are resolved from it.\n */\n org?: Record<string, unknown> | null\n /**\n * A client-minted key for this attempt, so a double click makes one copy\n * and a retry of a finished attempt answers what the first did. A caller\n * with no client behind it — the AI runtime — sends none and is not\n * claimed.\n */\n attemptKey?: string | null\n}\n\nexport type DuplicateResourceResult =\n | {\n ok: true\n id: string\n /** The copy's first version, for the kinds that version. */\n versionId: string | null\n name: string\n }\n | { ok: false; status: number; error: string }\n\n/** What a sibling contributes to the three answers the siblings give. */\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n deletedAt: unknown\n sourceType: unknown\n}\n\nconst asRecord = (value: unknown): Record<string, unknown> =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n\n/**\n * Makes the copy. The caller has authenticated `uid` and verified that they\n * may write content on `hostId`; this decides everything after that.\n */\nexport async function duplicateResource(\n kind: DuplicableHostResourceKind,\n options: DuplicateResourceOptions,\n): Promise<DuplicateResourceResult> {\n const recipe = RECIPES[kind]\n const noun = DUPLICABLE_RESOURCE_NOUNS[kind]\n const sourceId = String(options.sourceId ?? '').trim().slice(0, 64)\n if (!sourceId) return { ok: false, status: 400, error: `Missing the ${noun} to copy` }\n const firestore = firebaseAdmin.app().firestore()\n\n const attemptKey = String(options.attemptKey ?? '').trim().slice(0, 200)\n const claimed = attemptKey\n ? await claimAttempt(firestore as never, {\n kind: `duplicate-${kind}`,\n scopeId: `${options.hostId}:${sourceId}`,\n orgId: options.orgId,\n key: attemptKey,\n busyMessage: DUPLICATE_BUSY_MESSAGE,\n })\n : null\n if (claimed && 'replay' in claimed) {\n return claimed.replay.body as DuplicateResourceResult\n }\n const claim = claimed && 'claim' in claimed ? claimed.claim : null\n\n try {\n const result = await copy(firestore, kind, recipe, { ...options, sourceId })\n if (result.ok) {\n await logResourceDuplicated(\n kind,\n { uid: options.uid, email: options.email ?? null },\n {\n orgId: options.orgId,\n hostId: options.hostId,\n source: { id: sourceId, name: result.sourceName },\n target: { id: result.id, name: result.name, versionId: result.versionId },\n },\n )\n const answer: DuplicateResourceResult = {\n ok: true,\n id: result.id,\n versionId: result.versionId,\n name: result.name,\n }\n await claim?.record(200, answer)\n return answer\n }\n // A refusal releases the key: the person fixes the cause — frees a slot,\n // picks another name — and presses the same button again.\n await claim?.release()\n return result\n } catch (error) {\n await claim?.release()\n throw error\n }\n}\n\ntype CopyOutcome =\n | { ok: true; id: string; versionId: string | null; name: string; sourceName: string }\n | { ok: false; status: number; error: string }\n\nasync function copy(\n firestore: FirebaseFirestore.Firestore,\n kind: DuplicableHostResourceKind,\n recipe: KindRecipe,\n options: DuplicateResourceOptions & { sourceId: string },\n): Promise<CopyOutcome> {\n const { hostId, sourceId, uid } = options\n const noun = DUPLICABLE_RESOURCE_NOUNS[kind]\n const hostRef = firestore.collection('hosts').doc(hostId)\n const collectionRef = hostRef.collection(recipe.collection)\n const sourceRef = collectionRef.doc(sourceId)\n const org =\n options.org ??\n (((await firestore.collection('orgs').doc(options.orgId).get()).data() ??\n null) as Record<string, unknown> | null)\n\n if (recipe.entitlement && !checkEntitlement(org as never, recipe.entitlement)) {\n return {\n ok: false,\n status: 403,\n error: 'This feature is not included in your plan — see Billing',\n }\n }\n\n const id = createResourceUid()\n const versionId = recipe.versionParentField ? createResourceUid() : null\n\n return firestore.runTransaction(async (tx): Promise<CopyOutcome> => {\n // ALL READS BEFORE ANY WRITE, which Firestore requires. The host is read\n // inside the transaction rather than passed in because its routing map\n // is an input to the screen count and is ordinary client-writable.\n const siblingFields = [\n recipe.nameField,\n 'slug',\n 'kind',\n 'deletedAt',\n 'source.type',\n ]\n const [hostSnapshot, source, siblingsSnapshot] = await Promise.all([\n tx.get(hostRef),\n tx.get(sourceRef),\n tx.get(collectionRef.select(...siblingFields)),\n ])\n if (!hostSnapshot.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (!source.exists || source.get('deletedAt') != null) {\n return { ok: false, status: 404, error: `Unknown ${noun}` }\n }\n const data = asRecord(source.data())\n\n // The two screen kinds share a collection and are told apart by `kind`:\n // an email design copied as a page would land on the Screens list, and\n // the other way round would put a page on the Emails page.\n //\n // Of the screens that are not pages, only a collection's entry template\n // may be copied as a screen (AGL-3102), and the copy stays a template:\n // `kind` rides across with the recipe's fields, so it composes nothing\n // until a collection points at it — which is how a second collection\n // starts from the first one's design. An error screen is refused:\n // `kind: 'error'` is the slot assignment's to stamp, because that write\n // is what bounds how many a site holds, and a copy would stamp one\n // outside it.\n const sourceKind = data['kind']\n const nonPageScreen =\n recipe.collection === 'screens' &&\n !screenClaimsToBeAPage({ kind: sourceKind as string })\n if (recipe.collection === 'screens') {\n if (kind === 'emailDesign' && sourceKind !== SCREEN_KIND_EMAIL) {\n return { ok: false, status: 400, error: 'That screen is not an email design' }\n }\n if (kind === 'screen' && nonPageScreen && sourceKind !== SCREEN_KIND_TEMPLATE) {\n return {\n ok: false,\n status: 400,\n error: 'Only a page or a collection entry template can be duplicated as a screen',\n }\n }\n }\n\n const siblings: SiblingRow[] = siblingsSnapshot.docs.map((row) => ({\n id: row.id,\n name: String(row.get(recipe.nameField) ?? ''),\n slug: String(row.get('slug') ?? ''),\n kind: row.get('kind'),\n deletedAt: row.get('deletedAt'),\n sourceType: row.get('source.type'),\n }))\n const live = siblings.filter((row) => row.deletedAt == null)\n\n // THE BAND, met inside the transaction (AGL-2231) with the create route's\n // own arithmetic: screens count what the routing map makes billable,\n // templates exclude the platform's starters, everything else counts its\n // live documents. A screen copy that is not a page — an email design or\n // an entry template — meets the flat platform ceiling on non-page screens\n // instead, the one an email design's create meets and a demoted template\n // fills, and never the page band: it spends none of it.\n const screenRows: BillableScreenSource[] = siblings.map((row) => ({\n id: row.id,\n kind: row.kind,\n deletedAt: row.deletedAt,\n }))\n const routingMap = hostSnapshot.get('screens') as never\n if (nonPageScreen) {\n if (nonPageScreenIds(screenRows, routingMap).size >= NON_PAGE_SCREEN_MAX_PER_HOST) {\n return {\n ok: false,\n status: 403,\n error:\n 'This site is at its limit of ' +\n `${NON_PAGE_SCREEN_MAX_PER_HOST} email and template screens — ` +\n 'delete some to make room',\n }\n }\n } else if (recipe.quotaKey) {\n const used =\n kind === 'screen'\n ? billableScreenIds(screenRows, routingMap).size\n : kind === 'template'\n ? live.filter((row) => row.sourceType !== 'starter').length\n : live.length\n const quota = checkQuota(org as never, recipe.quotaKey as never, used)\n if (!quota.allowed) {\n return {\n ok: false,\n status: 403,\n error:\n `Your plan includes ${quota.limit} ${noun}s — ` +\n 'upgrade in Billing for more',\n }\n }\n }\n\n // THE NAME AND THE SLUG, unique among the live siblings. A deleted\n // sibling's name is free: a person who deleted \"Home\" and copies\n // \"About\" as \"Home\" is not confused by a tombstone.\n const sourceName = String(data[recipe.nameField] ?? '').trim()\n const requested = String(options.name ?? '').trim() || duplicateDisplayName(sourceName)\n const name = uniqueDuplicateName(\n requested,\n live.map((row) => row.name),\n )\n const slug = recipe.slug\n ? uniqueDuplicateSlug(\n data['slug'] as string | undefined,\n live.map((row) => row.slug),\n )\n : undefined\n\n // THE VERSION: the most recently saved one, else the one the document\n // points at. Read inside the transaction so the copy is of what was\n // saved when the count was taken.\n let versionData: Record<string, unknown> | null = null\n let sourceVersionNumber: number | null = null\n if (recipe.versionParentField) {\n const versionsRef = sourceRef.collection('versions')\n const [newest, all] = await Promise.all([\n tx.get(versionsRef.orderBy('updatedAt', 'desc').limit(1)),\n tx.get(versionsRef.select()),\n ])\n let found: FirebaseFirestore.DocumentSnapshot | null = newest.docs[0] ?? null\n const pointer = String(data['versionId'] ?? '')\n if (!found && pointer) {\n const pointed = await tx.get(versionsRef.doc(pointer))\n if (pointed.exists) found = pointed\n }\n if (found) {\n versionData = asRecord(found.data())\n sourceVersionNumber = all.size\n }\n }\n\n // THE DOCUMENT: the recipe's fields, the name, the slug, the stamps.\n const doc: Record<string, unknown> = {}\n for (const field of recipe.fields) {\n if (data[field] !== undefined) doc[field] = data[field]\n }\n doc[recipe.nameField] = name\n if (recipe.slug) {\n if (slug) doc['slug'] = slug\n else delete doc['slug']\n }\n /*\n * THE FORM'S BINDING, moved onto the copy (AGL-3024).\n *\n * A form's design names its own form in a prop, and that name is what\n * `/api/forms/submit` files a submission under. Copied verbatim it still\n * names the SOURCE, so the copy renders, collects, and files everything\n * it collects under the form it was copied from — while its own list\n * stays empty and nothing anywhere reports an error. `checkFormContract`\n * is what the console's form page banners it with; `formDesignReboundTo`\n * is the write side of the same fact.\n *\n * This is the one place a copied tree is not carried across untouched,\n * and the exception is narrow on purpose: no other kind here embeds its\n * own id in its design. `formDesignReboundTo` answers `null` when there\n * is nothing to move, so every other kind — and a form whose design is\n * already correct — pays nothing for this.\n */\n const rebound = (raw: unknown): unknown => {\n if (kind !== 'form') return raw\n return (\n formDesignReboundTo(decodeStoredNodes(raw), {\n formId: id,\n formName: name,\n }) ?? raw\n )\n }\n // The stored tree is msgpack at rest; `encodeStoredNodes` passes encoded\n // bytes through and packs a plain map, so a legacy plain source lands\n // compressed like every create does — and so does a rebound one, which\n // comes back decoded.\n const packed = encodeStoredNodes(rebound(doc['nodes']))\n if (packed) doc['nodes'] = Buffer.from(packed)\n tx.create(collectionRef.doc(id), {\n ...doc,\n ...(recipe.stamps ?? {}),\n ...(recipe.collection === 'screens' ? { nameLower: nameSearchKey(name) } : {}),\n ...(versionData && versionId ? { versionId } : {}),\n createdAt: Timestamp.now(),\n updatedAt: Timestamp.now(),\n createdBy: uid,\n })\n if (versionData && versionId && recipe.versionParentField) {\n const versionNodes = encodeStoredNodes(rebound(versionData['nodes']))\n tx.create(collectionRef.doc(id).collection('versions').doc(versionId), {\n ...versionData,\n ...(versionNodes ? { nodes: Buffer.from(versionNodes) } : {}),\n [recipe.versionParentField]: id,\n hostId,\n displayName: duplicateVersionNote(sourceName, sourceVersionNumber),\n createdAt: Timestamp.now(),\n updatedAt: Timestamp.now(),\n createdBy: uid,\n })\n }\n return {\n ok: true,\n id,\n versionId: versionData && versionId ? versionId : null,\n name,\n sourceName,\n }\n })\n}\n"],"names":["billableScreenIds","checkEntitlement","checkQuota","claimAttempt","createResourceUid","decodeStoredNodes","encodeStoredNodes","nameSearchKey","NON_PAGE_SCREEN_MAX_PER_HOST","nonPageScreenIds","SCREEN_KIND_EMAIL","SCREEN_KIND_TEMPLATE","screenClaimsToBeAPage","DUPLICABLE_RESOURCE_NOUNS","DUPLICATE_BUSY_MESSAGE","duplicateDisplayName","duplicateVersionNote","uniqueDuplicateName","uniqueDuplicateSlug","formDesignReboundTo","Timestamp","logResourceDuplicated","firebaseAdmin","RECIPES","screen","collection","nameField","fields","versionParentField","quotaKey","slug","emailDesign","component","entitlement","layout","template","stamps","source","type","form","workflow","trigger","asRecord","value","Array","isArray","duplicateResource","kind","options","recipe","noun","sourceId","String","trim","slice","ok","status","error","firestore","app","attemptKey","claimed","scopeId","hostId","orgId","key","busyMessage","replay","body","claim","result","copy","uid","email","id","name","sourceName","target","versionId","answer","record","release","hostRef","doc","collectionRef","sourceRef","org","get","data","runTransaction","tx","siblingFields","hostSnapshot","siblingsSnapshot","Promise","all","select","exists","sourceKind","nonPageScreen","siblings","docs","map","row","deletedAt","sourceType","live","filter","screenRows","routingMap","size","used","length","quota","allowed","limit","requested","undefined","versionData","sourceVersionNumber","newest","versionsRef","orderBy","found","pointer","pointed","field","rebound","raw","formId","formName","packed","Buffer","from","create","nameLower","createdAt","now","updatedAt","createdBy","versionNodes","nodes","displayName"],"mappings":"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"}
|