@open-mercato/core 0.6.3-develop.3593.1.8eda8943fa → 0.6.3-develop.3598.2.259c138c7d

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.
@@ -14,74 +14,84 @@ const openApi = {
14
14
  summary: "Start a data sync run"
15
15
  };
16
16
  async function POST(req) {
17
- const auth = await getAuthFromRequest(req);
18
- if (!auth?.tenantId || !auth.orgId) {
19
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20
- }
21
- const payload = await readJsonSafe(req);
22
- const parsed = runSyncSchema.safeParse(payload);
23
- if (!parsed.success) {
24
- return NextResponse.json({ error: "Invalid payload", details: parsed.error.flatten() }, { status: 422 });
25
- }
26
- const container = await createRequestContainer();
27
- const syncRunService = container.resolve("dataSyncRunService");
28
- const progressService = container.resolve("progressService");
29
- const integrationStateService = container.resolve("integrationStateService");
30
- const scope = {
31
- organizationId: auth.orgId,
32
- tenantId: auth.tenantId
33
- };
34
- const integration = getIntegration(parsed.data.integrationId);
35
- if (!integration?.providerKey) {
36
- return NextResponse.json({ error: "Integration not found" }, { status: 404 });
37
- }
38
- const adapter = getDataSyncAdapter(integration.providerKey);
39
- if (!adapter) {
40
- return NextResponse.json({ error: "No registered sync adapter for provider" }, { status: 404 });
41
- }
42
- if (adapter.runMode === "provider") {
43
- return NextResponse.json(
44
- {
45
- error: "This integration must be started from its provider-specific import flow.",
46
- settingsPath: `/backend/integrations/${encodeURIComponent(parsed.data.integrationId)}`
17
+ try {
18
+ const auth = await getAuthFromRequest(req);
19
+ if (!auth?.tenantId || !auth.orgId) {
20
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
21
+ }
22
+ const payload = await readJsonSafe(req);
23
+ const parsed = runSyncSchema.safeParse(payload);
24
+ if (!parsed.success) {
25
+ return NextResponse.json({ error: "Invalid payload", details: parsed.error.flatten() }, { status: 422 });
26
+ }
27
+ const container = await createRequestContainer();
28
+ const syncRunService = container.resolve("dataSyncRunService");
29
+ const progressService = container.resolve("progressService");
30
+ const integrationStateService = container.resolve("integrationStateService");
31
+ const scope = {
32
+ organizationId: auth.orgId,
33
+ tenantId: auth.tenantId
34
+ };
35
+ const integration = getIntegration(parsed.data.integrationId);
36
+ if (!integration?.providerKey) {
37
+ return NextResponse.json({ error: "Integration not found" }, { status: 404 });
38
+ }
39
+ const adapter = getDataSyncAdapter(integration.providerKey);
40
+ if (!adapter) {
41
+ return NextResponse.json({ error: "No registered sync adapter for provider" }, { status: 404 });
42
+ }
43
+ if (adapter.runMode === "provider") {
44
+ return NextResponse.json(
45
+ {
46
+ error: "This integration must be started from its provider-specific import flow.",
47
+ settingsPath: `/backend/integrations/${encodeURIComponent(parsed.data.integrationId)}`
48
+ },
49
+ { status: 422 }
50
+ );
51
+ }
52
+ if (!adapter.supportedEntities.includes(parsed.data.entityType)) {
53
+ return NextResponse.json({ error: "Unsupported entity type for this integration" }, { status: 422 });
54
+ }
55
+ const integrationEnabled = await integrationStateService.isEnabled(parsed.data.integrationId, scope);
56
+ if (!integrationEnabled) {
57
+ return NextResponse.json({ error: "Integration is disabled" }, { status: 409 });
58
+ }
59
+ const overlap = await syncRunService.findRunningOverlap(
60
+ parsed.data.integrationId,
61
+ parsed.data.entityType,
62
+ parsed.data.direction,
63
+ scope
64
+ );
65
+ if (overlap) {
66
+ return NextResponse.json({ error: "A sync run is already in progress for this integration and entity direction" }, { status: 409 });
67
+ }
68
+ const cursor = parsed.data.fullSync ? null : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope);
69
+ const { run, progressJob } = await startDataSyncRun({
70
+ syncRunService,
71
+ progressService,
72
+ scope: {
73
+ ...scope,
74
+ userId: auth.sub
47
75
  },
48
- { status: 422 }
76
+ input: {
77
+ integrationId: parsed.data.integrationId,
78
+ entityType: parsed.data.entityType,
79
+ direction: parsed.data.direction,
80
+ cursor,
81
+ triggeredBy: parsed.data.triggeredBy ?? auth.sub,
82
+ batchSize: parsed.data.batchSize
83
+ }
84
+ });
85
+ return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 });
86
+ } catch (error) {
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ const stack = error instanceof Error ? error.stack : void 0;
89
+ console.error("[data_sync.run] unhandled error", { message, stack });
90
+ return NextResponse.json(
91
+ { error: "Failed to start data sync run.", message },
92
+ { status: 500 }
49
93
  );
50
94
  }
51
- if (!adapter.supportedEntities.includes(parsed.data.entityType)) {
52
- return NextResponse.json({ error: "Unsupported entity type for this integration" }, { status: 422 });
53
- }
54
- const integrationEnabled = await integrationStateService.isEnabled(parsed.data.integrationId, scope);
55
- if (!integrationEnabled) {
56
- return NextResponse.json({ error: "Integration is disabled" }, { status: 409 });
57
- }
58
- const overlap = await syncRunService.findRunningOverlap(
59
- parsed.data.integrationId,
60
- parsed.data.entityType,
61
- parsed.data.direction,
62
- scope
63
- );
64
- if (overlap) {
65
- return NextResponse.json({ error: "A sync run is already in progress for this integration and entity direction" }, { status: 409 });
66
- }
67
- const cursor = parsed.data.fullSync ? null : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope);
68
- const { run, progressJob } = await startDataSyncRun({
69
- syncRunService,
70
- progressService,
71
- scope: {
72
- ...scope,
73
- userId: auth.sub
74
- },
75
- input: {
76
- integrationId: parsed.data.integrationId,
77
- entityType: parsed.data.entityType,
78
- direction: parsed.data.direction,
79
- cursor,
80
- triggeredBy: parsed.data.triggeredBy ?? auth.sub,
81
- batchSize: parsed.data.batchSize
82
- }
83
- });
84
- return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 });
85
95
  }
86
96
  export {
87
97
  POST,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/data_sync/api/run.ts"],
4
- "sourcesContent": ["import { 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 { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { runSyncSchema } from '../data/validators'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { getDataSyncAdapter } from '../lib/adapter-registry'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Start a data sync run',\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = runSyncSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n\n const scope = {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }\n\n const integration = getIntegration(parsed.data.integrationId)\n if (!integration?.providerKey) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const adapter = getDataSyncAdapter(integration.providerKey)\n if (!adapter) {\n return NextResponse.json({ error: 'No registered sync adapter for provider' }, { status: 404 })\n }\n if (adapter.runMode === 'provider') {\n return NextResponse.json(\n {\n error: 'This integration must be started from its provider-specific import flow.',\n settingsPath: `/backend/integrations/${encodeURIComponent(parsed.data.integrationId)}`,\n },\n { status: 422 },\n )\n }\n\n if (!adapter.supportedEntities.includes(parsed.data.entityType)) {\n return NextResponse.json({ error: 'Unsupported entity type for this integration' }, { status: 422 })\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(parsed.data.integrationId, scope)\n if (!integrationEnabled) {\n return NextResponse.json({ error: 'Integration is disabled' }, { status: 409 })\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n parsed.data.integrationId,\n parsed.data.entityType,\n parsed.data.direction,\n scope,\n )\n if (overlap) {\n return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })\n }\n\n const cursor = parsed.data.fullSync\n ? null\n : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope)\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n direction: parsed.data.direction,\n cursor,\n triggeredBy: parsed.data.triggeredBy ?? auth.sub,\n batchSize: parsed.data.batchSize,\n },\n })\n\n return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { 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 { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { runSyncSchema } from '../data/validators'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { getDataSyncAdapter } from '../lib/adapter-registry'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },\n}\n\nexport const openApi = {\n tags: ['DataSync'],\n summary: 'Start a data sync run',\n}\n\nexport async function POST(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const payload = await readJsonSafe(req)\n const parsed = runSyncSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\n const progressService = container.resolve('progressService') as ProgressService\n const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService\n\n const scope = {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }\n\n const integration = getIntegration(parsed.data.integrationId)\n if (!integration?.providerKey) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const adapter = getDataSyncAdapter(integration.providerKey)\n if (!adapter) {\n return NextResponse.json({ error: 'No registered sync adapter for provider' }, { status: 404 })\n }\n if (adapter.runMode === 'provider') {\n return NextResponse.json(\n {\n error: 'This integration must be started from its provider-specific import flow.',\n settingsPath: `/backend/integrations/${encodeURIComponent(parsed.data.integrationId)}`,\n },\n { status: 422 },\n )\n }\n\n if (!adapter.supportedEntities.includes(parsed.data.entityType)) {\n return NextResponse.json({ error: 'Unsupported entity type for this integration' }, { status: 422 })\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(parsed.data.integrationId, scope)\n if (!integrationEnabled) {\n return NextResponse.json({ error: 'Integration is disabled' }, { status: 409 })\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n parsed.data.integrationId,\n parsed.data.entityType,\n parsed.data.direction,\n scope,\n )\n if (overlap) {\n return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })\n }\n\n const cursor = parsed.data.fullSync\n ? null\n : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope)\n\n const { run, progressJob } = await startDataSyncRun({\n syncRunService,\n progressService,\n scope: {\n ...scope,\n userId: auth.sub,\n },\n input: {\n integrationId: parsed.data.integrationId,\n entityType: parsed.data.entityType,\n direction: parsed.data.direction,\n cursor,\n triggeredBy: parsed.data.triggeredBy ?? auth.sub,\n batchSize: parsed.data.batchSize,\n },\n })\n\n return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n console.error('[data_sync.run] unhandled error', { message, stack })\n return NextResponse.json(\n { error: 'Failed to start data sync run.', message },\n { status: 500 },\n )\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -101,23 +101,33 @@ async function GET(req) {
101
101
  return NextResponse.json({ items, total, page, pageSize, totalPages });
102
102
  }
103
103
  async function POST(req) {
104
- const auth = await getAuthFromRequest(req);
105
- if (!auth || !auth.tenantId) {
106
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
107
- }
108
- const body = await req.json();
109
- const parsed = createProgressJobSchema.safeParse(body);
110
- if (!parsed.success) {
111
- return NextResponse.json({ error: "Invalid input", details: parsed.error.flatten() }, { status: 400 });
104
+ try {
105
+ const auth = await getAuthFromRequest(req);
106
+ if (!auth || !auth.tenantId) {
107
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
108
+ }
109
+ const body = await req.json();
110
+ const parsed = createProgressJobSchema.safeParse(body);
111
+ if (!parsed.success) {
112
+ return NextResponse.json({ error: "Invalid input", details: parsed.error.flatten() }, { status: 400 });
113
+ }
114
+ const container = await createRequestContainer();
115
+ const progressService = container.resolve("progressService");
116
+ const job = await progressService.createJob(parsed.data, {
117
+ tenantId: auth.tenantId,
118
+ organizationId: auth.orgId,
119
+ userId: auth.sub
120
+ });
121
+ return NextResponse.json({ id: job.id }, { status: 201 });
122
+ } catch (error) {
123
+ const message = error instanceof Error ? error.message : String(error);
124
+ const stack = error instanceof Error ? error.stack : void 0;
125
+ console.error("[progress.jobs.create] unhandled error", { message, stack });
126
+ return NextResponse.json(
127
+ { error: "Failed to create progress job.", message },
128
+ { status: 500 }
129
+ );
112
130
  }
113
- const container = await createRequestContainer();
114
- const progressService = container.resolve("progressService");
115
- const job = await progressService.createJob(parsed.data, {
116
- tenantId: auth.tenantId,
117
- organizationId: auth.orgId,
118
- userId: auth.sub
119
- });
120
- return NextResponse.json({ id: job.id }, { status: 201 });
121
131
  }
122
132
  const jobListItemSchema = z.object({
123
133
  id: z.string().uuid(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/progress/api/jobs/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { ProgressJob } from '../../data/entities'\nimport { createProgressJobSchema, listProgressJobsSchema } from '../../data/validators'\nimport {\n createProgressCrudOpenApi,\n createPagedListResponseSchema,\n} from '../openapi'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['progress.create'] },\n}\n\nexport const metadata = routeMetadata\n\nconst listQuerySchema = listProgressJobsSchema\n\ntype JobRow = {\n id: string\n jobType: string\n name: string\n description: string | null\n status: string\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n cancellable: boolean\n startedAt: string | null\n finishedAt: string | null\n errorMessage: string | null\n createdAt: string | null\n tenantId: string\n organizationId: string | null\n}\n\nconst toRow = (job: ProgressJob): JobRow => ({\n id: String(job.id),\n jobType: String(job.jobType),\n name: String(job.name),\n description: job.description ?? null,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount ?? null,\n etaSeconds: job.etaSeconds ?? null,\n cancellable: !!job.cancellable,\n startedAt: job.startedAt ? job.startedAt.toISOString() : null,\n finishedAt: job.finishedAt ? job.finishedAt.toISOString() : null,\n errorMessage: job.errorMessage ?? null,\n createdAt: job.createdAt ? job.createdAt.toISOString() : null,\n tenantId: String(job.tenantId),\n organizationId: job.organizationId ? String(job.organizationId) : null,\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n status: url.searchParams.get('status') ?? undefined,\n jobType: url.searchParams.get('jobType') ?? undefined,\n parentJobId: url.searchParams.get('parentJobId') ?? undefined,\n includeCompleted: url.searchParams.get('includeCompleted') ?? undefined,\n completedSince: url.searchParams.get('completedSince') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { status, jobType, parentJobId, includeCompleted, completedSince, page, pageSize, search, sortField, sortDir } = parsed.data\n const filter: FilterQuery<ProgressJob> = {\n tenantId: auth.tenantId,\n }\n\n if (auth.orgId) {\n filter.organizationId = auth.orgId\n }\n\n if (status) {\n const statusValues = status.split(',')\n filter.status = statusValues.length > 1 ? { $in: statusValues as never } : status as never\n } else if (includeCompleted !== 'true') {\n filter.status = { $in: ['pending', 'running'] }\n }\n\n if (jobType) filter.jobType = jobType\n if (parentJobId) filter.parentJobId = parentJobId\n if (completedSince) filter.finishedAt = { $gte: new Date(completedSince) }\n\n if (search) {\n const escaped = escapeLikePattern(search)\n filter.$or = [\n { name: { $ilike: `%${escaped}%` } },\n { jobType: { $ilike: `%${escaped}%` } },\n ]\n }\n\n const fieldMap: Record<string, string> = {\n createdAt: 'createdAt',\n startedAt: 'startedAt',\n finishedAt: 'finishedAt',\n }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'createdAt'\n orderBy[mapped] = sortDir === 'asc' ? 'ASC' : 'DESC'\n } else {\n orderBy.createdAt = 'DESC'\n }\n\n const [rows, total] = await em.findAndCount(ProgressJob, filter, {\n orderBy,\n limit: pageSize,\n offset: (page - 1) * pageSize,\n })\n const items = rows.map(toRow)\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = createProgressJobSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const job = await progressService.createJob(parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ id: job.id }, { status: 201 })\n}\n\nconst jobListItemSchema = z.object({\n id: z.string().uuid(),\n jobType: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n status: z.string(),\n progressPercent: z.number(),\n processedCount: z.number(),\n totalCount: z.number().nullable(),\n etaSeconds: z.number().nullable(),\n cancellable: z.boolean(),\n startedAt: z.string().nullable(),\n finishedAt: z.string().nullable(),\n errorMessage: z.string().nullable(),\n createdAt: z.string().nullable(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createProgressCrudOpenApi({\n resourceName: 'ProgressJob',\n pluralName: 'ProgressJobs',\n querySchema: listQuerySchema,\n listResponseSchema: createPagedListResponseSchema(jobListItemSchema),\n create: {\n schema: createProgressJobSchema,\n description: 'Creates a new progress job for tracking a long-running operation.',\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { ProgressJob } from '../../data/entities'\nimport { createProgressJobSchema, listProgressJobsSchema } from '../../data/validators'\nimport {\n createProgressCrudOpenApi,\n createPagedListResponseSchema,\n} from '../openapi'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['progress.create'] },\n}\n\nexport const metadata = routeMetadata\n\nconst listQuerySchema = listProgressJobsSchema\n\ntype JobRow = {\n id: string\n jobType: string\n name: string\n description: string | null\n status: string\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n cancellable: boolean\n startedAt: string | null\n finishedAt: string | null\n errorMessage: string | null\n createdAt: string | null\n tenantId: string\n organizationId: string | null\n}\n\nconst toRow = (job: ProgressJob): JobRow => ({\n id: String(job.id),\n jobType: String(job.jobType),\n name: String(job.name),\n description: job.description ?? null,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount ?? null,\n etaSeconds: job.etaSeconds ?? null,\n cancellable: !!job.cancellable,\n startedAt: job.startedAt ? job.startedAt.toISOString() : null,\n finishedAt: job.finishedAt ? job.finishedAt.toISOString() : null,\n errorMessage: job.errorMessage ?? null,\n createdAt: job.createdAt ? job.createdAt.toISOString() : null,\n tenantId: String(job.tenantId),\n organizationId: job.organizationId ? String(job.organizationId) : null,\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n status: url.searchParams.get('status') ?? undefined,\n jobType: url.searchParams.get('jobType') ?? undefined,\n parentJobId: url.searchParams.get('parentJobId') ?? undefined,\n includeCompleted: url.searchParams.get('includeCompleted') ?? undefined,\n completedSince: url.searchParams.get('completedSince') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { status, jobType, parentJobId, includeCompleted, completedSince, page, pageSize, search, sortField, sortDir } = parsed.data\n const filter: FilterQuery<ProgressJob> = {\n tenantId: auth.tenantId,\n }\n\n if (auth.orgId) {\n filter.organizationId = auth.orgId\n }\n\n if (status) {\n const statusValues = status.split(',')\n filter.status = statusValues.length > 1 ? { $in: statusValues as never } : status as never\n } else if (includeCompleted !== 'true') {\n filter.status = { $in: ['pending', 'running'] }\n }\n\n if (jobType) filter.jobType = jobType\n if (parentJobId) filter.parentJobId = parentJobId\n if (completedSince) filter.finishedAt = { $gte: new Date(completedSince) }\n\n if (search) {\n const escaped = escapeLikePattern(search)\n filter.$or = [\n { name: { $ilike: `%${escaped}%` } },\n { jobType: { $ilike: `%${escaped}%` } },\n ]\n }\n\n const fieldMap: Record<string, string> = {\n createdAt: 'createdAt',\n startedAt: 'startedAt',\n finishedAt: 'finishedAt',\n }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'createdAt'\n orderBy[mapped] = sortDir === 'asc' ? 'ASC' : 'DESC'\n } else {\n orderBy.createdAt = 'DESC'\n }\n\n const [rows, total] = await em.findAndCount(ProgressJob, filter, {\n orderBy,\n limit: pageSize,\n offset: (page - 1) * pageSize,\n })\n const items = rows.map(toRow)\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport async function POST(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = createProgressJobSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const job = await progressService.createJob(parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ id: job.id }, { status: 201 })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n console.error('[progress.jobs.create] unhandled error', { message, stack })\n return NextResponse.json(\n { error: 'Failed to create progress job.', message },\n { status: 500 },\n )\n }\n}\n\nconst jobListItemSchema = z.object({\n id: z.string().uuid(),\n jobType: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n status: z.string(),\n progressPercent: z.number(),\n processedCount: z.number(),\n totalCount: z.number().nullable(),\n etaSeconds: z.number().nullable(),\n cancellable: z.boolean(),\n startedAt: z.string().nullable(),\n finishedAt: z.string().nullable(),\n errorMessage: z.string().nullable(),\n createdAt: z.string().nullable(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createProgressCrudOpenApi({\n resourceName: 'ProgressJob',\n pluralName: 'ProgressJobs',\n querySchema: listQuerySchema,\n listResponseSchema: createPagedListResponseSchema(jobListItemSchema),\n create: {\n schema: createProgressJobSchema,\n description: 'Creates a new progress job for tracking a long-running operation.',\n },\n})\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -37,124 +37,134 @@ const openApi = {
37
37
  }
38
38
  };
39
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
40
+ try {
41
+ const auth = await getAuthFromRequest(request);
42
+ if (!auth?.tenantId) {
43
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
44
+ }
45
+ const payload = await readJsonSafe(request);
46
+ const parsedPayload = syncExcelImportRequestSchema.safeParse(payload);
47
+ if (!parsedPayload.success) {
48
+ return NextResponse.json({ error: "Invalid import payload." }, { status: 422 });
49
+ }
50
+ const container = await createRequestContainer();
51
+ const em = container.resolve("em");
52
+ const syncRunService = container.resolve("dataSyncRunService");
53
+ const progressService = container.resolve("progressService");
54
+ const credentialsService = container.resolve("integrationCredentialsService");
55
+ const integrationStateService = container.resolve("integrationStateService");
56
+ const scopeResult = await resolveSyncExcelConcreteScope({ auth, container, request });
57
+ if (!scopeResult.ok) {
58
+ return NextResponse.json({ error: scopeResult.error }, { status: scopeResult.status });
59
+ }
60
+ const { scope } = scopeResult;
61
+ const upload = await findOneWithDecryption(
62
+ em,
63
+ SyncExcelUpload,
64
+ {
65
+ id: parsedPayload.data.uploadId,
66
+ organizationId: scope.organizationId,
67
+ tenantId: scope.tenantId
68
+ },
69
+ void 0,
70
+ scope
71
+ );
72
+ if (!upload) {
73
+ return NextResponse.json({ error: "Upload preview not found." }, { status: 404 });
74
+ }
75
+ const attachment = await findOneWithDecryption(
76
+ em,
77
+ Attachment,
78
+ {
79
+ id: upload.attachmentId,
80
+ organizationId: scope.organizationId,
81
+ tenantId: scope.tenantId
82
+ },
83
+ void 0,
84
+ scope
85
+ );
86
+ if (!attachment) {
87
+ return NextResponse.json({ error: "Upload attachment not found." }, { status: 404 });
88
+ }
89
+ if (upload.entityType !== parsedPayload.data.entityType) {
90
+ return NextResponse.json({ error: "Upload entity type does not match requested import target." }, { status: 422 });
91
+ }
92
+ if (parsedPayload.data.mapping.entityType !== parsedPayload.data.entityType) {
93
+ return NextResponse.json({ error: "Mapping entity type does not match requested import target." }, { status: 422 });
94
+ }
95
+ const overlap = await syncRunService.findRunningOverlap("sync_excel", parsedPayload.data.entityType, "import", scope);
96
+ if (overlap) {
97
+ return NextResponse.json({ error: "A sync_excel import is already in progress for this entity type." }, { status: 409 });
98
+ }
99
+ const existingMapping = await findOneWithDecryption(
100
+ em,
101
+ SyncMapping,
102
+ {
103
+ integrationId: "sync_excel",
104
+ entityType: parsedPayload.data.entityType,
105
+ organizationId: scope.organizationId,
106
+ tenantId: scope.tenantId
107
+ },
108
+ void 0,
109
+ scope
110
+ );
111
+ if (existingMapping) {
112
+ existingMapping.mapping = parsedPayload.data.mapping;
113
+ } else {
114
+ em.persist(em.create(SyncMapping, {
115
+ integrationId: "sync_excel",
116
+ entityType: parsedPayload.data.entityType,
117
+ mapping: parsedPayload.data.mapping,
118
+ organizationId: scope.organizationId,
119
+ tenantId: scope.tenantId
120
+ }));
121
+ }
122
+ await credentialsService.save("sync_excel", {}, scope);
123
+ await integrationStateService.upsert("sync_excel", { isEnabled: true }, scope);
124
+ await em.flush();
125
+ const { run, progressJob } = await startDataSyncRun({
126
+ syncRunService,
127
+ progressService,
128
+ scope: {
129
+ ...scope,
130
+ userId: auth.sub
131
+ },
132
+ input: {
133
+ integrationId: "sync_excel",
134
+ entityType: parsedPayload.data.entityType,
135
+ direction: "import",
136
+ cursor: createCursor(upload.id, 0),
137
+ triggeredBy: auth.sub,
138
+ batchSize: parsedPayload.data.batchSize ?? 100,
139
+ progressJob: {
140
+ jobType: "sync_excel:import",
141
+ name: `CSV import \u2014 ${parsedPayload.data.entityType}`,
142
+ description: upload.filename,
143
+ meta: {
144
+ integrationId: "sync_excel",
145
+ uploadId: upload.id,
146
+ hiddenFromTopBar: false
147
+ }
146
148
  }
147
149
  }
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 });
150
+ });
151
+ upload.syncRunId = run.id;
152
+ upload.status = "importing";
153
+ await em.flush();
154
+ return NextResponse.json(syncExcelImportResponseSchema.parse({
155
+ runId: run.id,
156
+ progressJobId: progressJob?.id ?? null,
157
+ status: run.status
158
+ }), { status: 201 });
159
+ } catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ const stack = error instanceof Error ? error.stack : void 0;
162
+ console.error("[sync_excel.import] unhandled error", { message, stack });
163
+ return NextResponse.json(
164
+ { error: "Failed to start sync_excel import.", message },
165
+ { status: 500 }
166
+ );
167
+ }
158
168
  }
159
169
  export {
160
170
  POST,