@open-mercato/core 0.7.1-develop.7130.1.fef2396fd8 → 0.7.1-develop.7135.1.19bf969756
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +5 -0
- package/dist/modules/auth/api/profile/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +40 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/lib/access-control.js +6 -1
- package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
- package/dist/modules/communication_channels/lib/system-user.js +8 -3
- package/dist/modules/communication_channels/lib/system-user.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +13 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +9 -0
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/cancel/route.js +5 -0
- package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/complete/route.js +5 -0
- package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
- package/dist/modules/customers/api/labels/assign/route.js +5 -0
- package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
- package/dist/modules/customers/api/labels/route.js +5 -0
- package/dist/modules/customers/api/labels/route.js.map +2 -2
- package/dist/modules/customers/api/labels/unassign/route.js +5 -0
- package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +9 -0
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/route.js +5 -0
- package/dist/modules/customers/api/people/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +5 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/route.js +13 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
- package/dist/modules/customers/api/pipelines/route.js +13 -0
- package/dist/modules/customers/api/pipelines/route.js.map +2 -2
- package/dist/modules/customers/api/settings/address-format/route.js +5 -0
- package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js +5 -0
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js.map +2 -2
- package/dist/modules/customers/api/settings/stuck-threshold/route.js +5 -0
- package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +2 -2
- package/dist/modules/customers/api/tags/assign/route.js +5 -0
- package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
- package/dist/modules/customers/api/tags/unassign/route.js +5 -0
- package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +13 -0
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +9 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +5 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/eudr/api/plots/import/route.js.map +2 -2
- package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +5 -0
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +5 -0
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/archive/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/read/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/forward/route.js +5 -0
- package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/reply/route.js +5 -0
- package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +9 -0
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/token/[token]/route.js +5 -0
- package/dist/modules/messages/api/token/[token]/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/assign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/unassign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +5 -0
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/convert/route.js +5 -0
- package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
- package/dist/modules/sales/api/settings/document-numbers/route.js +5 -0
- package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
- package/dist/modules/sales/api/settings/order-editing/route.js +5 -0
- package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/self/route.js +5 -0
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +5 -0
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +9 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +25 -18
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +5 -0
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +16 -12
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +6 -1
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +24 -27
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js +8 -10
- package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js +4 -9
- package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +8 -3
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +8 -3
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +9 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +17 -13
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +19 -15
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/registrations/route.js +9 -4
- package/dist/modules/warranty_claims/api/registrations/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +16 -12
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +25 -19
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js +2 -1
- package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +16 -12
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +5 -0
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +5 -0
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +5 -0
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js +22 -1
- package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +5 -0
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -1
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
- package/dist/modules/warranty_claims/commands/shared.js +2 -2
- package/dist/modules/warranty_claims/commands/shared.js.map +2 -2
- package/dist/modules/warranty_claims/lib/portalAuthGuard.js +31 -0
- package/dist/modules/warranty_claims/lib/portalAuthGuard.js.map +7 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +5 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/communicationChannelsFixtures.ts +13 -0
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +5 -0
- package/src/modules/auth/api/profile/route.ts +5 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +47 -2
- package/src/modules/communication_channels/lib/access-control.ts +28 -0
- package/src/modules/communication_channels/lib/system-user.ts +40 -22
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +5 -0
- package/src/modules/customers/api/dictionaries/kind-settings/route.ts +5 -0
- package/src/modules/customers/api/interactions/cancel/route.ts +5 -0
- package/src/modules/customers/api/interactions/complete/route.ts +5 -0
- package/src/modules/customers/api/labels/assign/route.ts +5 -0
- package/src/modules/customers/api/labels/route.ts +5 -0
- package/src/modules/customers/api/labels/unassign/route.ts +5 -0
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +9 -0
- package/src/modules/customers/api/people/[id]/companies/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +13 -0
- package/src/modules/customers/api/pipelines/route.ts +13 -0
- package/src/modules/customers/api/settings/address-format/route.ts +5 -0
- package/src/modules/customers/api/settings/dictionary-sort-modes/route.ts +5 -0
- package/src/modules/customers/api/settings/stuck-threshold/route.ts +5 -0
- package/src/modules/customers/api/tags/assign/route.ts +5 -0
- package/src/modules/customers/api/tags/unassign/route.ts +5 -0
- package/src/modules/customers/api/todos/route.ts +13 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +9 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +5 -0
- package/src/modules/directory/api/organization-branding/route.ts +5 -0
- package/src/modules/eudr/api/plots/import/route.ts +7 -0
- package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +7 -0
- package/src/modules/feature_toggles/api/overrides/route.ts +5 -0
- package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/archive/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/read/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/route.ts +5 -0
- package/src/modules/messages/api/[id]/forward/route.ts +5 -0
- package/src/modules/messages/api/[id]/reply/route.ts +5 -0
- package/src/modules/messages/api/[id]/route.ts +9 -0
- package/src/modules/messages/api/token/[token]/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/assign/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/unassign/route.ts +5 -0
- package/src/modules/sales/api/quotes/accept/route.ts +5 -0
- package/src/modules/sales/api/quotes/convert/route.ts +5 -0
- package/src/modules/sales/api/settings/document-numbers/route.ts +5 -0
- package/src/modules/sales/api/settings/order-editing/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/accept/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/reject/route.ts +5 -0
- package/src/modules/staff/api/team-members/self/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/assign/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +5 -0
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +5 -0
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +9 -0
- package/src/modules/warranty_claims/api/ai/assess/route.ts +28 -17
- package/src/modules/warranty_claims/api/assign/route.ts +5 -0
- package/src/modules/warranty_claims/api/credit-memo/route.ts +19 -13
- package/src/modules/warranty_claims/api/events/route.ts +6 -1
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +29 -27
- package/src/modules/warranty_claims/api/portal/claims/[id]/route.ts +9 -10
- package/src/modules/warranty_claims/api/portal/claims/[id]/shared.ts +5 -9
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +8 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +8 -3
- package/src/modules/warranty_claims/api/portal/claims/route.ts +10 -9
- package/src/modules/warranty_claims/api/portal/events/route.ts +18 -13
- package/src/modules/warranty_claims/api/receiving/route.ts +22 -15
- package/src/modules/warranty_claims/api/registrations/route.ts +9 -3
- package/src/modules/warranty_claims/api/replacement-order/route.ts +19 -13
- package/src/modules/warranty_claims/api/return-label/route.ts +26 -17
- package/src/modules/warranty_claims/api/risk/route.ts +3 -1
- package/src/modules/warranty_claims/api/sales-return/route.ts +19 -13
- package/src/modules/warranty_claims/api/settings-general/route.ts +5 -0
- package/src/modules/warranty_claims/api/submit/route.ts +5 -0
- package/src/modules/warranty_claims/api/transition/route.ts +5 -0
- package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +23 -1
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +5 -0
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +3 -1
- package/src/modules/warranty_claims/commands/shared.ts +7 -1
- package/src/modules/warranty_claims/lib/portalAuthGuard.ts +30 -0
- package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +5 -0
|
@@ -6,7 +6,6 @@ import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-muta
|
|
|
6
6
|
import { emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
|
|
7
7
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
8
8
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
9
|
-
import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
|
|
10
9
|
import { Attachment, AttachmentPartition } from "@open-mercato/core/modules/attachments/data/entities";
|
|
11
10
|
import { buildAttachmentImageUrl, slugifyAttachmentFileName } from "@open-mercato/core/modules/attachments/lib/imageUrls";
|
|
12
11
|
import { StorageDriverFactory } from "@open-mercato/core/modules/attachments/lib/drivers";
|
|
@@ -22,6 +21,7 @@ import { WARRANTY_CLAIM_RESOURCE_KIND } from "../../../commands/shared.js";
|
|
|
22
21
|
import { CUSTOMER_VISIBLE_ATTACHMENT_TAG, isCustomerVisibleAttachment } from "../../../lib/attachmentVisibility.js";
|
|
23
22
|
import { loadPortalOwnedClaim } from "../../../lib/portalClaimAccess.js";
|
|
24
23
|
import { resolvePortalAttachmentUploadService } from "../../../lib/portalAttachmentUpload.js";
|
|
24
|
+
import { resolveLinkedCustomerAuth } from "../../../lib/portalAuthGuard.js";
|
|
25
25
|
const CLAIM_ATTACHMENT_ENTITY_ID = "warranty_claims:warranty_claim";
|
|
26
26
|
const logger = createLogger("warranty_claims").child({ route: "portal-attachments" });
|
|
27
27
|
const ATTACHMENT_ERROR_TRANSLATIONS = {
|
|
@@ -57,13 +57,9 @@ function toIso(value) {
|
|
|
57
57
|
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
58
58
|
}
|
|
59
59
|
async function resolvePortalContext(req) {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (!auth.customerEntityId) {
|
|
65
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
|
|
66
|
-
}
|
|
60
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req);
|
|
61
|
+
if (authOrResponse instanceof Response) return authOrResponse;
|
|
62
|
+
const auth = authOrResponse;
|
|
67
63
|
const container = await createRequestContainer();
|
|
68
64
|
const em = container.resolve("em");
|
|
69
65
|
return {
|
|
@@ -199,19 +195,19 @@ async function deletePortalAttachment(context, attachment) {
|
|
|
199
195
|
await dataEngine.flushOrmEntityChanges();
|
|
200
196
|
}
|
|
201
197
|
}
|
|
202
|
-
async function streamOwnedAttachment(context, attachmentId) {
|
|
198
|
+
async function streamOwnedAttachment(context, attachmentId, translate) {
|
|
203
199
|
const owned = await loadOwnedVisibleAttachment(context, attachmentId);
|
|
204
200
|
if (!owned) {
|
|
205
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
201
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
206
202
|
}
|
|
207
203
|
const { attachment } = owned;
|
|
208
204
|
const partition = await context.em.findOne(AttachmentPartition, { code: attachment.partitionCode });
|
|
209
205
|
if (!partition) {
|
|
210
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.load_failed" }, { status: 500 });
|
|
206
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.load_failed", "Failed to load warranty claim data.") }, { status: 500 });
|
|
211
207
|
}
|
|
212
208
|
const access = checkAttachmentAccess(attachmentAuth(context), attachment, partition, { requireAuthForPublic: true });
|
|
213
209
|
if (!access.ok) {
|
|
214
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
210
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
215
211
|
}
|
|
216
212
|
const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {
|
|
217
213
|
tenantId: attachment.tenantId ?? "",
|
|
@@ -221,7 +217,7 @@ async function streamOwnedAttachment(context, attachmentId) {
|
|
|
221
217
|
try {
|
|
222
218
|
buffer = (await driver.read(attachment.partitionCode, attachment.storagePath)).buffer;
|
|
223
219
|
} catch {
|
|
224
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
220
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
225
221
|
}
|
|
226
222
|
const headers = {
|
|
227
223
|
"Cache-Control": "private, max-age=60",
|
|
@@ -237,14 +233,15 @@ async function GET(req) {
|
|
|
237
233
|
const contextOrResponse = await resolvePortalContext(req);
|
|
238
234
|
if (contextOrResponse instanceof Response) return contextOrResponse;
|
|
239
235
|
const context = contextOrResponse;
|
|
236
|
+
const { translate } = await resolveTranslations();
|
|
240
237
|
const url = new URL(req.url);
|
|
241
238
|
const attachmentId = url.searchParams.get("attachmentId");
|
|
242
239
|
if (attachmentId) {
|
|
243
240
|
const attachmentIdParsed = z.string().uuid().safeParse(attachmentId);
|
|
244
241
|
if (!attachmentIdParsed.success) {
|
|
245
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
242
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
246
243
|
}
|
|
247
|
-
return streamOwnedAttachment(context, attachmentIdParsed.data);
|
|
244
|
+
return streamOwnedAttachment(context, attachmentIdParsed.data, translate);
|
|
248
245
|
}
|
|
249
246
|
const parsed = attachmentQuerySchema.safeParse({
|
|
250
247
|
claimId: url.searchParams.get("claimId") ?? void 0,
|
|
@@ -252,11 +249,11 @@ async function GET(req) {
|
|
|
252
249
|
pageSize: url.searchParams.get("pageSize") ?? void 0
|
|
253
250
|
});
|
|
254
251
|
if (!parsed.success) {
|
|
255
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
252
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
256
253
|
}
|
|
257
254
|
const claim = await loadOwnedClaim(context, parsed.data.claimId);
|
|
258
255
|
if (!claim) {
|
|
259
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
256
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
260
257
|
}
|
|
261
258
|
const filter = {
|
|
262
259
|
entityId: CLAIM_ATTACHMENT_ENTITY_ID,
|
|
@@ -296,12 +293,12 @@ async function handleUpload(req, replacement) {
|
|
|
296
293
|
const contextOrResponse = await resolvePortalContext(req);
|
|
297
294
|
if (contextOrResponse instanceof Response) return contextOrResponse;
|
|
298
295
|
const context = contextOrResponse;
|
|
296
|
+
const { t, translate } = await resolveTranslations();
|
|
299
297
|
const contentType = req.headers.get("content-type") || "";
|
|
300
298
|
if (!contentType.toLowerCase().includes("multipart/form-data")) {
|
|
301
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
299
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
302
300
|
}
|
|
303
301
|
if (!isMultipartRequestWithinUploadLimit(req.headers.get("content-length"))) {
|
|
304
|
-
const { t } = await resolveTranslations();
|
|
305
302
|
return NextResponse.json({
|
|
306
303
|
ok: false,
|
|
307
304
|
error: t("attachments.errors.maxUploadSize", "Attachment exceeds the maximum upload size.")
|
|
@@ -314,21 +311,21 @@ async function handleUpload(req, replacement) {
|
|
|
314
311
|
});
|
|
315
312
|
const parsedAttachmentId = replacement ? z.string().uuid().safeParse(form.get("attachmentId") ?? void 0) : null;
|
|
316
313
|
if (!parsed.success || replacement && !parsedAttachmentId?.success) {
|
|
317
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
314
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
318
315
|
}
|
|
319
316
|
const claim = await loadOwnedClaim(context, parsed.data.claimId);
|
|
320
317
|
if (!claim) {
|
|
321
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
318
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
322
319
|
}
|
|
323
320
|
const file = form.get("file");
|
|
324
321
|
if (!(file instanceof File)) {
|
|
325
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.portal.detail.fileRequired" }, { status: 400 });
|
|
322
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.portal.detail.fileRequired", "Choose a file before uploading.") }, { status: 400 });
|
|
326
323
|
}
|
|
327
324
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
328
325
|
const attachmentId = replacement && parsedAttachmentId?.success ? parsedAttachmentId.data : null;
|
|
329
326
|
const replacementTarget = attachmentId ? await loadOwnedVisibleAttachment(context, attachmentId) : null;
|
|
330
327
|
if (replacement && (!replacementTarget || replacementTarget.claim.id !== claim.id)) {
|
|
331
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
328
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
332
329
|
}
|
|
333
330
|
const guarded = await runPortalAttachmentGuard(req, context, claim.id, replacement ? "update" : "create", {
|
|
334
331
|
claimId: claim.id,
|
|
@@ -342,7 +339,7 @@ async function handleUpload(req, replacement) {
|
|
|
342
339
|
}
|
|
343
340
|
const uploadService = resolvePortalAttachmentUploadService(context.container);
|
|
344
341
|
if (!uploadService) {
|
|
345
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.portal.detail.attachmentError" }, { status: 503 });
|
|
342
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.portal.detail.attachmentError", "Attachment upload failed.") }, { status: 503 });
|
|
346
343
|
}
|
|
347
344
|
let attachment;
|
|
348
345
|
try {
|
|
@@ -358,7 +355,6 @@ async function handleUpload(req, replacement) {
|
|
|
358
355
|
});
|
|
359
356
|
} catch (error) {
|
|
360
357
|
if (isScopedAttachmentUploadError(error)) {
|
|
361
|
-
const { t } = await resolveTranslations();
|
|
362
358
|
const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code] ?? ["warranty_claims.portal.detail.attachmentError", "Attachment upload failed."];
|
|
363
359
|
return NextResponse.json(
|
|
364
360
|
{ ok: false, error: t(key, fallback), code: error.code },
|
|
@@ -389,16 +385,17 @@ async function DELETE(req) {
|
|
|
389
385
|
const contextOrResponse = await resolvePortalContext(req);
|
|
390
386
|
if (contextOrResponse instanceof Response) return contextOrResponse;
|
|
391
387
|
const context = contextOrResponse;
|
|
388
|
+
const { translate } = await resolveTranslations();
|
|
392
389
|
const url = new URL(req.url);
|
|
393
390
|
const parsed = deleteQuerySchema.safeParse({
|
|
394
391
|
attachmentId: url.searchParams.get("attachmentId") ?? void 0
|
|
395
392
|
});
|
|
396
393
|
if (!parsed.success) {
|
|
397
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
394
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
398
395
|
}
|
|
399
396
|
const owned = await loadOwnedVisibleAttachment(context, parsed.data.attachmentId);
|
|
400
397
|
if (!owned) {
|
|
401
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
398
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
402
399
|
}
|
|
403
400
|
const guarded = await runPortalAttachmentGuard(req, context, owned.claim.id, "delete", {
|
|
404
401
|
claimId: owned.claim.id,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/warranty_claims/api/portal/attachments/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { Attachment, AttachmentPartition } from '@open-mercato/core/modules/attachments/data/entities'\nimport { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { StorageDriverFactory } from '@open-mercato/core/modules/attachments/lib/drivers'\nimport { checkAttachmentAccess } from '@open-mercato/core/modules/attachments/lib/access'\nimport { attachmentCrudEvents, attachmentCrudIndexer } from '@open-mercato/core/modules/attachments/lib/crud'\nimport { readAttachmentMetadata } from '@open-mercato/core/modules/attachments/lib/metadata'\nimport { clearAttachmentThumbnailCache } from '@open-mercato/core/modules/attachments/lib/thumbnailCache'\nimport { isMultipartRequestWithinUploadLimit } from '@open-mercato/core/modules/attachments/lib/upload-limits'\nimport {\n isScopedAttachmentUploadError,\n type ScopedAttachmentUploadErrorCode,\n} from '@open-mercato/core/modules/attachments/lib/scoped-upload-service'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim } from '../../../data/entities'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { CUSTOMER_VISIBLE_ATTACHMENT_TAG, isCustomerVisibleAttachment } from '../../../lib/attachmentVisibility'\nimport { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'\nimport { resolvePortalAttachmentUploadService } from '../../../lib/portalAttachmentUpload'\n\nconst CLAIM_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim'\nconst logger = createLogger('warranty_claims').child({ route: 'portal-attachments' })\nconst ATTACHMENT_ERROR_TRANSLATIONS: Partial<Record<ScopedAttachmentUploadErrorCode, readonly [string, string]>> = {\n dangerous_executable: ['attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'],\n max_upload_size: ['attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'],\n active_content: ['attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.'],\n}\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n PUT: { requireAuth: false },\n DELETE: { requireAuth: false },\n}\n\nconst attachmentQuerySchema = z.object({\n claimId: z.string().uuid(),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n})\n\nconst uploadBodySchema = z.object({\n claimId: z.string().uuid(),\n file: z.string().min(1).describe('Binary file payload; supplied as multipart form-data'),\n})\n\nconst replaceBodySchema = uploadBodySchema.extend({\n attachmentId: z.string().uuid(),\n})\n\nconst deleteQuerySchema = z.object({\n attachmentId: z.string().uuid(),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n container: Awaited<ReturnType<typeof createRequestContainer>>\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n container,\n }\n}\n\nfunction attachmentAuth(context: PortalContext): NonNullable<AuthContext> {\n return {\n sub: context.auth.sub,\n tenantId: context.tenantId,\n orgId: context.organizationId,\n email: context.auth.email,\n }\n}\n\nasync function loadOwnedClaim(context: PortalContext, claimId: string): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nasync function runPortalAttachmentGuard(\n req: Request,\n context: PortalContext,\n claimId: string,\n operation: 'create' | 'update' | 'delete',\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation,\n mutationPayload,\n },\n })\n}\n\nfunction serializeAttachment(attachment: Attachment) {\n const metadata = readAttachmentMetadata(attachment.storageMetadata)\n return {\n id: attachment.id,\n url: attachment.url,\n downloadUrl: `/api/warranty_claims/portal/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,\n fileName: attachment.fileName,\n fileSize: attachment.fileSize,\n mimeType: attachment.mimeType ?? null,\n partitionCode: attachment.partitionCode,\n content: attachment.content ?? null,\n thumbnailUrl: buildAttachmentImageUrl(attachment.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(attachment.fileName),\n }),\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n createdAt: toIso(attachment.createdAt),\n }\n}\n\nasync function resolveStorageDriverFactory(context: PortalContext): Promise<StorageDriverFactory> {\n try {\n const resolved = context.container.resolve('storageDriverFactory') as StorageDriverFactory | null\n return resolved ?? new StorageDriverFactory(context.em)\n } catch {\n return new StorageDriverFactory(context.em)\n }\n}\n\nasync function loadOwnedVisibleAttachment(\n context: PortalContext,\n attachmentId: string,\n): Promise<{ attachment: Attachment; claim: WarrantyClaim } | null> {\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const attachment = await findOneWithDecryption(\n context.em,\n Attachment,\n {\n id: attachmentId,\n entityId: CLAIM_ATTACHMENT_ENTITY_ID,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n {},\n scope,\n )\n if (!attachment) return null\n const claim = await loadOwnedClaim(context, attachment.recordId)\n if (!claim) return null\n if (!isCustomerVisibleAttachment(readAttachmentMetadata(attachment.storageMetadata).tags)) return null\n return { attachment, claim }\n}\n\nasync function deletePortalAttachment(context: PortalContext, attachment: Attachment): Promise<void> {\n await context.em.remove(attachment).flush()\n await clearAttachmentThumbnailCache(attachment.partitionCode, attachment.id).catch((error) => {\n logger.error('Failed to clean portal attachment thumbnails', { err: error, attachmentId: attachment.id })\n })\n if (attachment.storagePath) {\n try {\n const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {\n tenantId: attachment.tenantId ?? context.tenantId,\n organizationId: attachment.organizationId ?? context.organizationId,\n })\n await driver.delete(attachment.partitionCode, attachment.storagePath)\n } catch (error) {\n logger.error('Failed to remove portal attachment storage', { err: error, attachmentId: attachment.id })\n }\n }\n\n let dataEngine: DataEngine | null = null\n try {\n dataEngine = context.container.resolve<DataEngine>('dataEngine') ?? null\n } catch {\n dataEngine = null\n }\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: attachment,\n identifiers: {\n id: attachment.id,\n organizationId: attachment.organizationId ?? null,\n tenantId: attachment.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n}\n\nasync function streamOwnedAttachment(context: PortalContext, attachmentId: string): Promise<Response> {\n const owned = await loadOwnedVisibleAttachment(context, attachmentId)\n if (!owned) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const { attachment } = owned\n const partition = await context.em.findOne(AttachmentPartition, { code: attachment.partitionCode })\n if (!partition) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.load_failed' }, { status: 500 })\n }\n const access = checkAttachmentAccess(attachmentAuth(context), attachment, partition, { requireAuthForPublic: true })\n if (!access.ok) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {\n tenantId: attachment.tenantId ?? '',\n organizationId: attachment.organizationId ?? '',\n })\n let buffer: Buffer\n try {\n buffer = (await driver.read(attachment.partitionCode, attachment.storagePath)).buffer\n } catch {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const headers: Record<string, string> = {\n 'Cache-Control': 'private, max-age=60',\n 'Content-Security-Policy': \"default-src 'none'; sandbox\",\n 'Content-Type': 'application/octet-stream',\n 'Content-Disposition': `attachment; filename=\"${slugifyAttachmentFileName(attachment.fileName)}\"`,\n 'X-Content-Type-Options': 'nosniff',\n }\n if (attachment.fileSize > 0) headers['Content-Length'] = String(attachment.fileSize)\n return new NextResponse(new Uint8Array(buffer), { status: 200, headers })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const attachmentId = url.searchParams.get('attachmentId')\n if (attachmentId) {\n const attachmentIdParsed = z.string().uuid().safeParse(attachmentId)\n if (!attachmentIdParsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n return streamOwnedAttachment(context, attachmentIdParsed.data)\n }\n const parsed = attachmentQuerySchema.safeParse({\n claimId: url.searchParams.get('claimId') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const filter = {\n entityId: CLAIM_ATTACHMENT_ENTITY_ID,\n recordId: claim.id,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n }\n const page = parsed.data.page ?? 1\n const pageSize = parsed.data.pageSize ?? 100\n const attachments = await findWithDecryption(\n context.em,\n Attachment,\n filter,\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const partitionCodes = Array.from(new Set(attachments.map((attachment) => attachment.partitionCode)))\n const partitions = partitionCodes.length\n ? await context.em.find(AttachmentPartition, { code: { $in: partitionCodes } })\n : []\n const partitionsByCode = new Map(partitions.map((partition) => [partition.code, partition]))\n const auth = attachmentAuth(context)\n const visible = attachments.filter((attachment) => {\n const partition = partitionsByCode.get(attachment.partitionCode)\n if (!partition) return false\n if (!isCustomerVisibleAttachment(readAttachmentMetadata(attachment.storageMetadata).tags)) return false\n return checkAttachmentAccess(auth, attachment, partition, { requireAuthForPublic: true }).ok\n })\n\n const pagedVisible = visible.slice((page - 1) * pageSize, page * pageSize)\n\n return NextResponse.json({\n items: pagedVisible.map(serializeAttachment),\n total: visible.length,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(visible.length / pageSize)),\n })\n}\n\nasync function handleUpload(req: Request, replacement: boolean): Promise<Response> {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const contentType = req.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {\n const { t } = await resolveTranslations()\n return NextResponse.json({\n ok: false,\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n\n const form = await req.formData()\n const parsed = uploadBodySchema.safeParse({\n claimId: form.get('claimId') ?? undefined,\n file: form.get('file') instanceof File ? (form.get('file') as File).name : '',\n })\n const parsedAttachmentId = replacement\n ? z.string().uuid().safeParse(form.get('attachmentId') ?? undefined)\n : null\n if (!parsed.success || (replacement && !parsedAttachmentId?.success)) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const file = form.get('file')\n if (!(file instanceof File)) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.portal.detail.fileRequired' }, { status: 400 })\n }\n const buffer = Buffer.from(await file.arrayBuffer())\n\n const attachmentId = replacement && parsedAttachmentId?.success ? parsedAttachmentId.data : null\n const replacementTarget = attachmentId\n ? await loadOwnedVisibleAttachment(context, attachmentId)\n : null\n if (replacement && (!replacementTarget || replacementTarget.claim.id !== claim.id)) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n\n const guarded = await runPortalAttachmentGuard(req, context, claim.id, replacement ? 'update' : 'create', {\n claimId: claim.id,\n attachmentId,\n fileName: file.name,\n fileSize: file.size,\n mimeType: file.type,\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const uploadService = resolvePortalAttachmentUploadService(context.container)\n if (!uploadService) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.portal.detail.attachmentError' }, { status: 503 })\n }\n\n let attachment: Attachment\n try {\n attachment = await uploadService.upload({\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n entityId: CLAIM_ATTACHMENT_ENTITY_ID,\n recordId: claim.id,\n fileName: file.name,\n buffer,\n declaredMimeType: file.type,\n tags: [CUSTOMER_VISIBLE_ATTACHMENT_TAG],\n })\n } catch (error) {\n if (isScopedAttachmentUploadError(error)) {\n const { t } = await resolveTranslations()\n const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code]\n ?? ['warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.']\n return NextResponse.json(\n { ok: false, error: t(key, fallback), code: error.code },\n { status: error.status },\n )\n }\n throw error\n }\n\n if (replacementTarget) {\n await deletePortalAttachment(context, replacementTarget.attachment)\n }\n await guarded.runAfterSuccess()\n\n return NextResponse.json({\n ok: true,\n item: {\n ...serializeAttachment(attachment),\n entityType: 'attachments:attachment',\n },\n })\n}\n\nexport async function POST(req: Request) {\n return handleUpload(req, false)\n}\n\nexport async function PUT(req: Request) {\n return handleUpload(req, true)\n}\n\nexport async function DELETE(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const parsed = deleteQuerySchema.safeParse({\n attachmentId: url.searchParams.get('attachmentId') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const owned = await loadOwnedVisibleAttachment(context, parsed.data.attachmentId)\n if (!owned) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const guarded = await runPortalAttachmentGuard(req, context, owned.claim.id, 'delete', {\n claimId: owned.claim.id,\n attachmentId: owned.attachment.id,\n })\n if (!guarded.ok) return guarded.response\n\n await deletePortalAttachment(context, owned.attachment)\n await guarded.runAfterSuccess()\n return NextResponse.json({ ok: true })\n}\n\nconst assignmentSchema = z.object({\n type: z.string(),\n id: z.string(),\n href: z.string().nullable().optional(),\n label: z.string().nullable().optional(),\n})\n\nconst attachmentItemSchema = z.object({\n id: z.string(),\n url: z.string(),\n downloadUrl: z.string(),\n fileName: z.string(),\n fileSize: z.number().int().nonnegative(),\n mimeType: z.string().nullable(),\n partitionCode: z.string(),\n content: z.string().nullable(),\n thumbnailUrl: z.string(),\n tags: z.array(z.string()),\n assignments: z.array(assignmentSchema),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim attachments',\n methods: {\n GET: {\n summary: 'List attachments for an owned claim',\n query: attachmentQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Claim attachments',\n schema: z.object({ items: z.array(attachmentItemSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Upload an attachment for an owned claim',\n requestBody: { contentType: 'multipart/form-data', schema: uploadBodySchema },\n responses: [\n {\n status: 200,\n description: 'Attachment uploaded',\n schema: z.object({ ok: z.boolean(), item: attachmentItemSchema.extend({ entityType: z.string() }) }),\n },\n ],\n },\n PUT: {\n summary: 'Replace an attachment on an owned claim',\n requestBody: { contentType: 'multipart/form-data', schema: replaceBodySchema },\n responses: [\n {\n status: 200,\n description: 'Attachment replaced',\n schema: z.object({ ok: z.boolean(), item: attachmentItemSchema.extend({ entityType: z.string() }) }),\n },\n ],\n },\n DELETE: {\n summary: 'Delete an attachment from an owned claim',\n query: deleteQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Attachment deleted',\n schema: z.object({ ok: z.literal(true) }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { Attachment, AttachmentPartition } from '@open-mercato/core/modules/attachments/data/entities'\nimport { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { StorageDriverFactory } from '@open-mercato/core/modules/attachments/lib/drivers'\nimport { checkAttachmentAccess } from '@open-mercato/core/modules/attachments/lib/access'\nimport { attachmentCrudEvents, attachmentCrudIndexer } from '@open-mercato/core/modules/attachments/lib/crud'\nimport { readAttachmentMetadata } from '@open-mercato/core/modules/attachments/lib/metadata'\nimport { clearAttachmentThumbnailCache } from '@open-mercato/core/modules/attachments/lib/thumbnailCache'\nimport { isMultipartRequestWithinUploadLimit } from '@open-mercato/core/modules/attachments/lib/upload-limits'\nimport {\n isScopedAttachmentUploadError,\n type ScopedAttachmentUploadErrorCode,\n} from '@open-mercato/core/modules/attachments/lib/scoped-upload-service'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim } from '../../../data/entities'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { CUSTOMER_VISIBLE_ATTACHMENT_TAG, isCustomerVisibleAttachment } from '../../../lib/attachmentVisibility'\nimport { loadPortalOwnedClaim } from '../../../lib/portalClaimAccess'\nimport { resolvePortalAttachmentUploadService } from '../../../lib/portalAttachmentUpload'\nimport { resolveLinkedCustomerAuth } from '../../../lib/portalAuthGuard'\n\nconst CLAIM_ATTACHMENT_ENTITY_ID = 'warranty_claims:warranty_claim'\nconst logger = createLogger('warranty_claims').child({ route: 'portal-attachments' })\nconst ATTACHMENT_ERROR_TRANSLATIONS: Partial<Record<ScopedAttachmentUploadErrorCode, readonly [string, string]>> = {\n dangerous_executable: ['attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'],\n max_upload_size: ['attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'],\n active_content: ['attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.'],\n}\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n PUT: { requireAuth: false },\n DELETE: { requireAuth: false },\n}\n\nconst attachmentQuerySchema = z.object({\n claimId: z.string().uuid(),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n})\n\nconst uploadBodySchema = z.object({\n claimId: z.string().uuid(),\n file: z.string().min(1).describe('Binary file payload; supplied as multipart form-data'),\n})\n\nconst replaceBodySchema = uploadBodySchema.extend({\n attachmentId: z.string().uuid(),\n})\n\nconst deleteQuerySchema = z.object({\n attachmentId: z.string().uuid(),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n container: Awaited<ReturnType<typeof createRequestContainer>>\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n container,\n }\n}\n\nfunction attachmentAuth(context: PortalContext): NonNullable<AuthContext> {\n return {\n sub: context.auth.sub,\n tenantId: context.tenantId,\n orgId: context.organizationId,\n email: context.auth.email,\n }\n}\n\nasync function loadOwnedClaim(context: PortalContext, claimId: string): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nasync function runPortalAttachmentGuard(\n req: Request,\n context: PortalContext,\n claimId: string,\n operation: 'create' | 'update' | 'delete',\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation,\n mutationPayload,\n },\n })\n}\n\nfunction serializeAttachment(attachment: Attachment) {\n const metadata = readAttachmentMetadata(attachment.storageMetadata)\n return {\n id: attachment.id,\n url: attachment.url,\n downloadUrl: `/api/warranty_claims/portal/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,\n fileName: attachment.fileName,\n fileSize: attachment.fileSize,\n mimeType: attachment.mimeType ?? null,\n partitionCode: attachment.partitionCode,\n content: attachment.content ?? null,\n thumbnailUrl: buildAttachmentImageUrl(attachment.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(attachment.fileName),\n }),\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n createdAt: toIso(attachment.createdAt),\n }\n}\n\nasync function resolveStorageDriverFactory(context: PortalContext): Promise<StorageDriverFactory> {\n try {\n const resolved = context.container.resolve('storageDriverFactory') as StorageDriverFactory | null\n return resolved ?? new StorageDriverFactory(context.em)\n } catch {\n return new StorageDriverFactory(context.em)\n }\n}\n\nasync function loadOwnedVisibleAttachment(\n context: PortalContext,\n attachmentId: string,\n): Promise<{ attachment: Attachment; claim: WarrantyClaim } | null> {\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const attachment = await findOneWithDecryption(\n context.em,\n Attachment,\n {\n id: attachmentId,\n entityId: CLAIM_ATTACHMENT_ENTITY_ID,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n },\n {},\n scope,\n )\n if (!attachment) return null\n const claim = await loadOwnedClaim(context, attachment.recordId)\n if (!claim) return null\n if (!isCustomerVisibleAttachment(readAttachmentMetadata(attachment.storageMetadata).tags)) return null\n return { attachment, claim }\n}\n\nasync function deletePortalAttachment(context: PortalContext, attachment: Attachment): Promise<void> {\n await context.em.remove(attachment).flush()\n await clearAttachmentThumbnailCache(attachment.partitionCode, attachment.id).catch((error) => {\n logger.error('Failed to clean portal attachment thumbnails', { err: error, attachmentId: attachment.id })\n })\n if (attachment.storagePath) {\n try {\n const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {\n tenantId: attachment.tenantId ?? context.tenantId,\n organizationId: attachment.organizationId ?? context.organizationId,\n })\n await driver.delete(attachment.partitionCode, attachment.storagePath)\n } catch (error) {\n logger.error('Failed to remove portal attachment storage', { err: error, attachmentId: attachment.id })\n }\n }\n\n let dataEngine: DataEngine | null = null\n try {\n dataEngine = context.container.resolve<DataEngine>('dataEngine') ?? null\n } catch {\n dataEngine = null\n }\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: attachment,\n identifiers: {\n id: attachment.id,\n organizationId: attachment.organizationId ?? null,\n tenantId: attachment.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n}\n\nasync function streamOwnedAttachment(\n context: PortalContext,\n attachmentId: string,\n translate: (key: string, fallback?: string) => string,\n): Promise<Response> {\n const owned = await loadOwnedVisibleAttachment(context, attachmentId)\n if (!owned) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const { attachment } = owned\n const partition = await context.em.findOne(AttachmentPartition, { code: attachment.partitionCode })\n if (!partition) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data.') }, { status: 500 })\n }\n const access = checkAttachmentAccess(attachmentAuth(context), attachment, partition, { requireAuthForPublic: true })\n if (!access.ok) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const driver = await (await resolveStorageDriverFactory(context)).resolveForPartition(attachment.partitionCode, {\n tenantId: attachment.tenantId ?? '',\n organizationId: attachment.organizationId ?? '',\n })\n let buffer: Buffer\n try {\n buffer = (await driver.read(attachment.partitionCode, attachment.storagePath)).buffer\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const headers: Record<string, string> = {\n 'Cache-Control': 'private, max-age=60',\n 'Content-Security-Policy': \"default-src 'none'; sandbox\",\n 'Content-Type': 'application/octet-stream',\n 'Content-Disposition': `attachment; filename=\"${slugifyAttachmentFileName(attachment.fileName)}\"`,\n 'X-Content-Type-Options': 'nosniff',\n }\n if (attachment.fileSize > 0) headers['Content-Length'] = String(attachment.fileSize)\n return new NextResponse(new Uint8Array(buffer), { status: 200, headers })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const url = new URL(req.url)\n const attachmentId = url.searchParams.get('attachmentId')\n if (attachmentId) {\n const attachmentIdParsed = z.string().uuid().safeParse(attachmentId)\n if (!attachmentIdParsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n return streamOwnedAttachment(context, attachmentIdParsed.data, translate)\n }\n const parsed = attachmentQuerySchema.safeParse({\n claimId: url.searchParams.get('claimId') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const filter = {\n entityId: CLAIM_ATTACHMENT_ENTITY_ID,\n recordId: claim.id,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n }\n const page = parsed.data.page ?? 1\n const pageSize = parsed.data.pageSize ?? 100\n const attachments = await findWithDecryption(\n context.em,\n Attachment,\n filter,\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const partitionCodes = Array.from(new Set(attachments.map((attachment) => attachment.partitionCode)))\n const partitions = partitionCodes.length\n ? await context.em.find(AttachmentPartition, { code: { $in: partitionCodes } })\n : []\n const partitionsByCode = new Map(partitions.map((partition) => [partition.code, partition]))\n const auth = attachmentAuth(context)\n const visible = attachments.filter((attachment) => {\n const partition = partitionsByCode.get(attachment.partitionCode)\n if (!partition) return false\n if (!isCustomerVisibleAttachment(readAttachmentMetadata(attachment.storageMetadata).tags)) return false\n return checkAttachmentAccess(auth, attachment, partition, { requireAuthForPublic: true }).ok\n })\n\n const pagedVisible = visible.slice((page - 1) * pageSize, page * pageSize)\n\n return NextResponse.json({\n items: pagedVisible.map(serializeAttachment),\n total: visible.length,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(visible.length / pageSize)),\n })\n}\n\nasync function handleUpload(req: Request, replacement: boolean): Promise<Response> {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { t, translate } = await resolveTranslations()\n const contentType = req.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {\n return NextResponse.json({\n ok: false,\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n\n const form = await req.formData()\n const parsed = uploadBodySchema.safeParse({\n claimId: form.get('claimId') ?? undefined,\n file: form.get('file') instanceof File ? (form.get('file') as File).name : '',\n })\n const parsedAttachmentId = replacement\n ? z.string().uuid().safeParse(form.get('attachmentId') ?? undefined)\n : null\n if (!parsed.success || (replacement && !parsedAttachmentId?.success)) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const claim = await loadOwnedClaim(context, parsed.data.claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const file = form.get('file')\n if (!(file instanceof File)) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.portal.detail.fileRequired', 'Choose a file before uploading.') }, { status: 400 })\n }\n const buffer = Buffer.from(await file.arrayBuffer())\n\n const attachmentId = replacement && parsedAttachmentId?.success ? parsedAttachmentId.data : null\n const replacementTarget = attachmentId\n ? await loadOwnedVisibleAttachment(context, attachmentId)\n : null\n if (replacement && (!replacementTarget || replacementTarget.claim.id !== claim.id)) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n\n const guarded = await runPortalAttachmentGuard(req, context, claim.id, replacement ? 'update' : 'create', {\n claimId: claim.id,\n attachmentId,\n fileName: file.name,\n fileSize: file.size,\n mimeType: file.type,\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const uploadService = resolvePortalAttachmentUploadService(context.container)\n if (!uploadService) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.') }, { status: 503 })\n }\n\n let attachment: Attachment\n try {\n attachment = await uploadService.upload({\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n entityId: CLAIM_ATTACHMENT_ENTITY_ID,\n recordId: claim.id,\n fileName: file.name,\n buffer,\n declaredMimeType: file.type,\n tags: [CUSTOMER_VISIBLE_ATTACHMENT_TAG],\n })\n } catch (error) {\n if (isScopedAttachmentUploadError(error)) {\n const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code]\n ?? ['warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.']\n return NextResponse.json(\n { ok: false, error: t(key, fallback), code: error.code },\n { status: error.status },\n )\n }\n throw error\n }\n\n if (replacementTarget) {\n await deletePortalAttachment(context, replacementTarget.attachment)\n }\n await guarded.runAfterSuccess()\n\n return NextResponse.json({\n ok: true,\n item: {\n ...serializeAttachment(attachment),\n entityType: 'attachments:attachment',\n },\n })\n}\n\nexport async function POST(req: Request) {\n return handleUpload(req, false)\n}\n\nexport async function PUT(req: Request) {\n return handleUpload(req, true)\n}\n\nexport async function DELETE(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const url = new URL(req.url)\n const parsed = deleteQuerySchema.safeParse({\n attachmentId: url.searchParams.get('attachmentId') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const owned = await loadOwnedVisibleAttachment(context, parsed.data.attachmentId)\n if (!owned) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const guarded = await runPortalAttachmentGuard(req, context, owned.claim.id, 'delete', {\n claimId: owned.claim.id,\n attachmentId: owned.attachment.id,\n })\n if (!guarded.ok) return guarded.response\n\n await deletePortalAttachment(context, owned.attachment)\n await guarded.runAfterSuccess()\n return NextResponse.json({ ok: true })\n}\n\nconst assignmentSchema = z.object({\n type: z.string(),\n id: z.string(),\n href: z.string().nullable().optional(),\n label: z.string().nullable().optional(),\n})\n\nconst attachmentItemSchema = z.object({\n id: z.string(),\n url: z.string(),\n downloadUrl: z.string(),\n fileName: z.string(),\n fileSize: z.number().int().nonnegative(),\n mimeType: z.string().nullable(),\n partitionCode: z.string(),\n content: z.string().nullable(),\n thumbnailUrl: z.string(),\n tags: z.array(z.string()),\n assignments: z.array(assignmentSchema),\n createdAt: z.string().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim attachments',\n methods: {\n GET: {\n summary: 'List attachments for an owned claim',\n query: attachmentQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Claim attachments',\n schema: z.object({ items: z.array(attachmentItemSchema) }),\n },\n ],\n },\n POST: {\n summary: 'Upload an attachment for an owned claim',\n requestBody: { contentType: 'multipart/form-data', schema: uploadBodySchema },\n responses: [\n {\n status: 200,\n description: 'Attachment uploaded',\n schema: z.object({ ok: z.boolean(), item: attachmentItemSchema.extend({ entityType: z.string() }) }),\n },\n ],\n },\n PUT: {\n summary: 'Replace an attachment on an owned claim',\n requestBody: { contentType: 'multipart/form-data', schema: replaceBodySchema },\n responses: [\n {\n status: 200,\n description: 'Attachment replaced',\n schema: z.object({ ok: z.boolean(), item: attachmentItemSchema.extend({ entityType: z.string() }) }),\n },\n ],\n },\n DELETE: {\n summary: 'Delete an attachment from an owned claim',\n query: deleteQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Attachment deleted',\n schema: z.object({ ok: z.literal(true) }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["metadata"]
|
|
7
7
|
}
|
|
@@ -2,9 +2,10 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
6
|
import { WarrantyClaimLine } from "../../../../data/entities.js";
|
|
7
7
|
import { loadPortalOwnedClaim } from "../../../../lib/portalClaimAccess.js";
|
|
8
|
+
import { resolveLinkedCustomerAuth } from "../../../../lib/portalAuthGuard.js";
|
|
8
9
|
const metadata = {
|
|
9
10
|
GET: { requireAuth: false }
|
|
10
11
|
};
|
|
@@ -23,13 +24,9 @@ async function resolveClaimId(ctx) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
async function resolvePortalContext(req) {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
if (!auth.customerEntityId) {
|
|
31
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
|
|
32
|
-
}
|
|
27
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req);
|
|
28
|
+
if (authOrResponse instanceof Response) return authOrResponse;
|
|
29
|
+
const auth = authOrResponse;
|
|
33
30
|
const container = await createRequestContainer();
|
|
34
31
|
const em = container.resolve("em");
|
|
35
32
|
return {
|
|
@@ -88,9 +85,10 @@ async function GET(req, ctx) {
|
|
|
88
85
|
const contextOrResponse = await resolvePortalContext(req);
|
|
89
86
|
if (contextOrResponse instanceof Response) return contextOrResponse;
|
|
90
87
|
const context = contextOrResponse;
|
|
88
|
+
const { translate } = await resolveTranslations();
|
|
91
89
|
const claimId = await resolveClaimId(ctx);
|
|
92
90
|
if (!claimId) {
|
|
93
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
91
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
94
92
|
}
|
|
95
93
|
const scope = { tenantId: context.tenantId, organizationId: context.organizationId };
|
|
96
94
|
const claim = await loadPortalOwnedClaim(
|
|
@@ -103,7 +101,7 @@ async function GET(req, ctx) {
|
|
|
103
101
|
claimId
|
|
104
102
|
);
|
|
105
103
|
if (!claim) {
|
|
106
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
104
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
107
105
|
}
|
|
108
106
|
const lines = await findWithDecryption(
|
|
109
107
|
context.em,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../../data/entities'\nimport { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'\nimport { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'\n\nexport const metadata = {\n GET: { requireAuth: false },\n}\n\ntype RouteParams = { id: string }\ntype RouteContext = { params: Promise<RouteParams> }\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nasync function resolveClaimId(ctx: RouteContext): Promise<string | null> {\n try {\n const params = await ctx.params\n return typeof params.id === 'string' && params.id.trim().length ? params.id.trim() : null\n } catch {\n return null\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n }\n}\n\nfunction serializeLine(line: WarrantyClaimLine) {\n return {\n id: line.id,\n lineNo: line.lineNo,\n productId: line.productId ?? null,\n sku: line.sku ?? null,\n productName: line.productName ?? null,\n orderLineId: line.orderLineId ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription ?? null,\n qtyClaimed: line.qtyClaimed,\n qtyApproved: line.qtyApproved ?? null,\n disposition: line.disposition ?? null,\n lineStatus: line.lineStatus,\n creditAmount: line.creditAmount ?? null,\n createdAt: toIso(line.createdAt),\n updatedAt: toIso(line.updatedAt),\n }\n}\n\nfunction serializeClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n channel: claim.channel,\n priority: claim.priority,\n customerId: claim.customerId ?? null,\n customerName: claim.customerName ?? null,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n rejectionReasonCode: claim.rejectionReasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n slaDueAt: toIso(claim.slaDueAt),\n submittedAt: toIso(claim.submittedAt),\n resolvedAt: toIso(claim.resolvedAt),\n closedAt: toIso(claim.closedAt),\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: lines.map(serializeLine),\n }\n}\n\nexport async function GET(req: Request, ctx: RouteContext) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const claimId = await resolveClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const claim = await loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: claim.id, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n { orderBy: { lineNo: 'ASC' } },\n scope,\n )\n return NextResponse.json({ item: serializeClaim(claim, lines) })\n}\n\nconst lineSchema = z.object({\n id: z.string().uuid(),\n lineNo: z.number().int(),\n productId: z.string().uuid().nullable(),\n sku: z.string().nullable(),\n productName: z.string().nullable(),\n orderLineId: z.string().uuid().nullable(),\n serialNumber: z.string().nullable(),\n faultCode: z.string().nullable(),\n faultDescription: z.string().nullable(),\n qtyClaimed: z.string(),\n qtyApproved: z.string().nullable(),\n disposition: z.string().nullable(),\n lineStatus: z.string(),\n creditAmount: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n})\n\nconst claimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n channel: z.string(),\n priority: z.string(),\n customerId: z.string().uuid().nullable(),\n customerName: z.string().nullable(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n rejectionReasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n slaDueAt: z.string().nullable(),\n submittedAt: z.string().nullable(),\n resolvedAt: z.string().nullable(),\n closedAt: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: z.array(lineSchema),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim detail',\n methods: {\n GET: {\n summary: 'Read one authenticated customer claim',\n responses: [\n {\n status: 200,\n description: 'Customer claim detail',\n schema: z.object({ item: claimSchema }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
3
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
5
|
-
import { getCustomerAuthFromRequest } from "@open-mercato/core/modules/customer_accounts/lib/customerAuth";
|
|
6
4
|
import { WARRANTY_CLAIM_RESOURCE_KIND } from "../../../../commands/shared.js";
|
|
7
5
|
import { loadPortalOwnedClaim } from "../../../../lib/portalClaimAccess.js";
|
|
6
|
+
import { resolveLinkedCustomerAuth } from "../../../../lib/portalAuthGuard.js";
|
|
8
7
|
const claimIdParamSchema = z.string().uuid();
|
|
9
8
|
async function resolvePortalClaimId(ctx) {
|
|
10
9
|
try {
|
|
@@ -16,13 +15,9 @@ async function resolvePortalClaimId(ctx) {
|
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
async function resolvePortalActionContext(req) {
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
if (!auth.customerEntityId) {
|
|
24
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
|
|
25
|
-
}
|
|
18
|
+
const authOrResponse = await resolveLinkedCustomerAuth(req);
|
|
19
|
+
if (authOrResponse instanceof Response) return authOrResponse;
|
|
20
|
+
const auth = authOrResponse;
|
|
26
21
|
const container = await createRequestContainer();
|
|
27
22
|
const em = container.resolve("em");
|
|
28
23
|
const commandAuth = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/shared.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { WarrantyClaim } from '../../../../data/entities'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../../commands/shared'\nimport { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'\nimport { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'\n\nexport type PortalClaimActionRouteContext = { params: Promise<{ id: string }> }\n\nexport type PortalClaimActionContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\nconst claimIdParamSchema = z.string().uuid()\n\nexport async function resolvePortalClaimId(ctx: PortalClaimActionRouteContext): Promise<string | null> {\n try {\n const params = await ctx.params\n const parsed = claimIdParamSchema.safeParse(typeof params.id === 'string' ? params.id.trim() : params.id)\n return parsed.success ? parsed.data : null\n } catch {\n return null\n }\n}\n\nexport async function resolvePortalActionContext(req: Request): Promise<PortalClaimActionContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\nexport async function loadOwnedClaim(\n context: PortalClaimActionContext,\n claimId: string,\n): Promise<WarrantyClaim | null> {\n return loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nexport async function loadOwnedClaimFresh(\n context: PortalClaimActionContext,\n claimId: string,\n): Promise<WarrantyClaim | null> {\n const em = context.em.fork()\n return loadPortalOwnedClaim(\n em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n}\n\nexport async function runPortalClaimActionGuard(\n req: Request,\n context: PortalClaimActionContext,\n claimId: string,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: claimId,\n operation: 'custom',\n mutationPayload,\n },\n })\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAGlB,SAAS,8BAA8B;AAEvC,SAAS,8BAA6D;AAGtE,SAAS,oCAAoC;AAC7C,SAAS,4BAA4B;AACrC,SAAS,iCAAiC;AAa1C,MAAM,qBAAqB,EAAE,OAAO,EAAE,KAAK;AAE3C,eAAsB,qBAAqB,KAA4D;AACrG,MAAI;AACF,UAAM,SAAS,MAAM,IAAI;AACzB,UAAM,SAAS,mBAAmB,UAAU,OAAO,OAAO,OAAO,WAAW,OAAO,GAAG,KAAK,IAAI,OAAO,EAAE;AACxG,WAAO,OAAO,UAAU,OAAO,OAAO;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,2BAA2B,KAA4D;AAC3G,QAAM,iBAAiB,MAAM,0BAA0B,GAAG;AAC1D,MAAI,0BAA0B,SAAU,QAAO;AAC/C,QAAM,OAAO;AACb,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,cAAwC;AAAA,IAC5C,KAAK,KAAK;AAAA,IACV,KAAK,KAAK;AAAA,IACV,UAAU,KAAK;AAAA,IACf,OAAO,KAAK;AAAA,IACZ,OAAO,KAAK;AAAA,IACZ,kBAAkB,KAAK,oBAAoB;AAAA,IAC3C,gBAAgB,KAAK,kBAAkB;AAAA,EACzC;AACA,SAAO;AAAA,IACL;AAAA,IACA,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,IACrB;AAAA,IACA,YAAY;AAAA,MACV;AAAA,MACA,MAAM;AAAA,MACN,mBAAmB;AAAA,MACnB,wBAAwB,KAAK;AAAA,MAC7B,iBAAiB,CAAC,KAAK,KAAK;AAAA,MAC5B,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,eAAsB,eACpB,SACA,SAC+B;AAC/B,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,MACE,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,YAAY,QAAQ;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,oBACpB,SACA,SAC+B;AAC/B,QAAM,KAAK,QAAQ,GAAG,KAAK;AAC3B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,YAAY,QAAQ;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,0BACpB,KACA,SACA,SACA,iBACmC;AACnC,SAAO,uBAAuB;AAAA,IAC5B,WAAW,QAAQ,WAAW;AAAA,IAC9B;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,QAAQ,KAAK;AAAA,MACrB,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,cAAc,CAAC;AAAA,IACjB;AAAA,IACA,OAAO;AAAA,MACL,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,IACF;AAAA,EACF,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
4
5
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
6
|
import {
|
|
6
7
|
loadOwnedClaim,
|
|
@@ -16,16 +17,16 @@ async function POST(req, ctx) {
|
|
|
16
17
|
const contextOrResponse = await resolvePortalActionContext(req);
|
|
17
18
|
if (contextOrResponse instanceof Response) return contextOrResponse;
|
|
18
19
|
const context = contextOrResponse;
|
|
20
|
+
const { translate } = await resolveTranslations();
|
|
19
21
|
const claimId = await resolvePortalClaimId(ctx);
|
|
20
22
|
if (!claimId) {
|
|
21
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
23
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
22
24
|
}
|
|
23
25
|
const claim = await loadOwnedClaim(context, claimId);
|
|
24
26
|
if (!claim) {
|
|
25
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.notFound" }, { status: 404 });
|
|
27
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.notFound", "Claim not found.") }, { status: 404 });
|
|
26
28
|
}
|
|
27
29
|
if (claim.status !== "draft") {
|
|
28
|
-
const { translate } = await resolveTranslations();
|
|
29
30
|
return NextResponse.json(
|
|
30
31
|
{ ok: false, error: translate("warranty_claims.errors.invalidTransition", "That status change is not allowed.") },
|
|
31
32
|
{ status: 400 }
|
|
@@ -48,6 +49,10 @@ async function POST(req, ctx) {
|
|
|
48
49
|
if (isCrudHttpError(err)) {
|
|
49
50
|
return NextResponse.json(err.body, { status: err.status });
|
|
50
51
|
}
|
|
52
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
53
|
+
if (interceptorRejection) {
|
|
54
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
55
|
+
}
|
|
51
56
|
throw err;
|
|
52
57
|
}
|
|
53
58
|
await guarded.runAfterSuccess();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/submit/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n loadOwnedClaim,\n loadOwnedClaimFresh,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.notFound' }, { status: 404 })\n }\n if (claim.status !== 'draft') {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AAEpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n loadOwnedClaim,\n loadOwnedClaimFresh,\n resolvePortalActionContext,\n resolvePortalClaimId,\n runPortalClaimActionGuard,\n type PortalClaimActionRouteContext,\n} from '../shared'\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nexport async function POST(req: Request, ctx: PortalClaimActionRouteContext) {\n const contextOrResponse = await resolvePortalActionContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const claimId = await resolvePortalClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const claim = await loadOwnedClaim(context, claimId)\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n if (claim.status !== 'draft') {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.invalidTransition', 'That status change is not allowed.') },\n { status: 400 },\n )\n }\n\n const guarded = await runPortalClaimActionGuard(req, context, claim.id, {\n id: claim.id,\n action: 'submit',\n })\n if (!guarded.ok) {\n return guarded.response\n }\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claim.id, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n throw err\n }\n\n await guarded.runAfterSuccess()\n\n const updated = await loadOwnedClaimFresh(context, claim.id)\n return NextResponse.json({ ok: true, claimId: claim.id, status: updated?.status ?? 'submitted' })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim submit action',\n methods: {\n POST: {\n summary: 'Submit an owned draft claim from the customer portal',\n responses: [\n {\n status: 200,\n description: 'Claim submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid(), status: z.string() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B;AAEpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,eAAsB,KAAK,KAAc,KAAoC;AAC3E,QAAM,oBAAoB,MAAM,2BAA2B,GAAG;AAC9D,MAAI,6BAA6B,SAAU,QAAO;AAClD,QAAM,UAAU;AAChB,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAM,UAAU,MAAM,qBAAqB,GAAG;AAC9C,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,UAAU,mCAAmC,kBAAkB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClI;AACA,QAAM,QAAQ,MAAM,eAAe,SAAS,OAAO;AACnD,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,UAAU,mCAAmC,kBAAkB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClI;AACA,MAAI,MAAM,WAAW,SAAS;AAC5B,WAAO,aAAa;AAAA,MAClB,EAAE,IAAI,OAAO,OAAO,UAAU,4CAA4C,oCAAoC,EAAE;AAAA,MAChH,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,0BAA0B,KAAK,SAAS,MAAM,IAAI;AAAA,IACtE,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,CAAC,QAAQ,IAAI;AACf,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,aAAa,QAAQ,WAAW,UAAU,QAAQ,YAAY;AACpE,MAAI;AACF,UAAM,WAAW;AAAA,MACf;AAAA,MACA,EAAE,OAAO,EAAE,IAAI,MAAM,IAAI,iBAAiB,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW;AAAA,IAC1F;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,uBAAuB,mCAAmC,GAAG;AACnE,QAAI,sBAAsB;AACxB,aAAO,aAAa,KAAK,qBAAqB,MAAM,EAAE,QAAQ,qBAAqB,OAAO,CAAC;AAAA,IAC7F;AACA,UAAM;AAAA,EACR;AAEA,QAAM,QAAQ,gBAAgB;AAE9B,QAAM,UAAU,MAAM,oBAAoB,SAAS,MAAM,EAAE;AAC3D,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,QAAQ,SAAS,UAAU,YAAY,CAAC;AAClG;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,OAAO,EAAE,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|