@open-mercato/core 0.6.8-develop.7051.1.2e20648fd5 → 0.6.8-develop.7056.1.1a563a270d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/communicationChannelsFixtures.js +19 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -1
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +95 -10
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +5 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
- package/dist/modules/communication_channels/di.js +7 -1
- package/dist/modules/communication_channels/di.js.map +2 -2
- package/dist/modules/communication_channels/lib/email-capabilities.js +4 -1
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +2 -2
- package/dist/modules/communication_channels/lib/outbound-recipient.js +33 -0
- package/dist/modules/communication_channels/lib/outbound-recipient.js.map +7 -0
- package/dist/modules/communication_channels/lib/resolve-channel-type.js +51 -0
- package/dist/modules/communication_channels/lib/resolve-channel-type.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +41 -2
- package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
- package/dist/modules/customers/api/people/check-phone/route.js +51 -9
- package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
- package/dist/modules/messages/api/openapi.js +2 -0
- package/dist/modules/messages/api/openapi.js.map +2 -2
- package/dist/modules/messages/api/route.js +17 -2
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/data/validators.js +18 -4
- package/dist/modules/messages/data/validators.js.map +2 -2
- package/dist/modules/messages/lib/channel-sender-identity.js +26 -0
- package/dist/modules/messages/lib/channel-sender-identity.js.map +7 -0
- package/dist/modules/messages/lib/composeSourceChannelType.js +41 -0
- package/dist/modules/messages/lib/composeSourceChannelType.js.map +7 -0
- package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
- package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/communicationChannelsFixtures.ts +69 -1
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +17 -1
- package/src/modules/communication_channels/api/post/test-seed/route.ts +155 -16
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +5 -0
- package/src/modules/communication_channels/di.ts +7 -0
- package/src/modules/communication_channels/lib/adapter.ts +14 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +4 -0
- package/src/modules/communication_channels/lib/outbound-recipient.ts +71 -0
- package/src/modules/communication_channels/lib/resolve-channel-type.ts +99 -0
- package/src/modules/communication_channels/lib/test-seed.ts +64 -4
- package/src/modules/customers/api/people/check-phone/route.ts +75 -10
- package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
- package/src/modules/messages/api/openapi.ts +2 -0
- package/src/modules/messages/api/route.ts +31 -2
- package/src/modules/messages/data/validators.ts +43 -4
- package/src/modules/messages/lib/channel-sender-identity.ts +55 -0
- package/src/modules/messages/lib/composeSourceChannelType.ts +95 -0
- package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/people/check-phone/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerEntity } from '../../../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst querySchema = z.object({\n digits: z\n .string()\n .regex(/^\\d{4,}$/)\n .transform((value) => value.trim()),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const rawQuery: Record<string, string | null> = { digits: url.searchParams.get('digits') }\n const parse = querySchema.safeParse(rawQuery)\n\n if (!parse.success) {\n return NextResponse.json({ match: null })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = (container.resolve('em') as EntityManager)\n\n const allowedOrgIds = new Set<string>()\n if (scope?.selectedId) allowedOrgIds.add(scope.selectedId)\n if (scope?.filterIds?.length) scope.filterIds.forEach((id) => allowedOrgIds.add(id))\n if (!allowedOrgIds.size && auth.orgId) allowedOrgIds.add(auth.orgId)\n\n if (allowedOrgIds.size === 0) {\n return NextResponse.json({ match: null })\n }\n\n const qb = em.createQueryBuilder(CustomerEntity, 'person')\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,sBAAsB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { CustomerEntity } from '../../../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { MATCH_CANDIDATE_LIMIT } from '../../../lib/findPeopleByAddresses'\n\nconst querySchema = z.object({\n digits: z\n .string()\n .regex(/^\\d{4,}$/)\n .transform((value) => value.trim()),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },\n}\n\nfunction normalizePhoneDigits(value: string | null | undefined): string {\n return typeof value === 'string' ? value.replace(/\\D/g, '') : ''\n}\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const rawQuery: Record<string, string | null> = { digits: url.searchParams.get('digits') }\n const parse = querySchema.safeParse(rawQuery)\n\n if (!parse.success) {\n return NextResponse.json({ match: null })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = (container.resolve('em') as EntityManager)\n\n const allowedOrgIds = new Set<string>()\n if (scope?.selectedId) allowedOrgIds.add(scope.selectedId)\n if (scope?.filterIds?.length) scope.filterIds.forEach((id) => allowedOrgIds.add(id))\n if (!allowedOrgIds.size && auth.orgId) allowedOrgIds.add(auth.orgId)\n\n if (allowedOrgIds.size === 0) {\n return NextResponse.json({ match: null })\n }\n\n // SQL fast path, gated on the encryption toggle: it can only match stored\n // plaintext digits, so running it while tenant data encryption is enabled\n // (the default) would pay a per-row scan that provably returns nothing.\n // When encryption is off it serves the pre-encryption single-query behavior,\n // including legacy plaintext rows. Encrypted rows written before the toggle\n // was turned off are not resolvable by either path by design; plaintext\n // legacy rows under an enabled toggle still resolve through the decrypted\n // scan below, where decryption no-ops on non-ciphertext values.\n if (!isTenantDataEncryptionEnabled()) {\n const qb = em.createQueryBuilder(CustomerEntity, 'person')\n qb.select(['person.id', 'person.displayName'])\n qb.where({ kind: 'person', deletedAt: null })\n qb.andWhere('person.primary_phone is not null')\n qb.andWhere(\"regexp_replace(person.primary_phone, '\\\\D', '', 'g') = ?\", [parse.data.digits])\n if (auth.tenantId) {\n qb.andWhere({ tenantId: auth.tenantId })\n }\n qb.andWhere({ organizationId: { $in: Array.from(allowedOrgIds) } })\n qb.limit(1)\n\n const fastMatch = await qb.getSingleResult()\n if (fastMatch) {\n return NextResponse.json({\n match: {\n id: fastMatch.id,\n displayName: fastMatch.displayName,\n },\n })\n }\n }\n\n // Encryption-on path: primary_phone holds random-IV ciphertext, so digit\n // normalization must run in application code over decrypted rows instead of\n // SQL against the stored column (issue #3840).\n // Encryption-on path: primary_phone holds random-IV ciphertext, so digit\n // normalization must run in application code over decrypted rows instead of\n // SQL against the stored column (issue #3840). The scan is bounded newest-\n // first and projected to the columns the route uses plus tenantId/\n // organizationId, which the decryption path reads per row to resolve each\n // row's own key (a request-level fallback cannot cover multi-org scans or\n // null-tenant sessions). A primary_phone blind index is the exact O(1)\n // follow-up (#5515).\n const where: FilterQuery<CustomerEntity> = {\n kind: 'person',\n deletedAt: null,\n primaryPhone: { $ne: null },\n organizationId: { $in: Array.from(allowedOrgIds) },\n }\n if (auth.tenantId) {\n where.tenantId = auth.tenantId\n }\n\n const candidates = await findWithDecryption(\n em,\n CustomerEntity,\n where,\n {\n limit: MATCH_CANDIDATE_LIMIT,\n orderBy: { createdAt: 'DESC' },\n fields: ['id', 'displayName', 'primaryPhone', 'tenantId', 'organizationId'],\n },\n {\n tenantId: auth.tenantId ?? null,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n },\n )\n\n const match = candidates.find(\n (candidate) => normalizePhoneDigits(candidate.primaryPhone) === parse.data.digits,\n )\n if (!match) {\n return NextResponse.json({ match: null })\n }\n\n return NextResponse.json({\n match: {\n id: match.id,\n displayName: match.displayName,\n },\n })\n}\n\nconst phoneCheckSuccessSchema = z.object({\n match: z\n .object({\n id: z.string().uuid(),\n displayName: z.string().nullable(),\n })\n .nullable(),\n})\n\nconst phoneCheckErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Check customer phone number',\n methods: {\n GET: {\n summary: 'Find person by phone digits',\n description: 'Performs an exact digits comparison (stripping non-numeric characters) to determine whether a customer contact matches the provided phone fragment.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Matching contact (if any)', schema: phoneCheckSuccessSchema },\n { status: 401, description: 'Unauthorized', schema: phoneCheckErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/lib/findPeopleByAddresses.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerEntity } from '../data/entities'\n\n/**\n * Lower-cases, trims, and dedupes a list of email-shaped strings.\n * Rejects anything that doesn't look like `x@y` (single `@`, not at the start\n * or end, no consecutive `@`).\n */\nexport function normalizeAddresses(input: unknown): string[] {\n if (!Array.isArray(input)) return []\n const seen = new Set<string>()\n const out: string[] = []\n for (const value of input) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim().toLowerCase()\n if (!trimmed) continue\n const at = trimmed.indexOf('@')\n if (at <= 0 || at === trimmed.length - 1 || trimmed.lastIndexOf('@') !== at) continue\n if (seen.has(trimmed)) continue\n seen.add(trimmed)\n out.push(trimmed)\n }\n return out\n}\n\nexport interface MatchedPerson {\n /** customer_entities.id (the anchor for customer_interactions.entity_id). */\n id: string\n /** Lowercased email address. */\n email: string\n}\n\n/**\n * Max
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAOxB,SAAS,mBAAmB,OAA0B;AAC3D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,OAAO;AACzB,QAAI,OAAO,UAAU,SAAU;AAC/B,UAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,QAAI,CAAC,QAAS;AACd,UAAM,KAAK,QAAQ,QAAQ,GAAG;AAC9B,QAAI,MAAM,KAAK,OAAO,QAAQ,SAAS,KAAK,QAAQ,YAAY,GAAG,MAAM,GAAI;AAC7E,QAAI,KAAK,IAAI,OAAO,EAAG;AACvB,SAAK,IAAI,OAAO;AAChB,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,SAAO;AACT;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerEntity } from '../data/entities'\n\n/**\n * Lower-cases, trims, and dedupes a list of email-shaped strings.\n * Rejects anything that doesn't look like `x@y` (single `@`, not at the start\n * or end, no consecutive `@`).\n */\nexport function normalizeAddresses(input: unknown): string[] {\n if (!Array.isArray(input)) return []\n const seen = new Set<string>()\n const out: string[] = []\n for (const value of input) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim().toLowerCase()\n if (!trimmed) continue\n const at = trimmed.indexOf('@')\n if (at <= 0 || at === trimmed.length - 1 || trimmed.lastIndexOf('@') !== at) continue\n if (seen.has(trimmed)) continue\n seen.add(trimmed)\n out.push(trimmed)\n }\n return out\n}\n\nexport interface MatchedPerson {\n /** customer_entities.id (the anchor for customer_interactions.entity_id). */\n id: string\n /** Lowercased email address. */\n email: string\n}\n\n/**\n * Max rows scanned by in-memory fallback matches over encrypted contact\n * columns (`primary_email` here, `primary_phone` in the check-phone route).\n * These columns are GDPR-encrypted with a random IV (see\n * `customers/encryption.ts`), so they cannot be filtered by value in SQL when\n * tenant data encryption is on \u2014 fallbacks decrypt recent rows and compare in\n * memory instead. Bounded to keep interactive lookups cheap; a blind-index\n * (hash) column per field is the follow-up if tenants outgrow it (#5515).\n */\nexport const MATCH_CANDIDATE_LIMIT = 500\n\n/**\n * Batch lookup of CustomerEntity rows (kind='person') whose `primaryEmail`\n * matches any of the given addresses (case-insensitive), scoped to the tenant\n * and organization. Returns at most one MatchedPerson per resolved person.\n *\n * `primary_email` is encrypted with a non-deterministic IV, so a `WHERE\n * primary_email = ?` filter silently matches nothing when encryption is on. We\n * therefore try a direct equality match first (the fast path when encryption is\n * off) and, for any address it leaves unresolved, fall back to scanning recent\n * decrypted person rows and comparing in memory \u2014 the same dual-mode pattern as\n * `inbox-actions.ts`.\n */\nexport async function findPeopleByAddresses(\n em: EntityManager,\n addresses: string[],\n tenantId: string,\n organizationId: string | null = null,\n): Promise<MatchedPerson[]> {\n const normalized = normalizeAddresses(addresses)\n if (normalized.length === 0) return []\n if (!organizationId) return []\n const dscope = { tenantId, organizationId }\n const resolved = new Map<string, string>()\n\n // Fast path: direct equality match. Matches exactly when tenant data encryption\n // is off; returns nothing against encrypted (random-IV) ciphertext, which the\n // in-memory fallback below covers.\n const direct = (await findWithDecryption(\n em,\n CustomerEntity,\n { primaryEmail: { $in: normalized }, kind: 'person', tenantId, organizationId, deletedAt: null },\n undefined,\n dscope,\n )) as Array<{ id: string; primaryEmail?: string | null }>\n for (const row of direct) {\n const rowEmail = row.primaryEmail?.trim().toLowerCase()\n if (rowEmail && !resolved.has(rowEmail)) resolved.set(rowEmail, row.id)\n }\n\n // Fallback for unresolved addresses (the encryption-on path): scan recent person\n // rows and compare decrypted emails in memory.\n if (normalized.some((email) => !resolved.has(email))) {\n const candidates = (await findWithDecryption(\n em,\n CustomerEntity,\n { kind: 'person', tenantId, organizationId, deletedAt: null },\n { limit: MATCH_CANDIDATE_LIMIT, orderBy: { createdAt: 'DESC' } },\n dscope,\n )) as Array<{ id: string; primaryEmail?: string | null }>\n for (const row of candidates) {\n const rowEmail = row.primaryEmail?.trim().toLowerCase()\n if (rowEmail && !resolved.has(rowEmail)) resolved.set(rowEmail, row.id)\n }\n }\n\n const seen = new Set<string>()\n const out: MatchedPerson[] = []\n for (const email of normalized) {\n const id = resolved.get(email)\n if (!id || seen.has(id)) continue\n seen.add(id)\n out.push({ id, email })\n }\n return out\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAOxB,SAAS,mBAAmB,OAA0B;AAC3D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,OAAO;AACzB,QAAI,OAAO,UAAU,SAAU;AAC/B,UAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,QAAI,CAAC,QAAS;AACd,UAAM,KAAK,QAAQ,QAAQ,GAAG;AAC9B,QAAI,MAAM,KAAK,OAAO,QAAQ,SAAS,KAAK,QAAQ,YAAY,GAAG,MAAM,GAAI;AAC7E,QAAI,KAAK,IAAI,OAAO,EAAG;AACvB,SAAK,IAAI,OAAO;AAChB,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,SAAO;AACT;AAkBO,MAAM,wBAAwB;AAcrC,eAAsB,sBACpB,IACA,WACA,UACA,iBAAgC,MACN;AAC1B,QAAM,aAAa,mBAAmB,SAAS;AAC/C,MAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AACrC,MAAI,CAAC,eAAgB,QAAO,CAAC;AAC7B,QAAM,SAAS,EAAE,UAAU,eAAe;AAC1C,QAAM,WAAW,oBAAI,IAAoB;AAKzC,QAAM,SAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,EAAE,cAAc,EAAE,KAAK,WAAW,GAAG,MAAM,UAAU,UAAU,gBAAgB,WAAW,KAAK;AAAA,IAC/F;AAAA,IACA;AAAA,EACF;AACA,aAAW,OAAO,QAAQ;AACxB,UAAM,WAAW,IAAI,cAAc,KAAK,EAAE,YAAY;AACtD,QAAI,YAAY,CAAC,SAAS,IAAI,QAAQ,EAAG,UAAS,IAAI,UAAU,IAAI,EAAE;AAAA,EACxE;AAIA,MAAI,WAAW,KAAK,CAAC,UAAU,CAAC,SAAS,IAAI,KAAK,CAAC,GAAG;AACpD,UAAM,aAAc,MAAM;AAAA,MACxB;AAAA,MACA;AAAA,MACA,EAAE,MAAM,UAAU,UAAU,gBAAgB,WAAW,KAAK;AAAA,MAC5D,EAAE,OAAO,uBAAuB,SAAS,EAAE,WAAW,OAAO,EAAE;AAAA,MAC/D;AAAA,IACF;AACA,eAAW,OAAO,YAAY;AAC5B,YAAM,WAAW,IAAI,cAAc,KAAK,EAAE,YAAY;AACtD,UAAI,YAAY,CAAC,SAAS,IAAI,QAAQ,EAAG,UAAS,IAAI,UAAU,IAAI,EAAE;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAuB,CAAC;AAC9B,aAAW,SAAS,YAAY;AAC9B,UAAM,KAAK,SAAS,IAAI,KAAK;AAC7B,QAAI,CAAC,MAAM,KAAK,IAAI,EAAE,EAAG;AACzB,SAAK,IAAI,EAAE;AACX,QAAI,KAAK,EAAE,IAAI,MAAM,CAAC;AAAA,EACxB;AACA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
3
|
attachmentIdsPayloadSchema,
|
|
4
|
+
composeMessageRequestSchema,
|
|
4
5
|
composeMessageSchema,
|
|
5
6
|
executeActionSchema,
|
|
6
7
|
forwardMessageSchema,
|
|
@@ -251,6 +252,7 @@ const messageObjectTypeListResponseSchema = z.object({
|
|
|
251
252
|
export {
|
|
252
253
|
actionResultResponseSchema,
|
|
253
254
|
attachmentIdsPayloadSchema,
|
|
255
|
+
composeMessageRequestSchema,
|
|
254
256
|
composeMessageSchema,
|
|
255
257
|
composeResponseSchema,
|
|
256
258
|
conversationMutationResponseSchema,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/api/openapi.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport {\n attachmentIdsPayloadSchema,\n composeMessageSchema,\n executeActionSchema,\n forwardMessageSchema,\n listMessagesSchema,\n messageObjectTypesQuerySchema,\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n replyMessageSchema,\n unlinkAttachmentPayloadSchema,\n updateDraftSchema,\n} from '../data/validators'\n\nexport const messageAttachmentSchema = z.object({\n id: z.string().uuid(),\n fileName: z.string(),\n fileSize: z.number(),\n mimeType: z.string(),\n url: z.string(),\n})\n\nexport const messageListItemSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n subject: z.string(),\n bodyPreview: z.string(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n priority: z.string(),\n status: z.string(),\n hasObjects: z.boolean(),\n objectCount: z.number(),\n hasAttachments: z.boolean(),\n attachmentCount: z.number(),\n recipientCount: z.number(),\n hasActions: z.boolean(),\n actionTaken: z.string().nullable().optional(),\n sentAt: z.string().nullable().optional(),\n readAt: z.string().nullable().optional(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const enrichmentMetaSchema = z.object({\n enrichedBy: z.array(z.string()),\n enricherErrors: z.array(z.string()).optional(),\n})\n\nexport const messageThreadItemSchema = z.object({\n id: z.string().uuid(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']).optional(),\n sentAt: z.string().nullable().optional(),\n})\n\nexport const messageDetailResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string().nullable().optional(),\n type: z.string(),\n isDraft: z.boolean(),\n canEditDraft: z.boolean(),\n canArchive: z.boolean(),\n isArchived: z.boolean(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n typeDefinition: z.object({\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().optional().nullable(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n }),\n threadId: z.string().uuid().nullable().optional(),\n parentMessageId: z.string().uuid().nullable().optional(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.string(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n recipients: z.array(z.object({\n userId: z.string().uuid(),\n type: z.enum(['to', 'cc', 'bcc']),\n status: z.string(),\n readAt: z.string().nullable().optional(),\n })),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n preview: z.object({\n title: z.string(),\n subtitle: z.string().optional(),\n status: z.string().optional(),\n statusColor: z.string().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n }).nullable().optional(),\n })),\n thread: z.array(messageThreadItemSchema),\n isRead: z.boolean(),\n conversationArchived: z.boolean(),\n conversationAllUnread: z.boolean(),\n _channel: z.record(z.string(), z.unknown()).nullable().optional(),\n _channelPayload: z.record(z.string(), z.unknown()).nullable().optional(),\n _channelContact: z.record(z.string(), z.unknown()).nullable().optional(),\n _reactions: z.array(z.record(z.string(), z.unknown())).optional(),\n _meta: enrichmentMetaSchema.optional(),\n})\n // The detail route runs response enrichers for `messages.message`, so any\n // module registering one may contribute further `_`-prefixed keys. Without\n // this the generator emits `additionalProperties: false` and the published\n // spec forbids the very fields the route returns.\n .passthrough()\n\nexport const messageTokenDetailResponseSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.enum(['low', 'normal', 'high', 'urgent']),\n senderUserId: z.string().uuid(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n })),\n requiresAuth: z.boolean(),\n recipientUserId: z.string().uuid(),\n})\n\nexport const messageTokenPreflightResponseSchema = z.object({\n requiresAuth: z.literal(true),\n})\n\nexport const messageTokenResponseSchema = z.union([\n messageTokenDetailResponseSchema,\n messageTokenPreflightResponseSchema,\n])\n\nexport const unreadCountResponseSchema = z.object({\n unreadCount: z.number(),\n})\n\nexport const messageConfirmationResponseSchema = z.object({\n messageId: z.string().uuid(),\n confirmed: z.boolean(),\n confirmedAt: z.string().nullable(),\n confirmedByUserId: z.string().uuid().nullable(),\n})\n\nexport const actionResultResponseSchema = z.object({\n ok: z.boolean(),\n actionId: z.string(),\n result: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const forwardResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const forwardPreviewResponseSchema = z.object({\n subject: z.string(),\n body: z.string(),\n})\n\nexport const conversationMutationResponseSchema = z.object({\n ok: z.boolean(),\n affectedCount: z.number().int().min(0),\n})\n\nexport const composeResponseSchema = z.object({\n id: z.string().uuid(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const messageAttachmentResponseSchema = z.object({\n attachments: z.array(messageAttachmentSchema),\n})\n\nexport const okResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid().optional(),\n message: z.string().optional(),\n})\n\nexport const errorResponseSchema = z.object({\n code: z.string().optional(),\n error: z.string(),\n})\n\nexport const messageTypeResponseItemSchema = z.object({\n type: z.string(),\n module: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().nullable().optional(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n actionsExpireAfterHours: z.number().int().nullable().optional(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n})\n\nexport const messageTypeListResponseSchema = z.object({\n items: z.array(messageTypeResponseItemSchema),\n})\n\nexport const messageObjectTypeActionResponseSchema = z.object({\n id: z.string(),\n labelKey: z.string(),\n variant: z.enum(['default', 'secondary', 'destructive', 'outline']).optional(),\n icon: z.string().optional(),\n commandId: z.string().optional(),\n href: z.string().optional(),\n isTerminal: z.boolean().optional(),\n confirmRequired: z.boolean().optional(),\n confirmMessage: z.string().optional(),\n})\n\nexport const messageObjectTypeResponseItemSchema = z.object({\n module: z.string(),\n entityType: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n actions: z.array(messageObjectTypeActionResponseSchema),\n})\n\nexport const messageObjectTypeListResponseSchema = z.object({\n items: z.array(messageObjectTypeResponseItemSchema),\n})\n\nexport {\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n composeMessageSchema,\n listMessagesSchema,\n forwardMessageSchema,\n replyMessageSchema,\n updateDraftSchema,\n executeActionSchema,\n messageObjectTypesQuerySchema,\n attachmentIdsPayloadSchema,\n unlinkAttachmentPayloadSchema,\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport {\n attachmentIdsPayloadSchema,\n composeMessageRequestSchema,\n composeMessageSchema,\n executeActionSchema,\n forwardMessageSchema,\n listMessagesSchema,\n messageObjectTypesQuerySchema,\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n replyMessageSchema,\n unlinkAttachmentPayloadSchema,\n updateDraftSchema,\n} from '../data/validators'\n\nexport const messageAttachmentSchema = z.object({\n id: z.string().uuid(),\n fileName: z.string(),\n fileSize: z.number(),\n mimeType: z.string(),\n url: z.string(),\n})\n\nexport const messageListItemSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n subject: z.string(),\n bodyPreview: z.string(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n priority: z.string(),\n status: z.string(),\n hasObjects: z.boolean(),\n objectCount: z.number(),\n hasAttachments: z.boolean(),\n attachmentCount: z.number(),\n recipientCount: z.number(),\n hasActions: z.boolean(),\n actionTaken: z.string().nullable().optional(),\n sentAt: z.string().nullable().optional(),\n readAt: z.string().nullable().optional(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const enrichmentMetaSchema = z.object({\n enrichedBy: z.array(z.string()),\n enricherErrors: z.array(z.string()).optional(),\n})\n\nexport const messageThreadItemSchema = z.object({\n id: z.string().uuid(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']).optional(),\n sentAt: z.string().nullable().optional(),\n})\n\nexport const messageDetailResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string().nullable().optional(),\n type: z.string(),\n isDraft: z.boolean(),\n canEditDraft: z.boolean(),\n canArchive: z.boolean(),\n isArchived: z.boolean(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n typeDefinition: z.object({\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().optional().nullable(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n }),\n threadId: z.string().uuid().nullable().optional(),\n parentMessageId: z.string().uuid().nullable().optional(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.string(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n recipients: z.array(z.object({\n userId: z.string().uuid(),\n type: z.enum(['to', 'cc', 'bcc']),\n status: z.string(),\n readAt: z.string().nullable().optional(),\n })),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n preview: z.object({\n title: z.string(),\n subtitle: z.string().optional(),\n status: z.string().optional(),\n statusColor: z.string().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n }).nullable().optional(),\n })),\n thread: z.array(messageThreadItemSchema),\n isRead: z.boolean(),\n conversationArchived: z.boolean(),\n conversationAllUnread: z.boolean(),\n _channel: z.record(z.string(), z.unknown()).nullable().optional(),\n _channelPayload: z.record(z.string(), z.unknown()).nullable().optional(),\n _channelContact: z.record(z.string(), z.unknown()).nullable().optional(),\n _reactions: z.array(z.record(z.string(), z.unknown())).optional(),\n _meta: enrichmentMetaSchema.optional(),\n})\n // The detail route runs response enrichers for `messages.message`, so any\n // module registering one may contribute further `_`-prefixed keys. Without\n // this the generator emits `additionalProperties: false` and the published\n // spec forbids the very fields the route returns.\n .passthrough()\n\nexport const messageTokenDetailResponseSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.enum(['low', 'normal', 'high', 'urgent']),\n senderUserId: z.string().uuid(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n })),\n requiresAuth: z.boolean(),\n recipientUserId: z.string().uuid(),\n})\n\nexport const messageTokenPreflightResponseSchema = z.object({\n requiresAuth: z.literal(true),\n})\n\nexport const messageTokenResponseSchema = z.union([\n messageTokenDetailResponseSchema,\n messageTokenPreflightResponseSchema,\n])\n\nexport const unreadCountResponseSchema = z.object({\n unreadCount: z.number(),\n})\n\nexport const messageConfirmationResponseSchema = z.object({\n messageId: z.string().uuid(),\n confirmed: z.boolean(),\n confirmedAt: z.string().nullable(),\n confirmedByUserId: z.string().uuid().nullable(),\n})\n\nexport const actionResultResponseSchema = z.object({\n ok: z.boolean(),\n actionId: z.string(),\n result: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const forwardResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const forwardPreviewResponseSchema = z.object({\n subject: z.string(),\n body: z.string(),\n})\n\nexport const conversationMutationResponseSchema = z.object({\n ok: z.boolean(),\n affectedCount: z.number().int().min(0),\n})\n\nexport const composeResponseSchema = z.object({\n id: z.string().uuid(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const messageAttachmentResponseSchema = z.object({\n attachments: z.array(messageAttachmentSchema),\n})\n\nexport const okResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid().optional(),\n message: z.string().optional(),\n})\n\nexport const errorResponseSchema = z.object({\n code: z.string().optional(),\n error: z.string(),\n})\n\nexport const messageTypeResponseItemSchema = z.object({\n type: z.string(),\n module: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().nullable().optional(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n actionsExpireAfterHours: z.number().int().nullable().optional(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n})\n\nexport const messageTypeListResponseSchema = z.object({\n items: z.array(messageTypeResponseItemSchema),\n})\n\nexport const messageObjectTypeActionResponseSchema = z.object({\n id: z.string(),\n labelKey: z.string(),\n variant: z.enum(['default', 'secondary', 'destructive', 'outline']).optional(),\n icon: z.string().optional(),\n commandId: z.string().optional(),\n href: z.string().optional(),\n isTerminal: z.boolean().optional(),\n confirmRequired: z.boolean().optional(),\n confirmMessage: z.string().optional(),\n})\n\nexport const messageObjectTypeResponseItemSchema = z.object({\n module: z.string(),\n entityType: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n actions: z.array(messageObjectTypeActionResponseSchema),\n})\n\nexport const messageObjectTypeListResponseSchema = z.object({\n items: z.array(messageObjectTypeResponseItemSchema),\n})\n\nexport {\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n composeMessageSchema,\n composeMessageRequestSchema,\n listMessagesSchema,\n forwardMessageSchema,\n replyMessageSchema,\n updateDraftSchema,\n executeActionSchema,\n messageObjectTypesQuerySchema,\n attachmentIdsPayloadSchema,\n unlinkAttachmentPayloadSchema,\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,6 +14,11 @@ import { lookupHashCandidates } from "@open-mercato/shared/lib/encryption/aes";
|
|
|
14
14
|
import { User } from "../../auth/data/entities.js";
|
|
15
15
|
import { Message, MessageObject } from "../data/entities.js";
|
|
16
16
|
import { composeMessageSchema, listMessagesSchema } from "../data/validators.js";
|
|
17
|
+
import {
|
|
18
|
+
composeRequiresChannelTypeResolution,
|
|
19
|
+
composeSourceHintSchema,
|
|
20
|
+
resolveComposeSourceChannelType
|
|
21
|
+
} from "../lib/composeSourceChannelType.js";
|
|
17
22
|
import { MESSAGE_ATTACHMENT_ENTITY_ID } from "../lib/constants.js";
|
|
18
23
|
import { getMessageType } from "../lib/message-types-registry.js";
|
|
19
24
|
import { validateMessageObjectsForType } from "../lib/object-validation.js";
|
|
@@ -23,7 +28,7 @@ import { applyMessageParticipantScope } from "../lib/participantScope.js";
|
|
|
23
28
|
import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "./guards.js";
|
|
24
29
|
import { findMessageIdsBySearchTokens } from "../lib/searchLookup.js";
|
|
25
30
|
import {
|
|
26
|
-
|
|
31
|
+
composeMessageRequestSchema as composeSchema,
|
|
27
32
|
composeResponseSchema,
|
|
28
33
|
listMessagesSchema as listSchema,
|
|
29
34
|
messageListItemSchema
|
|
@@ -283,7 +288,17 @@ async function POST(req) {
|
|
|
283
288
|
const { ctx, scope } = await resolveMessageContext(req);
|
|
284
289
|
const commandBus = ctx.container.resolve("commandBus");
|
|
285
290
|
const body = await req.json().catch(() => ({}));
|
|
286
|
-
const
|
|
291
|
+
const sourceHint = composeRequiresChannelTypeResolution(body) ? composeSourceHintSchema.safeParse(body) : null;
|
|
292
|
+
const sourceChannelType = sourceHint?.success ? await resolveComposeSourceChannelType(
|
|
293
|
+
ctx.container,
|
|
294
|
+
{ tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },
|
|
295
|
+
sourceHint.data
|
|
296
|
+
) : void 0;
|
|
297
|
+
const { sourceChannelType: _clientSuppliedChannelType, ...clientBody } = body ?? {};
|
|
298
|
+
const input = composeMessageSchema.parse({
|
|
299
|
+
...clientBody,
|
|
300
|
+
...sourceChannelType ? { sourceChannelType } : {}
|
|
301
|
+
});
|
|
287
302
|
const isPublicVisibility = input.visibility === "public";
|
|
288
303
|
const sendViaEmail = isPublicVisibility ? true : input.sendViaEmail;
|
|
289
304
|
if (sendViaEmail && !await canUseMessageEmailFeature(ctx, scope)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/api/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash } from 'node:crypto'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport {\n buildCollectionTags,\n debugCrudCache,\n isCrudCacheEnabled,\n normalizeTagSegment,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { lookupHashCandidates } from '@open-mercato/shared/lib/encryption/aes'\nimport { User } from '../../auth/data/entities'\nimport { Message, MessageObject } from '../data/entities'\nimport { composeMessageSchema, listMessagesSchema, type ListMessagesInput } from '../data/validators'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'\nimport { getMessageType } from '../lib/message-types-registry'\nimport { validateMessageObjectsForType } from '../lib/object-validation'\nimport { attachOperationMetadataHeader } from '../lib/operationMetadata'\nimport { canUseMessageEmailFeature, resolveMessageContext } from '../lib/routeHelpers'\nimport { applyMessageParticipantScope } from '../lib/participantScope'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from './guards'\nimport { findMessageIdsBySearchTokens } from '../lib/searchLookup'\nimport { MessageCommandExecuteResult } from '../commands/shared'\nimport {\n composeMessageSchema as composeSchema,\n composeResponseSchema,\n listMessagesSchema as listSchema,\n messageListItemSchema,\n} from './openapi'\n\ntype MessageCommandExecuteResultWithThreadId = MessageCommandExecuteResult & {\n threadId: string\n}\n\nconst NO_MATCH_ID = '00000000-0000-0000-0000-000000000000'\nconst MESSAGE_LIST_CACHE_TTL_MS = 30_000\nconst MESSAGE_LIST_RESOURCE = 'messages.message'\n\nfunction getDb(em: EntityManager): Kysely<any> {\n return em.getKysely<any>()\n}\n\ntype MessageListScopeRow = {\n id: string\n sender_user_id: string\n is_draft: boolean\n recipient_status: string | null\n read_at: string | null\n}\n\ntype AttachmentCountRow = {\n record_id: string\n count: string | number\n}\n\ntype RecipientCountRow = {\n message_id: string\n count: string | number\n}\n\ntype MessageListPayload = {\n items: Array<Record<string, unknown>>\n page: number\n pageSize: number\n total: number\n totalPages: number\n}\n\ntype MessageListScope = Awaited<ReturnType<typeof resolveMessageContext>>['scope']\n\nfunction normalizeCacheFilterValue(value: unknown): unknown {\n if (value === undefined) return null\n if (value instanceof Date) return value.toISOString()\n if (Array.isArray(value)) return value.map(normalizeCacheFilterValue)\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, entryValue]) => [key, normalizeCacheFilterValue(entryValue)])\n }\n return value\n}\n\nfunction buildMessageListFilterSignature(input: ListMessagesInput): string {\n const canonicalInput = Object.entries(input)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, value]) => [key, normalizeCacheFilterValue(value)])\n\n return createHash('sha256')\n .update(JSON.stringify(canonicalInput))\n .digest('hex')\n .slice(0, 32)\n}\n\nfunction buildMessageListCacheKey(scope: MessageListScope, input: ListMessagesInput): string {\n return [\n 'messages:list:v1',\n `tenant:${normalizeTagSegment(scope.tenantId)}`,\n `org:${normalizeTagSegment(scope.organizationId)}`,\n `user:${normalizeTagSegment(scope.userId)}`,\n `filters:${buildMessageListFilterSignature(input)}`,\n ].join('|')\n}\n\nfunction isMessageListPayload(value: unknown): value is MessageListPayload {\n if (!value || typeof value !== 'object') return false\n const payload = value as Partial<MessageListPayload>\n return Array.isArray(payload.items)\n && typeof payload.page === 'number'\n && typeof payload.pageSize === 'number'\n && typeof payload.total === 'number'\n && typeof payload.totalPages === 'number'\n}\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveMessageContext(req)\n const url = new URL(req.url)\n const params = Object.fromEntries(url.searchParams)\n const input = listMessagesSchema.parse(params)\n\n const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null\n const cacheKey = cache ? buildMessageListCacheKey(scope, input) : null\n if (cache && cacheKey) {\n try {\n const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))\n if (isMessageListPayload(cached)) {\n return Response.json(cached)\n }\n } catch (error) {\n debugCrudCache('messages-list-cache-read-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const em = ctx.container.resolve('em') as EntityManager\n const db = getDb(em) as any\n\n const searchIds = input.search\n ? await findMessageIdsBySearchTokens({\n em,\n query: input.search,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n })\n : undefined\n\n const buildBaseQuery = () => {\n let q: any = db\n .selectFrom('messages as m')\n .where('m.tenant_id', '=', scope.tenantId)\n .where('m.deleted_at', 'is', null)\n\n if (scope.organizationId) {\n q = q.where('m.organization_id', '=', scope.organizationId)\n } else {\n q = q.where('m.organization_id', 'is', null)\n }\n\n const joinRecipient = () => {\n q = q.leftJoin('message_recipients as r', (jb: any) => jb\n .onRef('m.id', '=', 'r.message_id')\n .on('r.recipient_user_id', '=', scope.userId))\n }\n\n switch (input.folder) {\n case 'inbox':\n joinRecipient()\n q = q\n .where('r.message_id', 'is not', null)\n .where('r.deleted_at', 'is', null)\n .where('r.archived_at', 'is', null)\n .where('m.is_draft', '=', false)\n break\n case 'archived':\n joinRecipient()\n q = q\n .where('r.message_id', 'is not', null)\n .where('r.deleted_at', 'is', null)\n .where('r.archived_at', 'is not', null)\n break\n case 'sent':\n q = q\n .where('m.sender_user_id', '=', scope.userId)\n .where('m.is_draft', '=', false)\n joinRecipient()\n break\n case 'drafts':\n q = q\n .where('m.sender_user_id', '=', scope.userId)\n .where('m.is_draft', '=', true)\n joinRecipient()\n break\n case 'all':\n // Sender-OR-recipient participant scope shared with the\n // communication_channels message enricher \u2014 see participantScope.ts (#4133).\n q = applyMessageParticipantScope(q, scope.userId)\n break\n default: {\n const unsupportedFolder: never = input.folder\n throw new Error(`Unsupported folder: ${String(unsupportedFolder)}`)\n }\n }\n\n if (input.status) q = q.where('r.status', '=', input.status)\n if (input.type) q = q.where('m.type', '=', input.type)\n if (input.visibility) q = q.where('m.visibility', '=', input.visibility)\n if (input.sourceEntityType) q = q.where('m.source_entity_type', '=', input.sourceEntityType)\n if (input.sourceEntityId) q = q.where('m.source_entity_id', '=', input.sourceEntityId)\n if (input.externalEmail) q = q.where('m.external_email_hash', 'in', lookupHashCandidates(input.externalEmail))\n if (input.senderId) q = q.where('m.sender_user_id', '=', input.senderId)\n\n if (input.search) {\n if (!searchIds || searchIds.length === 0) {\n q = q.where('m.id', '=', NO_MATCH_ID)\n } else {\n q = q.where('m.id', 'in', searchIds)\n }\n }\n\n if (input.since) q = q.where('m.sent_at', '>', new Date(input.since))\n\n if (input.hasObjects !== undefined) {\n const existsFn = (eb: any) => eb.exists(\n eb.selectFrom('message_objects')\n .select(sql<number>`1`.as('one'))\n .whereRef('message_objects.message_id', '=', 'm.id')\n )\n const notExistsFn = (eb: any) => eb.not(eb.exists(\n eb.selectFrom('message_objects')\n .select(sql<number>`1`.as('one'))\n .whereRef('message_objects.message_id', '=', 'm.id')\n ))\n q = input.hasObjects ? q.where(existsFn) : q.where(notExistsFn)\n }\n\n if (input.hasAttachments !== undefined) {\n const existsFn = (eb: any) => eb.exists(\n eb.selectFrom('attachments')\n .select(sql<number>`1`.as('one'))\n .where('attachments.entity_id', '=', MESSAGE_ATTACHMENT_ENTITY_ID)\n .where(sql<boolean>`attachments.record_id = m.id::text`)\n )\n const notExistsFn = (eb: any) => eb.not(eb.exists(\n eb.selectFrom('attachments')\n .select(sql<number>`1`.as('one'))\n .where('attachments.entity_id', '=', MESSAGE_ATTACHMENT_ENTITY_ID)\n .where(sql<boolean>`attachments.record_id = m.id::text`)\n ))\n q = input.hasAttachments ? q.where(existsFn) : q.where(notExistsFn)\n }\n\n if (input.hasActions !== undefined) {\n q = input.hasActions\n ? q.where('m.action_data', 'is not', null)\n : q.where('m.action_data', 'is', null)\n }\n\n return q\n }\n\n // Audited for #3386 rollout (P3): sort is on m.sent_at (a plain timestamp \u2014\n // not in the messages:message encryption map whose encrypted fields are:\n // subject, body, external_email, external_name, action_data, action_result).\n // The handler already uses the correct two-phase shape: Kysely SQL\n // ORDER BY + LIMIT/OFFSET produces a bounded page of IDs, then\n // findWithDecryption is called only for those IDs \u2014 never for the full\n // result set. The #3278 unbounded-decrypt hazard does not apply here.\n // Covered by __tests__/list.test.ts.\n const countResult = await buildBaseQuery()\n .select(sql<number>`count(*)`.as('count'))\n .executeTakeFirst() as { count: string | number } | undefined\n const total = Number(countResult?.count ?? 0)\n\n const offset = (input.page - 1) * input.pageSize\n const scopeRows = await buildBaseQuery()\n .select([\n 'm.id',\n 'm.sender_user_id',\n 'm.is_draft',\n 'r.status as recipient_status',\n 'r.read_at',\n ])\n .orderBy('m.sent_at', 'desc')\n .offset(offset)\n .limit(input.pageSize)\n .execute()\n\n const typedRows = scopeRows as MessageListScopeRow[]\n const messageIds = typedRows.map((row) => row.id)\n\n const messageEntities = messageIds.length > 0\n ? await findWithDecryption(\n em,\n Message,\n { id: { $in: messageIds } },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n : []\n\n const messagesById = new Map<string, Message>()\n for (const message of messageEntities) {\n messagesById.set(message.id, message)\n }\n\n const objects = messageIds.length > 0\n ? await em.find(MessageObject, { messageId: { $in: messageIds } })\n : []\n\n const objectsByMessage = objects.reduce((acc, obj) => {\n if (!acc[obj.messageId]) acc[obj.messageId] = []\n acc[obj.messageId].push(obj)\n return acc\n }, {} as Record<string, MessageObject[]>)\n\n const attachmentCounts: AttachmentCountRow[] = messageIds.length > 0\n ? await (getDb(em) as any)\n .selectFrom('attachments')\n .select(['record_id', sql<string>`count(*)`.as('count')])\n .where('entity_id', '=', MESSAGE_ATTACHMENT_ENTITY_ID)\n .where('record_id', 'in', messageIds)\n .groupBy('record_id')\n .execute()\n : []\n\n const attachmentCountByMessage = attachmentCounts.reduce((acc: Record<string, number>, row) => {\n acc[row.record_id] = Number(row.count)\n return acc\n }, {})\n\n const recipientCounts: RecipientCountRow[] = messageIds.length > 0\n ? await (getDb(em) as any)\n .selectFrom('message_recipients')\n .select(['message_id', sql<string>`count(*)`.as('count')])\n .where('message_id', 'in', messageIds)\n .where('deleted_at', 'is', null)\n .groupBy('message_id')\n .execute()\n : []\n\n const recipientCountByMessage = recipientCounts.reduce((acc: Record<string, number>, row) => {\n acc[row.message_id] = Number(row.count)\n return acc\n }, {})\n\n const senderUserIds = Array.from(new Set(typedRows.map((row) => row.sender_user_id).filter(Boolean)))\n const senderUsers = senderUserIds.length > 0\n ? await findWithDecryption(\n em,\n User,\n { id: { $in: senderUserIds } },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n : []\n\n const senderMetaById = new Map<string, { name: string | null; email: string | null }>()\n senderUsers.forEach((user) => {\n const name = typeof user.name === 'string' && user.name.trim().length ? user.name.trim() : null\n senderMetaById.set(user.id, { name, email: user.email ?? null })\n })\n\n const payload: MessageListPayload = {\n items: typedRows\n .map((row) => {\n const message = messagesById.get(row.id)\n if (!message) return null\n const body = typeof message.body === 'string' ? message.body : ''\n const bodyPreview = body.substring(0, 150) + (body.length > 150 ? '...' : '')\n const actionData = message.actionData ?? null\n return {\n ...(senderMetaById.get(row.sender_user_id)\n ? {\n senderName: senderMetaById.get(row.sender_user_id)?.name ?? null,\n senderEmail: senderMetaById.get(row.sender_user_id)?.email ?? null,\n }\n : { senderName: null, senderEmail: null }),\n id: message.id,\n type: message.type,\n visibility: message.visibility ?? null,\n sourceEntityType: message.sourceEntityType ?? null,\n sourceEntityId: message.sourceEntityId ?? null,\n externalEmail: message.externalEmail ?? null,\n externalName: message.externalName ?? null,\n subject: message.subject,\n bodyPreview,\n senderUserId: message.senderUserId,\n priority: message.priority,\n status: row.recipient_status ?? (row.is_draft ? 'draft' : 'sent'),\n hasObjects: (objectsByMessage[message.id] || []).length > 0,\n objectCount: (objectsByMessage[message.id] || []).length,\n hasAttachments: (attachmentCountByMessage[message.id] || 0) > 0,\n attachmentCount: attachmentCountByMessage[message.id] || 0,\n recipientCount: recipientCountByMessage[message.id] || 0,\n hasActions:\n Boolean(actionData?.actions?.length)\n || Boolean(getMessageType(message.type)?.defaultActions?.length)\n || (objectsByMessage[message.id] || []).some((item) => item.actionRequired && Boolean(item.actionType)),\n actionTaken: message.actionTaken ?? null,\n sentAt: message.sentAt ? message.sentAt.toISOString() : null,\n readAt: row.read_at,\n threadId: message.threadId ?? null,\n }\n })\n .filter((item): item is NonNullable<typeof item> => item !== null),\n page: input.page,\n pageSize: input.pageSize,\n total,\n totalPages: Math.ceil(total / input.pageSize),\n }\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(scope.tenantId, () =>\n cache.set(cacheKey, payload, {\n ttl: MESSAGE_LIST_CACHE_TTL_MS,\n tags: buildCollectionTags(\n MESSAGE_LIST_RESOURCE,\n scope.tenantId,\n [scope.organizationId],\n ),\n }),\n )\n } catch (error) {\n debugCrudCache('messages-list-cache-write-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n return Response.json(payload)\n}\n\nexport async function POST(req: Request) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = composeMessageSchema.parse(body)\n\n const isPublicVisibility = input.visibility === 'public'\n const sendViaEmail = isPublicVisibility ? true : input.sendViaEmail\n if (sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n if (input.objects?.length) {\n const objectValidationError = validateMessageObjectsForType(input.type, input.objects)\n if (objectValidationError) {\n return Response.json({ error: objectValidationError }, { status: 400 })\n }\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const { result, logEntry } = await commandBus.execute('messages.messages.compose', {\n input: {\n ...input,\n sendViaEmail,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n const { id: messageId, threadId: responseThreadId } = result as unknown as MessageCommandExecuteResultWithThreadId\n\n const response = Response.json({ id: messageId, threadId: responseThreadId }, { status: 201 })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: messageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: messageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n GET: {\n summary: 'List messages',\n query: listSchema,\n responses: [\n {\n status: 200,\n description: 'Message list',\n schema: z.object({\n items: z.array(messageListItemSchema),\n page: z.number(),\n pageSize: z.number(),\n total: z.number(),\n totalPages: z.number(),\n }),\n },\n ],\n },\n POST: {\n summary: 'Compose a message',\n requestBody: {\n schema: composeSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Message created',\n schema: composeResponseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport {\n buildCollectionTags,\n debugCrudCache,\n isCrudCacheEnabled,\n normalizeTagSegment,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { lookupHashCandidates } from '@open-mercato/shared/lib/encryption/aes'\nimport { User } from '../../auth/data/entities'\nimport { Message, MessageObject } from '../data/entities'\nimport { composeMessageSchema, listMessagesSchema, type ListMessagesInput } from '../data/validators'\nimport {\n composeRequiresChannelTypeResolution,\n composeSourceHintSchema,\n resolveComposeSourceChannelType,\n} from '../lib/composeSourceChannelType'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'\nimport { getMessageType } from '../lib/message-types-registry'\nimport { validateMessageObjectsForType } from '../lib/object-validation'\nimport { attachOperationMetadataHeader } from '../lib/operationMetadata'\nimport { canUseMessageEmailFeature, resolveMessageContext } from '../lib/routeHelpers'\nimport { applyMessageParticipantScope } from '../lib/participantScope'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from './guards'\nimport { findMessageIdsBySearchTokens } from '../lib/searchLookup'\nimport { MessageCommandExecuteResult } from '../commands/shared'\nimport {\n composeMessageRequestSchema as composeSchema,\n composeResponseSchema,\n listMessagesSchema as listSchema,\n messageListItemSchema,\n} from './openapi'\n\ntype MessageCommandExecuteResultWithThreadId = MessageCommandExecuteResult & {\n threadId: string\n}\n\nconst NO_MATCH_ID = '00000000-0000-0000-0000-000000000000'\nconst MESSAGE_LIST_CACHE_TTL_MS = 30_000\nconst MESSAGE_LIST_RESOURCE = 'messages.message'\n\nfunction getDb(em: EntityManager): Kysely<any> {\n return em.getKysely<any>()\n}\n\ntype MessageListScopeRow = {\n id: string\n sender_user_id: string\n is_draft: boolean\n recipient_status: string | null\n read_at: string | null\n}\n\ntype AttachmentCountRow = {\n record_id: string\n count: string | number\n}\n\ntype RecipientCountRow = {\n message_id: string\n count: string | number\n}\n\ntype MessageListPayload = {\n items: Array<Record<string, unknown>>\n page: number\n pageSize: number\n total: number\n totalPages: number\n}\n\ntype MessageListScope = Awaited<ReturnType<typeof resolveMessageContext>>['scope']\n\nfunction normalizeCacheFilterValue(value: unknown): unknown {\n if (value === undefined) return null\n if (value instanceof Date) return value.toISOString()\n if (Array.isArray(value)) return value.map(normalizeCacheFilterValue)\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, entryValue]) => [key, normalizeCacheFilterValue(entryValue)])\n }\n return value\n}\n\nfunction buildMessageListFilterSignature(input: ListMessagesInput): string {\n const canonicalInput = Object.entries(input)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, value]) => [key, normalizeCacheFilterValue(value)])\n\n return createHash('sha256')\n .update(JSON.stringify(canonicalInput))\n .digest('hex')\n .slice(0, 32)\n}\n\nfunction buildMessageListCacheKey(scope: MessageListScope, input: ListMessagesInput): string {\n return [\n 'messages:list:v1',\n `tenant:${normalizeTagSegment(scope.tenantId)}`,\n `org:${normalizeTagSegment(scope.organizationId)}`,\n `user:${normalizeTagSegment(scope.userId)}`,\n `filters:${buildMessageListFilterSignature(input)}`,\n ].join('|')\n}\n\nfunction isMessageListPayload(value: unknown): value is MessageListPayload {\n if (!value || typeof value !== 'object') return false\n const payload = value as Partial<MessageListPayload>\n return Array.isArray(payload.items)\n && typeof payload.page === 'number'\n && typeof payload.pageSize === 'number'\n && typeof payload.total === 'number'\n && typeof payload.totalPages === 'number'\n}\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveMessageContext(req)\n const url = new URL(req.url)\n const params = Object.fromEntries(url.searchParams)\n const input = listMessagesSchema.parse(params)\n\n const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null\n const cacheKey = cache ? buildMessageListCacheKey(scope, input) : null\n if (cache && cacheKey) {\n try {\n const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))\n if (isMessageListPayload(cached)) {\n return Response.json(cached)\n }\n } catch (error) {\n debugCrudCache('messages-list-cache-read-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const em = ctx.container.resolve('em') as EntityManager\n const db = getDb(em) as any\n\n const searchIds = input.search\n ? await findMessageIdsBySearchTokens({\n em,\n query: input.search,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId,\n })\n : undefined\n\n const buildBaseQuery = () => {\n let q: any = db\n .selectFrom('messages as m')\n .where('m.tenant_id', '=', scope.tenantId)\n .where('m.deleted_at', 'is', null)\n\n if (scope.organizationId) {\n q = q.where('m.organization_id', '=', scope.organizationId)\n } else {\n q = q.where('m.organization_id', 'is', null)\n }\n\n const joinRecipient = () => {\n q = q.leftJoin('message_recipients as r', (jb: any) => jb\n .onRef('m.id', '=', 'r.message_id')\n .on('r.recipient_user_id', '=', scope.userId))\n }\n\n switch (input.folder) {\n case 'inbox':\n joinRecipient()\n q = q\n .where('r.message_id', 'is not', null)\n .where('r.deleted_at', 'is', null)\n .where('r.archived_at', 'is', null)\n .where('m.is_draft', '=', false)\n break\n case 'archived':\n joinRecipient()\n q = q\n .where('r.message_id', 'is not', null)\n .where('r.deleted_at', 'is', null)\n .where('r.archived_at', 'is not', null)\n break\n case 'sent':\n q = q\n .where('m.sender_user_id', '=', scope.userId)\n .where('m.is_draft', '=', false)\n joinRecipient()\n break\n case 'drafts':\n q = q\n .where('m.sender_user_id', '=', scope.userId)\n .where('m.is_draft', '=', true)\n joinRecipient()\n break\n case 'all':\n // Sender-OR-recipient participant scope shared with the\n // communication_channels message enricher \u2014 see participantScope.ts (#4133).\n q = applyMessageParticipantScope(q, scope.userId)\n break\n default: {\n const unsupportedFolder: never = input.folder\n throw new Error(`Unsupported folder: ${String(unsupportedFolder)}`)\n }\n }\n\n if (input.status) q = q.where('r.status', '=', input.status)\n if (input.type) q = q.where('m.type', '=', input.type)\n if (input.visibility) q = q.where('m.visibility', '=', input.visibility)\n if (input.sourceEntityType) q = q.where('m.source_entity_type', '=', input.sourceEntityType)\n if (input.sourceEntityId) q = q.where('m.source_entity_id', '=', input.sourceEntityId)\n if (input.externalEmail) q = q.where('m.external_email_hash', 'in', lookupHashCandidates(input.externalEmail))\n if (input.senderId) q = q.where('m.sender_user_id', '=', input.senderId)\n\n if (input.search) {\n if (!searchIds || searchIds.length === 0) {\n q = q.where('m.id', '=', NO_MATCH_ID)\n } else {\n q = q.where('m.id', 'in', searchIds)\n }\n }\n\n if (input.since) q = q.where('m.sent_at', '>', new Date(input.since))\n\n if (input.hasObjects !== undefined) {\n const existsFn = (eb: any) => eb.exists(\n eb.selectFrom('message_objects')\n .select(sql<number>`1`.as('one'))\n .whereRef('message_objects.message_id', '=', 'm.id')\n )\n const notExistsFn = (eb: any) => eb.not(eb.exists(\n eb.selectFrom('message_objects')\n .select(sql<number>`1`.as('one'))\n .whereRef('message_objects.message_id', '=', 'm.id')\n ))\n q = input.hasObjects ? q.where(existsFn) : q.where(notExistsFn)\n }\n\n if (input.hasAttachments !== undefined) {\n const existsFn = (eb: any) => eb.exists(\n eb.selectFrom('attachments')\n .select(sql<number>`1`.as('one'))\n .where('attachments.entity_id', '=', MESSAGE_ATTACHMENT_ENTITY_ID)\n .where(sql<boolean>`attachments.record_id = m.id::text`)\n )\n const notExistsFn = (eb: any) => eb.not(eb.exists(\n eb.selectFrom('attachments')\n .select(sql<number>`1`.as('one'))\n .where('attachments.entity_id', '=', MESSAGE_ATTACHMENT_ENTITY_ID)\n .where(sql<boolean>`attachments.record_id = m.id::text`)\n ))\n q = input.hasAttachments ? q.where(existsFn) : q.where(notExistsFn)\n }\n\n if (input.hasActions !== undefined) {\n q = input.hasActions\n ? q.where('m.action_data', 'is not', null)\n : q.where('m.action_data', 'is', null)\n }\n\n return q\n }\n\n // Audited for #3386 rollout (P3): sort is on m.sent_at (a plain timestamp \u2014\n // not in the messages:message encryption map whose encrypted fields are:\n // subject, body, external_email, external_name, action_data, action_result).\n // The handler already uses the correct two-phase shape: Kysely SQL\n // ORDER BY + LIMIT/OFFSET produces a bounded page of IDs, then\n // findWithDecryption is called only for those IDs \u2014 never for the full\n // result set. The #3278 unbounded-decrypt hazard does not apply here.\n // Covered by __tests__/list.test.ts.\n const countResult = await buildBaseQuery()\n .select(sql<number>`count(*)`.as('count'))\n .executeTakeFirst() as { count: string | number } | undefined\n const total = Number(countResult?.count ?? 0)\n\n const offset = (input.page - 1) * input.pageSize\n const scopeRows = await buildBaseQuery()\n .select([\n 'm.id',\n 'm.sender_user_id',\n 'm.is_draft',\n 'r.status as recipient_status',\n 'r.read_at',\n ])\n .orderBy('m.sent_at', 'desc')\n .offset(offset)\n .limit(input.pageSize)\n .execute()\n\n const typedRows = scopeRows as MessageListScopeRow[]\n const messageIds = typedRows.map((row) => row.id)\n\n const messageEntities = messageIds.length > 0\n ? await findWithDecryption(\n em,\n Message,\n { id: { $in: messageIds } },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n : []\n\n const messagesById = new Map<string, Message>()\n for (const message of messageEntities) {\n messagesById.set(message.id, message)\n }\n\n const objects = messageIds.length > 0\n ? await em.find(MessageObject, { messageId: { $in: messageIds } })\n : []\n\n const objectsByMessage = objects.reduce((acc, obj) => {\n if (!acc[obj.messageId]) acc[obj.messageId] = []\n acc[obj.messageId].push(obj)\n return acc\n }, {} as Record<string, MessageObject[]>)\n\n const attachmentCounts: AttachmentCountRow[] = messageIds.length > 0\n ? await (getDb(em) as any)\n .selectFrom('attachments')\n .select(['record_id', sql<string>`count(*)`.as('count')])\n .where('entity_id', '=', MESSAGE_ATTACHMENT_ENTITY_ID)\n .where('record_id', 'in', messageIds)\n .groupBy('record_id')\n .execute()\n : []\n\n const attachmentCountByMessage = attachmentCounts.reduce((acc: Record<string, number>, row) => {\n acc[row.record_id] = Number(row.count)\n return acc\n }, {})\n\n const recipientCounts: RecipientCountRow[] = messageIds.length > 0\n ? await (getDb(em) as any)\n .selectFrom('message_recipients')\n .select(['message_id', sql<string>`count(*)`.as('count')])\n .where('message_id', 'in', messageIds)\n .where('deleted_at', 'is', null)\n .groupBy('message_id')\n .execute()\n : []\n\n const recipientCountByMessage = recipientCounts.reduce((acc: Record<string, number>, row) => {\n acc[row.message_id] = Number(row.count)\n return acc\n }, {})\n\n const senderUserIds = Array.from(new Set(typedRows.map((row) => row.sender_user_id).filter(Boolean)))\n const senderUsers = senderUserIds.length > 0\n ? await findWithDecryption(\n em,\n User,\n { id: { $in: senderUserIds } },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n : []\n\n const senderMetaById = new Map<string, { name: string | null; email: string | null }>()\n senderUsers.forEach((user) => {\n const name = typeof user.name === 'string' && user.name.trim().length ? user.name.trim() : null\n senderMetaById.set(user.id, { name, email: user.email ?? null })\n })\n\n const payload: MessageListPayload = {\n items: typedRows\n .map((row) => {\n const message = messagesById.get(row.id)\n if (!message) return null\n const body = typeof message.body === 'string' ? message.body : ''\n const bodyPreview = body.substring(0, 150) + (body.length > 150 ? '...' : '')\n const actionData = message.actionData ?? null\n return {\n ...(senderMetaById.get(row.sender_user_id)\n ? {\n senderName: senderMetaById.get(row.sender_user_id)?.name ?? null,\n senderEmail: senderMetaById.get(row.sender_user_id)?.email ?? null,\n }\n : { senderName: null, senderEmail: null }),\n id: message.id,\n type: message.type,\n visibility: message.visibility ?? null,\n sourceEntityType: message.sourceEntityType ?? null,\n sourceEntityId: message.sourceEntityId ?? null,\n externalEmail: message.externalEmail ?? null,\n externalName: message.externalName ?? null,\n subject: message.subject,\n bodyPreview,\n senderUserId: message.senderUserId,\n priority: message.priority,\n status: row.recipient_status ?? (row.is_draft ? 'draft' : 'sent'),\n hasObjects: (objectsByMessage[message.id] || []).length > 0,\n objectCount: (objectsByMessage[message.id] || []).length,\n hasAttachments: (attachmentCountByMessage[message.id] || 0) > 0,\n attachmentCount: attachmentCountByMessage[message.id] || 0,\n recipientCount: recipientCountByMessage[message.id] || 0,\n hasActions:\n Boolean(actionData?.actions?.length)\n || Boolean(getMessageType(message.type)?.defaultActions?.length)\n || (objectsByMessage[message.id] || []).some((item) => item.actionRequired && Boolean(item.actionType)),\n actionTaken: message.actionTaken ?? null,\n sentAt: message.sentAt ? message.sentAt.toISOString() : null,\n readAt: row.read_at,\n threadId: message.threadId ?? null,\n }\n })\n .filter((item): item is NonNullable<typeof item> => item !== null),\n page: input.page,\n pageSize: input.pageSize,\n total,\n totalPages: Math.ceil(total / input.pageSize),\n }\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(scope.tenantId, () =>\n cache.set(cacheKey, payload, {\n ttl: MESSAGE_LIST_CACHE_TTL_MS,\n tags: buildCollectionTags(\n MESSAGE_LIST_RESOURCE,\n scope.tenantId,\n [scope.organizationId],\n ),\n }),\n )\n } catch (error) {\n debugCrudCache('messages-list-cache-write-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n return Response.json(payload)\n}\n\nexport async function POST(req: Request) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n\n // #4975: whether `externalEmail` is required depends on the channel this\n // message originates from, so the channel type has to be established BEFORE\n // validation. It is resolved server-side from the referenced conversation or\n // parent message and any client-sent `sourceChannelType` is dropped \u2014\n // otherwise a caller could waive the requirement by asserting its own type.\n // An unresolvable source stays `undefined`, which the validator fails closed on.\n // Only the \"public, non-draft, no address\" branch of the validator reads the\n // answer, so every other compose skips the lookup instead of discarding it.\n const sourceHint = composeRequiresChannelTypeResolution(body)\n ? composeSourceHintSchema.safeParse(body)\n : null\n const sourceChannelType = sourceHint?.success\n ? await resolveComposeSourceChannelType(\n ctx.container,\n { tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },\n sourceHint.data,\n )\n : undefined\n const { sourceChannelType: _clientSuppliedChannelType, ...clientBody } =\n (body ?? {}) as Record<string, unknown>\n const input = composeMessageSchema.parse({\n ...clientBody,\n ...(sourceChannelType ? { sourceChannelType } : {}),\n })\n\n const isPublicVisibility = input.visibility === 'public'\n const sendViaEmail = isPublicVisibility ? true : input.sendViaEmail\n if (sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n if (input.objects?.length) {\n const objectValidationError = validateMessageObjectsForType(input.type, input.objects)\n if (objectValidationError) {\n return Response.json({ error: objectValidationError }, { status: 400 })\n }\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const { result, logEntry } = await commandBus.execute('messages.messages.compose', {\n input: {\n ...input,\n sendViaEmail,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n const { id: messageId, threadId: responseThreadId } = result as unknown as MessageCommandExecuteResultWithThreadId\n\n const response = Response.json({ id: messageId, threadId: responseThreadId }, { status: 201 })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: messageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: messageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n GET: {\n summary: 'List messages',\n query: listSchema,\n responses: [\n {\n status: 200,\n description: 'Message list',\n schema: z.object({\n items: z.array(messageListItemSchema),\n page: z.number(),\n pageSize: z.number(),\n total: z.number(),\n totalPages: z.number(),\n }),\n },\n ],\n },\n POST: {\n summary: 'Compose a message',\n requestBody: {\n schema: composeSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Message created',\n schema: composeResponseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { parseBooleanFlag } from "@open-mercato/shared/lib/boolean";
|
|
3
3
|
import { sanitizeRichTextHref } from "@open-mercato/shared/lib/html/sanitizeRichText";
|
|
4
|
+
import { channelTypeRequiresExternalEmail } from "../lib/channel-sender-identity.js";
|
|
4
5
|
function collectDuplicateRecipientIds(recipients) {
|
|
5
6
|
const seen = /* @__PURE__ */ new Set();
|
|
6
7
|
const duplicates = /* @__PURE__ */ new Set();
|
|
@@ -79,13 +80,22 @@ const messageActionDataSchema = z.object({
|
|
|
79
80
|
primaryActionId: z.string().optional(),
|
|
80
81
|
expiresAt: z.string().datetime().optional()
|
|
81
82
|
});
|
|
82
|
-
const
|
|
83
|
+
const composeMessageBaseSchema = z.object({
|
|
83
84
|
type: z.string().optional().default("default"),
|
|
84
85
|
visibility: z.enum(["public", "internal"]).nullable().optional(),
|
|
85
86
|
sourceEntityType: z.string().min(1).optional(),
|
|
86
87
|
sourceEntityId: z.string().uuid().optional(),
|
|
87
88
|
externalEmail: z.string().email().optional(),
|
|
88
89
|
externalName: z.string().min(1).max(255).optional(),
|
|
90
|
+
/**
|
|
91
|
+
* Channel type the message originates from, when the caller knows it (#4975).
|
|
92
|
+
* Non-email channels (Discord, Slack, SMS…) have senders with no address, so
|
|
93
|
+
* `externalEmail` is not required for them. Resolved server-side — the HTTP
|
|
94
|
+
* route strips any client-sent value and derives it from the referenced
|
|
95
|
+
* conversation or parent message, so a caller cannot waive the requirement by
|
|
96
|
+
* asserting its own channel type.
|
|
97
|
+
*/
|
|
98
|
+
sourceChannelType: z.string().min(1).max(64).optional(),
|
|
89
99
|
recipients: z.array(messageRecipientSchema).max(100).optional().default([]),
|
|
90
100
|
subject: z.string().max(500).optional().default(""),
|
|
91
101
|
body: z.string().max(5e4).optional().default(""),
|
|
@@ -98,7 +108,8 @@ const composeMessageSchema = z.object({
|
|
|
98
108
|
sendViaEmail: z.boolean().optional().default(false),
|
|
99
109
|
parentMessageId: z.string().uuid().optional(),
|
|
100
110
|
isDraft: z.boolean().optional().default(false)
|
|
101
|
-
})
|
|
111
|
+
});
|
|
112
|
+
function refineComposeMessage(value, ctx) {
|
|
102
113
|
const isDraft = value.isDraft ?? false;
|
|
103
114
|
const visibility = value.visibility ?? "internal";
|
|
104
115
|
const recipientCount = value.recipients.length;
|
|
@@ -107,7 +118,7 @@ const composeMessageSchema = z.object({
|
|
|
107
118
|
const hasBody = value.body.trim().length > 0;
|
|
108
119
|
if (!isDraft) {
|
|
109
120
|
if (visibility === "public") {
|
|
110
|
-
if (!hasExternalEmail) {
|
|
121
|
+
if (!hasExternalEmail && channelTypeRequiresExternalEmail(value.sourceChannelType)) {
|
|
111
122
|
ctx.addIssue({
|
|
112
123
|
code: z.ZodIssueCode.custom,
|
|
113
124
|
path: ["externalEmail"],
|
|
@@ -152,7 +163,9 @@ const composeMessageSchema = z.object({
|
|
|
152
163
|
});
|
|
153
164
|
}
|
|
154
165
|
validateDefaultWithObjectsPayload(value, ctx);
|
|
155
|
-
}
|
|
166
|
+
}
|
|
167
|
+
const composeMessageSchema = composeMessageBaseSchema.superRefine(refineComposeMessage);
|
|
168
|
+
const composeMessageRequestSchema = composeMessageBaseSchema.omit({ sourceChannelType: true }).superRefine(refineComposeMessage);
|
|
156
169
|
const updateDraftSchema = z.object({
|
|
157
170
|
type: z.string().optional(),
|
|
158
171
|
visibility: z.enum(["public", "internal"]).nullable().optional(),
|
|
@@ -261,6 +274,7 @@ const unlinkAttachmentPayloadSchema = z.object({
|
|
|
261
274
|
);
|
|
262
275
|
export {
|
|
263
276
|
attachmentIdsPayloadSchema,
|
|
277
|
+
composeMessageRequestSchema,
|
|
264
278
|
composeMessageSchema,
|
|
265
279
|
confirmMessageSchema,
|
|
266
280
|
executeActionSchema,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/data/validators.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'\nimport { sanitizeRichTextHref } from '@open-mercato/shared/lib/html/sanitizeRichText'\n\nfunction collectDuplicateRecipientIds(\n recipients: Array<{ userId: string }>,\n): string[] {\n const seen = new Set<string>()\n const duplicates = new Set<string>()\n for (const recipient of recipients) {\n if (seen.has(recipient.userId)) {\n duplicates.add(recipient.userId)\n continue\n }\n seen.add(recipient.userId)\n }\n return Array.from(duplicates)\n}\n\nfunction validateDefaultWithObjectsPayload(\n value: {\n type?: string\n objects?: Array<{ actionRequired?: boolean; actionType?: string; actionLabel?: string }>\n },\n ctx: z.RefinementCtx,\n): void {\n if (value.type !== 'messages.defaultWithObjects') return\n\n if (!Array.isArray(value.objects) || value.objects.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects'],\n message: 'at least one object is required for messages.defaultWithObjects',\n })\n return\n }\n\n value.objects.forEach((object, index) => {\n if (object.actionRequired === true) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects', index, 'actionRequired'],\n message: 'actionRequired must be false for messages.defaultWithObjects',\n })\n }\n if (typeof object.actionType === 'string' && object.actionType.trim().length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects', index, 'actionType'],\n message: 'actionType is not allowed for messages.defaultWithObjects',\n })\n }\n if (typeof object.actionLabel === 'string' && object.actionLabel.trim().length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects', index, 'actionLabel'],\n message: 'actionLabel is not allowed for messages.defaultWithObjects',\n })\n }\n })\n}\n\nexport const messageRecipientSchema = z.object({\n userId: z.string().uuid(),\n type: z.enum(['to', 'cc', 'bcc']).optional().default('to'),\n})\n\nexport const messageObjectSchema = z.object({\n entityModule: z.string().min(1),\n entityType: z.string().min(1),\n entityId: z.string().uuid(),\n actionRequired: z.boolean().optional().default(false),\n actionType: z.string().optional(),\n actionLabel: z.string().optional(),\n})\n\nexport const messageActionSchema = z.object({\n id: z.string().min(1),\n label: z.string().min(1),\n labelKey: z.string().optional(),\n variant: z.enum(['default', 'secondary', 'destructive', 'outline', 'ghost']).optional(),\n icon: z.string().optional(),\n commandId: z.string().optional(),\n href: z\n .string()\n .optional()\n .refine(\n (value) => value == null || sanitizeRichTextHref(value) != null,\n { message: '[internal] message action href must be http(s), mailto, tel, or a relative path' },\n ),\n isTerminal: z.boolean().optional(),\n confirmRequired: z.boolean().optional(),\n confirmMessage: z.string().optional(),\n})\n\nexport const messageActionDataSchema = z.object({\n actions: z.array(messageActionSchema),\n primaryActionId: z.string().optional(),\n expiresAt: z.string().datetime().optional(),\n})\n\nexport const composeMessageSchema = z.object({\n type: z.string().optional().default('default'),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().min(1).optional(),\n sourceEntityId: z.string().uuid().optional(),\n externalEmail: z.string().email().optional(),\n externalName: z.string().min(1).max(255).optional(),\n recipients: z.array(messageRecipientSchema).max(100).optional().default([]),\n subject: z.string().max(500).optional().default(''),\n body: z.string().max(50000).optional().default(''),\n bodyFormat: z.enum(['text', 'markdown']).optional().default('text'),\n priority: z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal'),\n objects: z.array(messageObjectSchema).optional(),\n attachmentIds: z.array(z.string().uuid()).optional(),\n attachmentRecordId: z.string().min(1).max(255).optional(),\n actionData: messageActionDataSchema.optional(),\n sendViaEmail: z.boolean().optional().default(false),\n parentMessageId: z.string().uuid().optional(),\n isDraft: z.boolean().optional().default(false),\n}).superRefine((value, ctx) => {\n const isDraft = value.isDraft ?? false\n const visibility = value.visibility ?? 'internal'\n const recipientCount = value.recipients.length\n const hasExternalEmail = Boolean(value.externalEmail?.trim())\n const hasSubject = value.subject.trim().length > 0\n const hasBody = value.body.trim().length > 0\n\n if (!isDraft) {\n if (visibility === 'public') {\n if (!hasExternalEmail) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['externalEmail'],\n message: 'externalEmail is required when visibility is public',\n })\n }\n if (recipientCount > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipients must be empty when visibility is public',\n })\n }\n } else if (recipientCount === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'at least one recipient is required when visibility is internal',\n })\n }\n\n if (!hasSubject) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['subject'],\n message: 'subject is required',\n })\n }\n if (!hasBody) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['body'],\n message: 'body is required',\n })\n }\n }\n\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n\n validateDefaultWithObjectsPayload(value, ctx)\n})\n\nexport const updateDraftSchema = z.object({\n type: z.string().optional(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().min(1).optional(),\n sourceEntityId: z.string().uuid().optional(),\n externalEmail: z.string().email().optional(),\n externalName: z.string().min(1).max(255).optional(),\n recipients: z.array(messageRecipientSchema).optional(),\n subject: z.string().max(500).optional(),\n body: z.string().max(50000).optional(),\n bodyFormat: z.enum(['text', 'markdown']).optional(),\n priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),\n objects: z.array(messageObjectSchema).optional(),\n attachmentIds: z.array(z.string().uuid()).optional(),\n actionData: messageActionDataSchema.optional(),\n sendViaEmail: z.boolean().optional(),\n isDraft: z.literal(false).optional(),\n}).superRefine((value, ctx) => {\n if (value.recipients) {\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n }\n\n validateDefaultWithObjectsPayload(value, ctx)\n})\n\nexport const listMessagesSchema = z.object({\n folder: z.enum(['inbox', 'sent', 'drafts', 'archived', 'all']).optional().default('inbox'),\n status: z.enum(['unread', 'read', 'archived']).optional(),\n type: z.string().optional(),\n visibility: z.enum(['public', 'internal']).optional(),\n sourceEntityType: z.string().optional(),\n sourceEntityId: z.string().uuid().optional(),\n externalEmail: z.string().email().optional(),\n hasObjects: z.string().transform(parseBooleanFlag).optional(),\n hasAttachments: z.string().transform(parseBooleanFlag).optional(),\n hasActions: z.string().transform(parseBooleanFlag).optional(),\n senderId: z.string().uuid().optional(),\n search: z.string().max(200).optional(),\n since: z.string().datetime().optional(),\n page: z.coerce.number().int().min(1).optional().default(1),\n pageSize: z.coerce.number().int().min(1).max(100).optional().default(20),\n})\n\nexport const forwardMessageSchema = z.object({\n recipients: z.array(messageRecipientSchema).min(1).max(100),\n body: z.string().max(50000).optional(),\n additionalBody: z.string().max(10000).optional(),\n includeAttachments: z.boolean().optional().default(true),\n sendViaEmail: z.boolean().optional().default(false),\n}).superRefine((value, ctx) => {\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n})\n\nexport const replyMessageSchema = z.object({\n body: z.string().min(1).max(50000),\n bodyFormat: z.enum(['text', 'markdown']).optional().default('text'),\n recipients: z.array(messageRecipientSchema).max(100).optional(),\n attachmentIds: z.array(z.string().uuid()).optional(),\n attachmentRecordId: z.string().min(1).max(255).optional(),\n replyAll: z.boolean().optional().default(false),\n sendViaEmail: z.boolean().optional().default(false),\n}).superRefine((value, ctx) => {\n if (!value.recipients) return\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n})\n\nexport const executeActionSchema = z.object({\n messageId: z.string().uuid(),\n actionId: z.string().min(1),\n payload: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const confirmMessageSchema = z.object({\n messageId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n confirmed: z.boolean().optional().default(true),\n})\n\nexport const messageObjectTypesQuerySchema = z.object({\n messageType: z.string().min(1),\n})\n\nexport const attachmentIdsPayloadSchema = z.object({\n attachmentIds: z.array(z.string().uuid()).min(1).max(100),\n})\n\nexport const unlinkAttachmentPayloadSchema = z.object({\n attachmentId: z.string().uuid().optional(),\n attachmentIds: z.array(z.string().uuid()).min(1).max(100).optional(),\n}).refine(\n (value) => Boolean(value.attachmentId || value.attachmentIds?.length),\n { message: 'attachmentId or attachmentIds is required' }\n)\n\nexport type ComposeMessageInput = z.infer<typeof composeMessageSchema>\nexport type UpdateDraftInput = z.infer<typeof updateDraftSchema>\nexport type ListMessagesInput = z.infer<typeof listMessagesSchema>\nexport type ForwardMessageInput = z.infer<typeof forwardMessageSchema>\nexport type ReplyMessageInput = z.infer<typeof replyMessageSchema>\nexport type ExecuteActionInput = z.infer<typeof executeActionSchema>\nexport type ConfirmMessageInput = z.infer<typeof confirmMessageSchema>\nexport type MessageObjectTypesQueryInput = z.infer<typeof messageObjectTypesQuerySchema>\nexport type AttachmentIdsPayload = z.infer<typeof attachmentIdsPayloadSchema>\nexport type UnlinkAttachmentPayload = z.infer<typeof unlinkAttachmentPayloadSchema>\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,wBAAwB;AACjC,SAAS,4BAA4B;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'\nimport { sanitizeRichTextHref } from '@open-mercato/shared/lib/html/sanitizeRichText'\nimport { channelTypeRequiresExternalEmail } from '../lib/channel-sender-identity'\n\nfunction collectDuplicateRecipientIds(\n recipients: Array<{ userId: string }>,\n): string[] {\n const seen = new Set<string>()\n const duplicates = new Set<string>()\n for (const recipient of recipients) {\n if (seen.has(recipient.userId)) {\n duplicates.add(recipient.userId)\n continue\n }\n seen.add(recipient.userId)\n }\n return Array.from(duplicates)\n}\n\nfunction validateDefaultWithObjectsPayload(\n value: {\n type?: string\n objects?: Array<{ actionRequired?: boolean; actionType?: string; actionLabel?: string }>\n },\n ctx: z.RefinementCtx,\n): void {\n if (value.type !== 'messages.defaultWithObjects') return\n\n if (!Array.isArray(value.objects) || value.objects.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects'],\n message: 'at least one object is required for messages.defaultWithObjects',\n })\n return\n }\n\n value.objects.forEach((object, index) => {\n if (object.actionRequired === true) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects', index, 'actionRequired'],\n message: 'actionRequired must be false for messages.defaultWithObjects',\n })\n }\n if (typeof object.actionType === 'string' && object.actionType.trim().length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects', index, 'actionType'],\n message: 'actionType is not allowed for messages.defaultWithObjects',\n })\n }\n if (typeof object.actionLabel === 'string' && object.actionLabel.trim().length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['objects', index, 'actionLabel'],\n message: 'actionLabel is not allowed for messages.defaultWithObjects',\n })\n }\n })\n}\n\nexport const messageRecipientSchema = z.object({\n userId: z.string().uuid(),\n type: z.enum(['to', 'cc', 'bcc']).optional().default('to'),\n})\n\nexport const messageObjectSchema = z.object({\n entityModule: z.string().min(1),\n entityType: z.string().min(1),\n entityId: z.string().uuid(),\n actionRequired: z.boolean().optional().default(false),\n actionType: z.string().optional(),\n actionLabel: z.string().optional(),\n})\n\nexport const messageActionSchema = z.object({\n id: z.string().min(1),\n label: z.string().min(1),\n labelKey: z.string().optional(),\n variant: z.enum(['default', 'secondary', 'destructive', 'outline', 'ghost']).optional(),\n icon: z.string().optional(),\n commandId: z.string().optional(),\n href: z\n .string()\n .optional()\n .refine(\n (value) => value == null || sanitizeRichTextHref(value) != null,\n { message: '[internal] message action href must be http(s), mailto, tel, or a relative path' },\n ),\n isTerminal: z.boolean().optional(),\n confirmRequired: z.boolean().optional(),\n confirmMessage: z.string().optional(),\n})\n\nexport const messageActionDataSchema = z.object({\n actions: z.array(messageActionSchema),\n primaryActionId: z.string().optional(),\n expiresAt: z.string().datetime().optional(),\n})\n\nconst composeMessageBaseSchema = z.object({\n type: z.string().optional().default('default'),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().min(1).optional(),\n sourceEntityId: z.string().uuid().optional(),\n externalEmail: z.string().email().optional(),\n externalName: z.string().min(1).max(255).optional(),\n /**\n * Channel type the message originates from, when the caller knows it (#4975).\n * Non-email channels (Discord, Slack, SMS\u2026) have senders with no address, so\n * `externalEmail` is not required for them. Resolved server-side \u2014 the HTTP\n * route strips any client-sent value and derives it from the referenced\n * conversation or parent message, so a caller cannot waive the requirement by\n * asserting its own channel type.\n */\n sourceChannelType: z.string().min(1).max(64).optional(),\n recipients: z.array(messageRecipientSchema).max(100).optional().default([]),\n subject: z.string().max(500).optional().default(''),\n body: z.string().max(50000).optional().default(''),\n bodyFormat: z.enum(['text', 'markdown']).optional().default('text'),\n priority: z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal'),\n objects: z.array(messageObjectSchema).optional(),\n attachmentIds: z.array(z.string().uuid()).optional(),\n attachmentRecordId: z.string().min(1).max(255).optional(),\n actionData: messageActionDataSchema.optional(),\n sendViaEmail: z.boolean().optional().default(false),\n parentMessageId: z.string().uuid().optional(),\n isDraft: z.boolean().optional().default(false),\n})\n\ntype ComposeMessageRefinementValue = Omit<\n z.infer<typeof composeMessageBaseSchema>,\n 'sourceChannelType'\n> & { sourceChannelType?: string }\n\nfunction refineComposeMessage(value: ComposeMessageRefinementValue, ctx: z.RefinementCtx): void {\n const isDraft = value.isDraft ?? false\n const visibility = value.visibility ?? 'internal'\n const recipientCount = value.recipients.length\n const hasExternalEmail = Boolean(value.externalEmail?.trim())\n const hasSubject = value.subject.trim().length > 0\n const hasBody = value.body.trim().length > 0\n\n if (!isDraft) {\n if (visibility === 'public') {\n // #4975: an external correspondent is only guaranteed to have an address\n // on an email-typed channel. `channelTypeRequiresExternalEmail` fails\n // closed, so an unknown or absent channel type keeps the original rule.\n if (!hasExternalEmail && channelTypeRequiresExternalEmail(value.sourceChannelType)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['externalEmail'],\n message: 'externalEmail is required when visibility is public',\n })\n }\n if (recipientCount > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipients must be empty when visibility is public',\n })\n }\n } else if (recipientCount === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'at least one recipient is required when visibility is internal',\n })\n }\n\n if (!hasSubject) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['subject'],\n message: 'subject is required',\n })\n }\n if (!hasBody) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['body'],\n message: 'body is required',\n })\n }\n }\n\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n\n validateDefaultWithObjectsPayload(value, ctx)\n}\n\n/**\n * Full compose contract, including the server-resolved `sourceChannelType`.\n * Used by the `messages.messages.compose` command and by the HTTP route AFTER\n * it has resolved the channel type itself.\n */\nexport const composeMessageSchema = composeMessageBaseSchema.superRefine(refineComposeMessage)\n\n/**\n * Client-facing compose contract \u2014 the same rules minus `sourceChannelType`,\n * which is never accepted from a request body (#4975). Published in OpenAPI so\n * the documented request shape matches what `POST /api/messages` actually reads:\n * the route discards any client-sent channel type, resolves the real one from\n * the referenced conversation or parent message, and only then validates against\n * {@link composeMessageSchema}.\n */\nexport const composeMessageRequestSchema = composeMessageBaseSchema\n .omit({ sourceChannelType: true })\n .superRefine(refineComposeMessage)\n\nexport const updateDraftSchema = z.object({\n type: z.string().optional(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().min(1).optional(),\n sourceEntityId: z.string().uuid().optional(),\n externalEmail: z.string().email().optional(),\n externalName: z.string().min(1).max(255).optional(),\n recipients: z.array(messageRecipientSchema).optional(),\n subject: z.string().max(500).optional(),\n body: z.string().max(50000).optional(),\n bodyFormat: z.enum(['text', 'markdown']).optional(),\n priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),\n objects: z.array(messageObjectSchema).optional(),\n attachmentIds: z.array(z.string().uuid()).optional(),\n actionData: messageActionDataSchema.optional(),\n sendViaEmail: z.boolean().optional(),\n isDraft: z.literal(false).optional(),\n}).superRefine((value, ctx) => {\n if (value.recipients) {\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n }\n\n validateDefaultWithObjectsPayload(value, ctx)\n})\n\nexport const listMessagesSchema = z.object({\n folder: z.enum(['inbox', 'sent', 'drafts', 'archived', 'all']).optional().default('inbox'),\n status: z.enum(['unread', 'read', 'archived']).optional(),\n type: z.string().optional(),\n visibility: z.enum(['public', 'internal']).optional(),\n sourceEntityType: z.string().optional(),\n sourceEntityId: z.string().uuid().optional(),\n externalEmail: z.string().email().optional(),\n hasObjects: z.string().transform(parseBooleanFlag).optional(),\n hasAttachments: z.string().transform(parseBooleanFlag).optional(),\n hasActions: z.string().transform(parseBooleanFlag).optional(),\n senderId: z.string().uuid().optional(),\n search: z.string().max(200).optional(),\n since: z.string().datetime().optional(),\n page: z.coerce.number().int().min(1).optional().default(1),\n pageSize: z.coerce.number().int().min(1).max(100).optional().default(20),\n})\n\nexport const forwardMessageSchema = z.object({\n recipients: z.array(messageRecipientSchema).min(1).max(100),\n body: z.string().max(50000).optional(),\n additionalBody: z.string().max(10000).optional(),\n includeAttachments: z.boolean().optional().default(true),\n sendViaEmail: z.boolean().optional().default(false),\n}).superRefine((value, ctx) => {\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n})\n\nexport const replyMessageSchema = z.object({\n body: z.string().min(1).max(50000),\n bodyFormat: z.enum(['text', 'markdown']).optional().default('text'),\n recipients: z.array(messageRecipientSchema).max(100).optional(),\n attachmentIds: z.array(z.string().uuid()).optional(),\n attachmentRecordId: z.string().min(1).max(255).optional(),\n replyAll: z.boolean().optional().default(false),\n sendViaEmail: z.boolean().optional().default(false),\n}).superRefine((value, ctx) => {\n if (!value.recipients) return\n const duplicateRecipientIds = collectDuplicateRecipientIds(value.recipients)\n if (duplicateRecipientIds.length > 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipients'],\n message: 'recipient user ids must be unique',\n })\n }\n})\n\nexport const executeActionSchema = z.object({\n messageId: z.string().uuid(),\n actionId: z.string().min(1),\n payload: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const confirmMessageSchema = z.object({\n messageId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n confirmed: z.boolean().optional().default(true),\n})\n\nexport const messageObjectTypesQuerySchema = z.object({\n messageType: z.string().min(1),\n})\n\nexport const attachmentIdsPayloadSchema = z.object({\n attachmentIds: z.array(z.string().uuid()).min(1).max(100),\n})\n\nexport const unlinkAttachmentPayloadSchema = z.object({\n attachmentId: z.string().uuid().optional(),\n attachmentIds: z.array(z.string().uuid()).min(1).max(100).optional(),\n}).refine(\n (value) => Boolean(value.attachmentId || value.attachmentIds?.length),\n { message: 'attachmentId or attachmentIds is required' }\n)\n\nexport type ComposeMessageInput = z.infer<typeof composeMessageSchema>\nexport type UpdateDraftInput = z.infer<typeof updateDraftSchema>\nexport type ListMessagesInput = z.infer<typeof listMessagesSchema>\nexport type ForwardMessageInput = z.infer<typeof forwardMessageSchema>\nexport type ReplyMessageInput = z.infer<typeof replyMessageSchema>\nexport type ExecuteActionInput = z.infer<typeof executeActionSchema>\nexport type ConfirmMessageInput = z.infer<typeof confirmMessageSchema>\nexport type MessageObjectTypesQueryInput = z.infer<typeof messageObjectTypesQuerySchema>\nexport type AttachmentIdsPayload = z.infer<typeof attachmentIdsPayloadSchema>\nexport type UnlinkAttachmentPayload = z.infer<typeof unlinkAttachmentPayloadSchema>\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|