@open-mercato/core 0.6.2-develop.3330.1.8ad2b3b84c → 0.6.2-develop.3366.1.4692954e44
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 +2 -2
- package/dist/generated/entities/sync_excel_upload/index.js +37 -0
- package/dist/generated/entities/sync_excel_upload/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +4 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +19 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/customers/search.js +3 -2
- package/dist/modules/customers/search.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +16 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/di.js +2 -1
- package/dist/modules/data_sync/di.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +178 -54
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +23 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +3 -3
- package/dist/modules/integrations/backend/integrations/[id]/page.js +241 -55
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +3 -3
- package/dist/modules/sync_excel/acl.js +10 -0
- package/dist/modules/sync_excel/acl.js.map +7 -0
- package/dist/modules/sync_excel/api/import/route.js +164 -0
- package/dist/modules/sync_excel/api/import/route.js.map +7 -0
- package/dist/modules/sync_excel/api/preview/route.js +84 -0
- package/dist/modules/sync_excel/api/preview/route.js.map +7 -0
- package/dist/modules/sync_excel/api/upload/route.js +131 -0
- package/dist/modules/sync_excel/api/upload/route.js.map +7 -0
- package/dist/modules/sync_excel/data/entities.js +79 -0
- package/dist/modules/sync_excel/data/entities.js.map +7 -0
- package/dist/modules/sync_excel/data/validators.js +58 -0
- package/dist/modules/sync_excel/data/validators.js.map +7 -0
- package/dist/modules/sync_excel/di.js +11 -0
- package/dist/modules/sync_excel/di.js.map +7 -0
- package/dist/modules/sync_excel/index.js +11 -0
- package/dist/modules/sync_excel/index.js.map +7 -0
- package/dist/modules/sync_excel/integration.js +36 -0
- package/dist/modules/sync_excel/integration.js.map +7 -0
- package/dist/modules/sync_excel/lib/adapters/customers.js +852 -0
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +7 -0
- package/dist/modules/sync_excel/lib/column-detector.js +77 -0
- package/dist/modules/sync_excel/lib/column-detector.js.map +7 -0
- package/dist/modules/sync_excel/lib/mapping.js +16 -0
- package/dist/modules/sync_excel/lib/mapping.js.map +7 -0
- package/dist/modules/sync_excel/lib/parser.js +125 -0
- package/dist/modules/sync_excel/lib/parser.js.map +7 -0
- package/dist/modules/sync_excel/lib/scope.js +44 -0
- package/dist/modules/sync_excel/lib/scope.js.map +7 -0
- package/dist/modules/sync_excel/lib/upload-storage.js +63 -0
- package/dist/modules/sync_excel/lib/upload-storage.js.map +7 -0
- package/dist/modules/sync_excel/migrations/Migration20260329181120_sync_excel.js +11 -0
- package/dist/modules/sync_excel/migrations/Migration20260329181120_sync_excel.js.map +7 -0
- package/dist/modules/sync_excel/setup.js +19 -0
- package/dist/modules/sync_excel/setup.js.map +7 -0
- package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js +293 -0
- package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js.map +7 -0
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js +934 -0
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js.map +7 -0
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.js +15 -0
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.js.map +7 -0
- package/dist/modules/sync_excel/widgets/injection-table.js +17 -0
- package/dist/modules/sync_excel/widgets/injection-table.js.map +7 -0
- package/generated/entities/sync_excel_upload/index.ts +17 -0
- package/generated/entities.ids.generated.ts +4 -0
- package/generated/entity-fields-registry.ts +19 -0
- package/package.json +7 -7
- package/src/modules/customers/search.ts +3 -2
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -0
- package/src/modules/data_sync/di.ts +4 -1
- package/src/modules/data_sync/lib/adapter.ts +15 -0
- package/src/modules/data_sync/lib/sync-engine.ts +205 -52
- package/src/modules/data_sync/lib/sync-run-service.ts +24 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +220 -15
- package/src/modules/integrations/i18n/de.json +9 -0
- package/src/modules/integrations/i18n/en.json +9 -0
- package/src/modules/integrations/i18n/es.json +9 -0
- package/src/modules/integrations/i18n/pl.json +9 -0
- package/src/modules/sync_excel/acl.ts +6 -0
- package/src/modules/sync_excel/api/import/route.ts +182 -0
- package/src/modules/sync_excel/api/preview/route.ts +89 -0
- package/src/modules/sync_excel/api/upload/route.ts +144 -0
- package/src/modules/sync_excel/data/entities.ts +61 -0
- package/src/modules/sync_excel/data/validators.ts +58 -0
- package/src/modules/sync_excel/di.ts +9 -0
- package/src/modules/sync_excel/i18n/de.json +107 -0
- package/src/modules/sync_excel/i18n/en.json +107 -0
- package/src/modules/sync_excel/i18n/es.json +107 -0
- package/src/modules/sync_excel/i18n/pl.json +107 -0
- package/src/modules/sync_excel/index.ts +7 -0
- package/src/modules/sync_excel/integration.ts +31 -0
- package/src/modules/sync_excel/lib/adapters/customers.ts +1097 -0
- package/src/modules/sync_excel/lib/column-detector.ts +91 -0
- package/src/modules/sync_excel/lib/mapping.ts +19 -0
- package/src/modules/sync_excel/lib/parser.ts +165 -0
- package/src/modules/sync_excel/lib/scope.ts +63 -0
- package/src/modules/sync_excel/lib/upload-storage.ts +79 -0
- package/src/modules/sync_excel/migrations/.snapshot-open-mercato.json +199 -0
- package/src/modules/sync_excel/migrations/Migration20260329181120_sync_excel.ts +10 -0
- package/src/modules/sync_excel/setup.ts +20 -0
- package/src/modules/sync_excel/widgets/injection/upload-config/target-options.ts +351 -0
- package/src/modules/sync_excel/widgets/injection/upload-config/widget.client.tsx +1224 -0
- package/src/modules/sync_excel/widgets/injection/upload-config/widget.ts +30 -0
- package/src/modules/sync_excel/widgets/injection-table.ts +15 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
5
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { startDataSyncRun } from "@open-mercato/core/modules/data_sync/lib/start-run";
|
|
8
|
+
import { SyncMapping } from "@open-mercato/core/modules/data_sync/data/entities";
|
|
9
|
+
import { SyncExcelUpload } from "../../data/entities.js";
|
|
10
|
+
import { Attachment } from "../../../attachments/data/entities.js";
|
|
11
|
+
import { createCursor } from "../../lib/adapters/customers.js";
|
|
12
|
+
import {
|
|
13
|
+
syncExcelImportRequestSchema,
|
|
14
|
+
syncExcelImportResponseSchema
|
|
15
|
+
} from "../../data/validators.js";
|
|
16
|
+
import { resolveSyncExcelConcreteScope } from "../../lib/scope.js";
|
|
17
|
+
const metadata = {
|
|
18
|
+
POST: { requireAuth: true, requireFeatures: ["sync_excel.run"] }
|
|
19
|
+
};
|
|
20
|
+
const errorSchema = z.object({
|
|
21
|
+
error: z.string()
|
|
22
|
+
});
|
|
23
|
+
const openApi = {
|
|
24
|
+
tags: ["SyncExcel"],
|
|
25
|
+
summary: "Start a CSV import run for a stored sync_excel upload",
|
|
26
|
+
methods: {
|
|
27
|
+
POST: {
|
|
28
|
+
summary: "Start CSV import",
|
|
29
|
+
responses: [
|
|
30
|
+
{ status: 201, description: "Import run started", schema: syncExcelImportResponseSchema },
|
|
31
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
32
|
+
{ status: 404, description: "Upload not found", schema: errorSchema },
|
|
33
|
+
{ status: 409, description: "Import overlap detected", schema: errorSchema },
|
|
34
|
+
{ status: 422, description: "Invalid import payload", schema: errorSchema }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
async function POST(request) {
|
|
40
|
+
const auth = await getAuthFromRequest(request);
|
|
41
|
+
if (!auth?.tenantId) {
|
|
42
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
43
|
+
}
|
|
44
|
+
const payload = await readJsonSafe(request);
|
|
45
|
+
const parsedPayload = syncExcelImportRequestSchema.safeParse(payload);
|
|
46
|
+
if (!parsedPayload.success) {
|
|
47
|
+
return NextResponse.json({ error: "Invalid import payload." }, { status: 422 });
|
|
48
|
+
}
|
|
49
|
+
const container = await createRequestContainer();
|
|
50
|
+
const em = container.resolve("em");
|
|
51
|
+
const syncRunService = container.resolve("dataSyncRunService");
|
|
52
|
+
const progressService = container.resolve("progressService");
|
|
53
|
+
const credentialsService = container.resolve("integrationCredentialsService");
|
|
54
|
+
const integrationStateService = container.resolve("integrationStateService");
|
|
55
|
+
const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request });
|
|
56
|
+
if (!scopeResult.ok) {
|
|
57
|
+
return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status });
|
|
58
|
+
}
|
|
59
|
+
const { scope } = scopeResult;
|
|
60
|
+
const upload = await findOneWithDecryption(
|
|
61
|
+
em,
|
|
62
|
+
SyncExcelUpload,
|
|
63
|
+
{
|
|
64
|
+
id: parsedPayload.data.uploadId,
|
|
65
|
+
organizationId: scope.organizationId,
|
|
66
|
+
tenantId: scope.tenantId
|
|
67
|
+
},
|
|
68
|
+
void 0,
|
|
69
|
+
scope
|
|
70
|
+
);
|
|
71
|
+
if (!upload) {
|
|
72
|
+
return NextResponse.json({ error: "Upload preview not found." }, { status: 404 });
|
|
73
|
+
}
|
|
74
|
+
const attachment = await findOneWithDecryption(
|
|
75
|
+
em,
|
|
76
|
+
Attachment,
|
|
77
|
+
{
|
|
78
|
+
id: upload.attachmentId,
|
|
79
|
+
organizationId: scope.organizationId,
|
|
80
|
+
tenantId: scope.tenantId
|
|
81
|
+
},
|
|
82
|
+
void 0,
|
|
83
|
+
scope
|
|
84
|
+
);
|
|
85
|
+
if (!attachment) {
|
|
86
|
+
return NextResponse.json({ error: "Upload attachment not found." }, { status: 404 });
|
|
87
|
+
}
|
|
88
|
+
if (upload.entityType !== parsedPayload.data.entityType) {
|
|
89
|
+
return NextResponse.json({ error: "Upload entity type does not match requested import target." }, { status: 422 });
|
|
90
|
+
}
|
|
91
|
+
if (parsedPayload.data.mapping.entityType !== parsedPayload.data.entityType) {
|
|
92
|
+
return NextResponse.json({ error: "Mapping entity type does not match requested import target." }, { status: 422 });
|
|
93
|
+
}
|
|
94
|
+
const overlap = await syncRunService.findRunningOverlap("sync_excel", parsedPayload.data.entityType, "import", scope);
|
|
95
|
+
if (overlap) {
|
|
96
|
+
return NextResponse.json({ error: "A sync_excel import is already in progress for this entity type." }, { status: 409 });
|
|
97
|
+
}
|
|
98
|
+
const existingMapping = await findOneWithDecryption(
|
|
99
|
+
em,
|
|
100
|
+
SyncMapping,
|
|
101
|
+
{
|
|
102
|
+
integrationId: "sync_excel",
|
|
103
|
+
entityType: parsedPayload.data.entityType,
|
|
104
|
+
organizationId: scope.organizationId,
|
|
105
|
+
tenantId: scope.tenantId
|
|
106
|
+
},
|
|
107
|
+
void 0,
|
|
108
|
+
scope
|
|
109
|
+
);
|
|
110
|
+
if (existingMapping) {
|
|
111
|
+
existingMapping.mapping = parsedPayload.data.mapping;
|
|
112
|
+
} else {
|
|
113
|
+
em.persist(em.create(SyncMapping, {
|
|
114
|
+
integrationId: "sync_excel",
|
|
115
|
+
entityType: parsedPayload.data.entityType,
|
|
116
|
+
mapping: parsedPayload.data.mapping,
|
|
117
|
+
organizationId: scope.organizationId,
|
|
118
|
+
tenantId: scope.tenantId
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
await credentialsService.save("sync_excel", {}, scope);
|
|
122
|
+
await integrationStateService.upsert("sync_excel", { isEnabled: true }, scope);
|
|
123
|
+
await em.flush();
|
|
124
|
+
const { run, progressJob } = await startDataSyncRun({
|
|
125
|
+
syncRunService,
|
|
126
|
+
progressService,
|
|
127
|
+
scope: {
|
|
128
|
+
...scope,
|
|
129
|
+
userId: auth.sub
|
|
130
|
+
},
|
|
131
|
+
input: {
|
|
132
|
+
integrationId: "sync_excel",
|
|
133
|
+
entityType: parsedPayload.data.entityType,
|
|
134
|
+
direction: "import",
|
|
135
|
+
cursor: createCursor(upload.id, 0),
|
|
136
|
+
triggeredBy: auth.sub,
|
|
137
|
+
batchSize: parsedPayload.data.batchSize ?? 100,
|
|
138
|
+
progressJob: {
|
|
139
|
+
jobType: "sync_excel:import",
|
|
140
|
+
name: `CSV import \u2014 ${parsedPayload.data.entityType}`,
|
|
141
|
+
description: upload.filename,
|
|
142
|
+
meta: {
|
|
143
|
+
integrationId: "sync_excel",
|
|
144
|
+
uploadId: upload.id,
|
|
145
|
+
hiddenFromTopBar: false
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
upload.syncRunId = run.id;
|
|
151
|
+
upload.status = "importing";
|
|
152
|
+
await em.flush();
|
|
153
|
+
return NextResponse.json(syncExcelImportResponseSchema.parse({
|
|
154
|
+
runId: run.id,
|
|
155
|
+
progressJobId: progressJob?.id ?? null,
|
|
156
|
+
status: run.status
|
|
157
|
+
}), { status: 201 });
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
POST,
|
|
161
|
+
metadata,
|
|
162
|
+
openApi
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/sync_excel/api/import/route.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { z } from 'zod'\nimport type { ProgressService } from '@open-mercato/core/modules/progress/lib/progressService'\nimport type { SyncRunService } from '@open-mercato/core/modules/data_sync/lib/sync-run-service'\nimport { startDataSyncRun } from '@open-mercato/core/modules/data_sync/lib/start-run'\nimport { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'\nimport type { CredentialsService } from '@open-mercato/core/modules/integrations/lib/credentials-service'\nimport type { IntegrationStateService } from '@open-mercato/core/modules/integrations/lib/state-service'\nimport { SyncExcelUpload } from '../../data/entities'\nimport { Attachment } from '../../../attachments/data/entities'\nimport { createCursor } from '../../lib/adapters/customers'\nimport {\n syncExcelImportRequestSchema,\n syncExcelImportResponseSchema,\n} from '../../data/validators'\nimport { resolveSyncExcelConcreteScope } from '../../lib/scope'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['sync_excel.run'] },\n}\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi = {\n tags: ['SyncExcel'],\n summary: 'Start a CSV import run for a stored sync_excel upload',\n methods: {\n POST: {\n summary: 'Start CSV import',\n responses: [\n { status: 201, description: 'Import run started', schema: syncExcelImportResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Upload not found', schema: errorSchema },\n { status: 409, description: 'Import overlap detected', schema: errorSchema },\n { status: 422, description: 'Invalid import payload', schema: errorSchema },\n ],\n },\n },\n}\n\nexport async function POST(request: Request) {\n const auth = await getAuthFromRequest(request)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(request)\n const parsedPayload = syncExcelImportRequestSchema.safeParse(payload)\n if (!parsedPayload.success) {\n return NextResponse.json({ error: 'Invalid import payload.' }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request })\n if (!scopeResult.ok) {\n return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status })\n }\n const { scope } = scopeResult\n\n const upload = await findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n id: parsedPayload.data.uploadId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!upload) {\n return NextResponse.json({ error: 'Upload preview not found.' }, { status: 404 })\n }\n\n const attachment = await findOneWithDecryption(\n em,\n Attachment,\n {\n id: upload.attachmentId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!attachment) {\n return NextResponse.json({ error: 'Upload attachment not found.' }, { status: 404 })\n }\n\n if (upload.entityType !== parsedPayload.data.entityType) {\n return NextResponse.json({ error: 'Upload entity type does not match requested import target.' }, { status: 422 })\n }\n\n if (parsedPayload.data.mapping.entityType !== parsedPayload.data.entityType) {\n return NextResponse.json({ error: 'Mapping entity type does not match requested import target.' }, { status: 422 })\n }\n\n const overlap = await syncRunService.findRunningOverlap('sync_excel', parsedPayload.data.entityType, 'import', scope)\n if (overlap) {\n return NextResponse.json({ error: 'A sync_excel import is already in progress for this entity type.' }, { status: 409 })\n }\n\n const existingMapping = await findOneWithDecryption(\n em,\n SyncMapping,\n {\n integrationId: 'sync_excel',\n entityType: parsedPayload.data.entityType,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (existingMapping) {\n existingMapping.mapping = parsedPayload.data.mapping\n } else {\n em.persist(em.create(SyncMapping, {\n integrationId: 'sync_excel',\n entityType: parsedPayload.data.entityType,\n mapping: parsedPayload.data.mapping,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n }))\n }\n\n await credentialsService.save('sync_excel', {}, scope)\n await integrationStateService.upsert('sync_excel', { isEnabled: true }, scope)\n await em.flush()\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: 'sync_excel',\n entityType: parsedPayload.data.entityType,\n direction: 'import',\n cursor: createCursor(upload.id, 0),\n triggeredBy: auth.sub,\n batchSize: parsedPayload.data.batchSize ?? 100,\n progressJob: {\n jobType: 'sync_excel:import',\n name: `CSV import \u2014 ${parsedPayload.data.entityType}`,\n description: upload.filename,\n meta: {\n integrationId: 'sync_excel',\n uploadId: upload.id,\n hiddenFromTopBar: false,\n },\n },\n },\n })\n\n upload.syncRunId = run.id\n upload.status = 'importing'\n await em.flush()\n\n return NextResponse.json(syncExcelImportResponseSchema.parse({\n runId: run.id,\n progressJobId: progressJob?.id ?? null,\n status: run.status,\n }), { status: 201 })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { syncExcelPreviewQuerySchema, syncExcelUploadResponseSchema } from "../../data/validators.js";
|
|
7
|
+
import { SyncExcelUpload } from "../../data/entities.js";
|
|
8
|
+
import { buildSuggestedMapping } from "../../lib/mapping.js";
|
|
9
|
+
import { resolveSyncExcelConcreteScope } from "../../lib/scope.js";
|
|
10
|
+
const metadata = {
|
|
11
|
+
GET: { requireAuth: true, requireFeatures: ["sync_excel.view"] }
|
|
12
|
+
};
|
|
13
|
+
const errorSchema = z.object({
|
|
14
|
+
error: z.string()
|
|
15
|
+
});
|
|
16
|
+
const openApi = {
|
|
17
|
+
tags: ["SyncExcel"],
|
|
18
|
+
summary: "Fetch stored preview for a sync_excel upload",
|
|
19
|
+
methods: {
|
|
20
|
+
GET: {
|
|
21
|
+
summary: "Fetch upload preview",
|
|
22
|
+
responses: [
|
|
23
|
+
{ status: 200, description: "Stored upload preview", schema: syncExcelUploadResponseSchema },
|
|
24
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
25
|
+
{ status: 422, description: "Invalid query", schema: errorSchema },
|
|
26
|
+
{ status: 404, description: "Upload preview not found", schema: errorSchema }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
async function GET(request) {
|
|
32
|
+
const auth = await getAuthFromRequest(request);
|
|
33
|
+
if (!auth?.tenantId) {
|
|
34
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
35
|
+
}
|
|
36
|
+
const url = new URL(request.url);
|
|
37
|
+
const parsedQuery = syncExcelPreviewQuerySchema.safeParse({
|
|
38
|
+
uploadId: url.searchParams.get("uploadId"),
|
|
39
|
+
entityType: url.searchParams.get("entityType") ?? void 0
|
|
40
|
+
});
|
|
41
|
+
if (!parsedQuery.success) {
|
|
42
|
+
return NextResponse.json({ error: "Invalid query" }, { status: 422 });
|
|
43
|
+
}
|
|
44
|
+
const container = await createRequestContainer();
|
|
45
|
+
const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request });
|
|
46
|
+
if (!scopeResult.ok) {
|
|
47
|
+
return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status });
|
|
48
|
+
}
|
|
49
|
+
const { scope } = scopeResult;
|
|
50
|
+
const em = container.resolve("em");
|
|
51
|
+
const upload = await findOneWithDecryption(
|
|
52
|
+
em,
|
|
53
|
+
SyncExcelUpload,
|
|
54
|
+
{
|
|
55
|
+
id: parsedQuery.data.uploadId,
|
|
56
|
+
organizationId: scope.organizationId,
|
|
57
|
+
tenantId: scope.tenantId
|
|
58
|
+
},
|
|
59
|
+
void 0,
|
|
60
|
+
scope
|
|
61
|
+
);
|
|
62
|
+
if (!upload) {
|
|
63
|
+
return NextResponse.json({ error: "Upload preview not found." }, { status: 404 });
|
|
64
|
+
}
|
|
65
|
+
const entityType = parsedQuery.data.entityType ?? upload.entityType;
|
|
66
|
+
const suggestedMapping = buildSuggestedMapping(entityType, upload.headers);
|
|
67
|
+
return NextResponse.json(syncExcelUploadResponseSchema.parse({
|
|
68
|
+
uploadId: upload.id,
|
|
69
|
+
filename: upload.filename,
|
|
70
|
+
mimeType: upload.mimeType,
|
|
71
|
+
fileSize: upload.fileSize,
|
|
72
|
+
entityType,
|
|
73
|
+
headers: upload.headers,
|
|
74
|
+
sampleRows: upload.sampleRows,
|
|
75
|
+
totalRows: upload.totalRows,
|
|
76
|
+
suggestedMapping
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
GET,
|
|
81
|
+
metadata,
|
|
82
|
+
openApi
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/sync_excel/api/preview/route.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { z } from 'zod'\nimport { syncExcelPreviewQuerySchema, syncExcelUploadResponseSchema } from '../../data/validators'\nimport { SyncExcelUpload } from '../../data/entities'\nimport { buildSuggestedMapping } from '../../lib/mapping'\nimport { resolveSyncExcelConcreteScope } from '../../lib/scope'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['sync_excel.view'] },\n}\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi = {\n tags: ['SyncExcel'],\n summary: 'Fetch stored preview for a sync_excel upload',\n methods: {\n GET: {\n summary: 'Fetch upload preview',\n responses: [\n { status: 200, description: 'Stored upload preview', schema: syncExcelUploadResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 422, description: 'Invalid query', schema: errorSchema },\n { status: 404, description: 'Upload preview not found', schema: errorSchema },\n ],\n },\n },\n}\n\nexport async function GET(request: Request) {\n const auth = await getAuthFromRequest(request)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(request.url)\n const parsedQuery = syncExcelPreviewQuerySchema.safeParse({\n uploadId: url.searchParams.get('uploadId'),\n entityType: url.searchParams.get('entityType') ?? undefined,\n })\n\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'Invalid query' }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request })\n if (!scopeResult.ok) {\n return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status })\n }\n const { scope } = scopeResult\n const em = container.resolve('em') as EntityManager\n const upload = await findOneWithDecryption(\n em,\n SyncExcelUpload,\n {\n id: parsedQuery.data.uploadId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n\n if (!upload) {\n return NextResponse.json({ error: 'Upload preview not found.' }, { status: 404 })\n }\n\n const entityType = parsedQuery.data.entityType ?? upload.entityType\n const suggestedMapping = buildSuggestedMapping(entityType, upload.headers)\n\n return NextResponse.json(syncExcelUploadResponseSchema.parse({\n uploadId: upload.id,\n filename: upload.filename,\n mimeType: upload.mimeType,\n fileSize: upload.fileSize,\n entityType,\n headers: upload.headers,\n sampleRows: upload.sampleRows,\n totalRows: upload.totalRows,\n suggestedMapping,\n }))\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,SAAS;AAClB,SAAS,6BAA6B,qCAAqC;AAC3E,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,qCAAqC;AAEvC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,WAAW;AAAA,EAClB,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,8BAA8B;AAAA,QAC3F,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,YAAY;AAAA,QACjE,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,YAAY;AAAA,MAC9E;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,IAAI,SAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,OAAO;AAC7C,MAAI,CAAC,MAAM,UAAU;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,cAAc,4BAA4B,UAAU;AAAA,IACxD,UAAU,IAAI,aAAa,IAAI,UAAU;AAAA,IACzC,YAAY,IAAI,aAAa,IAAI,YAAY,KAAK;AAAA,EACpD,CAAC;AAED,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,aAAa,KAAK,EAAE,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtE;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,MAAM,8BAA8B,EAAE,MAAM,WAAW,QAAQ,CAAC;AACpF,MAAI,CAAC,YAAY,IAAI;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,YAAY,MAAM,GAAG,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,EACvF;AACA,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,MACE,IAAI,YAAY,KAAK;AAAA,MACrB,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ;AACX,WAAO,aAAa,KAAK,EAAE,OAAO,4BAA4B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AAEA,QAAM,aAAa,YAAY,KAAK,cAAc,OAAO;AACzD,QAAM,mBAAmB,sBAAsB,YAAY,OAAO,OAAO;AAEzE,SAAO,aAAa,KAAK,8BAA8B,MAAM;AAAA,IAC3D,UAAU,OAAO;AAAA,IACjB,UAAU,OAAO;AAAA,IACjB,UAAU,OAAO;AAAA,IACjB,UAAU,OAAO;AAAA,IACjB;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,YAAY,OAAO;AAAA,IACnB,WAAW,OAAO;AAAA,IAClB;AAAA,EACF,CAAC,CAAC;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { syncExcelUploadResponseSchema, syncExcelEntityTypeSchema } from "../../data/validators.js";
|
|
7
|
+
import { SyncExcelUpload } from "../../data/entities.js";
|
|
8
|
+
import { buildSuggestedMapping } from "../../lib/mapping.js";
|
|
9
|
+
import { parseCsvPreview } from "../../lib/parser.js";
|
|
10
|
+
import { createSyncExcelUploadAttachment } from "../../lib/upload-storage.js";
|
|
11
|
+
import { resolveSyncExcelConcreteScope } from "../../lib/scope.js";
|
|
12
|
+
import { isMultipartRequestWithinUploadLimit, resolveDefaultAttachmentMaxUploadBytes } from "../../../attachments/lib/upload-limits.js";
|
|
13
|
+
const metadata = {
|
|
14
|
+
POST: { requireAuth: true, requireFeatures: ["sync_excel.run"] }
|
|
15
|
+
};
|
|
16
|
+
const multipartSchema = z.object({
|
|
17
|
+
entityType: syncExcelEntityTypeSchema.default("customers.person")
|
|
18
|
+
});
|
|
19
|
+
const errorSchema = z.object({
|
|
20
|
+
error: z.string()
|
|
21
|
+
});
|
|
22
|
+
const openApi = {
|
|
23
|
+
tags: ["SyncExcel"],
|
|
24
|
+
summary: "Upload CSV file for sync_excel preview",
|
|
25
|
+
methods: {
|
|
26
|
+
POST: {
|
|
27
|
+
summary: "Upload CSV file",
|
|
28
|
+
requestBody: {
|
|
29
|
+
contentType: "multipart/form-data"
|
|
30
|
+
},
|
|
31
|
+
responses: [
|
|
32
|
+
{ status: 200, description: "CSV uploaded and preview parsed", schema: syncExcelUploadResponseSchema }
|
|
33
|
+
],
|
|
34
|
+
errors: [
|
|
35
|
+
{ status: 400, description: "Invalid multipart payload", schema: errorSchema },
|
|
36
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
37
|
+
{ status: 413, description: "CSV upload exceeds the maximum upload size", schema: errorSchema },
|
|
38
|
+
{ status: 422, description: "Unsupported entity type or file type", schema: errorSchema }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
async function POST(request) {
|
|
44
|
+
const auth = await getAuthFromRequest(request);
|
|
45
|
+
if (!auth?.tenantId) {
|
|
46
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
47
|
+
}
|
|
48
|
+
const container = await createRequestContainer();
|
|
49
|
+
const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request });
|
|
50
|
+
if (!scopeResult.ok) {
|
|
51
|
+
return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status });
|
|
52
|
+
}
|
|
53
|
+
const { scope } = scopeResult;
|
|
54
|
+
const contentType = request.headers.get("content-type") || "";
|
|
55
|
+
if (!contentType.toLowerCase().includes("multipart/form-data")) {
|
|
56
|
+
return NextResponse.json({ error: "Expected multipart/form-data" }, { status: 400 });
|
|
57
|
+
}
|
|
58
|
+
if (!isMultipartRequestWithinUploadLimit(request.headers.get("content-length"))) {
|
|
59
|
+
return NextResponse.json({ error: "CSV upload exceeds the maximum upload size." }, { status: 413 });
|
|
60
|
+
}
|
|
61
|
+
const formData = await request.formData();
|
|
62
|
+
const parsedPayload = multipartSchema.safeParse({
|
|
63
|
+
entityType: typeof formData.get("entityType") === "string" ? formData.get("entityType") : void 0
|
|
64
|
+
});
|
|
65
|
+
if (!parsedPayload.success) {
|
|
66
|
+
return NextResponse.json({ error: "Invalid upload payload." }, { status: 422 });
|
|
67
|
+
}
|
|
68
|
+
const file = formData.get("file");
|
|
69
|
+
if (!(file instanceof File)) {
|
|
70
|
+
return NextResponse.json({ error: "Select a CSV file to upload." }, { status: 400 });
|
|
71
|
+
}
|
|
72
|
+
const supportedMimeTypes = /* @__PURE__ */ new Set(["text/csv", "application/vnd.ms-excel", "application/csv"]);
|
|
73
|
+
const isCsvByMime = supportedMimeTypes.has(file.type);
|
|
74
|
+
const isCsvByName = file.name.toLowerCase().endsWith(".csv");
|
|
75
|
+
if (!isCsvByMime && !isCsvByName) {
|
|
76
|
+
return NextResponse.json({ error: "Only CSV uploads are supported in this foundation slice." }, { status: 422 });
|
|
77
|
+
}
|
|
78
|
+
const maxUploadBytes = resolveDefaultAttachmentMaxUploadBytes();
|
|
79
|
+
if (file.size > maxUploadBytes) {
|
|
80
|
+
return NextResponse.json({ error: "CSV upload exceeds the maximum upload size." }, { status: 413 });
|
|
81
|
+
}
|
|
82
|
+
const fileBuffer = Buffer.from(await file.arrayBuffer());
|
|
83
|
+
const preview = parseCsvPreview(fileBuffer, { maxRows: 5 });
|
|
84
|
+
const suggestedMapping = buildSuggestedMapping(parsedPayload.data.entityType, preview.headers);
|
|
85
|
+
const em = container.resolve("em");
|
|
86
|
+
const uploadId = randomUUID();
|
|
87
|
+
const attachment = await createSyncExcelUploadAttachment({
|
|
88
|
+
em,
|
|
89
|
+
uploadId,
|
|
90
|
+
organizationId: scope.organizationId,
|
|
91
|
+
tenantId: scope.tenantId,
|
|
92
|
+
fileName: file.name,
|
|
93
|
+
mimeType: file.type || "text/csv",
|
|
94
|
+
buffer: fileBuffer
|
|
95
|
+
});
|
|
96
|
+
const upload = em.create(SyncExcelUpload, {
|
|
97
|
+
id: uploadId,
|
|
98
|
+
attachmentId: attachment.id,
|
|
99
|
+
filename: file.name,
|
|
100
|
+
mimeType: file.type || "text/csv",
|
|
101
|
+
fileSize: fileBuffer.length,
|
|
102
|
+
entityType: parsedPayload.data.entityType,
|
|
103
|
+
delimiter: preview.delimiter,
|
|
104
|
+
encoding: preview.encoding,
|
|
105
|
+
headers: preview.headers,
|
|
106
|
+
sampleRows: preview.sampleRows,
|
|
107
|
+
totalRows: preview.totalRows,
|
|
108
|
+
status: "uploaded",
|
|
109
|
+
organizationId: scope.organizationId,
|
|
110
|
+
tenantId: scope.tenantId
|
|
111
|
+
});
|
|
112
|
+
em.persist(upload);
|
|
113
|
+
await em.flush();
|
|
114
|
+
return NextResponse.json(syncExcelUploadResponseSchema.parse({
|
|
115
|
+
uploadId: upload.id,
|
|
116
|
+
filename: upload.filename,
|
|
117
|
+
mimeType: upload.mimeType,
|
|
118
|
+
fileSize: upload.fileSize,
|
|
119
|
+
entityType: upload.entityType,
|
|
120
|
+
headers: upload.headers,
|
|
121
|
+
sampleRows: upload.sampleRows,
|
|
122
|
+
totalRows: upload.totalRows,
|
|
123
|
+
suggestedMapping
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
POST,
|
|
128
|
+
metadata,
|
|
129
|
+
openApi
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/sync_excel/api/upload/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { z } from 'zod'\nimport { syncExcelUploadResponseSchema, syncExcelEntityTypeSchema } from '../../data/validators'\nimport { SyncExcelUpload } from '../../data/entities'\nimport { buildSuggestedMapping } from '../../lib/mapping'\nimport { parseCsvPreview } from '../../lib/parser'\nimport { createSyncExcelUploadAttachment } from '../../lib/upload-storage'\nimport { resolveSyncExcelConcreteScope } from '../../lib/scope'\nimport { isMultipartRequestWithinUploadLimit, resolveDefaultAttachmentMaxUploadBytes } from '../../../attachments/lib/upload-limits'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['sync_excel.run'] },\n}\n\nconst multipartSchema = z.object({\n entityType: syncExcelEntityTypeSchema.default('customers.person'),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi = {\n tags: ['SyncExcel'],\n summary: 'Upload CSV file for sync_excel preview',\n methods: {\n POST: {\n summary: 'Upload CSV file',\n requestBody: {\n contentType: 'multipart/form-data',\n },\n responses: [\n { status: 200, description: 'CSV uploaded and preview parsed', schema: syncExcelUploadResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid multipart payload', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 413, description: 'CSV upload exceeds the maximum upload size', schema: errorSchema },\n { status: 422, description: 'Unsupported entity type or file type', schema: errorSchema },\n ],\n },\n },\n}\n\nexport async function POST(request: Request) {\n const auth = await getAuthFromRequest(request)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request })\n if (!scopeResult.ok) {\n return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status })\n }\n const { scope } = scopeResult\n\n const contentType = request.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(request.headers.get('content-length'))) {\n return NextResponse.json({ error: 'CSV upload exceeds the maximum upload size.' }, { status: 413 })\n }\n\n const formData = await request.formData()\n const parsedPayload = multipartSchema.safeParse({\n entityType: typeof formData.get('entityType') === 'string' ? formData.get('entityType') : undefined,\n })\n\n if (!parsedPayload.success) {\n return NextResponse.json({ error: 'Invalid upload payload.' }, { status: 422 })\n }\n\n const file = formData.get('file')\n if (!(file instanceof File)) {\n return NextResponse.json({ error: 'Select a CSV file to upload.' }, { status: 400 })\n }\n\n const supportedMimeTypes = new Set(['text/csv', 'application/vnd.ms-excel', 'application/csv'])\n const isCsvByMime = supportedMimeTypes.has(file.type)\n const isCsvByName = file.name.toLowerCase().endsWith('.csv')\n\n if (!isCsvByMime && !isCsvByName) {\n return NextResponse.json({ error: 'Only CSV uploads are supported in this foundation slice.' }, { status: 422 })\n }\n\n const maxUploadBytes = resolveDefaultAttachmentMaxUploadBytes()\n if (file.size > maxUploadBytes) {\n return NextResponse.json({ error: 'CSV upload exceeds the maximum upload size.' }, { status: 413 })\n }\n\n const fileBuffer = Buffer.from(await file.arrayBuffer())\n const preview = parseCsvPreview(fileBuffer, { maxRows: 5 })\n const suggestedMapping = buildSuggestedMapping(parsedPayload.data.entityType, preview.headers)\n\n const em = container.resolve('em') as EntityManager\n const uploadId = randomUUID()\n const attachment = await createSyncExcelUploadAttachment({\n em,\n uploadId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n fileName: file.name,\n mimeType: file.type || 'text/csv',\n buffer: fileBuffer,\n })\n\n const upload = em.create(SyncExcelUpload, {\n id: uploadId,\n attachmentId: attachment.id,\n filename: file.name,\n mimeType: file.type || 'text/csv',\n fileSize: fileBuffer.length,\n entityType: parsedPayload.data.entityType,\n delimiter: preview.delimiter,\n encoding: preview.encoding,\n headers: preview.headers,\n sampleRows: preview.sampleRows,\n totalRows: preview.totalRows,\n status: 'uploaded',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n em.persist(upload)\n await em.flush()\n\n return NextResponse.json(syncExcelUploadResponseSchema.parse({\n uploadId: upload.id,\n filename: upload.filename,\n mimeType: upload.mimeType,\n fileSize: upload.fileSize,\n entityType: upload.entityType,\n headers: upload.headers,\n sampleRows: upload.sampleRows,\n totalRows: upload.totalRows,\n suggestedMapping,\n }))\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import { OptionalProps } from "@mikro-orm/core";
|
|
12
|
+
import { Entity, Index, PrimaryKey, Property } from "@mikro-orm/decorators/legacy";
|
|
13
|
+
OptionalProps;
|
|
14
|
+
let SyncExcelUpload = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.status = "uploaded";
|
|
17
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
18
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
__decorateClass([
|
|
22
|
+
PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
|
|
23
|
+
], SyncExcelUpload.prototype, "id", 2);
|
|
24
|
+
__decorateClass([
|
|
25
|
+
Property({ name: "attachment_id", type: "uuid" })
|
|
26
|
+
], SyncExcelUpload.prototype, "attachmentId", 2);
|
|
27
|
+
__decorateClass([
|
|
28
|
+
Property({ name: "filename", type: "text" })
|
|
29
|
+
], SyncExcelUpload.prototype, "filename", 2);
|
|
30
|
+
__decorateClass([
|
|
31
|
+
Property({ name: "mime_type", type: "text" })
|
|
32
|
+
], SyncExcelUpload.prototype, "mimeType", 2);
|
|
33
|
+
__decorateClass([
|
|
34
|
+
Property({ name: "file_size", type: "int" })
|
|
35
|
+
], SyncExcelUpload.prototype, "fileSize", 2);
|
|
36
|
+
__decorateClass([
|
|
37
|
+
Property({ name: "entity_type", type: "text" })
|
|
38
|
+
], SyncExcelUpload.prototype, "entityType", 2);
|
|
39
|
+
__decorateClass([
|
|
40
|
+
Property({ name: "delimiter", type: "text", nullable: true })
|
|
41
|
+
], SyncExcelUpload.prototype, "delimiter", 2);
|
|
42
|
+
__decorateClass([
|
|
43
|
+
Property({ name: "encoding", type: "text", nullable: true })
|
|
44
|
+
], SyncExcelUpload.prototype, "encoding", 2);
|
|
45
|
+
__decorateClass([
|
|
46
|
+
Property({ name: "headers", type: "json" })
|
|
47
|
+
], SyncExcelUpload.prototype, "headers", 2);
|
|
48
|
+
__decorateClass([
|
|
49
|
+
Property({ name: "sample_rows", type: "json" })
|
|
50
|
+
], SyncExcelUpload.prototype, "sampleRows", 2);
|
|
51
|
+
__decorateClass([
|
|
52
|
+
Property({ name: "total_rows", type: "int" })
|
|
53
|
+
], SyncExcelUpload.prototype, "totalRows", 2);
|
|
54
|
+
__decorateClass([
|
|
55
|
+
Property({ name: "status", type: "text", default: "uploaded" })
|
|
56
|
+
], SyncExcelUpload.prototype, "status", 2);
|
|
57
|
+
__decorateClass([
|
|
58
|
+
Property({ name: "sync_run_id", type: "uuid", nullable: true })
|
|
59
|
+
], SyncExcelUpload.prototype, "syncRunId", 2);
|
|
60
|
+
__decorateClass([
|
|
61
|
+
Property({ name: "organization_id", type: "uuid" })
|
|
62
|
+
], SyncExcelUpload.prototype, "organizationId", 2);
|
|
63
|
+
__decorateClass([
|
|
64
|
+
Property({ name: "tenant_id", type: "uuid" })
|
|
65
|
+
], SyncExcelUpload.prototype, "tenantId", 2);
|
|
66
|
+
__decorateClass([
|
|
67
|
+
Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
|
|
68
|
+
], SyncExcelUpload.prototype, "createdAt", 2);
|
|
69
|
+
__decorateClass([
|
|
70
|
+
Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
|
|
71
|
+
], SyncExcelUpload.prototype, "updatedAt", 2);
|
|
72
|
+
SyncExcelUpload = __decorateClass([
|
|
73
|
+
Entity({ tableName: "sync_excel_uploads" }),
|
|
74
|
+
Index({ properties: ["organizationId", "tenantId", "status"] })
|
|
75
|
+
], SyncExcelUpload);
|
|
76
|
+
export {
|
|
77
|
+
SyncExcelUpload
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sync_excel/data/entities.ts"],
|
|
4
|
+
"sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\n\nexport type SyncExcelUploadStatus = 'uploaded' | 'previewed' | 'importing' | 'completed' | 'failed'\n\n@Entity({ tableName: 'sync_excel_uploads' })\n@Index({ properties: ['organizationId', 'tenantId', 'status'] })\nexport class SyncExcelUpload {\n [OptionalProps]?: 'delimiter' | 'encoding' | 'status' | 'syncRunId' | 'createdAt' | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'attachment_id', type: 'uuid' })\n attachmentId!: string\n\n @Property({ name: 'filename', type: 'text' })\n filename!: string\n\n @Property({ name: 'mime_type', type: 'text' })\n mimeType!: string\n\n @Property({ name: 'file_size', type: 'int' })\n fileSize!: number\n\n @Property({ name: 'entity_type', type: 'text' })\n entityType!: string\n\n @Property({ name: 'delimiter', type: 'text', nullable: true })\n delimiter?: string | null\n\n @Property({ name: 'encoding', type: 'text', nullable: true })\n encoding?: string | null\n\n @Property({ name: 'headers', type: 'json' })\n headers!: string[]\n\n @Property({ name: 'sample_rows', type: 'json' })\n sampleRows!: Array<Record<string, string | null>>\n\n @Property({ name: 'total_rows', type: 'int' })\n totalRows!: number\n\n @Property({ name: 'status', type: 'text', default: 'uploaded' })\n status: SyncExcelUploadStatus = 'uploaded'\n\n @Property({ name: 'sync_run_id', type: 'uuid', nullable: true })\n syncRunId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid' })\n organizationId!: string\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,QAAQ,OAAO,YAAY,gBAAgB;AAOjD;AADI,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AAqCL,kBAAgC;AAYhC,qBAAkB,oBAAI,KAAK;AAG3B,qBAAkB,oBAAI,KAAK;AAAA;AAC7B;AAjDE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAHlD,gBAIX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,iBAAiB,MAAM,OAAO,CAAC;AAAA,GANtC,gBAOX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,YAAY,MAAM,OAAO,CAAC;AAAA,GATjC,gBAUX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,GAZlC,gBAaX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,GAfjC,gBAgBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,eAAe,MAAM,OAAO,CAAC;AAAA,GAlBpC,gBAmBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GArBlD,gBAsBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,YAAY,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAxBjD,gBAyBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,GA3BhC,gBA4BX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,eAAe,MAAM,OAAO,CAAC;AAAA,GA9BpC,gBA+BX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,CAAC;AAAA,GAjClC,gBAkCX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,UAAU,MAAM,QAAQ,SAAS,WAAW,CAAC;AAAA,GApCpD,gBAqCX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,eAAe,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAvCpD,gBAwCX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,mBAAmB,MAAM,OAAO,CAAC;AAAA,GA1CxC,gBA2CX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,GA7ClC,gBA8CX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAhD7D,gBAiDX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAnD7D,gBAoDX;AApDW,kBAAN;AAAA,EAFN,OAAO,EAAE,WAAW,qBAAqB,CAAC;AAAA,EAC1C,MAAM,EAAE,YAAY,CAAC,kBAAkB,YAAY,QAAQ,EAAE,CAAC;AAAA,GAClD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const syncExcelEntityTypes = ["customers.person"];
|
|
3
|
+
const syncExcelEntityTypeSchema = z.enum(syncExcelEntityTypes);
|
|
4
|
+
const syncExcelPreviewRowSchema = z.record(z.string(), z.string().nullable());
|
|
5
|
+
const syncExcelSuggestedFieldSchema = z.object({
|
|
6
|
+
externalField: z.string().min(1),
|
|
7
|
+
localField: z.string().min(1),
|
|
8
|
+
transform: z.string().optional(),
|
|
9
|
+
required: z.boolean().optional(),
|
|
10
|
+
defaultValue: z.unknown().optional(),
|
|
11
|
+
mappingKind: z.enum(["core", "relation", "external_id", "custom_field", "metadata", "ignore"]).optional(),
|
|
12
|
+
dedupeRole: z.enum(["primary", "secondary"]).optional()
|
|
13
|
+
});
|
|
14
|
+
const syncExcelSuggestedMappingSchema = z.object({
|
|
15
|
+
entityType: syncExcelEntityTypeSchema,
|
|
16
|
+
matchStrategy: z.enum(["externalId", "email", "custom"]),
|
|
17
|
+
matchField: z.string().optional(),
|
|
18
|
+
fields: z.array(syncExcelSuggestedFieldSchema),
|
|
19
|
+
unmappedColumns: z.array(z.string())
|
|
20
|
+
});
|
|
21
|
+
const syncExcelUploadResponseSchema = z.object({
|
|
22
|
+
uploadId: z.string().uuid(),
|
|
23
|
+
filename: z.string().min(1),
|
|
24
|
+
mimeType: z.string().min(1),
|
|
25
|
+
fileSize: z.number().int().nonnegative(),
|
|
26
|
+
entityType: syncExcelEntityTypeSchema,
|
|
27
|
+
headers: z.array(z.string()),
|
|
28
|
+
sampleRows: z.array(syncExcelPreviewRowSchema),
|
|
29
|
+
totalRows: z.number().int().nonnegative(),
|
|
30
|
+
suggestedMapping: syncExcelSuggestedMappingSchema
|
|
31
|
+
});
|
|
32
|
+
const syncExcelPreviewQuerySchema = z.object({
|
|
33
|
+
uploadId: z.string().uuid(),
|
|
34
|
+
entityType: syncExcelEntityTypeSchema.optional()
|
|
35
|
+
});
|
|
36
|
+
const syncExcelImportRequestSchema = z.object({
|
|
37
|
+
uploadId: z.string().uuid(),
|
|
38
|
+
entityType: syncExcelEntityTypeSchema,
|
|
39
|
+
mapping: syncExcelSuggestedMappingSchema,
|
|
40
|
+
batchSize: z.number().int().min(1).max(1e3).default(100).optional()
|
|
41
|
+
});
|
|
42
|
+
const syncExcelImportResponseSchema = z.object({
|
|
43
|
+
runId: z.string().uuid(),
|
|
44
|
+
progressJobId: z.string().uuid().nullable(),
|
|
45
|
+
status: z.enum(["pending", "running", "completed", "failed", "cancelled"])
|
|
46
|
+
});
|
|
47
|
+
export {
|
|
48
|
+
syncExcelEntityTypeSchema,
|
|
49
|
+
syncExcelEntityTypes,
|
|
50
|
+
syncExcelImportRequestSchema,
|
|
51
|
+
syncExcelImportResponseSchema,
|
|
52
|
+
syncExcelPreviewQuerySchema,
|
|
53
|
+
syncExcelPreviewRowSchema,
|
|
54
|
+
syncExcelSuggestedFieldSchema,
|
|
55
|
+
syncExcelSuggestedMappingSchema,
|
|
56
|
+
syncExcelUploadResponseSchema
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sync_excel/data/validators.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\n\nexport const syncExcelEntityTypes = ['customers.person'] as const\n\nexport const syncExcelEntityTypeSchema = z.enum(syncExcelEntityTypes)\n\nexport const syncExcelPreviewRowSchema = z.record(z.string(), z.string().nullable())\n\nexport const syncExcelSuggestedFieldSchema = z.object({\n externalField: z.string().min(1),\n localField: z.string().min(1),\n transform: z.string().optional(),\n required: z.boolean().optional(),\n defaultValue: z.unknown().optional(),\n mappingKind: z.enum(['core', 'relation', 'external_id', 'custom_field', 'metadata', 'ignore']).optional(),\n dedupeRole: z.enum(['primary', 'secondary']).optional(),\n})\n\nexport const syncExcelSuggestedMappingSchema = z.object({\n entityType: syncExcelEntityTypeSchema,\n matchStrategy: z.enum(['externalId', 'email', 'custom']),\n matchField: z.string().optional(),\n fields: z.array(syncExcelSuggestedFieldSchema),\n unmappedColumns: z.array(z.string()),\n})\n\nexport const syncExcelUploadResponseSchema = z.object({\n uploadId: z.string().uuid(),\n filename: z.string().min(1),\n mimeType: z.string().min(1),\n fileSize: z.number().int().nonnegative(),\n entityType: syncExcelEntityTypeSchema,\n headers: z.array(z.string()),\n sampleRows: z.array(syncExcelPreviewRowSchema),\n totalRows: z.number().int().nonnegative(),\n suggestedMapping: syncExcelSuggestedMappingSchema,\n})\n\nexport const syncExcelPreviewQuerySchema = z.object({\n uploadId: z.string().uuid(),\n entityType: syncExcelEntityTypeSchema.optional(),\n})\n\nexport const syncExcelImportRequestSchema = z.object({\n uploadId: z.string().uuid(),\n entityType: syncExcelEntityTypeSchema,\n mapping: syncExcelSuggestedMappingSchema,\n batchSize: z.number().int().min(1).max(1000).default(100).optional(),\n})\n\nexport const syncExcelImportResponseSchema = z.object({\n runId: z.string().uuid(),\n progressJobId: z.string().uuid().nullable(),\n status: z.enum(['pending', 'running', 'completed', 'failed', 'cancelled']),\n})\n\nexport type SyncExcelPreviewQuery = z.infer<typeof syncExcelPreviewQuerySchema>\nexport type SyncExcelImportRequest = z.infer<typeof syncExcelImportRequestSchema>\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,uBAAuB,CAAC,kBAAkB;AAEhD,MAAM,4BAA4B,EAAE,KAAK,oBAAoB;AAE7D,MAAM,4BAA4B,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC;AAE5E,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC/B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC/B,cAAc,EAAE,QAAQ,EAAE,SAAS;AAAA,EACnC,aAAa,EAAE,KAAK,CAAC,QAAQ,YAAY,eAAe,gBAAgB,YAAY,QAAQ,CAAC,EAAE,SAAS;AAAA,EACxG,YAAY,EAAE,KAAK,CAAC,WAAW,WAAW,CAAC,EAAE,SAAS;AACxD,CAAC;AAEM,MAAM,kCAAkC,EAAE,OAAO;AAAA,EACtD,YAAY;AAAA,EACZ,eAAe,EAAE,KAAK,CAAC,cAAc,SAAS,QAAQ,CAAC;AAAA,EACvD,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,QAAQ,EAAE,MAAM,6BAA6B;AAAA,EAC7C,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC;AACrC,CAAC;AAEM,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACvC,YAAY;AAAA,EACZ,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAC3B,YAAY,EAAE,MAAM,yBAAyB;AAAA,EAC7C,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,kBAAkB;AACpB,CAAC;AAEM,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,YAAY,0BAA0B,SAAS;AACjD,CAAC;AAEM,MAAM,+BAA+B,EAAE,OAAO;AAAA,EACnD,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI,EAAE,QAAQ,GAAG,EAAE,SAAS;AACrE,CAAC;AAEM,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,OAAO,EAAE,OAAO,EAAE,KAAK;AAAA,EACvB,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC1C,QAAQ,EAAE,KAAK,CAAC,WAAW,WAAW,aAAa,UAAU,WAAW,CAAC;AAC3E,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerDataSyncAdapter } from "../data_sync/lib/adapter-registry.js";
|
|
2
|
+
import { syncExcelCustomersAdapter } from "./lib/adapters/customers.js";
|
|
3
|
+
function register(_container) {
|
|
4
|
+
registerDataSyncAdapter(syncExcelCustomersAdapter);
|
|
5
|
+
}
|
|
6
|
+
var di_default = register;
|
|
7
|
+
export {
|
|
8
|
+
di_default as default,
|
|
9
|
+
register
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=di.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/sync_excel/di.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { registerDataSyncAdapter } from '../data_sync/lib/adapter-registry'\nimport { syncExcelCustomersAdapter } from './lib/adapters/customers'\n\nexport function register(_container: AppContainer) {\n registerDataSyncAdapter(syncExcelCustomersAdapter)\n}\n\nexport default register\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,+BAA+B;AACxC,SAAS,iCAAiC;AAEnC,SAAS,SAAS,YAA0B;AACjD,0BAAwB,yBAAyB;AACnD;AAEA,IAAO,aAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|