@open-mercato/core 0.10.1-develop.7741.1.61a2652671 → 0.10.1-develop.7749.1.ea3edf75e3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/attachment_partition/index.js +2 -0
  3. package/dist/generated/entities/attachment_partition/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +1 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/helpers/integration/attachmentAccessFixtures.js +144 -0
  7. package/dist/helpers/integration/attachmentAccessFixtures.js.map +7 -0
  8. package/dist/modules/attachments/api/file/[id]/route.js +16 -10
  9. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  10. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +15 -9
  11. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  12. package/dist/modules/attachments/api/library/[id]/route.js +100 -56
  13. package/dist/modules/attachments/api/library/[id]/route.js.map +3 -3
  14. package/dist/modules/attachments/api/library/route.js +56 -17
  15. package/dist/modules/attachments/api/library/route.js.map +2 -2
  16. package/dist/modules/attachments/api/partitions/route.js +2 -0
  17. package/dist/modules/attachments/api/partitions/route.js.map +2 -2
  18. package/dist/modules/attachments/api/route.js +74 -34
  19. package/dist/modules/attachments/api/route.js.map +3 -3
  20. package/dist/modules/attachments/api/transfer/route.js +63 -37
  21. package/dist/modules/attachments/api/transfer/route.js.map +2 -2
  22. package/dist/modules/attachments/data/entities.js +3 -0
  23. package/dist/modules/attachments/data/entities.js.map +2 -2
  24. package/dist/modules/attachments/data/validators.js +41 -0
  25. package/dist/modules/attachments/data/validators.js.map +7 -0
  26. package/dist/modules/attachments/di.js +5 -2
  27. package/dist/modules/attachments/di.js.map +2 -2
  28. package/dist/modules/attachments/generators.js +29 -0
  29. package/dist/modules/attachments/generators.js.map +7 -0
  30. package/dist/modules/attachments/index.js +8 -1
  31. package/dist/modules/attachments/index.js.map +2 -2
  32. package/dist/modules/attachments/lib/access-errors.js +35 -0
  33. package/dist/modules/attachments/lib/access-errors.js.map +7 -0
  34. package/dist/modules/attachments/lib/access-mutation.js +23 -0
  35. package/dist/modules/attachments/lib/access-mutation.js.map +7 -0
  36. package/dist/modules/attachments/lib/access-protection.js +73 -0
  37. package/dist/modules/attachments/lib/access-protection.js.map +7 -0
  38. package/dist/modules/attachments/lib/access-query.js +74 -0
  39. package/dist/modules/attachments/lib/access-query.js.map +7 -0
  40. package/dist/modules/attachments/lib/access-registry.js +87 -0
  41. package/dist/modules/attachments/lib/access-registry.js.map +7 -0
  42. package/dist/modules/attachments/lib/access-runner.js +161 -0
  43. package/dist/modules/attachments/lib/access-runner.js.map +7 -0
  44. package/dist/modules/attachments/lib/access-types.js +1 -0
  45. package/dist/modules/attachments/lib/access-types.js.map +7 -0
  46. package/dist/modules/attachments/lib/attachment-service.js +14 -6
  47. package/dist/modules/attachments/lib/attachment-service.js.map +2 -2
  48. package/dist/modules/attachments/lib/createFromBuffer.js +2 -0
  49. package/dist/modules/attachments/lib/createFromBuffer.js.map +2 -2
  50. package/dist/modules/attachments/lib/partitions.js +3 -2
  51. package/dist/modules/attachments/lib/partitions.js.map +2 -2
  52. package/dist/modules/attachments/lib/scoped-upload-service.js +2 -0
  53. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  54. package/dist/modules/attachments/lib/target-access-service.js +12 -3
  55. package/dist/modules/attachments/lib/target-access-service.js.map +2 -2
  56. package/dist/modules/attachments/migrations/Migration20261001120000_attachments.js +38 -0
  57. package/dist/modules/attachments/migrations/Migration20261001120000_attachments.js.map +7 -0
  58. package/dist/modules/attachments/setup.js +10 -0
  59. package/dist/modules/attachments/setup.js.map +2 -2
  60. package/dist/modules/notifications/api/[id]/action/route.js +8 -0
  61. package/dist/modules/notifications/api/[id]/action/route.js.map +2 -2
  62. package/dist/modules/notifications/api/route.js +17 -0
  63. package/dist/modules/notifications/api/route.js.map +2 -2
  64. package/dist/modules/notifications/data/validators.js +1 -1
  65. package/dist/modules/notifications/data/validators.js.map +2 -2
  66. package/dist/modules/notifications/lib/notificationService.js +45 -3
  67. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  68. package/dist/modules/notifications/lib/routeHelpers.js +27 -0
  69. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  70. package/dist/modules/payment_gateways/lib/gateway-service.js +50 -34
  71. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  72. package/dist/modules/payment_gateways/lib/status-machine.js +10 -1
  73. package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
  74. package/dist/modules/payment_gateways/lib/webhook-processor.js +1 -1
  75. package/dist/modules/payment_gateways/lib/webhook-processor.js.map +2 -2
  76. package/dist/modules/staff/notifications.js +4 -2
  77. package/dist/modules/staff/notifications.js.map +2 -2
  78. package/dist/modules/workflows/data/entities.js.map +2 -2
  79. package/dist/modules/workflows/lib/activity-executor.js +18 -4
  80. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  81. package/dist/modules/workflows/lib/activity-types.js +2 -2
  82. package/dist/modules/workflows/lib/activity-types.js.map +2 -2
  83. package/dist/modules/workflows/lib/condition-handler.js +7 -1
  84. package/dist/modules/workflows/lib/condition-handler.js.map +2 -2
  85. package/dist/modules/workflows/lib/duration.js +13 -0
  86. package/dist/modules/workflows/lib/duration.js.map +2 -2
  87. package/dist/modules/workflows/lib/step-handler.js +9 -2
  88. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  89. package/dist/modules/workflows/lib/task-quick-action.js +13 -10
  90. package/dist/modules/workflows/lib/task-quick-action.js.map +2 -2
  91. package/dist/modules/workflows/lib/transition-handler.js +2 -1
  92. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  93. package/dist/modules/workflows/notifications.js +4 -0
  94. package/dist/modules/workflows/notifications.js.map +2 -2
  95. package/generated/entities/attachment_partition/index.ts +1 -0
  96. package/generated/entity-fields-registry.ts +1 -0
  97. package/package.json +7 -7
  98. package/src/helpers/integration/attachmentAccessFixtures.ts +141 -0
  99. package/src/modules/attachments/api/file/[id]/route.ts +12 -9
  100. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +10 -8
  101. package/src/modules/attachments/api/library/[id]/route.ts +87 -58
  102. package/src/modules/attachments/api/library/route.ts +40 -11
  103. package/src/modules/attachments/api/partitions/route.ts +2 -0
  104. package/src/modules/attachments/api/route.ts +68 -35
  105. package/src/modules/attachments/api/transfer/route.ts +65 -42
  106. package/src/modules/attachments/data/entities.ts +4 -0
  107. package/src/modules/attachments/data/validators.ts +36 -0
  108. package/src/modules/attachments/di.ts +4 -1
  109. package/src/modules/attachments/generators.ts +27 -0
  110. package/src/modules/attachments/i18n/de.json +4 -0
  111. package/src/modules/attachments/i18n/en.json +4 -0
  112. package/src/modules/attachments/i18n/es.json +4 -0
  113. package/src/modules/attachments/i18n/ko.json +4 -0
  114. package/src/modules/attachments/i18n/pl.json +4 -0
  115. package/src/modules/attachments/index.ts +18 -0
  116. package/src/modules/attachments/lib/access-errors.ts +32 -0
  117. package/src/modules/attachments/lib/access-mutation.ts +26 -0
  118. package/src/modules/attachments/lib/access-protection.ts +83 -0
  119. package/src/modules/attachments/lib/access-query.ts +86 -0
  120. package/src/modules/attachments/lib/access-registry.ts +117 -0
  121. package/src/modules/attachments/lib/access-runner.ts +183 -0
  122. package/src/modules/attachments/lib/access-types.ts +78 -0
  123. package/src/modules/attachments/lib/attachment-service.ts +8 -4
  124. package/src/modules/attachments/lib/createFromBuffer.ts +3 -0
  125. package/src/modules/attachments/lib/partitions.ts +3 -2
  126. package/src/modules/attachments/lib/scoped-upload-service.ts +2 -0
  127. package/src/modules/attachments/lib/target-access-service.ts +10 -3
  128. package/src/modules/attachments/migrations/.snapshot-open-mercato.json +17 -0
  129. package/src/modules/attachments/migrations/Migration20261001120000_attachments.ts +35 -0
  130. package/src/modules/attachments/setup.ts +10 -0
  131. package/src/modules/auth/i18n/de.json +3 -0
  132. package/src/modules/auth/i18n/en.json +3 -0
  133. package/src/modules/auth/i18n/es.json +3 -0
  134. package/src/modules/auth/i18n/ko.json +3 -0
  135. package/src/modules/auth/i18n/pl.json +3 -0
  136. package/src/modules/notifications/api/[id]/action/route.ts +8 -0
  137. package/src/modules/notifications/api/route.ts +17 -0
  138. package/src/modules/notifications/data/validators.ts +1 -1
  139. package/src/modules/notifications/i18n/de.json +2 -0
  140. package/src/modules/notifications/i18n/en.json +2 -0
  141. package/src/modules/notifications/i18n/es.json +2 -0
  142. package/src/modules/notifications/i18n/ko.json +2 -0
  143. package/src/modules/notifications/i18n/pl.json +2 -0
  144. package/src/modules/notifications/lib/notificationService.ts +67 -1
  145. package/src/modules/notifications/lib/routeHelpers.ts +38 -0
  146. package/src/modules/payment_gateways/lib/gateway-service.ts +50 -33
  147. package/src/modules/payment_gateways/lib/status-machine.ts +19 -0
  148. package/src/modules/payment_gateways/lib/webhook-processor.ts +1 -1
  149. package/src/modules/staff/notifications.ts +2 -0
  150. package/src/modules/workflows/AGENTS.md +2 -2
  151. package/src/modules/workflows/data/entities.ts +6 -0
  152. package/src/modules/workflows/lib/activity-executor.ts +36 -4
  153. package/src/modules/workflows/lib/activity-types.ts +2 -2
  154. package/src/modules/workflows/lib/condition-handler.ts +16 -1
  155. package/src/modules/workflows/lib/duration.ts +25 -0
  156. package/src/modules/workflows/lib/step-handler.ts +16 -1
  157. package/src/modules/workflows/lib/task-quick-action.ts +19 -10
  158. package/src/modules/workflows/lib/transition-handler.ts +2 -1
  159. package/src/modules/workflows/notifications.ts +4 -0
@@ -1,3 +1,11 @@
1
+ import { LockMode } from "@mikro-orm/core";
2
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { Attachment } from "../../data/entities.js";
4
+ import { createAttachmentAccessContext } from "../../lib/access-runner.js";
5
+ import { assertAttachmentOwnerAccess } from "../../lib/access-query.js";
6
+ import { withAttachmentAccessErrors, throwAttachmentAccessError } from "../../lib/access-errors.js";
7
+ import { prepareAttachmentMutation } from "../../lib/access-mutation.js";
8
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
1
9
  import { NextResponse } from "next/server";
2
10
  import { z } from "zod";
3
11
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
@@ -18,56 +26,72 @@ const transferSchema = z.object({
18
26
  const metadata = {
19
27
  POST: { requireAuth: true, requireFeatures: ["attachments.manage"] }
20
28
  };
21
- async function POST(req) {
29
+ async function transferAttachments(req) {
22
30
  const auth = await getAuthFromRequest(req);
23
31
  if (!auth || !auth.tenantId || !auth.orgId) {
24
32
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
25
33
  }
26
- const json = await req.json().catch(() => null);
34
+ const json = await readJsonSafe(req, null);
27
35
  const parsed = transferSchema.safeParse(json);
28
36
  if (!parsed.success) {
29
37
  return NextResponse.json({ error: "Invalid payload" }, { status: 400 });
30
38
  }
31
- const { attachmentIds, entityId, fromRecordId, toRecordId } = parsed.data;
32
- const { resolve } = await createRequestContainer();
33
- const em = resolve("em");
34
- let AttachmentEntity;
35
- try {
36
- const mod = await import("@open-mercato/core/modules/attachments/data/entities");
37
- AttachmentEntity = mod.Attachment;
38
- } catch {
39
- return NextResponse.json({ error: "Attachment model missing" }, { status: 500 });
40
- }
39
+ const container = await createRequestContainer();
40
+ const em = container.resolve("em");
41
+ const guard = await prepareAttachmentMutation({ container, req, auth, operation: "update", payload: parsed.data });
42
+ const effective = transferSchema.safeParse(guard.modifiedPayload ?? parsed.data);
43
+ if (!effective.success) return throwAttachmentAccessError(403);
44
+ const { attachmentIds, entityId, fromRecordId } = effective.data;
41
45
  const filters = {
42
- id: { $in: attachmentIds },
46
+ id: { $in: [...new Set(attachmentIds)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0) },
43
47
  entityId,
44
48
  tenantId: auth.tenantId,
45
- organizationId: auth.orgId
49
+ organizationId: auth.orgId,
50
+ ...fromRecordId ? { recordId: fromRecordId } : {}
46
51
  };
47
- if (fromRecordId) {
48
- filters.recordId = fromRecordId;
49
- }
50
- const records = await em.find(AttachmentEntity, filters);
51
- if (!records.length) {
52
- return NextResponse.json({ error: "Attachments not found" }, { status: 404 });
53
- }
54
- for (const record of records) {
55
- const previousRecordId = record.recordId;
56
- record.recordId = toRecordId;
57
- const metadata2 = readAttachmentMetadata(record.storageMetadata);
58
- const nextAssignments = metadata2.assignments?.map((assignment) => {
59
- const matchesType = assignment.type === entityId;
60
- const matchesRecord = fromRecordId ? assignment.id === fromRecordId : assignment.id === previousRecordId;
61
- if (matchesType && matchesRecord) {
62
- return { ...assignment, id: toRecordId };
52
+ const accessContext = createAttachmentAccessContext(container);
53
+ const outcome = await em.transactional(async (tx) => {
54
+ const records = await findWithDecryption(tx, Attachment, filters, {
55
+ orderBy: { id: "asc" },
56
+ lockMode: LockMode.PESSIMISTIC_WRITE,
57
+ refresh: true
58
+ }, { tenantId: auth.tenantId, organizationId: auth.orgId });
59
+ if (records.length !== new Set(attachmentIds).size) return throwAttachmentAccessError(404);
60
+ const changes = [];
61
+ for (const record of records) {
62
+ const recordPayload = { ...effective.data, attachmentIds: [record.id] };
63
+ const recordGuard = await prepareAttachmentMutation({
64
+ container,
65
+ req,
66
+ auth,
67
+ recordId: record.id,
68
+ operation: "update",
69
+ payload: recordPayload
70
+ });
71
+ const recordEffective = transferSchema.safeParse(recordGuard.modifiedPayload ?? recordPayload);
72
+ if (!recordEffective.success || recordEffective.data.attachmentIds.length !== 1 || recordEffective.data.attachmentIds[0] !== record.id || recordEffective.data.entityId !== record.entityId || recordEffective.data.fromRecordId && recordEffective.data.fromRecordId !== record.recordId) {
73
+ return throwAttachmentAccessError(403);
63
74
  }
64
- return assignment;
65
- }) ?? [];
66
- record.storageMetadata = mergeAttachmentMetadata(record.storageMetadata, { assignments: nextAssignments });
67
- }
68
- await em.persist(records).flush();
69
- return NextResponse.json({ ok: true, updated: records.length });
75
+ const destinationId = recordEffective.data.toRecordId;
76
+ const metadata2 = readAttachmentMetadata(record.storageMetadata);
77
+ const assignments = metadata2.assignments?.map((assignment) => assignment.type === entityId && assignment.id === (fromRecordId ?? record.recordId) ? { ...assignment, id: destinationId } : assignment) ?? [];
78
+ const storageMetadata = mergeAttachmentMetadata(record.storageMetadata, { assignments });
79
+ await assertAttachmentOwnerAccess({ em: tx, context: accessContext, auth, attachment: record, action: "reassign", persistProtection: true });
80
+ await assertAttachmentOwnerAccess({ em: tx, context: accessContext, auth, attachment: { ...record, recordId: destinationId, storageMetadata }, action: "reassign" });
81
+ changes.push({ record, storageMetadata, destinationId, guard: recordGuard });
82
+ }
83
+ for (const { record, storageMetadata, destinationId } of changes) {
84
+ record.recordId = destinationId;
85
+ record.storageMetadata = storageMetadata;
86
+ }
87
+ await tx.flush();
88
+ return { updated: records.length, guards: changes.map((change) => change.guard) };
89
+ });
90
+ await guard.runAfterSuccess();
91
+ for (const recordGuard of outcome.guards) await recordGuard.runAfterSuccess();
92
+ return NextResponse.json({ ok: true, updated: outcome.updated });
70
93
  }
94
+ const POST = withAttachmentAccessErrors(transferAttachments);
71
95
  const openApi = {
72
96
  tag: attachmentsTag,
73
97
  summary: "Transfer attachments between records",
@@ -86,7 +110,9 @@ const openApi = {
86
110
  { status: 400, description: "Invalid payload", schema: attachmentErrorSchema },
87
111
  { status: 401, description: "Unauthorized", schema: attachmentErrorSchema },
88
112
  { status: 404, description: "Attachments not found", schema: attachmentErrorSchema },
89
- { status: 500, description: "Attachment model missing", schema: attachmentErrorSchema }
113
+ { status: 500, description: "Attachment model missing", schema: attachmentErrorSchema },
114
+ { status: 403, description: "Owner policy denies access", schema: attachmentErrorSchema },
115
+ { status: 504, description: "Owner authorization timed out", schema: attachmentErrorSchema }
90
116
  ]
91
117
  }
92
118
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/attachments/api/transfer/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { mergeAttachmentMetadata, readAttachmentMetadata } from '../../lib/metadata'\nimport {\n attachmentsTag,\n transferAttachmentsRequestSchema,\n transferAttachmentsResponseSchema,\n attachmentErrorSchema,\n} from '../openapi'\n\nconst transferSchema = z.object({\n entityId: z.string().min(1),\n attachmentIds: z.array(z.string().uuid()).min(1),\n fromRecordId: z.string().min(1).optional(),\n toRecordId: z.string().min(1),\n})\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const json = await req.json().catch(() => null)\n const parsed = transferSchema.safeParse(json)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload' }, { status: 400 })\n }\n const { attachmentIds, entityId, fromRecordId, toRecordId } = parsed.data\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n let AttachmentEntity: any\n try {\n const mod = await import('@open-mercato/core/modules/attachments/data/entities')\n AttachmentEntity = mod.Attachment\n } catch {\n return NextResponse.json({ error: 'Attachment model missing' }, { status: 500 })\n }\n const filters: Record<string, unknown> = {\n id: { $in: attachmentIds },\n entityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }\n if (fromRecordId) {\n filters.recordId = fromRecordId\n }\n const records = await em.find(AttachmentEntity, filters)\n if (!records.length) {\n return NextResponse.json({ error: 'Attachments not found' }, { status: 404 })\n }\n for (const record of records) {\n const previousRecordId = record.recordId\n record.recordId = toRecordId\n const metadata = readAttachmentMetadata(record.storageMetadata)\n const nextAssignments =\n metadata.assignments?.map((assignment) => {\n const matchesType = assignment.type === entityId\n const matchesRecord = fromRecordId\n ? assignment.id === fromRecordId\n : assignment.id === previousRecordId\n if (matchesType && matchesRecord) {\n return { ...assignment, id: toRecordId }\n }\n return assignment\n }) ?? []\n record.storageMetadata = mergeAttachmentMetadata(record.storageMetadata, { assignments: nextAssignments })\n }\n await em.persist(records).flush()\n return NextResponse.json({ ok: true, updated: records.length })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: attachmentsTag,\n summary: 'Transfer attachments between records',\n methods: {\n POST: {\n summary: 'Transfer attachments to different record',\n description: 'Transfers one or more attachments from one record to another within the same entity type. Updates attachment assignments and metadata to reflect the new record.',\n requestBody: {\n contentType: 'application/json',\n schema: transferAttachmentsRequestSchema,\n },\n responses: [\n { status: 200, description: 'Attachments transferred successfully', schema: transferAttachmentsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: attachmentErrorSchema },\n { status: 401, description: 'Unauthorized', schema: attachmentErrorSchema },\n { status: 404, description: 'Attachments not found', schema: attachmentErrorSchema },\n { status: 500, description: 'Attachment model missing', schema: attachmentErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { LockMode } from '@mikro-orm/core'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Attachment } from '../../data/entities'\nimport { createAttachmentAccessContext } from '../../lib/access-runner'\nimport { assertAttachmentOwnerAccess } from '../../lib/access-query'\nimport { withAttachmentAccessErrors, throwAttachmentAccessError } from '../../lib/access-errors'\nimport { prepareAttachmentMutation } from '../../lib/access-mutation'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { mergeAttachmentMetadata, readAttachmentMetadata } from '../../lib/metadata'\nimport {\n attachmentsTag,\n transferAttachmentsRequestSchema,\n transferAttachmentsResponseSchema,\n attachmentErrorSchema,\n} from '../openapi'\n\nconst transferSchema = z.object({\n entityId: z.string().min(1),\n attachmentIds: z.array(z.string().uuid()).min(1),\n fromRecordId: z.string().min(1).optional(),\n toRecordId: z.string().min(1),\n})\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n}\n\nasync function transferAttachments(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const json = await readJsonSafe(req, null)\n const parsed = transferSchema.safeParse(json)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload' }, { status: 400 })\n }\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n const guard = await prepareAttachmentMutation({ container, req, auth, operation: 'update', payload: parsed.data })\n const effective = transferSchema.safeParse(guard.modifiedPayload ?? parsed.data)\n if (!effective.success) return throwAttachmentAccessError(403)\n const { attachmentIds, entityId, fromRecordId } = effective.data\n const filters: Record<string, unknown> = {\n id: { $in: [...new Set(attachmentIds)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0) }, entityId,\n tenantId: auth.tenantId, organizationId: auth.orgId,\n ...(fromRecordId ? { recordId: fromRecordId } : {}),\n }\n const accessContext = createAttachmentAccessContext(container)\n const outcome = await em.transactional(async (tx) => {\n const records = await findWithDecryption(tx, Attachment, filters, {\n orderBy: { id: 'asc' }, lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true,\n }, { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (records.length !== new Set(attachmentIds).size) return throwAttachmentAccessError(404)\n const changes = []\n for (const record of records) {\n const recordPayload = { ...effective.data, attachmentIds: [record.id] }\n const recordGuard = await prepareAttachmentMutation({\n container, req, auth, recordId: record.id, operation: 'update', payload: recordPayload,\n })\n const recordEffective = transferSchema.safeParse(recordGuard.modifiedPayload ?? recordPayload)\n if (!recordEffective.success\n || recordEffective.data.attachmentIds.length !== 1\n || recordEffective.data.attachmentIds[0] !== record.id\n || recordEffective.data.entityId !== record.entityId\n || (recordEffective.data.fromRecordId && recordEffective.data.fromRecordId !== record.recordId)) {\n return throwAttachmentAccessError(403)\n }\n const destinationId = recordEffective.data.toRecordId\n const metadata = readAttachmentMetadata(record.storageMetadata)\n const assignments = metadata.assignments?.map((assignment) => (\n assignment.type === entityId && assignment.id === (fromRecordId ?? record.recordId)\n ? { ...assignment, id: destinationId } : assignment\n )) ?? []\n const storageMetadata = mergeAttachmentMetadata(record.storageMetadata, { assignments })\n await assertAttachmentOwnerAccess({ em: tx, context: accessContext, auth, attachment: record, action: 'reassign', persistProtection: true })\n await assertAttachmentOwnerAccess({ em: tx, context: accessContext, auth, attachment: { ...record, recordId: destinationId, storageMetadata }, action: 'reassign' })\n changes.push({ record, storageMetadata, destinationId, guard: recordGuard })\n }\n for (const { record, storageMetadata, destinationId } of changes) {\n record.recordId = destinationId\n record.storageMetadata = storageMetadata\n }\n await tx.flush()\n return { updated: records.length, guards: changes.map((change) => change.guard) }\n })\n await guard.runAfterSuccess()\n for (const recordGuard of outcome.guards) await recordGuard.runAfterSuccess()\n return NextResponse.json({ ok: true, updated: outcome.updated })\n}\n\nexport const POST = withAttachmentAccessErrors(transferAttachments)\n\nexport const openApi: OpenApiRouteDoc = {\n tag: attachmentsTag,\n summary: 'Transfer attachments between records',\n methods: {\n POST: {\n summary: 'Transfer attachments to different record',\n description: 'Transfers one or more attachments from one record to another within the same entity type. Updates attachment assignments and metadata to reflect the new record.',\n requestBody: {\n contentType: 'application/json',\n schema: transferAttachmentsRequestSchema,\n },\n responses: [\n { status: 200, description: 'Attachments transferred successfully', schema: transferAttachmentsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: attachmentErrorSchema },\n { status: 401, description: 'Unauthorized', schema: attachmentErrorSchema },\n { status: 404, description: 'Attachments not found', schema: attachmentErrorSchema },\n { status: 500, description: 'Attachment model missing', schema: attachmentErrorSchema },\n { status: 403, description: 'Owner policy denies access', schema: attachmentErrorSchema },\n { status: 504, description: 'Owner authorization timed out', schema: attachmentErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["metadata"]
7
7
  }
@@ -39,6 +39,9 @@ __decorateClass([
39
39
  __decorateClass([
40
40
  Property({ name: "config_json", type: "json", nullable: true })
41
41
  ], AttachmentPartition.prototype, "configJson", 2);
42
+ __decorateClass([
43
+ Property({ name: "access_resolver_requirements", type: "jsonb", nullable: true })
44
+ ], AttachmentPartition.prototype, "accessResolverRequirements", 2);
42
45
  __decorateClass([
43
46
  Property({ name: "is_public", type: "boolean", default: false })
44
47
  ], AttachmentPartition.prototype, "isPublic", 2);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/data/entities.ts"],
4
- "sourcesContent": ["import { BigIntType, OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\nimport { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'\n\n@Entity({ tableName: 'attachment_partitions' })\n@Unique({ name: 'attachment_partitions_code_unique', properties: ['code'] })\nexport class AttachmentPartition {\n [OptionalProps]?: 'createdAt' | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n code!: string\n\n @Property({ type: 'text' })\n title!: string\n\n @Property({ type: 'text', nullable: true })\n description?: string | null\n\n @Property({ name: 'storage_driver', type: 'text', default: 'local' })\n storageDriver: string = 'local'\n\n @Property({ name: 'config_json', type: 'json', nullable: true })\n configJson?: Record<string, unknown> | null\n\n @Property({ name: 'is_public', type: 'boolean', default: false })\n isPublic: boolean = false\n\n @Property({ name: 'requires_ocr', type: 'boolean', default: resolveDefaultAttachmentOcrEnabled() })\n requiresOcr: boolean = resolveDefaultAttachmentOcrEnabled()\n\n @Property({ name: 'ocr_model', type: 'text', nullable: true })\n ocrModel?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n @Index({ name: 'attachment_partitions_tenant_idx' })\n tenantId?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n\n@Entity({ tableName: 'attachments' })\n@Index({ name: 'attachments_storage_reference_idx', properties: ['partitionCode', 'storagePath'] })\nexport class Attachment {\n [OptionalProps]?: 'createdAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'entity_id', type: 'text' })\n entityId!: string\n\n @Property({ name: 'record_id', type: 'text' })\n @Index({ name: 'attachments_entity_record_idx' })\n recordId!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'partition_code', type: 'text' })\n @Index({ name: 'attachments_partition_code_idx' })\n partitionCode!: string\n\n @Property({ name: 'file_name', type: 'text' })\n fileName!: string\n\n @Property({ name: 'mime_type', type: 'text' })\n mimeType!: string\n\n @Property({ name: 'file_size', type: 'int' })\n fileSize!: number\n\n @Property({ name: 'storage_driver', type: 'text', default: 'local' })\n storageDriver: string = 'local'\n\n @Property({ name: 'storage_path', type: 'text' })\n storagePath!: string\n\n @Property({ name: 'storage_metadata', type: 'jsonb', nullable: true })\n storageMetadata?: Record<string, unknown> | null\n\n @Property({ name: 'url', type: 'text' })\n url!: string\n\n @Property({ name: 'content', type: 'text', nullable: true })\n content: string | null = null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n\n@Entity({ tableName: 'attachment_quota_reservations' })\n@Unique({\n name: 'attachment_quota_reservations_scope_path_unique',\n properties: ['tenantId', 'storageDriver', 'storagePath'],\n})\n@Index({\n name: 'attachment_quota_reservations_tenant_status_idx',\n properties: ['tenantId', 'status'],\n})\nexport class AttachmentQuotaReservation {\n [OptionalProps]?: 'actualBytes' | 'createdAt' | 'status' | 'updatedAt' | 'uploadTokenHash'\n\n @PrimaryKey({ type: 'uuid' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n /** Backed by a `bigint` column; hydrate as `number` (safe under Number.MAX_SAFE_INTEGER for attachment byte counts) instead of MikroORM's default JS `bigint`. */\n @Property({ name: 'reserved_bytes', type: new BigIntType('number') })\n reservedBytes!: number\n\n /** Backed by a `bigint` column; hydrate as `number`, not MikroORM's default JS `bigint`. */\n @Property({ name: 'actual_bytes', type: new BigIntType('number'), nullable: true })\n actualBytes?: number | null\n\n @Property({ type: 'text', default: 'reserved' })\n status: 'reserved' | 'storing' | 'stored' | 'recovering' | 'committed' = 'reserved'\n\n @Property({ type: 'text' })\n source!: string\n\n @Property({ name: 'storage_driver', type: 'text' })\n storageDriver!: string\n\n @Property({ name: 'partition_code', type: 'text', nullable: true })\n partitionCode?: string | null\n\n @Property({ name: 'storage_path', type: 'text' })\n storagePath!: string\n\n @Property({ name: 'lease_token', type: 'uuid' })\n leaseToken!: string\n\n @Property({ name: 'upload_token_hash', type: 'text', nullable: true })\n uploadTokenHash?: string | null\n\n @Property({ name: 'expires_at', type: Date, nullable: true })\n @Index({ name: 'attachment_quota_reservations_expires_idx' })\n expiresAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { BigIntType, OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\nimport { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'\nimport type { AttachmentAccessRequirement } from '../lib/access-types'\n\n@Entity({ tableName: 'attachment_partitions' })\n@Unique({ name: 'attachment_partitions_code_unique', properties: ['code'] })\nexport class AttachmentPartition {\n [OptionalProps]?: 'createdAt' | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n code!: string\n\n @Property({ type: 'text' })\n title!: string\n\n @Property({ type: 'text', nullable: true })\n description?: string | null\n\n @Property({ name: 'storage_driver', type: 'text', default: 'local' })\n storageDriver: string = 'local'\n\n @Property({ name: 'config_json', type: 'json', nullable: true })\n configJson?: Record<string, unknown> | null\n\n @Property({ name: 'access_resolver_requirements', type: 'jsonb', nullable: true })\n accessResolverRequirements?: AttachmentAccessRequirement[] | null\n\n @Property({ name: 'is_public', type: 'boolean', default: false })\n isPublic: boolean = false\n\n @Property({ name: 'requires_ocr', type: 'boolean', default: resolveDefaultAttachmentOcrEnabled() })\n requiresOcr: boolean = resolveDefaultAttachmentOcrEnabled()\n\n @Property({ name: 'ocr_model', type: 'text', nullable: true })\n ocrModel?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n @Index({ name: 'attachment_partitions_tenant_idx' })\n tenantId?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n\n@Entity({ tableName: 'attachments' })\n@Index({ name: 'attachments_storage_reference_idx', properties: ['partitionCode', 'storagePath'] })\nexport class Attachment {\n [OptionalProps]?: 'createdAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'entity_id', type: 'text' })\n entityId!: string\n\n @Property({ name: 'record_id', type: 'text' })\n @Index({ name: 'attachments_entity_record_idx' })\n recordId!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'partition_code', type: 'text' })\n @Index({ name: 'attachments_partition_code_idx' })\n partitionCode!: string\n\n @Property({ name: 'file_name', type: 'text' })\n fileName!: string\n\n @Property({ name: 'mime_type', type: 'text' })\n mimeType!: string\n\n @Property({ name: 'file_size', type: 'int' })\n fileSize!: number\n\n @Property({ name: 'storage_driver', type: 'text', default: 'local' })\n storageDriver: string = 'local'\n\n @Property({ name: 'storage_path', type: 'text' })\n storagePath!: string\n\n @Property({ name: 'storage_metadata', type: 'jsonb', nullable: true })\n storageMetadata?: Record<string, unknown> | null\n\n @Property({ name: 'url', type: 'text' })\n url!: string\n\n @Property({ name: 'content', type: 'text', nullable: true })\n content: string | null = null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n}\n\n@Entity({ tableName: 'attachment_quota_reservations' })\n@Unique({\n name: 'attachment_quota_reservations_scope_path_unique',\n properties: ['tenantId', 'storageDriver', 'storagePath'],\n})\n@Index({\n name: 'attachment_quota_reservations_tenant_status_idx',\n properties: ['tenantId', 'status'],\n})\nexport class AttachmentQuotaReservation {\n [OptionalProps]?: 'actualBytes' | 'createdAt' | 'status' | 'updatedAt' | 'uploadTokenHash'\n\n @PrimaryKey({ type: 'uuid' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n /** Backed by a `bigint` column; hydrate as `number` (safe under Number.MAX_SAFE_INTEGER for attachment byte counts) instead of MikroORM's default JS `bigint`. */\n @Property({ name: 'reserved_bytes', type: new BigIntType('number') })\n reservedBytes!: number\n\n /** Backed by a `bigint` column; hydrate as `number`, not MikroORM's default JS `bigint`. */\n @Property({ name: 'actual_bytes', type: new BigIntType('number'), nullable: true })\n actualBytes?: number | null\n\n @Property({ type: 'text', default: 'reserved' })\n status: 'reserved' | 'storing' | 'stored' | 'recovering' | 'committed' = 'reserved'\n\n @Property({ type: 'text' })\n source!: string\n\n @Property({ name: 'storage_driver', type: 'text' })\n storageDriver!: string\n\n @Property({ name: 'partition_code', type: 'text', nullable: true })\n partitionCode?: string | null\n\n @Property({ name: 'storage_path', type: 'text' })\n storagePath!: string\n\n @Property({ name: 'lease_token', type: 'uuid' })\n leaseToken!: string\n\n @Property({ name: 'upload_token_hash', type: 'text', nullable: true })\n uploadTokenHash?: string | null\n\n @Property({ name: 'expires_at', type: Date, nullable: true })\n @Index({ name: 'attachment_quota_reservations_expires_idx' })\n expiresAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ const attachmentAccessActionSchema = z.enum(["read", "render", "metadata", "reassign", "delete", "export"]);
3
+ const attachmentAccessSubjectFeaturesSchema = z.array(z.string());
4
+ const attachmentAccessSelectorSchema = z.string().min(1).max(200).regex(/^(?:\*|[^*\s]+(?:\.\*)?)$/);
5
+ const resolverIdSchema = z.string().min(3).max(200).regex(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_.-]+$/);
6
+ const attachmentAccessRequirementSchema = z.object({
7
+ resolverId: resolverIdSchema,
8
+ targetEntity: attachmentAccessSelectorSchema
9
+ }).strict();
10
+ const protectedAttachmentTargetSchema = attachmentAccessRequirementSchema.extend({
11
+ targetPartition: attachmentAccessSelectorSchema
12
+ });
13
+ const attachmentAccessResolverSchema = z.object({
14
+ id: resolverIdSchema,
15
+ targetPartition: attachmentAccessSelectorSchema,
16
+ targetEntity: attachmentAccessSelectorSchema.optional(),
17
+ actions: z.array(attachmentAccessActionSchema).min(1).optional(),
18
+ priority: z.number().finite().optional(),
19
+ timeoutMs: z.number().int().min(1).max(15e3).optional(),
20
+ resolve: z.custom((value) => typeof value === "function")
21
+ }).strict();
22
+ const attachmentAccessRequirementsSchema = z.array(attachmentAccessRequirementSchema);
23
+ const attachmentAccessDecisionSchema = z.discriminatedUnion("ok", [
24
+ z.object({ ok: z.literal(true) }).strict(),
25
+ z.object({
26
+ ok: z.literal(false),
27
+ status: z.union([z.literal(401), z.literal(403), z.literal(404), z.literal(504)]),
28
+ reason: z.string().min(1).max(200).regex(/^[a-zA-Z0-9_.:-]+$/)
29
+ }).strict()
30
+ ]);
31
+ export {
32
+ attachmentAccessActionSchema,
33
+ attachmentAccessDecisionSchema,
34
+ attachmentAccessRequirementSchema,
35
+ attachmentAccessRequirementsSchema,
36
+ attachmentAccessResolverSchema,
37
+ attachmentAccessSelectorSchema,
38
+ attachmentAccessSubjectFeaturesSchema,
39
+ protectedAttachmentTargetSchema
40
+ };
41
+ //# sourceMappingURL=validators.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/data/validators.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { AttachmentAccessResolver } from '../lib/access-types'\n\nexport const attachmentAccessActionSchema = z.enum(['read', 'render', 'metadata', 'reassign', 'delete', 'export'])\nexport const attachmentAccessSubjectFeaturesSchema = z.array(z.string())\nexport const attachmentAccessSelectorSchema = z.string().min(1).max(200).regex(/^(?:\\*|[^*\\s]+(?:\\.\\*)?)$/)\nconst resolverIdSchema = z.string().min(3).max(200).regex(/^[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_.-]+$/)\n\nexport const attachmentAccessRequirementSchema = z.object({\n resolverId: resolverIdSchema,\n targetEntity: attachmentAccessSelectorSchema,\n}).strict()\n\nexport const protectedAttachmentTargetSchema = attachmentAccessRequirementSchema.extend({\n targetPartition: attachmentAccessSelectorSchema,\n})\n\nexport const attachmentAccessResolverSchema = z.object({\n id: resolverIdSchema,\n targetPartition: attachmentAccessSelectorSchema,\n targetEntity: attachmentAccessSelectorSchema.optional(),\n actions: z.array(attachmentAccessActionSchema).min(1).optional(),\n priority: z.number().finite().optional(),\n timeoutMs: z.number().int().min(1).max(15_000).optional(),\n resolve: z.custom<AttachmentAccessResolver['resolve']>((value) => typeof value === 'function'),\n}).strict()\n\nexport const attachmentAccessRequirementsSchema = z.array(attachmentAccessRequirementSchema)\nexport const attachmentAccessDecisionSchema = z.discriminatedUnion('ok', [\n z.object({ ok: z.literal(true) }).strict(),\n z.object({\n ok: z.literal(false),\n status: z.union([z.literal(401), z.literal(403), z.literal(404), z.literal(504)]),\n reason: z.string().min(1).max(200).regex(/^[a-zA-Z0-9_.:-]+$/),\n }).strict(),\n])\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAGX,MAAM,+BAA+B,EAAE,KAAK,CAAC,QAAQ,UAAU,YAAY,YAAY,UAAU,QAAQ,CAAC;AAC1G,MAAM,wCAAwC,EAAE,MAAM,EAAE,OAAO,CAAC;AAChE,MAAM,iCAAiC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,2BAA2B;AAC1G,MAAM,mBAAmB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,mCAAmC;AAEtF,MAAM,oCAAoC,EAAE,OAAO;AAAA,EACxD,YAAY;AAAA,EACZ,cAAc;AAChB,CAAC,EAAE,OAAO;AAEH,MAAM,kCAAkC,kCAAkC,OAAO;AAAA,EACtF,iBAAiB;AACnB,CAAC;AAEM,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,IAAI;AAAA,EACJ,iBAAiB;AAAA,EACjB,cAAc,+BAA+B,SAAS;AAAA,EACtD,SAAS,EAAE,MAAM,4BAA4B,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC/D,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EACvC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,IAAM,EAAE,SAAS;AAAA,EACxD,SAAS,EAAE,OAA4C,CAAC,UAAU,OAAO,UAAU,UAAU;AAC/F,CAAC,EAAE,OAAO;AAEH,MAAM,qCAAqC,EAAE,MAAM,iCAAiC;AACpF,MAAM,iCAAiC,EAAE,mBAAmB,MAAM;AAAA,EACvE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE,OAAO;AAAA,EACzC,EAAE,OAAO;AAAA,IACP,IAAI,EAAE,QAAQ,KAAK;AAAA,IACnB,QAAQ,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,GAAG,EAAE,QAAQ,GAAG,GAAG,EAAE,QAAQ,GAAG,GAAG,EAAE,QAAQ,GAAG,CAAC,CAAC;AAAA,IAChF,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,oBAAoB;AAAA,EAC/D,CAAC,EAAE,OAAO;AACZ,CAAC;",
6
+ "names": []
7
+ }
@@ -1,3 +1,4 @@
1
+ import { createAttachmentAccessContext } from "./lib/access-runner.js";
1
2
  import { asFunction, asValue } from "awilix";
2
3
  import { StorageDriverFactory } from "./lib/drivers/driverFactory.js";
3
4
  import { DefaultAttachmentService } from "./lib/attachment-service.js";
@@ -6,13 +7,14 @@ import { scheduleAttachmentQuotaRecovery } from "./lib/quota-recovery-queue.js";
6
7
  import { AttachmentTargetAccessService } from "./lib/target-access-service.js";
7
8
  import { ScopedAttachmentUploadService } from "./lib/scoped-upload-service.js";
8
9
  function register(container) {
10
+ const accessContext = () => createAttachmentAccessContext(container);
9
11
  container.register({
10
12
  attachmentQuotaRecoveryScheduler: asValue(scheduleAttachmentQuotaRecovery),
11
13
  attachmentQuotaService: asFunction(
12
14
  ({ em }) => createAttachmentQuotaService(em)
13
15
  ).scoped().proxy(),
14
16
  attachmentTargetAccessService: asFunction(
15
- ({ em }) => new AttachmentTargetAccessService(em)
17
+ ({ em }) => new AttachmentTargetAccessService(em, accessContext)
16
18
  ).scoped().proxy(),
17
19
  attachmentScopedUploadService: asFunction(({
18
20
  em,
@@ -37,7 +39,8 @@ function register(container) {
37
39
  attachmentService: asFunction((cradle) => new DefaultAttachmentService(
38
40
  cradle.em,
39
41
  cradle.storageDriverFactory,
40
- () => cradle.attachmentScopedUploadService ?? null
42
+ () => cradle.attachmentScopedUploadService ?? null,
43
+ accessContext
41
44
  )).scoped().proxy()
42
45
  });
43
46
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/attachments/di.ts"],
4
- "sourcesContent": ["import { asFunction, asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StorageDriverFactory } from './lib/drivers/driverFactory'\nimport { DefaultAttachmentService } from './lib/attachment-service'\nimport { createAttachmentQuotaService, type AttachmentQuotaService } from './lib/quota-service'\nimport { scheduleAttachmentQuotaRecovery } from './lib/quota-recovery-queue'\nimport { AttachmentTargetAccessService } from './lib/target-access-service'\nimport { ScopedAttachmentUploadService } from './lib/scoped-upload-service'\n\nexport function register(container: AppContainer) {\n container.register({\n attachmentQuotaRecoveryScheduler: asValue(scheduleAttachmentQuotaRecovery),\n attachmentQuotaService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n createAttachmentQuotaService(em),\n )\n .scoped()\n .proxy(),\n attachmentTargetAccessService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new AttachmentTargetAccessService(em),\n )\n .scoped()\n .proxy(),\n attachmentScopedUploadService: asFunction(({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }: {\n em: ConstructorParameters<typeof StorageDriverFactory>[0]\n dataEngine: DataEngine\n storageDriverFactory: StorageDriverFactory\n attachmentQuotaService: AttachmentQuotaService\n attachmentQuotaRecoveryScheduler: typeof scheduleAttachmentQuotaRecovery\n }) => new ScopedAttachmentUploadService({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }))\n .scoped()\n .proxy(),\n storageDriverFactory: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new StorageDriverFactory(em),\n )\n .singleton()\n .proxy(),\n // The scoped upload service is handed over as a lazy resolver so module\n // uploads share the public attachment route's single fenced reservation\n // ledger, without dragging that service's own dependencies into every\n // container that merely constructs an `attachmentService`.\n attachmentService: asFunction((cradle: {\n em: ConstructorParameters<typeof DefaultAttachmentService>[0]\n storageDriverFactory: ConstructorParameters<typeof DefaultAttachmentService>[1]\n attachmentScopedUploadService: ScopedAttachmentUploadService\n }) => new DefaultAttachmentService(\n cradle.em,\n cradle.storageDriverFactory,\n () => cradle.attachmentScopedUploadService ?? null,\n ))\n .scoped()\n .proxy(),\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY,eAAe;AAGpC,SAAS,4BAA4B;AACrC,SAAS,gCAAgC;AACzC,SAAS,oCAAiE;AAC1E,SAAS,uCAAuC;AAChD,SAAS,qCAAqC;AAC9C,SAAS,qCAAqC;AAEvC,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,kCAAkC,QAAQ,+BAA+B;AAAA,IACzE,wBAAwB;AAAA,MAAW,CAAC,EAAE,GAAG,MACvC,6BAA6B,EAAE;AAAA,IACjC,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B;AAAA,MAAW,CAAC,EAAE,GAAG,MAC9C,IAAI,8BAA8B,EAAE;AAAA,IACtC,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B,WAAW,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAMM,IAAI,8BAA8B;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,CAAC,EACC,OAAO,EACP,MAAM;AAAA,IACT,sBAAsB;AAAA,MAAW,CAAC,EAAE,GAAG,MACrC,IAAI,qBAAqB,EAAE;AAAA,IAC7B,EACG,UAAU,EACV,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKT,mBAAmB,WAAW,CAAC,WAIzB,IAAI;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM,OAAO,iCAAiC;AAAA,IAChD,CAAC,EACE,OAAO,EACP,MAAM;AAAA,EACX,CAAC;AACH;",
4
+ "sourcesContent": ["import { createAttachmentAccessContext } from './lib/access-runner'\nimport { asFunction, asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StorageDriverFactory } from './lib/drivers/driverFactory'\nimport { DefaultAttachmentService } from './lib/attachment-service'\nimport { createAttachmentQuotaService, type AttachmentQuotaService } from './lib/quota-service'\nimport { scheduleAttachmentQuotaRecovery } from './lib/quota-recovery-queue'\nimport { AttachmentTargetAccessService } from './lib/target-access-service'\nimport { ScopedAttachmentUploadService } from './lib/scoped-upload-service'\n\nexport function register(container: AppContainer) {\n const accessContext = () => createAttachmentAccessContext(container)\n container.register({\n attachmentQuotaRecoveryScheduler: asValue(scheduleAttachmentQuotaRecovery),\n attachmentQuotaService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n createAttachmentQuotaService(em),\n )\n .scoped()\n .proxy(),\n attachmentTargetAccessService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new AttachmentTargetAccessService(em, accessContext),\n )\n .scoped()\n .proxy(),\n attachmentScopedUploadService: asFunction(({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }: {\n em: ConstructorParameters<typeof StorageDriverFactory>[0]\n dataEngine: DataEngine\n storageDriverFactory: StorageDriverFactory\n attachmentQuotaService: AttachmentQuotaService\n attachmentQuotaRecoveryScheduler: typeof scheduleAttachmentQuotaRecovery\n }) => new ScopedAttachmentUploadService({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }))\n .scoped()\n .proxy(),\n storageDriverFactory: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new StorageDriverFactory(em),\n )\n .singleton()\n .proxy(),\n // The scoped upload service is handed over as a lazy resolver so module\n // uploads share the public attachment route's single fenced reservation\n // ledger, without dragging that service's own dependencies into every\n // container that merely constructs an `attachmentService`.\n attachmentService: asFunction((cradle: {\n em: ConstructorParameters<typeof DefaultAttachmentService>[0]\n storageDriverFactory: ConstructorParameters<typeof DefaultAttachmentService>[1]\n attachmentScopedUploadService: ScopedAttachmentUploadService\n }) => new DefaultAttachmentService(\n cradle.em,\n cradle.storageDriverFactory,\n () => cradle.attachmentScopedUploadService ?? null,\n accessContext,\n ))\n .scoped()\n .proxy(),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,qCAAqC;AAC9C,SAAS,YAAY,eAAe;AAGpC,SAAS,4BAA4B;AACrC,SAAS,gCAAgC;AACzC,SAAS,oCAAiE;AAC1E,SAAS,uCAAuC;AAChD,SAAS,qCAAqC;AAC9C,SAAS,qCAAqC;AAEvC,SAAS,SAAS,WAAyB;AAChD,QAAM,gBAAgB,MAAM,8BAA8B,SAAS;AACnE,YAAU,SAAS;AAAA,IACjB,kCAAkC,QAAQ,+BAA+B;AAAA,IACzE,wBAAwB;AAAA,MAAW,CAAC,EAAE,GAAG,MACvC,6BAA6B,EAAE;AAAA,IACjC,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B;AAAA,MAAW,CAAC,EAAE,GAAG,MAC9C,IAAI,8BAA8B,IAAI,aAAa;AAAA,IACrD,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B,WAAW,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAMM,IAAI,8BAA8B;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,CAAC,EACC,OAAO,EACP,MAAM;AAAA,IACT,sBAAsB;AAAA,MAAW,CAAC,EAAE,GAAG,MACrC,IAAI,qBAAqB,EAAE;AAAA,IAC7B,EACG,UAAU,EACV,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKT,mBAAmB,WAAW,CAAC,WAIzB,IAAI;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM,OAAO,iCAAiC;AAAA,MAC9C;AAAA,IACF,CAAC,EACE,OAAO,EACP,MAAM;AAAA,EACX,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,29 @@
1
+ const accessPlugin = {
2
+ id: "attachments.access-resolvers",
3
+ conventionFile: "data/attachment-access.ts",
4
+ importPrefix: "ATTACHMENT_ACCESS",
5
+ configExpr: (importName, moduleId) => `{ moduleId: '${moduleId}', resolvers: (${importName}.attachmentAccessResolvers ?? []), protectedTargets: (${importName}.protectedAttachmentTargets ?? []) }`,
6
+ outputFileName: "attachment-access.generated.ts",
7
+ buildOutput: ({ importSection, entriesLiteral }) => `// AUTO-GENERATED by mercato generate registry
8
+ import type { AttachmentAccessResolverEntry } from '@open-mercato/core/modules/attachments'
9
+ ${importSection}
10
+
11
+ export const attachmentAccessEntries: AttachmentAccessResolverEntry[] = [
12
+ ${entriesLiteral ? ` ${entriesLiteral}
13
+ ` : ""}]
14
+ `,
15
+ bootstrapRegistration: {
16
+ entriesExportName: "attachmentAccessEntries",
17
+ registrationImports: [
18
+ "import { registerAttachmentAccessResolvers } from '@open-mercato/core/modules/attachments/lib/access-registry'"
19
+ ],
20
+ buildCall: (entries) => `registerAttachmentAccessResolvers(${entries})`
21
+ }
22
+ };
23
+ const generatorPlugins = [accessPlugin];
24
+ var generators_default = generatorPlugins;
25
+ export {
26
+ generators_default as default,
27
+ generatorPlugins
28
+ };
29
+ //# sourceMappingURL=generators.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/attachments/generators.ts"],
4
+ "sourcesContent": ["import type { GeneratorPlugin } from '@open-mercato/shared/modules/generators'\n\nconst accessPlugin: GeneratorPlugin = {\n id: 'attachments.access-resolvers',\n conventionFile: 'data/attachment-access.ts',\n importPrefix: 'ATTACHMENT_ACCESS',\n configExpr: (importName, moduleId) =>\n `{ moduleId: '${moduleId}', resolvers: (${importName}.attachmentAccessResolvers ?? []), protectedTargets: (${importName}.protectedAttachmentTargets ?? []) }`,\n outputFileName: 'attachment-access.generated.ts',\n buildOutput: ({ importSection, entriesLiteral }) => `// AUTO-GENERATED by mercato generate registry\nimport type { AttachmentAccessResolverEntry } from '@open-mercato/core/modules/attachments'\n${importSection}\n\nexport const attachmentAccessEntries: AttachmentAccessResolverEntry[] = [\n${entriesLiteral ? ` ${entriesLiteral}\\n` : ''}]\n`,\n bootstrapRegistration: {\n entriesExportName: 'attachmentAccessEntries',\n registrationImports: [\n \"import { registerAttachmentAccessResolvers } from '@open-mercato/core/modules/attachments/lib/access-registry'\",\n ],\n buildCall: (entries) => `registerAttachmentAccessResolvers(${entries})`,\n },\n}\n\nexport const generatorPlugins: GeneratorPlugin[] = [accessPlugin]\nexport default generatorPlugins\n"],
5
+ "mappings": "AAEA,MAAM,eAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,YAAY,CAAC,YAAY,aACvB,gBAAgB,QAAQ,kBAAkB,UAAU,yDAAyD,UAAU;AAAA,EACzH,gBAAgB;AAAA,EAChB,aAAa,CAAC,EAAE,eAAe,eAAe,MAAM;AAAA;AAAA,EAEpD,aAAa;AAAA;AAAA;AAAA,EAGb,iBAAiB,KAAK,cAAc;AAAA,IAAO,EAAE;AAAA;AAAA,EAE7C,uBAAuB;AAAA,IACrB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,MACnB;AAAA,IACF;AAAA,IACA,WAAW,CAAC,YAAY,qCAAqC,OAAO;AAAA,EACtE;AACF;AAEO,MAAM,mBAAsC,CAAC,YAAY;AAChE,IAAO,qBAAQ;",
6
+ "names": []
7
+ }
@@ -6,7 +6,14 @@ const metadata = {
6
6
  author: "Open Mercato Team",
7
7
  license: "MIT"
8
8
  };
9
+ import { registerAttachmentAccessResolvers } from "./lib/access-registry.js";
10
+ import { syncAttachmentAccessProtection } from "./lib/access-protection.js";
11
+ import { createAttachmentAccessContext, evaluateAttachmentAccess } from "./lib/access-runner.js";
9
12
  export {
10
- metadata
13
+ createAttachmentAccessContext,
14
+ evaluateAttachmentAccess,
15
+ metadata,
16
+ registerAttachmentAccessResolvers,
17
+ syncAttachmentAccessProtection
11
18
  };
12
19
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/attachments/index.ts"],
4
- "sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'attachments',\n title: 'Attachments',\n version: '0.1.0',\n description: 'File attachments and media management.',\n author: 'Open Mercato Team',\n license: 'MIT',\n}\n\nexport {\n type AttachmentOwner,\n type AttachmentProviderCleanup,\n type AttachmentService,\n type CreatedScopedAttachment,\n type CreateScopedAttachmentInput,\n type ReadScopedAttachmentInput,\n type ReadScopedAttachmentResult,\n type ReleaseScopedAttachmentInput,\n} from './lib/attachment-service'\n"],
5
- "mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AACX;",
4
+ "sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'attachments',\n title: 'Attachments',\n version: '0.1.0',\n description: 'File attachments and media management.',\n author: 'Open Mercato Team',\n license: 'MIT',\n}\n\nexport {\n type AttachmentOwner,\n type AttachmentProviderCleanup,\n type AttachmentService,\n type CreatedScopedAttachment,\n type CreateScopedAttachmentInput,\n type ReadScopedAttachmentInput,\n type ReadScopedAttachmentResult,\n type ReleaseScopedAttachmentInput,\n} from './lib/attachment-service'\n\nexport type {\n AttachmentAccessAction,\n AttachmentAccessDecision,\n AttachmentAccessInput,\n AttachmentAccessPartition,\n AttachmentAccessRecord,\n AttachmentAccessRequirement,\n AttachmentAccessResolver,\n AttachmentAccessResolverEntry,\n AttachmentAccessSubject,\n AttachmentAccessTarget,\n ProtectedAttachmentTarget,\n} from './lib/access-types'\nexport { registerAttachmentAccessResolvers } from './lib/access-registry'\nexport { syncAttachmentAccessProtection } from './lib/access-protection'\nexport { createAttachmentAccessContext, evaluateAttachmentAccess } from './lib/access-runner'\nexport type { AttachmentAccessContext, AttachmentAccessResult } from './lib/access-runner'\n"],
5
+ "mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AACX;AA0BA,SAAS,yCAAyC;AAClD,SAAS,sCAAsC;AAC/C,SAAS,+BAA+B,gCAAgC;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,35 @@
1
+ import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
2
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
3
+ async function attachmentAccessErrorBody(status) {
4
+ const { t } = await resolveTranslations();
5
+ const key = status === 401 ? "unauthorized" : status === 404 ? "notFound" : status === 504 ? "timeout" : "forbidden";
6
+ return { error: t(`attachments.access.${key}`) };
7
+ }
8
+ async function attachmentAccessErrorResponse(decision) {
9
+ return Response.json(await attachmentAccessErrorBody(decision.status), {
10
+ status: decision.status,
11
+ headers: { "Cache-Control": "private, no-store" }
12
+ });
13
+ }
14
+ async function throwAttachmentAccessError(status) {
15
+ throw new CrudHttpError(status, await attachmentAccessErrorBody(status));
16
+ }
17
+ function withAttachmentAccessErrors(handler) {
18
+ return async (...args) => {
19
+ try {
20
+ return await handler(...args);
21
+ } catch (error) {
22
+ if (isCrudHttpError(error)) {
23
+ return Response.json(error.body, { status: error.status, headers: { "Cache-Control": "private, no-store" } });
24
+ }
25
+ throw error;
26
+ }
27
+ };
28
+ }
29
+ export {
30
+ attachmentAccessErrorBody,
31
+ attachmentAccessErrorResponse,
32
+ throwAttachmentAccessError,
33
+ withAttachmentAccessErrors
34
+ };
35
+ //# sourceMappingURL=access-errors.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-errors.ts"],
4
+ "sourcesContent": ["import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { AttachmentAccessDecision } from './access-types'\n\nexport async function attachmentAccessErrorBody(status: number): Promise<{ error: string }> {\n const { t } = await resolveTranslations()\n const key = status === 401 ? 'unauthorized' : status === 404 ? 'notFound' : status === 504 ? 'timeout' : 'forbidden'\n return { error: t(`attachments.access.${key}`) }\n}\n\nexport async function attachmentAccessErrorResponse(decision: Extract<AttachmentAccessDecision, { ok: false }>): Promise<Response> {\n return Response.json(await attachmentAccessErrorBody(decision.status), {\n status: decision.status, headers: { 'Cache-Control': 'private, no-store' },\n })\n}\n\nexport async function throwAttachmentAccessError(status: number): Promise<never> {\n throw new CrudHttpError(status, await attachmentAccessErrorBody(status))\n}\n\nexport function withAttachmentAccessErrors<Arguments extends unknown[]>(handler: (...args: Arguments) => Promise<Response>) {\n return async (...args: Arguments): Promise<Response> => {\n try {\n return await handler(...args)\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status, headers: { 'Cache-Control': 'private, no-store' } })\n }\n throw error\n }\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AAGpC,eAAsB,0BAA0B,QAA4C;AAC1F,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,MAAM,WAAW,MAAM,iBAAiB,WAAW,MAAM,aAAa,WAAW,MAAM,YAAY;AACzG,SAAO,EAAE,OAAO,EAAE,sBAAsB,GAAG,EAAE,EAAE;AACjD;AAEA,eAAsB,8BAA8B,UAA+E;AACjI,SAAO,SAAS,KAAK,MAAM,0BAA0B,SAAS,MAAM,GAAG;AAAA,IACrE,QAAQ,SAAS;AAAA,IAAQ,SAAS,EAAE,iBAAiB,oBAAoB;AAAA,EAC3E,CAAC;AACH;AAEA,eAAsB,2BAA2B,QAAgC;AAC/E,QAAM,IAAI,cAAc,QAAQ,MAAM,0BAA0B,MAAM,CAAC;AACzE;AAEO,SAAS,2BAAwD,SAAoD;AAC1H,SAAO,UAAU,SAAuC;AACtD,QAAI;AACF,aAAO,MAAM,QAAQ,GAAG,IAAI;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,gBAAgB,KAAK,GAAG;AAC1B,eAAO,SAAS,KAAK,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ,SAAS,EAAE,iBAAiB,oBAAoB,EAAE,CAAC;AAAA,MAC9G;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ import { throwAttachmentAccessError } from "./access-errors.js";
2
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
+ import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
4
+ async function prepareAttachmentMutation(input) {
5
+ if (!input.auth.tenantId) return throwAttachmentAccessError(401);
6
+ const guard = await runRouteMutationGuards({
7
+ container: input.container,
8
+ req: input.req,
9
+ auth: { tenantId: input.auth.tenantId, organizationId: input.auth.orgId ?? null, userId: input.auth.sub },
10
+ input: {
11
+ resourceKind: "attachments:attachment",
12
+ resourceId: input.recordId,
13
+ operation: input.operation,
14
+ mutationPayload: input.payload
15
+ }
16
+ });
17
+ if (!guard.ok) throw new CrudHttpError(guard.errorStatus, guard.errorBody);
18
+ return guard;
19
+ }
20
+ export {
21
+ prepareAttachmentMutation
22
+ };
23
+ //# sourceMappingURL=access-mutation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-mutation.ts"],
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { throwAttachmentAccessError } from './access-errors'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\n\nexport async function prepareAttachmentMutation(input: {\n container: AwilixContainer\n req: Request\n auth: NonNullable<AuthContext>\n recordId?: string\n operation: 'update' | 'delete'\n payload?: Record<string, unknown>\n}) {\n if (!input.auth.tenantId) return throwAttachmentAccessError(401)\n const guard = await runRouteMutationGuards({\n container: input.container, req: input.req,\n auth: { tenantId: input.auth.tenantId, organizationId: input.auth.orgId ?? null, userId: input.auth.sub },\n input: {\n resourceKind: 'attachments:attachment', resourceId: input.recordId,\n operation: input.operation, mutationPayload: input.payload,\n },\n })\n if (!guard.ok) throw new CrudHttpError(guard.errorStatus, guard.errorBody)\n return guard\n}\n"],
5
+ "mappings": "AAEA,SAAS,kCAAkC;AAC3C,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AAEvC,eAAsB,0BAA0B,OAO7C;AACD,MAAI,CAAC,MAAM,KAAK,SAAU,QAAO,2BAA2B,GAAG;AAC/D,QAAM,QAAQ,MAAM,uBAAuB;AAAA,IACzC,WAAW,MAAM;AAAA,IAAW,KAAK,MAAM;AAAA,IACvC,MAAM,EAAE,UAAU,MAAM,KAAK,UAAU,gBAAgB,MAAM,KAAK,SAAS,MAAM,QAAQ,MAAM,KAAK,IAAI;AAAA,IACxG,OAAO;AAAA,MACL,cAAc;AAAA,MAA0B,YAAY,MAAM;AAAA,MAC1D,WAAW,MAAM;AAAA,MAAW,iBAAiB,MAAM;AAAA,IACrD;AAAA,EACF,CAAC;AACD,MAAI,CAAC,MAAM,GAAI,OAAM,IAAI,cAAc,MAAM,aAAa,MAAM,SAAS;AACzE,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,73 @@
1
+ import { LockMode } from "@mikro-orm/core";
2
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { AttachmentPartition } from "../data/entities.js";
4
+ import { attachmentAccessRequirementsSchema } from "../data/validators.js";
5
+ import { getAttachmentAccessRegistry, matchesAttachmentAccessSelector } from "./access-registry.js";
6
+ function getDeclaredAttachmentAccessRequirements(partitionCode) {
7
+ const registry = getAttachmentAccessRegistry();
8
+ if (registry.invalid) throw new Error("[internal] Attachment access registry is invalid");
9
+ return registry.protectedTargets.filter((target) => matchesAttachmentAccessSelector(target.targetPartition, partitionCode)).map(({ resolverId, targetEntity }) => ({ resolverId, targetEntity }));
10
+ }
11
+ function mergeAttachmentAccessRequirements(current, required) {
12
+ const existing = attachmentAccessRequirementsSchema.parse(current ?? []);
13
+ const union = /* @__PURE__ */ new Map();
14
+ for (const requirement of [...existing, ...required]) {
15
+ union.set(JSON.stringify([requirement.resolverId, requirement.targetEntity]), { ...requirement });
16
+ }
17
+ return [...union.values()];
18
+ }
19
+ async function syncAttachmentAccessProtection(em, options = {}) {
20
+ const registry = getAttachmentAccessRegistry();
21
+ if (registry.invalid) throw new Error("[internal] Attachment access registry is invalid");
22
+ if (!registry.protectedTargets.length) return 0;
23
+ const isolated = em.fork({ clear: true, useContext: false });
24
+ const candidates = await findWithDecryption(
25
+ isolated,
26
+ AttachmentPartition,
27
+ options.partitionCode ? { code: options.partitionCode } : {},
28
+ { orderBy: { id: "asc" } },
29
+ { tenantId: null, organizationId: null }
30
+ );
31
+ if (options.partitionCode && getDeclaredAttachmentAccessRequirements(options.partitionCode).length && !candidates.length) {
32
+ throw new Error("[internal] Protected attachment partition is not durably configured");
33
+ }
34
+ const pending = candidates.filter((partition) => {
35
+ const required = getDeclaredAttachmentAccessRequirements(partition.code);
36
+ if (!required.length) return false;
37
+ const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? []);
38
+ return JSON.stringify(mergeAttachmentAccessRequirements(existing, required)) !== JSON.stringify(existing);
39
+ });
40
+ if (!pending.length) return 0;
41
+ return isolated.transactional(async (transaction) => {
42
+ let changed = 0;
43
+ for (const candidate of pending) {
44
+ const partition = await findOneWithDecryption(transaction, AttachmentPartition, { id: candidate.id }, {
45
+ lockMode: LockMode.PESSIMISTIC_WRITE,
46
+ refresh: true
47
+ }, { tenantId: null, organizationId: null });
48
+ if (!partition) throw new Error("[internal] Attachment partition disappeared during access protection synchronization");
49
+ const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? []);
50
+ const merged = mergeAttachmentAccessRequirements(existing, getDeclaredAttachmentAccessRequirements(partition.code));
51
+ if (JSON.stringify(merged) === JSON.stringify(existing)) continue;
52
+ partition.accessResolverRequirements = merged;
53
+ changed += 1;
54
+ }
55
+ if (changed) await transaction.flush();
56
+ return changed;
57
+ });
58
+ }
59
+ async function ensureAttachmentPartitionProtection(em, partition) {
60
+ const required = getDeclaredAttachmentAccessRequirements(partition.code);
61
+ if (!required.length) return;
62
+ const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? []);
63
+ const merged = mergeAttachmentAccessRequirements(existing, required);
64
+ if (JSON.stringify(existing) === JSON.stringify(merged)) return;
65
+ await syncAttachmentAccessProtection(em, { partitionCode: partition.code });
66
+ }
67
+ export {
68
+ ensureAttachmentPartitionProtection,
69
+ getDeclaredAttachmentAccessRequirements,
70
+ mergeAttachmentAccessRequirements,
71
+ syncAttachmentAccessProtection
72
+ };
73
+ //# sourceMappingURL=access-protection.js.map