@open-mercato/core 0.9.1-develop.7515.1.e2712c8b00 → 0.9.1-develop.7516.1.176184247d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/communication_channels/lib/send-as-user.js.map +2 -2
  3. package/dist/modules/customers/api/people/[id]/emails/route.js +44 -1
  4. package/dist/modules/customers/api/people/[id]/emails/route.js.map +2 -2
  5. package/dist/modules/customers/lib/personEmailThreads.js +36 -1
  6. package/dist/modules/customers/lib/personEmailThreads.js.map +2 -2
  7. package/dist/modules/messages/api/[id]/route.js +3 -9
  8. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  9. package/dist/modules/messages/api/route.js +33 -16
  10. package/dist/modules/messages/api/route.js.map +2 -2
  11. package/dist/modules/messages/commands/messages.js.map +2 -2
  12. package/dist/modules/messages/lib/routeHelpers.js +40 -1
  13. package/dist/modules/messages/lib/routeHelpers.js.map +2 -2
  14. package/package.json +7 -7
  15. package/src/modules/communication_channels/lib/send-as-user.ts +4 -0
  16. package/src/modules/customers/api/people/[id]/emails/route.ts +67 -1
  17. package/src/modules/customers/i18n/de.json +1 -0
  18. package/src/modules/customers/i18n/en.json +1 -0
  19. package/src/modules/customers/i18n/es.json +1 -0
  20. package/src/modules/customers/i18n/ko.json +1 -0
  21. package/src/modules/customers/i18n/pl.json +1 -0
  22. package/src/modules/customers/lib/personEmailThreads.ts +61 -0
  23. package/src/modules/messages/api/[id]/route.ts +2 -9
  24. package/src/modules/messages/api/route.ts +42 -20
  25. package/src/modules/messages/commands/messages.ts +7 -0
  26. package/src/modules/messages/i18n/de.json +1 -0
  27. package/src/modules/messages/i18n/en.json +1 -0
  28. package/src/modules/messages/i18n/es.json +1 -0
  29. package/src/modules/messages/i18n/ko.json +1 -0
  30. package/src/modules/messages/i18n/pl.json +1 -0
  31. package/src/modules/messages/lib/routeHelpers.ts +84 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/commands/messages.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { registerCommand, type CommandHandler, type CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Message, MessageObject, MessageRecipient, type MessageActionData } from '../data/entities'\nimport { emitMessagesEvent } from '../events'\nimport {\n composeMessageSchema,\n forwardMessageSchema,\n replyMessageSchema,\n updateDraftSchema,\n} from '../data/validators'\nimport { linkAttachmentsToMessage, linkLibraryAttachmentsToMessage, copyAttachmentsForForwardMessages } from '../lib/attachments'\nimport {\n EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE,\n resolveActorFeatures,\n resolveMessageChannelThreadAccess,\n} from '../lib/channelThreadAccess'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID, MESSAGE_ENTITY_ID, SYSTEM_SENDER_USER_ID } from '../lib/constants'\nimport { canUseChannelThreadFallback } from '../lib/routeHelpers'\nimport { getMessageTypeOrDefault } from '../lib/message-types-registry'\nimport { validateMessageObjectsForType } from '../lib/object-validation'\nimport { buildForwardBodyFromLegacyInput, buildForwardPreviewFromThreadSlice, buildForwardThreadSlice } from '../lib/forwarding'\nimport {\n assertOrganizationAccess,\n loadMessageAggregateSnapshot,\n restoreMessageAggregateSnapshot,\n type MessageAggregateSnapshot,\n type MessageScopeInput,\n} from './shared'\n\ntype MessageSentEventPayload = {\n messageId: string\n senderUserId: string\n recipientUserIds: string[]\n sendViaEmail: boolean\n /**\n * The message arrived from an external channel (#6093). Its `senderUserId`\n * is the channel's system user and its real correspondent lives in\n * `externalName` / `externalEmail`; subscribers that show a sender read it\n * from there.\n */\n inboundFromChannel?: boolean\n externalEmail?: string | null\n forwardedFrom?: string\n replyTo?: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype ContainerWithResolve = {\n resolve: (name: string) => unknown\n}\n\nasync function emitMessageSentEvent(_container: ContainerWithResolve, payload: MessageSentEventPayload) {\n await emitMessagesEvent('messages.message.sent', payload, { persistent: true })\n}\n\nasync function emitMessageDeletedEvent(_container: ContainerWithResolve, payload: {\n messageId: string\n actorUserId: string\n target: 'sender' | 'recipient'\n tenantId: string\n organizationId: string | null\n}) {\n await emitMessagesEvent(\n 'messages.message.deleted',\n { ...payload, recipientUserId: payload.actorUserId },\n { persistent: true },\n )\n}\n\nasync function emitMessageGloballyDeletedEvent(_container: ContainerWithResolve, payload: {\n messageId: string\n actorUserId: string\n recipientUserIds: string[]\n tenantId: string\n organizationId: string | null\n}) {\n const audience = Array.from(new Set([payload.actorUserId, ...payload.recipientUserIds]))\n await emitMessagesEvent(\n 'messages.message.deleted',\n {\n messageId: payload.messageId,\n actorUserId: payload.actorUserId,\n target: 'global',\n recipientUserIds: audience,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n },\n { persistent: true },\n )\n}\n\nasync function emitMessageIndexUpsert(\n container: ContainerWithResolve,\n payload: {\n messageId: string\n tenantId: string\n organizationId: string | null\n },\n) {\n let bus: { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> } | null = null\n try {\n bus = container.resolve('eventBus') as { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> }\n } catch {\n bus = null\n }\n\n if (!bus) return\n\n await bus.emitEvent(\n 'query_index.upsert_one',\n {\n entityType: MESSAGE_ENTITY_ID,\n recordId: payload.messageId,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n crudAction: 'updated',\n coverageBaseDelta: 1,\n },\n {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n },\n ).catch(() => undefined)\n}\n\nconst scopeSchema = z.object({\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n userId: z.string().uuid(),\n})\n\nconst composeCommandSchema = composeMessageSchema.safeExtend({\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n // Optional dedup key (inbound email ingest sets it; other callers leave it\n // undefined). When set, a re-issued compose returns the first message instead\n // of creating a duplicate.\n idempotencyKey: z.string().min(1).max(255).optional(),\n})\n\nconst updateDraftCommandSchema = updateDraftSchema.safeExtend({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\nconst replyCommandSchema = replyMessageSchema.safeExtend({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\nconst forwardCommandSchema = forwardMessageSchema.safeExtend({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\nconst deleteForActorCommandSchema = z.object({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\ntype ComposeCommandInput = z.infer<typeof composeCommandSchema>\ntype UpdateDraftCommandInput = z.infer<typeof updateDraftCommandSchema>\ntype ReplyCommandInput = z.infer<typeof replyCommandSchema>\ntype ForwardCommandInput = z.infer<typeof forwardCommandSchema>\ntype DeleteForActorCommandInput = z.infer<typeof deleteForActorCommandSchema>\n\ntype MessageDeleteUndoState = {\n messageId: string\n messageDeletedAt: string | null\n recipientId: string | null\n recipientStatus: 'unread' | 'read' | 'archived' | 'deleted' | null\n recipientDeletedAt: string | null\n}\n\nfunction toIso(value: Date | null | undefined): string | null {\n return value ? value.toISOString() : null\n}\n\nfunction toDate(value: string | null | undefined): Date | null {\n if (!value) return null\n return new Date(value)\n}\n\nfunction buildReplySubject(subject: string): string {\n if (/^re:\\s*/i.test(subject)) return subject\n return `Re: ${subject}`\n}\n\nasync function requireMessageById(\n em: EntityManager,\n scope: MessageScopeInput,\n messageId: string,\n) {\n const message = await findOneWithDecryption(\n em,\n Message,\n {\n id: messageId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n )\n if (!message) throw new Error('Message not found')\n assertOrganizationAccess(scope, message)\n return message\n}\n\nfunction isUniqueViolation(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false\n const code = (err as { code?: string }).code\n if (code === '23505') return true\n const message = (err as { message?: string }).message\n return typeof message === 'string' && /duplicate key value|unique constraint/i.test(message)\n}\n\ntype ComposeMessageResult = {\n id: string\n threadId: string | null\n externalEmail: string | null\n isDraft: boolean\n recipientUserIds: string[]\n /**\n * True when this was an idempotent replay \u2014 an existing message was returned\n * and nothing was written. Signals `buildLog` to skip the audit/undo entry.\n */\n deduplicated?: boolean\n}\n\nasync function buildComposeResultFromExisting(\n em: EntityManager,\n message: Message,\n): Promise<ComposeMessageResult> {\n const recipients = await findWithDecryption(\n em,\n MessageRecipient,\n { messageId: message.id, deletedAt: null },\n undefined,\n { tenantId: message.tenantId, organizationId: message.organizationId ?? null },\n )\n return {\n id: message.id,\n threadId: message.threadId ?? null,\n externalEmail: message.externalEmail ?? null,\n isDraft: message.isDraft,\n recipientUserIds: recipients.map((recipient) => recipient.recipientUserId),\n deduplicated: true,\n }\n}\n\nconst composeMessageCommand: CommandHandler<unknown, { id: string; threadId: string | null; externalEmail: string | null; isDraft: boolean; recipientUserIds: string[]; deduplicated?: boolean }> = {\n id: 'messages.messages.compose',\n async execute(rawInput, ctx) {\n const input = composeCommandSchema.parse(rawInput)\n if (input.objects?.length) {\n const objectValidationError = validateMessageObjectsForType(input.type, input.objects)\n if (objectValidationError) throw new Error(objectValidationError)\n }\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = { tenantId: input.tenantId, organizationId: input.organizationId }\n\n // Idempotency fast-path: a retried inbound-email ingest re-issues compose\n // for the same source message (the first attempt committed the message but a\n // downstream transient failure rolled the ingest back). Return that message\n // so the retry cannot create a duplicate.\n if (input.idempotencyKey) {\n const existing = await findOneWithDecryption(\n em,\n Message,\n { tenantId: input.tenantId, idempotencyKey: input.idempotencyKey },\n undefined,\n scope,\n )\n if (existing) return buildComposeResultFromExisting(em, existing)\n }\n\n let messageId = ''\n let responseThreadId: string | null = null\n let responseExternalEmail: string | null = null\n\n const composeTx = em.transactional(async (trx) => {\n const threadId = input.parentMessageId\n ? (\n await findOneWithDecryption(\n trx,\n Message,\n {\n id: input.parentMessageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n )?.threadId ?? input.parentMessageId\n : undefined\n\n const isPublicVisibility = input.visibility === 'public'\n // #6093: a channel-ingested message was RECEIVED, not sent. Ingest passes\n // `sendViaEmail: false` on purpose; forcing it for public visibility would\n // mail the assignee a copy and echo the message to its external sender\n // (#6089) the moment the recipients waiver lets it compose. #6090 removes\n // the forcing for every caller; until then it is skipped here for the\n // ingest path only.\n const sendViaEmail = isPublicVisibility && !input.inboundFromChannel ? true : input.sendViaEmail\n const message = trx.create(Message, {\n type: input.type,\n visibility: input.visibility ?? null,\n sourceEntityType: input.sourceEntityType,\n sourceEntityId: input.sourceEntityId,\n externalEmail: input.externalEmail,\n externalName: input.externalName,\n threadId: threadId ?? undefined,\n parentMessageId: input.parentMessageId,\n senderUserId: input.userId,\n subject: input.subject,\n body: input.body,\n bodyFormat: input.bodyFormat,\n priority: input.priority,\n status: input.isDraft ? 'draft' : 'sent',\n isDraft: input.isDraft ?? false,\n // #6095: channel ingest passes the provider's timestamp so an imported\n // mailbox sorts by when the mail was received, not when it was imported.\n sentAt: input.isDraft ? null : input.sentAt ?? new Date(),\n actionData: input.actionData as MessageActionData | undefined,\n sendViaEmail,\n idempotencyKey: input.idempotencyKey ?? null,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n await trx.persist(message).flush()\n if (!threadId && !input.isDraft && !message.threadId) {\n message.threadId = message.id\n await trx.flush()\n }\n\n for (const recipient of input.recipients) {\n trx.persist(trx.create(MessageRecipient, {\n messageId: message.id,\n recipientUserId: recipient.userId,\n recipientType: recipient.type,\n status: 'unread',\n }))\n }\n\n if (input.objects) {\n for (const obj of input.objects) {\n trx.persist(trx.create(MessageObject, {\n messageId: message.id,\n entityModule: obj.entityModule,\n entityType: obj.entityType,\n entityId: obj.entityId,\n actionRequired: obj.actionRequired,\n actionType: obj.actionType,\n actionLabel: obj.actionLabel,\n }))\n }\n }\n\n await trx.flush()\n\n if (input.attachmentIds?.length) {\n await linkAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentIds,\n input.organizationId,\n input.tenantId,\n )\n }\n\n if (input.attachmentRecordId) {\n await linkLibraryAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentRecordId,\n input.organizationId,\n input.tenantId,\n )\n }\n\n messageId = message.id\n responseThreadId = message.threadId ?? null\n responseExternalEmail = message.externalEmail ?? null\n })\n try {\n await composeTx\n } catch (err) {\n // Lost a concurrent race on the same idempotency key \u2014 return the message\n // the winning compose created. A propagated 23505 would otherwise be\n // classified permanent and dead-letter the inbound mail.\n if (input.idempotencyKey && isUniqueViolation(err)) {\n const existing = await findOneWithDecryption(\n em.fork(),\n Message,\n { tenantId: input.tenantId, idempotencyKey: input.idempotencyKey },\n undefined,\n scope,\n )\n if (existing) return buildComposeResultFromExisting(em.fork(), existing)\n }\n throw err\n }\n\n if (!input.isDraft) {\n await emitMessageSentEvent(ctx.container, {\n messageId,\n senderUserId: input.userId,\n recipientUserIds: input.recipients.map((recipient) => recipient.userId),\n sendViaEmail:\n input.visibility === 'public' && !input.inboundFromChannel ? true : input.sendViaEmail,\n inboundFromChannel: input.inboundFromChannel === true,\n externalEmail: responseExternalEmail,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n }\n\n await emitMessageIndexUpsert(ctx.container, {\n messageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n return {\n id: messageId,\n threadId: responseThreadId,\n externalEmail: responseExternalEmail,\n isDraft: input.isDraft,\n recipientUserIds: input.recipients.map((recipient) => recipient.userId),\n }\n },\n async captureAfter(_input, result, ctx) {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, result.id)\n },\n buildLog: async ({ input, result, snapshots }) => {\n // Idempotent replay: execute returned a pre-existing message without writing\n // anything. Skip the audit entry entirely \u2014 logging it as a fresh \"Compose\n // message\" would both misrepresent the dedup and expose an undo that\n // soft-deletes a legitimately-received inbound message.\n if (result.deduplicated) return { skipLog: true }\n const parsed = composeCommandSchema.parse(input)\n return {\n actionLabel: parsed.isDraft ? 'Create draft message' : 'Compose message',\n resourceKind: 'messages.message',\n resourceId: result.id,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const after = undo?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: after.message.id })\n if (!message) return\n message.deletedAt = new Date()\n await em.flush()\n if (!after.message.isDraft) {\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: after.message.id,\n actorUserId: after.message.senderUserId,\n recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),\n tenantId: after.message.tenantId,\n organizationId: after.message.organizationId,\n })\n }\n },\n}\n\nconst updateDraftCommand: CommandHandler<unknown, { ok: true; id: string }> = {\n id: 'messages.messages.update_draft',\n async prepare(rawInput, ctx) {\n const input = updateDraftCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadMessageAggregateSnapshot(em, input.messageId, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const input = updateDraftCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await requireMessageById(em, input, input.messageId)\n\n if (message.senderUserId !== input.userId) throw new Error('Access denied')\n if (!message.isDraft) throw new Error('Only draft messages can be edited')\n\n const isSending = input.isDraft === false\n const preloadedRecipients = isSending && !input.recipients\n ? await findWithDecryption(\n em,\n MessageRecipient,\n { messageId: message.id, deletedAt: null },\n undefined,\n {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n },\n )\n : null\n\n const nextMessageType = input.type ?? message.type\n if (input.objects) {\n const objectValidationError = validateMessageObjectsForType(nextMessageType, input.objects)\n if (objectValidationError) throw new Error(objectValidationError)\n } else if (input.type !== undefined) {\n const existingObjects = await em.find(MessageObject, { messageId: message.id })\n if (existingObjects.length > 0) {\n const objectValidationError = validateMessageObjectsForType(\n nextMessageType,\n existingObjects.map((item) => ({\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n })),\n )\n if (objectValidationError) throw new Error(objectValidationError)\n }\n }\n\n // Validate the send transition BEFORE the atomic-flush block so a rejected\n // send never mutates or flushes anything (everything checked here is known\n // from the parsed input + the loaded message).\n if (isSending) {\n const finalVisibility = input.visibility ?? message.visibility\n const finalSubject = input.subject ?? message.subject\n const finalBody = input.body ?? message.body\n const finalRecipientCount = input.recipients\n ? input.recipients.length\n : (preloadedRecipients?.length ?? 0)\n if (finalVisibility !== 'public' && finalRecipientCount === 0) {\n throw new Error('at least one recipient is required')\n }\n if (!finalSubject?.trim()) throw new Error('subject is required')\n if (!finalBody?.trim()) throw new Error('body is required')\n }\n\n // Phase 1 mutates the managed `message` scalars; the per-phase flush issues\n // those changes before phase 2's interleaved reads run (e.g.\n // `linkAttachmentsToMessage` issues an `em.find`). Under MikroORM v7 a read\n // between a scalar mutation and the terminal flush on the same EntityManager\n // silently drops the pending changeset, so keeping the scalar edits in their\n // own phase guarantees subject/body/etc. are persisted (SPEC-018 Problem 1).\n await withAtomicFlush(em, [() => {\n if (input.type !== undefined) message.type = input.type\n if (input.visibility !== undefined) message.visibility = input.visibility\n if (input.sourceEntityType !== undefined) message.sourceEntityType = input.sourceEntityType\n if (input.sourceEntityId !== undefined) message.sourceEntityId = input.sourceEntityId\n if (input.externalEmail !== undefined) message.externalEmail = input.externalEmail\n if (input.externalName !== undefined) message.externalName = input.externalName\n if (input.subject !== undefined) message.subject = input.subject\n if (input.body !== undefined) message.body = input.body\n if (input.bodyFormat !== undefined) message.bodyFormat = input.bodyFormat\n if (input.priority !== undefined) message.priority = input.priority\n if (input.actionData !== undefined) message.actionData = input.actionData\n if (input.sendViaEmail !== undefined) message.sendViaEmail = input.sendViaEmail\n }, async () => {\n if (input.recipients) {\n await em.nativeDelete(MessageRecipient, { messageId: message.id })\n for (const recipient of input.recipients) {\n em.persist(em.create(MessageRecipient, {\n messageId: message.id,\n recipientUserId: recipient.userId,\n recipientType: recipient.type,\n status: 'unread',\n }))\n }\n }\n\n if (input.objects) {\n await em.nativeDelete(MessageObject, { messageId: message.id })\n for (const object of input.objects) {\n em.persist(em.create(MessageObject, {\n messageId: message.id,\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n actionRequired: object.actionRequired,\n actionType: object.actionType,\n actionLabel: object.actionLabel,\n }))\n }\n }\n\n if (input.attachmentIds) {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n if (input.attachmentIds.length === 0) {\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: message.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n } else {\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: message.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n id: { $nin: input.attachmentIds },\n })\n }\n await linkAttachmentsToMessage(\n em,\n message.id,\n input.attachmentIds,\n input.organizationId,\n input.tenantId,\n )\n }\n\n if (isSending) {\n // Send transition was validated before the block; just apply the\n // status mutations here (persisted by this phase's boundary flush).\n message.isDraft = false\n message.status = 'sent'\n message.sentAt = new Date()\n if (!message.threadId) message.threadId = message.id\n }\n }], { transaction: true })\n\n await emitMessageIndexUpsert(ctx.container, {\n messageId: message.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n if (isSending) {\n const recipientUserIds = input.recipients\n ? input.recipients.map((r) => r.userId)\n : (preloadedRecipients ?? []).map((r) => r.recipientUserId)\n const resolvedVisibility = input.visibility ?? message.visibility\n const resolvedSendViaEmail = input.sendViaEmail ?? message.sendViaEmail\n await emitMessageSentEvent(ctx.container, {\n messageId: message.id,\n senderUserId: input.userId,\n recipientUserIds,\n sendViaEmail: resolvedVisibility === 'public' ? true : resolvedSendViaEmail,\n externalEmail: message.externalEmail ?? null,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n }\n\n return { ok: true, id: message.id }\n },\n async captureAfter(rawInput, _result, ctx) {\n const input = updateDraftCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, input.messageId, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n },\n buildLog: async ({ input, snapshots }) => {\n const parsed = updateDraftCommandSchema.parse(input)\n return {\n actionLabel: parsed.isDraft === false ? 'Send draft message' : 'Update draft message',\n resourceKind: 'messages.message',\n resourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n before: (snapshots.before as MessageAggregateSnapshot | undefined) ?? null,\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotBefore: snapshots.before ?? null,\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const before = undo?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreMessageAggregateSnapshot(em, before)\n },\n}\n\n/**\n * The channel-thread fallback for a non-participant acting on `original`\n * (#5535): the channels hub decides whether the caller may work the thread.\n * An internal thread resolves to `null` and denies, leaving the participant\n * rule in force. Only an explicitly public message qualifies \u2014 channel access\n * never opens another operator's internal note, matching the detail read.\n *\n * The same `messages.view` gate as the read routes applies, because the hub\n * grants every shared channel regardless of features: without it, a caller\n * holding only `messages.compose` could forward a conversation it may not read\n * to itself and read it as the recipient.\n */\nasync function canActOnChannelThreadMessage(\n ctx: CommandRuntimeContext,\n input: { tenantId: string; organizationId?: string | null; userId: string },\n original: Message,\n): Promise<boolean> {\n if (original.visibility !== 'public') return false\n const scope = { tenantId: input.tenantId, organizationId: input.organizationId ?? null, userId: input.userId }\n if (!(await canUseChannelThreadFallback(ctx, scope))) return false\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: input.tenantId, organizationId: input.organizationId ?? null },\n { messageThreadId: original.threadId ?? original.id },\n { userId: input.userId, features: resolveActorFeatures(ctx.auth) },\n )\n return channelThread?.canAccess === true\n}\n\nasync function isOnChannelThread(\n ctx: CommandRuntimeContext,\n input: { tenantId: string; organizationId?: string | null; userId: string },\n original: Message,\n): Promise<boolean> {\n if (original.sourceEntityType === EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE) return true\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: input.tenantId, organizationId: input.organizationId ?? null },\n { messageThreadId: original.threadId ?? original.id },\n { userId: input.userId, features: resolveActorFeatures(ctx.auth) },\n )\n return channelThread !== null\n}\n\nconst replyMessageCommand: CommandHandler<unknown, { id: string; externalEmail: string | null; recipientUserIds: string[] }> = {\n id: 'messages.messages.reply',\n async execute(rawInput, ctx) {\n const input = replyCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const original = await requireMessageById(em, input, input.messageId)\n const ownRecipient = await em.findOne(MessageRecipient, {\n messageId: original.id,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n if (original.senderUserId !== input.userId && !ownRecipient) {\n // A message that arrived over a communication channel has no platform\n // participant to match: its sender is the channel system user and an\n // unassigned conversation produces no recipient rows, so the\n // sender-or-recipient test denies every operator \u2014 a tenant admin\n // included (#5535). For a thread the channels hub owns, that hub's own\n // access rule is the applicable one; an internal thread resolves to\n // `null` here and keeps the participant rule unchanged.\n if (!(await canActOnChannelThreadMessage(ctx, input, original))) throw new Error('Access denied')\n }\n\n const messageType = getMessageTypeOrDefault(original.type)\n if (messageType.allowReply === false) throw new Error('Reply is not allowed for this message type')\n\n const isReplyRecipient = (userId: string) => userId !== input.userId && userId !== SYSTEM_SENDER_USER_ID\n const recipientIds = new Set(\n (input.recipients ?? [])\n .map((recipient) => recipient.userId)\n .filter(isReplyRecipient),\n )\n\n if (recipientIds.size === 0) {\n const originalRecipients = await em.find(MessageRecipient, { messageId: original.id, deletedAt: null })\n if (input.replyAll) {\n if (isReplyRecipient(original.senderUserId)) recipientIds.add(original.senderUserId)\n for (const recipient of originalRecipients) {\n if (isReplyRecipient(recipient.recipientUserId)) recipientIds.add(recipient.recipientUserId)\n }\n } else if (original.senderUserId !== input.userId) {\n if (isReplyRecipient(original.senderUserId)) recipientIds.add(original.senderUserId)\n } else {\n for (const recipient of originalRecipients) {\n if (isReplyRecipient(recipient.recipientUserId)) {\n recipientIds.add(recipient.recipientUserId)\n break\n }\n }\n }\n\n if (recipientIds.size === 0 && original.senderUserId === input.userId) {\n recipientIds.add(input.userId)\n }\n }\n if (recipientIds.size === 0 && original.senderUserId !== SYSTEM_SENDER_USER_ID) {\n throw new Error('No recipients available for reply')\n }\n\n let messageId = ''\n let responseExternalEmail: string | null = null\n await em.transactional(async (trx) => {\n const message = trx.create(Message, {\n type: original.type,\n visibility: original.visibility ?? null,\n sourceEntityType: original.sourceEntityType,\n sourceEntityId: original.sourceEntityId,\n externalEmail: original.externalEmail,\n externalName: original.externalName,\n threadId: original.threadId ?? original.id,\n parentMessageId: original.id,\n senderUserId: input.userId,\n subject: buildReplySubject(original.subject),\n body: input.body,\n bodyFormat: input.bodyFormat,\n priority: original.priority,\n status: 'sent',\n isDraft: false,\n sentAt: new Date(),\n sendViaEmail: input.sendViaEmail,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await trx.persist(message).flush()\n for (const recipientUserId of recipientIds) {\n trx.persist(trx.create(MessageRecipient, {\n messageId: message.id,\n recipientUserId,\n recipientType: 'to',\n status: 'unread',\n }))\n }\n await trx.flush()\n if (input.attachmentIds?.length) {\n await linkAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentIds,\n input.organizationId,\n input.tenantId,\n )\n }\n if (input.attachmentRecordId) {\n await linkLibraryAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentRecordId,\n input.organizationId,\n input.tenantId,\n )\n }\n messageId = message.id\n responseExternalEmail = message.externalEmail ?? null\n })\n\n await emitMessageSentEvent(ctx.container, {\n messageId,\n senderUserId: input.userId,\n recipientUserIds: Array.from(recipientIds),\n sendViaEmail: input.sendViaEmail,\n externalEmail: responseExternalEmail,\n replyTo: original.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await emitMessageIndexUpsert(ctx.container, {\n messageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n return {\n id: messageId,\n externalEmail: responseExternalEmail,\n recipientUserIds: Array.from(recipientIds),\n }\n },\n async captureAfter(_input, result, ctx) {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, result.id)\n },\n buildLog: async ({ input, result, snapshots }) => {\n const parsed = replyCommandSchema.parse(input)\n return {\n actionLabel: 'Reply to message',\n resourceKind: 'messages.message',\n resourceId: result.id,\n parentResourceKind: 'messages.message',\n parentResourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const after = undo?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: after.message.id })\n if (!message) return\n message.deletedAt = new Date()\n await em.flush()\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: after.message.id,\n actorUserId: after.message.senderUserId,\n recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),\n tenantId: after.message.tenantId,\n organizationId: after.message.organizationId,\n })\n },\n}\n\nconst forwardMessageCommand: CommandHandler<unknown, { id: string; externalEmail: string | null; recipientUserIds: string[] }> = {\n id: 'messages.messages.forward',\n async execute(rawInput, ctx) {\n const input = forwardCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const original = await requireMessageById(em, input, input.messageId)\n const isRecipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n // Same fallback as the reply command (#6355): an operator who may answer an\n // inbound channel message may also forward it internally.\n const viaChannelThread = original.senderUserId !== input.userId && !isRecipient\n if (viaChannelThread && !(await canActOnChannelThreadMessage(ctx, input, original))) {\n throw new Error('Access denied')\n }\n\n const messageType = getMessageTypeOrDefault(original.type)\n if (messageType.allowForward === false) throw new Error('Forward is not allowed for this message type')\n\n const originalObjects = await em.find(MessageObject, { messageId: input.messageId })\n const forwardThreadSlice = await buildForwardThreadSlice(em, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: input.userId,\n }, original, { includePublicThreadMessages: viaChannelThread })\n const generatedPreview = await buildForwardPreviewFromThreadSlice(em, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: input.userId,\n }, original, forwardThreadSlice)\n const forwardedBody = typeof input.body === 'string'\n ? input.body\n : buildForwardBodyFromLegacyInput(generatedPreview.body, input.additionalBody)\n // A forward of a channel conversation is addressed to colleagues, never to\n // the correspondent. The channels bridge already refuses `forwardedFrom`, but\n // the messages email path would still mail `externalEmail` on\n // `sendViaEmail` \u2014 so the customer's address is not carried onto it.\n // It is also filed as internal: replies copy their parent's visibility and\n // carry no `forwardedFrom`, so a public forward made every reply under it\n // deliverable to the correspondent (#6431, #6428). The bridge routes on the\n // thread, not the source, and some public messages on a channel thread\n // carry no source type (an AI or parented compose), so the thread decides.\n const keepsExternalCorrespondent = !(await isOnChannelThread(ctx, input, original))\n let newMessageId = ''\n let responseExternalEmail: string | null = null\n await em.transactional(async (trx) => {\n const newMessage = trx.create(Message, {\n type: original.type,\n visibility: keepsExternalCorrespondent ? original.visibility ?? null : 'internal',\n sourceEntityType: original.sourceEntityType,\n sourceEntityId: original.sourceEntityId,\n externalEmail: keepsExternalCorrespondent ? original.externalEmail : null,\n externalName: keepsExternalCorrespondent ? original.externalName : null,\n threadId: original.threadId ?? original.id,\n parentMessageId: original.id,\n senderUserId: input.userId,\n subject: `Fwd: ${original.subject}`,\n body: forwardedBody,\n bodyFormat: original.bodyFormat,\n priority: original.priority,\n status: 'sent',\n isDraft: false,\n sentAt: new Date(),\n sendViaEmail: input.sendViaEmail,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await trx.persist(newMessage).flush()\n for (const recipient of input.recipients) {\n trx.persist(trx.create(MessageRecipient, {\n messageId: newMessage.id,\n recipientUserId: recipient.userId,\n recipientType: recipient.type,\n status: 'unread',\n }))\n }\n for (const obj of originalObjects) {\n trx.persist(trx.create(MessageObject, {\n messageId: newMessage.id,\n entityModule: obj.entityModule,\n entityType: obj.entityType,\n entityId: obj.entityId,\n actionRequired: obj.actionRequired,\n actionType: obj.actionType,\n actionLabel: obj.actionLabel,\n entitySnapshot: obj.entitySnapshot,\n }))\n }\n await trx.flush()\n if (input.includeAttachments !== false) {\n await copyAttachmentsForForwardMessages(\n trx,\n forwardThreadSlice.map((item) => item.id),\n newMessage.id,\n input.tenantId,\n )\n }\n newMessageId = newMessage.id\n responseExternalEmail = newMessage.externalEmail ?? null\n })\n\n await emitMessageSentEvent(ctx.container, {\n messageId: newMessageId,\n senderUserId: input.userId,\n recipientUserIds: input.recipients.map((item) => item.userId),\n sendViaEmail: input.sendViaEmail,\n externalEmail: responseExternalEmail,\n forwardedFrom: original.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await emitMessageIndexUpsert(ctx.container, {\n messageId: newMessageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n return {\n id: newMessageId,\n externalEmail: responseExternalEmail,\n recipientUserIds: input.recipients.map((item) => item.userId),\n }\n },\n async captureAfter(_input, result, ctx) {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, result.id)\n },\n buildLog: async ({ input, result, snapshots }) => {\n const parsed = forwardCommandSchema.parse(input)\n return {\n actionLabel: 'Forward message',\n resourceKind: 'messages.message',\n resourceId: result.id,\n parentResourceKind: 'messages.message',\n parentResourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const after = undo?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: after.message.id })\n if (!message) return\n message.deletedAt = new Date()\n await em.flush()\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: after.message.id,\n actorUserId: after.message.senderUserId,\n recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),\n tenantId: after.message.tenantId,\n organizationId: after.message.organizationId,\n })\n },\n}\n\nconst deleteForActorCommand: CommandHandler<unknown, { ok: true }> = {\n id: 'messages.messages.delete_for_actor',\n async prepare(rawInput, ctx) {\n const input = deleteForActorCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await requireMessageById(em, input, input.messageId)\n const recipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n return {\n before: {\n messageId: message.id,\n messageDeletedAt: toIso(message.deletedAt),\n recipientId: recipient?.id ?? null,\n recipientStatus: recipient?.status ?? null,\n recipientDeletedAt: toIso(recipient?.deletedAt),\n } satisfies MessageDeleteUndoState,\n }\n },\n async execute(rawInput, ctx) {\n const input = deleteForActorCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await requireMessageById(em, input, input.messageId)\n const recipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n if (recipient) {\n recipient.status = 'deleted'\n recipient.deletedAt = new Date()\n await em.flush()\n await emitMessageDeletedEvent(ctx.container, {\n messageId: input.messageId,\n actorUserId: input.userId,\n target: 'recipient',\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n return { ok: true }\n }\n if (message.senderUserId === input.userId) {\n const recipientRows = await em.find(MessageRecipient, { messageId: input.messageId })\n message.deletedAt = new Date()\n await em.flush()\n const recipientUserIds = recipientRows\n .map((row) => row.recipientUserId)\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: input.messageId,\n actorUserId: input.userId,\n recipientUserIds,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n return { ok: true }\n }\n throw new Error('Access denied')\n },\n async captureAfter(rawInput, _result, ctx) {\n const input = deleteForActorCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: input.messageId, tenantId: input.tenantId })\n const recipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n })\n return {\n messageId: input.messageId,\n messageDeletedAt: toIso(message?.deletedAt),\n recipientId: recipient?.id ?? null,\n recipientStatus: recipient?.status ?? null,\n recipientDeletedAt: toIso(recipient?.deletedAt),\n } satisfies MessageDeleteUndoState\n },\n buildLog: async ({ input, snapshots }) => {\n const parsed = deleteForActorCommandSchema.parse(input)\n return {\n actionLabel: 'Delete message for actor',\n resourceKind: 'messages.message',\n resourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n before: (snapshots.before as MessageDeleteUndoState | undefined) ?? null,\n after: (snapshots.after as MessageDeleteUndoState | undefined) ?? null,\n } satisfies UndoPayload<MessageDeleteUndoState>,\n },\n snapshotBefore: snapshots.before ?? null,\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageDeleteUndoState>>(logEntry)\n const before = undo?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: before.messageId })\n if (message) {\n message.deletedAt = toDate(before.messageDeletedAt)\n }\n if (before.recipientId) {\n const recipient = await em.findOne(MessageRecipient, { id: before.recipientId })\n if (recipient) {\n recipient.status = (before.recipientStatus ?? 'unread') as MessageRecipient['status']\n recipient.deletedAt = toDate(before.recipientDeletedAt)\n }\n }\n await em.flush()\n },\n}\n\nregisterCommand(composeMessageCommand)\nregisterCommand(updateDraftCommand)\nregisterCommand(replyMessageCommand)\nregisterCommand(forwardMessageCommand)\nregisterCommand(deleteForActorCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { registerCommand, type CommandHandler, type CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Message, MessageObject, MessageRecipient, type MessageActionData } from '../data/entities'\nimport { emitMessagesEvent } from '../events'\nimport {\n composeMessageSchema,\n forwardMessageSchema,\n replyMessageSchema,\n updateDraftSchema,\n} from '../data/validators'\nimport { linkAttachmentsToMessage, linkLibraryAttachmentsToMessage, copyAttachmentsForForwardMessages } from '../lib/attachments'\nimport {\n EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE,\n resolveActorFeatures,\n resolveMessageChannelThreadAccess,\n} from '../lib/channelThreadAccess'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID, MESSAGE_ENTITY_ID, SYSTEM_SENDER_USER_ID } from '../lib/constants'\nimport { canUseChannelThreadFallback } from '../lib/routeHelpers'\nimport { getMessageTypeOrDefault } from '../lib/message-types-registry'\nimport { validateMessageObjectsForType } from '../lib/object-validation'\nimport { buildForwardBodyFromLegacyInput, buildForwardPreviewFromThreadSlice, buildForwardThreadSlice } from '../lib/forwarding'\nimport {\n assertOrganizationAccess,\n loadMessageAggregateSnapshot,\n restoreMessageAggregateSnapshot,\n type MessageAggregateSnapshot,\n type MessageScopeInput,\n} from './shared'\n\ntype MessageSentEventPayload = {\n messageId: string\n senderUserId: string\n recipientUserIds: string[]\n sendViaEmail: boolean\n /**\n * The message arrived from an external channel (#6093). Its `senderUserId`\n * is the channel's system user and its real correspondent lives in\n * `externalName` / `externalEmail`; subscribers that show a sender read it\n * from there.\n */\n inboundFromChannel?: boolean\n externalEmail?: string | null\n forwardedFrom?: string\n replyTo?: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype ContainerWithResolve = {\n resolve: (name: string) => unknown\n}\n\nasync function emitMessageSentEvent(_container: ContainerWithResolve, payload: MessageSentEventPayload) {\n await emitMessagesEvent('messages.message.sent', payload, { persistent: true })\n}\n\nasync function emitMessageDeletedEvent(_container: ContainerWithResolve, payload: {\n messageId: string\n actorUserId: string\n target: 'sender' | 'recipient'\n tenantId: string\n organizationId: string | null\n}) {\n await emitMessagesEvent(\n 'messages.message.deleted',\n { ...payload, recipientUserId: payload.actorUserId },\n { persistent: true },\n )\n}\n\nasync function emitMessageGloballyDeletedEvent(_container: ContainerWithResolve, payload: {\n messageId: string\n actorUserId: string\n recipientUserIds: string[]\n tenantId: string\n organizationId: string | null\n}) {\n const audience = Array.from(new Set([payload.actorUserId, ...payload.recipientUserIds]))\n await emitMessagesEvent(\n 'messages.message.deleted',\n {\n messageId: payload.messageId,\n actorUserId: payload.actorUserId,\n target: 'global',\n recipientUserIds: audience,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n },\n { persistent: true },\n )\n}\n\nasync function emitMessageIndexUpsert(\n container: ContainerWithResolve,\n payload: {\n messageId: string\n tenantId: string\n organizationId: string | null\n },\n) {\n let bus: { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> } | null = null\n try {\n bus = container.resolve('eventBus') as { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> }\n } catch {\n bus = null\n }\n\n if (!bus) return\n\n await bus.emitEvent(\n 'query_index.upsert_one',\n {\n entityType: MESSAGE_ENTITY_ID,\n recordId: payload.messageId,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n crudAction: 'updated',\n coverageBaseDelta: 1,\n },\n {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n },\n ).catch(() => undefined)\n}\n\nconst scopeSchema = z.object({\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n userId: z.string().uuid(),\n})\n\nconst composeCommandSchema = composeMessageSchema.safeExtend({\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n // Optional dedup key (inbound email ingest sets it; other callers leave it\n // undefined). When set, a re-issued compose returns the first message instead\n // of creating a duplicate.\n idempotencyKey: z.string().min(1).max(255).optional(),\n})\n\nconst updateDraftCommandSchema = updateDraftSchema.safeExtend({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\nconst replyCommandSchema = replyMessageSchema.safeExtend({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\nconst forwardCommandSchema = forwardMessageSchema.safeExtend({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\nconst deleteForActorCommandSchema = z.object({\n messageId: z.string().uuid(),\n tenantId: scopeSchema.shape.tenantId,\n organizationId: scopeSchema.shape.organizationId,\n userId: scopeSchema.shape.userId,\n})\n\ntype ComposeCommandInput = z.infer<typeof composeCommandSchema>\ntype UpdateDraftCommandInput = z.infer<typeof updateDraftCommandSchema>\ntype ReplyCommandInput = z.infer<typeof replyCommandSchema>\ntype ForwardCommandInput = z.infer<typeof forwardCommandSchema>\ntype DeleteForActorCommandInput = z.infer<typeof deleteForActorCommandSchema>\n\ntype MessageDeleteUndoState = {\n messageId: string\n messageDeletedAt: string | null\n recipientId: string | null\n recipientStatus: 'unread' | 'read' | 'archived' | 'deleted' | null\n recipientDeletedAt: string | null\n}\n\nfunction toIso(value: Date | null | undefined): string | null {\n return value ? value.toISOString() : null\n}\n\nfunction toDate(value: string | null | undefined): Date | null {\n if (!value) return null\n return new Date(value)\n}\n\nfunction buildReplySubject(subject: string): string {\n if (/^re:\\s*/i.test(subject)) return subject\n return `Re: ${subject}`\n}\n\nasync function requireMessageById(\n em: EntityManager,\n scope: MessageScopeInput,\n messageId: string,\n) {\n const message = await findOneWithDecryption(\n em,\n Message,\n {\n id: messageId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n )\n if (!message) throw new Error('Message not found')\n assertOrganizationAccess(scope, message)\n return message\n}\n\nfunction isUniqueViolation(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false\n const code = (err as { code?: string }).code\n if (code === '23505') return true\n const message = (err as { message?: string }).message\n return typeof message === 'string' && /duplicate key value|unique constraint/i.test(message)\n}\n\ntype ComposeMessageResult = {\n id: string\n threadId: string | null\n externalEmail: string | null\n isDraft: boolean\n recipientUserIds: string[]\n /**\n * True when this was an idempotent replay \u2014 an existing message was returned\n * and nothing was written. Signals `buildLog` to skip the audit/undo entry.\n */\n deduplicated?: boolean\n}\n\nasync function buildComposeResultFromExisting(\n em: EntityManager,\n message: Message,\n): Promise<ComposeMessageResult> {\n const recipients = await findWithDecryption(\n em,\n MessageRecipient,\n { messageId: message.id, deletedAt: null },\n undefined,\n { tenantId: message.tenantId, organizationId: message.organizationId ?? null },\n )\n return {\n id: message.id,\n threadId: message.threadId ?? null,\n externalEmail: message.externalEmail ?? null,\n isDraft: message.isDraft,\n recipientUserIds: recipients.map((recipient) => recipient.recipientUserId),\n deduplicated: true,\n }\n}\n\n/**\n * `parentMessageId` is trusted: the thread is derived from it without a read\n * check, because the acting `userId` is itself an input and server-side callers\n * (channel ingest) pass parents the actor is not a participant of. Every surface\n * that accepts a parent from a caller MUST resolve it against its own read rule\n * first (`resolveReplyParentMessage` for the messages API).\n */\nconst composeMessageCommand: CommandHandler<unknown, { id: string; threadId: string | null; externalEmail: string | null; isDraft: boolean; recipientUserIds: string[]; deduplicated?: boolean }> = {\n id: 'messages.messages.compose',\n async execute(rawInput, ctx) {\n const input = composeCommandSchema.parse(rawInput)\n if (input.objects?.length) {\n const objectValidationError = validateMessageObjectsForType(input.type, input.objects)\n if (objectValidationError) throw new Error(objectValidationError)\n }\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = { tenantId: input.tenantId, organizationId: input.organizationId }\n\n // Idempotency fast-path: a retried inbound-email ingest re-issues compose\n // for the same source message (the first attempt committed the message but a\n // downstream transient failure rolled the ingest back). Return that message\n // so the retry cannot create a duplicate.\n if (input.idempotencyKey) {\n const existing = await findOneWithDecryption(\n em,\n Message,\n { tenantId: input.tenantId, idempotencyKey: input.idempotencyKey },\n undefined,\n scope,\n )\n if (existing) return buildComposeResultFromExisting(em, existing)\n }\n\n let messageId = ''\n let responseThreadId: string | null = null\n let responseExternalEmail: string | null = null\n\n const composeTx = em.transactional(async (trx) => {\n const threadId = input.parentMessageId\n ? (\n await findOneWithDecryption(\n trx,\n Message,\n {\n id: input.parentMessageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n )?.threadId ?? input.parentMessageId\n : undefined\n\n const isPublicVisibility = input.visibility === 'public'\n // #6093: a channel-ingested message was RECEIVED, not sent. Ingest passes\n // `sendViaEmail: false` on purpose; forcing it for public visibility would\n // mail the assignee a copy and echo the message to its external sender\n // (#6089) the moment the recipients waiver lets it compose. #6090 removes\n // the forcing for every caller; until then it is skipped here for the\n // ingest path only.\n const sendViaEmail = isPublicVisibility && !input.inboundFromChannel ? true : input.sendViaEmail\n const message = trx.create(Message, {\n type: input.type,\n visibility: input.visibility ?? null,\n sourceEntityType: input.sourceEntityType,\n sourceEntityId: input.sourceEntityId,\n externalEmail: input.externalEmail,\n externalName: input.externalName,\n threadId: threadId ?? undefined,\n parentMessageId: input.parentMessageId,\n senderUserId: input.userId,\n subject: input.subject,\n body: input.body,\n bodyFormat: input.bodyFormat,\n priority: input.priority,\n status: input.isDraft ? 'draft' : 'sent',\n isDraft: input.isDraft ?? false,\n // #6095: channel ingest passes the provider's timestamp so an imported\n // mailbox sorts by when the mail was received, not when it was imported.\n sentAt: input.isDraft ? null : input.sentAt ?? new Date(),\n actionData: input.actionData as MessageActionData | undefined,\n sendViaEmail,\n idempotencyKey: input.idempotencyKey ?? null,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n await trx.persist(message).flush()\n if (!threadId && !input.isDraft && !message.threadId) {\n message.threadId = message.id\n await trx.flush()\n }\n\n for (const recipient of input.recipients) {\n trx.persist(trx.create(MessageRecipient, {\n messageId: message.id,\n recipientUserId: recipient.userId,\n recipientType: recipient.type,\n status: 'unread',\n }))\n }\n\n if (input.objects) {\n for (const obj of input.objects) {\n trx.persist(trx.create(MessageObject, {\n messageId: message.id,\n entityModule: obj.entityModule,\n entityType: obj.entityType,\n entityId: obj.entityId,\n actionRequired: obj.actionRequired,\n actionType: obj.actionType,\n actionLabel: obj.actionLabel,\n }))\n }\n }\n\n await trx.flush()\n\n if (input.attachmentIds?.length) {\n await linkAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentIds,\n input.organizationId,\n input.tenantId,\n )\n }\n\n if (input.attachmentRecordId) {\n await linkLibraryAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentRecordId,\n input.organizationId,\n input.tenantId,\n )\n }\n\n messageId = message.id\n responseThreadId = message.threadId ?? null\n responseExternalEmail = message.externalEmail ?? null\n })\n try {\n await composeTx\n } catch (err) {\n // Lost a concurrent race on the same idempotency key \u2014 return the message\n // the winning compose created. A propagated 23505 would otherwise be\n // classified permanent and dead-letter the inbound mail.\n if (input.idempotencyKey && isUniqueViolation(err)) {\n const existing = await findOneWithDecryption(\n em.fork(),\n Message,\n { tenantId: input.tenantId, idempotencyKey: input.idempotencyKey },\n undefined,\n scope,\n )\n if (existing) return buildComposeResultFromExisting(em.fork(), existing)\n }\n throw err\n }\n\n if (!input.isDraft) {\n await emitMessageSentEvent(ctx.container, {\n messageId,\n senderUserId: input.userId,\n recipientUserIds: input.recipients.map((recipient) => recipient.userId),\n sendViaEmail:\n input.visibility === 'public' && !input.inboundFromChannel ? true : input.sendViaEmail,\n inboundFromChannel: input.inboundFromChannel === true,\n externalEmail: responseExternalEmail,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n }\n\n await emitMessageIndexUpsert(ctx.container, {\n messageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n return {\n id: messageId,\n threadId: responseThreadId,\n externalEmail: responseExternalEmail,\n isDraft: input.isDraft,\n recipientUserIds: input.recipients.map((recipient) => recipient.userId),\n }\n },\n async captureAfter(_input, result, ctx) {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, result.id)\n },\n buildLog: async ({ input, result, snapshots }) => {\n // Idempotent replay: execute returned a pre-existing message without writing\n // anything. Skip the audit entry entirely \u2014 logging it as a fresh \"Compose\n // message\" would both misrepresent the dedup and expose an undo that\n // soft-deletes a legitimately-received inbound message.\n if (result.deduplicated) return { skipLog: true }\n const parsed = composeCommandSchema.parse(input)\n return {\n actionLabel: parsed.isDraft ? 'Create draft message' : 'Compose message',\n resourceKind: 'messages.message',\n resourceId: result.id,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const after = undo?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: after.message.id })\n if (!message) return\n message.deletedAt = new Date()\n await em.flush()\n if (!after.message.isDraft) {\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: after.message.id,\n actorUserId: after.message.senderUserId,\n recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),\n tenantId: after.message.tenantId,\n organizationId: after.message.organizationId,\n })\n }\n },\n}\n\nconst updateDraftCommand: CommandHandler<unknown, { ok: true; id: string }> = {\n id: 'messages.messages.update_draft',\n async prepare(rawInput, ctx) {\n const input = updateDraftCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadMessageAggregateSnapshot(em, input.messageId, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const input = updateDraftCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await requireMessageById(em, input, input.messageId)\n\n if (message.senderUserId !== input.userId) throw new Error('Access denied')\n if (!message.isDraft) throw new Error('Only draft messages can be edited')\n\n const isSending = input.isDraft === false\n const preloadedRecipients = isSending && !input.recipients\n ? await findWithDecryption(\n em,\n MessageRecipient,\n { messageId: message.id, deletedAt: null },\n undefined,\n {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n },\n )\n : null\n\n const nextMessageType = input.type ?? message.type\n if (input.objects) {\n const objectValidationError = validateMessageObjectsForType(nextMessageType, input.objects)\n if (objectValidationError) throw new Error(objectValidationError)\n } else if (input.type !== undefined) {\n const existingObjects = await em.find(MessageObject, { messageId: message.id })\n if (existingObjects.length > 0) {\n const objectValidationError = validateMessageObjectsForType(\n nextMessageType,\n existingObjects.map((item) => ({\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n })),\n )\n if (objectValidationError) throw new Error(objectValidationError)\n }\n }\n\n // Validate the send transition BEFORE the atomic-flush block so a rejected\n // send never mutates or flushes anything (everything checked here is known\n // from the parsed input + the loaded message).\n if (isSending) {\n const finalVisibility = input.visibility ?? message.visibility\n const finalSubject = input.subject ?? message.subject\n const finalBody = input.body ?? message.body\n const finalRecipientCount = input.recipients\n ? input.recipients.length\n : (preloadedRecipients?.length ?? 0)\n if (finalVisibility !== 'public' && finalRecipientCount === 0) {\n throw new Error('at least one recipient is required')\n }\n if (!finalSubject?.trim()) throw new Error('subject is required')\n if (!finalBody?.trim()) throw new Error('body is required')\n }\n\n // Phase 1 mutates the managed `message` scalars; the per-phase flush issues\n // those changes before phase 2's interleaved reads run (e.g.\n // `linkAttachmentsToMessage` issues an `em.find`). Under MikroORM v7 a read\n // between a scalar mutation and the terminal flush on the same EntityManager\n // silently drops the pending changeset, so keeping the scalar edits in their\n // own phase guarantees subject/body/etc. are persisted (SPEC-018 Problem 1).\n await withAtomicFlush(em, [() => {\n if (input.type !== undefined) message.type = input.type\n if (input.visibility !== undefined) message.visibility = input.visibility\n if (input.sourceEntityType !== undefined) message.sourceEntityType = input.sourceEntityType\n if (input.sourceEntityId !== undefined) message.sourceEntityId = input.sourceEntityId\n if (input.externalEmail !== undefined) message.externalEmail = input.externalEmail\n if (input.externalName !== undefined) message.externalName = input.externalName\n if (input.subject !== undefined) message.subject = input.subject\n if (input.body !== undefined) message.body = input.body\n if (input.bodyFormat !== undefined) message.bodyFormat = input.bodyFormat\n if (input.priority !== undefined) message.priority = input.priority\n if (input.actionData !== undefined) message.actionData = input.actionData\n if (input.sendViaEmail !== undefined) message.sendViaEmail = input.sendViaEmail\n }, async () => {\n if (input.recipients) {\n await em.nativeDelete(MessageRecipient, { messageId: message.id })\n for (const recipient of input.recipients) {\n em.persist(em.create(MessageRecipient, {\n messageId: message.id,\n recipientUserId: recipient.userId,\n recipientType: recipient.type,\n status: 'unread',\n }))\n }\n }\n\n if (input.objects) {\n await em.nativeDelete(MessageObject, { messageId: message.id })\n for (const object of input.objects) {\n em.persist(em.create(MessageObject, {\n messageId: message.id,\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n actionRequired: object.actionRequired,\n actionType: object.actionType,\n actionLabel: object.actionLabel,\n }))\n }\n }\n\n if (input.attachmentIds) {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n if (input.attachmentIds.length === 0) {\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: message.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n } else {\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: message.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n id: { $nin: input.attachmentIds },\n })\n }\n await linkAttachmentsToMessage(\n em,\n message.id,\n input.attachmentIds,\n input.organizationId,\n input.tenantId,\n )\n }\n\n if (isSending) {\n // Send transition was validated before the block; just apply the\n // status mutations here (persisted by this phase's boundary flush).\n message.isDraft = false\n message.status = 'sent'\n message.sentAt = new Date()\n if (!message.threadId) message.threadId = message.id\n }\n }], { transaction: true })\n\n await emitMessageIndexUpsert(ctx.container, {\n messageId: message.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n if (isSending) {\n const recipientUserIds = input.recipients\n ? input.recipients.map((r) => r.userId)\n : (preloadedRecipients ?? []).map((r) => r.recipientUserId)\n const resolvedVisibility = input.visibility ?? message.visibility\n const resolvedSendViaEmail = input.sendViaEmail ?? message.sendViaEmail\n await emitMessageSentEvent(ctx.container, {\n messageId: message.id,\n senderUserId: input.userId,\n recipientUserIds,\n sendViaEmail: resolvedVisibility === 'public' ? true : resolvedSendViaEmail,\n externalEmail: message.externalEmail ?? null,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n }\n\n return { ok: true, id: message.id }\n },\n async captureAfter(rawInput, _result, ctx) {\n const input = updateDraftCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, input.messageId, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n },\n buildLog: async ({ input, snapshots }) => {\n const parsed = updateDraftCommandSchema.parse(input)\n return {\n actionLabel: parsed.isDraft === false ? 'Send draft message' : 'Update draft message',\n resourceKind: 'messages.message',\n resourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n before: (snapshots.before as MessageAggregateSnapshot | undefined) ?? null,\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotBefore: snapshots.before ?? null,\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const before = undo?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreMessageAggregateSnapshot(em, before)\n },\n}\n\n/**\n * The channel-thread fallback for a non-participant acting on `original`\n * (#5535): the channels hub decides whether the caller may work the thread.\n * An internal thread resolves to `null` and denies, leaving the participant\n * rule in force. Only an explicitly public message qualifies \u2014 channel access\n * never opens another operator's internal note, matching the detail read.\n *\n * The same `messages.view` gate as the read routes applies, because the hub\n * grants every shared channel regardless of features: without it, a caller\n * holding only `messages.compose` could forward a conversation it may not read\n * to itself and read it as the recipient.\n */\nasync function canActOnChannelThreadMessage(\n ctx: CommandRuntimeContext,\n input: { tenantId: string; organizationId?: string | null; userId: string },\n original: Message,\n): Promise<boolean> {\n if (original.visibility !== 'public') return false\n const scope = { tenantId: input.tenantId, organizationId: input.organizationId ?? null, userId: input.userId }\n if (!(await canUseChannelThreadFallback(ctx, scope))) return false\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: input.tenantId, organizationId: input.organizationId ?? null },\n { messageThreadId: original.threadId ?? original.id },\n { userId: input.userId, features: resolveActorFeatures(ctx.auth) },\n )\n return channelThread?.canAccess === true\n}\n\nasync function isOnChannelThread(\n ctx: CommandRuntimeContext,\n input: { tenantId: string; organizationId?: string | null; userId: string },\n original: Message,\n): Promise<boolean> {\n if (original.sourceEntityType === EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE) return true\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: input.tenantId, organizationId: input.organizationId ?? null },\n { messageThreadId: original.threadId ?? original.id },\n { userId: input.userId, features: resolveActorFeatures(ctx.auth) },\n )\n return channelThread !== null\n}\n\nconst replyMessageCommand: CommandHandler<unknown, { id: string; externalEmail: string | null; recipientUserIds: string[] }> = {\n id: 'messages.messages.reply',\n async execute(rawInput, ctx) {\n const input = replyCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const original = await requireMessageById(em, input, input.messageId)\n const ownRecipient = await em.findOne(MessageRecipient, {\n messageId: original.id,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n if (original.senderUserId !== input.userId && !ownRecipient) {\n // A message that arrived over a communication channel has no platform\n // participant to match: its sender is the channel system user and an\n // unassigned conversation produces no recipient rows, so the\n // sender-or-recipient test denies every operator \u2014 a tenant admin\n // included (#5535). For a thread the channels hub owns, that hub's own\n // access rule is the applicable one; an internal thread resolves to\n // `null` here and keeps the participant rule unchanged.\n if (!(await canActOnChannelThreadMessage(ctx, input, original))) throw new Error('Access denied')\n }\n\n const messageType = getMessageTypeOrDefault(original.type)\n if (messageType.allowReply === false) throw new Error('Reply is not allowed for this message type')\n\n const isReplyRecipient = (userId: string) => userId !== input.userId && userId !== SYSTEM_SENDER_USER_ID\n const recipientIds = new Set(\n (input.recipients ?? [])\n .map((recipient) => recipient.userId)\n .filter(isReplyRecipient),\n )\n\n if (recipientIds.size === 0) {\n const originalRecipients = await em.find(MessageRecipient, { messageId: original.id, deletedAt: null })\n if (input.replyAll) {\n if (isReplyRecipient(original.senderUserId)) recipientIds.add(original.senderUserId)\n for (const recipient of originalRecipients) {\n if (isReplyRecipient(recipient.recipientUserId)) recipientIds.add(recipient.recipientUserId)\n }\n } else if (original.senderUserId !== input.userId) {\n if (isReplyRecipient(original.senderUserId)) recipientIds.add(original.senderUserId)\n } else {\n for (const recipient of originalRecipients) {\n if (isReplyRecipient(recipient.recipientUserId)) {\n recipientIds.add(recipient.recipientUserId)\n break\n }\n }\n }\n\n if (recipientIds.size === 0 && original.senderUserId === input.userId) {\n recipientIds.add(input.userId)\n }\n }\n if (recipientIds.size === 0 && original.senderUserId !== SYSTEM_SENDER_USER_ID) {\n throw new Error('No recipients available for reply')\n }\n\n let messageId = ''\n let responseExternalEmail: string | null = null\n await em.transactional(async (trx) => {\n const message = trx.create(Message, {\n type: original.type,\n visibility: original.visibility ?? null,\n sourceEntityType: original.sourceEntityType,\n sourceEntityId: original.sourceEntityId,\n externalEmail: original.externalEmail,\n externalName: original.externalName,\n threadId: original.threadId ?? original.id,\n parentMessageId: original.id,\n senderUserId: input.userId,\n subject: buildReplySubject(original.subject),\n body: input.body,\n bodyFormat: input.bodyFormat,\n priority: original.priority,\n status: 'sent',\n isDraft: false,\n sentAt: new Date(),\n sendViaEmail: input.sendViaEmail,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await trx.persist(message).flush()\n for (const recipientUserId of recipientIds) {\n trx.persist(trx.create(MessageRecipient, {\n messageId: message.id,\n recipientUserId,\n recipientType: 'to',\n status: 'unread',\n }))\n }\n await trx.flush()\n if (input.attachmentIds?.length) {\n await linkAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentIds,\n input.organizationId,\n input.tenantId,\n )\n }\n if (input.attachmentRecordId) {\n await linkLibraryAttachmentsToMessage(\n trx,\n message.id,\n input.attachmentRecordId,\n input.organizationId,\n input.tenantId,\n )\n }\n messageId = message.id\n responseExternalEmail = message.externalEmail ?? null\n })\n\n await emitMessageSentEvent(ctx.container, {\n messageId,\n senderUserId: input.userId,\n recipientUserIds: Array.from(recipientIds),\n sendViaEmail: input.sendViaEmail,\n externalEmail: responseExternalEmail,\n replyTo: original.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await emitMessageIndexUpsert(ctx.container, {\n messageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n return {\n id: messageId,\n externalEmail: responseExternalEmail,\n recipientUserIds: Array.from(recipientIds),\n }\n },\n async captureAfter(_input, result, ctx) {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, result.id)\n },\n buildLog: async ({ input, result, snapshots }) => {\n const parsed = replyCommandSchema.parse(input)\n return {\n actionLabel: 'Reply to message',\n resourceKind: 'messages.message',\n resourceId: result.id,\n parentResourceKind: 'messages.message',\n parentResourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const after = undo?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: after.message.id })\n if (!message) return\n message.deletedAt = new Date()\n await em.flush()\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: after.message.id,\n actorUserId: after.message.senderUserId,\n recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),\n tenantId: after.message.tenantId,\n organizationId: after.message.organizationId,\n })\n },\n}\n\nconst forwardMessageCommand: CommandHandler<unknown, { id: string; externalEmail: string | null; recipientUserIds: string[] }> = {\n id: 'messages.messages.forward',\n async execute(rawInput, ctx) {\n const input = forwardCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const original = await requireMessageById(em, input, input.messageId)\n const isRecipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n // Same fallback as the reply command (#6355): an operator who may answer an\n // inbound channel message may also forward it internally.\n const viaChannelThread = original.senderUserId !== input.userId && !isRecipient\n if (viaChannelThread && !(await canActOnChannelThreadMessage(ctx, input, original))) {\n throw new Error('Access denied')\n }\n\n const messageType = getMessageTypeOrDefault(original.type)\n if (messageType.allowForward === false) throw new Error('Forward is not allowed for this message type')\n\n const originalObjects = await em.find(MessageObject, { messageId: input.messageId })\n const forwardThreadSlice = await buildForwardThreadSlice(em, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: input.userId,\n }, original, { includePublicThreadMessages: viaChannelThread })\n const generatedPreview = await buildForwardPreviewFromThreadSlice(em, {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: input.userId,\n }, original, forwardThreadSlice)\n const forwardedBody = typeof input.body === 'string'\n ? input.body\n : buildForwardBodyFromLegacyInput(generatedPreview.body, input.additionalBody)\n // A forward of a channel conversation is addressed to colleagues, never to\n // the correspondent. The channels bridge already refuses `forwardedFrom`, but\n // the messages email path would still mail `externalEmail` on\n // `sendViaEmail` \u2014 so the customer's address is not carried onto it.\n // It is also filed as internal: replies copy their parent's visibility and\n // carry no `forwardedFrom`, so a public forward made every reply under it\n // deliverable to the correspondent (#6431, #6428). The bridge routes on the\n // thread, not the source, and some public messages on a channel thread\n // carry no source type (an AI or parented compose), so the thread decides.\n const keepsExternalCorrespondent = !(await isOnChannelThread(ctx, input, original))\n let newMessageId = ''\n let responseExternalEmail: string | null = null\n await em.transactional(async (trx) => {\n const newMessage = trx.create(Message, {\n type: original.type,\n visibility: keepsExternalCorrespondent ? original.visibility ?? null : 'internal',\n sourceEntityType: original.sourceEntityType,\n sourceEntityId: original.sourceEntityId,\n externalEmail: keepsExternalCorrespondent ? original.externalEmail : null,\n externalName: keepsExternalCorrespondent ? original.externalName : null,\n threadId: original.threadId ?? original.id,\n parentMessageId: original.id,\n senderUserId: input.userId,\n subject: `Fwd: ${original.subject}`,\n body: forwardedBody,\n bodyFormat: original.bodyFormat,\n priority: original.priority,\n status: 'sent',\n isDraft: false,\n sentAt: new Date(),\n sendViaEmail: input.sendViaEmail,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await trx.persist(newMessage).flush()\n for (const recipient of input.recipients) {\n trx.persist(trx.create(MessageRecipient, {\n messageId: newMessage.id,\n recipientUserId: recipient.userId,\n recipientType: recipient.type,\n status: 'unread',\n }))\n }\n for (const obj of originalObjects) {\n trx.persist(trx.create(MessageObject, {\n messageId: newMessage.id,\n entityModule: obj.entityModule,\n entityType: obj.entityType,\n entityId: obj.entityId,\n actionRequired: obj.actionRequired,\n actionType: obj.actionType,\n actionLabel: obj.actionLabel,\n entitySnapshot: obj.entitySnapshot,\n }))\n }\n await trx.flush()\n if (input.includeAttachments !== false) {\n await copyAttachmentsForForwardMessages(\n trx,\n forwardThreadSlice.map((item) => item.id),\n newMessage.id,\n input.tenantId,\n )\n }\n newMessageId = newMessage.id\n responseExternalEmail = newMessage.externalEmail ?? null\n })\n\n await emitMessageSentEvent(ctx.container, {\n messageId: newMessageId,\n senderUserId: input.userId,\n recipientUserIds: input.recipients.map((item) => item.userId),\n sendViaEmail: input.sendViaEmail,\n externalEmail: responseExternalEmail,\n forwardedFrom: original.id,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n await emitMessageIndexUpsert(ctx.container, {\n messageId: newMessageId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n\n return {\n id: newMessageId,\n externalEmail: responseExternalEmail,\n recipientUserIds: input.recipients.map((item) => item.userId),\n }\n },\n async captureAfter(_input, result, ctx) {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadMessageAggregateSnapshot(em, result.id)\n },\n buildLog: async ({ input, result, snapshots }) => {\n const parsed = forwardCommandSchema.parse(input)\n return {\n actionLabel: 'Forward message',\n resourceKind: 'messages.message',\n resourceId: result.id,\n parentResourceKind: 'messages.message',\n parentResourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,\n } satisfies UndoPayload<MessageAggregateSnapshot>,\n },\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)\n const after = undo?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: after.message.id })\n if (!message) return\n message.deletedAt = new Date()\n await em.flush()\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: after.message.id,\n actorUserId: after.message.senderUserId,\n recipientUserIds: after.recipients.map((recipient) => recipient.recipientUserId),\n tenantId: after.message.tenantId,\n organizationId: after.message.organizationId,\n })\n },\n}\n\nconst deleteForActorCommand: CommandHandler<unknown, { ok: true }> = {\n id: 'messages.messages.delete_for_actor',\n async prepare(rawInput, ctx) {\n const input = deleteForActorCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await requireMessageById(em, input, input.messageId)\n const recipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n return {\n before: {\n messageId: message.id,\n messageDeletedAt: toIso(message.deletedAt),\n recipientId: recipient?.id ?? null,\n recipientStatus: recipient?.status ?? null,\n recipientDeletedAt: toIso(recipient?.deletedAt),\n } satisfies MessageDeleteUndoState,\n }\n },\n async execute(rawInput, ctx) {\n const input = deleteForActorCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await requireMessageById(em, input, input.messageId)\n const recipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n deletedAt: null,\n })\n if (recipient) {\n recipient.status = 'deleted'\n recipient.deletedAt = new Date()\n await em.flush()\n await emitMessageDeletedEvent(ctx.container, {\n messageId: input.messageId,\n actorUserId: input.userId,\n target: 'recipient',\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n return { ok: true }\n }\n if (message.senderUserId === input.userId) {\n const recipientRows = await em.find(MessageRecipient, { messageId: input.messageId })\n message.deletedAt = new Date()\n await em.flush()\n const recipientUserIds = recipientRows\n .map((row) => row.recipientUserId)\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n await emitMessageGloballyDeletedEvent(ctx.container, {\n messageId: input.messageId,\n actorUserId: input.userId,\n recipientUserIds,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n })\n return { ok: true }\n }\n throw new Error('Access denied')\n },\n async captureAfter(rawInput, _result, ctx) {\n const input = deleteForActorCommandSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: input.messageId, tenantId: input.tenantId })\n const recipient = await em.findOne(MessageRecipient, {\n messageId: input.messageId,\n recipientUserId: input.userId,\n })\n return {\n messageId: input.messageId,\n messageDeletedAt: toIso(message?.deletedAt),\n recipientId: recipient?.id ?? null,\n recipientStatus: recipient?.status ?? null,\n recipientDeletedAt: toIso(recipient?.deletedAt),\n } satisfies MessageDeleteUndoState\n },\n buildLog: async ({ input, snapshots }) => {\n const parsed = deleteForActorCommandSchema.parse(input)\n return {\n actionLabel: 'Delete message for actor',\n resourceKind: 'messages.message',\n resourceId: parsed.messageId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n before: (snapshots.before as MessageDeleteUndoState | undefined) ?? null,\n after: (snapshots.after as MessageDeleteUndoState | undefined) ?? null,\n } satisfies UndoPayload<MessageDeleteUndoState>,\n },\n snapshotBefore: snapshots.before ?? null,\n snapshotAfter: snapshots.after ?? null,\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<UndoPayload<MessageDeleteUndoState>>(logEntry)\n const before = undo?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const message = await em.findOne(Message, { id: before.messageId })\n if (message) {\n message.deletedAt = toDate(before.messageDeletedAt)\n }\n if (before.recipientId) {\n const recipient = await em.findOne(MessageRecipient, { id: before.recipientId })\n if (recipient) {\n recipient.status = (before.recipientStatus ?? 'unread') as MessageRecipient['status']\n recipient.deletedAt = toDate(before.recipientDeletedAt)\n }\n }\n await em.flush()\n },\n}\n\nregisterCommand(composeMessageCommand)\nregisterCommand(updateDraftCommand)\nregisterCommand(replyMessageCommand)\nregisterCommand(forwardMessageCommand)\nregisterCommand(deleteForActorCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,6 @@
1
1
  import { resolveRequestContext } from "@open-mercato/shared/lib/api/context";
2
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { Message, MessageRecipient } from "../data/entities.js";
2
4
  import {
3
5
  CHANNEL_THREAD_FALLBACK_FEATURE,
4
6
  EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE,
@@ -55,6 +57,41 @@ async function hasChannelThreadReadAccess(ctx, scope, message) {
55
57
  );
56
58
  return channelThread?.canAccess === true;
57
59
  }
60
+ async function resolveMessageReadAccess(ctx, scope, message) {
61
+ const em = ctx.container.resolve("em");
62
+ const recipient = await em.findOne(MessageRecipient, {
63
+ messageId: message.id,
64
+ recipientUserId: scope.userId,
65
+ deletedAt: null
66
+ });
67
+ const isSender = message.senderUserId === scope.userId;
68
+ const hasChannelThreadAccess = await hasChannelThreadReadAccess(ctx, scope, message);
69
+ const isParticipant = isSender || Boolean(recipient);
70
+ return {
71
+ recipient,
72
+ isSender,
73
+ hasChannelThreadAccess,
74
+ canRead: isParticipant || hasChannelThreadAccess && message.visibility === "public"
75
+ };
76
+ }
77
+ async function resolveReplyParentMessage(ctx, scope, parentMessageId) {
78
+ const em = ctx.container.resolve("em");
79
+ const parent = await findOneWithDecryption(
80
+ em,
81
+ Message,
82
+ {
83
+ id: parentMessageId,
84
+ tenantId: scope.tenantId,
85
+ organizationId: scope.organizationId,
86
+ deletedAt: null
87
+ },
88
+ void 0,
89
+ { tenantId: scope.tenantId, organizationId: scope.organizationId }
90
+ );
91
+ if (!parent) return { status: "not_found" };
92
+ const access = await resolveMessageReadAccess(ctx, scope, parent);
93
+ return access.canRead ? { status: "readable", message: parent } : { status: "forbidden" };
94
+ }
58
95
  async function canPostToChannelThread(ctx, scope, messageThreadId) {
59
96
  const channelThread = await resolveMessageChannelThreadAccess(
60
97
  ctx.container,
@@ -80,6 +117,8 @@ export {
80
117
  hasChannelThreadReadAccess,
81
118
  hasOrganizationAccess,
82
119
  parseRequestBodySafe,
83
- resolveMessageContext
120
+ resolveMessageContext,
121
+ resolveMessageReadAccess,
122
+ resolveReplyParentMessage
84
123
  };
85
124
  //# sourceMappingURL=routeHelpers.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/lib/routeHelpers.ts"],
4
- "sourcesContent": ["import { resolveRequestContext } from '@open-mercato/shared/lib/api/context'\nimport {\n CHANNEL_THREAD_FALLBACK_FEATURE,\n EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE,\n resolveActorFeatures,\n resolveMessageChannelThreadAccess,\n} from './channelThreadAccess'\n\nexport function hasOrganizationAccess(\n scopeOrganizationId: string | null,\n messageOrganizationId: string | null | undefined,\n): boolean {\n if (scopeOrganizationId) {\n return messageOrganizationId === scopeOrganizationId\n }\n return messageOrganizationId == null\n}\n\nexport type MessageScope = {\n tenantId: string\n organizationId: string | null\n userId: string\n}\n\nexport async function resolveMessageContext(req: Request): Promise<{\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx']\n scope: MessageScope\n}> {\n const { ctx } = await resolveRequestContext(req)\n return {\n ctx,\n scope: {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.auth?.orgId ?? null,\n userId: ctx.auth?.sub ?? '',\n },\n }\n}\n\ntype RbacService = {\n userHasAllFeatures: (\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null }\n ) => Promise<boolean>\n}\n\nexport async function parseRequestBodySafe(req: Request): Promise<unknown> {\n try {\n const text = await req.text()\n if (!text) return {}\n return JSON.parse(text)\n } catch {\n return {}\n }\n}\n\n/**\n * Whether the caller may fall back to the channels hub's access rule on a\n * channel-linked thread (#5535).\n *\n * Resolved through RBAC, never through `ctx.auth.features`: the session JWT\n * carries no `features` claim, so reading it here would deny every caller \u2014\n * a tenant admin included \u2014 and re-close the very journey #5535 opened. RBAC is\n * also what makes the check wildcard-aware. Fails closed, like the sibling\n * feature checks in this file.\n *\n * Takes only the container, so the reply and forward commands apply the same\n * gate as the read routes (#6355).\n */\nexport async function canUseChannelThreadFallback(\n ctx: { container: { resolve: (name: string) => unknown } },\n scope: MessageScope,\n): Promise<boolean> {\n if (!scope.userId || !scope.tenantId) return false\n try {\n const rbac = ctx.container.resolve('rbacService') as RbacService | undefined\n if (typeof rbac?.userHasAllFeatures !== 'function') return false\n return await rbac.userHasAllFeatures(scope.userId, [CHANNEL_THREAD_FALLBACK_FEATURE], {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n } catch {\n return false\n }\n}\n\n/**\n * Whether the caller may work the channel-linked thread `message` belongs to\n * through the channels hub's access rule, rather than as a participant (#5535).\n *\n * Shared by every read route that widens its participant test for channel\n * threads \u2014 the detail read, the attachments list and the forward preview \u2014 so\n * the three cannot drift apart (#6354). Only channel-sourced messages pay the\n * lookup; the fallback is feature-gated through {@link canUseChannelThreadFallback}\n * because the hub's rule grants every shared channel unconditionally. An\n * internal thread, a missing hub or a denied gate all read `false`, leaving the\n * participant rule in force.\n *\n * Callers still decide what the widening covers: channel access never opens a\n * message that is not explicitly public.\n */\nexport async function hasChannelThreadReadAccess(\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx'],\n scope: MessageScope,\n message: { id: string; threadId?: string | null; sourceEntityType?: string | null },\n): Promise<boolean> {\n if (message.sourceEntityType !== EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE) return false\n if (!(await canUseChannelThreadFallback(ctx, scope))) return false\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },\n { messageThreadId: message.threadId ?? message.id },\n { userId: scope.userId, features: resolveActorFeatures(ctx.auth) },\n )\n return channelThread?.canAccess === true\n}\n\n/**\n * Whether the caller may post a message meant to leave the platform onto\n * `messageThreadId` (#5645 review, #6432).\n *\n * An internal thread, a thread outside the caller's scope and an absent hub all\n * resolve to `null` and allow, keeping the pre-existing rule. A channel thread\n * needs both the hub's access rule and `messages.view`, because the hub grants\n * every shared channel regardless of features. Shared by the compose route and\n * the draft send transition so a draft cannot be used to skip the gate.\n */\nexport async function canPostToChannelThread(\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx'],\n scope: MessageScope,\n messageThreadId: string,\n): Promise<boolean> {\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },\n { messageThreadId },\n { userId: scope.userId, features: resolveActorFeatures(ctx.auth) },\n )\n if (!channelThread) return true\n return channelThread.canAccess && (await canUseChannelThreadFallback(ctx, scope))\n}\n\nexport async function canUseMessageEmailFeature(\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx'],\n scope: MessageScope,\n): Promise<boolean> {\n if (!scope.userId || !scope.tenantId) return false\n\n const rbac = ctx.container.resolve('rbacService') as RbacService\n return rbac.userHasAllFeatures(scope.userId, ['messages.email'], {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,SAAS,sBACd,qBACA,uBACS;AACT,MAAI,qBAAqB;AACvB,WAAO,0BAA0B;AAAA,EACnC;AACA,SAAO,yBAAyB;AAClC;AAQA,eAAsB,sBAAsB,KAGzC;AACD,QAAM,EAAE,IAAI,IAAI,MAAM,sBAAsB,GAAG;AAC/C,SAAO;AAAA,IACL;AAAA,IACA,OAAO;AAAA,MACL,UAAU,IAAI,MAAM,YAAY;AAAA,MAChC,gBAAgB,IAAI,MAAM,SAAS;AAAA,MACnC,QAAQ,IAAI,MAAM,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AAUA,eAAsB,qBAAqB,KAAgC;AACzE,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,CAAC,KAAM,QAAO,CAAC;AACnB,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAeA,eAAsB,4BACpB,KACA,OACkB;AAClB,MAAI,CAAC,MAAM,UAAU,CAAC,MAAM,SAAU,QAAO;AAC7C,MAAI;AACF,UAAM,OAAO,IAAI,UAAU,QAAQ,aAAa;AAChD,QAAI,OAAO,MAAM,uBAAuB,WAAY,QAAO;AAC3D,WAAO,MAAM,KAAK,mBAAmB,MAAM,QAAQ,CAAC,+BAA+B,GAAG;AAAA,MACpF,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,IACxB,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBA,eAAsB,2BACpB,KACA,OACA,SACkB;AAClB,MAAI,QAAQ,qBAAqB,yCAA0C,QAAO;AAClF,MAAI,CAAE,MAAM,4BAA4B,KAAK,KAAK,EAAI,QAAO;AAC7D,QAAM,gBAAgB,MAAM;AAAA,IAC1B,IAAI;AAAA,IACJ,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,kBAAkB,KAAK;AAAA,IACzE,EAAE,iBAAiB,QAAQ,YAAY,QAAQ,GAAG;AAAA,IAClD,EAAE,QAAQ,MAAM,QAAQ,UAAU,qBAAqB,IAAI,IAAI,EAAE;AAAA,EACnE;AACA,SAAO,eAAe,cAAc;AACtC;AAYA,eAAsB,uBACpB,KACA,OACA,iBACkB;AAClB,QAAM,gBAAgB,MAAM;AAAA,IAC1B,IAAI;AAAA,IACJ,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,kBAAkB,KAAK;AAAA,IACzE,EAAE,gBAAgB;AAAA,IAClB,EAAE,QAAQ,MAAM,QAAQ,UAAU,qBAAqB,IAAI,IAAI,EAAE;AAAA,EACnE;AACA,MAAI,CAAC,cAAe,QAAO;AAC3B,SAAO,cAAc,aAAc,MAAM,4BAA4B,KAAK,KAAK;AACjF;AAEA,eAAsB,0BACpB,KACA,OACkB;AAClB,MAAI,CAAC,MAAM,UAAU,CAAC,MAAM,SAAU,QAAO;AAE7C,QAAM,OAAO,IAAI,UAAU,QAAQ,aAAa;AAChD,SAAO,KAAK,mBAAmB,MAAM,QAAQ,CAAC,gBAAgB,GAAG;AAAA,IAC/D,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,EACxB,CAAC;AACH;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveRequestContext } from '@open-mercato/shared/lib/api/context'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Message, MessageRecipient } from '../data/entities'\nimport {\n CHANNEL_THREAD_FALLBACK_FEATURE,\n EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE,\n resolveActorFeatures,\n resolveMessageChannelThreadAccess,\n} from './channelThreadAccess'\n\nexport function hasOrganizationAccess(\n scopeOrganizationId: string | null,\n messageOrganizationId: string | null | undefined,\n): boolean {\n if (scopeOrganizationId) {\n return messageOrganizationId === scopeOrganizationId\n }\n return messageOrganizationId == null\n}\n\nexport type ChannelThreadCallerContext = {\n container: { resolve: <T = unknown>(name: string) => T }\n auth?: unknown\n}\n\nexport type MessageScope = {\n tenantId: string\n organizationId: string | null\n userId: string\n}\n\nexport async function resolveMessageContext(req: Request): Promise<{\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx']\n scope: MessageScope\n}> {\n const { ctx } = await resolveRequestContext(req)\n return {\n ctx,\n scope: {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.auth?.orgId ?? null,\n userId: ctx.auth?.sub ?? '',\n },\n }\n}\n\ntype RbacService = {\n userHasAllFeatures: (\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null }\n ) => Promise<boolean>\n}\n\nexport async function parseRequestBodySafe(req: Request): Promise<unknown> {\n try {\n const text = await req.text()\n if (!text) return {}\n return JSON.parse(text)\n } catch {\n return {}\n }\n}\n\n/**\n * Whether the caller may fall back to the channels hub's access rule on a\n * channel-linked thread (#5535).\n *\n * Resolved through RBAC, never through `ctx.auth.features`: the session JWT\n * carries no `features` claim, so reading it here would deny every caller \u2014\n * a tenant admin included \u2014 and re-close the very journey #5535 opened. RBAC is\n * also what makes the check wildcard-aware. Fails closed, like the sibling\n * feature checks in this file.\n *\n * Takes only the container, so the reply and forward commands apply the same\n * gate as the read routes (#6355).\n */\nexport async function canUseChannelThreadFallback(\n ctx: { container: { resolve: (name: string) => unknown } },\n scope: MessageScope,\n): Promise<boolean> {\n if (!scope.userId || !scope.tenantId) return false\n try {\n const rbac = ctx.container.resolve('rbacService') as RbacService | undefined\n if (typeof rbac?.userHasAllFeatures !== 'function') return false\n return await rbac.userHasAllFeatures(scope.userId, [CHANNEL_THREAD_FALLBACK_FEATURE], {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n } catch {\n return false\n }\n}\n\n/**\n * Whether the caller may work the channel-linked thread `message` belongs to\n * through the channels hub's access rule, rather than as a participant (#5535).\n *\n * Shared by every read route that widens its participant test for channel\n * threads \u2014 the detail read, the attachments list and the forward preview \u2014 so\n * the three cannot drift apart (#6354). Only channel-sourced messages pay the\n * lookup; the fallback is feature-gated through {@link canUseChannelThreadFallback}\n * because the hub's rule grants every shared channel unconditionally. An\n * internal thread, a missing hub or a denied gate all read `false`, leaving the\n * participant rule in force.\n *\n * Callers still decide what the widening covers: channel access never opens a\n * message that is not explicitly public.\n */\nexport async function hasChannelThreadReadAccess(\n ctx: ChannelThreadCallerContext,\n scope: MessageScope,\n message: { id: string; threadId?: string | null; sourceEntityType?: string | null },\n): Promise<boolean> {\n if (message.sourceEntityType !== EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE) return false\n if (!(await canUseChannelThreadFallback(ctx, scope))) return false\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },\n { messageThreadId: message.threadId ?? message.id },\n { userId: scope.userId, features: resolveActorFeatures(ctx.auth) },\n )\n return channelThread?.canAccess === true\n}\n\nexport type MessageReadAccess = {\n recipient: MessageRecipient | null\n isSender: boolean\n hasChannelThreadAccess: boolean\n canRead: boolean\n}\n\n/**\n * The read rule of `GET /api/messages/[id]`, for a message already loaded in the\n * caller's scope: the sender, a recipient whose row is not deleted, or \u2014 for an\n * explicitly public message only \u2014 a caller the channels hub lets work the\n * channel thread. Channel access never opens an internal note.\n *\n * Shared by the detail read and by every write that names another message as its\n * parent, so \"may reference\" can never be wider than \"may read\".\n */\nexport async function resolveMessageReadAccess(\n ctx: ChannelThreadCallerContext,\n scope: MessageScope,\n message: Pick<Message, 'id' | 'senderUserId' | 'visibility' | 'threadId' | 'sourceEntityType'>,\n): Promise<MessageReadAccess> {\n const em = ctx.container.resolve('em') as EntityManager\n const recipient = await em.findOne(MessageRecipient, {\n messageId: message.id,\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n const isSender = message.senderUserId === scope.userId\n const hasChannelThreadAccess = await hasChannelThreadReadAccess(ctx, scope, message)\n const isParticipant = isSender || Boolean(recipient)\n return {\n recipient,\n isSender,\n hasChannelThreadAccess,\n canRead: isParticipant || (hasChannelThreadAccess && message.visibility === 'public'),\n }\n}\n\nexport type ReplyParentResolution =\n | { status: 'readable'; message: Message }\n | { status: 'not_found' }\n | { status: 'forbidden' }\n\n/**\n * Resolve a caller-supplied parent message before a write threads onto it.\n *\n * The parent is looked up in exactly the scope the new message is written to \u2014\n * tenant, organization (null only matches null) and not deleted \u2014 so a parent\n * that is unknown, deleted, or in another organization or tenant all answer\n * `not_found`. A parent in scope that the caller may not read answers\n * `forbidden`, the same distinction the detail read already makes.\n */\nexport async function resolveReplyParentMessage(\n ctx: ChannelThreadCallerContext,\n scope: MessageScope,\n parentMessageId: string,\n): Promise<ReplyParentResolution> {\n const em = ctx.container.resolve('em') as EntityManager\n const parent = await findOneWithDecryption(\n em,\n Message,\n {\n id: parentMessageId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n if (!parent) return { status: 'not_found' }\n const access = await resolveMessageReadAccess(ctx, scope, parent)\n return access.canRead ? { status: 'readable', message: parent } : { status: 'forbidden' }\n}\n\n/**\n * Whether the caller may post a message meant to leave the platform onto\n * `messageThreadId` (#5645 review, #6432).\n *\n * An internal thread, a thread outside the caller's scope and an absent hub all\n * resolve to `null` and allow, keeping the pre-existing rule. A channel thread\n * needs both the hub's access rule and `messages.view`, because the hub grants\n * every shared channel regardless of features. Shared by the compose route and\n * the draft send transition so a draft cannot be used to skip the gate.\n */\nexport async function canPostToChannelThread(\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx'],\n scope: MessageScope,\n messageThreadId: string,\n): Promise<boolean> {\n const channelThread = await resolveMessageChannelThreadAccess(\n ctx.container,\n { tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },\n { messageThreadId },\n { userId: scope.userId, features: resolveActorFeatures(ctx.auth) },\n )\n if (!channelThread) return true\n return channelThread.canAccess && (await canUseChannelThreadFallback(ctx, scope))\n}\n\nexport async function canUseMessageEmailFeature(\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx'],\n scope: MessageScope,\n): Promise<boolean> {\n if (!scope.userId || !scope.tenantId) return false\n\n const rbac = ctx.container.resolve('rbacService') as RbacService\n return rbac.userHasAllFeatures(scope.userId, ['messages.email'], {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.9.1-develop.7515.1.e2712c8b00",
3
+ "version": "0.9.1-develop.7516.1.176184247d",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -264,16 +264,16 @@
264
264
  "zod": "^4.4.3"
265
265
  },
266
266
  "peerDependencies": {
267
- "@open-mercato/ai-assistant": "0.9.1-develop.7515.1.e2712c8b00",
268
- "@open-mercato/shared": "0.9.1-develop.7515.1.e2712c8b00",
269
- "@open-mercato/ui": "0.9.1-develop.7515.1.e2712c8b00",
267
+ "@open-mercato/ai-assistant": "0.9.1-develop.7516.1.176184247d",
268
+ "@open-mercato/shared": "0.9.1-develop.7516.1.176184247d",
269
+ "@open-mercato/ui": "0.9.1-develop.7516.1.176184247d",
270
270
  "react": "^19.0.0",
271
271
  "react-dom": "^19.0.0"
272
272
  },
273
273
  "devDependencies": {
274
- "@open-mercato/ai-assistant": "0.9.1-develop.7515.1.e2712c8b00",
275
- "@open-mercato/shared": "0.9.1-develop.7515.1.e2712c8b00",
276
- "@open-mercato/ui": "0.9.1-develop.7515.1.e2712c8b00",
274
+ "@open-mercato/ai-assistant": "0.9.1-develop.7516.1.176184247d",
275
+ "@open-mercato/shared": "0.9.1-develop.7516.1.176184247d",
276
+ "@open-mercato/ui": "0.9.1-develop.7516.1.176184247d",
277
277
  "@testing-library/dom": "^10.4.1",
278
278
  "@testing-library/jest-dom": "^7.0.1",
279
279
  "@testing-library/react": "^16.3.3",
@@ -39,6 +39,10 @@ export type SendAsUserInput = {
39
39
  * derives `threadId` from the parent), so a CRM reply continues the existing
40
40
  * conversation instead of starting a new thread. Optional — omitted for new
41
41
  * threads.
42
+ *
43
+ * Not authorized here: the facade serves more than one surface, and each owns
44
+ * what "may reply to" means. The caller MUST have established that the actor
45
+ * can read this message under its own read rule before passing it.
42
46
  */
43
47
  parentMessageId?: string
44
48
  /**
@@ -12,8 +12,11 @@ import {
12
12
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
13
13
  import { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
14
14
  import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
15
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
15
16
  import { resolveAuthActorId } from '../../../../lib/interactionRequestContext'
16
17
  import { CustomerEntity } from '../../../../data/entities'
18
+ import { listGrantsForViewerOnPerson, listSharedChannelIds } from '../../../../lib/conversationShares'
19
+ import { isMessageOnPersonEmailHistory } from '../../../../lib/personEmailThreads'
17
20
  import type { SendAsUserService } from '@open-mercato/core/modules/communication_channels/lib/send-as-user'
18
21
 
19
22
  export const metadata = {
@@ -40,6 +43,29 @@ const composeSchema = z
40
43
  })
41
44
  .strict()
42
45
 
46
+ type RbacService = {
47
+ userHasAllFeatures: (
48
+ userId: string,
49
+ required: string[],
50
+ scope: { tenantId: string | null; organizationId: string | null },
51
+ ) => Promise<boolean>
52
+ }
53
+
54
+ async function callerHasFeature(
55
+ container: { resolve: (name: string) => unknown },
56
+ userId: string,
57
+ feature: string,
58
+ scope: { tenantId: string; organizationId: string | null },
59
+ ): Promise<boolean> {
60
+ try {
61
+ const rbac = container.resolve('rbacService') as RbacService | undefined
62
+ if (typeof rbac?.userHasAllFeatures !== 'function') return false
63
+ return await rbac.userHasAllFeatures(userId, [feature], scope)
64
+ } catch {
65
+ return false
66
+ }
67
+ }
68
+
43
69
  type RouteContext = {
44
70
  params: Promise<{ id: string }> | { id: string }
45
71
  }
@@ -96,6 +122,46 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
96
122
  return NextResponse.json({ error: 'Person not found' }, { status: 404 })
97
123
  }
98
124
 
125
+ // A reply parent decides which thread the sent email joins, so it must be an
126
+ // email this caller could see on this Person's Emails tab — the same feature,
127
+ // anchor and visibility rule as `GET .../email-threads`, including conversation
128
+ // shares and shared team mailboxes. Anything else answers like an unknown id.
129
+ if (body.parentMessageId) {
130
+ const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
131
+ const shareScope = { tenantId: auth.tenantId as string, organizationId }
132
+ const canViewPeople = await callerHasFeature(container, userId, 'customers.people.view', shareScope)
133
+ const [sharedConversations, sharedChannelIds] = canViewPeople
134
+ ? await Promise.all([
135
+ listGrantsForViewerOnPerson(em, shareScope, viewerUserId, personId),
136
+ listSharedChannelIds(em, shareScope, viewerUserId),
137
+ ])
138
+ : [[], []]
139
+ const parentVisible =
140
+ canViewPeople &&
141
+ (await isMessageOnPersonEmailHistory(em, {
142
+ personId,
143
+ tenantId: auth.tenantId as string,
144
+ organizationId,
145
+ viewerUserId,
146
+ userFeatures: undefined,
147
+ sharedConversations,
148
+ sharedChannelIds,
149
+ messageId: body.parentMessageId,
150
+ }))
151
+ if (!parentVisible) {
152
+ const { translate } = await resolveTranslations()
153
+ return NextResponse.json(
154
+ {
155
+ error: translate(
156
+ 'customers.email.errors.parentMessageNotFound',
157
+ 'The email you are replying to was not found',
158
+ ),
159
+ },
160
+ { status: 404 },
161
+ )
162
+ }
163
+ }
164
+
99
165
  const guardResult = await validateCrudMutationGuard(container, {
100
166
  tenantId: auth.tenantId,
101
167
  organizationId,
@@ -178,7 +244,7 @@ export const openApi = {
178
244
  { status: 400, description: 'Invalid person id' },
179
245
  { status: 401, description: 'Unauthorized' },
180
246
  { status: 403, description: 'Missing customers.email.compose feature or mutation guard rejection' },
181
- { status: 404, description: 'Person or channel not found' },
247
+ { status: 404, description: 'Person or channel not found, or the reply parent is not an email the caller can see on this Person' },
182
248
  { status: 409, description: 'Channel not connected' },
183
249
  { status: 422, description: 'Invalid request body' },
184
250
  { status: 500, description: 'Send failed' },
@@ -1691,6 +1691,7 @@
1691
1691
  "customers.email.conversationShare.sharedByTeammate": "Von einem Kollegen geteilt",
1692
1692
  "customers.email.errors.deliveryFailed": "Zustellung fehlgeschlagen – nicht gesendet",
1693
1693
  "customers.email.errors.flipFailed": "Aktualisierung der Sichtbarkeit fehlgeschlagen",
1694
+ "customers.email.errors.parentMessageNotFound": "Die E-Mail, auf die Sie antworten, wurde nicht gefunden",
1694
1695
  "customers.email.errors.sendFailed": "Senden fehlgeschlagen",
1695
1696
  "customers.email.sync.button": "Synchronisieren",
1696
1697
  "customers.email.sync.failed": "Postfach konnte nicht synchronisiert werden",