@open-mercato/core 0.7.1-develop.7175.1.d49ab48ee2 → 0.7.1-develop.7176.1.d229567a50

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 (79) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/phone_call/index.js +53 -0
  3. package/dist/generated/entities/phone_call/index.js.map +7 -0
  4. package/dist/generated/entities/phone_call_participant/index.js +27 -0
  5. package/dist/generated/entities/phone_call_participant/index.js.map +7 -0
  6. package/dist/generated/entities.ids.generated.js +5 -0
  7. package/dist/generated/entities.ids.generated.js.map +2 -2
  8. package/dist/generated/entity-fields-registry.js +41 -0
  9. package/dist/generated/entity-fields-registry.js.map +2 -2
  10. package/dist/modules/configs/lib/upgrade-actions.js +20 -0
  11. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  12. package/dist/modules/entities/migrations/Migration20260822120000.js +44 -0
  13. package/dist/modules/entities/migrations/Migration20260822120000.js.map +7 -0
  14. package/dist/modules/phone_calls/acl.js +15 -0
  15. package/dist/modules/phone_calls/acl.js.map +7 -0
  16. package/dist/modules/phone_calls/api/calls/route.js +132 -0
  17. package/dist/modules/phone_calls/api/calls/route.js.map +7 -0
  18. package/dist/modules/phone_calls/api/openapi.js +23 -0
  19. package/dist/modules/phone_calls/api/openapi.js.map +7 -0
  20. package/dist/modules/phone_calls/backend/page.js +261 -0
  21. package/dist/modules/phone_calls/backend/page.js.map +7 -0
  22. package/dist/modules/phone_calls/backend/page.meta.js +16 -0
  23. package/dist/modules/phone_calls/backend/page.meta.js.map +7 -0
  24. package/dist/modules/phone_calls/commands/calls.js +125 -0
  25. package/dist/modules/phone_calls/commands/calls.js.map +7 -0
  26. package/dist/modules/phone_calls/commands/index.js +2 -0
  27. package/dist/modules/phone_calls/commands/index.js.map +7 -0
  28. package/dist/modules/phone_calls/data/entities.js +162 -0
  29. package/dist/modules/phone_calls/data/entities.js.map +7 -0
  30. package/dist/modules/phone_calls/data/validators.js +68 -0
  31. package/dist/modules/phone_calls/data/validators.js.map +7 -0
  32. package/dist/modules/phone_calls/di.js +12 -0
  33. package/dist/modules/phone_calls/di.js.map +7 -0
  34. package/dist/modules/phone_calls/encryption.js +28 -0
  35. package/dist/modules/phone_calls/encryption.js.map +7 -0
  36. package/dist/modules/phone_calls/events.js +18 -0
  37. package/dist/modules/phone_calls/events.js.map +7 -0
  38. package/dist/modules/phone_calls/index.js +15 -0
  39. package/dist/modules/phone_calls/index.js.map +7 -0
  40. package/dist/modules/phone_calls/migrations/Migration20260706124120_phone_calls.js +22 -0
  41. package/dist/modules/phone_calls/migrations/Migration20260706124120_phone_calls.js.map +7 -0
  42. package/dist/modules/phone_calls/migrations/Migration20260814124333_phone_calls.js +13 -0
  43. package/dist/modules/phone_calls/migrations/Migration20260814124333_phone_calls.js.map +7 -0
  44. package/dist/modules/phone_calls/setup.js +16 -0
  45. package/dist/modules/phone_calls/setup.js.map +7 -0
  46. package/generated/entities/phone_call/index.ts +25 -0
  47. package/generated/entities/phone_call_participant/index.ts +12 -0
  48. package/generated/entities.ids.generated.ts +5 -0
  49. package/generated/entity-fields-registry.ts +41 -0
  50. package/package.json +7 -7
  51. package/src/modules/auth/i18n/de.json +3 -0
  52. package/src/modules/auth/i18n/en.json +3 -0
  53. package/src/modules/auth/i18n/es.json +3 -0
  54. package/src/modules/auth/i18n/ko.json +3 -0
  55. package/src/modules/auth/i18n/pl.json +3 -0
  56. package/src/modules/configs/lib/upgrade-actions.ts +20 -0
  57. package/src/modules/entities/migrations/Migration20260822120000.ts +56 -0
  58. package/src/modules/phone_calls/acl.ts +11 -0
  59. package/src/modules/phone_calls/api/calls/route.ts +150 -0
  60. package/src/modules/phone_calls/api/openapi.ts +25 -0
  61. package/src/modules/phone_calls/backend/page.meta.ts +12 -0
  62. package/src/modules/phone_calls/backend/page.tsx +303 -0
  63. package/src/modules/phone_calls/commands/calls.ts +156 -0
  64. package/src/modules/phone_calls/commands/index.ts +1 -0
  65. package/src/modules/phone_calls/data/entities.ts +144 -0
  66. package/src/modules/phone_calls/data/validators.ts +69 -0
  67. package/src/modules/phone_calls/di.ts +10 -0
  68. package/src/modules/phone_calls/encryption.ts +26 -0
  69. package/src/modules/phone_calls/events.ts +25 -0
  70. package/src/modules/phone_calls/i18n/de.json +42 -0
  71. package/src/modules/phone_calls/i18n/en.json +42 -0
  72. package/src/modules/phone_calls/i18n/es.json +42 -0
  73. package/src/modules/phone_calls/i18n/ko.json +42 -0
  74. package/src/modules/phone_calls/i18n/pl.json +42 -0
  75. package/src/modules/phone_calls/index.ts +13 -0
  76. package/src/modules/phone_calls/migrations/.snapshot-open-mercato.json +772 -0
  77. package/src/modules/phone_calls/migrations/Migration20260706124120_phone_calls.ts +23 -0
  78. package/src/modules/phone_calls/migrations/Migration20260814124333_phone_calls.ts +13 -0
  79. package/src/modules/phone_calls/setup.ts +14 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/backend/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { SortingState } from '@tanstack/react-table'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { StatusBadge, type StatusMap } from '@open-mercato/ui/primitives/status-badge'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { useProgressPoll } from '@open-mercato/ui/backend/progress/useProgressPoll'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { PHONE_CALL_RESOURCE_KIND } from '@open-mercato/shared/modules/phone_calls/types'\n\ntype PhoneCallRow = {\n id: string\n provider_key: string | null\n external_call_id: string | null\n direction: string | null\n status: string | null\n started_at: string | null\n duration_seconds: number | null\n ingest_status: string | null\n}\n\ntype PhoneCallListResponse = {\n items?: PhoneCallRow[]\n total?: number\n page?: number\n totalPages?: number\n totalIsCapped?: boolean\n}\n\nconst DIRECTION_VALUES = ['inbound', 'outbound', 'internal', 'unknown'] as const\nconst STATUS_VALUES = ['new', 'ringing', 'answered', 'missed', 'failed', 'completed', 'unknown'] as const\n\ntype PhoneCallStatusValue = typeof STATUS_VALUES[number]\n\nfunction isPhoneCallStatus(value: string): value is PhoneCallStatusValue {\n return (STATUS_VALUES as readonly string[]).includes(value)\n}\n\n// Column id (snake_case accessorKey) -> API sortField key (camelCase, matches\n// the route's sortFieldMap). Columns absent from this map are not sortable.\nconst SORT_FIELDS: Record<string, string> = {\n external_call_id: 'externalCallId',\n direction: 'direction',\n status: 'status',\n provider_key: 'providerKey',\n started_at: 'startedAt',\n duration_seconds: 'durationSeconds',\n ingest_status: 'ingestStatus',\n}\n\nconst STATUS_TONE: StatusMap<PhoneCallStatusValue> = {\n completed: 'success',\n answered: 'success',\n ringing: 'warning',\n new: 'neutral',\n missed: 'error',\n failed: 'error',\n unknown: 'neutral',\n}\n\nfunction formatDuration(seconds: number | null): string {\n if (seconds == null || Number.isNaN(seconds)) return '\u2014'\n const total = Math.max(0, Math.floor(seconds))\n const mins = Math.floor(total / 60)\n const secs = total % 60\n return `${mins}:${String(secs).padStart(2, '0')}`\n}\n\nexport default function PhoneCallsPage() {\n const t = useT()\n const [rows, setRows] = React.useState<PhoneCallRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [page, setPage] = React.useState(1)\n const [pageSize, setPageSize] = React.useState(50)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'started_at', desc: true }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n\n const handleSortingChange = React.useCallback((next: SortingState) => {\n setSorting(next)\n setPage(1)\n }, [])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [reloadToken, setReloadToken] = React.useState(0)\n\n // Ingest runs in a provider worker, so the rows land after the page did. Any job that\n // declares this resource kind wrote calls, whichever provider queued it.\n useAppEvent('progress.job.completed', (event) => {\n const meta = (event.payload as { meta?: Record<string, unknown> | null } | undefined)?.meta\n if (meta?.resourceKind !== PHONE_CALL_RESOURCE_KIND) return\n setReloadToken((token) => token + 1)\n }, [])\n\n // The event above is a live subscription with no replay: one emitted while the stream is\n // reconnecting is gone, and the grid keeps showing a range the ingest already filled. Polling\n // asks for state instead of waiting to be told, keyed by job id so nothing reloads twice.\n const { recentlyCompleted } = useProgressPoll()\n const seenJobIds = React.useRef<Set<string> | null>(null)\n React.useEffect(() => {\n const ingestJobs = recentlyCompleted.filter(\n (job) => (job.meta as { resourceKind?: string } | null | undefined)?.resourceKind === PHONE_CALL_RESOURCE_KIND,\n )\n // Jobs that finished before this page mounted are recorded without a reload; the initial\n // load already covers them.\n if (seenJobIds.current === null) {\n seenJobIds.current = new Set(ingestJobs.map((job) => job.id))\n return\n }\n const unseen = ingestJobs.filter((job) => !seenJobIds.current!.has(job.id))\n if (!unseen.length) return\n for (const job of unseen) seenJobIds.current.add(job.id)\n setReloadToken((token) => token + 1)\n }, [recentlyCompleted])\n\n const queryString = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (search.trim()) params.set('q', search.trim())\n if (typeof filterValues.direction === 'string' && filterValues.direction) params.set('direction', filterValues.direction)\n if (typeof filterValues.status === 'string' && filterValues.status) params.set('status', filterValues.status)\n if (typeof filterValues.providerKey === 'string' && filterValues.providerKey.trim()) params.set('providerKey', filterValues.providerKey.trim())\n const started = filterValues.started as { from?: string; to?: string } | undefined\n if (started?.from) params.set('startedFrom', started.from)\n if (started?.to) params.set('startedTo', started.to)\n const activeSort = sorting[0]\n if (activeSort) {\n const sortField = SORT_FIELDS[activeSort.id]\n if (sortField) {\n params.set('sortField', sortField)\n params.set('sortDir', activeSort.desc ? 'desc' : 'asc')\n }\n }\n return params.toString()\n }, [page, pageSize, search, filterValues, sorting])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n const response = await apiCall<PhoneCallListResponse>(`/api/phone_calls/calls?${queryString}`).catch((err: unknown) => ({\n ok: false as const,\n result: { error: err instanceof Error ? err.message : undefined },\n }))\n if (cancelled) return\n if (!response.ok) {\n const errBody = response.result as { error?: string } | undefined\n setError(errBody?.error ?? t('phone_calls.calls.list.error', 'Failed to load phone calls'))\n setRows([])\n setTotal(0)\n setTotalPages(1)\n setTotalIsCapped(false)\n } else {\n const data = (response.result ?? {}) as PhoneCallListResponse\n setRows(Array.isArray(data.items) ? data.items : [])\n setTotal(typeof data.total === 'number' ? data.total : 0)\n setTotalPages(typeof data.totalPages === 'number' ? data.totalPages : 1)\n setTotalIsCapped(data.totalIsCapped === true)\n }\n setIsLoading(false)\n }\n void load()\n return () => { cancelled = true }\n }, [queryString, reloadToken, t])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'direction',\n label: t('phone_calls.calls.filters.direction', 'Direction'),\n type: 'select',\n options: DIRECTION_VALUES.map((value) => ({\n value,\n label: t(`phone_calls.calls.direction.${value}`, value),\n })),\n },\n {\n id: 'status',\n label: t('phone_calls.calls.filters.status', 'Status'),\n type: 'select',\n options: STATUS_VALUES.map((value) => ({\n value,\n label: t(`phone_calls.calls.status.${value}`, value),\n })),\n },\n {\n id: 'providerKey',\n label: t('phone_calls.calls.filters.provider', 'Provider'),\n type: 'text',\n placeholder: t('phone_calls.calls.filters.providerPlaceholder', 'Provider key'),\n },\n {\n id: 'started',\n label: t('phone_calls.calls.filters.started', 'Started'),\n type: 'dateRange',\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<PhoneCallRow>[]>(() => [\n {\n accessorKey: 'external_call_id',\n header: t('phone_calls.calls.columns.callId', 'Call ID'),\n meta: { truncate: true, maxWidth: '240px' },\n cell: ({ row }) => (\n <span className=\"text-sm font-medium\">{row.original.external_call_id || '\u2014'}</span>\n ),\n },\n {\n accessorKey: 'direction',\n header: t('phone_calls.calls.columns.direction', 'Direction'),\n cell: ({ row }) => {\n const value = row.original.direction\n return <span className=\"text-sm\">{value ? t(`phone_calls.calls.direction.${value}`, value) : '\u2014'}</span>\n },\n },\n {\n accessorKey: 'status',\n header: t('phone_calls.calls.columns.status', 'Status'),\n cell: ({ row }) => {\n const value = row.original.status\n if (!value) return <span className=\"text-sm text-muted-foreground\">\u2014</span>\n return (\n <StatusBadge variant={STATUS_TONE[isPhoneCallStatus(value) ? value : 'unknown']} dot>\n {t(`phone_calls.calls.status.${value}`, value)}\n </StatusBadge>\n )\n },\n },\n {\n accessorKey: 'provider_key',\n header: t('phone_calls.calls.columns.provider', 'Provider'),\n cell: ({ row }) => <span className=\"text-sm\">{row.original.provider_key || '\u2014'}</span>,\n },\n {\n accessorKey: 'started_at',\n header: t('phone_calls.calls.columns.started', 'Started'),\n cell: ({ row }) => <span className=\"text-sm text-muted-foreground\">{formatDateTime(row.original.started_at) ?? '\u2014'}</span>,\n },\n {\n accessorKey: 'duration_seconds',\n header: t('phone_calls.calls.columns.duration', 'Duration'),\n cell: ({ row }) => <span className=\"text-sm tabular-nums\">{formatDuration(row.original.duration_seconds)}</span>,\n },\n {\n accessorKey: 'ingest_status',\n header: t('phone_calls.calls.columns.ingestStatus', 'Ingest'),\n cell: ({ row }) => {\n const value = row.original.ingest_status\n return (\n <span className=\"text-sm text-muted-foreground\">\n {value ? t(`phone_calls.calls.ingestStatus.${value}`, value) : '\u2014'}\n </span>\n )\n },\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<PhoneCallRow>\n title={t('phone_calls.calls.list.title', 'Phone Calls')}\n extensionTableId=\"phone_calls.calls\"\n columns={columns}\n data={rows}\n isLoading={isLoading}\n error={error}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={handleSortingChange}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('phone_calls.calls.list.searchPlaceholder', 'Search by call, conversation or provider')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => { setFilterValues(values); setPage(1) }}\n onFiltersClear={() => { setFilterValues({}); setPage(1) }}\n emptyState={t('phone_calls.calls.list.empty', 'No phone calls yet. Calls appear here once they are ingested from a provider.')}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n totalIsCapped,\n onPageChange: setPage,\n pageSizeOptions: [10, 25, 50, 100],\n onPageSizeChange: (size) => { setPageSize(size); setPage(1) },\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ["phone_calls.view"],
4
+ pageTitle: "Phone Calls",
5
+ pageTitleKey: "phone_calls.nav.calls",
6
+ pageGroup: "Communication",
7
+ pageGroupKey: "phone_calls.nav.group",
8
+ pagePriority: 10,
9
+ pageOrder: 100,
10
+ icon: "phone-call",
11
+ breadcrumb: [{ label: "Phone Calls", labelKey: "phone_calls.nav.calls" }]
12
+ };
13
+ export {
14
+ metadata
15
+ };
16
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/backend/page.meta.ts"],
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['phone_calls.view'],\n pageTitle: 'Phone Calls',\n pageTitleKey: 'phone_calls.nav.calls',\n pageGroup: 'Communication',\n pageGroupKey: 'phone_calls.nav.group',\n pagePriority: 10,\n pageOrder: 100,\n icon: 'phone-call',\n breadcrumb: [{ label: 'Phone Calls', labelKey: 'phone_calls.nav.calls' }],\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,kBAAkB;AAAA,EACpC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY,CAAC,EAAE,OAAO,eAAe,UAAU,wBAAwB,CAAC;AAC1E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,125 @@
1
+ import { registerCommand } from "@open-mercato/shared/lib/commands";
2
+ import { emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
3
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
4
+ import { ensureOrganizationScope, ensureTenantScope } from "@open-mercato/shared/lib/commands/scope";
5
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
6
+ import { PHONE_CALL_RESOURCE_KIND } from "@open-mercato/shared/modules/phone_calls/types";
7
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
+ import { E } from "../../../generated/entities.ids.generated.js";
9
+ import { PhoneCall, PhoneCallParticipant } from "../data/entities.js";
10
+ import {
11
+ ingestPhoneCallSchema
12
+ } from "../data/validators.js";
13
+ import { emitPhoneCallsEvent } from "../events.js";
14
+ const PHONE_CALLS_CALL_INGEST_COMMAND_ID = "phone_calls.call.ingest";
15
+ const phoneCallCrudIndexer = {
16
+ entityType: E.phone_calls.phone_call
17
+ };
18
+ const PHONE_CALL_CACHE_ALIASES = [PhoneCall.name];
19
+ function applyScalarFields(call, input, ingestedAt) {
20
+ call.integrationId = input.integrationId ?? null;
21
+ call.externalConversationId = input.externalConversationId ?? null;
22
+ call.direction = input.direction;
23
+ call.status = input.status;
24
+ call.startedAt = input.startedAt ?? null;
25
+ call.answeredAt = input.answeredAt ?? null;
26
+ call.endedAt = input.endedAt ?? null;
27
+ call.durationSeconds = input.durationSeconds ?? null;
28
+ call.recordingUrl = input.recording?.url ?? null;
29
+ call.providerFacts = input.providerFacts ?? null;
30
+ call.rawSnapshot = input.rawPayload;
31
+ call.ingestStatus = "complete";
32
+ call.lastIngestedAt = ingestedAt;
33
+ }
34
+ async function syncParticipants(em, call, input) {
35
+ await em.nativeDelete(PhoneCallParticipant, { phoneCallId: call.id });
36
+ for (const participant of input.participants) {
37
+ em.persist(em.create(PhoneCallParticipant, {
38
+ organizationId: call.organizationId,
39
+ tenantId: call.tenantId,
40
+ phoneCallId: call.id,
41
+ providerParticipantId: participant.providerParticipantId ?? null,
42
+ role: participant.role,
43
+ phoneNumber: participant.phoneNumber ?? null,
44
+ displayName: participant.displayName ?? null,
45
+ email: participant.email ?? null,
46
+ metadata: participant.metadata ?? null
47
+ }));
48
+ }
49
+ }
50
+ const ingestPhoneCallCommand = {
51
+ id: PHONE_CALLS_CALL_INGEST_COMMAND_ID,
52
+ async execute(rawInput, ctx) {
53
+ const input = ingestPhoneCallSchema.parse(rawInput);
54
+ ensureTenantScope(ctx, input.tenantId);
55
+ ensureOrganizationScope(ctx, input.organizationId);
56
+ const em = ctx.container.resolve("em").fork();
57
+ const existing = await findOneWithDecryption(
58
+ em,
59
+ PhoneCall,
60
+ {
61
+ providerKey: input.providerKey,
62
+ externalCallId: input.externalCallId,
63
+ tenantId: input.tenantId,
64
+ organizationId: input.organizationId
65
+ },
66
+ void 0,
67
+ { tenantId: input.tenantId, organizationId: input.organizationId }
68
+ );
69
+ const created = !existing;
70
+ const call = existing ?? em.create(PhoneCall, {
71
+ organizationId: input.organizationId,
72
+ tenantId: input.tenantId,
73
+ providerKey: input.providerKey,
74
+ externalCallId: input.externalCallId,
75
+ direction: input.direction,
76
+ status: input.status
77
+ });
78
+ if (created) em.persist(call);
79
+ const ingestedAt = /* @__PURE__ */ new Date();
80
+ await withAtomicFlush(
81
+ em,
82
+ [
83
+ () => applyScalarFields(call, input, ingestedAt),
84
+ () => syncParticipants(em, call, input)
85
+ ],
86
+ { transaction: true, label: PHONE_CALLS_CALL_INGEST_COMMAND_ID }
87
+ );
88
+ await emitCrudSideEffects({
89
+ dataEngine: ctx.container.resolve("dataEngine"),
90
+ action: created ? "created" : "updated",
91
+ entity: call,
92
+ identifiers: {
93
+ id: call.id,
94
+ organizationId: call.organizationId,
95
+ tenantId: call.tenantId
96
+ },
97
+ indexer: phoneCallCrudIndexer
98
+ });
99
+ await emitPhoneCallsEvent(
100
+ created ? "phone_calls.call.ingested" : "phone_calls.call.updated",
101
+ { id: call.id, organizationId: call.organizationId, tenantId: call.tenantId }
102
+ ).catch(() => void 0);
103
+ return { phoneCallId: call.id, created };
104
+ },
105
+ // Calls carry PII (numbers, names, recordings), so the audit entry stays at identifiers
106
+ // and the ingest outcome. No snapshot, and the command is not undoable.
107
+ buildLog: async ({ input, result }) => {
108
+ const { translate } = await resolveTranslations();
109
+ return {
110
+ actionLabel: result.created ? translate("phone_calls.audit.calls.ingested", "Ingest phone call") : translate("phone_calls.audit.calls.updated", "Update ingested phone call"),
111
+ resourceKind: PHONE_CALL_RESOURCE_KIND,
112
+ resourceId: result.phoneCallId,
113
+ tenantId: input.tenantId,
114
+ organizationId: input.organizationId,
115
+ context: { cacheAliases: PHONE_CALL_CACHE_ALIASES }
116
+ };
117
+ }
118
+ };
119
+ registerCommand(ingestPhoneCallCommand);
120
+ export {
121
+ PHONE_CALLS_CALL_INGEST_COMMAND_ID,
122
+ PHONE_CALL_RESOURCE_KIND,
123
+ ingestPhoneCallCommand
124
+ };
125
+ //# sourceMappingURL=calls.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/commands/calls.ts"],
4
+ "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { PHONE_CALL_RESOURCE_KIND } from '@open-mercato/shared/modules/phone_calls/types'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { E } from '#generated/entities.ids.generated'\nimport { PhoneCall, PhoneCallParticipant } from '../data/entities'\nimport {\n ingestPhoneCallSchema,\n type IngestPhoneCallInput,\n} from '../data/validators'\nimport { emitPhoneCallsEvent } from '../events'\n\nexport const PHONE_CALLS_CALL_INGEST_COMMAND_ID = 'phone_calls.call.ingest'\nexport { PHONE_CALL_RESOURCE_KIND }\n\nconst phoneCallCrudIndexer: CrudIndexerConfig<PhoneCall> = {\n entityType: E.phone_calls.phone_call,\n}\n\n// The list route derives its cache tag from the entity class name, and the tag the bus\n// derives from this command id is a different one, so without the alias the cached list\n// pages survive an ingest.\nconst PHONE_CALL_CACHE_ALIASES = [PhoneCall.name]\n\nexport type IngestPhoneCallResult = { phoneCallId: string; created: boolean }\n\nfunction applyScalarFields(call: PhoneCall, input: IngestPhoneCallInput, ingestedAt: Date): void {\n call.integrationId = input.integrationId ?? null\n call.externalConversationId = input.externalConversationId ?? null\n call.direction = input.direction\n call.status = input.status\n call.startedAt = input.startedAt ?? null\n call.answeredAt = input.answeredAt ?? null\n call.endedAt = input.endedAt ?? null\n call.durationSeconds = input.durationSeconds ?? null\n call.recordingUrl = input.recording?.url ?? null\n call.providerFacts = input.providerFacts ?? null\n call.rawSnapshot = input.rawPayload\n call.ingestStatus = 'complete'\n call.lastIngestedAt = ingestedAt\n}\n\nasync function syncParticipants(\n em: EntityManager,\n call: PhoneCall,\n input: IngestPhoneCallInput,\n): Promise<void> {\n // Replace the whole participant set on every ingest: participants have no reliable\n // natural key (providerParticipantId/phoneNumber are often null), so a diff/upsert would\n // be guesswork. Delete-all + insert stays deterministic and idempotent. Revisit this if\n // anything starts referencing participant.id externally (re-pull rotates these ids).\n await em.nativeDelete(PhoneCallParticipant, { phoneCallId: call.id })\n for (const participant of input.participants) {\n em.persist(em.create(PhoneCallParticipant, {\n organizationId: call.organizationId,\n tenantId: call.tenantId,\n phoneCallId: call.id,\n providerParticipantId: participant.providerParticipantId ?? null,\n role: participant.role,\n phoneNumber: participant.phoneNumber ?? null,\n displayName: participant.displayName ?? null,\n email: participant.email ?? null,\n metadata: participant.metadata ?? null,\n }))\n }\n}\n\nconst ingestPhoneCallCommand: CommandHandler<IngestPhoneCallInput, IngestPhoneCallResult> = {\n id: PHONE_CALLS_CALL_INGEST_COMMAND_ID,\n async execute(rawInput, ctx) {\n const input = ingestPhoneCallSchema.parse(rawInput)\n ensureTenantScope(ctx, input.tenantId)\n ensureOrganizationScope(ctx, input.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await findOneWithDecryption(\n em,\n PhoneCall,\n {\n providerKey: input.providerKey,\n externalCallId: input.externalCallId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n },\n undefined,\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const created = !existing\n const call = existing ?? em.create(PhoneCall, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n providerKey: input.providerKey,\n externalCallId: input.externalCallId,\n direction: input.direction,\n status: input.status,\n })\n if (created) em.persist(call)\n const ingestedAt = new Date()\n\n await withAtomicFlush(\n em,\n [\n () => applyScalarFields(call, input, ingestedAt),\n () => syncParticipants(em, call, input),\n ],\n { transaction: true, label: PHONE_CALLS_CALL_INGEST_COMMAND_ID },\n )\n\n // The module emits its own declared events, so only the indexer is handed to the\n // shared side effects: the query index and the read caches have to see the write.\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine') as DataEngine,\n action: created ? 'created' : 'updated',\n entity: call,\n identifiers: {\n id: call.id,\n organizationId: call.organizationId,\n tenantId: call.tenantId,\n },\n indexer: phoneCallCrudIndexer,\n })\n\n await emitPhoneCallsEvent(\n created ? 'phone_calls.call.ingested' : 'phone_calls.call.updated',\n { id: call.id, organizationId: call.organizationId, tenantId: call.tenantId },\n ).catch(() => undefined)\n\n return { phoneCallId: call.id, created }\n },\n // Calls carry PII (numbers, names, recordings), so the audit entry stays at identifiers\n // and the ingest outcome. No snapshot, and the command is not undoable.\n buildLog: async ({ input, result }) => {\n const { translate } = await resolveTranslations()\n return {\n actionLabel: result.created\n ? translate('phone_calls.audit.calls.ingested', 'Ingest phone call')\n : translate('phone_calls.audit.calls.updated', 'Update ingested phone call'),\n resourceKind: PHONE_CALL_RESOURCE_KIND,\n resourceId: result.phoneCallId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n context: { cacheAliases: PHONE_CALL_CACHE_ALIASES },\n }\n },\n}\n\nregisterCommand(ingestPhoneCallCommand)\n\nexport { ingestPhoneCallCommand }\n"],
5
+ "mappings": "AAAA,SAAS,uBAAuB;AAEhC,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB,yBAAyB;AAG3D,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AAEzC,SAAS,6BAA6B;AACtC,SAAS,SAAS;AAClB,SAAS,WAAW,4BAA4B;AAChD;AAAA,EACE;AAAA,OAEK;AACP,SAAS,2BAA2B;AAE7B,MAAM,qCAAqC;AAGlD,MAAM,uBAAqD;AAAA,EACzD,YAAY,EAAE,YAAY;AAC5B;AAKA,MAAM,2BAA2B,CAAC,UAAU,IAAI;AAIhD,SAAS,kBAAkB,MAAiB,OAA6B,YAAwB;AAC/F,OAAK,gBAAgB,MAAM,iBAAiB;AAC5C,OAAK,yBAAyB,MAAM,0BAA0B;AAC9D,OAAK,YAAY,MAAM;AACvB,OAAK,SAAS,MAAM;AACpB,OAAK,YAAY,MAAM,aAAa;AACpC,OAAK,aAAa,MAAM,cAAc;AACtC,OAAK,UAAU,MAAM,WAAW;AAChC,OAAK,kBAAkB,MAAM,mBAAmB;AAChD,OAAK,eAAe,MAAM,WAAW,OAAO;AAC5C,OAAK,gBAAgB,MAAM,iBAAiB;AAC5C,OAAK,cAAc,MAAM;AACzB,OAAK,eAAe;AACpB,OAAK,iBAAiB;AACxB;AAEA,eAAe,iBACb,IACA,MACA,OACe;AAKf,QAAM,GAAG,aAAa,sBAAsB,EAAE,aAAa,KAAK,GAAG,CAAC;AACpE,aAAW,eAAe,MAAM,cAAc;AAC5C,OAAG,QAAQ,GAAG,OAAO,sBAAsB;AAAA,MACzC,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,MAClB,uBAAuB,YAAY,yBAAyB;AAAA,MAC5D,MAAM,YAAY;AAAA,MAClB,aAAa,YAAY,eAAe;AAAA,MACxC,aAAa,YAAY,eAAe;AAAA,MACxC,OAAO,YAAY,SAAS;AAAA,MAC5B,UAAU,YAAY,YAAY;AAAA,IACpC,CAAC,CAAC;AAAA,EACJ;AACF;AAEA,MAAM,yBAAsF;AAAA,EAC1F,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,QAAQ,sBAAsB,MAAM,QAAQ;AAClD,sBAAkB,KAAK,MAAM,QAAQ;AACrC,4BAAwB,KAAK,MAAM,cAAc;AAEjD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,QACE,aAAa,MAAM;AAAA,QACnB,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,MACxB;AAAA,MACA;AAAA,MACA,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAAA,IACnE;AACA,UAAM,UAAU,CAAC;AACjB,UAAM,OAAO,YAAY,GAAG,OAAO,WAAW;AAAA,MAC5C,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,IAChB,CAAC;AACD,QAAI,QAAS,IAAG,QAAQ,IAAI;AAC5B,UAAM,aAAa,oBAAI,KAAK;AAE5B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM,kBAAkB,MAAM,OAAO,UAAU;AAAA,QAC/C,MAAM,iBAAiB,IAAI,MAAM,KAAK;AAAA,MACxC;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,mCAAmC;AAAA,IACjE;AAIA,UAAM,oBAAoB;AAAA,MACxB,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ,UAAU,YAAY;AAAA,MAC9B,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,KAAK;AAAA,QACT,gBAAgB,KAAK;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAED,UAAM;AAAA,MACJ,UAAU,8BAA8B;AAAA,MACxC,EAAE,IAAI,KAAK,IAAI,gBAAgB,KAAK,gBAAgB,UAAU,KAAK,SAAS;AAAA,IAC9E,EAAE,MAAM,MAAM,MAAS;AAEvB,WAAO,EAAE,aAAa,KAAK,IAAI,QAAQ;AAAA,EACzC;AAAA;AAAA;AAAA,EAGA,UAAU,OAAO,EAAE,OAAO,OAAO,MAAM;AACrC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO;AAAA,MACL,aAAa,OAAO,UAChB,UAAU,oCAAoC,mBAAmB,IACjE,UAAU,mCAAmC,4BAA4B;AAAA,MAC7E,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,SAAS,EAAE,cAAc,yBAAyB;AAAA,IACpD;AAAA,EACF;AACF;AAEA,gBAAgB,sBAAsB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ import "./calls.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/commands/index.ts"],
4
+ "sourcesContent": ["import './calls'\n"],
5
+ "mappings": "AAAA,OAAO;",
6
+ "names": []
7
+ }
@@ -0,0 +1,162 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { OptionalProps } from "@mikro-orm/core";
12
+ import { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from "@mikro-orm/decorators/legacy";
13
+ OptionalProps;
14
+ let PhoneCall = class {
15
+ constructor() {
16
+ this.ingestStatus = "pending";
17
+ this.createdAt = /* @__PURE__ */ new Date();
18
+ this.updatedAt = /* @__PURE__ */ new Date();
19
+ }
20
+ };
21
+ __decorateClass([
22
+ PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
23
+ ], PhoneCall.prototype, "id", 2);
24
+ __decorateClass([
25
+ Property({ name: "organization_id", type: "uuid" })
26
+ ], PhoneCall.prototype, "organizationId", 2);
27
+ __decorateClass([
28
+ Property({ name: "tenant_id", type: "uuid" })
29
+ ], PhoneCall.prototype, "tenantId", 2);
30
+ __decorateClass([
31
+ Property({ name: "provider_key", type: "text" })
32
+ ], PhoneCall.prototype, "providerKey", 2);
33
+ __decorateClass([
34
+ Property({ name: "integration_id", type: "text", nullable: true })
35
+ ], PhoneCall.prototype, "integrationId", 2);
36
+ __decorateClass([
37
+ Property({ name: "external_call_id", type: "text" })
38
+ ], PhoneCall.prototype, "externalCallId", 2);
39
+ __decorateClass([
40
+ Property({ name: "external_conversation_id", type: "text", nullable: true })
41
+ ], PhoneCall.prototype, "externalConversationId", 2);
42
+ __decorateClass([
43
+ Property({ name: "direction", type: "text" })
44
+ ], PhoneCall.prototype, "direction", 2);
45
+ __decorateClass([
46
+ Property({ name: "status", type: "text" })
47
+ ], PhoneCall.prototype, "status", 2);
48
+ __decorateClass([
49
+ Property({ name: "started_at", type: Date, nullable: true })
50
+ ], PhoneCall.prototype, "startedAt", 2);
51
+ __decorateClass([
52
+ Property({ name: "answered_at", type: Date, nullable: true })
53
+ ], PhoneCall.prototype, "answeredAt", 2);
54
+ __decorateClass([
55
+ Property({ name: "ended_at", type: Date, nullable: true })
56
+ ], PhoneCall.prototype, "endedAt", 2);
57
+ __decorateClass([
58
+ Property({ name: "duration_seconds", type: "integer", nullable: true })
59
+ ], PhoneCall.prototype, "durationSeconds", 2);
60
+ __decorateClass([
61
+ Property({ name: "recording_url", type: "text", nullable: true })
62
+ ], PhoneCall.prototype, "recordingUrl", 2);
63
+ __decorateClass([
64
+ Property({ name: "recording_attachment_id", type: "uuid", nullable: true })
65
+ ], PhoneCall.prototype, "recordingAttachmentId", 2);
66
+ __decorateClass([
67
+ Property({ name: "active_transcript_version_id", type: "uuid", nullable: true })
68
+ ], PhoneCall.prototype, "activeTranscriptVersionId", 2);
69
+ __decorateClass([
70
+ Property({ name: "active_summary_version_id", type: "uuid", nullable: true })
71
+ ], PhoneCall.prototype, "activeSummaryVersionId", 2);
72
+ __decorateClass([
73
+ Property({ name: "communication_projection_id", type: "uuid", nullable: true })
74
+ ], PhoneCall.prototype, "communicationProjectionId", 2);
75
+ __decorateClass([
76
+ Property({ name: "provider_facts", type: "jsonb", nullable: true })
77
+ ], PhoneCall.prototype, "providerFacts", 2);
78
+ __decorateClass([
79
+ Property({ name: "raw_snapshot", type: "jsonb", nullable: true })
80
+ ], PhoneCall.prototype, "rawSnapshot", 2);
81
+ __decorateClass([
82
+ Property({ name: "ingest_status", type: "text", default: "pending" })
83
+ ], PhoneCall.prototype, "ingestStatus", 2);
84
+ __decorateClass([
85
+ Property({ name: "last_ingested_at", type: Date, nullable: true })
86
+ ], PhoneCall.prototype, "lastIngestedAt", 2);
87
+ __decorateClass([
88
+ Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
89
+ ], PhoneCall.prototype, "createdAt", 2);
90
+ __decorateClass([
91
+ Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
92
+ ], PhoneCall.prototype, "updatedAt", 2);
93
+ __decorateClass([
94
+ Property({ name: "deleted_at", type: Date, nullable: true })
95
+ ], PhoneCall.prototype, "deletedAt", 2);
96
+ PhoneCall = __decorateClass([
97
+ Entity({ tableName: "phone_calls" }),
98
+ Index({ name: "phone_calls_org_tenant_idx", properties: ["organizationId", "tenantId"] }),
99
+ Unique({
100
+ name: "phone_calls_provider_external_scope_uq",
101
+ properties: ["providerKey", "externalCallId", "tenantId", "organizationId"]
102
+ }),
103
+ Index({ name: "phone_calls_org_started_at_idx", properties: ["organizationId", "startedAt"] }),
104
+ Index({ name: "phone_calls_org_status_idx", properties: ["organizationId", "status"] }),
105
+ Index({
106
+ name: "phone_calls_communication_projection_id_idx",
107
+ expression: 'create index "phone_calls_communication_projection_id_idx" on "phone_calls" ("communication_projection_id") where "communication_projection_id" is not null'
108
+ })
109
+ ], PhoneCall);
110
+ OptionalProps;
111
+ let PhoneCallParticipant = class {
112
+ constructor() {
113
+ this.createdAt = /* @__PURE__ */ new Date();
114
+ this.updatedAt = /* @__PURE__ */ new Date();
115
+ }
116
+ };
117
+ __decorateClass([
118
+ PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
119
+ ], PhoneCallParticipant.prototype, "id", 2);
120
+ __decorateClass([
121
+ Property({ name: "organization_id", type: "uuid" })
122
+ ], PhoneCallParticipant.prototype, "organizationId", 2);
123
+ __decorateClass([
124
+ Property({ name: "tenant_id", type: "uuid" })
125
+ ], PhoneCallParticipant.prototype, "tenantId", 2);
126
+ __decorateClass([
127
+ ManyToOne(() => PhoneCall, { fieldName: "phone_call_id", deleteRule: "cascade", mapToPk: true })
128
+ ], PhoneCallParticipant.prototype, "phoneCallId", 2);
129
+ __decorateClass([
130
+ Property({ name: "provider_participant_id", type: "text", nullable: true })
131
+ ], PhoneCallParticipant.prototype, "providerParticipantId", 2);
132
+ __decorateClass([
133
+ Property({ name: "role", type: "text" })
134
+ ], PhoneCallParticipant.prototype, "role", 2);
135
+ __decorateClass([
136
+ Property({ name: "phone_number", type: "text", nullable: true })
137
+ ], PhoneCallParticipant.prototype, "phoneNumber", 2);
138
+ __decorateClass([
139
+ Property({ name: "display_name", type: "text", nullable: true })
140
+ ], PhoneCallParticipant.prototype, "displayName", 2);
141
+ __decorateClass([
142
+ Property({ name: "email", type: "text", nullable: true })
143
+ ], PhoneCallParticipant.prototype, "email", 2);
144
+ __decorateClass([
145
+ Property({ name: "metadata", type: "jsonb", nullable: true })
146
+ ], PhoneCallParticipant.prototype, "metadata", 2);
147
+ __decorateClass([
148
+ Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
149
+ ], PhoneCallParticipant.prototype, "createdAt", 2);
150
+ __decorateClass([
151
+ Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
152
+ ], PhoneCallParticipant.prototype, "updatedAt", 2);
153
+ PhoneCallParticipant = __decorateClass([
154
+ Entity({ tableName: "phone_call_participants" }),
155
+ Index({ name: "phone_call_participants_call_idx", properties: ["phoneCallId"] }),
156
+ Index({ name: "phone_call_participants_org_tenant_idx", properties: ["organizationId", "tenantId"] })
157
+ ], PhoneCallParticipant);
158
+ export {
159
+ PhoneCall,
160
+ PhoneCallParticipant
161
+ };
162
+ //# sourceMappingURL=entities.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/data/entities.ts"],
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\nimport type {\n PhoneCallDirection,\n PhoneCallStatus,\n PhoneCallParticipantRole,\n} from '@open-mercato/shared/modules/phone_calls/types'\n\nexport type PhoneCallIngestStatus = 'pending' | 'complete' | 'failed'\n\n@Entity({ tableName: 'phone_calls' })\n@Index({ name: 'phone_calls_org_tenant_idx', properties: ['organizationId', 'tenantId'] })\n@Unique({\n name: 'phone_calls_provider_external_scope_uq',\n properties: ['providerKey', 'externalCallId', 'tenantId', 'organizationId'],\n})\n@Index({ name: 'phone_calls_org_started_at_idx', properties: ['organizationId', 'startedAt'] })\n@Index({ name: 'phone_calls_org_status_idx', properties: ['organizationId', 'status'] })\n@Index({\n name: 'phone_calls_communication_projection_id_idx',\n expression:\n 'create index \"phone_calls_communication_projection_id_idx\" on \"phone_calls\" (\"communication_projection_id\") where \"communication_projection_id\" is not null',\n})\nexport class PhoneCall {\n [OptionalProps]?: 'ingestStatus' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'provider_key', type: 'text' })\n providerKey!: string\n\n @Property({ name: 'integration_id', type: 'text', nullable: true })\n integrationId?: string | null\n\n @Property({ name: 'external_call_id', type: 'text' })\n externalCallId!: string\n\n @Property({ name: 'external_conversation_id', type: 'text', nullable: true })\n externalConversationId?: string | null\n\n @Property({ name: 'direction', type: 'text' })\n direction!: PhoneCallDirection\n\n @Property({ name: 'status', type: 'text' })\n status!: PhoneCallStatus\n\n @Property({ name: 'started_at', type: Date, nullable: true })\n startedAt?: Date | null\n\n @Property({ name: 'answered_at', type: Date, nullable: true })\n answeredAt?: Date | null\n\n @Property({ name: 'ended_at', type: Date, nullable: true })\n endedAt?: Date | null\n\n @Property({ name: 'duration_seconds', type: 'integer', nullable: true })\n durationSeconds?: number | null\n\n @Property({ name: 'recording_url', type: 'text', nullable: true })\n recordingUrl?: string | null\n\n @Property({ name: 'recording_attachment_id', type: 'uuid', nullable: true })\n recordingAttachmentId?: string | null\n\n @Property({ name: 'active_transcript_version_id', type: 'uuid', nullable: true })\n activeTranscriptVersionId?: string | null\n\n @Property({ name: 'active_summary_version_id', type: 'uuid', nullable: true })\n activeSummaryVersionId?: string | null\n\n @Property({ name: 'communication_projection_id', type: 'uuid', nullable: true })\n communicationProjectionId?: string | null\n\n @Property({ name: 'provider_facts', type: 'jsonb', nullable: true })\n providerFacts?: Record<string, unknown> | null\n\n @Property({ name: 'raw_snapshot', type: 'jsonb', nullable: true })\n rawSnapshot?: Record<string, unknown> | null\n\n @Property({ name: 'ingest_status', type: 'text', default: 'pending' })\n ingestStatus: PhoneCallIngestStatus = 'pending'\n\n @Property({ name: 'last_ingested_at', type: Date, nullable: true })\n lastIngestedAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'phone_call_participants' })\n@Index({ name: 'phone_call_participants_call_idx', properties: ['phoneCallId'] })\n@Index({ name: 'phone_call_participants_org_tenant_idx', properties: ['organizationId', 'tenantId'] })\nexport class PhoneCallParticipant {\n [OptionalProps]?: 'createdAt' | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @ManyToOne(() => PhoneCall, { fieldName: 'phone_call_id', deleteRule: 'cascade', mapToPk: true })\n phoneCallId!: string\n\n @Property({ name: 'provider_participant_id', type: 'text', nullable: true })\n providerParticipantId?: string | null\n\n @Property({ name: 'role', type: 'text' })\n role!: PhoneCallParticipantRole\n\n @Property({ name: 'phone_number', type: 'text', nullable: true })\n phoneNumber?: string | null\n\n @Property({ name: 'display_name', type: 'text', nullable: true })\n displayName?: string | null\n\n @Property({ name: 'email', type: 'text', nullable: true })\n email?: string | null\n\n @Property({ name: 'metadata', type: 'jsonb', nullable: true })\n metadata?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ const uuid = () => z.string().uuid();
3
+ const scopedSchema = z.object({
4
+ organizationId: uuid(),
5
+ tenantId: uuid()
6
+ });
7
+ const phoneCallDirectionSchema = z.enum([
8
+ "inbound",
9
+ "outbound",
10
+ "internal",
11
+ "unknown"
12
+ ]);
13
+ const phoneCallStatusSchema = z.enum([
14
+ "new",
15
+ "ringing",
16
+ "answered",
17
+ "missed",
18
+ "failed",
19
+ "completed",
20
+ "unknown"
21
+ ]);
22
+ const phoneCallParticipantRoleSchema = z.enum([
23
+ "caller",
24
+ "callee",
25
+ "agent",
26
+ "unknown"
27
+ ]);
28
+ const ingestParticipantSchema = z.object({
29
+ role: phoneCallParticipantRoleSchema,
30
+ providerParticipantId: z.string().nullish(),
31
+ phoneNumber: z.string().nullish(),
32
+ displayName: z.string().nullish(),
33
+ email: z.string().nullish(),
34
+ metadata: z.record(z.string(), z.unknown()).optional()
35
+ });
36
+ const ingestRecordingSchema = z.object({
37
+ url: z.string().nullish(),
38
+ providerRecordingId: z.string().nullish(),
39
+ mimeType: z.string().nullish(),
40
+ durationSeconds: z.number().int().nullish(),
41
+ metadata: z.record(z.string(), z.unknown()).optional()
42
+ });
43
+ const ingestPhoneCallSchema = scopedSchema.extend({
44
+ providerKey: z.string().min(1),
45
+ integrationId: z.string().nullish(),
46
+ externalCallId: z.string().min(1),
47
+ externalConversationId: z.string().nullish(),
48
+ direction: phoneCallDirectionSchema,
49
+ status: phoneCallStatusSchema,
50
+ participants: z.array(ingestParticipantSchema).default([]),
51
+ recording: ingestRecordingSchema.nullish(),
52
+ startedAt: z.coerce.date().nullish(),
53
+ answeredAt: z.coerce.date().nullish(),
54
+ endedAt: z.coerce.date().nullish(),
55
+ durationSeconds: z.number().int().nullish(),
56
+ providerFacts: z.record(z.string(), z.unknown()).optional(),
57
+ rawPayload: z.record(z.string(), z.unknown())
58
+ });
59
+ export {
60
+ ingestParticipantSchema,
61
+ ingestPhoneCallSchema,
62
+ ingestRecordingSchema,
63
+ phoneCallDirectionSchema,
64
+ phoneCallParticipantRoleSchema,
65
+ phoneCallStatusSchema,
66
+ scopedSchema
67
+ };
68
+ //# sourceMappingURL=validators.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/data/validators.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\n\nconst uuid = () => z.string().uuid()\n\nexport const scopedSchema = z.object({\n organizationId: uuid(),\n tenantId: uuid(),\n})\n\nexport const phoneCallDirectionSchema = z.enum([\n 'inbound',\n 'outbound',\n 'internal',\n 'unknown',\n])\n\nexport const phoneCallStatusSchema = z.enum([\n 'new',\n 'ringing',\n 'answered',\n 'missed',\n 'failed',\n 'completed',\n 'unknown',\n])\n\nexport const phoneCallParticipantRoleSchema = z.enum([\n 'caller',\n 'callee',\n 'agent',\n 'unknown',\n])\n\nexport const ingestParticipantSchema = z.object({\n role: phoneCallParticipantRoleSchema,\n providerParticipantId: z.string().nullish(),\n phoneNumber: z.string().nullish(),\n displayName: z.string().nullish(),\n email: z.string().nullish(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const ingestRecordingSchema = z.object({\n url: z.string().nullish(),\n providerRecordingId: z.string().nullish(),\n mimeType: z.string().nullish(),\n durationSeconds: z.number().int().nullish(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const ingestPhoneCallSchema = scopedSchema.extend({\n providerKey: z.string().min(1),\n integrationId: z.string().nullish(),\n externalCallId: z.string().min(1),\n externalConversationId: z.string().nullish(),\n direction: phoneCallDirectionSchema,\n status: phoneCallStatusSchema,\n participants: z.array(ingestParticipantSchema).default([]),\n recording: ingestRecordingSchema.nullish(),\n startedAt: z.coerce.date().nullish(),\n answeredAt: z.coerce.date().nullish(),\n endedAt: z.coerce.date().nullish(),\n durationSeconds: z.number().int().nullish(),\n providerFacts: z.record(z.string(), z.unknown()).optional(),\n rawPayload: z.record(z.string(), z.unknown()),\n})\n\nexport type IngestPhoneCallInput = z.infer<typeof ingestPhoneCallSchema>\nexport type IngestParticipantInput = z.infer<typeof ingestParticipantSchema>\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAElB,MAAM,OAAO,MAAM,EAAE,OAAO,EAAE,KAAK;AAE5B,MAAM,eAAe,EAAE,OAAO;AAAA,EACnC,gBAAgB,KAAK;AAAA,EACrB,UAAU,KAAK;AACjB,CAAC;AAEM,MAAM,2BAA2B,EAAE,KAAK;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,wBAAwB,EAAE,KAAK;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,iCAAiC,EAAE,KAAK;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,MAAM;AAAA,EACN,uBAAuB,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC1C,aAAa,EAAE,OAAO,EAAE,QAAQ;AAAA,EAChC,aAAa,EAAE,OAAO,EAAE,QAAQ;AAAA,EAChC,OAAO,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AACvD,CAAC;AAEM,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,KAAK,EAAE,OAAO,EAAE,QAAQ;AAAA,EACxB,qBAAqB,EAAE,OAAO,EAAE,QAAQ;AAAA,EACxC,UAAU,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC7B,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ;AAAA,EAC1C,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AACvD,CAAC;AAEM,MAAM,wBAAwB,aAAa,OAAO;AAAA,EACvD,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,QAAQ;AAAA,EAClC,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAChC,wBAAwB,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC3C,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,cAAc,EAAE,MAAM,uBAAuB,EAAE,QAAQ,CAAC,CAAC;AAAA,EACzD,WAAW,sBAAsB,QAAQ;AAAA,EACzC,WAAW,EAAE,OAAO,KAAK,EAAE,QAAQ;AAAA,EACnC,YAAY,EAAE,OAAO,KAAK,EAAE,QAAQ;AAAA,EACpC,SAAS,EAAE,OAAO,KAAK,EAAE,QAAQ;AAAA,EACjC,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ;AAAA,EAC1C,eAAe,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,EAC1D,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAC9C,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { asValue } from "awilix";
2
+ import { PhoneCall, PhoneCallParticipant } from "./data/entities.js";
3
+ function register(container) {
4
+ container.register({
5
+ PhoneCall: asValue(PhoneCall),
6
+ PhoneCallParticipant: asValue(PhoneCallParticipant)
7
+ });
8
+ }
9
+ export {
10
+ register
11
+ };
12
+ //# sourceMappingURL=di.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/phone_calls/di.ts"],
4
+ "sourcesContent": ["import { asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { PhoneCall, PhoneCallParticipant } from './data/entities'\n\nexport function register(container: AppContainer) {\n container.register({\n PhoneCall: asValue(PhoneCall),\n PhoneCallParticipant: asValue(PhoneCallParticipant),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe;AAExB,SAAS,WAAW,4BAA4B;AAEzC,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,WAAW,QAAQ,SAAS;AAAA,IAC5B,sBAAsB,QAAQ,oBAAoB;AAAA,EACpD,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ const defaultEncryptionMaps = [
2
+ {
3
+ entityId: "phone_calls:phone_call",
4
+ fields: [
5
+ // raw_snapshot and provider_facts hold the untouched Tillio payload, which
6
+ // carries caller/destination numbers (PII) that we already encrypt on the
7
+ // participant. recording_url points at the call recording. QueryEngine
8
+ // decrypts these transparently on read.
9
+ { field: "raw_snapshot" },
10
+ { field: "provider_facts" },
11
+ { field: "recording_url" }
12
+ ]
13
+ },
14
+ {
15
+ entityId: "phone_calls:phone_call_participant",
16
+ fields: [
17
+ { field: "phone_number" },
18
+ { field: "display_name" },
19
+ { field: "email" }
20
+ ]
21
+ }
22
+ ];
23
+ var encryption_default = defaultEncryptionMaps;
24
+ export {
25
+ encryption_default as default,
26
+ defaultEncryptionMaps
27
+ };
28
+ //# sourceMappingURL=encryption.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/phone_calls/encryption.ts"],
4
+ "sourcesContent": ["import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'\n\nexport const defaultEncryptionMaps: ModuleEncryptionMap[] = [\n {\n entityId: 'phone_calls:phone_call',\n fields: [\n // raw_snapshot and provider_facts hold the untouched Tillio payload, which\n // carries caller/destination numbers (PII) that we already encrypt on the\n // participant. recording_url points at the call recording. QueryEngine\n // decrypts these transparently on read.\n { field: 'raw_snapshot' },\n { field: 'provider_facts' },\n { field: 'recording_url' },\n ],\n },\n {\n entityId: 'phone_calls:phone_call_participant',\n fields: [\n { field: 'phone_number' },\n { field: 'display_name' },\n { field: 'email' },\n ],\n },\n]\n\nexport default defaultEncryptionMaps\n"],
5
+ "mappings": "AAEO,MAAM,wBAA+C;AAAA,EAC1D;AAAA,IACE,UAAU;AAAA,IACV,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,MAKN,EAAE,OAAO,eAAe;AAAA,MACxB,EAAE,OAAO,iBAAiB;AAAA,MAC1B,EAAE,OAAO,gBAAgB;AAAA,IAC3B;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,QAAQ;AAAA,MACN,EAAE,OAAO,eAAe;AAAA,MACxB,EAAE,OAAO,eAAe;AAAA,MACxB,EAAE,OAAO,QAAQ;AAAA,IACnB;AAAA,EACF;AACF;AAEA,IAAO,qBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import { createModuleEvents } from "@open-mercato/shared/modules/events";
2
+ const events = [
3
+ { id: "phone_calls.call.ingested", label: "Phone Call Ingested", entity: "call", category: "crud" },
4
+ { id: "phone_calls.call.updated", label: "Phone Call Updated", entity: "call", category: "crud" },
5
+ { id: "phone_calls.call.ingest_failed", label: "Phone Call Ingest Failed", entity: "call", category: "crud" }
6
+ ];
7
+ const eventsConfig = createModuleEvents({
8
+ moduleId: "phone_calls",
9
+ events
10
+ });
11
+ const emitPhoneCallsEvent = eventsConfig.emit;
12
+ var events_default = eventsConfig;
13
+ export {
14
+ events_default as default,
15
+ emitPhoneCallsEvent,
16
+ eventsConfig
17
+ };
18
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/phone_calls/events.ts"],
4
+ "sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * Phone Calls Module Events\n *\n * Declares all events that can be emitted by the phone_calls module.\n */\nconst events = [\n { id: 'phone_calls.call.ingested', label: 'Phone Call Ingested', entity: 'call', category: 'crud' },\n { id: 'phone_calls.call.updated', label: 'Phone Call Updated', entity: 'call', category: 'crud' },\n { id: 'phone_calls.call.ingest_failed', label: 'Phone Call Ingest Failed', entity: 'call', category: 'crud' },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'phone_calls',\n events,\n})\n\n/** Type-safe event emitter for phone_calls module */\nexport const emitPhoneCallsEvent = eventsConfig.emit\n\n/** Event IDs that can be emitted by the phone_calls module */\nexport type PhoneCallsEventId = typeof events[number]['id']\n\nexport default eventsConfig\n"],
5
+ "mappings": "AAAA,SAAS,0BAA0B;AAOnC,MAAM,SAAS;AAAA,EACb,EAAE,IAAI,6BAA6B,OAAO,uBAAuB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EAClG,EAAE,IAAI,4BAA4B,OAAO,sBAAsB,QAAQ,QAAQ,UAAU,OAAO;AAAA,EAChG,EAAE,IAAI,kCAAkC,OAAO,4BAA4B,QAAQ,QAAQ,UAAU,OAAO;AAC9G;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAGM,MAAM,sBAAsB,aAAa;AAKhD,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,15 @@
1
+ const metadata = {
2
+ name: "phone_calls",
3
+ title: "Phone Calls",
4
+ version: "0.1.0",
5
+ description: "Provider-neutral core hub for ingesting and browsing phone calls.",
6
+ author: "Open Mercato Team",
7
+ license: "MIT",
8
+ ejectable: true
9
+ };
10
+ import { features } from "./acl.js";
11
+ export {
12
+ features,
13
+ metadata
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/phone_calls/index.ts"],
4
+ "sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'phone_calls',\n title: 'Phone Calls',\n version: '0.1.0',\n description: 'Provider-neutral core hub for ingesting and browsing phone calls.',\n author: 'Open Mercato Team',\n license: 'MIT',\n ejectable: true,\n}\n\nexport { features } from './acl'\n"],
5
+ "mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AACb;AAEA,SAAS,gBAAgB;",
6
+ "names": []
7
+ }