@aglyn/plugins-crm 1.0.0-beta.217 → 1.0.0-beta.219

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 (249) hide show
  1. package/package.json +14 -13
  2. package/src/index.d.ts +1 -0
  3. package/src/index.js.map +1 -1
  4. package/src/lib/components/companies-bulk-bar.js +12 -4
  5. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  6. package/src/lib/components/company-detail-page.js +11 -0
  7. package/src/lib/components/company-detail-page.js.map +1 -1
  8. package/src/lib/components/company-edit-drawer.js +12 -2
  9. package/src/lib/components/company-edit-drawer.js.map +1 -1
  10. package/src/lib/components/company-import-drawer.d.ts +1 -1
  11. package/src/lib/components/company-picker.js +10 -3
  12. package/src/lib/components/company-picker.js.map +1 -1
  13. package/src/lib/components/contact-detail-page.d.ts +4 -4
  14. package/src/lib/components/contact-detail-page.js +50 -53
  15. package/src/lib/components/contact-detail-page.js.map +1 -1
  16. package/src/lib/components/contact-list-columns.d.ts +3 -0
  17. package/src/lib/components/contact-list-columns.js +21 -5
  18. package/src/lib/components/contact-list-columns.js.map +1 -1
  19. package/src/lib/components/contact-merge-dialog.js +2 -1
  20. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  21. package/src/lib/components/contact-timeline-card.js +4 -3
  22. package/src/lib/components/contact-timeline-card.js.map +1 -1
  23. package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
  24. package/src/lib/components/contacts-bulk-bar.js +24 -51
  25. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  26. package/src/lib/components/contacts-section.js +9 -3
  27. package/src/lib/components/contacts-section.js.map +1 -1
  28. package/src/lib/components/crm-export-all-button.d.ts +1 -1
  29. package/src/lib/components/crm-export-all-button.js.map +1 -1
  30. package/src/lib/components/crm-send-email-dialog.js +9 -6
  31. package/src/lib/components/crm-send-email-dialog.js.map +1 -1
  32. package/src/lib/components/csv-import-drawer.js +2 -2
  33. package/src/lib/components/csv-import-drawer.js.map +1 -1
  34. package/src/lib/components/deal-detail-page.js +18 -2
  35. package/src/lib/components/deal-detail-page.js.map +1 -1
  36. package/src/lib/components/deal-edit-drawer.js +10 -0
  37. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  38. package/src/lib/components/deals-bulk-bar.js +13 -5
  39. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  40. package/src/lib/components/lead-campaigns-card.js +6 -0
  41. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  42. package/src/lib/components/lead-detail-page.js +25 -5
  43. package/src/lib/components/lead-detail-page.js.map +1 -1
  44. package/src/lib/components/lead-properties-card.js +6 -0
  45. package/src/lib/components/lead-properties-card.js.map +1 -1
  46. package/src/lib/components/lead-surfaces-note.js +4 -5
  47. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  48. package/src/lib/components/lead-unqualify-dialog.js +6 -0
  49. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  50. package/src/lib/components/leads-bulk-bar.js +28 -3
  51. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  52. package/src/lib/components/leads-section.js +36 -7
  53. package/src/lib/components/leads-section.js.map +1 -1
  54. package/src/lib/components/org-lead-surfaces-note.js +6 -2
  55. package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
  56. package/src/lib/components/recipes-card.js +9 -4
  57. package/src/lib/components/recipes-card.js.map +1 -1
  58. package/src/lib/components/record-sharing-card.d.ts +57 -0
  59. package/src/lib/components/record-sharing-card.js +244 -0
  60. package/src/lib/components/record-sharing-card.js.map +1 -0
  61. package/src/lib/components/settings-section.js +7 -0
  62. package/src/lib/components/settings-section.js.map +1 -1
  63. package/src/lib/components/share-records-dialog.d.ts +28 -0
  64. package/src/lib/components/share-records-dialog.js +184 -0
  65. package/src/lib/components/share-records-dialog.js.map +1 -0
  66. package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
  67. package/src/lib/components/sharing-rule-drawer.js +374 -0
  68. package/src/lib/components/sharing-rule-drawer.js.map +1 -0
  69. package/src/lib/components/sharing-rules-card.d.ts +24 -0
  70. package/src/lib/components/sharing-rules-card.js +287 -0
  71. package/src/lib/components/sharing-rules-card.js.map +1 -0
  72. package/src/lib/components/use-crm-api.d.ts +1 -1
  73. package/src/lib/components/use-crm-api.js.map +1 -1
  74. package/src/lib/components/use-site-route-context.d.ts +25 -0
  75. package/src/lib/components/use-site-route-context.js +58 -0
  76. package/src/lib/components/use-site-route-context.js.map +1 -0
  77. package/src/lib/constants/api-routes.d.ts +31 -0
  78. package/src/lib/constants/api-routes.js +27 -1
  79. package/src/lib/constants/api-routes.js.map +1 -1
  80. package/src/lib/declarations.console-server.d.ts +37 -0
  81. package/src/lib/declarations.console-server.js +103 -0
  82. package/src/lib/declarations.console-server.js.map +1 -0
  83. package/src/lib/declarations.server.d.ts +6 -0
  84. package/src/lib/declarations.server.js +33 -0
  85. package/src/lib/declarations.server.js.map +1 -1
  86. package/src/lib/hooks/use-contact-remove.d.ts +35 -0
  87. package/src/lib/hooks/use-contact-remove.js +77 -0
  88. package/src/lib/hooks/use-contact-remove.js.map +1 -0
  89. package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
  90. package/src/lib/hooks/use-crm-sharing.js +109 -0
  91. package/src/lib/hooks/use-crm-sharing.js.map +1 -0
  92. package/src/lib/model/companies-csv.d.ts +1 -1
  93. package/src/lib/model/companies-csv.js +2 -2
  94. package/src/lib/model/companies-csv.js.map +1 -1
  95. package/src/lib/model/contact-holder.d.ts +68 -0
  96. package/src/lib/model/contact-holder.js +129 -0
  97. package/src/lib/model/contact-holder.js.map +1 -0
  98. package/src/lib/model/contact-interaction-links.d.ts +46 -0
  99. package/src/lib/model/contact-interaction-links.js +65 -0
  100. package/src/lib/model/contact-interaction-links.js.map +1 -0
  101. package/src/lib/model/contact-merge.d.ts +136 -0
  102. package/src/lib/model/contact-merge.js +412 -0
  103. package/src/lib/model/contact-merge.js.map +1 -0
  104. package/src/lib/model/contact-record.d.ts +7 -1
  105. package/src/lib/model/contact-record.js +6 -5
  106. package/src/lib/model/contact-record.js.map +1 -1
  107. package/src/lib/model/contact-remove.d.ts +48 -0
  108. package/src/lib/model/contact-remove.js +22 -0
  109. package/src/lib/model/contact-remove.js.map +1 -0
  110. package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
  111. package/src/lib/model/contacts-bulk-writes.js +2 -82
  112. package/src/lib/model/contacts-bulk-writes.js.map +1 -1
  113. package/src/lib/model/contacts-csv.d.ts +2 -2
  114. package/src/lib/model/contacts-csv.js +2 -2
  115. package/src/lib/model/contacts-csv.js.map +1 -1
  116. package/src/lib/model/crm-csv.d.ts +239 -0
  117. package/src/lib/model/crm-csv.js +467 -0
  118. package/src/lib/model/crm-csv.js.map +1 -0
  119. package/src/lib/model/crm-digest.d.ts +185 -0
  120. package/src/lib/model/crm-digest.js +272 -0
  121. package/src/lib/model/crm-digest.js.map +1 -0
  122. package/src/lib/model/crm-import.d.ts +7 -1
  123. package/src/lib/model/crm-import.js +2 -1
  124. package/src/lib/model/crm-import.js.map +1 -1
  125. package/src/lib/model/crm-record-routes.d.ts +5 -0
  126. package/src/lib/model/crm-record-routes.js +7 -0
  127. package/src/lib/model/crm-record-routes.js.map +1 -1
  128. package/src/lib/model/crm-routes.d.ts +6 -5
  129. package/src/lib/model/crm-routes.js +7 -7
  130. package/src/lib/model/crm-routes.js.map +1 -1
  131. package/src/lib/model/crm-search-sources.d.ts +15 -0
  132. package/src/lib/model/crm-search-sources.js +162 -0
  133. package/src/lib/model/crm-search-sources.js.map +1 -0
  134. package/src/lib/model/crm-sharing-api.d.ts +32 -0
  135. package/src/lib/model/crm-sharing-api.js +59 -0
  136. package/src/lib/model/crm-sharing-api.js.map +1 -0
  137. package/src/lib/model/crm-sharing.d.ts +333 -0
  138. package/src/lib/model/crm-sharing.js +603 -0
  139. package/src/lib/model/crm-sharing.js.map +1 -0
  140. package/src/lib/model/deals-csv.d.ts +1 -1
  141. package/src/lib/model/deals-csv.js +4 -5
  142. package/src/lib/model/deals-csv.js.map +1 -1
  143. package/src/lib/model/lead-filters.d.ts +1 -1
  144. package/src/lib/model/leads-csv.d.ts +1 -1
  145. package/src/lib/model/leads-csv.js +1 -1
  146. package/src/lib/model/leads-csv.js.map +1 -1
  147. package/src/lib/model/tasks-csv.d.ts +1 -1
  148. package/src/lib/model/tasks-csv.js +1 -1
  149. package/src/lib/model/tasks-csv.js.map +1 -1
  150. package/src/lib/plan-entitlements.d.ts +75 -0
  151. package/src/lib/plan-entitlements.js +56 -0
  152. package/src/lib/plan-entitlements.js.map +1 -0
  153. package/src/lib/plugin.js +3 -0
  154. package/src/lib/plugin.js.map +1 -1
  155. package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
  156. package/src/lib/server/api-v1/contacts-merge.js +120 -0
  157. package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
  158. package/src/lib/server/api-v1/contacts.d.ts +18 -0
  159. package/src/lib/server/api-v1/contacts.js +1076 -0
  160. package/src/lib/server/api-v1/contacts.js.map +1 -0
  161. package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
  162. package/src/lib/server/api-v1/crm-activities.js +269 -0
  163. package/src/lib/server/api-v1/crm-activities.js.map +1 -0
  164. package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
  165. package/src/lib/server/api-v1/crm-companies.js +391 -0
  166. package/src/lib/server/api-v1/crm-companies.js.map +1 -0
  167. package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
  168. package/src/lib/server/api-v1/crm-deals.js +631 -0
  169. package/src/lib/server/api-v1/crm-deals.js.map +1 -0
  170. package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
  171. package/src/lib/server/api-v1/crm-email-templates.js +336 -0
  172. package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
  173. package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
  174. package/src/lib/server/api-v1/crm-leads.js +832 -0
  175. package/src/lib/server/api-v1/crm-leads.js.map +1 -0
  176. package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
  177. package/src/lib/server/api-v1/crm-pipelines.js +146 -0
  178. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
  179. package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
  180. package/src/lib/server/api-v1/crm-shared.js +436 -0
  181. package/src/lib/server/api-v1/crm-shared.js.map +1 -0
  182. package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
  183. package/src/lib/server/api-v1/crm-tasks.js +398 -0
  184. package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
  185. package/src/lib/server/api-v1/openapi.d.ts +29 -0
  186. package/src/lib/server/api-v1/openapi.js +985 -0
  187. package/src/lib/server/api-v1/openapi.js.map +1 -0
  188. package/src/lib/server/api-v1/usage.d.ts +26 -0
  189. package/src/lib/server/api-v1/usage.js +73 -0
  190. package/src/lib/server/api-v1/usage.js.map +1 -0
  191. package/src/lib/server/consent-group-participant.js +1 -1
  192. package/src/lib/server/consent-group-participant.js.map +1 -1
  193. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  194. package/src/lib/server/contact-lifecycle-floor.js +91 -0
  195. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  196. package/src/lib/server/contact-remove.d.ts +75 -0
  197. package/src/lib/server/contact-remove.js +244 -0
  198. package/src/lib/server/contact-remove.js.map +1 -0
  199. package/src/lib/server/contact-update.js +2 -1
  200. package/src/lib/server/contact-update.js.map +1 -1
  201. package/src/lib/server/contacts-import.d.ts +47 -11
  202. package/src/lib/server/contacts-import.js +171 -71
  203. package/src/lib/server/contacts-import.js.map +1 -1
  204. package/src/lib/server/contacts-merge.d.ts +1 -1
  205. package/src/lib/server/contacts-merge.js +3 -2
  206. package/src/lib/server/contacts-merge.js.map +1 -1
  207. package/src/lib/server/convert-host-lead.d.ts +189 -0
  208. package/src/lib/server/convert-host-lead.js +518 -0
  209. package/src/lib/server/convert-host-lead.js.map +1 -0
  210. package/src/lib/server/crm-sharing.d.ts +108 -0
  211. package/src/lib/server/crm-sharing.js +790 -0
  212. package/src/lib/server/crm-sharing.js.map +1 -0
  213. package/src/lib/server/daily-digest-route.d.ts +107 -0
  214. package/src/lib/server/daily-digest-route.js +588 -0
  215. package/src/lib/server/daily-digest-route.js.map +1 -0
  216. package/src/lib/server/export-route.d.ts +60 -0
  217. package/src/lib/server/export-route.js +447 -0
  218. package/src/lib/server/export-route.js.map +1 -0
  219. package/src/lib/server/inbound-route.d.ts +74 -0
  220. package/src/lib/server/inbound-route.js +257 -0
  221. package/src/lib/server/inbound-route.js.map +1 -0
  222. package/src/lib/server/lead-convert.d.ts +1 -1
  223. package/src/lib/server/lead-convert.js +2 -2
  224. package/src/lib/server/lead-convert.js.map +1 -1
  225. package/src/lib/server/merge-contacts.d.ts +55 -0
  226. package/src/lib/server/merge-contacts.js +274 -0
  227. package/src/lib/server/merge-contacts.js.map +1 -0
  228. package/src/lib/server/pipeline-record-index.d.ts +36 -0
  229. package/src/lib/server/pipeline-record-index.js +87 -0
  230. package/src/lib/server/pipeline-record-index.js.map +1 -0
  231. package/src/lib/server/record-facts.js +2 -1
  232. package/src/lib/server/record-facts.js.map +1 -1
  233. package/src/lib/server/task-reminders-route.d.ts +82 -0
  234. package/src/lib/server/task-reminders-route.js +497 -0
  235. package/src/lib/server/task-reminders-route.js.map +1 -0
  236. package/src/lib/server-deal-stage.js +2 -1
  237. package/src/lib/server-deal-stage.js.map +1 -1
  238. package/src/lib/server.d.ts +5 -2
  239. package/src/lib/server.js +47 -2
  240. package/src/lib/server.js.map +1 -1
  241. package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
  242. package/src/lib/testing/stand-in-site-record-routes.js +55 -0
  243. package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
  244. package/src/lib/usage-axes.d.ts +35 -0
  245. package/src/lib/usage-axes.js +68 -0
  246. package/src/lib/usage-axes.js.map +1 -0
  247. package/src/lib/components/use-emails-hub-path.d.ts +0 -24
  248. package/src/lib/components/use-emails-hub-path.js +0 -59
  249. package/src/lib/components/use-emails-hub-path.js.map +0 -1
@@ -0,0 +1,120 @@
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
+ * `POST /v1/contacts/{contactId}/merge` (AGL-2625) — fold another contact
18
+ * into this one.
19
+ *
20
+ * The contact in the path SURVIVES: it keeps its address as the identity,
21
+ * and the record named by `sourceContactId` is folded into it and deleted.
22
+ * `mergeContacts` (`../merge-contacts.ts`) is the whole of the work — the same
23
+ * function behind the console's `crm/contacts-merge` — so an integration
24
+ * and a person at the record page cannot merge two different ways.
25
+ *
26
+ * ## Idempotency
27
+ *
28
+ * Takes an `Idempotency-Key` with the delete's exact semantics, and for the
29
+ * delete's reason: a merge destroys a record, and a retry after a lost
30
+ * response must be able to tell "already merged" from "wrong id". Without a
31
+ * key, a second call finds no source contact and answers `404`, which is
32
+ * true and is the confusion the key removes.
33
+ *
34
+ * ## What it answers
35
+ *
36
+ * The surviving contact, read back the way `GET` reads it — the
37
+ * organization-wide profile, or one site's through `?consentSiteId=` —
38
+ * with its `alternateEmails` now carrying the merged address.
39
+ */ import { ApiErrors, apiJson } from "@aglyn/tenant-data-admin";
40
+ import { apiKeyActorLabel } from "@aglyn/aglyn/app-utils/activity-presenter";
41
+ import { claimWrite, readJsonBody } from "@aglyn/tenant-data-admin/server/api-v1-kit";
42
+ import { mergeContacts } from "../merge-contacts.js";
43
+ /** The key an integration names the record to fold in by. */ export const MERGE_SOURCE_FIELD = 'sourceContactId';
44
+ export async function mergeContactRoute(request, ctx, survivorRef, view) {
45
+ const body = await readJsonBody(request);
46
+ const raw = body[MERGE_SOURCE_FIELD];
47
+ const sourceId = typeof raw === 'string' ? raw.trim() : '';
48
+ const fields = {};
49
+ if (raw === undefined) fields[MERGE_SOURCE_FIELD] = 'Required — the contact to merge into this one';
50
+ else if (!sourceId) fields[MERGE_SOURCE_FIELD] = 'Must be a contact id';
51
+ else if (sourceId === survivorRef.id) {
52
+ fields[MERGE_SOURCE_FIELD] = 'Must be a different contact from the one in the path';
53
+ }
54
+ for (const key of Object.keys(body)){
55
+ if (key !== MERGE_SOURCE_FIELD) fields[key] = 'Not accepted on a merge';
56
+ }
57
+ if (Object.keys(fields).length) {
58
+ return ApiErrors.badRequest({
59
+ message: 'Merge failed validation',
60
+ code: 'validation_failed',
61
+ fields,
62
+ headers: ctx.headers
63
+ });
64
+ }
65
+ const claimed = await claimWrite(ctx, '*', request.headers.get('Idempotency-Key'), 'contact-merges');
66
+ if ('replay' in claimed) return claimed.replay;
67
+ const { claim } = claimed;
68
+ try {
69
+ const result = await mergeContacts({
70
+ firestore: ctx.firestore,
71
+ orgRef: ctx.firestore.collection('orgs').doc(ctx.orgId),
72
+ survivorId: survivorRef.id,
73
+ mergedId: sourceId,
74
+ // A key has no uid; `'api'` is the attribution every API-created CRM
75
+ // record carries in `createdByUid`, and the note it leaves names the
76
+ // key when the organization named it.
77
+ actor: {
78
+ uid: 'api',
79
+ email: null,
80
+ apiKeyName: ctx.keyName
81
+ },
82
+ hostId: null,
83
+ actorName: ctx.keyName ? apiKeyActorLabel(ctx.keyName) : 'API'
84
+ });
85
+ if (result.ok === false) {
86
+ await claim.release();
87
+ if (result.reason === 'survivor-missing') {
88
+ return ApiErrors.notFound({
89
+ message: 'No such contact',
90
+ headers: ctx.headers
91
+ });
92
+ }
93
+ if (result.reason === 'merged-missing') {
94
+ return ApiErrors.notFound({
95
+ message: 'No such contact to merge',
96
+ code: 'source_not_found',
97
+ headers: ctx.headers
98
+ });
99
+ }
100
+ return ApiErrors.badRequest({
101
+ message: 'Merge failed validation',
102
+ code: 'validation_failed',
103
+ fields: {
104
+ [MERGE_SOURCE_FIELD]: 'Must be a different contact from the one in the path'
105
+ },
106
+ headers: ctx.headers
107
+ });
108
+ }
109
+ const answer = view(await survivorRef.get());
110
+ await claim.record(200, answer);
111
+ return apiJson(answer, {
112
+ headers: ctx.headers
113
+ });
114
+ } catch (error) {
115
+ await claim.release();
116
+ throw error;
117
+ }
118
+ }
119
+
120
+ //# sourceMappingURL=contacts-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/server/api-v1/contacts-merge.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 * `POST /v1/contacts/{contactId}/merge` (AGL-2625) — fold another contact\n * into this one.\n *\n * The contact in the path SURVIVES: it keeps its address as the identity,\n * and the record named by `sourceContactId` is folded into it and deleted.\n * `mergeContacts` (`../merge-contacts.ts`) is the whole of the work — the same\n * function behind the console's `crm/contacts-merge` — so an integration\n * and a person at the record page cannot merge two different ways.\n *\n * ## Idempotency\n *\n * Takes an `Idempotency-Key` with the delete's exact semantics, and for the\n * delete's reason: a merge destroys a record, and a retry after a lost\n * response must be able to tell \"already merged\" from \"wrong id\". Without a\n * key, a second call finds no source contact and answers `404`, which is\n * true and is the confusion the key removes.\n *\n * ## What it answers\n *\n * The surviving contact, read back the way `GET` reads it — the\n * organization-wide profile, or one site's through `?consentSiteId=` —\n * with its `alternateEmails` now carrying the merged address.\n */\n\nimport { ApiErrors, apiJson } from '@aglyn/tenant-data-admin'\nimport { apiKeyActorLabel } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport {\n type ApiV1Context,\n claimWrite,\n readJsonBody,\n} from '@aglyn/tenant-data-admin/server/api-v1-kit'\nimport { mergeContacts } from '../merge-contacts'\n\n/** The key an integration names the record to fold in by. */\nexport const MERGE_SOURCE_FIELD = 'sourceContactId'\n\nexport async function mergeContactRoute(\n request: Request,\n ctx: ApiV1Context,\n survivorRef: FirebaseFirestore.DocumentReference,\n view: (snapshot: FirebaseFirestore.DocumentSnapshot) => unknown,\n): Promise<Response> {\n const body = await readJsonBody(request)\n const raw = body[MERGE_SOURCE_FIELD]\n const sourceId = typeof raw === 'string' ? raw.trim() : ''\n const fields: Record<string, string> = {}\n if (raw === undefined) fields[MERGE_SOURCE_FIELD] = 'Required — the contact to merge into this one'\n else if (!sourceId) fields[MERGE_SOURCE_FIELD] = 'Must be a contact id'\n else if (sourceId === survivorRef.id) {\n fields[MERGE_SOURCE_FIELD] = 'Must be a different contact from the one in the path'\n }\n for (const key of Object.keys(body)) {\n if (key !== MERGE_SOURCE_FIELD) fields[key] = 'Not accepted on a merge'\n }\n if (Object.keys(fields).length) {\n return ApiErrors.badRequest({\n message: 'Merge failed validation',\n code: 'validation_failed',\n fields,\n headers: ctx.headers,\n })\n }\n\n const claimed = await claimWrite(\n ctx,\n '*',\n request.headers.get('Idempotency-Key'),\n 'contact-merges',\n )\n if ('replay' in claimed) return claimed.replay\n const { claim } = claimed\n\n try {\n const result = await mergeContacts({\n firestore: ctx.firestore,\n orgRef: ctx.firestore.collection('orgs').doc(ctx.orgId),\n survivorId: survivorRef.id,\n mergedId: sourceId,\n // A key has no uid; `'api'` is the attribution every API-created CRM\n // record carries in `createdByUid`, and the note it leaves names the\n // key when the organization named it.\n actor: { uid: 'api', email: null, apiKeyName: ctx.keyName },\n hostId: null,\n actorName: ctx.keyName ? apiKeyActorLabel(ctx.keyName) : 'API',\n })\n if (result.ok === false) {\n await claim.release()\n if (result.reason === 'survivor-missing') {\n return ApiErrors.notFound({ message: 'No such contact', headers: ctx.headers })\n }\n if (result.reason === 'merged-missing') {\n return ApiErrors.notFound({\n message: 'No such contact to merge',\n code: 'source_not_found',\n headers: ctx.headers,\n })\n }\n return ApiErrors.badRequest({\n message: 'Merge failed validation',\n code: 'validation_failed',\n fields: { [MERGE_SOURCE_FIELD]: 'Must be a different contact from the one in the path' },\n headers: ctx.headers,\n })\n }\n const answer = view(await survivorRef.get())\n await claim.record(200, answer)\n return apiJson(answer, { headers: ctx.headers })\n } catch (error) {\n await claim.release()\n throw error\n }\n}\n"],"names":["ApiErrors","apiJson","apiKeyActorLabel","claimWrite","readJsonBody","mergeContacts","MERGE_SOURCE_FIELD","mergeContactRoute","request","ctx","survivorRef","view","body","raw","sourceId","trim","fields","undefined","id","key","Object","keys","length","badRequest","message","code","headers","claimed","get","replay","claim","result","firestore","orgRef","collection","doc","orgId","survivorId","mergedId","actor","uid","email","apiKeyName","keyName","hostId","actorName","ok","release","reason","notFound","answer","record","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GAED,SAASA,SAAS,EAAEC,OAAO,QAAQ,2BAA0B;AAC7D,SAASC,gBAAgB,QAAQ,4CAA2C;AAC5E,SAEEC,UAAU,EACVC,YAAY,QACP,6CAA4C;AACnD,SAASC,aAAa,QAAQ,uBAAmB;AAEjD,2DAA2D,GAC3D,OAAO,MAAMC,qBAAqB,kBAAiB;AAEnD,OAAO,eAAeC,kBACpBC,OAAgB,EAChBC,GAAiB,EACjBC,WAAgD,EAChDC,IAA+D;IAE/D,MAAMC,OAAO,MAAMR,aAAaI;IAChC,MAAMK,MAAMD,IAAI,CAACN,mBAAmB;IACpC,MAAMQ,WAAW,OAAOD,QAAQ,WAAWA,IAAIE,IAAI,KAAK;IACxD,MAAMC,SAAiC,CAAC;IACxC,IAAIH,QAAQI,WAAWD,MAAM,CAACV,mBAAmB,GAAG;SAC/C,IAAI,CAACQ,UAAUE,MAAM,CAACV,mBAAmB,GAAG;SAC5C,IAAIQ,aAAaJ,YAAYQ,EAAE,EAAE;QACpCF,MAAM,CAACV,mBAAmB,GAAG;IAC/B;IACA,KAAK,MAAMa,OAAOC,OAAOC,IAAI,CAACT,MAAO;QACnC,IAAIO,QAAQb,oBAAoBU,MAAM,CAACG,IAAI,GAAG;IAChD;IACA,IAAIC,OAAOC,IAAI,CAACL,QAAQM,MAAM,EAAE;QAC9B,OAAOtB,UAAUuB,UAAU,CAAC;YAC1BC,SAAS;YACTC,MAAM;YACNT;YACAU,SAASjB,IAAIiB,OAAO;QACtB;IACF;IAEA,MAAMC,UAAU,MAAMxB,WACpBM,KACA,KACAD,QAAQkB,OAAO,CAACE,GAAG,CAAC,oBACpB;IAEF,IAAI,YAAYD,SAAS,OAAOA,QAAQE,MAAM;IAC9C,MAAM,EAAEC,KAAK,EAAE,GAAGH;IAElB,IAAI;QACF,MAAMI,SAAS,MAAM1B,cAAc;YACjC2B,WAAWvB,IAAIuB,SAAS;YACxBC,QAAQxB,IAAIuB,SAAS,CAACE,UAAU,CAAC,QAAQC,GAAG,CAAC1B,IAAI2B,KAAK;YACtDC,YAAY3B,YAAYQ,EAAE;YAC1BoB,UAAUxB;YACV,qEAAqE;YACrE,qEAAqE;YACrE,sCAAsC;YACtCyB,OAAO;gBAAEC,KAAK;gBAAOC,OAAO;gBAAMC,YAAYjC,IAAIkC,OAAO;YAAC;YAC1DC,QAAQ;YACRC,WAAWpC,IAAIkC,OAAO,GAAGzC,iBAAiBO,IAAIkC,OAAO,IAAI;QAC3D;QACA,IAAIZ,OAAOe,EAAE,KAAK,OAAO;YACvB,MAAMhB,MAAMiB,OAAO;YACnB,IAAIhB,OAAOiB,MAAM,KAAK,oBAAoB;gBACxC,OAAOhD,UAAUiD,QAAQ,CAAC;oBAAEzB,SAAS;oBAAmBE,SAASjB,IAAIiB,OAAO;gBAAC;YAC/E;YACA,IAAIK,OAAOiB,MAAM,KAAK,kBAAkB;gBACtC,OAAOhD,UAAUiD,QAAQ,CAAC;oBACxBzB,SAAS;oBACTC,MAAM;oBACNC,SAASjB,IAAIiB,OAAO;gBACtB;YACF;YACA,OAAO1B,UAAUuB,UAAU,CAAC;gBAC1BC,SAAS;gBACTC,MAAM;gBACNT,QAAQ;oBAAE,CAACV,mBAAmB,EAAE;gBAAuD;gBACvFoB,SAASjB,IAAIiB,OAAO;YACtB;QACF;QACA,MAAMwB,SAASvC,KAAK,MAAMD,YAAYkB,GAAG;QACzC,MAAME,MAAMqB,MAAM,CAAC,KAAKD;QACxB,OAAOjD,QAAQiD,QAAQ;YAAExB,SAASjB,IAAIiB,OAAO;QAAC;IAChD,EAAE,OAAO0B,OAAO;QACd,MAAMtB,MAAMiB,OAAO;QACnB,MAAMK;IACR;AACF"}
@@ -0,0 +1,18 @@
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
+ import { type ApiV1Context } from '@aglyn/tenant-data-admin/server/api-v1-kit';
18
+ export declare function handleContacts(request: Request, ctx: ApiV1Context, segments: string[], url: URL): Promise<Response>;