@aglyn/plugins-crm 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/package.json +57 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +29 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/activity-list.d.ts +98 -0
- package/src/lib/components/activity-list.js +321 -0
- package/src/lib/components/activity-list.js.map +1 -0
- package/src/lib/components/activity-queries.d.ts +117 -0
- package/src/lib/components/activity-queries.js +198 -0
- package/src/lib/components/activity-queries.js.map +1 -0
- package/src/lib/components/add-to-list-button.d.ts +25 -0
- package/src/lib/components/add-to-list-button.js +64 -0
- package/src/lib/components/add-to-list-button.js.map +1 -0
- package/src/lib/components/add-to-list-dialog.d.ts +24 -0
- package/src/lib/components/add-to-list-dialog.js +327 -0
- package/src/lib/components/add-to-list-dialog.js.map +1 -0
- package/src/lib/components/assignment-rule-drawer.d.ts +43 -0
- package/src/lib/components/assignment-rule-drawer.js +293 -0
- package/src/lib/components/assignment-rule-drawer.js.map +1 -0
- package/src/lib/components/companies-bulk-bar.d.ts +29 -0
- package/src/lib/components/companies-bulk-bar.js +274 -0
- package/src/lib/components/companies-bulk-bar.js.map +1 -0
- package/src/lib/components/companies-section.d.ts +54 -0
- package/src/lib/components/companies-section.js +522 -0
- package/src/lib/components/companies-section.js.map +1 -0
- package/src/lib/components/company-contacts-card.d.ts +49 -0
- package/src/lib/components/company-contacts-card.js +435 -0
- package/src/lib/components/company-contacts-card.js.map +1 -0
- package/src/lib/components/company-deals-card.d.ts +20 -0
- package/src/lib/components/company-deals-card.js +38 -0
- package/src/lib/components/company-deals-card.js.map +1 -0
- package/src/lib/components/company-detail-page.d.ts +20 -0
- package/src/lib/components/company-detail-page.js +165 -0
- package/src/lib/components/company-detail-page.js.map +1 -0
- package/src/lib/components/company-edit-drawer.d.ts +65 -0
- package/src/lib/components/company-edit-drawer.js +433 -0
- package/src/lib/components/company-edit-drawer.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +42 -0
- package/src/lib/components/company-import-drawer.js +99 -0
- package/src/lib/components/company-import-drawer.js.map +1 -0
- package/src/lib/components/company-picker.d.ts +127 -0
- package/src/lib/components/company-picker.js +323 -0
- package/src/lib/components/company-picker.js.map +1 -0
- package/src/lib/components/company-properties-card.d.ts +47 -0
- package/src/lib/components/company-properties-card.js +289 -0
- package/src/lib/components/company-properties-card.js.map +1 -0
- package/src/lib/components/contact-address-fields.d.ts +29 -0
- package/src/lib/components/contact-address-fields.js +149 -0
- package/src/lib/components/contact-address-fields.js.map +1 -0
- package/src/lib/components/contact-associations-card.d.ts +72 -0
- package/src/lib/components/contact-associations-card.js +296 -0
- package/src/lib/components/contact-associations-card.js.map +1 -0
- package/src/lib/components/contact-custom-columns.d.ts +38 -0
- package/src/lib/components/contact-custom-columns.js +102 -0
- package/src/lib/components/contact-custom-columns.js.map +1 -0
- package/src/lib/components/contact-custom-fields-card.d.ts +39 -0
- package/src/lib/components/contact-custom-fields-card.js +204 -0
- package/src/lib/components/contact-custom-fields-card.js.map +1 -0
- package/src/lib/components/contact-deals-card.d.ts +20 -0
- package/src/lib/components/contact-deals-card.js +38 -0
- package/src/lib/components/contact-deals-card.js.map +1 -0
- package/src/lib/components/contact-detail-page.d.ts +46 -0
- package/src/lib/components/contact-detail-page.js +535 -0
- package/src/lib/components/contact-detail-page.js.map +1 -0
- package/src/lib/components/contact-duplicates-card.d.ts +42 -0
- package/src/lib/components/contact-duplicates-card.js +167 -0
- package/src/lib/components/contact-duplicates-card.js.map +1 -0
- package/src/lib/components/contact-field-drawer.d.ts +64 -0
- package/src/lib/components/contact-field-drawer.js +251 -0
- package/src/lib/components/contact-field-drawer.js.map +1 -0
- package/src/lib/components/contact-import-drawer.d.ts +46 -0
- package/src/lib/components/contact-import-drawer.js +149 -0
- package/src/lib/components/contact-import-drawer.js.map +1 -0
- package/src/lib/components/contact-known-by-card.d.ts +39 -0
- package/src/lib/components/contact-known-by-card.js +142 -0
- package/src/lib/components/contact-known-by-card.js.map +1 -0
- package/src/lib/components/contact-list-columns.d.ts +53 -0
- package/src/lib/components/contact-list-columns.js +288 -0
- package/src/lib/components/contact-list-columns.js.map +1 -0
- package/src/lib/components/contact-merge-dialog.d.ts +79 -0
- package/src/lib/components/contact-merge-dialog.js +405 -0
- package/src/lib/components/contact-merge-dialog.js.map +1 -0
- package/src/lib/components/contact-properties-card.d.ts +89 -0
- package/src/lib/components/contact-properties-card.js +492 -0
- package/src/lib/components/contact-properties-card.js.map +1 -0
- package/src/lib/components/contact-timeline-card.d.ts +68 -0
- package/src/lib/components/contact-timeline-card.js +435 -0
- package/src/lib/components/contact-timeline-card.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +91 -0
- package/src/lib/components/contacts-bulk-bar.js +493 -0
- package/src/lib/components/contacts-bulk-bar.js.map +1 -0
- package/src/lib/components/contacts-section.d.ts +26 -0
- package/src/lib/components/contacts-section.js +1025 -0
- package/src/lib/components/contacts-section.js.map +1 -0
- package/src/lib/components/copy-capture-address-button.d.ts +33 -0
- package/src/lib/components/copy-capture-address-button.js +60 -0
- package/src/lib/components/copy-capture-address-button.js.map +1 -0
- package/src/lib/components/crm-attribution-zone.d.ts +38 -0
- package/src/lib/components/crm-attribution-zone.js +36 -0
- package/src/lib/components/crm-attribution-zone.js.map +1 -0
- package/src/lib/components/crm-booking-zone.d.ts +61 -0
- package/src/lib/components/crm-booking-zone.js +50 -0
- package/src/lib/components/crm-booking-zone.js.map +1 -0
- package/src/lib/components/crm-bulk-bar-frame.d.ts +47 -0
- package/src/lib/components/crm-bulk-bar-frame.js +137 -0
- package/src/lib/components/crm-bulk-bar-frame.js.map +1 -0
- package/src/lib/components/crm-call-actions.d.ts +65 -0
- package/src/lib/components/crm-call-actions.js +110 -0
- package/src/lib/components/crm-call-actions.js.map +1 -0
- package/src/lib/components/crm-column-menu.d.ts +21 -0
- package/src/lib/components/crm-column-menu.js +103 -0
- package/src/lib/components/crm-column-menu.js.map +1 -0
- package/src/lib/components/crm-console-page.d.ts +19 -0
- package/src/lib/components/crm-console-page.js +151 -0
- package/src/lib/components/crm-console-page.js.map +1 -0
- package/src/lib/components/crm-console-sections.d.ts +52 -0
- package/src/lib/components/crm-console-sections.js +83 -0
- package/src/lib/components/crm-console-sections.js.map +1 -0
- package/src/lib/components/crm-custom-field-control.d.ts +26 -0
- package/src/lib/components/crm-custom-field-control.js +131 -0
- package/src/lib/components/crm-custom-field-control.js.map +1 -0
- package/src/lib/components/crm-email-template-tools.d.ts +69 -0
- package/src/lib/components/crm-email-template-tools.js +240 -0
- package/src/lib/components/crm-email-template-tools.js.map +1 -0
- package/src/lib/components/crm-export-all-button.d.ts +39 -0
- package/src/lib/components/crm-export-all-button.js +98 -0
- package/src/lib/components/crm-export-all-button.js.map +1 -0
- package/src/lib/components/crm-filter-bar.d.ts +53 -0
- package/src/lib/components/crm-filter-bar.js +294 -0
- package/src/lib/components/crm-filter-bar.js.map +1 -0
- package/src/lib/components/crm-glance-card.d.ts +54 -0
- package/src/lib/components/crm-glance-card.js +219 -0
- package/src/lib/components/crm-glance-card.js.map +1 -0
- package/src/lib/components/crm-next-activity-column.d.ts +35 -0
- package/src/lib/components/crm-next-activity-column.js +100 -0
- package/src/lib/components/crm-next-activity-column.js.map +1 -0
- package/src/lib/components/crm-record-header.d.ts +97 -0
- package/src/lib/components/crm-record-header.js +160 -0
- package/src/lib/components/crm-record-header.js.map +1 -0
- package/src/lib/components/crm-record-insights-zone.d.ts +50 -0
- package/src/lib/components/crm-record-insights-zone.js +169 -0
- package/src/lib/components/crm-record-insights-zone.js.map +1 -0
- package/src/lib/components/crm-record-picker.d.ts +42 -0
- package/src/lib/components/crm-record-picker.js +104 -0
- package/src/lib/components/crm-record-picker.js.map +1 -0
- package/src/lib/components/crm-send-email-button.d.ts +39 -0
- package/src/lib/components/crm-send-email-button.js +81 -0
- package/src/lib/components/crm-send-email-button.js.map +1 -0
- package/src/lib/components/crm-send-email-dialog.d.ts +61 -0
- package/src/lib/components/crm-send-email-dialog.js +851 -0
- package/src/lib/components/crm-send-email-dialog.js.map +1 -0
- package/src/lib/components/crm-site-picker.d.ts +61 -0
- package/src/lib/components/crm-site-picker.js +99 -0
- package/src/lib/components/crm-site-picker.js.map +1 -0
- package/src/lib/components/crm-suite-lock.d.ts +29 -0
- package/src/lib/components/crm-suite-lock.js +114 -0
- package/src/lib/components/crm-suite-lock.js.map +1 -0
- package/src/lib/components/crm-tasks-due-card.d.ts +42 -0
- package/src/lib/components/crm-tasks-due-card.js +225 -0
- package/src/lib/components/crm-tasks-due-card.js.map +1 -0
- package/src/lib/components/crm-views-control.d.ts +65 -0
- package/src/lib/components/crm-views-control.js +518 -0
- package/src/lib/components/crm-views-control.js.map +1 -0
- package/src/lib/components/csv-import-drawer.d.ts +132 -0
- package/src/lib/components/csv-import-drawer.js +746 -0
- package/src/lib/components/csv-import-drawer.js.map +1 -0
- package/src/lib/components/deal-board.d.ts +70 -0
- package/src/lib/components/deal-board.js +478 -0
- package/src/lib/components/deal-board.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +18 -0
- package/src/lib/components/deal-detail-page.js +323 -0
- package/src/lib/components/deal-detail-page.js.map +1 -0
- package/src/lib/components/deal-edit-drawer.d.ts +59 -0
- package/src/lib/components/deal-edit-drawer.js +640 -0
- package/src/lib/components/deal-edit-drawer.js.map +1 -0
- package/src/lib/components/deal-import-drawer.d.ts +24 -0
- package/src/lib/components/deal-import-drawer.js +83 -0
- package/src/lib/components/deal-import-drawer.js.map +1 -0
- package/src/lib/components/deal-products-card.d.ts +48 -0
- package/src/lib/components/deal-products-card.js +614 -0
- package/src/lib/components/deal-products-card.js.map +1 -0
- package/src/lib/components/deal-properties-card.d.ts +29 -0
- package/src/lib/components/deal-properties-card.js +144 -0
- package/src/lib/components/deal-properties-card.js.map +1 -0
- package/src/lib/components/deal-stage-card.d.ts +24 -0
- package/src/lib/components/deal-stage-card.js +199 -0
- package/src/lib/components/deal-stage-card.js.map +1 -0
- package/src/lib/components/deals-bulk-bar.d.ts +31 -0
- package/src/lib/components/deals-bulk-bar.js +340 -0
- package/src/lib/components/deals-bulk-bar.js.map +1 -0
- package/src/lib/components/deals-section.d.ts +49 -0
- package/src/lib/components/deals-section.js +767 -0
- package/src/lib/components/deals-section.js.map +1 -0
- package/src/lib/components/email-capture-card.d.ts +42 -0
- package/src/lib/components/email-capture-card.js +134 -0
- package/src/lib/components/email-capture-card.js.map +1 -0
- package/src/lib/components/email-template-drawer.d.ts +33 -0
- package/src/lib/components/email-template-drawer.js +209 -0
- package/src/lib/components/email-template-drawer.js.map +1 -0
- package/src/lib/components/email-templates-card.d.ts +33 -0
- package/src/lib/components/email-templates-card.js +370 -0
- package/src/lib/components/email-templates-card.js.map +1 -0
- package/src/lib/components/erase-person-action.d.ts +92 -0
- package/src/lib/components/erase-person-action.js +261 -0
- package/src/lib/components/erase-person-action.js.map +1 -0
- package/src/lib/components/fields-section.d.ts +37 -0
- package/src/lib/components/fields-section.js +641 -0
- package/src/lib/components/fields-section.js.map +1 -0
- package/src/lib/components/form-contact-fields-card.d.ts +46 -0
- package/src/lib/components/form-contact-fields-card.js +253 -0
- package/src/lib/components/form-contact-fields-card.js.map +1 -0
- package/src/lib/components/insert-link-at-caret.d.ts +28 -0
- package/src/lib/components/insert-link-at-caret.js +37 -0
- package/src/lib/components/insert-link-at-caret.js.map +1 -0
- package/src/lib/components/lead-convert-dialog.d.ts +32 -0
- package/src/lib/components/lead-convert-dialog.js +493 -0
- package/src/lib/components/lead-convert-dialog.js.map +1 -0
- package/src/lib/components/lead-detail-page.d.ts +16 -0
- package/src/lib/components/lead-detail-page.js +163 -0
- package/src/lib/components/lead-detail-page.js.map +1 -0
- package/src/lib/components/lead-history-card.d.ts +32 -0
- package/src/lib/components/lead-history-card.js +165 -0
- package/src/lib/components/lead-history-card.js.map +1 -0
- package/src/lib/components/lead-import-drawer.d.ts +24 -0
- package/src/lib/components/lead-import-drawer.js +95 -0
- package/src/lib/components/lead-import-drawer.js.map +1 -0
- package/src/lib/components/lead-owner-select.d.ts +35 -0
- package/src/lib/components/lead-owner-select.js +91 -0
- package/src/lib/components/lead-owner-select.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +61 -0
- package/src/lib/components/lead-properties-card.js +395 -0
- package/src/lib/components/lead-properties-card.js.map +1 -0
- package/src/lib/components/lead-status-chip.d.ts +12 -0
- package/src/lib/components/lead-status-chip.js +43 -0
- package/src/lib/components/lead-status-chip.js.map +1 -0
- package/src/lib/components/lead-surfaces-note.d.ts +126 -0
- package/src/lib/components/lead-surfaces-note.js +262 -0
- package/src/lib/components/lead-surfaces-note.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +24 -0
- package/src/lib/components/lead-unqualify-dialog.js +125 -0
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -0
- package/src/lib/components/leads-bulk-bar.d.ts +58 -0
- package/src/lib/components/leads-bulk-bar.js +307 -0
- package/src/lib/components/leads-bulk-bar.js.map +1 -0
- package/src/lib/components/leads-section.d.ts +23 -0
- package/src/lib/components/leads-section.js +664 -0
- package/src/lib/components/leads-section.js.map +1 -0
- package/src/lib/components/linked-deals-card.d.ts +32 -0
- package/src/lib/components/linked-deals-card.js +163 -0
- package/src/lib/components/linked-deals-card.js.map +1 -0
- package/src/lib/components/log-activity-dialog.d.ts +60 -0
- package/src/lib/components/log-activity-dialog.js +368 -0
- package/src/lib/components/log-activity-dialog.js.map +1 -0
- package/src/lib/components/lost-reason-dialog.d.ts +26 -0
- package/src/lib/components/lost-reason-dialog.js +95 -0
- package/src/lib/components/lost-reason-dialog.js.map +1 -0
- package/src/lib/components/new-contact-drawer.d.ts +78 -0
- package/src/lib/components/new-contact-drawer.js +343 -0
- package/src/lib/components/new-contact-drawer.js.map +1 -0
- package/src/lib/components/org-lead-surfaces-note.d.ts +57 -0
- package/src/lib/components/org-lead-surfaces-note.js +172 -0
- package/src/lib/components/org-lead-surfaces-note.js.map +1 -0
- package/src/lib/components/owner-avatar.d.ts +18 -0
- package/src/lib/components/owner-avatar.js +61 -0
- package/src/lib/components/owner-avatar.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +43 -0
- package/src/lib/components/pipeline-stages-dialog.js +339 -0
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -0
- package/src/lib/components/pipelines-dialog.d.ts +55 -0
- package/src/lib/components/pipelines-dialog.js +517 -0
- package/src/lib/components/pipelines-dialog.js.map +1 -0
- package/src/lib/components/recent-activity-feed.d.ts +39 -0
- package/src/lib/components/recent-activity-feed.js +103 -0
- package/src/lib/components/recent-activity-feed.js.map +1 -0
- package/src/lib/components/recipes-card.d.ts +39 -0
- package/src/lib/components/recipes-card.js +485 -0
- package/src/lib/components/recipes-card.js.map +1 -0
- package/src/lib/components/record-activity-card.d.ts +26 -0
- package/src/lib/components/record-activity-card.js +121 -0
- package/src/lib/components/record-activity-card.js.map +1 -0
- package/src/lib/components/record-files-card.d.ts +67 -0
- package/src/lib/components/record-files-card.js +290 -0
- package/src/lib/components/record-files-card.js.map +1 -0
- package/src/lib/components/record-tasks-card.d.ts +32 -0
- package/src/lib/components/record-tasks-card.js +306 -0
- package/src/lib/components/record-tasks-card.js.map +1 -0
- package/src/lib/components/reports/activity-card.d.ts +57 -0
- package/src/lib/components/reports/activity-card.js +281 -0
- package/src/lib/components/reports/activity-card.js.map +1 -0
- package/src/lib/components/reports/closed-deals-card.d.ts +20 -0
- package/src/lib/components/reports/closed-deals-card.js +194 -0
- package/src/lib/components/reports/closed-deals-card.js.map +1 -0
- package/src/lib/components/reports/contacts-mix-card.d.ts +27 -0
- package/src/lib/components/reports/contacts-mix-card.js +161 -0
- package/src/lib/components/reports/contacts-mix-card.js.map +1 -0
- package/src/lib/components/reports/contacts-trend-card.d.ts +31 -0
- package/src/lib/components/reports/contacts-trend-card.js +144 -0
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -0
- package/src/lib/components/reports/forecast-card.d.ts +22 -0
- package/src/lib/components/reports/forecast-card.js +251 -0
- package/src/lib/components/reports/forecast-card.js.map +1 -0
- package/src/lib/components/reports/lead-counts.d.ts +32 -0
- package/src/lib/components/reports/lead-counts.js +44 -0
- package/src/lib/components/reports/lead-counts.js.map +1 -0
- package/src/lib/components/reports/lead-funnel-card.d.ts +38 -0
- package/src/lib/components/reports/lead-funnel-card.js +375 -0
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -0
- package/src/lib/components/reports/pipeline-card.d.ts +19 -0
- package/src/lib/components/reports/pipeline-card.js +338 -0
- package/src/lib/components/reports/pipeline-card.js.map +1 -0
- package/src/lib/components/reports/report-bar-chart.d.ts +39 -0
- package/src/lib/components/reports/report-bar-chart.js +133 -0
- package/src/lib/components/reports/report-bar-chart.js.map +1 -0
- package/src/lib/components/reports/report-breakdown.d.ts +32 -0
- package/src/lib/components/reports/report-breakdown.js +105 -0
- package/src/lib/components/reports/report-breakdown.js.map +1 -0
- package/src/lib/components/reports/report-export.d.ts +37 -0
- package/src/lib/components/reports/report-export.js +66 -0
- package/src/lib/components/reports/report-export.js.map +1 -0
- package/src/lib/components/reports/report-format.d.ts +36 -0
- package/src/lib/components/reports/report-format.js +43 -0
- package/src/lib/components/reports/report-format.js.map +1 -0
- package/src/lib/components/reports/report-scope.d.ts +81 -0
- package/src/lib/components/reports/report-scope.js +58 -0
- package/src/lib/components/reports/report-scope.js.map +1 -0
- package/src/lib/components/reports/report-stat-tile.d.ts +47 -0
- package/src/lib/components/reports/report-stat-tile.js +96 -0
- package/src/lib/components/reports/report-stat-tile.js.map +1 -0
- package/src/lib/components/reports/source-conversion-card.d.ts +28 -0
- package/src/lib/components/reports/source-conversion-card.js +205 -0
- package/src/lib/components/reports/source-conversion-card.js.map +1 -0
- package/src/lib/components/reports/tasks-card.d.ts +26 -0
- package/src/lib/components/reports/tasks-card.js +279 -0
- package/src/lib/components/reports/tasks-card.js.map +1 -0
- package/src/lib/components/reports/use-aggregate-read.d.ts +99 -0
- package/src/lib/components/reports/use-aggregate-read.js +195 -0
- package/src/lib/components/reports/use-aggregate-read.js.map +1 -0
- package/src/lib/components/reports/won-lost-by-owner-card.d.ts +30 -0
- package/src/lib/components/reports/won-lost-by-owner-card.js +168 -0
- package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -0
- package/src/lib/components/reports-section.d.ts +42 -0
- package/src/lib/components/reports-section.js +308 -0
- package/src/lib/components/reports-section.js.map +1 -0
- package/src/lib/components/sending-addresses-card.d.ts +49 -0
- package/src/lib/components/sending-addresses-card.js +262 -0
- package/src/lib/components/sending-addresses-card.js.map +1 -0
- package/src/lib/components/settings-section.d.ts +151 -0
- package/src/lib/components/settings-section.js +783 -0
- package/src/lib/components/settings-section.js.map +1 -0
- package/src/lib/components/task-cells.d.ts +51 -0
- package/src/lib/components/task-cells.js +139 -0
- package/src/lib/components/task-cells.js.map +1 -0
- package/src/lib/components/task-edit-drawer.d.ts +53 -0
- package/src/lib/components/task-edit-drawer.js +483 -0
- package/src/lib/components/task-edit-drawer.js.map +1 -0
- package/src/lib/components/task-import-drawer.d.ts +24 -0
- package/src/lib/components/task-import-drawer.js +82 -0
- package/src/lib/components/task-import-drawer.js.map +1 -0
- package/src/lib/components/task-snooze-menu.d.ts +65 -0
- package/src/lib/components/task-snooze-menu.js +207 -0
- package/src/lib/components/task-snooze-menu.js.map +1 -0
- package/src/lib/components/tasks-bulk-bar.d.ts +25 -0
- package/src/lib/components/tasks-bulk-bar.js +348 -0
- package/src/lib/components/tasks-bulk-bar.js.map +1 -0
- package/src/lib/components/tasks-calendar.d.ts +38 -0
- package/src/lib/components/tasks-calendar.js +228 -0
- package/src/lib/components/tasks-calendar.js.map +1 -0
- package/src/lib/components/tasks-section.d.ts +27 -0
- package/src/lib/components/tasks-section.js +579 -0
- package/src/lib/components/tasks-section.js.map +1 -0
- package/src/lib/components/use-contact-campaign-emails.d.ts +32 -0
- package/src/lib/components/use-contact-campaign-emails.js +83 -0
- package/src/lib/components/use-contact-campaign-emails.js.map +1 -0
- package/src/lib/components/use-crm-api.d.ts +44 -0
- package/src/lib/components/use-crm-api.js +84 -0
- package/src/lib/components/use-crm-api.js.map +1 -0
- package/src/lib/components/use-crm-inbound-address.d.ts +60 -0
- package/src/lib/components/use-crm-inbound-address.js +124 -0
- package/src/lib/components/use-crm-inbound-address.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +24 -0
- package/src/lib/components/use-emails-hub-path.js +59 -0
- package/src/lib/components/use-emails-hub-path.js.map +1 -0
- package/src/lib/components/use-member-email-aliases.d.ts +54 -0
- package/src/lib/components/use-member-email-aliases.js +186 -0
- package/src/lib/components/use-member-email-aliases.js.map +1 -0
- package/src/lib/components/use-org-members.d.ts +40 -0
- package/src/lib/components/use-org-members.js +100 -0
- package/src/lib/components/use-org-members.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +109 -0
- package/src/lib/constants/api-routes.js +74 -0
- package/src/lib/constants/api-routes.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +14 -0
- package/src/lib/constants/bundle-common.js +14 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/company-filters.d.ts +18 -0
- package/src/lib/constants/company-filters.js +51 -0
- package/src/lib/constants/company-filters.js.map +1 -0
- package/src/lib/constants/contact-filters.d.ts +38 -0
- package/src/lib/constants/contact-filters.js +294 -0
- package/src/lib/constants/contact-filters.js.map +1 -0
- package/src/lib/hooks/use-contact-field-definitions.d.ts +48 -0
- package/src/lib/hooks/use-contact-field-definitions.js +72 -0
- package/src/lib/hooks/use-contact-field-definitions.js.map +1 -0
- package/src/lib/hooks/use-contact-update.d.ts +25 -0
- package/src/lib/hooks/use-contact-update.js +80 -0
- package/src/lib/hooks/use-contact-update.js.map +1 -0
- package/src/lib/hooks/use-crm-activity-logger.d.ts +73 -0
- package/src/lib/hooks/use-crm-activity-logger.js +101 -0
- package/src/lib/hooks/use-crm-activity-logger.js.map +1 -0
- package/src/lib/hooks/use-crm-bulk-apply.d.ts +52 -0
- package/src/lib/hooks/use-crm-bulk-apply.js +112 -0
- package/src/lib/hooks/use-crm-bulk-apply.js.map +1 -0
- package/src/lib/hooks/use-crm-default-view.d.ts +24 -0
- package/src/lib/hooks/use-crm-default-view.js +64 -0
- package/src/lib/hooks/use-crm-default-view.js.map +1 -0
- package/src/lib/hooks/use-crm-email-templates.d.ts +59 -0
- package/src/lib/hooks/use-crm-email-templates.js +103 -0
- package/src/lib/hooks/use-crm-email-templates.js.map +1 -0
- package/src/lib/hooks/use-crm-hub-path.d.ts +29 -0
- package/src/lib/hooks/use-crm-hub-path.js +41 -0
- package/src/lib/hooks/use-crm-hub-path.js.map +1 -0
- package/src/lib/hooks/use-crm-org-mount.d.ts +97 -0
- package/src/lib/hooks/use-crm-org-mount.js +215 -0
- package/src/lib/hooks/use-crm-org-mount.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +52 -0
- package/src/lib/hooks/use-crm-record-names.js +126 -0
- package/src/lib/hooks/use-crm-record-names.js.map +1 -0
- package/src/lib/hooks/use-crm-records-quota.d.ts +68 -0
- package/src/lib/hooks/use-crm-records-quota.js +118 -0
- package/src/lib/hooks/use-crm-records-quota.js.map +1 -0
- package/src/lib/hooks/use-crm-saved-view.d.ts +96 -0
- package/src/lib/hooks/use-crm-saved-view.js +329 -0
- package/src/lib/hooks/use-crm-saved-view.js.map +1 -0
- package/src/lib/hooks/use-crm-scope.d.ts +117 -0
- package/src/lib/hooks/use-crm-scope.js +147 -0
- package/src/lib/hooks/use-crm-scope.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +85 -0
- package/src/lib/hooks/use-crm-tasks.js +153 -0
- package/src/lib/hooks/use-crm-tasks.js.map +1 -0
- package/src/lib/hooks/use-crm-view-grid.d.ts +55 -0
- package/src/lib/hooks/use-crm-view-grid.js +170 -0
- package/src/lib/hooks/use-crm-view-grid.js.map +1 -0
- package/src/lib/hooks/use-crm-views.d.ts +42 -0
- package/src/lib/hooks/use-crm-views.js +84 -0
- package/src/lib/hooks/use-crm-views.js.map +1 -0
- package/src/lib/hooks/use-deal-stage-api.d.ts +69 -0
- package/src/lib/hooks/use-deal-stage-api.js +78 -0
- package/src/lib/hooks/use-deal-stage-api.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +47 -0
- package/src/lib/hooks/use-deals.js +105 -0
- package/src/lib/hooks/use-deals.js.map +1 -0
- package/src/lib/hooks/use-org-leads.d.ts +53 -0
- package/src/lib/hooks/use-org-leads.js +114 -0
- package/src/lib/hooks/use-org-leads.js.map +1 -0
- package/src/lib/hooks/use-org-member-directory.d.ts +36 -0
- package/src/lib/hooks/use-org-member-directory.js +116 -0
- package/src/lib/hooks/use-org-member-directory.js.map +1 -0
- package/src/lib/hooks/use-org-member-options.d.ts +38 -0
- package/src/lib/hooks/use-org-member-options.js +131 -0
- package/src/lib/hooks/use-org-member-options.js.map +1 -0
- package/src/lib/hooks/use-pipeline.d.ts +71 -0
- package/src/lib/hooks/use-pipeline.js +159 -0
- package/src/lib/hooks/use-pipeline.js.map +1 -0
- package/src/lib/model/companies-bulk-writes.d.ts +40 -0
- package/src/lib/model/companies-bulk-writes.js +103 -0
- package/src/lib/model/companies-bulk-writes.js.map +1 -0
- package/src/lib/model/companies-csv.d.ts +36 -0
- package/src/lib/model/companies-csv.js +38 -0
- package/src/lib/model/companies-csv.js.map +1 -0
- package/src/lib/model/companies.d.ts +99 -0
- package/src/lib/model/companies.js +168 -0
- package/src/lib/model/companies.js.map +1 -0
- package/src/lib/model/company-delete-route.d.ts +43 -0
- package/src/lib/model/company-delete-route.js +31 -0
- package/src/lib/model/company-delete-route.js.map +1 -0
- package/src/lib/model/company-delete.d.ts +42 -0
- package/src/lib/model/company-delete.js +52 -0
- package/src/lib/model/company-delete.js.map +1 -0
- package/src/lib/model/contact-duplicates.d.ts +58 -0
- package/src/lib/model/contact-duplicates.js +67 -0
- package/src/lib/model/contact-duplicates.js.map +1 -0
- package/src/lib/model/contact-record.d.ts +113 -0
- package/src/lib/model/contact-record.js +70 -0
- package/src/lib/model/contact-record.js.map +1 -0
- package/src/lib/model/contact-update.d.ts +93 -0
- package/src/lib/model/contact-update.js +50 -0
- package/src/lib/model/contact-update.js.map +1 -0
- package/src/lib/model/contacts-bulk-writes.d.ts +173 -0
- package/src/lib/model/contacts-bulk-writes.js +196 -0
- package/src/lib/model/contacts-bulk-writes.js.map +1 -0
- package/src/lib/model/contacts-csv.d.ts +54 -0
- package/src/lib/model/contacts-csv.js +67 -0
- package/src/lib/model/contacts-csv.js.map +1 -0
- package/src/lib/model/contacts-list-seed.d.ts +51 -0
- package/src/lib/model/contacts-list-seed.js +63 -0
- package/src/lib/model/contacts-list-seed.js.map +1 -0
- package/src/lib/model/crm-activity-report.d.ts +27 -0
- package/src/lib/model/crm-activity-report.js +56 -0
- package/src/lib/model/crm-activity-report.js.map +1 -0
- package/src/lib/model/crm-api.d.ts +49 -0
- package/src/lib/model/crm-api.js +47 -0
- package/src/lib/model/crm-api.js.map +1 -0
- package/src/lib/model/crm-bulk-writes.d.ts +147 -0
- package/src/lib/model/crm-bulk-writes.js +207 -0
- package/src/lib/model/crm-bulk-writes.js.map +1 -0
- package/src/lib/model/crm-company-import.d.ts +154 -0
- package/src/lib/model/crm-company-import.js +331 -0
- package/src/lib/model/crm-company-import.js.map +1 -0
- package/src/lib/model/crm-custom-draft.d.ts +62 -0
- package/src/lib/model/crm-custom-draft.js +67 -0
- package/src/lib/model/crm-custom-draft.js.map +1 -0
- package/src/lib/model/crm-deal-import.d.ts +112 -0
- package/src/lib/model/crm-deal-import.js +263 -0
- package/src/lib/model/crm-deal-import.js.map +1 -0
- package/src/lib/model/crm-import.d.ts +244 -0
- package/src/lib/model/crm-import.js +466 -0
- package/src/lib/model/crm-import.js.map +1 -0
- package/src/lib/model/crm-lead-import.d.ts +105 -0
- package/src/lib/model/crm-lead-import.js +255 -0
- package/src/lib/model/crm-lead-import.js.map +1 -0
- package/src/lib/model/crm-record-routes.d.ts +21 -0
- package/src/lib/model/crm-record-routes.js +70 -0
- package/src/lib/model/crm-record-routes.js.map +1 -0
- package/src/lib/model/crm-routes.d.ts +101 -0
- package/src/lib/model/crm-routes.js +92 -0
- package/src/lib/model/crm-routes.js.map +1 -0
- package/src/lib/model/crm-task-import.d.ts +94 -0
- package/src/lib/model/crm-task-import.js +275 -0
- package/src/lib/model/crm-task-import.js.map +1 -0
- package/src/lib/model/crm-view-param.d.ts +43 -0
- package/src/lib/model/crm-view-param.js +49 -0
- package/src/lib/model/crm-view-param.js.map +1 -0
- package/src/lib/model/deal-board-model.d.ts +241 -0
- package/src/lib/model/deal-board-model.js +439 -0
- package/src/lib/model/deal-board-model.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +114 -0
- package/src/lib/model/deal-form-model.js +218 -0
- package/src/lib/model/deal-form-model.js.map +1 -0
- package/src/lib/model/deals-csv.d.ts +32 -0
- package/src/lib/model/deals-csv.js +37 -0
- package/src/lib/model/deals-csv.js.map +1 -0
- package/src/lib/model/import-column-shapes.d.ts +36 -0
- package/src/lib/model/import-column-shapes.js +76 -0
- package/src/lib/model/import-column-shapes.js.map +1 -0
- package/src/lib/model/lead-company-suggestion.d.ts +61 -0
- package/src/lib/model/lead-company-suggestion.js +65 -0
- package/src/lib/model/lead-company-suggestion.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +29 -0
- package/src/lib/model/lead-filters.js +39 -0
- package/src/lib/model/lead-filters.js.map +1 -0
- package/src/lib/model/lead-surfaces.d.ts +58 -0
- package/src/lib/model/lead-surfaces.js +67 -0
- package/src/lib/model/lead-surfaces.js.map +1 -0
- package/src/lib/model/leads-csv.d.ts +40 -0
- package/src/lib/model/leads-csv.js +40 -0
- package/src/lib/model/leads-csv.js.map +1 -0
- package/src/lib/model/next-activity-api.d.ts +31 -0
- package/src/lib/model/next-activity-api.js +57 -0
- package/src/lib/model/next-activity-api.js.map +1 -0
- package/src/lib/model/next-activity.d.ts +64 -0
- package/src/lib/model/next-activity.js +47 -0
- package/src/lib/model/next-activity.js.map +1 -0
- package/src/lib/model/person-key-web.d.ts +43 -0
- package/src/lib/model/person-key-web.js +52 -0
- package/src/lib/model/person-key-web.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +282 -0
- package/src/lib/model/record-facts.js +434 -0
- package/src/lib/model/record-facts.js.map +1 -0
- package/src/lib/model/task-api.d.ts +28 -0
- package/src/lib/model/task-api.js +57 -0
- package/src/lib/model/task-api.js.map +1 -0
- package/src/lib/model/task-calendar.d.ts +108 -0
- package/src/lib/model/task-calendar.js +139 -0
- package/src/lib/model/task-calendar.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +177 -0
- package/src/lib/model/task-routes.js +197 -0
- package/src/lib/model/task-routes.js.map +1 -0
- package/src/lib/model/task-scope.d.ts +45 -0
- package/src/lib/model/task-scope.js +51 -0
- package/src/lib/model/task-scope.js.map +1 -0
- package/src/lib/model/task-views.d.ts +149 -0
- package/src/lib/model/task-views.js +286 -0
- package/src/lib/model/task-views.js.map +1 -0
- package/src/lib/model/tasks-csv.d.ts +28 -0
- package/src/lib/model/tasks-csv.js +28 -0
- package/src/lib/model/tasks-csv.js.map +1 -0
- package/src/lib/plugin.d.ts +32 -0
- package/src/lib/plugin.js +208 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/companies-import.d.ts +57 -0
- package/src/lib/server/companies-import.js +242 -0
- package/src/lib/server/companies-import.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +59 -0
- package/src/lib/server/company-delete.js +146 -0
- package/src/lib/server/company-delete.js.map +1 -0
- package/src/lib/server/contact-email-history.d.ts +82 -0
- package/src/lib/server/contact-email-history.js +264 -0
- package/src/lib/server/contact-email-history.js.map +1 -0
- package/src/lib/server/contact-profile.d.ts +41 -0
- package/src/lib/server/contact-profile.js +45 -0
- package/src/lib/server/contact-profile.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +92 -0
- package/src/lib/server/contact-update.js +512 -0
- package/src/lib/server/contact-update.js.map +1 -0
- package/src/lib/server/contacts-import.d.ts +73 -0
- package/src/lib/server/contacts-import.js +263 -0
- package/src/lib/server/contacts-import.js.map +1 -0
- package/src/lib/server/contacts-merge.d.ts +86 -0
- package/src/lib/server/contacts-merge.js +206 -0
- package/src/lib/server/contacts-merge.js.map +1 -0
- package/src/lib/server/deals-import.d.ts +76 -0
- package/src/lib/server/deals-import.js +246 -0
- package/src/lib/server/deals-import.js.map +1 -0
- package/src/lib/server/email-send.d.ts +119 -0
- package/src/lib/server/email-send.js +667 -0
- package/src/lib/server/email-send.js.map +1 -0
- package/src/lib/server/email-template-duplicate.d.ts +19 -0
- package/src/lib/server/email-template-duplicate.js +206 -0
- package/src/lib/server/email-template-duplicate.js.map +1 -0
- package/src/lib/server/erase-person.d.ts +108 -0
- package/src/lib/server/erase-person.js +309 -0
- package/src/lib/server/erase-person.js.map +1 -0
- package/src/lib/server/import-context.d.ts +85 -0
- package/src/lib/server/import-context.js +181 -0
- package/src/lib/server/import-context.js.map +1 -0
- package/src/lib/server/inbound-address.d.ts +61 -0
- package/src/lib/server/inbound-address.js +188 -0
- package/src/lib/server/inbound-address.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +78 -0
- package/src/lib/server/lead-convert.js +250 -0
- package/src/lib/server/lead-convert.js.map +1 -0
- package/src/lib/server/leads-import.d.ts +84 -0
- package/src/lib/server/leads-import.js +261 -0
- package/src/lib/server/leads-import.js.map +1 -0
- package/src/lib/server/next-activity-routes.d.ts +33 -0
- package/src/lib/server/next-activity-routes.js +110 -0
- package/src/lib/server/next-activity-routes.js.map +1 -0
- package/src/lib/server/org-activity.d.ts +61 -0
- package/src/lib/server/org-activity.js +120 -0
- package/src/lib/server/org-activity.js.map +1 -0
- package/src/lib/server/org-caller.d.ts +110 -0
- package/src/lib/server/org-caller.js +154 -0
- package/src/lib/server/org-caller.js.map +1 -0
- package/src/lib/server/recipe-routes.d.ts +75 -0
- package/src/lib/server/recipe-routes.js +442 -0
- package/src/lib/server/recipe-routes.js.map +1 -0
- package/src/lib/server/record-facts.d.ts +65 -0
- package/src/lib/server/record-facts.js +337 -0
- package/src/lib/server/record-facts.js.map +1 -0
- package/src/lib/server/record-timeline.d.ts +51 -0
- package/src/lib/server/record-timeline.js +215 -0
- package/src/lib/server/record-timeline.js.map +1 -0
- package/src/lib/server/suite-gate.d.ts +42 -0
- package/src/lib/server/suite-gate.js +87 -0
- package/src/lib/server/suite-gate.js.map +1 -0
- package/src/lib/server/task-routes.d.ts +132 -0
- package/src/lib/server/task-routes.js +699 -0
- package/src/lib/server/task-routes.js.map +1 -0
- package/src/lib/server/tasks-import.d.ts +54 -0
- package/src/lib/server/tasks-import.js +157 -0
- package/src/lib/server/tasks-import.js.map +1 -0
- package/src/lib/server-deal-stage.d.ts +88 -0
- package/src/lib/server-deal-stage.js +316 -0
- package/src/lib/server-deal-stage.js.map +1 -0
- package/src/lib/server.d.ts +149 -0
- package/src/lib/server.js +615 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1,309 @@
|
|
|
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
|
+
*/ import { canManageOrg, CONTACT_ERASURE_REQUESTED_FIELD, normalizeContactEmail, PERSON_ERASURES_COLLECTION, personErasureConfirmationMatches, personErasureId } from "@aglyn/aglyn/server";
|
|
18
|
+
import { personKey } from "@aglyn/aglyn/app-utils/person-key";
|
|
19
|
+
import { firebaseAdmin, getOrgForHost, logHostActivity, logOrgActivity, orgDataCollectionForHost, suppressEmailForHostErasure } from "@aglyn/tenant-data-admin";
|
|
20
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
21
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
22
|
+
import { authorizeOrgCaller, orgHostIds, readCrmRouteScope } from "./org-caller.js";
|
|
23
|
+
/** The route key, as `registerCrmConsoleApi` registers it. */ export const CRM_ERASE_PERSON_ROUTE = 'crm/erase-person';
|
|
24
|
+
/**
|
|
25
|
+
* `POST /api/crm/erase-person` — file a privacy erasure for one person
|
|
26
|
+
* (AGL-2623).
|
|
27
|
+
*
|
|
28
|
+
* ## Who may
|
|
29
|
+
*
|
|
30
|
+
* A workspace admin or owner — `canManageOrg` on the caller's org role —
|
|
31
|
+
* and nobody else. Not a site editor with the data permission, who can
|
|
32
|
+
* detach a contact from their own site but has no standing to remove the
|
|
33
|
+
* person from every site in the workspace; and not staff acting alone,
|
|
34
|
+
* because the workspace is the controller of this data and the instruction
|
|
35
|
+
* has to come from it. Staff who are also admins of the workspace pass on
|
|
36
|
+
* that role, as anyone would.
|
|
37
|
+
*
|
|
38
|
+
* ## What it does, and what it does not
|
|
39
|
+
*
|
|
40
|
+
* It files the request and closes the doors; it deletes nothing. The daily
|
|
41
|
+
* erasure job executes the sweep, so that a workspace's person erasures and
|
|
42
|
+
* its own erasure run through one job, one audit trail and one queue a
|
|
43
|
+
* staff member can watch. What happens here, in order:
|
|
44
|
+
*
|
|
45
|
+
* 1. The record named by the body is read and its address is what the
|
|
46
|
+
* request is about — the body's address must match it, typed, so an
|
|
47
|
+
* admin who opened the wrong record is stopped by the confirmation
|
|
48
|
+
* rather than by luck.
|
|
49
|
+
* 2. The request document is written, or found already pending, in
|
|
50
|
+
* which case nothing is re-filed and the caller is told so.
|
|
51
|
+
* 3. Every site of the workspace gets its suppression row NOW, not when
|
|
52
|
+
* the job runs: from this moment a capture cannot rebuild the person.
|
|
53
|
+
* 4. The contact document and each site's lead are stamped with the
|
|
54
|
+
* request time, so their pages can say "erasure pending" off the
|
|
55
|
+
* document they already read.
|
|
56
|
+
* 5. The site's activity feed and the platform audit log each get a row
|
|
57
|
+
* that names the record by id and the person by hash, never by
|
|
58
|
+
* address.
|
|
59
|
+
*
|
|
60
|
+
* ## The organization variant (AGL-2634)
|
|
61
|
+
*
|
|
62
|
+
* `{ orgId, hostId?, contactId | leadId, email }` from the org-level hub:
|
|
63
|
+
* the same workspace admin, authorized by the org rather than through a
|
|
64
|
+
* site they name, so a contact no site captured can be erased from the
|
|
65
|
+
* page that lists it. The sweep is what it always was — every site of the
|
|
66
|
+
* org, found by `orgId` — and the row goes to the org's feed. A lead still
|
|
67
|
+
* names its site, because that is where a lead lives.
|
|
68
|
+
*
|
|
69
|
+
* ## By address alone (AGL-2839)
|
|
70
|
+
*
|
|
71
|
+
* `{ orgId, email, confirmEmail }` with no record, from the workspace's
|
|
72
|
+
* Privacy settings: the same workspace admin, for any person the workspace
|
|
73
|
+
* may hold — including one no page shows, since the CRM that lists records
|
|
74
|
+
* is included from Starter and a Free workspace has none of it. Nothing is
|
|
75
|
+
* read to file it: the sweep is keyed by the address, the markers are found
|
|
76
|
+
* by it, and the typed address must match its second typing, which is the
|
|
77
|
+
* confirmation a record's address gives the other variants. No plan is
|
|
78
|
+
* asked on any variant: erasure is an obligation, not a CRM feature.
|
|
79
|
+
*/ export const crmErasePersonHandler = async (req, res)=>{
|
|
80
|
+
var _req_headers_authorization, _req_body, _body_contactId, _body_leadId;
|
|
81
|
+
if (req.method !== 'POST') {
|
|
82
|
+
res.setHeader('Allow', 'POST');
|
|
83
|
+
res.status(405).json({
|
|
84
|
+
error: 'Method not allowed'
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
89
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
90
|
+
if (!idToken) {
|
|
91
|
+
res.status(401).json({
|
|
92
|
+
error: 'Unauthenticated'
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const body = typeof req.body === 'string' ? JSON.parse(req.body) : (_req_body = req.body) != null ? _req_body : {};
|
|
97
|
+
const routeScope = readCrmRouteScope(body);
|
|
98
|
+
const contactId = String((_body_contactId = body.contactId) != null ? _body_contactId : '').trim();
|
|
99
|
+
const leadId = String((_body_leadId = body.leadId) != null ? _body_leadId : '').trim();
|
|
100
|
+
const byAddress = (routeScope == null ? void 0 : routeScope.level) === 'org' && !contactId && !leadId;
|
|
101
|
+
if (!routeScope || !byAddress && !contactId && !leadId || contactId && leadId) {
|
|
102
|
+
res.status(400).json({
|
|
103
|
+
error: 'Name the site and exactly one contact or lead'
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const { hostId } = routeScope;
|
|
108
|
+
if (leadId && !hostId) {
|
|
109
|
+
res.status(400).json({
|
|
110
|
+
error: 'Name the site the lead lives under'
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const refusal = 'Only a workspace admin can erase a person from the workspace';
|
|
115
|
+
try {
|
|
116
|
+
let orgId;
|
|
117
|
+
let actor;
|
|
118
|
+
if (routeScope.level === 'org') {
|
|
119
|
+
const caller = await authorizeOrgCaller(req, routeScope.orgId, {
|
|
120
|
+
needs: 'manage-org',
|
|
121
|
+
refusal
|
|
122
|
+
});
|
|
123
|
+
if (caller.ok === false) {
|
|
124
|
+
res.status(caller.status).json({
|
|
125
|
+
error: caller.error
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
orgId = caller.orgId;
|
|
130
|
+
actor = {
|
|
131
|
+
uid: caller.uid,
|
|
132
|
+
email: caller.email
|
|
133
|
+
};
|
|
134
|
+
} else {
|
|
135
|
+
var _decoded_email;
|
|
136
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
137
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
138
|
+
hostId
|
|
139
|
+
});
|
|
140
|
+
if (!canManageOrg(membership.role)) {
|
|
141
|
+
res.status(403).json({
|
|
142
|
+
error: refusal
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const resolved = await getOrgForHost(hostId);
|
|
147
|
+
if (!resolved || resolved.orgId !== membership.orgId) {
|
|
148
|
+
res.status(404).json({
|
|
149
|
+
error: 'Unknown site'
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
orgId = resolved.orgId;
|
|
154
|
+
actor = {
|
|
155
|
+
uid: decoded.uid,
|
|
156
|
+
email: (_decoded_email = decoded.email) != null ? _decoded_email : null
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
160
|
+
let recordEmail = null;
|
|
161
|
+
if (byAddress) {
|
|
162
|
+
recordEmail = normalizeContactEmail(body.email);
|
|
163
|
+
} else if (contactId) {
|
|
164
|
+
const contactsRef = routeScope.level === 'org' ? firestore.collection('orgs').doc(orgId).collection('contacts') : await orgDataCollectionForHost(hostId, 'contacts');
|
|
165
|
+
const contact = await contactsRef.doc(contactId).get();
|
|
166
|
+
if (!contact.exists) {
|
|
167
|
+
res.status(404).json({
|
|
168
|
+
error: 'Unknown contact'
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
recordEmail = normalizeContactEmail(contact.get('email'));
|
|
173
|
+
} else {
|
|
174
|
+
const lead = await firestore.collection('hosts').doc(hostId).collection('leads').doc(leadId).get();
|
|
175
|
+
if (!lead.exists) {
|
|
176
|
+
res.status(404).json({
|
|
177
|
+
error: 'Unknown lead'
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
recordEmail = normalizeContactEmail(lead.get('email'));
|
|
182
|
+
}
|
|
183
|
+
const key = recordEmail ? personKey(recordEmail) : null;
|
|
184
|
+
if (!recordEmail || !key) {
|
|
185
|
+
res.status(byAddress ? 400 : 422).json({
|
|
186
|
+
error: byAddress ? 'Enter the email address of the person to erase' : 'This record has no usable email address to erase by'
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (byAddress && !personErasureConfirmationMatches(body.confirmEmail, recordEmail)) {
|
|
191
|
+
res.status(400).json({
|
|
192
|
+
error: 'Type the email address a second time, exactly, to confirm the erasure'
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!byAddress && !personErasureConfirmationMatches(body.email, recordEmail)) {
|
|
197
|
+
res.status(400).json({
|
|
198
|
+
error: 'Type the record’s email address exactly to confirm the erasure'
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const requestId = personErasureId(orgId, key);
|
|
203
|
+
const requestRef = firestore.collection(PERSON_ERASURES_COLLECTION).doc(requestId);
|
|
204
|
+
const existing = await requestRef.get();
|
|
205
|
+
if (existing.exists && existing.get('status') === 'pending') {
|
|
206
|
+
var _existing_get;
|
|
207
|
+
const answer = {
|
|
208
|
+
ok: true,
|
|
209
|
+
requestId,
|
|
210
|
+
pendingSinceMs: Number((_existing_get = existing.get('pendingSinceMs')) != null ? _existing_get : 0),
|
|
211
|
+
alreadyPending: true
|
|
212
|
+
};
|
|
213
|
+
res.status(200).json(answer);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const now = Date.now();
|
|
217
|
+
const request = _extends({
|
|
218
|
+
orgId,
|
|
219
|
+
personKey: key,
|
|
220
|
+
status: 'pending',
|
|
221
|
+
email: recordEmail,
|
|
222
|
+
requestedAtMs: now,
|
|
223
|
+
requestedByUid: actor.uid
|
|
224
|
+
}, hostId ? {
|
|
225
|
+
hostId
|
|
226
|
+
} : {}, contactId ? {
|
|
227
|
+
contactId
|
|
228
|
+
} : {}, leadId ? {
|
|
229
|
+
leadId
|
|
230
|
+
} : {}, {
|
|
231
|
+
pendingSinceMs: now
|
|
232
|
+
});
|
|
233
|
+
await requestRef.set(_extends({}, request, {
|
|
234
|
+
// A request re-filed after a failed or completed run starts clean.
|
|
235
|
+
erasedAtMs: FieldValue.delete(),
|
|
236
|
+
result: FieldValue.delete(),
|
|
237
|
+
failedAtMs: FieldValue.delete(),
|
|
238
|
+
lastError: FieldValue.delete(),
|
|
239
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
240
|
+
}), {
|
|
241
|
+
merge: true
|
|
242
|
+
});
|
|
243
|
+
const hostIds = await orgHostIds(firestore, orgId);
|
|
244
|
+
for (const siteId of hostIds){
|
|
245
|
+
await suppressEmailForHostErasure({
|
|
246
|
+
hostId: siteId,
|
|
247
|
+
email: recordEmail
|
|
248
|
+
}).catch((error)=>{
|
|
249
|
+
console.error('[crm] erase-person suppression write failed', siteId, error);
|
|
250
|
+
});
|
|
251
|
+
const lead = firestore.collection('hosts').doc(siteId).collection('leads').doc(key);
|
|
252
|
+
await lead.get().then((snapshot)=>snapshot.exists ? lead.update({
|
|
253
|
+
[CONTACT_ERASURE_REQUESTED_FIELD]: now
|
|
254
|
+
}) : undefined).catch((error)=>{
|
|
255
|
+
console.error('[crm] erase-person lead marker failed', siteId, error);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const contacts = await firestore.collection('orgs').doc(orgId).collection('contacts').where('email', '==', recordEmail).get();
|
|
260
|
+
for (const contact of contacts.docs){
|
|
261
|
+
await contact.ref.update({
|
|
262
|
+
[CONTACT_ERASURE_REQUESTED_FIELD]: now
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('[crm] erase-person contact marker failed', orgId, error);
|
|
267
|
+
}
|
|
268
|
+
// The feed the act was performed in: the site's under a site, the
|
|
269
|
+
// org's at the organization level.
|
|
270
|
+
const target = {
|
|
271
|
+
type: contactId ? 'contact' : 'lead',
|
|
272
|
+
id: contactId || leadId
|
|
273
|
+
};
|
|
274
|
+
if (routeScope.level === 'org') {
|
|
275
|
+
// Filed by address, the line names no record — and never the address.
|
|
276
|
+
await logOrgActivity(orgId, actor, 'Requested privacy erasure', byAddress ? {
|
|
277
|
+
type: 'org'
|
|
278
|
+
} : target).catch(()=>undefined);
|
|
279
|
+
} else {
|
|
280
|
+
await logHostActivity(hostId, actor, 'Requested privacy erasure', target).catch(()=>undefined);
|
|
281
|
+
}
|
|
282
|
+
await firestore.collection('adminAudit').add({
|
|
283
|
+
actorUid: actor.uid,
|
|
284
|
+
action: 'person.erasure-requested',
|
|
285
|
+
target: `orgs/${orgId}/people/${key}`,
|
|
286
|
+
before: null,
|
|
287
|
+
after: {
|
|
288
|
+
hostId: hostId || null,
|
|
289
|
+
hosts: hostIds.length,
|
|
290
|
+
from: byAddress ? 'address' : contactId ? 'contact' : 'lead'
|
|
291
|
+
},
|
|
292
|
+
at: FieldValue.serverTimestamp()
|
|
293
|
+
}).catch(()=>undefined);
|
|
294
|
+
const answer = {
|
|
295
|
+
ok: true,
|
|
296
|
+
requestId,
|
|
297
|
+
pendingSinceMs: now,
|
|
298
|
+
alreadyPending: false
|
|
299
|
+
};
|
|
300
|
+
res.status(200).json(answer);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error('[crm] erase-person failed', routeScope, error);
|
|
303
|
+
res.status(500).json({
|
|
304
|
+
error: 'The erasure could not be filed'
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
//# sourceMappingURL=erase-person.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/erase-person.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 {\n canManageOrg,\n CONTACT_ERASURE_REQUESTED_FIELD,\n normalizeContactEmail,\n PERSON_ERASURES_COLLECTION,\n personErasureConfirmationMatches,\n personErasureId,\n type PersonErasureRequest,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport { personKey } from '@aglyn/aglyn/app-utils/person-key'\nimport {\n firebaseAdmin,\n getOrgForHost,\n logHostActivity,\n logOrgActivity,\n orgDataCollectionForHost,\n suppressEmailForHostErasure,\n} from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { authorizeOrgCaller, orgHostIds, readCrmRouteScope } from './org-caller'\n\n/** The route key, as `registerCrmConsoleApi` registers it. */\nexport const CRM_ERASE_PERSON_ROUTE = 'crm/erase-person'\n\nexport interface ErasePersonRequestBody {\n /**\n * The site the request is filed from. At the organization level\n * (AGL-2634) the record's own site, or absent for a contact no site has\n * captured; a LEAD always names one, because a lead lives under its site.\n */\n hostId?: string\n /** The organization, at the organization level (AGL-2634). */\n orgId?: string\n /**\n * At most one of these names the record the request is filed from. Under\n * a site exactly one does; at the organization level neither may, which\n * files the request by `email` alone (AGL-2839).\n */\n contactId?: string\n leadId?: string\n /**\n * The address as the admin typed it — the confirmation of a record's\n * address, or, filed by address alone, the person the request is about.\n */\n email: string\n /** Filed by address alone: the address typed a second time. */\n confirmEmail?: string\n}\n\nexport interface ErasePersonResponse {\n ok: true\n requestId: string\n /** When the request entered the queue. */\n pendingSinceMs: number\n /** True when a request for this person was already waiting; nothing was re-filed. */\n alreadyPending: boolean\n}\n\n/**\n * `POST /api/crm/erase-person` — file a privacy erasure for one person\n * (AGL-2623).\n *\n * ## Who may\n *\n * A workspace admin or owner — `canManageOrg` on the caller's org role —\n * and nobody else. Not a site editor with the data permission, who can\n * detach a contact from their own site but has no standing to remove the\n * person from every site in the workspace; and not staff acting alone,\n * because the workspace is the controller of this data and the instruction\n * has to come from it. Staff who are also admins of the workspace pass on\n * that role, as anyone would.\n *\n * ## What it does, and what it does not\n *\n * It files the request and closes the doors; it deletes nothing. The daily\n * erasure job executes the sweep, so that a workspace's person erasures and\n * its own erasure run through one job, one audit trail and one queue a\n * staff member can watch. What happens here, in order:\n *\n * 1. The record named by the body is read and its address is what the\n * request is about — the body's address must match it, typed, so an\n * admin who opened the wrong record is stopped by the confirmation\n * rather than by luck.\n * 2. The request document is written, or found already pending, in\n * which case nothing is re-filed and the caller is told so.\n * 3. Every site of the workspace gets its suppression row NOW, not when\n * the job runs: from this moment a capture cannot rebuild the person.\n * 4. The contact document and each site's lead are stamped with the\n * request time, so their pages can say \"erasure pending\" off the\n * document they already read.\n * 5. The site's activity feed and the platform audit log each get a row\n * that names the record by id and the person by hash, never by\n * address.\n *\n * ## The organization variant (AGL-2634)\n *\n * `{ orgId, hostId?, contactId | leadId, email }` from the org-level hub:\n * the same workspace admin, authorized by the org rather than through a\n * site they name, so a contact no site captured can be erased from the\n * page that lists it. The sweep is what it always was — every site of the\n * org, found by `orgId` — and the row goes to the org's feed. A lead still\n * names its site, because that is where a lead lives.\n *\n * ## By address alone (AGL-2839)\n *\n * `{ orgId, email, confirmEmail }` with no record, from the workspace's\n * Privacy settings: the same workspace admin, for any person the workspace\n * may hold — including one no page shows, since the CRM that lists records\n * is included from Starter and a Free workspace has none of it. Nothing is\n * read to file it: the sweep is keyed by the address, the markers are found\n * by it, and the typed address must match its second typing, which is the\n * confirmation a record's address gives the other variants. No plan is\n * asked on any variant: erasure is an obligation, not a CRM feature.\n */\nexport const crmErasePersonHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) {\n res.status(401).json({ error: 'Unauthenticated' })\n return\n }\n const body: Partial<ErasePersonRequestBody> =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const routeScope = readCrmRouteScope(body as Record<string, unknown>)\n const contactId = String(body.contactId ?? '').trim()\n const leadId = String(body.leadId ?? '').trim()\n const byAddress = routeScope?.level === 'org' && !contactId && !leadId\n if (!routeScope || (!byAddress && !contactId && !leadId) || (contactId && leadId)) {\n res.status(400).json({ error: 'Name the site and exactly one contact or lead' })\n return\n }\n const { hostId } = routeScope\n if (leadId && !hostId) {\n res.status(400).json({ error: 'Name the site the lead lives under' })\n return\n }\n const refusal = 'Only a workspace admin can erase a person from the workspace'\n\n try {\n let orgId: string\n let actor: { uid: string; email: string | null }\n if (routeScope.level === 'org') {\n const caller = await authorizeOrgCaller(req, routeScope.orgId, {\n needs: 'manage-org',\n refusal,\n })\n if (caller.ok === false) {\n res.status(caller.status).json({ error: caller.error })\n return\n }\n orgId = caller.orgId\n actor = { uid: caller.uid, email: caller.email }\n } else {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!canManageOrg(membership.role)) {\n res.status(403).json({ error: refusal })\n return\n }\n const resolved = await getOrgForHost(hostId)\n if (!resolved || resolved.orgId !== membership.orgId) {\n res.status(404).json({ error: 'Unknown site' })\n return\n }\n orgId = resolved.orgId\n actor = { uid: decoded.uid, email: decoded.email ?? null }\n }\n const firestore = firebaseAdmin.app().firestore()\n\n let recordEmail: string | null = null\n if (byAddress) {\n recordEmail = normalizeContactEmail(body.email)\n } else if (contactId) {\n const contactsRef =\n routeScope.level === 'org'\n ? firestore.collection('orgs').doc(orgId).collection('contacts')\n : await orgDataCollectionForHost(hostId, 'contacts')\n const contact = await contactsRef.doc(contactId).get()\n if (!contact.exists) {\n res.status(404).json({ error: 'Unknown contact' })\n return\n }\n recordEmail = normalizeContactEmail(contact.get('email'))\n } else {\n const lead = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('leads')\n .doc(leadId)\n .get()\n if (!lead.exists) {\n res.status(404).json({ error: 'Unknown lead' })\n return\n }\n recordEmail = normalizeContactEmail(lead.get('email'))\n }\n const key = recordEmail ? personKey(recordEmail) : null\n if (!recordEmail || !key) {\n res.status(byAddress ? 400 : 422).json({\n error: byAddress\n ? 'Enter the email address of the person to erase'\n : 'This record has no usable email address to erase by',\n })\n return\n }\n if (byAddress && !personErasureConfirmationMatches(body.confirmEmail, recordEmail)) {\n res.status(400).json({\n error: 'Type the email address a second time, exactly, to confirm the erasure',\n })\n return\n }\n if (!byAddress && !personErasureConfirmationMatches(body.email, recordEmail)) {\n res.status(400).json({\n error: 'Type the record’s email address exactly to confirm the erasure',\n })\n return\n }\n\n const requestId = personErasureId(orgId, key)\n const requestRef = firestore.collection(PERSON_ERASURES_COLLECTION).doc(requestId)\n const existing = await requestRef.get()\n if (existing.exists && existing.get('status') === 'pending') {\n const answer: ErasePersonResponse = {\n ok: true,\n requestId,\n pendingSinceMs: Number(existing.get('pendingSinceMs') ?? 0),\n alreadyPending: true,\n }\n res.status(200).json(answer)\n return\n }\n\n const now = Date.now()\n const request: PersonErasureRequest = {\n orgId,\n personKey: key,\n status: 'pending',\n email: recordEmail,\n requestedAtMs: now,\n requestedByUid: actor.uid,\n ...(hostId ? { hostId } : {}),\n ...(contactId ? { contactId } : {}),\n ...(leadId ? { leadId } : {}),\n pendingSinceMs: now,\n }\n await requestRef.set(\n {\n ...request,\n // A request re-filed after a failed or completed run starts clean.\n erasedAtMs: FieldValue.delete(),\n result: FieldValue.delete(),\n failedAtMs: FieldValue.delete(),\n lastError: FieldValue.delete(),\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n\n const hostIds = await orgHostIds(firestore, orgId)\n for (const siteId of hostIds) {\n await suppressEmailForHostErasure({ hostId: siteId, email: recordEmail }).catch(\n (error: unknown) => {\n console.error('[crm] erase-person suppression write failed', siteId, error)\n },\n )\n const lead = firestore.collection('hosts').doc(siteId).collection('leads').doc(key)\n await lead\n .get()\n .then((snapshot: { exists: boolean }) =>\n snapshot.exists ? lead.update({ [CONTACT_ERASURE_REQUESTED_FIELD]: now }) : undefined,\n )\n .catch((error: unknown) => {\n console.error('[crm] erase-person lead marker failed', siteId, error)\n })\n }\n try {\n const contacts = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection('contacts')\n .where('email', '==', recordEmail)\n .get()\n for (const contact of contacts.docs) {\n await contact.ref.update({ [CONTACT_ERASURE_REQUESTED_FIELD]: now })\n }\n } catch (error) {\n console.error('[crm] erase-person contact marker failed', orgId, error)\n }\n\n // The feed the act was performed in: the site's under a site, the\n // org's at the organization level.\n const target = { type: contactId ? 'contact' : 'lead', id: contactId || leadId } as const\n if (routeScope.level === 'org') {\n // Filed by address, the line names no record — and never the address.\n await logOrgActivity(\n orgId,\n actor,\n 'Requested privacy erasure',\n byAddress ? { type: 'org' } : target,\n ).catch(() => undefined)\n } else {\n await logHostActivity(hostId, actor, 'Requested privacy erasure', target).catch(\n () => undefined,\n )\n }\n await firestore\n .collection('adminAudit')\n .add({\n actorUid: actor.uid,\n action: 'person.erasure-requested',\n target: `orgs/${orgId}/people/${key}`,\n before: null,\n after: {\n hostId: hostId || null,\n hosts: hostIds.length,\n from: byAddress ? 'address' : contactId ? 'contact' : 'lead',\n },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n\n const answer: ErasePersonResponse = {\n ok: true,\n requestId,\n pendingSinceMs: now,\n alreadyPending: false,\n }\n res.status(200).json(answer)\n } catch (error) {\n console.error('[crm] erase-person failed', routeScope, error)\n res.status(500).json({ error: 'The erasure could not be filed' })\n }\n}\n"],"names":["canManageOrg","CONTACT_ERASURE_REQUESTED_FIELD","normalizeContactEmail","PERSON_ERASURES_COLLECTION","personErasureConfirmationMatches","personErasureId","personKey","firebaseAdmin","getOrgForHost","logHostActivity","logOrgActivity","orgDataCollectionForHost","suppressEmailForHostErasure","resolveOrgPermissions","FieldValue","authorizeOrgCaller","orgHostIds","readCrmRouteScope","CRM_ERASE_PERSON_ROUTE","crmErasePersonHandler","req","res","body","method","setHeader","status","json","error","authorization","String","headers","idToken","startsWith","slice","length","undefined","JSON","parse","routeScope","contactId","trim","leadId","byAddress","level","hostId","refusal","orgId","actor","caller","needs","ok","uid","email","decoded","app","auth","verifyIdToken","membership","role","resolved","firestore","recordEmail","contactsRef","collection","doc","contact","get","exists","lead","key","confirmEmail","requestId","requestRef","existing","answer","pendingSinceMs","Number","alreadyPending","now","Date","request","requestedAtMs","requestedByUid","set","erasedAtMs","delete","result","failedAtMs","lastError","updatedAt","serverTimestamp","merge","hostIds","siteId","catch","console","then","snapshot","update","contacts","where","docs","ref","target","type","id","add","actorUid","action","before","after","hosts","from","at"],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* WHAT EVERY CSV IMPORT ROUTE ASKS BEFORE IT READS A ROW (AGL-2602, shared
|
|
19
|
+
* by the companies import since AGL-2621).
|
|
20
|
+
*
|
|
21
|
+
* Who is asking and on whose behalf, whether the request is the size a
|
|
22
|
+
* chunk is allowed to be, and — when a row names an owner — which member
|
|
23
|
+
* of the org that address is. The contacts route settled each answer; the
|
|
24
|
+
* companies route needs the same three and must not spell them
|
|
25
|
+
* differently, because two gates for one permission is how one of them
|
|
26
|
+
* comes to be wider.
|
|
27
|
+
*
|
|
28
|
+
* ## Who may call an import
|
|
29
|
+
*
|
|
30
|
+
* The same two gates the rules put on every CRM collection: a site role
|
|
31
|
+
* on the host the request names, AND the organization's `data.manage`
|
|
32
|
+
* permission resolved through the member's custom role and overrides. The
|
|
33
|
+
* Admin SDK evaluates no rules, so this is the enforcement rather than an
|
|
34
|
+
* echo of it — and `data.manage` rather than the raw role because an owner
|
|
35
|
+
* who unticked "Manage data" on a custom role meant it. A staff token
|
|
36
|
+
* passes both gates the way it does on every console route: support
|
|
37
|
+
* importing a customer's file for them is the support act this exists for.
|
|
38
|
+
*/
|
|
39
|
+
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
40
|
+
/** Everything an import needs about who is asking, or the refusal to send back. */
|
|
41
|
+
export type ImportContext = {
|
|
42
|
+
ok: true;
|
|
43
|
+
uid: string;
|
|
44
|
+
hostId: string;
|
|
45
|
+
orgId: string;
|
|
46
|
+
org: Record<string, unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* The stamp a record CREATED by this import carries — the org, when
|
|
49
|
+
* the org widened its default, and otherwise the capturing group.
|
|
50
|
+
*/
|
|
51
|
+
scopeTokens: string[];
|
|
52
|
+
/**
|
|
53
|
+
* The tokens a record must overlap to be SEEN from this site: the
|
|
54
|
+
* group's own, plus `org`, because an org-wide record is visible to
|
|
55
|
+
* every site in the account. The reader's set is wider than the
|
|
56
|
+
* creator's stamp, exactly as it is on the collection listeners.
|
|
57
|
+
*/
|
|
58
|
+
readTokens: string[];
|
|
59
|
+
} | {
|
|
60
|
+
ok: false;
|
|
61
|
+
status: number;
|
|
62
|
+
body: Record<string, unknown>;
|
|
63
|
+
};
|
|
64
|
+
export type ImportRequest = Parameters<PluginApiHandler>[0];
|
|
65
|
+
/** Who is asking, and on whose behalf. */
|
|
66
|
+
export declare function resolveImportContext(req: ImportRequest): Promise<ImportContext>;
|
|
67
|
+
/** The rows a request may carry, or the refusal to send back. */
|
|
68
|
+
export declare function readImportRows<R>(req: ImportRequest, limits: {
|
|
69
|
+
maxBodyBytes: number;
|
|
70
|
+
chunkSize: number;
|
|
71
|
+
}): {
|
|
72
|
+
rows: R[];
|
|
73
|
+
} | {
|
|
74
|
+
status: number;
|
|
75
|
+
error: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The org's members by address, read only when a row names an owner.
|
|
79
|
+
*
|
|
80
|
+
* A file with no owner column costs no roster read; a file with one costs
|
|
81
|
+
* exactly one, however many rows it has.
|
|
82
|
+
*/
|
|
83
|
+
export declare function ownerDirectory(orgId: string, rows: readonly {
|
|
84
|
+
ownerEmail?: string;
|
|
85
|
+
}[]): Promise<Map<string, string>>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* WHAT EVERY CSV IMPORT ROUTE ASKS BEFORE IT READS A ROW (AGL-2602, shared
|
|
18
|
+
* by the companies import since AGL-2621).
|
|
19
|
+
*
|
|
20
|
+
* Who is asking and on whose behalf, whether the request is the size a
|
|
21
|
+
* chunk is allowed to be, and — when a row names an owner — which member
|
|
22
|
+
* of the org that address is. The contacts route settled each answer; the
|
|
23
|
+
* companies route needs the same three and must not spell them
|
|
24
|
+
* differently, because two gates for one permission is how one of them
|
|
25
|
+
* comes to be wider.
|
|
26
|
+
*
|
|
27
|
+
* ## Who may call an import
|
|
28
|
+
*
|
|
29
|
+
* The same two gates the rules put on every CRM collection: a site role
|
|
30
|
+
* on the host the request names, AND the organization's `data.manage`
|
|
31
|
+
* permission resolved through the member's custom role and overrides. The
|
|
32
|
+
* Admin SDK evaluates no rules, so this is the enforcement rather than an
|
|
33
|
+
* echo of it — and `data.manage` rather than the raw role because an owner
|
|
34
|
+
* who unticked "Manage data" on a custom role meant it. A staff token
|
|
35
|
+
* passes both gates the way it does on every console route: support
|
|
36
|
+
* importing a customer's file for them is the support act this exists for.
|
|
37
|
+
*/ import { consentGroupScope, crmScopeTokens, ORG_SCOPE_TOKEN } from "@aglyn/aglyn/server";
|
|
38
|
+
import { consentGroupForSite, firebaseAdmin, getOrgForHost, listOrgMembers, memberHasOrgPermission, resolveOrgMembership } from "@aglyn/tenant-data-admin";
|
|
39
|
+
import { crmSuiteRefusal } from "./suite-gate.js";
|
|
40
|
+
/** Who is asking, and on whose behalf. */ export async function resolveImportContext(req) {
|
|
41
|
+
var _ref, _req_headers_authorization, _hostSnapshot_get, _ref1, _ref2;
|
|
42
|
+
var _req_body;
|
|
43
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
44
|
+
if (!hostId) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
status: 400,
|
|
48
|
+
body: {
|
|
49
|
+
error: 'Missing hostId'
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
54
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
55
|
+
if (!idToken) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
status: 401,
|
|
59
|
+
body: {
|
|
60
|
+
error: 'Unauthenticated'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
65
|
+
const staff = decoded['staff'] === true;
|
|
66
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
67
|
+
const hostSnapshot = await firestore.collection('hosts').doc(hostId).get();
|
|
68
|
+
if (!hostSnapshot.exists) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
status: 404,
|
|
72
|
+
body: {
|
|
73
|
+
error: 'Unknown site'
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const memberRole = ((_hostSnapshot_get = hostSnapshot.get('memberRoles')) != null ? _hostSnapshot_get : {})[decoded.uid];
|
|
78
|
+
if (!staff && memberRole !== 'admin' && memberRole !== 'editor') {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
status: 403,
|
|
82
|
+
body: {
|
|
83
|
+
error: 'Not a site admin or editor'
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const resolved = await getOrgForHost(hostId).catch(()=>null);
|
|
88
|
+
const orgId = String((_ref1 = resolved == null ? void 0 : resolved.orgId) != null ? _ref1 : '');
|
|
89
|
+
if (!orgId) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
status: 404,
|
|
93
|
+
body: {
|
|
94
|
+
error: 'This site has no organization, so it has no CRM.'
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const org = (_ref2 = resolved == null ? void 0 : resolved.org) != null ? _ref2 : {};
|
|
99
|
+
const membership = await resolveOrgMembership(decoded.uid, orgId).catch(()=>null);
|
|
100
|
+
const member = membership == null ? void 0 : membership.member;
|
|
101
|
+
if (!staff && (!member || member.orgSuspended === true || !await memberHasOrgPermission(orgId, member, 'data.manage'))) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
status: 403,
|
|
105
|
+
body: {
|
|
106
|
+
error: 'Your organization role does not allow managing contacts and ' + 'companies, so it cannot import them.'
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Every file this context serves — contacts, companies, deals, tasks and
|
|
111
|
+
// leads — is the CRM's to import, whoever is asking (AGL-2787).
|
|
112
|
+
const suite = crmSuiteRefusal(org, 'Importing from a CSV file');
|
|
113
|
+
if (suite) return {
|
|
114
|
+
ok: false,
|
|
115
|
+
status: suite.status,
|
|
116
|
+
body: suite.body
|
|
117
|
+
};
|
|
118
|
+
const group = await consentGroupForSite(hostId);
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
uid: decoded.uid,
|
|
122
|
+
hostId,
|
|
123
|
+
orgId,
|
|
124
|
+
org,
|
|
125
|
+
scopeTokens: crmScopeTokens(org, group),
|
|
126
|
+
readTokens: [
|
|
127
|
+
ORG_SCOPE_TOKEN,
|
|
128
|
+
...consentGroupScope(group)
|
|
129
|
+
]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** The rows a request may carry, or the refusal to send back. */ export function readImportRows(req, limits) {
|
|
133
|
+
var _ref;
|
|
134
|
+
var _req_rawBody, _req_body;
|
|
135
|
+
if (((_ref = (_req_rawBody = req.rawBody) == null ? void 0 : _req_rawBody.length) != null ? _ref : 0) > limits.maxBodyBytes) {
|
|
136
|
+
return {
|
|
137
|
+
status: 413,
|
|
138
|
+
error: 'That request is too large. Import the file in smaller pieces.'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const rows = (_req_body = req.body) == null ? void 0 : _req_body.rows;
|
|
142
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
status: 400,
|
|
145
|
+
error: 'No rows to import.'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (rows.length > limits.chunkSize) {
|
|
149
|
+
return {
|
|
150
|
+
status: 400,
|
|
151
|
+
error: `At most ${limits.chunkSize} rows per request.`
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (!rows.every((row)=>row && typeof row === 'object' && !Array.isArray(row))) {
|
|
155
|
+
return {
|
|
156
|
+
status: 400,
|
|
157
|
+
error: 'Every row must be an object.'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
rows: rows
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The org's members by address, read only when a row names an owner.
|
|
166
|
+
*
|
|
167
|
+
* A file with no owner column costs no roster read; a file with one costs
|
|
168
|
+
* exactly one, however many rows it has.
|
|
169
|
+
*/ export async function ownerDirectory(orgId, rows) {
|
|
170
|
+
const directory = new Map();
|
|
171
|
+
if (!rows.some((row)=>row.ownerEmail)) return directory;
|
|
172
|
+
const members = await listOrgMembers(orgId);
|
|
173
|
+
for (const member of members){
|
|
174
|
+
var _member_email;
|
|
175
|
+
const email = String((_member_email = member.email) != null ? _member_email : '').trim().toLowerCase();
|
|
176
|
+
if (email && member.$id) directory.set(email, String(member.$id));
|
|
177
|
+
}
|
|
178
|
+
return directory;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=import-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/import-context.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 * WHAT EVERY CSV IMPORT ROUTE ASKS BEFORE IT READS A ROW (AGL-2602, shared\n * by the companies import since AGL-2621).\n *\n * Who is asking and on whose behalf, whether the request is the size a\n * chunk is allowed to be, and — when a row names an owner — which member\n * of the org that address is. The contacts route settled each answer; the\n * companies route needs the same three and must not spell them\n * differently, because two gates for one permission is how one of them\n * comes to be wider.\n *\n * ## Who may call an import\n *\n * The same two gates the rules put on every CRM collection: a site role\n * on the host the request names, AND the organization's `data.manage`\n * permission resolved through the member's custom role and overrides. The\n * Admin SDK evaluates no rules, so this is the enforcement rather than an\n * echo of it — and `data.manage` rather than the raw role because an owner\n * who unticked \"Manage data\" on a custom role meant it. A staff token\n * passes both gates the way it does on every console route: support\n * importing a customer's file for them is the support act this exists for.\n */\n\nimport {\n consentGroupScope,\n crmScopeTokens,\n ORG_SCOPE_TOKEN,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport {\n consentGroupForSite,\n firebaseAdmin,\n getOrgForHost,\n listOrgMembers,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { crmSuiteRefusal } from './suite-gate'\n\n/** Everything an import needs about who is asking, or the refusal to send back. */\nexport type ImportContext =\n | {\n ok: true\n uid: string\n hostId: string\n orgId: string\n org: Record<string, unknown>\n /**\n * The stamp a record CREATED by this import carries — the org, when\n * the org widened its default, and otherwise the capturing group.\n */\n scopeTokens: string[]\n /**\n * The tokens a record must overlap to be SEEN from this site: the\n * group's own, plus `org`, because an org-wide record is visible to\n * every site in the account. The reader's set is wider than the\n * creator's stamp, exactly as it is on the collection listeners.\n */\n readTokens: string[]\n }\n | { ok: false; status: number; body: Record<string, unknown> }\n\nexport type ImportRequest = Parameters<PluginApiHandler>[0]\n\n/** Who is asking, and on whose behalf. */\nexport async function resolveImportContext(req: ImportRequest): Promise<ImportContext> {\n const hostId = String(req.body?.hostId ?? '')\n if (!hostId) {\n return { ok: false, status: 400, body: { error: 'Missing hostId' } }\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) {\n return { ok: false, status: 401, body: { error: 'Unauthenticated' } }\n }\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const staff = decoded['staff'] === true\n const firestore = firebaseAdmin.app().firestore()\n const hostSnapshot = await firestore.collection('hosts').doc(hostId).get()\n if (!hostSnapshot.exists) {\n return { ok: false, status: 404, body: { error: 'Unknown site' } }\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (!staff && memberRole !== 'admin' && memberRole !== 'editor') {\n return {\n ok: false,\n status: 403,\n body: { error: 'Not a site admin or editor' },\n }\n }\n const resolved = await getOrgForHost(hostId).catch(() => null)\n const orgId = String(resolved?.orgId ?? '')\n if (!orgId) {\n return {\n ok: false,\n status: 404,\n body: { error: 'This site has no organization, so it has no CRM.' },\n }\n }\n const org = (resolved?.org ?? {}) as Record<string, unknown>\n const membership = await resolveOrgMembership(decoded.uid, orgId).catch(\n () => null,\n )\n const member = membership?.member\n if (\n !staff &&\n (!member ||\n (member as { orgSuspended?: boolean }).orgSuspended === true ||\n !(await memberHasOrgPermission(orgId, member, 'data.manage')))\n ) {\n return {\n ok: false,\n status: 403,\n body: {\n error:\n 'Your organization role does not allow managing contacts and ' +\n 'companies, so it cannot import them.',\n },\n }\n }\n // Every file this context serves — contacts, companies, deals, tasks and\n // leads — is the CRM's to import, whoever is asking (AGL-2787).\n const suite = crmSuiteRefusal(org, 'Importing from a CSV file')\n if (suite) return { ok: false, status: suite.status, body: suite.body }\n const group = await consentGroupForSite(hostId)\n return {\n ok: true,\n uid: decoded.uid,\n hostId,\n orgId,\n org,\n scopeTokens: crmScopeTokens(org, group),\n readTokens: [ORG_SCOPE_TOKEN, ...consentGroupScope(group)],\n }\n}\n\n/** The rows a request may carry, or the refusal to send back. */\nexport function readImportRows<R>(\n req: ImportRequest,\n limits: { maxBodyBytes: number; chunkSize: number },\n): { rows: R[] } | { status: number; error: string } {\n if ((req.rawBody?.length ?? 0) > limits.maxBodyBytes) {\n return {\n status: 413,\n error: 'That request is too large. Import the file in smaller pieces.',\n }\n }\n const rows = req.body?.rows\n if (!Array.isArray(rows) || rows.length === 0) {\n return { status: 400, error: 'No rows to import.' }\n }\n if (rows.length > limits.chunkSize) {\n return {\n status: 400,\n error: `At most ${limits.chunkSize} rows per request.`,\n }\n }\n if (!rows.every((row) => row && typeof row === 'object' && !Array.isArray(row))) {\n return { status: 400, error: 'Every row must be an object.' }\n }\n return { rows: rows as R[] }\n}\n\n/**\n * The org's members by address, read only when a row names an owner.\n *\n * A file with no owner column costs no roster read; a file with one costs\n * exactly one, however many rows it has.\n */\nexport async function ownerDirectory(\n orgId: string,\n rows: readonly { ownerEmail?: string }[],\n): Promise<Map<string, string>> {\n const directory = new Map<string, string>()\n if (!rows.some((row) => row.ownerEmail)) return directory\n const members = await listOrgMembers(orgId)\n for (const member of members) {\n const email = String(member.email ?? '')\n .trim()\n .toLowerCase()\n if (email && member.$id) directory.set(email, String(member.$id))\n }\n return directory\n}\n"],"names":["consentGroupScope","crmScopeTokens","ORG_SCOPE_TOKEN","consentGroupForSite","firebaseAdmin","getOrgForHost","listOrgMembers","memberHasOrgPermission","resolveOrgMembership","crmSuiteRefusal","resolveImportContext","req","hostSnapshot","hostId","String","body","ok","status","error","authorization","headers","idToken","startsWith","slice","length","undefined","decoded","app","auth","verifyIdToken","staff","firestore","collection","doc","get","exists","memberRole","uid","resolved","catch","orgId","org","membership","member","orgSuspended","suite","group","scopeTokens","readTokens","readImportRows","limits","rawBody","maxBodyBytes","rows","Array","isArray","chunkSize","every","row","ownerDirectory","directory","Map","some","ownerEmail","members","email","trim","toLowerCase","$id","set"],"mappings":"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"}
|