@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.223

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.
Files changed (541) hide show
  1. package/README.md +2 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/activity-list.js +12 -5
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/companies-bulk-bar.d.ts +1 -4
  6. package/src/lib/components/companies-bulk-bar.js +7 -20
  7. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  8. package/src/lib/components/companies-section.d.ts +3 -3
  9. package/src/lib/components/companies-section.js +149 -46
  10. package/src/lib/components/companies-section.js.map +1 -1
  11. package/src/lib/components/company-edit-drawer.js +281 -20
  12. package/src/lib/components/company-edit-drawer.js.map +1 -1
  13. package/src/lib/components/company-properties-card.js +99 -8
  14. package/src/lib/components/company-properties-card.js.map +1 -1
  15. package/src/lib/components/contact-detail-page.js +3 -1
  16. package/src/lib/components/contact-detail-page.js.map +1 -1
  17. package/src/lib/components/contact-merge-dialog.js +33 -10
  18. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  19. package/src/lib/components/contact-properties-card.d.ts +0 -69
  20. package/src/lib/components/contact-properties-card.js +350 -60
  21. package/src/lib/components/contact-properties-card.js.map +1 -1
  22. package/src/lib/components/contact-reports-to-field.d.ts +46 -0
  23. package/src/lib/components/contact-reports-to-field.js +67 -0
  24. package/src/lib/components/contact-reports-to-field.js.map +1 -0
  25. package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
  26. package/src/lib/components/contacts-bulk-bar.js +7 -19
  27. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  28. package/src/lib/components/contacts-section.js +46 -33
  29. package/src/lib/components/contacts-section.js.map +1 -1
  30. package/src/lib/components/crm-call-actions.d.ts +10 -0
  31. package/src/lib/components/crm-call-actions.js +26 -9
  32. package/src/lib/components/crm-call-actions.js.map +1 -1
  33. package/src/lib/components/crm-next-activity-column.js +1 -1
  34. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  35. package/src/lib/components/crm-record-picker.d.ts +15 -0
  36. package/src/lib/components/crm-record-picker.js +16 -10
  37. package/src/lib/components/crm-record-picker.js.map +1 -1
  38. package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
  39. package/src/lib/components/crm-transfer-buttons.js +167 -0
  40. package/src/lib/components/crm-transfer-buttons.js.map +1 -0
  41. package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
  42. package/src/lib/components/deal-contact-roles-card.js +368 -0
  43. package/src/lib/components/deal-contact-roles-card.js.map +1 -0
  44. package/src/lib/components/deal-detail-page.d.ts +3 -2
  45. package/src/lib/components/deal-detail-page.js +25 -6
  46. package/src/lib/components/deal-detail-page.js.map +1 -1
  47. package/src/lib/components/deal-edit-drawer.d.ts +10 -0
  48. package/src/lib/components/deal-edit-drawer.js +219 -10
  49. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  50. package/src/lib/components/deal-properties-card.d.ts +7 -0
  51. package/src/lib/components/deal-properties-card.js +34 -3
  52. package/src/lib/components/deal-properties-card.js.map +1 -1
  53. package/src/lib/components/deal-stage-card.d.ts +4 -0
  54. package/src/lib/components/deal-stage-card.js +10 -2
  55. package/src/lib/components/deal-stage-card.js.map +1 -1
  56. package/src/lib/components/deals-bulk-bar.d.ts +0 -3
  57. package/src/lib/components/deals-bulk-bar.js +7 -20
  58. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  59. package/src/lib/components/deals-section.d.ts +3 -3
  60. package/src/lib/components/deals-section.js +123 -63
  61. package/src/lib/components/deals-section.js.map +1 -1
  62. package/src/lib/components/fields-section.js +31 -17
  63. package/src/lib/components/fields-section.js.map +1 -1
  64. package/src/lib/components/lead-convert-dialog.d.ts +4 -0
  65. package/src/lib/components/lead-convert-dialog.js +23 -1
  66. package/src/lib/components/lead-convert-dialog.js.map +1 -1
  67. package/src/lib/components/lead-detail-page.js +8 -2
  68. package/src/lib/components/lead-detail-page.js.map +1 -1
  69. package/src/lib/components/lead-profile-fields.d.ts +71 -0
  70. package/src/lib/components/lead-profile-fields.js +386 -0
  71. package/src/lib/components/lead-profile-fields.js.map +1 -0
  72. package/src/lib/components/lead-properties-card.d.ts +2 -1
  73. package/src/lib/components/lead-properties-card.js +85 -176
  74. package/src/lib/components/lead-properties-card.js.map +1 -1
  75. package/src/lib/components/lead-source-select.d.ts +4 -23
  76. package/src/lib/components/lead-source-select.js +9 -73
  77. package/src/lib/components/lead-source-select.js.map +1 -1
  78. package/src/lib/components/lead-status-chip.d.ts +9 -3
  79. package/src/lib/components/lead-status-chip.js +8 -4
  80. package/src/lib/components/lead-status-chip.js.map +1 -1
  81. package/src/lib/components/lead-status-options.d.ts +20 -0
  82. package/src/lib/components/lead-status-options.js +51 -0
  83. package/src/lib/components/lead-status-options.js.map +1 -0
  84. package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
  85. package/src/lib/components/lead-unqualify-dialog.js +50 -18
  86. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  87. package/src/lib/components/leads-bulk-bar.d.ts +0 -3
  88. package/src/lib/components/leads-bulk-bar.js +29 -29
  89. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  90. package/src/lib/components/leads-section.js +120 -68
  91. package/src/lib/components/leads-section.js.map +1 -1
  92. package/src/lib/components/linked-deals-card.d.ts +4 -0
  93. package/src/lib/components/linked-deals-card.js +47 -3
  94. package/src/lib/components/linked-deals-card.js.map +1 -1
  95. package/src/lib/components/log-activity-dialog.js +43 -3
  96. package/src/lib/components/log-activity-dialog.js.map +1 -1
  97. package/src/lib/components/new-contact-drawer.d.ts +8 -0
  98. package/src/lib/components/new-contact-drawer.js +108 -6
  99. package/src/lib/components/new-contact-drawer.js.map +1 -1
  100. package/src/lib/components/new-lead-drawer.d.ts +9 -2
  101. package/src/lib/components/new-lead-drawer.js +88 -121
  102. package/src/lib/components/new-lead-drawer.js.map +1 -1
  103. package/src/lib/components/picklist-select.d.ts +36 -0
  104. package/src/lib/components/picklist-select.js +114 -0
  105. package/src/lib/components/picklist-select.js.map +1 -0
  106. package/src/lib/components/picklist-values-card.d.ts +34 -0
  107. package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
  108. package/src/lib/components/picklist-values-card.js.map +1 -0
  109. package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
  110. package/src/lib/components/pipeline-stages-dialog.js +26 -4
  111. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  112. package/src/lib/components/pipelines-dialog.js +15 -6
  113. package/src/lib/components/pipelines-dialog.js.map +1 -1
  114. package/src/lib/components/recent-activity-feed.js +20 -5
  115. package/src/lib/components/recent-activity-feed.js.map +1 -1
  116. package/src/lib/components/recipes-card.js +4 -2
  117. package/src/lib/components/recipes-card.js.map +1 -1
  118. package/src/lib/components/record-tasks-card.js +15 -8
  119. package/src/lib/components/record-tasks-card.js.map +1 -1
  120. package/src/lib/components/reports/forecast-card.d.ts +6 -0
  121. package/src/lib/components/reports/forecast-card.js +101 -3
  122. package/src/lib/components/reports/forecast-card.js.map +1 -1
  123. package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
  124. package/src/lib/components/reports/lead-funnel-card.js +9 -5
  125. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  126. package/src/lib/components/reports/lead-source-card.d.ts +23 -0
  127. package/src/lib/components/reports/lead-source-card.js +318 -0
  128. package/src/lib/components/reports/lead-source-card.js.map +1 -0
  129. package/src/lib/components/reports/pipeline-card.js +2 -1
  130. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  131. package/src/lib/components/reports/report-export.js +1 -1
  132. package/src/lib/components/reports/report-export.js.map +1 -1
  133. package/src/lib/components/reports-section.js +8 -0
  134. package/src/lib/components/reports-section.js.map +1 -1
  135. package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
  136. package/src/lib/components/sharing-rule-drawer.js +108 -19
  137. package/src/lib/components/sharing-rule-drawer.js.map +1 -1
  138. package/src/lib/components/sharing-rules-card.js +11 -3
  139. package/src/lib/components/sharing-rules-card.js.map +1 -1
  140. package/src/lib/components/task-cells.d.ts +9 -1
  141. package/src/lib/components/task-cells.js +9 -4
  142. package/src/lib/components/task-cells.js.map +1 -1
  143. package/src/lib/components/task-edit-drawer.js +111 -43
  144. package/src/lib/components/task-edit-drawer.js.map +1 -1
  145. package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
  146. package/src/lib/components/tasks-bulk-bar.js +7 -20
  147. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
  148. package/src/lib/components/tasks-calendar.d.ts +3 -0
  149. package/src/lib/components/tasks-calendar.js +8 -1
  150. package/src/lib/components/tasks-calendar.js.map +1 -1
  151. package/src/lib/components/tasks-section.d.ts +3 -3
  152. package/src/lib/components/tasks-section.js +58 -43
  153. package/src/lib/components/tasks-section.js.map +1 -1
  154. package/src/lib/constants/api-routes.d.ts +2 -9
  155. package/src/lib/constants/api-routes.js +1 -7
  156. package/src/lib/constants/api-routes.js.map +1 -1
  157. package/src/lib/constants/bundle-common.d.ts +6 -0
  158. package/src/lib/constants/bundle-common.js +11 -0
  159. package/src/lib/constants/bundle-common.js.map +1 -1
  160. package/src/lib/constants/company-filters.d.ts +14 -0
  161. package/src/lib/constants/company-filters.js +51 -1
  162. package/src/lib/constants/company-filters.js.map +1 -1
  163. package/src/lib/constants/contact-filters.d.ts +2 -0
  164. package/src/lib/constants/contact-filters.js +18 -1
  165. package/src/lib/constants/contact-filters.js.map +1 -1
  166. package/src/lib/constants/deal-filters.d.ts +22 -1
  167. package/src/lib/constants/deal-filters.js +67 -4
  168. package/src/lib/constants/deal-filters.js.map +1 -1
  169. package/src/lib/declarations.console-server.d.ts +3 -0
  170. package/src/lib/declarations.console-server.js +35 -0
  171. package/src/lib/declarations.console-server.js.map +1 -1
  172. package/src/lib/declarations.d.ts +26 -0
  173. package/src/lib/declarations.js +35 -0
  174. package/src/lib/declarations.js.map +1 -0
  175. package/src/lib/declarations.server.d.ts +11 -0
  176. package/src/lib/declarations.server.js +97 -1
  177. package/src/lib/declarations.server.js.map +1 -1
  178. package/src/lib/hooks/use-company-picklists.d.ts +15 -0
  179. package/src/lib/hooks/use-company-picklists.js +55 -0
  180. package/src/lib/hooks/use-company-picklists.js.map +1 -0
  181. package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
  182. package/src/lib/hooks/use-crm-picklist.js +53 -0
  183. package/src/lib/hooks/use-crm-picklist.js.map +1 -0
  184. package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
  185. package/src/lib/hooks/use-crm-record-names.js +45 -19
  186. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  187. package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
  188. package/src/lib/hooks/use-crm-task-picklists.js +45 -0
  189. package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
  190. package/src/lib/hooks/use-deals.d.ts +14 -0
  191. package/src/lib/hooks/use-deals.js +29 -0
  192. package/src/lib/hooks/use-deals.js.map +1 -1
  193. package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
  194. package/src/lib/hooks/use-lead-picklists.js +46 -0
  195. package/src/lib/hooks/use-lead-picklists.js.map +1 -0
  196. package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
  197. package/src/lib/hooks/use-lead-source-picklist.js +12 -22
  198. package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
  199. package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
  200. package/src/lib/hooks/use-lead-status-picklist.js +40 -0
  201. package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
  202. package/src/lib/model/companies.d.ts +32 -3
  203. package/src/lib/model/companies.js +133 -16
  204. package/src/lib/model/companies.js.map +1 -1
  205. package/src/lib/model/contact-merge.d.ts +27 -3
  206. package/src/lib/model/contact-merge.js +81 -7
  207. package/src/lib/model/contact-merge.js.map +1 -1
  208. package/src/lib/model/contact-record.d.ts +17 -0
  209. package/src/lib/model/contact-record.js +17 -2
  210. package/src/lib/model/contact-record.js.map +1 -1
  211. package/src/lib/model/contact-update.d.ts +31 -2
  212. package/src/lib/model/contact-update.js +14 -0
  213. package/src/lib/model/contact-update.js.map +1 -1
  214. package/src/lib/model/crm-list-query.d.ts +5 -0
  215. package/src/lib/model/crm-list-query.js +8 -0
  216. package/src/lib/model/crm-list-query.js.map +1 -1
  217. package/src/lib/model/crm-next-activity.d.ts +107 -0
  218. package/src/lib/model/crm-next-activity.js +140 -0
  219. package/src/lib/model/crm-next-activity.js.map +1 -0
  220. package/src/lib/model/crm-recipes.d.ts +40 -0
  221. package/src/lib/model/crm-recipes.js +259 -0
  222. package/src/lib/model/crm-recipes.js.map +1 -0
  223. package/src/lib/model/crm-record-counts.d.ts +36 -0
  224. package/src/lib/model/crm-record-counts.js +54 -0
  225. package/src/lib/model/crm-record-counts.js.map +1 -0
  226. package/src/lib/model/crm-record-lists.d.ts +23 -0
  227. package/src/lib/model/crm-record-lists.js +161 -0
  228. package/src/lib/model/crm-record-lists.js.map +1 -0
  229. package/src/lib/model/crm-reports.d.ts +73 -3
  230. package/src/lib/model/crm-reports.js +120 -1
  231. package/src/lib/model/crm-reports.js.map +1 -1
  232. package/src/lib/model/crm-sharing.d.ts +21 -3
  233. package/src/lib/model/crm-sharing.js +27 -6
  234. package/src/lib/model/crm-sharing.js.map +1 -1
  235. package/src/lib/model/crm-task-reminders.d.ts +152 -0
  236. package/src/lib/model/crm-task-reminders.js +161 -0
  237. package/src/lib/model/crm-task-reminders.js.map +1 -0
  238. package/src/lib/model/deal-board-model.d.ts +8 -2
  239. package/src/lib/model/deal-board-model.js +15 -2
  240. package/src/lib/model/deal-board-model.js.map +1 -1
  241. package/src/lib/model/deal-contact-roles.d.ts +45 -0
  242. package/src/lib/model/deal-contact-roles.js +73 -0
  243. package/src/lib/model/deal-contact-roles.js.map +1 -0
  244. package/src/lib/model/deal-form-model.d.ts +56 -0
  245. package/src/lib/model/deal-form-model.js +79 -7
  246. package/src/lib/model/deal-form-model.js.map +1 -1
  247. package/src/lib/model/download-text-file.d.ts +23 -0
  248. package/src/lib/model/download-text-file.js +34 -0
  249. package/src/lib/model/download-text-file.js.map +1 -0
  250. package/src/lib/model/lead-filters.d.ts +36 -2
  251. package/src/lib/model/lead-filters.js +123 -2
  252. package/src/lib/model/lead-filters.js.map +1 -1
  253. package/src/lib/model/lead-source-direction.d.ts +38 -0
  254. package/src/lib/model/lead-source-direction.js +52 -0
  255. package/src/lib/model/lead-source-direction.js.map +1 -0
  256. package/src/lib/model/next-activity.d.ts +1 -1
  257. package/src/lib/model/next-activity.js.map +1 -1
  258. package/src/lib/model/picklist-values.d.ts +119 -0
  259. package/src/lib/model/picklist-values.js +256 -0
  260. package/src/lib/model/picklist-values.js.map +1 -0
  261. package/src/lib/model/record-facts-disclosed.d.ts +81 -0
  262. package/src/lib/model/record-facts-disclosed.js +281 -0
  263. package/src/lib/model/record-facts-disclosed.js.map +1 -0
  264. package/src/lib/model/record-facts.d.ts +215 -34
  265. package/src/lib/model/record-facts.js +307 -128
  266. package/src/lib/model/record-facts.js.map +1 -1
  267. package/src/lib/model/task-picklists.d.ts +52 -0
  268. package/src/lib/model/task-picklists.js +83 -0
  269. package/src/lib/model/task-picklists.js.map +1 -0
  270. package/src/lib/model/task-routes.d.ts +13 -0
  271. package/src/lib/model/task-routes.js +23 -4
  272. package/src/lib/model/task-routes.js.map +1 -1
  273. package/src/lib/plan-entitlements.js +6 -4
  274. package/src/lib/plan-entitlements.js.map +1 -1
  275. package/src/lib/plugin.js +17 -0
  276. package/src/lib/plugin.js.map +1 -1
  277. package/src/lib/server/api-v1/contacts.js +220 -14
  278. package/src/lib/server/api-v1/contacts.js.map +1 -1
  279. package/src/lib/server/api-v1/crm-activities.js +14 -1
  280. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  281. package/src/lib/server/api-v1/crm-companies.js +123 -14
  282. package/src/lib/server/api-v1/crm-companies.js.map +1 -1
  283. package/src/lib/server/api-v1/crm-deals.js +291 -26
  284. package/src/lib/server/api-v1/crm-deals.js.map +1 -1
  285. package/src/lib/server/api-v1/crm-leads.js +144 -38
  286. package/src/lib/server/api-v1/crm-leads.js.map +1 -1
  287. package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
  288. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  289. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  290. package/src/lib/server/api-v1/crm-tasks.js +78 -34
  291. package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
  292. package/src/lib/server/api-v1/openapi.d.ts +0 -11
  293. package/src/lib/server/api-v1/openapi.js +171 -16
  294. package/src/lib/server/api-v1/openapi.js.map +1 -1
  295. package/src/lib/server/assign-contact-owner.d.ts +188 -0
  296. package/src/lib/server/assign-contact-owner.js +446 -0
  297. package/src/lib/server/assign-contact-owner.js.map +1 -0
  298. package/src/lib/server/associate-company-by-domain.d.ts +70 -0
  299. package/src/lib/server/associate-company-by-domain.js +153 -0
  300. package/src/lib/server/associate-company-by-domain.js.map +1 -0
  301. package/src/lib/server/automation-steps.d.ts +140 -0
  302. package/src/lib/server/automation-steps.js +669 -0
  303. package/src/lib/server/automation-steps.js.map +1 -0
  304. package/src/lib/server/capture-contact.d.ts +17 -0
  305. package/src/lib/server/capture-contact.js +95 -26
  306. package/src/lib/server/capture-contact.js.map +1 -1
  307. package/src/lib/server/capture-host-contact.d.ts +104 -0
  308. package/src/lib/server/capture-host-contact.js +151 -0
  309. package/src/lib/server/capture-host-contact.js.map +1 -0
  310. package/src/lib/server/company-children.d.ts +25 -0
  311. package/src/lib/server/company-children.js +42 -0
  312. package/src/lib/server/company-children.js.map +1 -0
  313. package/src/lib/server/company-delete.d.ts +18 -0
  314. package/src/lib/server/company-delete.js +44 -21
  315. package/src/lib/server/company-delete.js.map +1 -1
  316. package/src/lib/server/contact-email-history.js +21 -19
  317. package/src/lib/server/contact-email-history.js.map +1 -1
  318. package/src/lib/server/contact-remove.js +27 -2
  319. package/src/lib/server/contact-remove.js.map +1 -1
  320. package/src/lib/server/contact-reports-to.d.ts +61 -0
  321. package/src/lib/server/contact-reports-to.js +114 -0
  322. package/src/lib/server/contact-reports-to.js.map +1 -0
  323. package/src/lib/server/contact-update.d.ts +42 -1
  324. package/src/lib/server/contact-update.js +204 -23
  325. package/src/lib/server/contact-update.js.map +1 -1
  326. package/src/lib/server/convert-host-lead.d.ts +46 -1
  327. package/src/lib/server/convert-host-lead.js +173 -15
  328. package/src/lib/server/convert-host-lead.js.map +1 -1
  329. package/src/lib/server/convert-open-lead.js +12 -5
  330. package/src/lib/server/convert-open-lead.js.map +1 -1
  331. package/src/lib/server/crm-email-activity.d.ts +118 -0
  332. package/src/lib/server/crm-email-activity.js +148 -0
  333. package/src/lib/server/crm-email-activity.js.map +1 -0
  334. package/src/lib/server/crm-inbound-email.js +1 -1
  335. package/src/lib/server/crm-inbound-email.js.map +1 -1
  336. package/src/lib/server/crm-next-activity.d.ts +74 -0
  337. package/src/lib/server/crm-next-activity.js +194 -0
  338. package/src/lib/server/crm-next-activity.js.map +1 -0
  339. package/src/lib/server/crm-record-indexes.d.ts +45 -0
  340. package/src/lib/server/crm-record-indexes.js +78 -0
  341. package/src/lib/server/crm-record-indexes.js.map +1 -0
  342. package/src/lib/server/crm-sharing.d.ts +9 -1
  343. package/src/lib/server/crm-sharing.js +23 -3
  344. package/src/lib/server/crm-sharing.js.map +1 -1
  345. package/src/lib/server/deal-contact-roles.d.ts +27 -0
  346. package/src/lib/server/deal-contact-roles.js +89 -0
  347. package/src/lib/server/deal-contact-roles.js.map +1 -0
  348. package/src/lib/server/email-send.js +2 -1
  349. package/src/lib/server/email-send.js.map +1 -1
  350. package/src/lib/server/lead-convert.d.ts +5 -0
  351. package/src/lib/server/lead-convert.js +19 -3
  352. package/src/lib/server/lead-convert.js.map +1 -1
  353. package/src/lib/server/lead-create.d.ts +17 -2
  354. package/src/lib/server/lead-create.js +46 -14
  355. package/src/lib/server/lead-create.js.map +1 -1
  356. package/src/lib/server/lead-nurturing.d.ts +6 -0
  357. package/src/lib/server/lead-nurturing.js +57 -20
  358. package/src/lib/server/lead-nurturing.js.map +1 -1
  359. package/src/lib/server/lead-picklists.d.ts +35 -0
  360. package/src/lib/server/lead-picklists.js +52 -0
  361. package/src/lib/server/lead-picklists.js.map +1 -0
  362. package/src/lib/server/lead-source-picklist.d.ts +1 -13
  363. package/src/lib/server/lead-source-picklist.js +6 -33
  364. package/src/lib/server/lead-source-picklist.js.map +1 -1
  365. package/src/lib/server/merge-contacts.js +23 -5
  366. package/src/lib/server/merge-contacts.js.map +1 -1
  367. package/src/lib/server/next-activity-routes.js +2 -1
  368. package/src/lib/server/next-activity-routes.js.map +1 -1
  369. package/src/lib/server/person-eraser.d.ts +53 -0
  370. package/src/lib/server/person-eraser.js +149 -0
  371. package/src/lib/server/person-eraser.js.map +1 -0
  372. package/src/lib/server/person-records.d.ts +69 -0
  373. package/src/lib/server/person-records.js +317 -0
  374. package/src/lib/server/person-records.js.map +1 -0
  375. package/src/lib/server/person-refund.d.ts +97 -0
  376. package/src/lib/server/person-refund.js +221 -0
  377. package/src/lib/server/person-refund.js.map +1 -0
  378. package/src/lib/server/picklist-values.d.ts +21 -0
  379. package/src/lib/server/picklist-values.js +308 -0
  380. package/src/lib/server/picklist-values.js.map +1 -0
  381. package/src/lib/server/read-picklist.d.ts +44 -0
  382. package/src/lib/server/read-picklist.js +65 -0
  383. package/src/lib/server/read-picklist.js.map +1 -0
  384. package/src/lib/server/recipe-routes.js +22 -8
  385. package/src/lib/server/recipe-routes.js.map +1 -1
  386. package/src/lib/server/record-email-state.js +7 -1
  387. package/src/lib/server/record-email-state.js.map +1 -1
  388. package/src/lib/server/record-facts.d.ts +3 -0
  389. package/src/lib/server/record-facts.js +233 -31
  390. package/src/lib/server/record-facts.js.map +1 -1
  391. package/src/lib/server/record-origin.d.ts +27 -0
  392. package/src/lib/server/record-origin.js +104 -0
  393. package/src/lib/server/record-origin.js.map +1 -0
  394. package/src/lib/server/record-timeline.js +40 -3
  395. package/src/lib/server/record-timeline.js.map +1 -1
  396. package/src/lib/server/suite-gate.js +4 -3
  397. package/src/lib/server/suite-gate.js.map +1 -1
  398. package/src/lib/server/task-reminders-route.js +2 -1
  399. package/src/lib/server/task-reminders-route.js.map +1 -1
  400. package/src/lib/server/task-routes.js +33 -17
  401. package/src/lib/server/task-routes.js.map +1 -1
  402. package/src/lib/server-deal-stage.d.ts +5 -1
  403. package/src/lib/server-deal-stage.js +10 -5
  404. package/src/lib/server-deal-stage.js.map +1 -1
  405. package/src/lib/server.d.ts +8 -3
  406. package/src/lib/server.js +47 -43
  407. package/src/lib/server.js.map +1 -1
  408. package/src/lib/testing/memory-firestore.d.ts +55 -0
  409. package/src/lib/testing/memory-firestore.js +335 -0
  410. package/src/lib/testing/memory-firestore.js.map +1 -0
  411. package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
  412. package/src/lib/testing/stand-in-email-send-index.js +49 -0
  413. package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
  414. package/src/lib/transfer/activities.d.ts +30 -0
  415. package/src/lib/transfer/activities.js +132 -0
  416. package/src/lib/transfer/activities.js.map +1 -0
  417. package/src/lib/transfer/aliases.d.ts +22 -0
  418. package/src/lib/transfer/aliases.js +1342 -0
  419. package/src/lib/transfer/aliases.js.map +1 -0
  420. package/src/lib/transfer/common.d.ts +222 -0
  421. package/src/lib/transfer/common.js +832 -0
  422. package/src/lib/transfer/common.js.map +1 -0
  423. package/src/lib/transfer/companies.d.ts +43 -0
  424. package/src/lib/transfer/companies.js +549 -0
  425. package/src/lib/transfer/companies.js.map +1 -0
  426. package/src/lib/transfer/contacts.d.ts +31 -0
  427. package/src/lib/transfer/contacts.js +727 -0
  428. package/src/lib/transfer/contacts.js.map +1 -0
  429. package/src/lib/transfer/deals.d.ts +66 -0
  430. package/src/lib/transfer/deals.js +698 -0
  431. package/src/lib/transfer/deals.js.map +1 -0
  432. package/src/lib/transfer/email-templates-package.d.ts +40 -0
  433. package/src/lib/transfer/email-templates-package.js +76 -0
  434. package/src/lib/transfer/email-templates-package.js.map +1 -0
  435. package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
  436. package/src/lib/transfer/email-templates-package.server.js +176 -0
  437. package/src/lib/transfer/email-templates-package.server.js.map +1 -0
  438. package/src/lib/transfer/fields.d.ts +102 -0
  439. package/src/lib/transfer/fields.js +1755 -0
  440. package/src/lib/transfer/fields.js.map +1 -0
  441. package/src/lib/transfer/leads.d.ts +30 -0
  442. package/src/lib/transfer/leads.js +582 -0
  443. package/src/lib/transfer/leads.js.map +1 -0
  444. package/src/lib/transfer/pipelines.d.ts +21 -0
  445. package/src/lib/transfer/pipelines.js +172 -0
  446. package/src/lib/transfer/pipelines.js.map +1 -0
  447. package/src/lib/transfer/register.d.ts +47 -0
  448. package/src/lib/transfer/register.js +277 -0
  449. package/src/lib/transfer/register.js.map +1 -0
  450. package/src/lib/transfer/tasks.d.ts +30 -0
  451. package/src/lib/transfer/tasks.js +418 -0
  452. package/src/lib/transfer/tasks.js.map +1 -0
  453. package/src/lib/transfer/ui.d.ts +20 -0
  454. package/src/lib/transfer/ui.js +42 -0
  455. package/src/lib/transfer/ui.js.map +1 -0
  456. package/src/lib/components/company-import-drawer.d.ts +0 -42
  457. package/src/lib/components/company-import-drawer.js +0 -99
  458. package/src/lib/components/company-import-drawer.js.map +0 -1
  459. package/src/lib/components/contact-import-drawer.d.ts +0 -46
  460. package/src/lib/components/contact-import-drawer.js +0 -149
  461. package/src/lib/components/contact-import-drawer.js.map +0 -1
  462. package/src/lib/components/crm-export-all-button.d.ts +0 -39
  463. package/src/lib/components/crm-export-all-button.js +0 -98
  464. package/src/lib/components/crm-export-all-button.js.map +0 -1
  465. package/src/lib/components/csv-import-drawer.d.ts +0 -139
  466. package/src/lib/components/csv-import-drawer.js +0 -757
  467. package/src/lib/components/csv-import-drawer.js.map +0 -1
  468. package/src/lib/components/deal-import-drawer.d.ts +0 -24
  469. package/src/lib/components/deal-import-drawer.js +0 -83
  470. package/src/lib/components/deal-import-drawer.js.map +0 -1
  471. package/src/lib/components/lead-import-drawer.d.ts +0 -27
  472. package/src/lib/components/lead-import-drawer.js +0 -118
  473. package/src/lib/components/lead-import-drawer.js.map +0 -1
  474. package/src/lib/components/lead-source-values-card.d.ts +0 -27
  475. package/src/lib/components/lead-source-values-card.js.map +0 -1
  476. package/src/lib/components/task-import-drawer.d.ts +0 -24
  477. package/src/lib/components/task-import-drawer.js +0 -82
  478. package/src/lib/components/task-import-drawer.js.map +0 -1
  479. package/src/lib/model/companies-csv.d.ts +0 -36
  480. package/src/lib/model/companies-csv.js +0 -38
  481. package/src/lib/model/companies-csv.js.map +0 -1
  482. package/src/lib/model/contacts-csv.d.ts +0 -54
  483. package/src/lib/model/contacts-csv.js +0 -67
  484. package/src/lib/model/contacts-csv.js.map +0 -1
  485. package/src/lib/model/crm-company-import.d.ts +0 -154
  486. package/src/lib/model/crm-company-import.js +0 -331
  487. package/src/lib/model/crm-company-import.js.map +0 -1
  488. package/src/lib/model/crm-csv.d.ts +0 -239
  489. package/src/lib/model/crm-csv.js +0 -467
  490. package/src/lib/model/crm-csv.js.map +0 -1
  491. package/src/lib/model/crm-deal-import.d.ts +0 -112
  492. package/src/lib/model/crm-deal-import.js +0 -263
  493. package/src/lib/model/crm-deal-import.js.map +0 -1
  494. package/src/lib/model/crm-import.d.ts +0 -250
  495. package/src/lib/model/crm-import.js +0 -467
  496. package/src/lib/model/crm-import.js.map +0 -1
  497. package/src/lib/model/crm-lead-import.d.ts +0 -138
  498. package/src/lib/model/crm-lead-import.js +0 -446
  499. package/src/lib/model/crm-lead-import.js.map +0 -1
  500. package/src/lib/model/crm-task-import.d.ts +0 -94
  501. package/src/lib/model/crm-task-import.js +0 -275
  502. package/src/lib/model/crm-task-import.js.map +0 -1
  503. package/src/lib/model/deals-csv.d.ts +0 -32
  504. package/src/lib/model/deals-csv.js +0 -36
  505. package/src/lib/model/deals-csv.js.map +0 -1
  506. package/src/lib/model/import-column-shapes.d.ts +0 -36
  507. package/src/lib/model/import-column-shapes.js +0 -76
  508. package/src/lib/model/import-column-shapes.js.map +0 -1
  509. package/src/lib/model/lead-source-values.d.ts +0 -82
  510. package/src/lib/model/lead-source-values.js +0 -149
  511. package/src/lib/model/lead-source-values.js.map +0 -1
  512. package/src/lib/model/leads-csv.d.ts +0 -40
  513. package/src/lib/model/leads-csv.js +0 -40
  514. package/src/lib/model/leads-csv.js.map +0 -1
  515. package/src/lib/model/tasks-csv.d.ts +0 -28
  516. package/src/lib/model/tasks-csv.js +0 -28
  517. package/src/lib/model/tasks-csv.js.map +0 -1
  518. package/src/lib/server/companies-import.d.ts +0 -57
  519. package/src/lib/server/companies-import.js +0 -242
  520. package/src/lib/server/companies-import.js.map +0 -1
  521. package/src/lib/server/contacts-import.d.ts +0 -109
  522. package/src/lib/server/contacts-import.js +0 -393
  523. package/src/lib/server/contacts-import.js.map +0 -1
  524. package/src/lib/server/deals-import.d.ts +0 -76
  525. package/src/lib/server/deals-import.js +0 -248
  526. package/src/lib/server/deals-import.js.map +0 -1
  527. package/src/lib/server/export-route.d.ts +0 -60
  528. package/src/lib/server/export-route.js +0 -447
  529. package/src/lib/server/export-route.js.map +0 -1
  530. package/src/lib/server/import-context.d.ts +0 -87
  531. package/src/lib/server/import-context.js +0 -182
  532. package/src/lib/server/import-context.js.map +0 -1
  533. package/src/lib/server/lead-source-values.d.ts +0 -50
  534. package/src/lib/server/lead-source-values.js +0 -191
  535. package/src/lib/server/lead-source-values.js.map +0 -1
  536. package/src/lib/server/leads-import.d.ts +0 -84
  537. package/src/lib/server/leads-import.js +0 -367
  538. package/src/lib/server/leads-import.js.map +0 -1
  539. package/src/lib/server/tasks-import.d.ts +0 -54
  540. package/src/lib/server/tasks-import.js +0 -160
  541. package/src/lib/server/tasks-import.js.map +0 -1
@@ -1,447 +0,0 @@
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 { checkEntitlement, consentGroupForHost, CRM_COLLECTIONS, csvCell, hostScopeToken, isOrgWideMember, MAX_SCOPE_HOSTS, memberCanSee, memberScopeTokens, scopeTokensForHost, planLabelGrantingFeature, pluginRequestFromWeb } from "@aglyn/aglyn/server";
18
- import { emailUnverifiedResponse, firebaseAdmin, isImpersonationSession, isServerReleaseFlagOnForOrg, lockdownRefusal, memberHasOrgPermission, resolveOrgMembership } from "@aglyn/tenant-data-admin";
19
- import { FieldPath, FieldValue } from "firebase-admin/firestore";
20
- import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
21
- import { invalidIdTokenResponse } from "@aglyn/tenant-data-admin/server/id-token-refusal";
22
- import { contactCsvRowFromDoc, crmExportCells, crmExportHeader, isCrmExportResource } from "../model/crm-csv.js";
23
- import { contactPrimaryGroup } from "../model/contact-holder.js";
24
- /**
25
- * Documents read per round trip. Not a cap — the stream keeps paging until
26
- * the collection is exhausted. It bounds what is held at once, which is the
27
- * only thing a page size should ever bound.
28
- */ const PAGE_SIZE = 500;
29
- /** Sites read per page when the leads file spans an organization. */ const HOST_PAGE = 100;
30
- /**
31
- * The most sites one organization-level leads file reads from.
32
- *
33
- * A ceiling rather than an unbounded sweep, and it is REPORTED: the file's
34
- * row header counts only what was read, so a short file is provably short
35
- * rather than quietly partial.
36
- */ const HOST_CEILING = 200;
37
- /** The most linked records a tasks file resolves names for before writing ids. */ const NAME_CACHE_CEILING = 2000;
38
- /** Header naming the row count the server undertook to send. */ export const EXPORT_ROWS_HEADER = 'X-Aglyn-Export-Rows';
39
- const json = (body, status)=>Response.json(body, {
40
- status
41
- });
42
- /** Which collection under the org root each resource is, bar leads. */ const ORG_COLLECTION = {
43
- contacts: 'contacts',
44
- companies: CRM_COLLECTIONS.companies,
45
- deals: CRM_COLLECTIONS.deals,
46
- tasks: CRM_COLLECTIONS.tasks
47
- };
48
- /** The file's name, before the date stamp. */ const FILE_BASE = {
49
- contacts: 'contacts',
50
- companies: 'companies',
51
- deals: 'deals',
52
- tasks: 'tasks',
53
- leads: 'leads'
54
- };
55
- /**
56
- * THE WHOLE COLLECTION, STREAMED (AGL-2662).
57
- *
58
- * ## What this fixes
59
- *
60
- * Every CRM export until now wrote the rows the LIST had loaded — a
61
- * `limit(1000)` window for contacts, a `limit(200)` view for tasks — and
62
- * called the file `contacts.csv`. A merchant with 40,000 contacts got 1,000
63
- * of them and nothing on screen or in the file said so. `docs/bulk-actions.md`
64
- * said it in prose, which is not where somebody opening a spreadsheet looks.
65
- * This is the same file over every row there is, and it carries
66
- * `X-Aglyn-Export-Rows` so the client can prove what it received is whole —
67
- * a stream that dies halfway yields a perfectly well-formed shorter file, and
68
- * nothing about the bytes says they are short.
69
- *
70
- * ## Why it is a route and not a bigger client read
71
- *
72
- * A browser cannot hold an unbounded collection, and the tier that most
73
- * needs a complete export is the tier with the most rows. Cursor-paged and
74
- * streamed, completeness costs neither a hang nor a buffer.
75
- *
76
- * ## The columns are the client's columns
77
- *
78
- * `crmExportHeader` and `crmExportCells` (`model/crm-csv.ts`) are the
79
- * writers each section's Export button uses. One feature, one file format.
80
- *
81
- * ## The order is by document id, deliberately
82
- *
83
- * Firestore DROPS every document missing the ordered field, so ordering by
84
- * `updatedAt` or `dueAtMs` would silently omit rows written before that
85
- * field existed — the exact silent loss an export must not have. A document
86
- * id is on every document and is a total order.
87
- *
88
- * ## Scope is enforced here, because nothing else can
89
- *
90
- * The Admin SDK bypasses the rules, so `memberScopeTokens` IS the
91
- * enforcement: an org-wide member reads every row, and a scoped
92
- * collaborator's query carries their own tokens as the `array-contains-any`
93
- * the rules would have evaluated. Leads are host data by path, so the
94
- * equivalent check is whether the caller reaches the site.
95
- */ export async function crmExportRoute(request) {
96
- var _headers_authorization, _ref, _ref1, _ref2;
97
- const { method, query, headers: rawHeaders } = await pluginRequestFromWeb(request);
98
- const headers = rawHeaders;
99
- if (method !== 'GET') return json({
100
- error: 'Method not allowed'
101
- }, 405);
102
- const authorization = (_headers_authorization = headers.authorization) != null ? _headers_authorization : '';
103
- const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
104
- if (!idToken) return json({
105
- error: 'Unauthenticated'
106
- }, 401);
107
- const orgId = String((_ref = query == null ? void 0 : query['orgId']) != null ? _ref : '');
108
- const resource = String((_ref1 = query == null ? void 0 : query['resource']) != null ? _ref1 : '');
109
- const hostId = String((_ref2 = query == null ? void 0 : query['hostId']) != null ? _ref2 : '');
110
- if (!orgId || !isCrmExportResource(resource)) {
111
- return json({
112
- error: 'Missing orgId or resource'
113
- }, 400);
114
- }
115
- try {
116
- const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
117
- if (!decoded.email_verified && !isImpersonationSession(decoded)) {
118
- return emailUnverifiedResponse();
119
- }
120
- const staff = decoded['staff'] === true;
121
- const membership = await resolveOrgMembership(decoded.uid, orgId);
122
- const member = membership == null ? void 0 : membership.member;
123
- if (!member && !staff) return json({
124
- error: 'Not found'
125
- }, 404);
126
- /*
127
- * THE PEOPLE FILES ARE EVERY WORKSPACE'S (AGL-2839, AGL-2851).
128
- *
129
- * Exporting the contacts and leads a workspace holds is an obligation,
130
- * not a CRM feature — a Free workspace takes them from its Privacy
131
- * settings — so neither the release flag nor the plan is asked of those
132
- * two. The CRM's own records, companies, deals and tasks, are asked
133
- * both: the flag here, and the plan once the org has been read.
134
- */ const peopleFile = resource === 'contacts' || resource === 'leads';
135
- if (!peopleFile && !staff && !await isServerReleaseFlagOnForOrg('release_crm', orgId)) {
136
- return json({
137
- error: 'Not available'
138
- }, 404);
139
- }
140
- // The permission the CRM's own rules read for. Reading is not writing,
141
- // but these records ARE the audience: `data.manage` is what admits a
142
- // person to the list this file copies.
143
- if (!staff && !await memberHasOrgPermission(orgId, member, 'data.manage')) {
144
- return json({
145
- error: 'Not found'
146
- }, 404);
147
- }
148
- const firestore = firebaseAdmin.app().firestore();
149
- const orgRef = firestore.collection('orgs').doc(orgId);
150
- const orgSnapshot = await orgRef.get();
151
- if (!orgSnapshot.exists) return json({
152
- error: 'Unknown organization'
153
- }, 404);
154
- const org = orgSnapshot.data();
155
- const locked = await lockdownRefusal({
156
- request,
157
- staff,
158
- uid: decoded.uid,
159
- org
160
- });
161
- if (locked) return locked;
162
- // The CRM's own records on a plan without the CRM, staff included: the
163
- // refusal every `crm/*` route gives, after the caller is known.
164
- if (!peopleFile && !checkEntitlement(org, 'crm')) {
165
- const plan = planLabelGrantingFeature('crm');
166
- return json({
167
- error: `Exporting ${resource} is part of the CRM, which is not included in ` + 'your current plan. Manage your plan and add-ons from Billing.' + (plan ? ` Included from ${plan}.` : ''),
168
- reason: 'plan_required',
169
- code: 'crm'
170
- }, 403);
171
- }
172
- const orgWide = staff || isOrgWideMember(member);
173
- // `array-contains-any` takes at most thirty values, which is also the
174
- // widest a consent group can be — a reader past that is the org-wide
175
- // case and never reaches here.
176
- const tokens = memberScopeTokens(member).slice(0, MAX_SCOPE_HOSTS);
177
- if (!orgWide && !tokens.length) return json({
178
- error: 'Not found'
179
- }, 404);
180
- /* The roster, once, so every owner and assignee cell is an address. */ const ownerEmails = new Map();
181
- for (const entry of (await orgRef.collection('members').get()).docs){
182
- var _ref3;
183
- var _entry_data;
184
- const email = String((_ref3 = (_entry_data = entry.data()) == null ? void 0 : _entry_data['email']) != null ? _ref3 : '');
185
- if (email) ownerEmails.set(entry.id, email);
186
- }
187
- const ownerEmail = (uid)=>{
188
- var _ownerEmails_get;
189
- return (_ownerEmails_get = ownerEmails.get(uid)) != null ? _ownerEmails_get : uid;
190
- };
191
- const options = {
192
- ownerEmail,
193
- assigneeEmail: ownerEmail
194
- };
195
- if (resource === 'contacts') {
196
- /*
197
- * A contact's fields live in one holder's FACET, so the file has to
198
- * say which. Under a site that is the site's own group, for every
199
- * row; at the organization level it is each row's PRIMARY holder —
200
- * the same answer the org-level Contacts list flattens by, so the
201
- * file and the table read one profile per person. What a file must
202
- * never do is read the top of the document, which is where the
203
- * pre-facet migration left one holder's fields.
204
- */ if (!hostId && !orgWide) return json({
205
- error: 'Missing hostId'
206
- }, 400);
207
- const fields = await orgRef.collection(CRM_COLLECTIONS.contactFields).get();
208
- options.customFields = fields.docs.map((entry)=>{
209
- var _ref, _ref1;
210
- var _entry_data, _entry_data1, _entry_data2;
211
- return {
212
- key: String((_ref = (_entry_data = entry.data()) == null ? void 0 : _entry_data['key']) != null ? _ref : entry.id),
213
- label: String((_ref1 = (_entry_data1 = entry.data()) == null ? void 0 : _entry_data1['label']) != null ? _ref1 : entry.id),
214
- archivedAt: (_entry_data2 = entry.data()) == null ? void 0 : _entry_data2['archivedAt']
215
- };
216
- }).filter((field)=>!field.archivedAt).map(({ key, label })=>({
217
- key,
218
- label
219
- }));
220
- }
221
- if (resource === 'deals') {
222
- const pipelines = await orgRef.collection(CRM_COLLECTIONS.pipelines).get();
223
- const names = new Map();
224
- const stages = new Map();
225
- for (const entry of pipelines.docs){
226
- var _entry_data1, _data_name, _data_stages;
227
- const data = (_entry_data1 = entry.data()) != null ? _entry_data1 : {};
228
- names.set(entry.id, String((_data_name = data['name']) != null ? _data_name : entry.id));
229
- for (const stage of (_data_stages = data['stages']) != null ? _data_stages : []){
230
- var _ref4;
231
- const stageId = String((_ref4 = stage == null ? void 0 : stage['id']) != null ? _ref4 : '');
232
- if (stageId) {
233
- var _ref5;
234
- stages.set(`${entry.id}/${stageId}`, String((_ref5 = stage == null ? void 0 : stage['name']) != null ? _ref5 : stageId));
235
- }
236
- }
237
- }
238
- options.pipelineName = (id)=>names.get(id);
239
- options.stageName = (pipelineId, stageId)=>stages.get(`${pipelineId}/${stageId}`);
240
- }
241
- /*
242
- * A task names its contact, company and deal by ID, and a spreadsheet
243
- * cannot read one. The list resolves them from rows it already holds;
244
- * the server looks them up as it meets them — batched per page,
245
- * remembered, and capped, so a tasks file costs reads in proportion to
246
- * the records it actually references rather than to the org. Past the
247
- * cap the id is written, which is the documented degradation everywhere
248
- * else in these files.
249
- */ const recordNames = new Map();
250
- const resolveNames = async (wanted)=>{
251
- const missing = wanted.filter((entry)=>entry.id && !recordNames.has(`${entry.kind}:${entry.id}`) && recordNames.size < NAME_CACHE_CEILING);
252
- if (!missing.length) return;
253
- const refs = missing.map((entry)=>orgRef.collection(entry.kind === 'contact' ? 'contacts' : entry.kind === 'company' ? CRM_COLLECTIONS.companies : CRM_COLLECTIONS.deals).doc(entry.id));
254
- const snapshots = await firestore.getAll(...refs);
255
- snapshots.forEach((snapshot, index)=>{
256
- var _snapshot_data, _data_title, _ref, _data_name;
257
- const entry = missing[index];
258
- const data = (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {};
259
- const name = entry.kind === 'deal' ? String((_data_title = data['title']) != null ? _data_title : '') : String((_ref = (_data_name = data['name']) != null ? _data_name : data['email']) != null ? _ref : '');
260
- recordNames.set(`${entry.kind}:${entry.id}`, name || entry.id);
261
- });
262
- };
263
- if (resource === 'tasks') {
264
- options.recordName = (kind, id)=>recordNames.get(`${kind}:${id}`);
265
- }
266
- /*
267
- * LEADS ARE HOST DATA BY PATH. Under one site the file is that site's;
268
- * with no site named, an org-wide member takes every site's leads in one
269
- * file with a `Site` column — the same file the organization-level Leads
270
- * list writes. A scoped collaborator must name a site they reach.
271
- */ const siteNames = new Map();
272
- let leadHostIds = [];
273
- if (resource === 'leads') {
274
- if (hostId) {
275
- if (!orgWide && !memberCanSee(member, [
276
- hostScopeToken(hostId)
277
- ])) {
278
- return json({
279
- error: 'Not found'
280
- }, 404);
281
- }
282
- leadHostIds = [
283
- hostId
284
- ];
285
- } else {
286
- if (!orgWide) return json({
287
- error: 'Missing hostId'
288
- }, 400);
289
- let cursor = null;
290
- for(;;){
291
- let page = firestore.collection('hosts').where('orgId', '==', orgId).orderBy(FieldPath.documentId()).limit(HOST_PAGE);
292
- if (cursor) page = page.startAfter(firestore.collection('hosts').doc(cursor));
293
- const snapshot = await page.get();
294
- for (const entry of snapshot.docs){
295
- var _ref6, _ref7;
296
- var _entry_data2, _entry_data3;
297
- leadHostIds.push(entry.id);
298
- siteNames.set(entry.id, String((_ref6 = (_ref7 = (_entry_data2 = entry.data()) == null ? void 0 : _entry_data2['displayName']) != null ? _ref7 : (_entry_data3 = entry.data()) == null ? void 0 : _entry_data3['subdomain']) != null ? _ref6 : entry.id));
299
- }
300
- if (snapshot.docs.length < HOST_PAGE) break;
301
- if (leadHostIds.length >= HOST_CEILING) break;
302
- cursor = snapshot.docs[snapshot.docs.length - 1].id;
303
- }
304
- options.siteName = (id)=>siteNames.get(id);
305
- }
306
- }
307
- /** One resource's rows, as a query per collection the file spans. */ const sources = resource === 'leads' ? leadHostIds.map((id)=>({
308
- hostId: id,
309
- /*
310
- * The org collection, narrowed per SITE (AGL-3275).
311
- *
312
- * One source per site is still right — the file names the site a
313
- * row came from — but a site's leads are selected by `visibleTo`
314
- * now rather than by the collection's parent, so the narrowing
315
- * moves onto the reference itself. It cannot use the `scoped`
316
- * flag below: that applies the MEMBER's tokens, and here each
317
- * source needs the tokens of its own site.
318
- */ reference: orgRef.collection('leads').where('visibleTo', 'array-contains-any', scopeTokensForHost(id)),
319
- scoped: false
320
- })) : [
321
- {
322
- hostId: '',
323
- reference: orgRef.collection(ORG_COLLECTION[resource]),
324
- scoped: !orgWide
325
- }
326
- ];
327
- // Taken BEFORE the first page, so it describes the collections the
328
- // export started from. A `count()` aggregate bills one read per 1,000
329
- // documents — cheap next to the export it is describing, and the only
330
- // way the client can tell a complete file from a truncated one.
331
- let total = 0;
332
- for (const source of sources){
333
- var _data_count;
334
- const counted = source.scoped ? source.reference.where('visibleTo', 'array-contains-any', tokens) : source.reference;
335
- total += Number((_data_count = (await counted.count().get()).data().count) != null ? _data_count : 0);
336
- }
337
- /** The holder one contact is written through, resolved per level. */ const holderFor = (document)=>{
338
- const group = hostId ? consentGroupForHost(org, hostId) : contactPrimaryGroup(document, org);
339
- return {
340
- groupId: group.groupId,
341
- hostIds: group.hostIds
342
- };
343
- };
344
- const encoder = new TextEncoder();
345
- const encode = (text)=>encoder.encode(text);
346
- const line = (cells)=>cells.map(csvCell).join(',');
347
- let sourceIndex = 0;
348
- let cursor = null;
349
- let phase = 'head';
350
- const stream = new ReadableStream({
351
- async pull (controller) {
352
- try {
353
- if (phase === 'head') {
354
- phase = sources.length ? 'body' : 'tail';
355
- controller.enqueue(encode(line(crmExportHeader(resource, options))));
356
- return;
357
- }
358
- if (phase === 'body') {
359
- var _snapshot_docs_;
360
- const source = sources[sourceIndex];
361
- let page = source.reference;
362
- if (source.scoped) {
363
- page = page.where('visibleTo', 'array-contains-any', tokens);
364
- }
365
- page = page.orderBy(FieldPath.documentId()).limit(PAGE_SIZE);
366
- if (cursor) page = page.startAfter(cursor);
367
- const snapshot = await page.get();
368
- const exhausted = snapshot.docs.length < PAGE_SIZE;
369
- cursor = exhausted ? null : (_snapshot_docs_ = snapshot.docs[snapshot.docs.length - 1]) != null ? _snapshot_docs_ : null;
370
- if (exhausted) {
371
- sourceIndex += 1;
372
- if (sourceIndex >= sources.length) phase = 'tail';
373
- }
374
- if (!snapshot.empty) {
375
- const documents = snapshot.docs.map((entry)=>{
376
- var _entry_data;
377
- return _extends({}, (_entry_data = entry.data()) != null ? _entry_data : {}, {
378
- $id: entry.id
379
- }, source.hostId ? {
380
- hostId: source.hostId
381
- } : {});
382
- });
383
- if (resource === 'tasks') {
384
- await resolveNames(documents.flatMap((document)=>[
385
- 'contact',
386
- 'company',
387
- 'deal'
388
- ].map((kind)=>{
389
- var _document_;
390
- return {
391
- kind,
392
- id: String((_document_ = document[kind === 'contact' ? 'contactId' : kind === 'company' ? 'companyId' : 'dealId']) != null ? _document_ : '')
393
- };
394
- })));
395
- }
396
- const lines = documents.map((document)=>line(crmExportCells(resource, resource === 'contacts' ? contactCsvRowFromDoc(document, holderFor(document)) : document, options)));
397
- controller.enqueue(encode(`\n${lines.join('\n')}`));
398
- return;
399
- }
400
- if (phase === 'body') return;
401
- }
402
- controller.enqueue(encode('\n'));
403
- controller.close();
404
- } catch (error) {
405
- // Erroring the stream truncates the download rather than
406
- // completing it short — a half-written file the browser reports
407
- // as a failed transfer, not a quiet partial handover.
408
- controller.error(error);
409
- }
410
- }
411
- });
412
- // Ids and counts only, never content: a full copy of an audience
413
- // leaving the platform is worth a row; who was in it is not ours to log.
414
- void addAdminAudit(firestore, {
415
- actorUid: decoded.uid,
416
- action: 'crm.exported',
417
- target: `orgs/${orgId}/${resource}`,
418
- before: null,
419
- after: {
420
- resource,
421
- rows: total,
422
- hostId: hostId || null
423
- },
424
- at: FieldValue.serverTimestamp()
425
- }).catch(()=>undefined);
426
- const stamp = new Date().toISOString().slice(0, 10);
427
- return new Response(stream, {
428
- status: 200,
429
- headers: {
430
- 'Content-Type': 'text/csv; charset=utf-8',
431
- 'Content-Disposition': `attachment; filename="${FILE_BASE[resource]}-${stamp}.csv"`,
432
- [EXPORT_ROWS_HEADER]: String(total),
433
- 'Cache-Control': 'no-store, private'
434
- }
435
- });
436
- } catch (error) {
437
- // A refused credential is a 401, not a fault of ours (AGL-1993). Null
438
- // for anything else, so a real failure keeps the answer below.
439
- const unauthenticated = invalidIdTokenResponse(error);
440
- if (unauthenticated) return unauthenticated;
441
- return json({
442
- error: 'Export failed'
443
- }, 500);
444
- }
445
- }
446
-
447
- //# sourceMappingURL=export-route.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/export-route.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 checkEntitlement,\n consentGroupForHost,\n CRM_COLLECTIONS,\n csvCell,\n hostScopeToken,\n isOrgWideMember,\n MAX_SCOPE_HOSTS,\n memberCanSee,\n memberScopeTokens,\n scopeTokensForHost,\n planLabelGrantingFeature,\n pluginRequestFromWeb,\n} from '@aglyn/aglyn/server'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n isServerReleaseFlagOnForOrg,\n lockdownRefusal,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { FieldPath, FieldValue } from 'firebase-admin/firestore'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport {\n contactCsvRowFromDoc,\n crmExportCells,\n crmExportHeader,\n type CrmExportOptions,\n type CrmExportResource,\n isCrmExportResource,\n} from '../model/crm-csv'\nimport { contactPrimaryGroup } from '../model/contact-holder'\n\n/**\n * Documents read per round trip. Not a cap — the stream keeps paging until\n * the collection is exhausted. It bounds what is held at once, which is the\n * only thing a page size should ever bound.\n */\nconst PAGE_SIZE = 500\n\n/** Sites read per page when the leads file spans an organization. */\nconst HOST_PAGE = 100\n\n/**\n * The most sites one organization-level leads file reads from.\n *\n * A ceiling rather than an unbounded sweep, and it is REPORTED: the file's\n * row header counts only what was read, so a short file is provably short\n * rather than quietly partial.\n */\nconst HOST_CEILING = 200\n\n/** The most linked records a tasks file resolves names for before writing ids. */\nconst NAME_CACHE_CEILING = 2000\n\n/** Header naming the row count the server undertook to send. */\nexport const EXPORT_ROWS_HEADER = 'X-Aglyn-Export-Rows'\n\nconst json = (body: unknown, status: number) => Response.json(body, { status })\n\n/** Which collection under the org root each resource is, bar leads. */\nconst ORG_COLLECTION: Record<Exclude<CrmExportResource, 'leads'>, string> = {\n contacts: 'contacts',\n companies: CRM_COLLECTIONS.companies,\n deals: CRM_COLLECTIONS.deals,\n tasks: CRM_COLLECTIONS.tasks,\n}\n\n/** The file's name, before the date stamp. */\nconst FILE_BASE: Record<CrmExportResource, string> = {\n contacts: 'contacts',\n companies: 'companies',\n deals: 'deals',\n tasks: 'tasks',\n leads: 'leads',\n}\n\n/**\n * THE WHOLE COLLECTION, STREAMED (AGL-2662).\n *\n * ## What this fixes\n *\n * Every CRM export until now wrote the rows the LIST had loaded — a\n * `limit(1000)` window for contacts, a `limit(200)` view for tasks — and\n * called the file `contacts.csv`. A merchant with 40,000 contacts got 1,000\n * of them and nothing on screen or in the file said so. `docs/bulk-actions.md`\n * said it in prose, which is not where somebody opening a spreadsheet looks.\n * This is the same file over every row there is, and it carries\n * `X-Aglyn-Export-Rows` so the client can prove what it received is whole —\n * a stream that dies halfway yields a perfectly well-formed shorter file, and\n * nothing about the bytes says they are short.\n *\n * ## Why it is a route and not a bigger client read\n *\n * A browser cannot hold an unbounded collection, and the tier that most\n * needs a complete export is the tier with the most rows. Cursor-paged and\n * streamed, completeness costs neither a hang nor a buffer.\n *\n * ## The columns are the client's columns\n *\n * `crmExportHeader` and `crmExportCells` (`model/crm-csv.ts`) are the\n * writers each section's Export button uses. One feature, one file format.\n *\n * ## The order is by document id, deliberately\n *\n * Firestore DROPS every document missing the ordered field, so ordering by\n * `updatedAt` or `dueAtMs` would silently omit rows written before that\n * field existed — the exact silent loss an export must not have. A document\n * id is on every document and is a total order.\n *\n * ## Scope is enforced here, because nothing else can\n *\n * The Admin SDK bypasses the rules, so `memberScopeTokens` IS the\n * enforcement: an org-wide member reads every row, and a scoped\n * collaborator's query carries their own tokens as the `array-contains-any`\n * the rules would have evaluated. Leads are host data by path, so the\n * equivalent check is whether the caller reaches the site.\n */\nexport async function crmExportRoute(request: Request): Promise<Response> {\n const { method, query, headers: rawHeaders } =\n await pluginRequestFromWeb(request)\n const headers = rawHeaders as Partial<Record<string, string>>\n if (method !== 'GET') return json({ error: 'Method not allowed' }, 405)\n\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return json({ error: 'Unauthenticated' }, 401)\n\n const orgId = String(query?.['orgId'] ?? '')\n const resource = String(query?.['resource'] ?? '')\n const hostId = String(query?.['hostId'] ?? '')\n if (!orgId || !isCrmExportResource(resource)) {\n return json({ error: 'Missing orgId or resource' }, 400)\n }\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n const staff = decoded['staff'] === true\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const member = membership?.member\n if (!member && !staff) return json({ error: 'Not found' }, 404)\n\n /*\n * THE PEOPLE FILES ARE EVERY WORKSPACE'S (AGL-2839, AGL-2851).\n *\n * Exporting the contacts and leads a workspace holds is an obligation,\n * not a CRM feature — a Free workspace takes them from its Privacy\n * settings — so neither the release flag nor the plan is asked of those\n * two. The CRM's own records, companies, deals and tasks, are asked\n * both: the flag here, and the plan once the org has been read.\n */\n const peopleFile = resource === 'contacts' || resource === 'leads'\n if (!peopleFile && !staff && !(await isServerReleaseFlagOnForOrg('release_crm', orgId))) {\n return json({ error: 'Not available' }, 404)\n }\n // The permission the CRM's own rules read for. Reading is not writing,\n // but these records ARE the audience: `data.manage` is what admits a\n // person to the list this file copies.\n if (!staff && !(await memberHasOrgPermission(orgId, member, 'data.manage'))) {\n return json({ error: 'Not found' }, 404)\n }\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) return json({ error: 'Unknown organization' }, 404)\n const org = orgSnapshot.data()\n\n const locked = await lockdownRefusal({\n request,\n staff,\n uid: decoded.uid,\n org,\n })\n if (locked) return locked\n\n // The CRM's own records on a plan without the CRM, staff included: the\n // refusal every `crm/*` route gives, after the caller is known.\n if (!peopleFile && !checkEntitlement(org as never, 'crm')) {\n const plan = planLabelGrantingFeature('crm')\n return json(\n {\n error:\n `Exporting ${resource} is part of the CRM, which is not included in ` +\n 'your current plan. Manage your plan and add-ons from Billing.' +\n (plan ? ` Included from ${plan}.` : ''),\n reason: 'plan_required',\n code: 'crm',\n },\n 403,\n )\n }\n\n const orgWide = staff || isOrgWideMember(member)\n // `array-contains-any` takes at most thirty values, which is also the\n // widest a consent group can be — a reader past that is the org-wide\n // case and never reaches here.\n const tokens = memberScopeTokens(member).slice(0, MAX_SCOPE_HOSTS)\n if (!orgWide && !tokens.length) return json({ error: 'Not found' }, 404)\n\n /* The roster, once, so every owner and assignee cell is an address. */\n const ownerEmails = new Map<string, string>()\n for (const entry of (await orgRef.collection('members').get()).docs) {\n const email = String(entry.data()?.['email'] ?? '')\n if (email) ownerEmails.set(entry.id, email)\n }\n const ownerEmail = (uid: string) => ownerEmails.get(uid) ?? uid\n\n const options: CrmExportOptions = { ownerEmail, assigneeEmail: ownerEmail }\n\n if (resource === 'contacts') {\n /*\n * A contact's fields live in one holder's FACET, so the file has to\n * say which. Under a site that is the site's own group, for every\n * row; at the organization level it is each row's PRIMARY holder —\n * the same answer the org-level Contacts list flattens by, so the\n * file and the table read one profile per person. What a file must\n * never do is read the top of the document, which is where the\n * pre-facet migration left one holder's fields.\n */\n if (!hostId && !orgWide) return json({ error: 'Missing hostId' }, 400)\n const fields = await orgRef.collection(CRM_COLLECTIONS.contactFields).get()\n options.customFields = fields.docs\n .map((entry) => ({\n key: String(entry.data()?.['key'] ?? entry.id),\n label: String(entry.data()?.['label'] ?? entry.id),\n archivedAt: entry.data()?.['archivedAt'],\n }))\n .filter((field) => !field.archivedAt)\n .map(({ key, label }) => ({ key, label }))\n }\n\n if (resource === 'deals') {\n const pipelines = await orgRef.collection(CRM_COLLECTIONS.pipelines).get()\n const names = new Map<string, string>()\n const stages = new Map<string, string>()\n for (const entry of pipelines.docs) {\n const data = entry.data() ?? {}\n names.set(entry.id, String(data['name'] ?? entry.id))\n for (const stage of (data['stages'] ?? []) as Array<Record<string, unknown>>) {\n const stageId = String(stage?.['id'] ?? '')\n if (stageId) {\n stages.set(`${entry.id}/${stageId}`, String(stage?.['name'] ?? stageId))\n }\n }\n }\n options.pipelineName = (id) => names.get(id)\n options.stageName = (pipelineId, stageId) =>\n stages.get(`${pipelineId}/${stageId}`)\n }\n\n /*\n * A task names its contact, company and deal by ID, and a spreadsheet\n * cannot read one. The list resolves them from rows it already holds;\n * the server looks them up as it meets them — batched per page,\n * remembered, and capped, so a tasks file costs reads in proportion to\n * the records it actually references rather than to the org. Past the\n * cap the id is written, which is the documented degradation everywhere\n * else in these files.\n */\n const recordNames = new Map<string, string>()\n const resolveNames = async (\n wanted: ReadonlyArray<{ kind: 'contact' | 'company' | 'deal'; id: string }>,\n ) => {\n const missing = wanted.filter(\n (entry) =>\n entry.id &&\n !recordNames.has(`${entry.kind}:${entry.id}`) &&\n recordNames.size < NAME_CACHE_CEILING,\n )\n if (!missing.length) return\n const refs = missing.map((entry) =>\n orgRef\n .collection(\n entry.kind === 'contact'\n ? 'contacts'\n : entry.kind === 'company'\n ? CRM_COLLECTIONS.companies\n : CRM_COLLECTIONS.deals,\n )\n .doc(entry.id),\n )\n const snapshots = await firestore.getAll(...refs)\n snapshots.forEach((snapshot, index) => {\n const entry = missing[index]\n const data = snapshot.data() ?? {}\n const name =\n entry.kind === 'deal'\n ? String(data['title'] ?? '')\n : String(data['name'] ?? data['email'] ?? '')\n recordNames.set(`${entry.kind}:${entry.id}`, name || entry.id)\n })\n }\n if (resource === 'tasks') {\n options.recordName = (kind, id) => recordNames.get(`${kind}:${id}`)\n }\n\n /*\n * LEADS ARE HOST DATA BY PATH. Under one site the file is that site's;\n * with no site named, an org-wide member takes every site's leads in one\n * file with a `Site` column — the same file the organization-level Leads\n * list writes. A scoped collaborator must name a site they reach.\n */\n const siteNames = new Map<string, string>()\n let leadHostIds: string[] = []\n if (resource === 'leads') {\n if (hostId) {\n if (!orgWide && !memberCanSee(member, [hostScopeToken(hostId)])) {\n return json({ error: 'Not found' }, 404)\n }\n leadHostIds = [hostId]\n } else {\n if (!orgWide) return json({ error: 'Missing hostId' }, 400)\n let cursor: string | null = null\n for (;;) {\n let page = firestore\n .collection('hosts')\n .where('orgId', '==', orgId)\n .orderBy(FieldPath.documentId())\n .limit(HOST_PAGE)\n if (cursor) page = page.startAfter(firestore.collection('hosts').doc(cursor))\n const snapshot = await page.get()\n for (const entry of snapshot.docs) {\n leadHostIds.push(entry.id)\n siteNames.set(\n entry.id,\n String(\n entry.data()?.['displayName'] ??\n entry.data()?.['subdomain'] ??\n entry.id,\n ),\n )\n }\n if (snapshot.docs.length < HOST_PAGE) break\n if (leadHostIds.length >= HOST_CEILING) break\n cursor = snapshot.docs[snapshot.docs.length - 1].id\n }\n options.siteName = (id) => siteNames.get(id)\n }\n }\n\n /** One resource's rows, as a query per collection the file spans. */\n const sources =\n resource === 'leads'\n ? leadHostIds.map((id) => ({\n hostId: id,\n /*\n * The org collection, narrowed per SITE (AGL-3275).\n *\n * One source per site is still right — the file names the site a\n * row came from — but a site's leads are selected by `visibleTo`\n * now rather than by the collection's parent, so the narrowing\n * moves onto the reference itself. It cannot use the `scoped`\n * flag below: that applies the MEMBER's tokens, and here each\n * source needs the tokens of its own site.\n */\n reference: orgRef\n .collection('leads')\n .where('visibleTo', 'array-contains-any', scopeTokensForHost(id)),\n scoped: false,\n }))\n : [\n {\n hostId: '',\n reference: orgRef.collection(\n ORG_COLLECTION[resource as Exclude<CrmExportResource, 'leads'>],\n ),\n scoped: !orgWide,\n },\n ]\n\n // Taken BEFORE the first page, so it describes the collections the\n // export started from. A `count()` aggregate bills one read per 1,000\n // documents — cheap next to the export it is describing, and the only\n // way the client can tell a complete file from a truncated one.\n let total = 0\n for (const source of sources) {\n const counted = source.scoped\n ? source.reference.where('visibleTo', 'array-contains-any', tokens)\n : source.reference\n total += Number((await counted.count().get()).data().count ?? 0)\n }\n\n /** The holder one contact is written through, resolved per level. */\n const holderFor = (document: Record<string, unknown>) => {\n const group = hostId\n ? consentGroupForHost(org as Record<string, unknown>, hostId)\n : contactPrimaryGroup(document, org as Record<string, unknown>)\n return { groupId: group.groupId, hostIds: group.hostIds }\n }\n\n const encoder = new TextEncoder()\n const encode = (text: string) => encoder.encode(text)\n const line = (cells: readonly unknown[]) => cells.map(csvCell).join(',')\n let sourceIndex = 0\n let cursor: FirebaseFirestore.QueryDocumentSnapshot | null = null\n let phase: 'head' | 'body' | 'tail' = 'head'\n\n const stream = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n if (phase === 'head') {\n phase = sources.length ? 'body' : 'tail'\n controller.enqueue(encode(line(crmExportHeader(resource, options))))\n return\n }\n if (phase === 'body') {\n const source = sources[sourceIndex]\n let page: FirebaseFirestore.Query = source.reference\n if (source.scoped) {\n page = page.where('visibleTo', 'array-contains-any', tokens)\n }\n page = page.orderBy(FieldPath.documentId()).limit(PAGE_SIZE)\n if (cursor) page = page.startAfter(cursor)\n const snapshot = await page.get()\n const exhausted = snapshot.docs.length < PAGE_SIZE\n cursor = exhausted\n ? null\n : (snapshot.docs[snapshot.docs.length - 1] ?? null)\n if (exhausted) {\n sourceIndex += 1\n if (sourceIndex >= sources.length) phase = 'tail'\n }\n if (!snapshot.empty) {\n const documents = snapshot.docs.map((entry) => ({\n ...(entry.data() ?? {}),\n $id: entry.id,\n ...(source.hostId ? { hostId: source.hostId } : {}),\n }))\n if (resource === 'tasks') {\n await resolveNames(\n documents.flatMap((document) =>\n (['contact', 'company', 'deal'] as const).map((kind) => ({\n kind,\n id: String(\n document[\n kind === 'contact'\n ? 'contactId'\n : kind === 'company'\n ? 'companyId'\n : 'dealId'\n ] ?? '',\n ),\n })),\n ),\n )\n }\n const lines = documents.map((document) =>\n line(\n crmExportCells(\n resource,\n resource === 'contacts'\n ? (contactCsvRowFromDoc(\n document,\n holderFor(document),\n ) as Record<string, unknown>)\n : document,\n options,\n ),\n ),\n )\n controller.enqueue(encode(`\\n${lines.join('\\n')}`))\n return\n }\n if (phase === 'body') return\n }\n controller.enqueue(encode('\\n'))\n controller.close()\n } catch (error) {\n // Erroring the stream truncates the download rather than\n // completing it short — a half-written file the browser reports\n // as a failed transfer, not a quiet partial handover.\n controller.error(error)\n }\n },\n })\n\n // Ids and counts only, never content: a full copy of an audience\n // leaving the platform is worth a row; who was in it is not ours to log.\n void addAdminAudit(firestore, {\n actorUid: decoded.uid,\n action: 'crm.exported',\n target: `orgs/${orgId}/${resource}`,\n before: null,\n after: { resource, rows: total, hostId: hostId || null },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n\n const stamp = new Date().toISOString().slice(0, 10)\n return new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/csv; charset=utf-8',\n 'Content-Disposition': `attachment; filename=\"${FILE_BASE[resource]}-${stamp}.csv\"`,\n [EXPORT_ROWS_HEADER]: String(total),\n 'Cache-Control': 'no-store, private',\n },\n })\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993). Null\n // for anything else, so a real failure keeps the answer below.\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n return json({ error: 'Export failed' }, 500)\n }\n}\n\n"],"names":["checkEntitlement","consentGroupForHost","CRM_COLLECTIONS","csvCell","hostScopeToken","isOrgWideMember","MAX_SCOPE_HOSTS","memberCanSee","memberScopeTokens","scopeTokensForHost","planLabelGrantingFeature","pluginRequestFromWeb","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","isServerReleaseFlagOnForOrg","lockdownRefusal","memberHasOrgPermission","resolveOrgMembership","FieldPath","FieldValue","addAdminAudit","invalidIdTokenResponse","contactCsvRowFromDoc","crmExportCells","crmExportHeader","isCrmExportResource","contactPrimaryGroup","PAGE_SIZE","HOST_PAGE","HOST_CEILING","NAME_CACHE_CEILING","EXPORT_ROWS_HEADER","json","body","status","Response","ORG_COLLECTION","contacts","companies","deals","tasks","FILE_BASE","leads","crmExportRoute","request","headers","method","query","rawHeaders","error","authorization","idToken","startsWith","slice","length","undefined","orgId","String","resource","hostId","decoded","app","auth","verifyIdToken","email_verified","staff","membership","uid","member","peopleFile","firestore","orgRef","collection","doc","orgSnapshot","get","exists","org","data","locked","plan","reason","code","orgWide","tokens","ownerEmails","Map","entry","docs","email","set","id","ownerEmail","options","assigneeEmail","fields","contactFields","customFields","map","key","label","archivedAt","filter","field","pipelines","names","stages","stage","stageId","pipelineName","stageName","pipelineId","recordNames","resolveNames","wanted","missing","has","kind","size","refs","snapshots","getAll","forEach","snapshot","index","name","recordName","siteNames","leadHostIds","cursor","page","where","orderBy","documentId","limit","startAfter","push","siteName","sources","reference","scoped","total","source","counted","Number","count","holderFor","document","group","groupId","hostIds","encoder","TextEncoder","encode","text","line","cells","join","sourceIndex","phase","stream","ReadableStream","pull","controller","enqueue","exhausted","empty","documents","$id","flatMap","lines","close","actorUid","action","target","before","after","rows","at","serverTimestamp","catch","stamp","Date","toISOString","unauthenticated"],"mappings":"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"}
@@ -1,87 +0,0 @@
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
- /** The importer's address, for who set off the runs it causes (AGL-3376). */
45
- email: string | null;
46
- hostId: string;
47
- orgId: string;
48
- org: Record<string, unknown>;
49
- /**
50
- * The stamp a record CREATED by this import carries — the org, when
51
- * the org widened its default, and otherwise the capturing group.
52
- */
53
- scopeTokens: string[];
54
- /**
55
- * The tokens a record must overlap to be SEEN from this site: the
56
- * group's own, plus `org`, because an org-wide record is visible to
57
- * every site in the account. The reader's set is wider than the
58
- * creator's stamp, exactly as it is on the collection listeners.
59
- */
60
- readTokens: string[];
61
- } | {
62
- ok: false;
63
- status: number;
64
- body: Record<string, unknown>;
65
- };
66
- export type ImportRequest = Parameters<PluginApiHandler>[0];
67
- /** Who is asking, and on whose behalf. */
68
- export declare function resolveImportContext(req: ImportRequest): Promise<ImportContext>;
69
- /** The rows a request may carry, or the refusal to send back. */
70
- export declare function readImportRows<R>(req: ImportRequest, limits: {
71
- maxBodyBytes: number;
72
- chunkSize: number;
73
- }): {
74
- rows: R[];
75
- } | {
76
- status: number;
77
- error: string;
78
- };
79
- /**
80
- * The org's members by address, read only when a row names an owner.
81
- *
82
- * A file with no owner column costs no roster read; a file with one costs
83
- * exactly one, however many rows it has.
84
- */
85
- export declare function ownerDirectory(orgId: string, rows: readonly {
86
- ownerEmail?: string;
87
- }[]): Promise<Map<string, string>>;