@open-mercato/core 0.6.8-develop.7021.1.61869f8014 → 0.6.8-develop.7023.1.a1960eb208

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.
@@ -189,7 +189,7 @@ async function GET(req) {
189
189
  const currentPage = usePaging ? Math.max(1, page) : null;
190
190
  const currentPageSize = usePaging ? pageSize : null;
191
191
  const totalPages = usePaging && total !== null ? Math.max(1, Math.ceil(total / currentPageSize)) : null;
192
- const pageOffset = usePaging ? (Math.min(currentPage, totalPages) - 1) * currentPageSize : void 0;
192
+ const pageOffset = usePaging ? (currentPage - 1) * currentPageSize : void 0;
193
193
  const items = await findWithDecryption(
194
194
  em,
195
195
  Attachment,
@@ -229,7 +229,7 @@ async function GET(req) {
229
229
  }),
230
230
  ...usePaging ? {
231
231
  total,
232
- page: Math.min(currentPage, totalPages),
232
+ page: currentPage,
233
233
  pageSize: currentPageSize,
234
234
  totalPages
235
235
  } : {}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/api/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { z } from 'zod'\nimport { sql } from 'kysely'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '../lib/imageUrls'\nimport { ensureDefaultPartitions, resolveDefaultPartitionCode, sanitizePartitionCode } from '../lib/partitions'\nimport { Attachment, AttachmentPartition } from '../data/entities'\nimport { extractAttachmentContent } from '../lib/textExtraction'\nimport { requestOcrProcessing } from '../lib/ocrQueue'\nimport { StorageDriverFactory } from '../lib/drivers'\nimport { OcrService, shouldUseLlmOcr } from '../lib/ocrService'\nimport { clearAttachmentThumbnailCache } from '../lib/thumbnailCache'\nimport { assertAttachmentScopeInvariant } from '../lib/access'\nimport { resolveAttachmentOrganizationId } from '../lib/requestScope'\nimport {\n mergeAttachmentMetadata,\n normalizeAttachmentAssignments,\n normalizeAttachmentTags,\n readAttachmentMetadata,\n upsertAssignment,\n type AttachmentAssignment,\n} from '../lib/metadata'\nimport { randomUUID } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { emitCrudSideEffects, setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { attachmentCrudEvents, attachmentCrudIndexer } from '../lib/crud'\nimport { E } from '#generated/entities.ids.generated'\nimport { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'\nimport {\n detectAttachmentMimeType,\n hasDangerousExecutableExtension,\n isActiveContentAttachment,\n sanitizeUploadedFileName,\n} from '../lib/security'\nimport {\n isMultipartUploadLimitError,\n isMultipartRequestWithinUploadLimit,\n parseMultipartFormDataWithinUploadLimit,\n resolveAttachmentMaxBytes,\n willExceedAttachmentTenantQuota,\n} from '../lib/upload-limits'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { AttachmentQuotaService } from '../lib/quota-service'\n\nconst logger = createLogger('attachments')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['attachments.view'] },\n POST: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n}\n\nconst attachmentQuerySchema = z.object({\n entityId: z.string().min(1).describe('Entity identifier that owns the attachments'),\n recordId: z.string().min(1).describe('Record identifier within the entity'),\n page: z.coerce.number().min(1).optional(),\n pageSize: z.coerce.number().min(1).max(100).optional(),\n})\n\nconst attachmentAssignmentSchema = z.object({\n type: z.string().describe('Assignment type identifier'),\n id: z.string().describe('Assignment record identifier'),\n href: z.string().nullable().optional().describe('Optional link to the related record'),\n label: z.string().nullable().optional().describe('Optional label for the assignment'),\n})\n\nconst attachmentItemSchema = z.object({\n id: z.string().describe('Attachment identifier'),\n url: z.string().describe('Public path to the stored asset'),\n fileName: z.string().describe('Original filename'),\n fileSize: z.number().int().nonnegative().describe('File size in bytes'),\n createdAt: z.string().describe('Upload timestamp (ISO 8601)'),\n mimeType: z.string().nullable().optional().describe('MIME type of the file'),\n thumbnailUrl: z.string().optional().describe('Helper route that renders a thumbnail'),\n partitionCode: z.string().optional().describe('Partition identifier'),\n tags: z.array(z.string()).optional().describe('Tags assigned to the attachment'),\n content: z.string().nullable().optional().describe('Extracted text or markdown content'),\n assignments: z.array(attachmentAssignmentSchema).optional().describe('Records that reference this attachment'),\n})\n\nconst attachmentListResponseSchema = z.object({\n items: z.array(attachmentItemSchema),\n total: z.number().int().nonnegative().optional(),\n page: z.number().int().min(1).optional(),\n pageSize: z.number().int().min(1).optional(),\n totalPages: z.number().int().min(1).optional(),\n})\n\nconst attachmentUploadBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n fieldKey: z.string().optional(),\n file: z.string().min(1).describe('Binary file payload; supplied as multipart form-data'),\n customFields: z\n .string()\n .optional()\n .describe('JSON encoded map of custom field values collected from the upload form.'),\n})\n\nconst attachmentDeleteQuerySchema = z.object({\n id: z.string().uuid(),\n})\n\nconst uploadResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string(),\n url: z.string(),\n fileName: z.string(),\n fileSize: z.number().int().nonnegative(),\n thumbnailUrl: z.string().optional(),\n content: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n assignments: z.array(attachmentAssignmentSchema).optional(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n }),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nconst LIBRARY_ENTITY_ID = 'attachments:library'\n\nfunction parseCustomFieldsEntry(value: FormDataEntryValue | null): Record<string, unknown> {\n if (!value) return {}\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return {}\n try {\n const parsed = JSON.parse(trimmed)\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>\n }\n } catch {\n return {}\n }\n }\n if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof File)) {\n return { ...(value as Record<string, unknown>) }\n }\n return {}\n}\n\nfunction buildFormPayload(form: FormData): Record<string, unknown> {\n const payload: Record<string, unknown> = {}\n form.forEach((value, key) => {\n if (key === 'customFields') {\n payload.customFields = parseCustomFieldsEntry(value)\n return\n }\n payload[key] = value\n })\n return payload\n}\n\nfunction parseFormTags(value: FormDataEntryValue | null): string[] {\n if (!value) return []\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentTags(parsed)\n } catch {\n return normalizeAttachmentTags(value)\n }\n }\n return []\n}\n\nfunction parseFormAssignments(value: FormDataEntryValue | null): AttachmentAssignment[] {\n if (!value) return []\n if (typeof value !== 'string') return []\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentAssignments(parsed)\n } catch {\n return []\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsedQuery = attachmentQuerySchema.safeParse({\n entityId: url.searchParams.get('entityId') || '',\n recordId: url.searchParams.get('recordId') || '',\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'entityId and recordId are required' }, { status: 400 })\n }\n const { entityId, recordId, page, pageSize } = parsedQuery.data\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n const filter: Record<string, unknown> = { entityId, recordId, tenantId: auth.tenantId! }\n if (orgId) filter.organizationId = orgId\n const orderBy: Record<string, 'ASC' | 'DESC'> = { createdAt: 'DESC' }\n const usePaging = typeof page === 'number' && typeof pageSize === 'number'\n const total = usePaging ? await em.count(Attachment, filter) : null\n const currentPage = usePaging ? Math.max(1, page) : null\n const currentPageSize = usePaging ? pageSize : null\n const totalPages = usePaging && total !== null ? Math.max(1, Math.ceil(total / currentPageSize!)) : null\n const pageOffset = usePaging ? (Math.min(currentPage!, totalPages!) - 1) * currentPageSize! : undefined\n const items = await findWithDecryption(\n em,\n Attachment,\n filter,\n {\n orderBy,\n ...(usePaging\n ? {\n limit: currentPageSize!,\n offset: pageOffset,\n }\n : {}),\n },\n {\n tenantId: auth.tenantId ?? null,\n organizationId: orgId ?? null,\n },\n )\n return NextResponse.json({\n items: items.map((a: any) => {\n const metadata = readAttachmentMetadata(a.storageMetadata)\n return {\n id: a.id,\n url: a.url,\n fileName: a.fileName,\n fileSize: a.fileSize,\n createdAt: a.createdAt,\n mimeType: a.mimeType ?? null,\n partitionCode: a.partitionCode,\n content: a.content ?? null,\n thumbnailUrl: buildAttachmentImageUrl(a.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(a.fileName),\n }),\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n }\n }),\n ...(usePaging\n ? {\n total,\n page: Math.min(currentPage!, totalPages!),\n pageSize: currentPageSize,\n totalPages,\n }\n : {}),\n })\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // A superadmin browsing with \"All organizations\" selected has no concrete\n // organization scope, but an attachment must be stored under exactly one\n // organization (the scope invariant forbids partial-null rows). Reject with a\n // clear, actionable error instead of a 401 \u2014 a 401 makes the client-side\n // fetch layer treat it as session expiry, show a misleading toast, and reset\n // the in-progress form (#3764).\n if (!auth.orgId) {\n return NextResponse.json({\n error: t('attachments.errors.selectOrganization', 'Select a specific organization before uploading an attachment.'),\n }, { status: 400 })\n }\n const tenantId = auth.tenantId\n\n const contentType = req.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n\n let form: FormData\n try {\n form = await parseMultipartFormDataWithinUploadLimit(req)\n } catch (error) {\n if (isMultipartUploadLimitError(error)) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n throw error\n }\n const formPayload = buildFormPayload(form)\n const customFieldValues = splitCustomFieldPayload(formPayload).custom\n const entityId = String(form.get('entityId') || '')\n const recordId = String(form.get('recordId') || '')\n const fieldKey = String(form.get('fieldKey') || '')\n const file = form.get('file') as unknown as File | null\n if (!entityId || !recordId || !file) return NextResponse.json({ error: 'entityId, recordId and file are required' }, { status: 400 })\n const partitionOverrideRaw = form.get('partitionCode')\n const partitionOverride =\n typeof partitionOverrideRaw === 'string' && partitionOverrideRaw.trim().length > 0\n ? sanitizePartitionCode(partitionOverrideRaw)\n : null\n const tags = parseFormTags(form.get('tags'))\n const assignmentsFromForm = parseFormAssignments(form.get('assignments'))\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const dataEngine = container.resolve('dataEngine')\n let attachmentQuotaService: AttachmentQuotaService | null = null\n let attachmentQuotaRecoveryScheduler: ((\n payload: { reservationId: string; tenantId: string; organizationId: string },\n delayMs: number,\n ) => Promise<void>) | null = null\n try {\n attachmentQuotaService = container.resolve('attachmentQuotaService') as AttachmentQuotaService\n attachmentQuotaRecoveryScheduler = container.resolve('attachmentQuotaRecoveryScheduler') as (\n payload: { reservationId: string; tenantId: string; organizationId: string },\n delayMs: number,\n ) => Promise<void>\n } catch {\n // Legacy fallback below when the quota service is not registered in this container.\n }\n const storageDriverFactory =\n (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n if (!orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n await ensureDefaultPartitions(em)\n // Optional per-field validations\n let partitionFromField: string | null = null\n let fieldMaxAttachmentSizeMb: number | null = null\n if (fieldKey) {\n try {\n const { CustomFieldDef } = await import('@open-mercato/core/modules/entities/data/entities')\n const def = await em.findOne(CustomFieldDef, {\n entityId,\n key: fieldKey,\n $and: [\n { $or: [ { tenantId: auth.tenantId }, { tenantId: null } ] },\n ],\n isActive: true,\n })\n const cfg = (def as any)?.configJson || {}\n const ext = (file.name || '').split('.').pop()?.toLowerCase() || ''\n if (Array.isArray(cfg.acceptExtensions) && cfg.acceptExtensions.length) {\n const allowed = new Set((cfg.acceptExtensions as any[]).map((x: any) => String(x).toLowerCase().replace(/^\\./, '')))\n if (!allowed.has(ext)) return NextResponse.json({ error: 'File type not allowed' }, { status: 400 })\n }\n if (typeof cfg.maxAttachmentSizeMb === 'number' && cfg.maxAttachmentSizeMb > 0) {\n fieldMaxAttachmentSizeMb = cfg.maxAttachmentSizeMb\n }\n if (typeof cfg.partitionCode === 'string' && cfg.partitionCode.trim().length > 0) {\n partitionFromField = sanitizePartitionCode(cfg.partitionCode)\n }\n } catch {}\n }\n if (hasDangerousExecutableExtension(file.name)) {\n return NextResponse.json({\n error: t('attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'),\n }, { status: 400 })\n }\n const effectiveMaxBytes = resolveAttachmentMaxBytes(fieldMaxAttachmentSizeMb)\n if (file.size > effectiveMaxBytes) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n if (!attachmentQuotaService) {\n try {\n const tenantUsageBytes = await readTenantAttachmentUsageBytes(em, tenantId)\n if (willExceedAttachmentTenantQuota(tenantUsageBytes, file.size)) {\n return NextResponse.json({\n error: t('attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'),\n }, { status: 413 })\n }\n } catch (error) {\n logger.error('Attachment quota accounting failed', { err: error })\n return NextResponse.json({\n error: t('attachments.errors.quotaUnavailable', 'Storage quota accounting is unavailable.'),\n }, { status: 500 })\n }\n }\n const buf = Buffer.from(await file.arrayBuffer())\n const safeName = sanitizeUploadedFileName(file.name)\n const fileMimeType = detectAttachmentMimeType(buf, safeName, (file as any).type)\n if (isActiveContentAttachment(buf, safeName, fileMimeType)) {\n return NextResponse.json({ error: t('attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.') }, { status: 400 })\n }\n const defaultPartitionCode = resolveDefaultPartitionCode(entityId)\n const resolvedPartitionCode = partitionOverride ?? partitionFromField ?? defaultPartitionCode\n const partitionCodeCandidates = Array.from(\n new Set(\n [partitionOverride, partitionFromField, resolvedPartitionCode].filter(\n (code): code is string => typeof code === 'string' && code.length > 0,\n ),\n ),\n )\n let partition: AttachmentPartition | null = null\n for (const code of partitionCodeCandidates) {\n const record = await em.findOne(AttachmentPartition, { code })\n if (record) {\n partition = record\n break\n }\n }\n if (!partition) {\n partition = await em.findOne(AttachmentPartition, { code: defaultPartitionCode })\n }\n if (!partition) {\n return NextResponse.json({ error: 'Storage partition is not configured.' }, { status: 400 })\n }\n const requestedPublicOverride =\n typeof partitionOverride === 'string' &&\n partitionOverride.length > 0 &&\n partition.code === partitionOverride &&\n partition.isPublic === true &&\n partition.code !== defaultPartitionCode &&\n partition.code !== partitionFromField\n if (requestedPublicOverride) {\n return NextResponse.json({ error: t('attachments.errors.publicPartitionBlocked', 'Public storage partitions cannot be selected explicitly for this upload.') }, { status: 403 })\n }\n const uploadDriver = await storageDriverFactory.resolveForPartition(partition.code, { tenantId, organizationId: orgId })\n const preparedStoragePath = attachmentQuotaService\n ? uploadDriver.prepareStoragePath?.({\n partitionCode: partition.code,\n orgId,\n tenantId,\n fileName: safeName,\n })\n : undefined\n if (attachmentQuotaService && !preparedStoragePath) {\n return NextResponse.json({\n error: t('attachments.errors.quotaRecoveryUnsupported', 'Storage driver cannot participate in quota recovery.'),\n }, { status: 500 })\n }\n let quotaReservation: { id: string; leaseToken: string; expiresAt: Date } | null = null\n if (attachmentQuotaService) {\n try {\n quotaReservation = await attachmentQuotaService.reserve({\n tenantId,\n organizationId: orgId,\n bytes: buf.length,\n source: 'attachment',\n storageDriver: uploadDriver.key,\n storagePath: preparedStoragePath!,\n partitionCode: partition.code,\n })\n if (!attachmentQuotaRecoveryScheduler) throw new Error('Attachment quota recovery is unavailable.')\n await attachmentQuotaRecoveryScheduler({\n reservationId: quotaReservation.id,\n tenantId,\n organizationId: orgId,\n }, Math.max(1_000, quotaReservation.expiresAt.getTime() - Date.now()))\n if (typeof attachmentQuotaService.beginStorage === 'function') {\n await attachmentQuotaService.beginStorage(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (error) {\n if (quotaReservation) {\n await attachmentQuotaService.release(quotaReservation.id, quotaReservation.leaseToken).catch(() => {})\n quotaReservation = null\n }\n const code = (error as { code?: unknown })?.code\n if (code === 'quota_exceeded') {\n return NextResponse.json({\n error: t('attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'),\n }, { status: 413 })\n }\n if (code === 'quota_target_exists') {\n return NextResponse.json({\n error: t('attachments.errors.storagePathExists', 'The target storage path already exists.'),\n }, { status: 409 })\n }\n logger.error('Attachment quota reservation failed', { err: error })\n return NextResponse.json({\n error: t('attachments.errors.quotaUnavailable', 'Storage quota accounting is unavailable.'),\n }, { status: 500 })\n }\n }\n let storedPath: string\n try {\n const stored = await uploadDriver.store({\n partitionCode: partition.code,\n orgId,\n tenantId,\n fileName: safeName,\n buffer: buf,\n storagePath: preparedStoragePath,\n })\n storedPath = stored.storagePath\n if (quotaReservation) {\n await attachmentQuotaService!.markStored(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (error) {\n logger.error('Failed to persist file', { err: error })\n if (preparedStoragePath) {\n try {\n await (uploadDriver.deleteStrict?.(partition.code, preparedStoragePath)\n ?? uploadDriver.delete(partition.code, preparedStoragePath))\n if (quotaReservation) {\n await attachmentQuotaService!.release(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (cleanupError) {\n logger.error('Failed to compensate attachment storage', { err: cleanupError })\n }\n }\n return NextResponse.json({ error: 'Failed to persist attachment.' }, { status: 500 })\n }\n\n const requiresOcr =\n typeof (partition as any).requiresOcr === 'boolean'\n ? Boolean((partition as any).requiresOcr)\n : resolveDefaultAttachmentOcrEnabled()\n let extractedContent: string | null = null\n const wantsLlmOcr = requiresOcr && shouldUseLlmOcr(fileMimeType, safeName)\n const ocrService = wantsLlmOcr ? new OcrService() : null\n const useLlmOcr = Boolean(wantsLlmOcr && ocrService?.available)\n\n if (requiresOcr && !useLlmOcr) {\n const { filePath: localPath, cleanup } = await uploadDriver.toLocalPath(partition.code, storedPath)\n try {\n extractedContent = await extractAttachmentContent({\n filePath: localPath,\n mimeType: fileMimeType,\n })\n } catch (error) {\n logger.error('Failed to extract attachment content', { err: error })\n } finally {\n await cleanup().catch(() => {})\n }\n }\n\n let assignments = assignmentsFromForm.slice()\n if (entityId !== LIBRARY_ENTITY_ID) {\n assignments = upsertAssignment(assignments, { type: entityId, id: recordId })\n }\n const metadata = mergeAttachmentMetadata(null, { assignments, tags })\n const attachmentId = randomUUID()\n assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: orgId })\n const att = em.create(Attachment, {\n id: attachmentId,\n entityId,\n recordId,\n organizationId: orgId,\n tenantId: auth.tenantId!,\n fileName: safeName,\n mimeType: fileMimeType,\n fileSize: buf.length,\n partitionCode: partition.code,\n storageDriver: partition.storageDriver || 'local',\n storagePath: storedPath,\n url: buildAttachmentFileUrl(attachmentId),\n content: extractedContent,\n storageMetadata: metadata,\n })\n // Persist the attachment row and its custom-field values atomically so a\n // custom-field failure cannot leave behind a committed orphan attachment.\n try {\n await em.transactional(async (tx) => {\n await tx.persist(att).flush()\n if (dataEngine) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.attachments.attachment,\n recordId: attachmentId,\n tenantId,\n organizationId: orgId,\n values: customFieldValues,\n })\n }\n if (quotaReservation) {\n await attachmentQuotaService!.completeAttachment(quotaReservation.id, quotaReservation.leaseToken, tx)\n }\n })\n } catch (error) {\n logger.error('Failed to persist attachment with custom attributes', { err: error })\n try {\n await (uploadDriver.deleteStrict?.(partition.code, storedPath)\n ?? uploadDriver.delete(partition.code, storedPath))\n if (quotaReservation) {\n await attachmentQuotaService!.release(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (cleanupError) {\n logger.error('Failed to compensate attachment persistence', { err: cleanupError })\n }\n return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })\n }\n\n if (useLlmOcr) {\n requestOcrProcessing(em, att, uploadDriver, storedPath).catch((error) => {\n logger.error('Failed to queue OCR processing', { err: error })\n })\n } else if (wantsLlmOcr) {\n logger.warn('OCR requested but OPENAI_API_KEY not configured, falling back to text extraction when available')\n }\n\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: att,\n identifiers: {\n id: att.id,\n organizationId: att.organizationId ?? null,\n tenantId: att.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n\n return NextResponse.json({\n ok: true,\n item: {\n id: attachmentId,\n url: att.url,\n fileName: safeName,\n fileSize: buf.length,\n partitionCode: partition.code,\n thumbnailUrl: buildAttachmentImageUrl(attachmentId, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(safeName),\n }),\n content: extractedContent ?? null,\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n customFields: Object.keys(customFieldValues).length ? customFieldValues : undefined,\n },\n })\n}\n\nasync function readTenantAttachmentUsageBytes(em: EntityManager, tenantId: string): Promise<number> {\n try {\n const db = em.getKysely<any>() as any\n const row = await db\n .selectFrom('attachments')\n .select(sql<string>`sum(file_size)`.as('total_size'))\n .where('tenant_id', '=', tenantId)\n .executeTakeFirst() as { total_size: string | number | null } | undefined\n const total = row?.total_size\n if (typeof total === 'number') return Number.isFinite(total) ? total : 0\n if (typeof total === 'string') {\n const parsed = Number(total)\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n } catch (error) {\n throw new Error('Attachment quota accounting is unavailable.', { cause: error })\n }\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const id = url.searchParams.get('id') || ''\n if (!id) return NextResponse.json({ error: 'Attachment id is required' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const dataEngine = container.resolve('dataEngine')\n const storageDriverFactory =\n (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n // Resolve the currently selected organization (#3765) so a multi-org admin who\n // switched the header org deletes within that org, not their pinned home org.\n // A superadmin browsing with \"All organizations\" selected has no concrete org,\n // so resolution returns null and the delete falls back to a tenant-only scope\n // (#3764) \u2014 letting them remove any attachment in the tenant.\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n const deleteFilter: Record<string, unknown> = { id, tenantId: auth.tenantId! }\n if (orgId) deleteFilter.organizationId = orgId\n const record = await em.findOne(Attachment, deleteFilter)\n if (!record) return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })\n await em.remove(record).flush()\n await clearAttachmentThumbnailCache(record.partitionCode, record.id).catch((error) => {\n logger.error('Failed to cleanup cached thumbnails', { err: error })\n })\n if (record.storagePath) {\n const delDriver = await storageDriverFactory.resolveForPartition(record.partitionCode, {\n tenantId: record.tenantId ?? auth.tenantId!,\n organizationId: record.organizationId ?? orgId ?? '',\n })\n await delDriver.delete(record.partitionCode, record.storagePath)\n }\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId ?? null,\n tenantId: record.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n return NextResponse.json({ ok: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage entity attachments',\n description: 'Upload and list attachments associated with module entities and records.',\n methods: {\n GET: {\n summary: 'List attachments for a record',\n description: 'Returns uploaded attachments for the given entity record, ordered by newest first.',\n query: attachmentQuerySchema,\n responses: [\n { status: 200, description: 'Attachments found for the record', schema: attachmentListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity or record identifiers', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Upload attachment',\n description: 'Uploads a new attachment using multipart form-data and stores metadata for later retrieval.',\n requestBody: {\n contentType: 'multipart/form-data',\n schema: attachmentUploadBodySchema,\n },\n responses: [\n { status: 200, description: 'Attachment stored successfully', schema: uploadResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Payload validation error', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Attachment violates field constraints', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete attachment',\n description: 'Removes an uploaded attachment and deletes the stored asset.',\n query: attachmentDeleteQuerySchema,\n responses: [\n { status: 200, description: 'Attachment deleted', schema: z.object({ ok: z.literal(true) }) },\n { status: 404, description: 'Attachment not found', schema: errorSchema },\n ],\n errors: [\n { status: 400, description: 'Missing attachment identifier', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { z } from 'zod'\nimport { sql } from 'kysely'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '../lib/imageUrls'\nimport { ensureDefaultPartitions, resolveDefaultPartitionCode, sanitizePartitionCode } from '../lib/partitions'\nimport { Attachment, AttachmentPartition } from '../data/entities'\nimport { extractAttachmentContent } from '../lib/textExtraction'\nimport { requestOcrProcessing } from '../lib/ocrQueue'\nimport { StorageDriverFactory } from '../lib/drivers'\nimport { OcrService, shouldUseLlmOcr } from '../lib/ocrService'\nimport { clearAttachmentThumbnailCache } from '../lib/thumbnailCache'\nimport { assertAttachmentScopeInvariant } from '../lib/access'\nimport { resolveAttachmentOrganizationId } from '../lib/requestScope'\nimport {\n mergeAttachmentMetadata,\n normalizeAttachmentAssignments,\n normalizeAttachmentTags,\n readAttachmentMetadata,\n upsertAssignment,\n type AttachmentAssignment,\n} from '../lib/metadata'\nimport { randomUUID } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { emitCrudSideEffects, setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { attachmentCrudEvents, attachmentCrudIndexer } from '../lib/crud'\nimport { E } from '#generated/entities.ids.generated'\nimport { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'\nimport {\n detectAttachmentMimeType,\n hasDangerousExecutableExtension,\n isActiveContentAttachment,\n sanitizeUploadedFileName,\n} from '../lib/security'\nimport {\n isMultipartUploadLimitError,\n isMultipartRequestWithinUploadLimit,\n parseMultipartFormDataWithinUploadLimit,\n resolveAttachmentMaxBytes,\n willExceedAttachmentTenantQuota,\n} from '../lib/upload-limits'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { AttachmentQuotaService } from '../lib/quota-service'\n\nconst logger = createLogger('attachments')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['attachments.view'] },\n POST: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n}\n\nconst attachmentQuerySchema = z.object({\n entityId: z.string().min(1).describe('Entity identifier that owns the attachments'),\n recordId: z.string().min(1).describe('Record identifier within the entity'),\n page: z.coerce.number().min(1).optional(),\n pageSize: z.coerce.number().min(1).max(100).optional(),\n})\n\nconst attachmentAssignmentSchema = z.object({\n type: z.string().describe('Assignment type identifier'),\n id: z.string().describe('Assignment record identifier'),\n href: z.string().nullable().optional().describe('Optional link to the related record'),\n label: z.string().nullable().optional().describe('Optional label for the assignment'),\n})\n\nconst attachmentItemSchema = z.object({\n id: z.string().describe('Attachment identifier'),\n url: z.string().describe('Public path to the stored asset'),\n fileName: z.string().describe('Original filename'),\n fileSize: z.number().int().nonnegative().describe('File size in bytes'),\n createdAt: z.string().describe('Upload timestamp (ISO 8601)'),\n mimeType: z.string().nullable().optional().describe('MIME type of the file'),\n thumbnailUrl: z.string().optional().describe('Helper route that renders a thumbnail'),\n partitionCode: z.string().optional().describe('Partition identifier'),\n tags: z.array(z.string()).optional().describe('Tags assigned to the attachment'),\n content: z.string().nullable().optional().describe('Extracted text or markdown content'),\n assignments: z.array(attachmentAssignmentSchema).optional().describe('Records that reference this attachment'),\n})\n\nconst attachmentListResponseSchema = z.object({\n items: z.array(attachmentItemSchema),\n total: z.number().int().nonnegative().optional(),\n page: z.number().int().min(1).optional(),\n pageSize: z.number().int().min(1).optional(),\n totalPages: z.number().int().min(1).optional(),\n})\n\nconst attachmentUploadBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n fieldKey: z.string().optional(),\n file: z.string().min(1).describe('Binary file payload; supplied as multipart form-data'),\n customFields: z\n .string()\n .optional()\n .describe('JSON encoded map of custom field values collected from the upload form.'),\n})\n\nconst attachmentDeleteQuerySchema = z.object({\n id: z.string().uuid(),\n})\n\nconst uploadResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string(),\n url: z.string(),\n fileName: z.string(),\n fileSize: z.number().int().nonnegative(),\n thumbnailUrl: z.string().optional(),\n content: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n assignments: z.array(attachmentAssignmentSchema).optional(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n }),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nconst LIBRARY_ENTITY_ID = 'attachments:library'\n\nfunction parseCustomFieldsEntry(value: FormDataEntryValue | null): Record<string, unknown> {\n if (!value) return {}\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return {}\n try {\n const parsed = JSON.parse(trimmed)\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>\n }\n } catch {\n return {}\n }\n }\n if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof File)) {\n return { ...(value as Record<string, unknown>) }\n }\n return {}\n}\n\nfunction buildFormPayload(form: FormData): Record<string, unknown> {\n const payload: Record<string, unknown> = {}\n form.forEach((value, key) => {\n if (key === 'customFields') {\n payload.customFields = parseCustomFieldsEntry(value)\n return\n }\n payload[key] = value\n })\n return payload\n}\n\nfunction parseFormTags(value: FormDataEntryValue | null): string[] {\n if (!value) return []\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentTags(parsed)\n } catch {\n return normalizeAttachmentTags(value)\n }\n }\n return []\n}\n\nfunction parseFormAssignments(value: FormDataEntryValue | null): AttachmentAssignment[] {\n if (!value) return []\n if (typeof value !== 'string') return []\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentAssignments(parsed)\n } catch {\n return []\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsedQuery = attachmentQuerySchema.safeParse({\n entityId: url.searchParams.get('entityId') || '',\n recordId: url.searchParams.get('recordId') || '',\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'entityId and recordId are required' }, { status: 400 })\n }\n const { entityId, recordId, page, pageSize } = parsedQuery.data\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n const filter: Record<string, unknown> = { entityId, recordId, tenantId: auth.tenantId! }\n if (orgId) filter.organizationId = orgId\n const orderBy: Record<string, 'ASC' | 'DESC'> = { createdAt: 'DESC' }\n const usePaging = typeof page === 'number' && typeof pageSize === 'number'\n const total = usePaging ? await em.count(Attachment, filter) : null\n const currentPage = usePaging ? Math.max(1, page) : null\n const currentPageSize = usePaging ? pageSize : null\n const totalPages = usePaging && total !== null ? Math.max(1, Math.ceil(total / currentPageSize!)) : null\n const pageOffset = usePaging ? (currentPage! - 1) * currentPageSize! : undefined\n const items = await findWithDecryption(\n em,\n Attachment,\n filter,\n {\n orderBy,\n ...(usePaging\n ? {\n limit: currentPageSize!,\n offset: pageOffset,\n }\n : {}),\n },\n {\n tenantId: auth.tenantId ?? null,\n organizationId: orgId ?? null,\n },\n )\n return NextResponse.json({\n items: items.map((a: any) => {\n const metadata = readAttachmentMetadata(a.storageMetadata)\n return {\n id: a.id,\n url: a.url,\n fileName: a.fileName,\n fileSize: a.fileSize,\n createdAt: a.createdAt,\n mimeType: a.mimeType ?? null,\n partitionCode: a.partitionCode,\n content: a.content ?? null,\n thumbnailUrl: buildAttachmentImageUrl(a.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(a.fileName),\n }),\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n }\n }),\n ...(usePaging\n ? {\n total,\n page: currentPage,\n pageSize: currentPageSize,\n totalPages,\n }\n : {}),\n })\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // A superadmin browsing with \"All organizations\" selected has no concrete\n // organization scope, but an attachment must be stored under exactly one\n // organization (the scope invariant forbids partial-null rows). Reject with a\n // clear, actionable error instead of a 401 \u2014 a 401 makes the client-side\n // fetch layer treat it as session expiry, show a misleading toast, and reset\n // the in-progress form (#3764).\n if (!auth.orgId) {\n return NextResponse.json({\n error: t('attachments.errors.selectOrganization', 'Select a specific organization before uploading an attachment.'),\n }, { status: 400 })\n }\n const tenantId = auth.tenantId\n\n const contentType = req.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n\n let form: FormData\n try {\n form = await parseMultipartFormDataWithinUploadLimit(req)\n } catch (error) {\n if (isMultipartUploadLimitError(error)) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n throw error\n }\n const formPayload = buildFormPayload(form)\n const customFieldValues = splitCustomFieldPayload(formPayload).custom\n const entityId = String(form.get('entityId') || '')\n const recordId = String(form.get('recordId') || '')\n const fieldKey = String(form.get('fieldKey') || '')\n const file = form.get('file') as unknown as File | null\n if (!entityId || !recordId || !file) return NextResponse.json({ error: 'entityId, recordId and file are required' }, { status: 400 })\n const partitionOverrideRaw = form.get('partitionCode')\n const partitionOverride =\n typeof partitionOverrideRaw === 'string' && partitionOverrideRaw.trim().length > 0\n ? sanitizePartitionCode(partitionOverrideRaw)\n : null\n const tags = parseFormTags(form.get('tags'))\n const assignmentsFromForm = parseFormAssignments(form.get('assignments'))\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const dataEngine = container.resolve('dataEngine')\n let attachmentQuotaService: AttachmentQuotaService | null = null\n let attachmentQuotaRecoveryScheduler: ((\n payload: { reservationId: string; tenantId: string; organizationId: string },\n delayMs: number,\n ) => Promise<void>) | null = null\n try {\n attachmentQuotaService = container.resolve('attachmentQuotaService') as AttachmentQuotaService\n attachmentQuotaRecoveryScheduler = container.resolve('attachmentQuotaRecoveryScheduler') as (\n payload: { reservationId: string; tenantId: string; organizationId: string },\n delayMs: number,\n ) => Promise<void>\n } catch {\n // Legacy fallback below when the quota service is not registered in this container.\n }\n const storageDriverFactory =\n (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n if (!orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n await ensureDefaultPartitions(em)\n // Optional per-field validations\n let partitionFromField: string | null = null\n let fieldMaxAttachmentSizeMb: number | null = null\n if (fieldKey) {\n try {\n const { CustomFieldDef } = await import('@open-mercato/core/modules/entities/data/entities')\n const def = await em.findOne(CustomFieldDef, {\n entityId,\n key: fieldKey,\n $and: [\n { $or: [ { tenantId: auth.tenantId }, { tenantId: null } ] },\n ],\n isActive: true,\n })\n const cfg = (def as any)?.configJson || {}\n const ext = (file.name || '').split('.').pop()?.toLowerCase() || ''\n if (Array.isArray(cfg.acceptExtensions) && cfg.acceptExtensions.length) {\n const allowed = new Set((cfg.acceptExtensions as any[]).map((x: any) => String(x).toLowerCase().replace(/^\\./, '')))\n if (!allowed.has(ext)) return NextResponse.json({ error: 'File type not allowed' }, { status: 400 })\n }\n if (typeof cfg.maxAttachmentSizeMb === 'number' && cfg.maxAttachmentSizeMb > 0) {\n fieldMaxAttachmentSizeMb = cfg.maxAttachmentSizeMb\n }\n if (typeof cfg.partitionCode === 'string' && cfg.partitionCode.trim().length > 0) {\n partitionFromField = sanitizePartitionCode(cfg.partitionCode)\n }\n } catch {}\n }\n if (hasDangerousExecutableExtension(file.name)) {\n return NextResponse.json({\n error: t('attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'),\n }, { status: 400 })\n }\n const effectiveMaxBytes = resolveAttachmentMaxBytes(fieldMaxAttachmentSizeMb)\n if (file.size > effectiveMaxBytes) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n if (!attachmentQuotaService) {\n try {\n const tenantUsageBytes = await readTenantAttachmentUsageBytes(em, tenantId)\n if (willExceedAttachmentTenantQuota(tenantUsageBytes, file.size)) {\n return NextResponse.json({\n error: t('attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'),\n }, { status: 413 })\n }\n } catch (error) {\n logger.error('Attachment quota accounting failed', { err: error })\n return NextResponse.json({\n error: t('attachments.errors.quotaUnavailable', 'Storage quota accounting is unavailable.'),\n }, { status: 500 })\n }\n }\n const buf = Buffer.from(await file.arrayBuffer())\n const safeName = sanitizeUploadedFileName(file.name)\n const fileMimeType = detectAttachmentMimeType(buf, safeName, (file as any).type)\n if (isActiveContentAttachment(buf, safeName, fileMimeType)) {\n return NextResponse.json({ error: t('attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.') }, { status: 400 })\n }\n const defaultPartitionCode = resolveDefaultPartitionCode(entityId)\n const resolvedPartitionCode = partitionOverride ?? partitionFromField ?? defaultPartitionCode\n const partitionCodeCandidates = Array.from(\n new Set(\n [partitionOverride, partitionFromField, resolvedPartitionCode].filter(\n (code): code is string => typeof code === 'string' && code.length > 0,\n ),\n ),\n )\n let partition: AttachmentPartition | null = null\n for (const code of partitionCodeCandidates) {\n const record = await em.findOne(AttachmentPartition, { code })\n if (record) {\n partition = record\n break\n }\n }\n if (!partition) {\n partition = await em.findOne(AttachmentPartition, { code: defaultPartitionCode })\n }\n if (!partition) {\n return NextResponse.json({ error: 'Storage partition is not configured.' }, { status: 400 })\n }\n const requestedPublicOverride =\n typeof partitionOverride === 'string' &&\n partitionOverride.length > 0 &&\n partition.code === partitionOverride &&\n partition.isPublic === true &&\n partition.code !== defaultPartitionCode &&\n partition.code !== partitionFromField\n if (requestedPublicOverride) {\n return NextResponse.json({ error: t('attachments.errors.publicPartitionBlocked', 'Public storage partitions cannot be selected explicitly for this upload.') }, { status: 403 })\n }\n const uploadDriver = await storageDriverFactory.resolveForPartition(partition.code, { tenantId, organizationId: orgId })\n const preparedStoragePath = attachmentQuotaService\n ? uploadDriver.prepareStoragePath?.({\n partitionCode: partition.code,\n orgId,\n tenantId,\n fileName: safeName,\n })\n : undefined\n if (attachmentQuotaService && !preparedStoragePath) {\n return NextResponse.json({\n error: t('attachments.errors.quotaRecoveryUnsupported', 'Storage driver cannot participate in quota recovery.'),\n }, { status: 500 })\n }\n let quotaReservation: { id: string; leaseToken: string; expiresAt: Date } | null = null\n if (attachmentQuotaService) {\n try {\n quotaReservation = await attachmentQuotaService.reserve({\n tenantId,\n organizationId: orgId,\n bytes: buf.length,\n source: 'attachment',\n storageDriver: uploadDriver.key,\n storagePath: preparedStoragePath!,\n partitionCode: partition.code,\n })\n if (!attachmentQuotaRecoveryScheduler) throw new Error('Attachment quota recovery is unavailable.')\n await attachmentQuotaRecoveryScheduler({\n reservationId: quotaReservation.id,\n tenantId,\n organizationId: orgId,\n }, Math.max(1_000, quotaReservation.expiresAt.getTime() - Date.now()))\n if (typeof attachmentQuotaService.beginStorage === 'function') {\n await attachmentQuotaService.beginStorage(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (error) {\n if (quotaReservation) {\n await attachmentQuotaService.release(quotaReservation.id, quotaReservation.leaseToken).catch(() => {})\n quotaReservation = null\n }\n const code = (error as { code?: unknown })?.code\n if (code === 'quota_exceeded') {\n return NextResponse.json({\n error: t('attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'),\n }, { status: 413 })\n }\n if (code === 'quota_target_exists') {\n return NextResponse.json({\n error: t('attachments.errors.storagePathExists', 'The target storage path already exists.'),\n }, { status: 409 })\n }\n logger.error('Attachment quota reservation failed', { err: error })\n return NextResponse.json({\n error: t('attachments.errors.quotaUnavailable', 'Storage quota accounting is unavailable.'),\n }, { status: 500 })\n }\n }\n let storedPath: string\n try {\n const stored = await uploadDriver.store({\n partitionCode: partition.code,\n orgId,\n tenantId,\n fileName: safeName,\n buffer: buf,\n storagePath: preparedStoragePath,\n })\n storedPath = stored.storagePath\n if (quotaReservation) {\n await attachmentQuotaService!.markStored(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (error) {\n logger.error('Failed to persist file', { err: error })\n if (preparedStoragePath) {\n try {\n await (uploadDriver.deleteStrict?.(partition.code, preparedStoragePath)\n ?? uploadDriver.delete(partition.code, preparedStoragePath))\n if (quotaReservation) {\n await attachmentQuotaService!.release(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (cleanupError) {\n logger.error('Failed to compensate attachment storage', { err: cleanupError })\n }\n }\n return NextResponse.json({ error: 'Failed to persist attachment.' }, { status: 500 })\n }\n\n const requiresOcr =\n typeof (partition as any).requiresOcr === 'boolean'\n ? Boolean((partition as any).requiresOcr)\n : resolveDefaultAttachmentOcrEnabled()\n let extractedContent: string | null = null\n const wantsLlmOcr = requiresOcr && shouldUseLlmOcr(fileMimeType, safeName)\n const ocrService = wantsLlmOcr ? new OcrService() : null\n const useLlmOcr = Boolean(wantsLlmOcr && ocrService?.available)\n\n if (requiresOcr && !useLlmOcr) {\n const { filePath: localPath, cleanup } = await uploadDriver.toLocalPath(partition.code, storedPath)\n try {\n extractedContent = await extractAttachmentContent({\n filePath: localPath,\n mimeType: fileMimeType,\n })\n } catch (error) {\n logger.error('Failed to extract attachment content', { err: error })\n } finally {\n await cleanup().catch(() => {})\n }\n }\n\n let assignments = assignmentsFromForm.slice()\n if (entityId !== LIBRARY_ENTITY_ID) {\n assignments = upsertAssignment(assignments, { type: entityId, id: recordId })\n }\n const metadata = mergeAttachmentMetadata(null, { assignments, tags })\n const attachmentId = randomUUID()\n assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: orgId })\n const att = em.create(Attachment, {\n id: attachmentId,\n entityId,\n recordId,\n organizationId: orgId,\n tenantId: auth.tenantId!,\n fileName: safeName,\n mimeType: fileMimeType,\n fileSize: buf.length,\n partitionCode: partition.code,\n storageDriver: partition.storageDriver || 'local',\n storagePath: storedPath,\n url: buildAttachmentFileUrl(attachmentId),\n content: extractedContent,\n storageMetadata: metadata,\n })\n // Persist the attachment row and its custom-field values atomically so a\n // custom-field failure cannot leave behind a committed orphan attachment.\n try {\n await em.transactional(async (tx) => {\n await tx.persist(att).flush()\n if (dataEngine) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.attachments.attachment,\n recordId: attachmentId,\n tenantId,\n organizationId: orgId,\n values: customFieldValues,\n })\n }\n if (quotaReservation) {\n await attachmentQuotaService!.completeAttachment(quotaReservation.id, quotaReservation.leaseToken, tx)\n }\n })\n } catch (error) {\n logger.error('Failed to persist attachment with custom attributes', { err: error })\n try {\n await (uploadDriver.deleteStrict?.(partition.code, storedPath)\n ?? uploadDriver.delete(partition.code, storedPath))\n if (quotaReservation) {\n await attachmentQuotaService!.release(quotaReservation.id, quotaReservation.leaseToken)\n }\n } catch (cleanupError) {\n logger.error('Failed to compensate attachment persistence', { err: cleanupError })\n }\n return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })\n }\n\n if (useLlmOcr) {\n requestOcrProcessing(em, att, uploadDriver, storedPath).catch((error) => {\n logger.error('Failed to queue OCR processing', { err: error })\n })\n } else if (wantsLlmOcr) {\n logger.warn('OCR requested but OPENAI_API_KEY not configured, falling back to text extraction when available')\n }\n\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: att,\n identifiers: {\n id: att.id,\n organizationId: att.organizationId ?? null,\n tenantId: att.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n\n return NextResponse.json({\n ok: true,\n item: {\n id: attachmentId,\n url: att.url,\n fileName: safeName,\n fileSize: buf.length,\n partitionCode: partition.code,\n thumbnailUrl: buildAttachmentImageUrl(attachmentId, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(safeName),\n }),\n content: extractedContent ?? null,\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n customFields: Object.keys(customFieldValues).length ? customFieldValues : undefined,\n },\n })\n}\n\nasync function readTenantAttachmentUsageBytes(em: EntityManager, tenantId: string): Promise<number> {\n try {\n const db = em.getKysely<any>() as any\n const row = await db\n .selectFrom('attachments')\n .select(sql<string>`sum(file_size)`.as('total_size'))\n .where('tenant_id', '=', tenantId)\n .executeTakeFirst() as { total_size: string | number | null } | undefined\n const total = row?.total_size\n if (typeof total === 'number') return Number.isFinite(total) ? total : 0\n if (typeof total === 'string') {\n const parsed = Number(total)\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n } catch (error) {\n throw new Error('Attachment quota accounting is unavailable.', { cause: error })\n }\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const id = url.searchParams.get('id') || ''\n if (!id) return NextResponse.json({ error: 'Attachment id is required' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const dataEngine = container.resolve('dataEngine')\n const storageDriverFactory =\n (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n // Resolve the currently selected organization (#3765) so a multi-org admin who\n // switched the header org deletes within that org, not their pinned home org.\n // A superadmin browsing with \"All organizations\" selected has no concrete org,\n // so resolution returns null and the delete falls back to a tenant-only scope\n // (#3764) \u2014 letting them remove any attachment in the tenant.\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n const deleteFilter: Record<string, unknown> = { id, tenantId: auth.tenantId! }\n if (orgId) deleteFilter.organizationId = orgId\n const record = await em.findOne(Attachment, deleteFilter)\n if (!record) return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })\n await em.remove(record).flush()\n await clearAttachmentThumbnailCache(record.partitionCode, record.id).catch((error) => {\n logger.error('Failed to cleanup cached thumbnails', { err: error })\n })\n if (record.storagePath) {\n const delDriver = await storageDriverFactory.resolveForPartition(record.partitionCode, {\n tenantId: record.tenantId ?? auth.tenantId!,\n organizationId: record.organizationId ?? orgId ?? '',\n })\n await delDriver.delete(record.partitionCode, record.storagePath)\n }\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId ?? null,\n tenantId: record.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n return NextResponse.json({ ok: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage entity attachments',\n description: 'Upload and list attachments associated with module entities and records.',\n methods: {\n GET: {\n summary: 'List attachments for a record',\n description: 'Returns uploaded attachments for the given entity record, ordered by newest first.',\n query: attachmentQuerySchema,\n responses: [\n { status: 200, description: 'Attachments found for the record', schema: attachmentListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity or record identifiers', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Upload attachment',\n description: 'Uploads a new attachment using multipart form-data and stores metadata for later retrieval.',\n requestBody: {\n contentType: 'multipart/form-data',\n schema: attachmentUploadBodySchema,\n },\n responses: [\n { status: 200, description: 'Attachment stored successfully', schema: uploadResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Payload validation error', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Attachment violates field constraints', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete attachment',\n description: 'Removes an uploaded attachment and deletes the stored asset.',\n query: attachmentDeleteQuerySchema,\n responses: [\n { status: 200, description: 'Attachment deleted', schema: z.object({ ok: z.literal(true) }) },\n { status: 404, description: 'Attachment not found', schema: errorSchema },\n ],\n errors: [\n { status: 400, description: 'Missing attachment identifier', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["metadata"]
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
2
+ import { applyResponseEnricherToRecord } from "@open-mercato/shared/lib/crud/enricher-runner";
2
3
  import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
3
4
  import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
5
  import { User } from "../../../auth/data/entities.js";
@@ -24,6 +25,18 @@ const metadata = {
24
25
  PATCH: { requireAuth: true, requireFeatures: ["messages.compose"] },
25
26
  DELETE: { requireAuth: true, requireFeatures: ["messages.view"] }
26
27
  };
28
+ async function resolveGrantedFeatures(container, scope) {
29
+ try {
30
+ const rbac = container.resolve("rbacService");
31
+ if (!rbac?.getGrantedFeatures) return void 0;
32
+ return await rbac.getGrantedFeatures(scope.userId, {
33
+ tenantId: scope.tenantId,
34
+ organizationId: scope.organizationId
35
+ });
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
27
40
  async function GET(req, { params }) {
28
41
  const { ctx, scope } = await resolveMessageContext(req);
29
42
  const em = ctx.container.resolve("em");
@@ -146,7 +159,7 @@ async function GET(req, { params }) {
146
159
  }
147
160
  });
148
161
  }
149
- return Response.json({
162
+ const detail = {
150
163
  id: message.id,
151
164
  updatedAt: message.updatedAt ?? null,
152
165
  type: message.type,
@@ -218,7 +231,19 @@ async function GET(req, { params }) {
218
231
  isRead: recipient ? autoMarkRead || recipient.status !== "unread" : true,
219
232
  conversationArchived,
220
233
  conversationAllUnread
234
+ };
235
+ const enriched = await applyResponseEnricherToRecord(detail, "messages.message", {
236
+ organizationId: scope.organizationId ?? "",
237
+ tenantId: scope.tenantId,
238
+ userId: scope.userId,
239
+ em,
240
+ container: ctx.container,
241
+ userFeatures: await resolveGrantedFeatures(ctx.container, scope)
221
242
  });
243
+ if (enriched._meta.enrichedBy.length > 0 || enriched._meta.enricherErrors?.length) {
244
+ return Response.json({ ...enriched.record, _meta: enriched._meta });
245
+ }
246
+ return Response.json(enriched.record);
222
247
  }
223
248
  async function PATCH(req, { params }) {
224
249
  const { ctx, scope } = await resolveMessageContext(req);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/messages/api/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { User } from '../../../auth/data/entities'\nimport { Message, MessageObject, MessageRecipient } from '../../data/entities'\nimport { updateDraftSchema } from '../../data/validators'\nimport { buildResolvedMessageActions } from '../../lib/actions'\nimport { MESSAGE_OPTIMISTIC_LOCK_RESOURCE_KIND } from '../../lib/constants'\nimport { getMessageObjectType } from '../../lib/message-objects-registry'\nimport { getMessageTypeOrDefault } from '../../lib/message-types-registry'\nimport { attachOperationMetadataHeader } from '../../lib/operationMetadata'\nimport { hasOrganizationAccess, resolveMessageContext } from '../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../guards'\nimport {\n errorResponseSchema,\n messageDetailResponseSchema,\n okResponseSchema,\n updateDraftSchema as updateDraftOpenApiSchema,\n} from '../openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('messages').child({ component: 'api' })\n\nexport const metadata = {\n GET: { requireAuth: true },\n PATCH: { requireAuth: true, requireFeatures: ['messages.compose'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\ntype MessageObjectPreviewPayload = {\n title: string\n subtitle?: string\n status?: string\n statusColor?: string\n metadata?: Record<string, string>\n} | null\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n const url = new URL(req.url)\n const skipMarkReadParam = url.searchParams.get('skipMarkRead')\n const skipMarkRead = skipMarkReadParam === '1'\n\n const message = await findOneWithDecryption(\n em,\n Message,\n {\n id: params.id,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n\n if (!message) {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const recipient = await em.findOne(MessageRecipient, {\n messageId: params.id,\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n\n const isSender = message.senderUserId === scope.userId\n const isRecipient = Boolean(recipient)\n\n if (!isSender && !isRecipient) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const autoMarkRead = !skipMarkRead && recipient?.status === 'unread'\n const readAt = autoMarkRead ? new Date() : recipient?.readAt ?? null\n\n const objects = await em.find(MessageObject, { messageId: params.id })\n const objectPreviews = await Promise.all(\n objects.map(async (item): Promise<MessageObjectPreviewPayload> => {\n const objectType = getMessageObjectType(item.entityModule, item.entityType)\n if (!objectType?.loadPreview) return null\n try {\n return await objectType.loadPreview(item.entityId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n } catch (error) {\n logger.error('Failed to load preview', { entityModule: item.entityModule, entityType: item.entityType, entityId: item.entityId, err: error })\n return null\n }\n }),\n )\n const allRecipients = await em.find(MessageRecipient, { messageId: params.id, deletedAt: null })\n\n const threadMessages = await findWithDecryption(\n em,\n Message,\n {\n threadId: message.threadId ?? message.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n isDraft: false,\n },\n { orderBy: { sentAt: 'ASC' } },\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n const threadMessageIds = threadMessages.map((item) => item.id)\n const visibleRecipientRows = threadMessageIds.length > 0\n ? await em.find(MessageRecipient, {\n messageId: { $in: threadMessageIds },\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n : []\n const visibleRecipientMessageIds = new Set(visibleRecipientRows.map((item) => item.messageId))\n const actorVisibleThreadMessages = threadMessages.filter((threadMessage) => (\n threadMessage.senderUserId === scope.userId || visibleRecipientMessageIds.has(threadMessage.id)\n ))\n\n const actorRecipientStatusByMessageId = new Map<string, string>()\n for (const row of visibleRecipientRows) {\n actorRecipientStatusByMessageId.set(row.messageId, row.status)\n }\n if (recipient) {\n actorRecipientStatusByMessageId.set(params.id, autoMarkRead ? 'read' : recipient.status)\n }\n const actorRecipientStatuses = Array.from(actorRecipientStatusByMessageId.values())\n const conversationArchived = actorRecipientStatuses.length > 0\n && actorRecipientStatuses.every((status) => status === 'archived')\n const conversationAllUnread = actorRecipientStatuses.length > 0\n && actorRecipientStatuses.every((status) => status === 'unread')\n\n const threadSenderIds = actorVisibleThreadMessages\n .map((threadMessage) => threadMessage.senderUserId)\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n\n const threadSenders = threadSenderIds.length > 0\n ? await findWithDecryption(\n em,\n User,\n { id: { $in: Array.from(new Set(threadSenderIds)) } },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n : []\n\n const threadSenderMap = new Map(threadSenders.map((user) => [user.id, user]))\n\n const senderUser = await findOneWithDecryption(\n em,\n User,\n { id: message.senderUserId },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n\n const senderName = typeof senderUser?.name === 'string' && senderUser.name.trim().length\n ? senderUser.name.trim()\n : null\n const senderEmail = senderUser?.email ?? null\n\n const messageType = getMessageTypeOrDefault(message.type)\n const resolvedActionData = buildResolvedMessageActions(message, objects)\n\n if (autoMarkRead) {\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n await commandBus.execute('messages.recipients.mark_read', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n }\n\n return Response.json({\n id: message.id,\n updatedAt: message.updatedAt ?? null,\n type: message.type,\n isDraft: message.isDraft,\n canEditDraft: message.isDraft && message.senderUserId === scope.userId,\n canArchive: Boolean(recipient),\n isArchived: recipient?.status === 'archived',\n visibility: message.visibility,\n sourceEntityType: message.sourceEntityType,\n sourceEntityId: message.sourceEntityId,\n externalEmail: message.externalEmail,\n externalName: message.externalName,\n typeDefinition: {\n labelKey: messageType.labelKey,\n icon: messageType.icon,\n color: messageType.color,\n allowReply: messageType.allowReply ?? true,\n allowForward: messageType.allowForward ?? true,\n ui: {\n listItemComponent: messageType.ui?.listItemComponent ?? null,\n contentComponent: messageType.ui?.contentComponent ?? null,\n actionsComponent: messageType.ui?.actionsComponent ?? null,\n },\n },\n threadId: message.threadId,\n parentMessageId: message.parentMessageId,\n senderUserId: message.senderUserId,\n senderName,\n senderEmail,\n subject: message.subject,\n body: message.body,\n bodyFormat: message.bodyFormat,\n priority: message.priority,\n sentAt: message.sentAt,\n actionData: resolvedActionData,\n actionTaken: message.actionTaken,\n actionTakenAt: message.actionTakenAt,\n actionTakenByUserId: message.actionTakenByUserId,\n recipients: allRecipients.map((item) => ({\n userId: item.recipientUserId,\n type: item.recipientType,\n status: autoMarkRead && item.recipientUserId === scope.userId ? 'read' : item.status,\n readAt: autoMarkRead && item.recipientUserId === scope.userId ? readAt : item.readAt,\n })),\n objects: objects.map((item, index) => ({\n id: item.id,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n actionType: item.actionType,\n actionLabel: item.actionLabel,\n snapshot: item.entitySnapshot,\n preview: objectPreviews[index] ?? null,\n })),\n thread: actorVisibleThreadMessages.map((threadMessage) => {\n const sender = threadSenderMap.get(threadMessage.senderUserId)\n const threadSenderName = typeof sender?.name === 'string' && sender.name.trim().length\n ? sender.name.trim()\n : null\n\n return {\n id: threadMessage.id,\n senderUserId: threadMessage.senderUserId,\n senderName: threadSenderName,\n senderEmail: sender?.email ?? null,\n body: threadMessage.body,\n bodyFormat: threadMessage.bodyFormat,\n sentAt: threadMessage.sentAt,\n }\n }),\n isRead: recipient ? (autoMarkRead || recipient.status !== 'unread') : true,\n conversationArchived,\n conversationAllUnread,\n })\n}\n\nexport async function PATCH(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = updateDraftSchema.parse(body)\n\n const message = await em.findOne(Message, {\n id: params.id,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n\n if (!message) {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n if (message.senderUserId !== scope.userId) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n if (!message.isDraft) {\n return Response.json({ error: 'Only draft messages can be edited' }, { status: 409 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: message.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n // Reject stale draft edits/sends before mutating: compare the client's\n // expected version (extension header) against the loaded draft.\n enforceCommandOptimisticLock({\n resourceKind: MESSAGE_OPTIMISTIC_LOCK_RESOURCE_KIND,\n resourceId: message.id,\n current: message.updatedAt,\n request: req,\n })\n\n const { logEntry } = await commandBus.execute('messages.messages.update_draft', {\n input: {\n ...input,\n messageId: message.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true, id: message.id })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: message.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: message.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message type cannot be created by users') {\n return Response.json({ error: error.message }, { status: 400 })\n }\n if (error.message === 'Only draft messages can be edited') {\n return Response.json({ error: error.message }, { status: 409 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const message = await em.findOne(Message, {\n id: params.id,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n\n if (!message) {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const isSender = message.senderUserId === scope.userId\n // Only look the recipient row up when the actor is not the sender: deleting one's own\n // message is the common path, and the lookup is a decryption-aware query.\n const isRecipient = isSender\n ? false\n : Boolean(await findOneWithDecryption(\n em,\n MessageRecipient,\n {\n messageId: params.id,\n recipientUserId: scope.userId,\n deletedAt: null,\n },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n ))\n\n if (!isSender && !isRecipient) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n // Reject a stale delete before mutating: a tab that loaded an older version\n // of the message must refresh rather than silently delete a changed record.\n enforceCommandOptimisticLock({\n resourceKind: MESSAGE_OPTIMISTIC_LOCK_RESOURCE_KIND,\n resourceId: message.id,\n current: message.updatedAt,\n request: req,\n })\n\n const { logEntry } = await commandBus.execute('messages.messages.delete_for_actor', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n if (error instanceof Error && error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n throw error\n }\n\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n GET: {\n summary: 'Get message detail',\n responses: [\n {\n status: 200,\n description: 'Message detail with actor-visible thread items only',\n schema: messageDetailResponseSchema,\n },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n PATCH: {\n summary: 'Update draft message',\n requestBody: { schema: updateDraftOpenApiSchema },\n responses: [\n { status: 200, description: 'Draft updated', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Only drafts can be edited, or the draft was modified concurrently (optimistic lock conflict)', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete message for current sender/recipient context',\n responses: [\n { status: 200, description: 'Message deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Message was modified concurrently (optimistic lock conflict)', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { applyResponseEnricherToRecord } from '@open-mercato/shared/lib/crud/enricher-runner'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { User } from '../../../auth/data/entities'\nimport { Message, MessageObject, MessageRecipient } from '../../data/entities'\nimport { updateDraftSchema } from '../../data/validators'\nimport { buildResolvedMessageActions } from '../../lib/actions'\nimport { MESSAGE_OPTIMISTIC_LOCK_RESOURCE_KIND } from '../../lib/constants'\nimport { getMessageObjectType } from '../../lib/message-objects-registry'\nimport { getMessageTypeOrDefault } from '../../lib/message-types-registry'\nimport { attachOperationMetadataHeader } from '../../lib/operationMetadata'\nimport { hasOrganizationAccess, resolveMessageContext } from '../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../guards'\nimport {\n errorResponseSchema,\n messageDetailResponseSchema,\n okResponseSchema,\n updateDraftSchema as updateDraftOpenApiSchema,\n} from '../openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('messages').child({ component: 'api' })\n\nexport const metadata = {\n GET: { requireAuth: true },\n PATCH: { requireAuth: true, requireFeatures: ['messages.compose'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\ntype MessageObjectPreviewPayload = {\n title: string\n subtitle?: string\n status?: string\n statusColor?: string\n metadata?: Record<string, string>\n} | null\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n}\n\n/**\n * Feature list for the enricher ACL gate, resolved from RBAC.\n *\n * Mirrors `customers/api/interactions/route.ts`. Returning `undefined` on\n * failure is deliberate \u2014 the runner then treats every feature-gated enricher\n * as denied, which is the safe direction.\n */\nasync function resolveGrantedFeatures(\n container: { resolve: (name: string) => unknown },\n scope: { tenantId: string; organizationId: string | null; userId: string },\n): Promise<string[] | undefined> {\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike | undefined\n if (!rbac?.getGrantedFeatures) return undefined\n return await rbac.getGrantedFeatures(scope.userId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n } catch {\n return undefined\n }\n}\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n const url = new URL(req.url)\n const skipMarkReadParam = url.searchParams.get('skipMarkRead')\n const skipMarkRead = skipMarkReadParam === '1'\n\n const message = await findOneWithDecryption(\n em,\n Message,\n {\n id: params.id,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n\n if (!message) {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const recipient = await em.findOne(MessageRecipient, {\n messageId: params.id,\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n\n const isSender = message.senderUserId === scope.userId\n const isRecipient = Boolean(recipient)\n\n if (!isSender && !isRecipient) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const autoMarkRead = !skipMarkRead && recipient?.status === 'unread'\n const readAt = autoMarkRead ? new Date() : recipient?.readAt ?? null\n\n const objects = await em.find(MessageObject, { messageId: params.id })\n const objectPreviews = await Promise.all(\n objects.map(async (item): Promise<MessageObjectPreviewPayload> => {\n const objectType = getMessageObjectType(item.entityModule, item.entityType)\n if (!objectType?.loadPreview) return null\n try {\n return await objectType.loadPreview(item.entityId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n } catch (error) {\n logger.error('Failed to load preview', { entityModule: item.entityModule, entityType: item.entityType, entityId: item.entityId, err: error })\n return null\n }\n }),\n )\n const allRecipients = await em.find(MessageRecipient, { messageId: params.id, deletedAt: null })\n\n const threadMessages = await findWithDecryption(\n em,\n Message,\n {\n threadId: message.threadId ?? message.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n isDraft: false,\n },\n { orderBy: { sentAt: 'ASC' } },\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n const threadMessageIds = threadMessages.map((item) => item.id)\n const visibleRecipientRows = threadMessageIds.length > 0\n ? await em.find(MessageRecipient, {\n messageId: { $in: threadMessageIds },\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n : []\n const visibleRecipientMessageIds = new Set(visibleRecipientRows.map((item) => item.messageId))\n const actorVisibleThreadMessages = threadMessages.filter((threadMessage) => (\n threadMessage.senderUserId === scope.userId || visibleRecipientMessageIds.has(threadMessage.id)\n ))\n\n const actorRecipientStatusByMessageId = new Map<string, string>()\n for (const row of visibleRecipientRows) {\n actorRecipientStatusByMessageId.set(row.messageId, row.status)\n }\n if (recipient) {\n actorRecipientStatusByMessageId.set(params.id, autoMarkRead ? 'read' : recipient.status)\n }\n const actorRecipientStatuses = Array.from(actorRecipientStatusByMessageId.values())\n const conversationArchived = actorRecipientStatuses.length > 0\n && actorRecipientStatuses.every((status) => status === 'archived')\n const conversationAllUnread = actorRecipientStatuses.length > 0\n && actorRecipientStatuses.every((status) => status === 'unread')\n\n const threadSenderIds = actorVisibleThreadMessages\n .map((threadMessage) => threadMessage.senderUserId)\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n\n const threadSenders = threadSenderIds.length > 0\n ? await findWithDecryption(\n em,\n User,\n { id: { $in: Array.from(new Set(threadSenderIds)) } },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n : []\n\n const threadSenderMap = new Map(threadSenders.map((user) => [user.id, user]))\n\n const senderUser = await findOneWithDecryption(\n em,\n User,\n { id: message.senderUserId },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId }\n )\n\n const senderName = typeof senderUser?.name === 'string' && senderUser.name.trim().length\n ? senderUser.name.trim()\n : null\n const senderEmail = senderUser?.email ?? null\n\n const messageType = getMessageTypeOrDefault(message.type)\n const resolvedActionData = buildResolvedMessageActions(message, objects)\n\n if (autoMarkRead) {\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n await commandBus.execute('messages.recipients.mark_read', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n }\n\n const detail: Record<string, unknown> = {\n id: message.id,\n updatedAt: message.updatedAt ?? null,\n type: message.type,\n isDraft: message.isDraft,\n canEditDraft: message.isDraft && message.senderUserId === scope.userId,\n canArchive: Boolean(recipient),\n isArchived: recipient?.status === 'archived',\n visibility: message.visibility,\n sourceEntityType: message.sourceEntityType,\n sourceEntityId: message.sourceEntityId,\n externalEmail: message.externalEmail,\n externalName: message.externalName,\n typeDefinition: {\n labelKey: messageType.labelKey,\n icon: messageType.icon,\n color: messageType.color,\n allowReply: messageType.allowReply ?? true,\n allowForward: messageType.allowForward ?? true,\n ui: {\n listItemComponent: messageType.ui?.listItemComponent ?? null,\n contentComponent: messageType.ui?.contentComponent ?? null,\n actionsComponent: messageType.ui?.actionsComponent ?? null,\n },\n },\n threadId: message.threadId,\n parentMessageId: message.parentMessageId,\n senderUserId: message.senderUserId,\n senderName,\n senderEmail,\n subject: message.subject,\n body: message.body,\n bodyFormat: message.bodyFormat,\n priority: message.priority,\n sentAt: message.sentAt,\n actionData: resolvedActionData,\n actionTaken: message.actionTaken,\n actionTakenAt: message.actionTakenAt,\n actionTakenByUserId: message.actionTakenByUserId,\n recipients: allRecipients.map((item) => ({\n userId: item.recipientUserId,\n type: item.recipientType,\n status: autoMarkRead && item.recipientUserId === scope.userId ? 'read' : item.status,\n readAt: autoMarkRead && item.recipientUserId === scope.userId ? readAt : item.readAt,\n })),\n objects: objects.map((item, index) => ({\n id: item.id,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n actionType: item.actionType,\n actionLabel: item.actionLabel,\n snapshot: item.entitySnapshot,\n preview: objectPreviews[index] ?? null,\n })),\n thread: actorVisibleThreadMessages.map((threadMessage) => {\n const sender = threadSenderMap.get(threadMessage.senderUserId)\n const threadSenderName = typeof sender?.name === 'string' && sender.name.trim().length\n ? sender.name.trim()\n : null\n\n return {\n id: threadMessage.id,\n senderUserId: threadMessage.senderUserId,\n senderName: threadSenderName,\n senderEmail: sender?.email ?? null,\n body: threadMessage.body,\n bodyFormat: threadMessage.bodyFormat,\n sentAt: threadMessage.sentAt,\n }\n }),\n isRead: recipient ? (autoMarkRead || recipient.status !== 'unread') : true,\n conversationArchived,\n conversationAllUnread,\n }\n\n // Mirrors `enrichSingleRecord` in `shared/lib/crud/factory.ts`, which every\n // `makeCrudRoute` host gets for free: same `_meta` envelope, and no blanket\n // catch so a `critical: true` enricher still surfaces as an HTTP error.\n // Non-critical failures never escape the runner \u2014 it merges `fallback` and\n // records the id in `_meta.enricherErrors`.\n const enriched = await applyResponseEnricherToRecord(detail, 'messages.message', {\n organizationId: scope.organizationId ?? '',\n tenantId: scope.tenantId,\n userId: scope.userId,\n em,\n container: ctx.container,\n userFeatures: await resolveGrantedFeatures(ctx.container, scope),\n })\n\n if (enriched._meta.enrichedBy.length > 0 || enriched._meta.enricherErrors?.length) {\n return Response.json({ ...enriched.record, _meta: enriched._meta })\n }\n\n return Response.json(enriched.record)\n}\n\nexport async function PATCH(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = updateDraftSchema.parse(body)\n\n const message = await em.findOne(Message, {\n id: params.id,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n\n if (!message) {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n if (message.senderUserId !== scope.userId) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n if (!message.isDraft) {\n return Response.json({ error: 'Only draft messages can be edited' }, { status: 409 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: message.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n // Reject stale draft edits/sends before mutating: compare the client's\n // expected version (extension header) against the loaded draft.\n enforceCommandOptimisticLock({\n resourceKind: MESSAGE_OPTIMISTIC_LOCK_RESOURCE_KIND,\n resourceId: message.id,\n current: message.updatedAt,\n request: req,\n })\n\n const { logEntry } = await commandBus.execute('messages.messages.update_draft', {\n input: {\n ...input,\n messageId: message.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true, id: message.id })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: message.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: message.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message type cannot be created by users') {\n return Response.json({ error: error.message }, { status: 400 })\n }\n if (error.message === 'Only draft messages can be edited') {\n return Response.json({ error: error.message }, { status: 409 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const message = await em.findOne(Message, {\n id: params.id,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n\n if (!message) {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const isSender = message.senderUserId === scope.userId\n // Only look the recipient row up when the actor is not the sender: deleting one's own\n // message is the common path, and the lookup is a decryption-aware query.\n const isRecipient = isSender\n ? false\n : Boolean(await findOneWithDecryption(\n em,\n MessageRecipient,\n {\n messageId: params.id,\n recipientUserId: scope.userId,\n deletedAt: null,\n },\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n ))\n\n if (!isSender && !isRecipient) {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n // Reject a stale delete before mutating: a tab that loaded an older version\n // of the message must refresh rather than silently delete a changed record.\n enforceCommandOptimisticLock({\n resourceKind: MESSAGE_OPTIMISTIC_LOCK_RESOURCE_KIND,\n resourceId: message.id,\n current: message.updatedAt,\n request: req,\n })\n\n const { logEntry } = await commandBus.execute('messages.messages.delete_for_actor', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n if (error instanceof Error && error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n throw error\n }\n\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n GET: {\n summary: 'Get message detail',\n responses: [\n {\n status: 200,\n description: 'Message detail with actor-visible thread items only',\n schema: messageDetailResponseSchema,\n },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n PATCH: {\n summary: 'Update draft message',\n requestBody: { schema: updateDraftOpenApiSchema },\n responses: [\n { status: 200, description: 'Draft updated', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Only drafts can be edited, or the draft was modified concurrently (optimistic lock conflict)', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete message for current sender/recipient context',\n responses: [\n { status: 200, description: 'Message deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Message was modified concurrently (optimistic lock conflict)', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -47,6 +47,10 @@ const messageListItemSchema = z.object({
47
47
  readAt: z.string().nullable().optional(),
48
48
  threadId: z.string().uuid().nullable().optional()
49
49
  });
50
+ const enrichmentMetaSchema = z.object({
51
+ enrichedBy: z.array(z.string()),
52
+ enricherErrors: z.array(z.string()).optional()
53
+ });
50
54
  const messageThreadItemSchema = z.object({
51
55
  id: z.string().uuid(),
52
56
  senderUserId: z.string().uuid(),
@@ -121,8 +125,13 @@ const messageDetailResponseSchema = z.object({
121
125
  thread: z.array(messageThreadItemSchema),
122
126
  isRead: z.boolean(),
123
127
  conversationArchived: z.boolean(),
124
- conversationAllUnread: z.boolean()
125
- });
128
+ conversationAllUnread: z.boolean(),
129
+ _channel: z.record(z.string(), z.unknown()).nullable().optional(),
130
+ _channelPayload: z.record(z.string(), z.unknown()).nullable().optional(),
131
+ _channelContact: z.record(z.string(), z.unknown()).nullable().optional(),
132
+ _reactions: z.array(z.record(z.string(), z.unknown())).optional(),
133
+ _meta: enrichmentMetaSchema.optional()
134
+ }).passthrough();
126
135
  const messageTokenDetailResponseSchema = z.object({
127
136
  id: z.string().uuid(),
128
137
  type: z.string(),
@@ -242,6 +251,7 @@ export {
242
251
  composeMessageSchema,
243
252
  composeResponseSchema,
244
253
  conversationMutationResponseSchema,
254
+ enrichmentMetaSchema,
245
255
  errorResponseSchema,
246
256
  executeActionSchema,
247
257
  forwardMessageSchema,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/api/openapi.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport {\n attachmentIdsPayloadSchema,\n composeMessageSchema,\n executeActionSchema,\n forwardMessageSchema,\n listMessagesSchema,\n messageObjectTypesQuerySchema,\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n replyMessageSchema,\n unlinkAttachmentPayloadSchema,\n updateDraftSchema,\n} from '../data/validators'\n\nexport const messageAttachmentSchema = z.object({\n id: z.string().uuid(),\n fileName: z.string(),\n fileSize: z.number(),\n mimeType: z.string(),\n url: z.string(),\n})\n\nexport const messageListItemSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n subject: z.string(),\n bodyPreview: z.string(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n priority: z.string(),\n status: z.string(),\n hasObjects: z.boolean(),\n objectCount: z.number(),\n hasAttachments: z.boolean(),\n attachmentCount: z.number(),\n recipientCount: z.number(),\n hasActions: z.boolean(),\n actionTaken: z.string().nullable().optional(),\n sentAt: z.string().nullable().optional(),\n readAt: z.string().nullable().optional(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const messageThreadItemSchema = z.object({\n id: z.string().uuid(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']).optional(),\n sentAt: z.string().nullable().optional(),\n})\n\nexport const messageDetailResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string().nullable().optional(),\n type: z.string(),\n isDraft: z.boolean(),\n canEditDraft: z.boolean(),\n canArchive: z.boolean(),\n isArchived: z.boolean(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n typeDefinition: z.object({\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().optional().nullable(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n }),\n threadId: z.string().uuid().nullable().optional(),\n parentMessageId: z.string().uuid().nullable().optional(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.string(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n recipients: z.array(z.object({\n userId: z.string().uuid(),\n type: z.enum(['to', 'cc', 'bcc']),\n status: z.string(),\n readAt: z.string().nullable().optional(),\n })),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n preview: z.object({\n title: z.string(),\n subtitle: z.string().optional(),\n status: z.string().optional(),\n statusColor: z.string().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n }).nullable().optional(),\n })),\n thread: z.array(messageThreadItemSchema),\n isRead: z.boolean(),\n conversationArchived: z.boolean(),\n conversationAllUnread: z.boolean(),\n})\n\nexport const messageTokenDetailResponseSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.enum(['low', 'normal', 'high', 'urgent']),\n senderUserId: z.string().uuid(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n })),\n requiresAuth: z.boolean(),\n recipientUserId: z.string().uuid(),\n})\n\nexport const messageTokenPreflightResponseSchema = z.object({\n requiresAuth: z.literal(true),\n})\n\nexport const messageTokenResponseSchema = z.union([\n messageTokenDetailResponseSchema,\n messageTokenPreflightResponseSchema,\n])\n\nexport const unreadCountResponseSchema = z.object({\n unreadCount: z.number(),\n})\n\nexport const messageConfirmationResponseSchema = z.object({\n messageId: z.string().uuid(),\n confirmed: z.boolean(),\n confirmedAt: z.string().nullable(),\n confirmedByUserId: z.string().uuid().nullable(),\n})\n\nexport const actionResultResponseSchema = z.object({\n ok: z.boolean(),\n actionId: z.string(),\n result: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const forwardResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const forwardPreviewResponseSchema = z.object({\n subject: z.string(),\n body: z.string(),\n})\n\nexport const conversationMutationResponseSchema = z.object({\n ok: z.boolean(),\n affectedCount: z.number().int().min(0),\n})\n\nexport const composeResponseSchema = z.object({\n id: z.string().uuid(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const messageAttachmentResponseSchema = z.object({\n attachments: z.array(messageAttachmentSchema),\n})\n\nexport const okResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid().optional(),\n message: z.string().optional(),\n})\n\nexport const errorResponseSchema = z.object({\n code: z.string().optional(),\n error: z.string(),\n})\n\nexport const messageTypeResponseItemSchema = z.object({\n type: z.string(),\n module: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().nullable().optional(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n actionsExpireAfterHours: z.number().int().nullable().optional(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n})\n\nexport const messageTypeListResponseSchema = z.object({\n items: z.array(messageTypeResponseItemSchema),\n})\n\nexport const messageObjectTypeActionResponseSchema = z.object({\n id: z.string(),\n labelKey: z.string(),\n variant: z.enum(['default', 'secondary', 'destructive', 'outline']).optional(),\n icon: z.string().optional(),\n commandId: z.string().optional(),\n href: z.string().optional(),\n isTerminal: z.boolean().optional(),\n confirmRequired: z.boolean().optional(),\n confirmMessage: z.string().optional(),\n})\n\nexport const messageObjectTypeResponseItemSchema = z.object({\n module: z.string(),\n entityType: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n actions: z.array(messageObjectTypeActionResponseSchema),\n})\n\nexport const messageObjectTypeListResponseSchema = z.object({\n items: z.array(messageObjectTypeResponseItemSchema),\n})\n\nexport {\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n composeMessageSchema,\n listMessagesSchema,\n forwardMessageSchema,\n replyMessageSchema,\n updateDraftSchema,\n executeActionSchema,\n messageObjectTypesQuerySchema,\n attachmentIdsPayloadSchema,\n unlinkAttachmentPayloadSchema,\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport {\n attachmentIdsPayloadSchema,\n composeMessageSchema,\n executeActionSchema,\n forwardMessageSchema,\n listMessagesSchema,\n messageObjectTypesQuerySchema,\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n replyMessageSchema,\n unlinkAttachmentPayloadSchema,\n updateDraftSchema,\n} from '../data/validators'\n\nexport const messageAttachmentSchema = z.object({\n id: z.string().uuid(),\n fileName: z.string(),\n fileSize: z.number(),\n mimeType: z.string(),\n url: z.string(),\n})\n\nexport const messageListItemSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n subject: z.string(),\n bodyPreview: z.string(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n priority: z.string(),\n status: z.string(),\n hasObjects: z.boolean(),\n objectCount: z.number(),\n hasAttachments: z.boolean(),\n attachmentCount: z.number(),\n recipientCount: z.number(),\n hasActions: z.boolean(),\n actionTaken: z.string().nullable().optional(),\n sentAt: z.string().nullable().optional(),\n readAt: z.string().nullable().optional(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const enrichmentMetaSchema = z.object({\n enrichedBy: z.array(z.string()),\n enricherErrors: z.array(z.string()).optional(),\n})\n\nexport const messageThreadItemSchema = z.object({\n id: z.string().uuid(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']).optional(),\n sentAt: z.string().nullable().optional(),\n})\n\nexport const messageDetailResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string().nullable().optional(),\n type: z.string(),\n isDraft: z.boolean(),\n canEditDraft: z.boolean(),\n canArchive: z.boolean(),\n isArchived: z.boolean(),\n visibility: z.enum(['public', 'internal']).nullable().optional(),\n sourceEntityType: z.string().nullable().optional(),\n sourceEntityId: z.string().uuid().nullable().optional(),\n externalEmail: z.string().nullable().optional(),\n externalName: z.string().nullable().optional(),\n typeDefinition: z.object({\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().optional().nullable(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n }),\n threadId: z.string().uuid().nullable().optional(),\n parentMessageId: z.string().uuid().nullable().optional(),\n senderUserId: z.string().uuid(),\n senderName: z.string().nullable().optional(),\n senderEmail: z.string().nullable().optional(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.string(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n recipients: z.array(z.object({\n userId: z.string().uuid(),\n type: z.enum(['to', 'cc', 'bcc']),\n status: z.string(),\n readAt: z.string().nullable().optional(),\n })),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n preview: z.object({\n title: z.string(),\n subtitle: z.string().optional(),\n status: z.string().optional(),\n statusColor: z.string().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n }).nullable().optional(),\n })),\n thread: z.array(messageThreadItemSchema),\n isRead: z.boolean(),\n conversationArchived: z.boolean(),\n conversationAllUnread: z.boolean(),\n _channel: z.record(z.string(), z.unknown()).nullable().optional(),\n _channelPayload: z.record(z.string(), z.unknown()).nullable().optional(),\n _channelContact: z.record(z.string(), z.unknown()).nullable().optional(),\n _reactions: z.array(z.record(z.string(), z.unknown())).optional(),\n _meta: enrichmentMetaSchema.optional(),\n})\n // The detail route runs response enrichers for `messages.message`, so any\n // module registering one may contribute further `_`-prefixed keys. Without\n // this the generator emits `additionalProperties: false` and the published\n // spec forbids the very fields the route returns.\n .passthrough()\n\nexport const messageTokenDetailResponseSchema = z.object({\n id: z.string().uuid(),\n type: z.string(),\n subject: z.string(),\n body: z.string(),\n bodyFormat: z.enum(['text', 'markdown']),\n priority: z.enum(['low', 'normal', 'high', 'urgent']),\n senderUserId: z.string().uuid(),\n sentAt: z.string().nullable().optional(),\n actionData: messageActionDataSchema.optional().nullable(),\n actionTaken: z.string().nullable().optional(),\n actionTakenAt: z.string().nullable().optional(),\n actionTakenByUserId: z.string().uuid().nullable().optional(),\n objects: z.array(z.object({\n id: z.string().uuid(),\n entityModule: z.string(),\n entityType: z.string(),\n entityId: z.string().uuid(),\n actionRequired: z.boolean(),\n actionType: z.string().nullable().optional(),\n actionLabel: z.string().nullable().optional(),\n snapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n })),\n requiresAuth: z.boolean(),\n recipientUserId: z.string().uuid(),\n})\n\nexport const messageTokenPreflightResponseSchema = z.object({\n requiresAuth: z.literal(true),\n})\n\nexport const messageTokenResponseSchema = z.union([\n messageTokenDetailResponseSchema,\n messageTokenPreflightResponseSchema,\n])\n\nexport const unreadCountResponseSchema = z.object({\n unreadCount: z.number(),\n})\n\nexport const messageConfirmationResponseSchema = z.object({\n messageId: z.string().uuid(),\n confirmed: z.boolean(),\n confirmedAt: z.string().nullable(),\n confirmedByUserId: z.string().uuid().nullable(),\n})\n\nexport const actionResultResponseSchema = z.object({\n ok: z.boolean(),\n actionId: z.string(),\n result: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const forwardResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const forwardPreviewResponseSchema = z.object({\n subject: z.string(),\n body: z.string(),\n})\n\nexport const conversationMutationResponseSchema = z.object({\n ok: z.boolean(),\n affectedCount: z.number().int().min(0),\n})\n\nexport const composeResponseSchema = z.object({\n id: z.string().uuid(),\n threadId: z.string().uuid().nullable().optional(),\n})\n\nexport const messageAttachmentResponseSchema = z.object({\n attachments: z.array(messageAttachmentSchema),\n})\n\nexport const okResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid().optional(),\n message: z.string().optional(),\n})\n\nexport const errorResponseSchema = z.object({\n code: z.string().optional(),\n error: z.string(),\n})\n\nexport const messageTypeResponseItemSchema = z.object({\n type: z.string(),\n module: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n color: z.string().nullable().optional(),\n allowReply: z.boolean(),\n allowForward: z.boolean(),\n actionsExpireAfterHours: z.number().int().nullable().optional(),\n ui: z.object({\n listItemComponent: z.string().nullable().optional(),\n contentComponent: z.string().nullable().optional(),\n actionsComponent: z.string().nullable().optional(),\n }).nullable().optional(),\n})\n\nexport const messageTypeListResponseSchema = z.object({\n items: z.array(messageTypeResponseItemSchema),\n})\n\nexport const messageObjectTypeActionResponseSchema = z.object({\n id: z.string(),\n labelKey: z.string(),\n variant: z.enum(['default', 'secondary', 'destructive', 'outline']).optional(),\n icon: z.string().optional(),\n commandId: z.string().optional(),\n href: z.string().optional(),\n isTerminal: z.boolean().optional(),\n confirmRequired: z.boolean().optional(),\n confirmMessage: z.string().optional(),\n})\n\nexport const messageObjectTypeResponseItemSchema = z.object({\n module: z.string(),\n entityType: z.string(),\n labelKey: z.string(),\n icon: z.string(),\n actions: z.array(messageObjectTypeActionResponseSchema),\n})\n\nexport const messageObjectTypeListResponseSchema = z.object({\n items: z.array(messageObjectTypeResponseItemSchema),\n})\n\nexport {\n messageActionDataSchema,\n messageActionSchema,\n messageObjectSchema,\n messageRecipientSchema,\n composeMessageSchema,\n listMessagesSchema,\n forwardMessageSchema,\n replyMessageSchema,\n updateDraftSchema,\n executeActionSchema,\n messageObjectTypesQuerySchema,\n attachmentIdsPayloadSchema,\n unlinkAttachmentPayloadSchema,\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.8-develop.7021.1.61869f8014",
3
+ "version": "0.6.8-develop.7023.1.a1960eb208",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -254,16 +254,16 @@
254
254
  "zod": "^4.4.3"
255
255
  },
256
256
  "peerDependencies": {
257
- "@open-mercato/ai-assistant": "0.6.8-develop.7021.1.61869f8014",
258
- "@open-mercato/shared": "0.6.8-develop.7021.1.61869f8014",
259
- "@open-mercato/ui": "0.6.8-develop.7021.1.61869f8014",
257
+ "@open-mercato/ai-assistant": "0.6.8-develop.7023.1.a1960eb208",
258
+ "@open-mercato/shared": "0.6.8-develop.7023.1.a1960eb208",
259
+ "@open-mercato/ui": "0.6.8-develop.7023.1.a1960eb208",
260
260
  "react": "^19.0.0",
261
261
  "react-dom": "^19.0.0"
262
262
  },
263
263
  "devDependencies": {
264
- "@open-mercato/ai-assistant": "0.6.8-develop.7021.1.61869f8014",
265
- "@open-mercato/shared": "0.6.8-develop.7021.1.61869f8014",
266
- "@open-mercato/ui": "0.6.8-develop.7021.1.61869f8014",
264
+ "@open-mercato/ai-assistant": "0.6.8-develop.7023.1.a1960eb208",
265
+ "@open-mercato/shared": "0.6.8-develop.7023.1.a1960eb208",
266
+ "@open-mercato/ui": "0.6.8-develop.7023.1.a1960eb208",
267
267
  "@testing-library/dom": "^10.4.1",
268
268
  "@testing-library/jest-dom": "^7.0.0",
269
269
  "@testing-library/react": "^16.3.1",
@@ -213,7 +213,7 @@ export async function GET(req: Request) {
213
213
  const currentPage = usePaging ? Math.max(1, page) : null
214
214
  const currentPageSize = usePaging ? pageSize : null
215
215
  const totalPages = usePaging && total !== null ? Math.max(1, Math.ceil(total / currentPageSize!)) : null
216
- const pageOffset = usePaging ? (Math.min(currentPage!, totalPages!) - 1) * currentPageSize! : undefined
216
+ const pageOffset = usePaging ? (currentPage! - 1) * currentPageSize! : undefined
217
217
  const items = await findWithDecryption(
218
218
  em,
219
219
  Attachment,
@@ -256,7 +256,7 @@ export async function GET(req: Request) {
256
256
  ...(usePaging
257
257
  ? {
258
258
  total,
259
- page: Math.min(currentPage!, totalPages!),
259
+ page: currentPage,
260
260
  pageSize: currentPageSize,
261
261
  totalPages,
262
262
  }
@@ -2,6 +2,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'
3
3
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'
4
4
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { applyResponseEnricherToRecord } from '@open-mercato/shared/lib/crud/enricher-runner'
5
6
  import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
6
7
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
8
  import { User } from '../../../auth/data/entities'
@@ -38,6 +39,36 @@ type MessageObjectPreviewPayload = {
38
39
  metadata?: Record<string, string>
39
40
  } | null
40
41
 
42
+ type RbacServiceLike = {
43
+ getGrantedFeatures?: (
44
+ userId: string,
45
+ scope: { tenantId: string | null; organizationId: string | null },
46
+ ) => Promise<string[]>
47
+ }
48
+
49
+ /**
50
+ * Feature list for the enricher ACL gate, resolved from RBAC.
51
+ *
52
+ * Mirrors `customers/api/interactions/route.ts`. Returning `undefined` on
53
+ * failure is deliberate — the runner then treats every feature-gated enricher
54
+ * as denied, which is the safe direction.
55
+ */
56
+ async function resolveGrantedFeatures(
57
+ container: { resolve: (name: string) => unknown },
58
+ scope: { tenantId: string; organizationId: string | null; userId: string },
59
+ ): Promise<string[] | undefined> {
60
+ try {
61
+ const rbac = container.resolve('rbacService') as RbacServiceLike | undefined
62
+ if (!rbac?.getGrantedFeatures) return undefined
63
+ return await rbac.getGrantedFeatures(scope.userId, {
64
+ tenantId: scope.tenantId,
65
+ organizationId: scope.organizationId,
66
+ })
67
+ } catch {
68
+ return undefined
69
+ }
70
+ }
71
+
41
72
  export async function GET(req: Request, { params }: { params: { id: string } }) {
42
73
  const { ctx, scope } = await resolveMessageContext(req)
43
74
  const em = ctx.container.resolve('em') as EntityManager
@@ -190,7 +221,7 @@ export async function GET(req: Request, { params }: { params: { id: string } })
190
221
  })
191
222
  }
192
223
 
193
- return Response.json({
224
+ const detail: Record<string, unknown> = {
194
225
  id: message.id,
195
226
  updatedAt: message.updatedAt ?? null,
196
227
  type: message.type,
@@ -265,7 +296,27 @@ export async function GET(req: Request, { params }: { params: { id: string } })
265
296
  isRead: recipient ? (autoMarkRead || recipient.status !== 'unread') : true,
266
297
  conversationArchived,
267
298
  conversationAllUnread,
299
+ }
300
+
301
+ // Mirrors `enrichSingleRecord` in `shared/lib/crud/factory.ts`, which every
302
+ // `makeCrudRoute` host gets for free: same `_meta` envelope, and no blanket
303
+ // catch so a `critical: true` enricher still surfaces as an HTTP error.
304
+ // Non-critical failures never escape the runner — it merges `fallback` and
305
+ // records the id in `_meta.enricherErrors`.
306
+ const enriched = await applyResponseEnricherToRecord(detail, 'messages.message', {
307
+ organizationId: scope.organizationId ?? '',
308
+ tenantId: scope.tenantId,
309
+ userId: scope.userId,
310
+ em,
311
+ container: ctx.container,
312
+ userFeatures: await resolveGrantedFeatures(ctx.container, scope),
268
313
  })
314
+
315
+ if (enriched._meta.enrichedBy.length > 0 || enriched._meta.enricherErrors?.length) {
316
+ return Response.json({ ...enriched.record, _meta: enriched._meta })
317
+ }
318
+
319
+ return Response.json(enriched.record)
269
320
  }
270
321
 
271
322
  export async function PATCH(req: Request, { params }: { params: { id: string } }) {
@@ -50,6 +50,11 @@ export const messageListItemSchema = z.object({
50
50
  threadId: z.string().uuid().nullable().optional(),
51
51
  })
52
52
 
53
+ export const enrichmentMetaSchema = z.object({
54
+ enrichedBy: z.array(z.string()),
55
+ enricherErrors: z.array(z.string()).optional(),
56
+ })
57
+
53
58
  export const messageThreadItemSchema = z.object({
54
59
  id: z.string().uuid(),
55
60
  senderUserId: z.string().uuid(),
@@ -126,7 +131,17 @@ export const messageDetailResponseSchema = z.object({
126
131
  isRead: z.boolean(),
127
132
  conversationArchived: z.boolean(),
128
133
  conversationAllUnread: z.boolean(),
134
+ _channel: z.record(z.string(), z.unknown()).nullable().optional(),
135
+ _channelPayload: z.record(z.string(), z.unknown()).nullable().optional(),
136
+ _channelContact: z.record(z.string(), z.unknown()).nullable().optional(),
137
+ _reactions: z.array(z.record(z.string(), z.unknown())).optional(),
138
+ _meta: enrichmentMetaSchema.optional(),
129
139
  })
140
+ // The detail route runs response enrichers for `messages.message`, so any
141
+ // module registering one may contribute further `_`-prefixed keys. Without
142
+ // this the generator emits `additionalProperties: false` and the published
143
+ // spec forbids the very fields the route returns.
144
+ .passthrough()
130
145
 
131
146
  export const messageTokenDetailResponseSchema = z.object({
132
147
  id: z.string().uuid(),