@open-mercato/core 0.6.6-develop.6505.1.6f6b83559f → 0.6.6-develop.6507.1.cc8b36505d

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 3459 entry points
1
+ [build:core] found 3460 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 186 entry points
4
4
  [build:core:generated] built successfully
@@ -11,6 +11,7 @@ import {
11
11
  UnauthorizedError
12
12
  } from "../../routeHelpers.js";
13
13
  import { createLogger } from "@open-mercato/shared/lib/logger";
14
+ import { canViewEmailContent, serializeInboxEmail } from "../response.js";
14
15
  const logger = createLogger("inbox_ops").child({ component: "emails" });
15
16
  const metadata = {
16
17
  GET: { requireAuth: true, requireFeatures: ["inbox_ops.log.view"] },
@@ -39,7 +40,8 @@ async function GET(req) {
39
40
  if (!email) {
40
41
  return NextResponse.json({ error: "Email not found" }, { status: 404 });
41
42
  }
42
- return NextResponse.json({ email });
43
+ const includeContent = await canViewEmailContent(ctx);
44
+ return NextResponse.json({ email: serializeInboxEmail(email, includeContent) });
43
45
  } catch (err) {
44
46
  if (err instanceof UnauthorizedError) {
45
47
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/inbox_ops/api/emails/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { InboxEmail } from '../../../data/entities'\nimport {\n resolveRequestContext,\n extractPathSegment,\n UnauthorizedError,\n} from '../../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'emails' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.log.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const id = extractPathSegment(url, 'emails')\n\n if (!id) {\n return NextResponse.json({ error: 'Missing email ID' }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n\n const email = await findOneWithDecryption(\n ctx.em,\n InboxEmail,\n {\n id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n if (!email) {\n return NextResponse.json({ error: 'Email not found' }, { status: 404 })\n }\n\n return NextResponse.json({ email })\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to load email detail', { err })\n return NextResponse.json({ error: 'Failed to load email' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const url = new URL(req.url)\n const id = extractPathSegment(url, 'emails')\n\n if (!id) {\n return NextResponse.json({ error: 'Missing email ID' }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_email',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const updated = await ctx.em.nativeUpdate(\n InboxEmail,\n {\n id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n { deletedAt: new Date() },\n )\n\n if (updated === 0) {\n return NextResponse.json({ error: 'Email not found' }, { status: 404 })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_email',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to delete email', { err })\n return NextResponse.json({ error: 'Failed to delete email' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Email detail',\n methods: {\n GET: {\n summary: 'Get email detail with parsed thread',\n responses: [\n { status: 200, description: 'Email detail' },\n { status: 404, description: 'Email not found' },\n ],\n },\n DELETE: {\n summary: 'Soft-delete an inbox email',\n responses: [\n { status: 200, description: 'Email deleted' },\n { status: 404, description: 'Email not found' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { InboxEmail } from '../../../data/entities'\nimport {\n resolveRequestContext,\n extractPathSegment,\n UnauthorizedError,\n} from '../../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { canViewEmailContent, serializeInboxEmail } from '../response'\n\nconst logger = createLogger('inbox_ops').child({ component: 'emails' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.log.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const id = extractPathSegment(url, 'emails')\n\n if (!id) {\n return NextResponse.json({ error: 'Missing email ID' }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n\n const email = await findOneWithDecryption(\n ctx.em,\n InboxEmail,\n {\n id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n if (!email) {\n return NextResponse.json({ error: 'Email not found' }, { status: 404 })\n }\n\n const includeContent = await canViewEmailContent(ctx)\n return NextResponse.json({ email: serializeInboxEmail(email, includeContent) })\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to load email detail', { err })\n return NextResponse.json({ error: 'Failed to load email' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const url = new URL(req.url)\n const id = extractPathSegment(url, 'emails')\n\n if (!id) {\n return NextResponse.json({ error: 'Missing email ID' }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_email',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const updated = await ctx.em.nativeUpdate(\n InboxEmail,\n {\n id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n { deletedAt: new Date() },\n )\n\n if (updated === 0) {\n return NextResponse.json({ error: 'Email not found' }, { status: 404 })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_email',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to delete email', { err })\n return NextResponse.json({ error: 'Failed to delete email' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Email detail',\n methods: {\n GET: {\n summary: 'Get email detail with parsed thread',\n responses: [\n { status: 200, description: 'Email detail' },\n { status: 404, description: 'Email not found' },\n ],\n },\n DELETE: {\n summary: 'Soft-delete an inbox email',\n responses: [\n { status: 200, description: 'Email deleted' },\n { status: 404, description: 'Email not found' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,46 @@
1
+ const EMAIL_CONTENT_FIELDS = [
2
+ "messageId",
3
+ "contentHash",
4
+ "toAddress",
5
+ "replyTo",
6
+ "inReplyTo",
7
+ "emailReferences",
8
+ "rawText",
9
+ "rawHtml",
10
+ "cleanedText",
11
+ "threadMessages",
12
+ "detectedLanguage",
13
+ "attachmentIds",
14
+ "isActive",
15
+ "metadata",
16
+ "organizationId",
17
+ "tenantId",
18
+ "createdAt",
19
+ "updatedAt",
20
+ "deletedAt"
21
+ ];
22
+ async function canViewEmailContent(ctx) {
23
+ try {
24
+ const rbac = ctx.container.resolve("rbacService");
25
+ return await rbac.userHasAllFeatures(
26
+ ctx.userId,
27
+ ["inbox_ops.proposals.view"],
28
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
29
+ );
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+ function serializeInboxEmail(email, includeContent) {
35
+ if (includeContent) return email;
36
+ const redacted = { ...email };
37
+ for (const field of EMAIL_CONTENT_FIELDS) {
38
+ redacted[field] = null;
39
+ }
40
+ return redacted;
41
+ }
42
+ export {
43
+ canViewEmailContent,
44
+ serializeInboxEmail
45
+ };
46
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/inbox_ops/api/emails/response.ts"],
4
+ "sourcesContent": ["import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { InboxEmail } from '../../data/entities'\nimport type { RequestContext } from '../routeHelpers'\n\nconst EMAIL_CONTENT_FIELDS = [\n 'messageId',\n 'contentHash',\n 'toAddress',\n 'replyTo',\n 'inReplyTo',\n 'emailReferences',\n 'rawText',\n 'rawHtml',\n 'cleanedText',\n 'threadMessages',\n 'detectedLanguage',\n 'attachmentIds',\n 'isActive',\n 'metadata',\n 'organizationId',\n 'tenantId',\n 'createdAt',\n 'updatedAt',\n 'deletedAt',\n] as const\n\nexport async function canViewEmailContent(ctx: RequestContext): Promise<boolean> {\n try {\n const rbac = ctx.container.resolve<RbacService>('rbacService')\n return await rbac.userHasAllFeatures(\n ctx.userId,\n ['inbox_ops.proposals.view'],\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n } catch {\n return false\n }\n}\n\nexport function serializeInboxEmail(\n email: InboxEmail,\n includeContent: boolean,\n): InboxEmail | Record<string, unknown> {\n if (includeContent) return email\n\n const redacted: Record<string, unknown> = { ...email }\n for (const field of EMAIL_CONTENT_FIELDS) {\n redacted[field] = null\n }\n return redacted\n}\n"],
5
+ "mappings": "AAIA,MAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,eAAsB,oBAAoB,KAAuC;AAC/E,MAAI;AACF,UAAM,OAAO,IAAI,UAAU,QAAqB,aAAa;AAC7D,WAAO,MAAM,KAAK;AAAA,MAChB,IAAI;AAAA,MACJ,CAAC,0BAA0B;AAAA,MAC3B,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAAA,IAC/D;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,oBACd,OACA,gBACsC;AACtC,MAAI,eAAgB,QAAO;AAE3B,QAAM,WAAoC,EAAE,GAAG,MAAM;AACrD,aAAW,SAAS,sBAAsB;AACxC,aAAS,KAAK,IAAI;AAAA,EACpB;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -4,6 +4,7 @@ import { InboxEmail } from "../../data/entities.js";
4
4
  import { emailListQuerySchema } from "../../data/validators.js";
5
5
  import { resolveRequestContext, UnauthorizedError } from "../routeHelpers.js";
6
6
  import { createLogger } from "@open-mercato/shared/lib/logger";
7
+ import { canViewEmailContent, serializeInboxEmail } from "./response.js";
7
8
  const logger = createLogger("inbox_ops").child({ component: "emails" });
8
9
  const metadata = {
9
10
  GET: { requireAuth: true, requireFeatures: ["inbox_ops.log.view"] }
@@ -26,6 +27,7 @@ async function GET(req) {
26
27
  where.status = query.status;
27
28
  }
28
29
  const offset = (query.page - 1) * query.pageSize;
30
+ const includeContent = await canViewEmailContent(ctx);
29
31
  const [items, total] = await findAndCountWithDecryption(
30
32
  ctx.em,
31
33
  InboxEmail,
@@ -38,7 +40,7 @@ async function GET(req) {
38
40
  ctx.scope
39
41
  );
40
42
  return NextResponse.json({
41
- items,
43
+ items: items.map((email) => serializeInboxEmail(email, includeContent)),
42
44
  total,
43
45
  page: query.page,
44
46
  pageSize: query.pageSize,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/inbox_ops/api/emails/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxEmail } from '../../data/entities'\nimport { emailListQuerySchema } from '../../data/validators'\nimport { resolveRequestContext, UnauthorizedError } from '../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'emails' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.log.view'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const query = emailListQuerySchema.parse({\n status: url.searchParams.get('status') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n })\n\n const ctx = await resolveRequestContext(req)\n\n const where: Record<string, unknown> = {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n }\n\n if (query.status) {\n where.status = query.status\n }\n\n const offset = (query.page - 1) * query.pageSize\n\n const [items, total] = await findAndCountWithDecryption(\n ctx.em,\n InboxEmail,\n where,\n {\n limit: query.pageSize,\n offset,\n orderBy: { receivedAt: 'DESC' },\n },\n ctx.scope,\n )\n\n return NextResponse.json({\n items,\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to list emails', { err })\n return NextResponse.json({ error: 'Failed to list emails' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Emails',\n methods: {\n GET: {\n summary: 'List received emails',\n description: 'Processing log of all received emails',\n responses: [\n { status: 200, description: 'Paginated list of emails' },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,kCAAkC;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AACrC,SAAS,uBAAuB,yBAAyB;AACzD,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAE/D,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,oBAAoB,EAAE;AACpE;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,qBAAqB,MAAM;AAAA,MACvC,QAAQ,IAAI,aAAa,IAAI,QAAQ,KAAK;AAAA,MAC1C,MAAM,IAAI,aAAa,IAAI,MAAM,KAAK;AAAA,MACtC,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,IAChD,CAAC;AAED,UAAM,MAAM,MAAM,sBAAsB,GAAG;AAE3C,UAAM,QAAiC;AAAA,MACrC,gBAAgB,IAAI;AAAA,MACpB,UAAU,IAAI;AAAA,MACd,WAAW;AAAA,IACb;AAEA,QAAI,MAAM,QAAQ;AAChB,YAAM,SAAS,MAAM;AAAA,IACvB;AAEA,UAAM,UAAU,MAAM,OAAO,KAAK,MAAM;AAExC,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM;AAAA,MAC3B,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,MAAM;AAAA,QACb;AAAA,QACA,SAAS,EAAE,YAAY,OAAO;AAAA,MAChC;AAAA,MACA,IAAI;AAAA,IACN;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB;AAAA,MACA;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,YAAY,KAAK,KAAK,QAAQ,MAAM,QAAQ;AAAA,IAC9C,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,eAAe,mBAAmB;AACpC,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,WAAO,MAAM,yBAAyB,EAAE,IAAI,CAAC;AAC7C,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,2BAA2B;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxEmail } from '../../data/entities'\nimport { emailListQuerySchema } from '../../data/validators'\nimport { resolveRequestContext, UnauthorizedError } from '../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { canViewEmailContent, serializeInboxEmail } from './response'\n\nconst logger = createLogger('inbox_ops').child({ component: 'emails' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.log.view'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const query = emailListQuerySchema.parse({\n status: url.searchParams.get('status') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n })\n\n const ctx = await resolveRequestContext(req)\n\n const where: Record<string, unknown> = {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n }\n\n if (query.status) {\n where.status = query.status\n }\n\n const offset = (query.page - 1) * query.pageSize\n const includeContent = await canViewEmailContent(ctx)\n\n const [items, total] = await findAndCountWithDecryption(\n ctx.em,\n InboxEmail,\n where,\n {\n limit: query.pageSize,\n offset,\n orderBy: { receivedAt: 'DESC' },\n },\n ctx.scope,\n )\n\n return NextResponse.json({\n items: items.map((email) => serializeInboxEmail(email, includeContent)),\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n logger.error('Failed to list emails', { err })\n return NextResponse.json({ error: 'Failed to list emails' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Emails',\n methods: {\n GET: {\n summary: 'List received emails',\n description: 'Processing log of all received emails',\n responses: [\n { status: 200, description: 'Paginated list of emails' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,kCAAkC;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AACrC,SAAS,uBAAuB,yBAAyB;AACzD,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB,2BAA2B;AAEzD,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAE/D,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,oBAAoB,EAAE;AACpE;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,qBAAqB,MAAM;AAAA,MACvC,QAAQ,IAAI,aAAa,IAAI,QAAQ,KAAK;AAAA,MAC1C,MAAM,IAAI,aAAa,IAAI,MAAM,KAAK;AAAA,MACtC,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,IAChD,CAAC;AAED,UAAM,MAAM,MAAM,sBAAsB,GAAG;AAE3C,UAAM,QAAiC;AAAA,MACrC,gBAAgB,IAAI;AAAA,MACpB,UAAU,IAAI;AAAA,MACd,WAAW;AAAA,IACb;AAEA,QAAI,MAAM,QAAQ;AAChB,YAAM,SAAS,MAAM;AAAA,IACvB;AAEA,UAAM,UAAU,MAAM,OAAO,KAAK,MAAM;AACxC,UAAM,iBAAiB,MAAM,oBAAoB,GAAG;AAEpD,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM;AAAA,MAC3B,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,MAAM;AAAA,QACb;AAAA,QACA,SAAS,EAAE,YAAY,OAAO;AAAA,MAChC;AAAA,MACA,IAAI;AAAA,IACN;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,OAAO,MAAM,IAAI,CAAC,UAAU,oBAAoB,OAAO,cAAc,CAAC;AAAA,MACtE;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,YAAY,KAAK,KAAK,QAAQ,MAAM,QAAQ;AAAA,IAC9C,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,QAAI,eAAe,mBAAmB;AACpC,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,WAAO,MAAM,yBAAyB,EAAE,IAAI,CAAC;AAC7C,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,2BAA2B;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACF;",
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.6-develop.6505.1.6f6b83559f",
3
+ "version": "0.6.6-develop.6507.1.cc8b36505d",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6505.1.6f6b83559f",
257
- "@open-mercato/shared": "0.6.6-develop.6505.1.6f6b83559f",
258
- "@open-mercato/ui": "0.6.6-develop.6505.1.6f6b83559f",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6507.1.cc8b36505d",
257
+ "@open-mercato/shared": "0.6.6-develop.6507.1.cc8b36505d",
258
+ "@open-mercato/ui": "0.6.6-develop.6507.1.cc8b36505d",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.6-develop.6505.1.6f6b83559f",
264
- "@open-mercato/shared": "0.6.6-develop.6505.1.6f6b83559f",
265
- "@open-mercato/ui": "0.6.6-develop.6505.1.6f6b83559f",
263
+ "@open-mercato/ai-assistant": "0.6.6-develop.6507.1.cc8b36505d",
264
+ "@open-mercato/shared": "0.6.6-develop.6507.1.cc8b36505d",
265
+ "@open-mercato/ui": "0.6.6-develop.6507.1.cc8b36505d",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -12,6 +12,7 @@ import {
12
12
  UnauthorizedError,
13
13
  } from '../../routeHelpers'
14
14
  import { createLogger } from '@open-mercato/shared/lib/logger'
15
+ import { canViewEmailContent, serializeInboxEmail } from '../response'
15
16
 
16
17
  const logger = createLogger('inbox_ops').child({ component: 'emails' })
17
18
 
@@ -48,7 +49,8 @@ export async function GET(req: Request) {
48
49
  return NextResponse.json({ error: 'Email not found' }, { status: 404 })
49
50
  }
50
51
 
51
- return NextResponse.json({ email })
52
+ const includeContent = await canViewEmailContent(ctx)
53
+ return NextResponse.json({ email: serializeInboxEmail(email, includeContent) })
52
54
  } catch (err) {
53
55
  if (err instanceof UnauthorizedError) {
54
56
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
@@ -0,0 +1,51 @@
1
+ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
2
+ import type { InboxEmail } from '../../data/entities'
3
+ import type { RequestContext } from '../routeHelpers'
4
+
5
+ const EMAIL_CONTENT_FIELDS = [
6
+ 'messageId',
7
+ 'contentHash',
8
+ 'toAddress',
9
+ 'replyTo',
10
+ 'inReplyTo',
11
+ 'emailReferences',
12
+ 'rawText',
13
+ 'rawHtml',
14
+ 'cleanedText',
15
+ 'threadMessages',
16
+ 'detectedLanguage',
17
+ 'attachmentIds',
18
+ 'isActive',
19
+ 'metadata',
20
+ 'organizationId',
21
+ 'tenantId',
22
+ 'createdAt',
23
+ 'updatedAt',
24
+ 'deletedAt',
25
+ ] as const
26
+
27
+ export async function canViewEmailContent(ctx: RequestContext): Promise<boolean> {
28
+ try {
29
+ const rbac = ctx.container.resolve<RbacService>('rbacService')
30
+ return await rbac.userHasAllFeatures(
31
+ ctx.userId,
32
+ ['inbox_ops.proposals.view'],
33
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
34
+ )
35
+ } catch {
36
+ return false
37
+ }
38
+ }
39
+
40
+ export function serializeInboxEmail(
41
+ email: InboxEmail,
42
+ includeContent: boolean,
43
+ ): InboxEmail | Record<string, unknown> {
44
+ if (includeContent) return email
45
+
46
+ const redacted: Record<string, unknown> = { ...email }
47
+ for (const field of EMAIL_CONTENT_FIELDS) {
48
+ redacted[field] = null
49
+ }
50
+ return redacted
51
+ }
@@ -5,6 +5,7 @@ import { InboxEmail } from '../../data/entities'
5
5
  import { emailListQuerySchema } from '../../data/validators'
6
6
  import { resolveRequestContext, UnauthorizedError } from '../routeHelpers'
7
7
  import { createLogger } from '@open-mercato/shared/lib/logger'
8
+ import { canViewEmailContent, serializeInboxEmail } from './response'
8
9
 
9
10
  const logger = createLogger('inbox_ops').child({ component: 'emails' })
10
11
 
@@ -34,6 +35,7 @@ export async function GET(req: Request) {
34
35
  }
35
36
 
36
37
  const offset = (query.page - 1) * query.pageSize
38
+ const includeContent = await canViewEmailContent(ctx)
37
39
 
38
40
  const [items, total] = await findAndCountWithDecryption(
39
41
  ctx.em,
@@ -48,7 +50,7 @@ export async function GET(req: Request) {
48
50
  )
49
51
 
50
52
  return NextResponse.json({
51
- items,
53
+ items: items.map((email) => serializeInboxEmail(email, includeContent)),
52
54
  total,
53
55
  page: query.page,
54
56
  pageSize: query.pageSize,