@open-mercato/scheduler 0.6.8-develop.7100.1.fbf66fca35 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/scheduler/__integration__/TC-SCHED-001.spec.js +2 -2
  3. package/dist/modules/scheduler/__integration__/TC-SCHED-001.spec.js.map +2 -2
  4. package/dist/modules/scheduler/__integration__/TC-SCHED-004.spec.js +4 -9
  5. package/dist/modules/scheduler/__integration__/TC-SCHED-004.spec.js.map +2 -2
  6. package/dist/modules/scheduler/__integration__/TC-SCHED-007.spec.js +3 -3
  7. package/dist/modules/scheduler/__integration__/TC-SCHED-007.spec.js.map +2 -2
  8. package/dist/modules/scheduler/__integration__/TC-SCHED-CRUDFORM-001.spec.js +3 -3
  9. package/dist/modules/scheduler/__integration__/TC-SCHED-CRUDFORM-001.spec.js.map +1 -1
  10. package/dist/modules/scheduler/__integration__/TC-UNDO-001-scheduler-jobs.spec.js +44 -107
  11. package/dist/modules/scheduler/__integration__/TC-UNDO-001-scheduler-jobs.spec.js.map +2 -2
  12. package/dist/modules/scheduler/__integration__/helpers/scheduler.js +3 -13
  13. package/dist/modules/scheduler/__integration__/helpers/scheduler.js.map +2 -2
  14. package/dist/modules/scheduler/api/jobs/[id]/executions/route.js +13 -7
  15. package/dist/modules/scheduler/api/jobs/[id]/executions/route.js.map +2 -2
  16. package/dist/modules/scheduler/api/targets/route.js +12 -2
  17. package/dist/modules/scheduler/api/targets/route.js.map +2 -2
  18. package/dist/modules/scheduler/api/trigger/route.js +13 -7
  19. package/dist/modules/scheduler/api/trigger/route.js.map +2 -2
  20. package/dist/modules/scheduler/backend/config/scheduled-jobs/page.js +1 -3
  21. package/dist/modules/scheduler/backend/config/scheduled-jobs/page.js.map +2 -2
  22. package/dist/modules/scheduler/cli.js +1 -32
  23. package/dist/modules/scheduler/cli.js.map +2 -2
  24. package/dist/modules/scheduler/commands/jobs.js +2 -84
  25. package/dist/modules/scheduler/commands/jobs.js.map +2 -2
  26. package/dist/modules/scheduler/data/validators.js +3 -27
  27. package/dist/modules/scheduler/data/validators.js.map +2 -2
  28. package/dist/modules/scheduler/i18n/de.json +0 -2
  29. package/dist/modules/scheduler/i18n/en.json +0 -2
  30. package/dist/modules/scheduler/i18n/es.json +0 -2
  31. package/dist/modules/scheduler/i18n/ko.json +0 -2
  32. package/dist/modules/scheduler/i18n/pl.json +0 -2
  33. package/dist/modules/scheduler/services/localSchedulerService.js +6 -31
  34. package/dist/modules/scheduler/services/localSchedulerService.js.map +2 -2
  35. package/dist/modules/scheduler/workers/execute-schedule.worker.js +6 -64
  36. package/dist/modules/scheduler/workers/execute-schedule.worker.js.map +2 -2
  37. package/package.json +6 -7
  38. package/src/modules/scheduler/__integration__/TC-SCHED-001.spec.ts +2 -2
  39. package/src/modules/scheduler/__integration__/TC-SCHED-004.spec.ts +8 -14
  40. package/src/modules/scheduler/__integration__/TC-SCHED-007.spec.ts +3 -3
  41. package/src/modules/scheduler/__integration__/TC-SCHED-CRUDFORM-001.spec.ts +3 -3
  42. package/src/modules/scheduler/__integration__/TC-UNDO-001-scheduler-jobs.spec.ts +66 -154
  43. package/src/modules/scheduler/__integration__/helpers/scheduler.ts +5 -24
  44. package/src/modules/scheduler/api/jobs/[id]/executions/route.ts +19 -9
  45. package/src/modules/scheduler/api/targets/__tests__/route.test.ts +4 -3
  46. package/src/modules/scheduler/api/targets/route.ts +17 -6
  47. package/src/modules/scheduler/api/trigger/route.ts +19 -9
  48. package/src/modules/scheduler/backend/config/scheduled-jobs/page.tsx +1 -4
  49. package/src/modules/scheduler/cli.ts +1 -44
  50. package/src/modules/scheduler/commands/jobs.ts +3 -137
  51. package/src/modules/scheduler/data/__tests__/validators.test.ts +4 -26
  52. package/src/modules/scheduler/data/validators.ts +2 -37
  53. package/src/modules/scheduler/i18n/de.json +0 -2
  54. package/src/modules/scheduler/i18n/en.json +0 -2
  55. package/src/modules/scheduler/i18n/es.json +0 -2
  56. package/src/modules/scheduler/i18n/ko.json +0 -2
  57. package/src/modules/scheduler/i18n/pl.json +0 -2
  58. package/src/modules/scheduler/services/__tests__/localSchedulerService.test.ts +0 -179
  59. package/src/modules/scheduler/services/localSchedulerService.ts +6 -39
  60. package/src/modules/scheduler/workers/__tests__/execute-schedule.worker.test.ts +3 -25
  61. package/src/modules/scheduler/workers/execute-schedule.worker.ts +7 -76
  62. package/dist/modules/scheduler/__integration__/TC-SCHED-008-safe-targets.spec.js +0 -72
  63. package/dist/modules/scheduler/__integration__/TC-SCHED-008-safe-targets.spec.js.map +0 -7
  64. package/dist/modules/scheduler/__integration__/TC-SCHED-008.spec.js +0 -66
  65. package/dist/modules/scheduler/__integration__/TC-SCHED-008.spec.js.map +0 -7
  66. package/dist/modules/scheduler/lib/safeQueueTargets.js +0 -157
  67. package/dist/modules/scheduler/lib/safeQueueTargets.js.map +0 -7
  68. package/dist/modules/scheduler/lib/scheduleAccess.js +0 -22
  69. package/dist/modules/scheduler/lib/scheduleAccess.js.map +0 -7
  70. package/dist/modules/scheduler/workers/test-echo-queue.worker.js +0 -22
  71. package/dist/modules/scheduler/workers/test-echo-queue.worker.js.map +0 -7
  72. package/src/modules/scheduler/__integration__/TC-SCHED-008-safe-targets.spec.ts +0 -90
  73. package/src/modules/scheduler/__integration__/TC-SCHED-008.spec.ts +0 -94
  74. package/src/modules/scheduler/commands/__tests__/jobs.target-guard.test.ts +0 -177
  75. package/src/modules/scheduler/data/__tests__/safe-targets.test.ts +0 -110
  76. package/src/modules/scheduler/lib/__tests__/safeQueueTargets.test.ts +0 -311
  77. package/src/modules/scheduler/lib/__tests__/scheduleAccess.test.ts +0 -164
  78. package/src/modules/scheduler/lib/safeQueueTargets.ts +0 -324
  79. package/src/modules/scheduler/lib/scheduleAccess.ts +0 -104
  80. package/src/modules/scheduler/workers/test-echo-queue.worker.ts +0 -25
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/scheduler/api/jobs/%5Bid%5D/executions/route.ts"],
4
- "sourcesContent": ["import { NextRequest, 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/core'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { ScheduledJob } from '../../../../data/entities.js'\nimport { resolveScheduleAccess } from '../../../../lib/scheduleAccess.js'\nimport { getRedisUrlOrThrow, parseRedisUrl } from '@open-mercato/shared/lib/redis/connection'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('scheduler').child({ component: 'executions' })\n\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['scheduler.jobs.view'],\n}\n\n/**\n * GET /api/scheduler/jobs/[id]/executions\n * Fetch execution history for a schedule from BullMQ\n * \n * Returns jobs from the scheduler-execution queue filtered by scheduleId.\n * This replaces the old /api/scheduler/runs endpoint that used the database table.\n */\nexport async function GET(\n req: NextRequest,\n { params }: { params: { id: string } }\n) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: translate('scheduler.error.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const scheduleId = params.id\n\n try {\n // Load by id alone, then apply isolation to the loaded row. Folding the actor's\n // tenant/org into the lookup would make every system-scoped schedule unmatchable.\n const schedule = await em.findOne(ScheduledJob, { id: scheduleId, deletedAt: null })\n\n if (!schedule) {\n return NextResponse.json({ error: translate('scheduler.error.not_found', 'Schedule not found') }, { status: 404 })\n }\n\n const access = resolveScheduleAccess(schedule, auth)\n if (access === 'not_found') {\n return NextResponse.json({ error: translate('scheduler.error.not_found', 'Schedule not found') }, { status: 404 })\n }\n if (access === 'forbidden') {\n return NextResponse.json({ error: translate('scheduler.error.access_denied', 'Access denied') }, { status: 403 })\n }\n\n // Check if using async strategy\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n if (queueStrategy !== 'async') {\n return NextResponse.json({\n error: translate('scheduler.error.async_strategy_required', 'Execution history requires QUEUE_STRATEGY=async'),\n message: translate('scheduler.error.async_strategy_hint', 'Please set QUEUE_STRATEGY=async to view execution history'),\n items: [],\n }, { status: 400 })\n }\n\n // Fetch jobs from BullMQ scheduler-execution queue\n const { Queue } = await import('bullmq')\n const queue = new Queue('scheduler-execution', {\n connection: parseRedisUrl(getRedisUrlOrThrow('QUEUE')),\n })\n\n try {\n // Validate query params with Zod schema\n const queryResult = executionsQuerySchema.safeParse({\n pageSize: req.nextUrl.searchParams.get('pageSize') ?? undefined,\n })\n const limit = queryResult.success ? queryResult.data.pageSize : 20\n\n const [completed, failed, active, waiting, delayed] = await Promise.all([\n queue.getCompleted(0, limit - 1),\n queue.getFailed(0, limit - 1),\n queue.getActive(0, limit - 1),\n queue.getWaiting(0, limit - 1),\n queue.getDelayed(0, limit - 1),\n ])\n\n // Combine all jobs and filter by scheduleId\n const allJobs = [...completed, ...failed, ...active, ...waiting, ...delayed]\n type BullJobData = { payload?: { scheduleId?: string; triggerType?: string; triggeredByUserId?: string }; scheduleId?: string; triggerType?: string; triggeredByUserId?: string }\n\n const filteredJobs = allJobs\n .filter(job => {\n const data = job.data as BullJobData | undefined\n return data?.payload?.scheduleId === scheduleId || data?.scheduleId === scheduleId\n })\n .slice(0, limit)\n\n // Get state for each job (parallel)\n const jobsWithState = await Promise.all(\n filteredJobs.map(async (job) => {\n const state = await job.getState()\n const data = job.data as BullJobData | undefined\n \n return {\n id: job.id,\n scheduleId: data?.payload?.scheduleId || data?.scheduleId,\n startedAt: job.processedOn ? new Date(job.processedOn).toISOString() : new Date(job.timestamp).toISOString(),\n finishedAt: job.finishedOn ? new Date(job.finishedOn).toISOString() : null,\n status: state === 'completed' ? 'completed' : state === 'failed' ? 'failed' : state === 'active' ? 'running' : 'waiting',\n triggerType: data?.payload?.triggerType || data?.triggerType || 'scheduled',\n triggeredByUserId: data?.payload?.triggeredByUserId || data?.triggeredByUserId || null,\n errorMessage: job.failedReason || null,\n errorStack: job.stacktrace ? job.stacktrace.join('\\n') : null,\n durationMs: (job.finishedOn && job.processedOn) ? (job.finishedOn - job.processedOn) : null,\n queueJobId: job.id,\n queueName: 'scheduler-execution',\n attemptsMade: job.attemptsMade,\n result: job.returnvalue,\n }\n })\n )\n\n // Sort by startedAt descending (most recent first)\n jobsWithState.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())\n\n return NextResponse.json({\n items: jobsWithState,\n total: jobsWithState.length,\n page: 1,\n pageSize: limit,\n })\n } finally {\n await queue.close()\n }\n\n } catch (error: unknown) {\n logger.error('Error fetching execution history', { err: error })\n return NextResponse.json(\n { error: error instanceof Error ? error.message : translate('scheduler.error.fetch_executions_failed', 'Failed to fetch execution history') },\n { status: 500 }\n )\n }\n}\n\n// Query schemas\nconst executionsQuerySchema = z.object({\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\n// Response schemas\nconst executionItemSchema = z.object({\n id: z.string(),\n scheduleId: z.string().uuid(),\n startedAt: z.string(),\n finishedAt: z.string().nullable(),\n status: z.enum(['running', 'completed', 'failed', 'waiting']),\n triggerType: z.enum(['scheduled', 'manual']),\n triggeredByUserId: z.string().uuid().nullable(),\n errorMessage: z.string().nullable(),\n errorStack: z.string().nullable(),\n durationMs: z.number().nullable(),\n queueJobId: z.string(),\n queueName: z.string(),\n attemptsMade: z.number(),\n result: z.unknown().nullable(),\n})\n\nconst executionsResponseSchema = z.object({\n items: z.array(executionItemSchema),\n total: z.number(),\n page: z.number(),\n pageSize: z.number(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n message: z.string().optional(),\n items: z.array(z.any()).optional(),\n})\n\n// OpenAPI specification\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Scheduler',\n summary: 'Get execution history',\n description: 'Fetches recent execution history for a scheduled job from BullMQ.',\n methods: {\n GET: {\n operationId: 'getScheduleExecutions',\n summary: 'Get execution history for a schedule',\n description: 'Fetch recent executions from BullMQ for a scheduled job. Requires QUEUE_STRATEGY=async.',\n query: executionsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Execution history',\n schema: executionsResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Local strategy not supported', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Schedule not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextRequest, 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/core'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { ScheduledJob } from '../../../../data/entities.js'\nimport { getRedisUrlOrThrow, parseRedisUrl } from '@open-mercato/shared/lib/redis/connection'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('scheduler').child({ component: 'executions' })\n\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['scheduler.jobs.view'],\n}\n\n/**\n * GET /api/scheduler/jobs/[id]/executions\n * Fetch execution history for a schedule from BullMQ\n * \n * Returns jobs from the scheduler-execution queue filtered by scheduleId.\n * This replaces the old /api/scheduler/runs endpoint that used the database table.\n */\nexport async function GET(\n req: NextRequest,\n { params }: { params: { id: string } }\n) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: translate('scheduler.error.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const scheduleId = params.id\n\n try {\n // Verify schedule exists and user has access (with tenant/org scope filter)\n const findFilter: Record<string, unknown> = {\n id: scheduleId,\n deletedAt: null,\n }\n\n // Apply tenant isolation: scope the query to the user's tenant/org\n if (auth.tenantId) {\n findFilter.tenantId = auth.tenantId\n }\n if (auth.orgId) {\n findFilter.organizationId = auth.orgId\n }\n\n const schedule = await em.findOne(ScheduledJob, findFilter)\n\n if (!schedule) {\n return NextResponse.json({ error: translate('scheduler.error.not_found', 'Schedule not found') }, { status: 404 })\n }\n\n // System-scoped schedules (no tenantId/orgId) require super-admin. Use the\n // immutable `isSuperAdmin` flag \u2014 never compare mutable/spoofable role names.\n const isSuperAdmin = auth.isSuperAdmin === true\n if (!schedule.tenantId && !schedule.organizationId && !isSuperAdmin) {\n return NextResponse.json({ error: translate('scheduler.error.access_denied', 'Access denied') }, { status: 403 })\n }\n\n // Check if using async strategy\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n if (queueStrategy !== 'async') {\n return NextResponse.json({\n error: translate('scheduler.error.async_strategy_required', 'Execution history requires QUEUE_STRATEGY=async'),\n message: translate('scheduler.error.async_strategy_hint', 'Please set QUEUE_STRATEGY=async to view execution history'),\n items: [],\n }, { status: 400 })\n }\n\n // Fetch jobs from BullMQ scheduler-execution queue\n const { Queue } = await import('bullmq')\n const queue = new Queue('scheduler-execution', {\n connection: parseRedisUrl(getRedisUrlOrThrow('QUEUE')),\n })\n\n try {\n // Validate query params with Zod schema\n const queryResult = executionsQuerySchema.safeParse({\n pageSize: req.nextUrl.searchParams.get('pageSize') ?? undefined,\n })\n const limit = queryResult.success ? queryResult.data.pageSize : 20\n\n const [completed, failed, active, waiting, delayed] = await Promise.all([\n queue.getCompleted(0, limit - 1),\n queue.getFailed(0, limit - 1),\n queue.getActive(0, limit - 1),\n queue.getWaiting(0, limit - 1),\n queue.getDelayed(0, limit - 1),\n ])\n\n // Combine all jobs and filter by scheduleId\n const allJobs = [...completed, ...failed, ...active, ...waiting, ...delayed]\n type BullJobData = { payload?: { scheduleId?: string; triggerType?: string; triggeredByUserId?: string }; scheduleId?: string; triggerType?: string; triggeredByUserId?: string }\n\n const filteredJobs = allJobs\n .filter(job => {\n const data = job.data as BullJobData | undefined\n return data?.payload?.scheduleId === scheduleId || data?.scheduleId === scheduleId\n })\n .slice(0, limit)\n\n // Get state for each job (parallel)\n const jobsWithState = await Promise.all(\n filteredJobs.map(async (job) => {\n const state = await job.getState()\n const data = job.data as BullJobData | undefined\n \n return {\n id: job.id,\n scheduleId: data?.payload?.scheduleId || data?.scheduleId,\n startedAt: job.processedOn ? new Date(job.processedOn).toISOString() : new Date(job.timestamp).toISOString(),\n finishedAt: job.finishedOn ? new Date(job.finishedOn).toISOString() : null,\n status: state === 'completed' ? 'completed' : state === 'failed' ? 'failed' : state === 'active' ? 'running' : 'waiting',\n triggerType: data?.payload?.triggerType || data?.triggerType || 'scheduled',\n triggeredByUserId: data?.payload?.triggeredByUserId || data?.triggeredByUserId || null,\n errorMessage: job.failedReason || null,\n errorStack: job.stacktrace ? job.stacktrace.join('\\n') : null,\n durationMs: (job.finishedOn && job.processedOn) ? (job.finishedOn - job.processedOn) : null,\n queueJobId: job.id,\n queueName: 'scheduler-execution',\n attemptsMade: job.attemptsMade,\n result: job.returnvalue,\n }\n })\n )\n\n // Sort by startedAt descending (most recent first)\n jobsWithState.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())\n\n return NextResponse.json({\n items: jobsWithState,\n total: jobsWithState.length,\n page: 1,\n pageSize: limit,\n })\n } finally {\n await queue.close()\n }\n\n } catch (error: unknown) {\n logger.error('Error fetching execution history', { err: error })\n return NextResponse.json(\n { error: error instanceof Error ? error.message : translate('scheduler.error.fetch_executions_failed', 'Failed to fetch execution history') },\n { status: 500 }\n )\n }\n}\n\n// Query schemas\nconst executionsQuerySchema = z.object({\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\n// Response schemas\nconst executionItemSchema = z.object({\n id: z.string(),\n scheduleId: z.string().uuid(),\n startedAt: z.string(),\n finishedAt: z.string().nullable(),\n status: z.enum(['running', 'completed', 'failed', 'waiting']),\n triggerType: z.enum(['scheduled', 'manual']),\n triggeredByUserId: z.string().uuid().nullable(),\n errorMessage: z.string().nullable(),\n errorStack: z.string().nullable(),\n durationMs: z.number().nullable(),\n queueJobId: z.string(),\n queueName: z.string(),\n attemptsMade: z.number(),\n result: z.unknown().nullable(),\n})\n\nconst executionsResponseSchema = z.object({\n items: z.array(executionItemSchema),\n total: z.number(),\n page: z.number(),\n pageSize: z.number(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n message: z.string().optional(),\n items: z.array(z.any()).optional(),\n})\n\n// OpenAPI specification\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Scheduler',\n summary: 'Get execution history',\n description: 'Fetches recent execution history for a scheduled job from BullMQ.',\n methods: {\n GET: {\n operationId: 'getScheduleExecutions',\n summary: 'Get execution history for a schedule',\n description: 'Fetch recent executions from BullMQ for a scheduled job. Requires QUEUE_STRATEGY=async.',\n query: executionsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Execution history',\n schema: executionsResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Local strategy not supported', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Schedule not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,9 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { getModules } from "@open-mercato/shared/lib/modules/registry";
4
5
  import { commandRegistry } from "@open-mercato/shared/lib/commands/registry";
5
6
  import { listSchedulerSafeCommands } from "../../lib/scheduler-safe-commands.js";
6
- import { listSchedulerSafeQueueTargets } from "../../lib/safeQueueTargets.js";
7
7
  const metadata = {
8
8
  requireAuth: true,
9
9
  requireFeatures: ["scheduler.jobs.view"]
@@ -13,7 +13,16 @@ async function GET(req) {
13
13
  if (!auth) {
14
14
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
15
15
  }
16
- const queues = listSchedulerSafeQueueTargets().map((target) => ({ value: target.queue, label: target.queue }));
16
+ const modules = getModules();
17
+ const queueSet = /* @__PURE__ */ new Set();
18
+ for (const mod of modules) {
19
+ if (mod.workers) {
20
+ for (const worker of mod.workers) {
21
+ queueSet.add(worker.queue);
22
+ }
23
+ }
24
+ }
25
+ const queues = Array.from(queueSet).sort((a, b) => a.localeCompare(b)).map((queue) => ({ value: queue, label: queue }));
17
26
  const commands = listSchedulerSafeCommands().filter((command) => commandRegistry.has(command.commandId)).map((command) => ({ value: command.commandId, label: command.commandId }));
18
27
  return NextResponse.json({ queues, commands });
19
28
  }
@@ -36,6 +45,7 @@ const openApi = {
36
45
  GET: {
37
46
  operationId: "listScheduleTargets",
38
47
  summary: "List available queues and commands",
48
+ description: "Returns all registered queue names (from module workers) and explicitly scheduler-safe command IDs that can be used as schedule targets.",
39
49
  responses: [
40
50
  {
41
51
  status: 200,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/scheduler/api/targets/route.ts"],
4
- "sourcesContent": ["import { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { commandRegistry } from '@open-mercato/shared/lib/commands/registry'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { listSchedulerSafeCommands } from '../../lib/scheduler-safe-commands'\nimport { listSchedulerSafeQueueTargets } from '../../lib/safeQueueTargets'\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['scheduler.jobs.view'],\n}\n\n/**\n * GET /api/scheduler/targets\n * Returns scheduler-safe queue names and command IDs for schedule target selection.\n * Only queues whose workers opted into scheduling (`schedulerSafe: true`) are\n * advertised; internal and system-only workers stay undiscoverable (#5213).\n */\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const queues = listSchedulerSafeQueueTargets()\n .map((target) => ({ value: target.queue, label: target.queue }))\n\n const commands = listSchedulerSafeCommands()\n .filter((command) => commandRegistry.has(command.commandId))\n .map((command) => ({ value: command.commandId, label: command.commandId }))\n\n return NextResponse.json({ queues, commands })\n}\n\n// Response schemas\nconst targetOptionSchema = z.object({\n value: z.string(),\n label: z.string(),\n})\n\nconst targetsResponseSchema = z.object({\n queues: z.array(targetOptionSchema),\n commands: z.array(targetOptionSchema),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Scheduler',\n summary: 'List available schedule targets',\n description: 'Returns available queue names and scheduler-safe command IDs for schedule target selection.',\n methods: {\n GET: {\n operationId: 'listScheduleTargets',\n summary: 'List available queues and commands',\n responses: [\n {\n status: 200,\n description: 'Available targets',\n schema: targetsResponseSchema,\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAsB,oBAAoB;AAC1C,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAEhC,SAAS,iCAAiC;AAC1C,SAAS,qCAAqC;AAEvC,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,qBAAqB;AACzC;AAQA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,SAAS,8BAA8B,EAC1C,IAAI,CAAC,YAAY,EAAE,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM,EAAE;AAEjE,QAAM,WAAW,0BAA0B,EACxC,OAAO,CAAC,YAAY,gBAAgB,IAAI,QAAQ,SAAS,CAAC,EAC1D,IAAI,CAAC,aAAa,EAAE,OAAO,QAAQ,WAAW,OAAO,QAAQ,UAAU,EAAE;AAE5E,SAAO,aAAa,KAAK,EAAE,QAAQ,SAAS,CAAC;AAC/C;AAGA,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,QAAQ,EAAE,MAAM,kBAAkB;AAAA,EAClC,UAAU,EAAE,MAAM,kBAAkB;AACtC,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { getModules } from '@open-mercato/shared/lib/modules/registry'\nimport { commandRegistry } from '@open-mercato/shared/lib/commands/registry'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { listSchedulerSafeCommands } from '../../lib/scheduler-safe-commands'\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['scheduler.jobs.view'],\n}\n\n/**\n * GET /api/scheduler/targets\n * Returns available queue names and command IDs for schedule target selection.\n */\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const modules = getModules()\n\n const queueSet = new Set<string>()\n for (const mod of modules) {\n if (mod.workers) {\n for (const worker of mod.workers) {\n queueSet.add(worker.queue)\n }\n }\n }\n\n const queues = Array.from(queueSet)\n .sort((a, b) => a.localeCompare(b))\n .map((queue) => ({ value: queue, label: queue }))\n\n const commands = listSchedulerSafeCommands()\n .filter((command) => commandRegistry.has(command.commandId))\n .map((command) => ({ value: command.commandId, label: command.commandId }))\n\n return NextResponse.json({ queues, commands })\n}\n\n// Response schemas\nconst targetOptionSchema = z.object({\n value: z.string(),\n label: z.string(),\n})\n\nconst targetsResponseSchema = z.object({\n queues: z.array(targetOptionSchema),\n commands: z.array(targetOptionSchema),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Scheduler',\n summary: 'List available schedule targets',\n description: 'Returns available queue names and scheduler-safe command IDs for schedule target selection.',\n methods: {\n GET: {\n operationId: 'listScheduleTargets',\n summary: 'List available queues and commands',\n description: 'Returns all registered queue names (from module workers) and explicitly scheduler-safe command IDs that can be used as schedule targets.',\n responses: [\n {\n status: 200,\n description: 'Available targets',\n schema: targetsResponseSchema,\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAsB,oBAAoB;AAC1C,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAEhC,SAAS,iCAAiC;AAEnC,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,qBAAqB;AACzC;AAMA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,UAAU,WAAW;AAE3B,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,OAAO,SAAS;AACzB,QAAI,IAAI,SAAS;AACf,iBAAW,UAAU,IAAI,SAAS;AAChC,iBAAS,IAAI,OAAO,KAAK;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,KAAK,QAAQ,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC,EACjC,IAAI,CAAC,WAAW,EAAE,OAAO,OAAO,OAAO,MAAM,EAAE;AAElD,QAAM,WAAW,0BAA0B,EACxC,OAAO,CAAC,YAAY,gBAAgB,IAAI,QAAQ,SAAS,CAAC,EAC1D,IAAI,CAAC,aAAa,EAAE,OAAO,QAAQ,WAAW,OAAO,QAAQ,UAAU,EAAE;AAE5E,SAAO,aAAa,KAAK,EAAE,QAAQ,SAAS,CAAC;AAC/C;AAGA,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,QAAQ,EAAE,MAAM,kBAAkB;AAAA,EAClC,UAAU,EAAE,MAAM,kBAAkB;AACtC,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -5,7 +5,6 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
5
  import { createQueue } from "@open-mercato/queue";
6
6
  import { getRedisUrlOrThrow } from "@open-mercato/shared/lib/redis/connection";
7
7
  import { ScheduledJob } from "../../data/entities.js";
8
- import { resolveScheduleAccess } from "../../lib/scheduleAccess.js";
9
8
  import { scheduleTriggerSchema } from "../../data/validators.js";
10
9
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
11
10
  import { createLogger } from "@open-mercato/shared/lib/logger";
@@ -25,15 +24,22 @@ async function POST(req) {
25
24
  try {
26
25
  const body = await req.json();
27
26
  const input = scheduleTriggerSchema.parse(body);
28
- const schedule = await em.findOne(ScheduledJob, { id: input.id, deletedAt: null });
29
- if (!schedule) {
30
- return NextResponse.json({ error: translate("scheduler.error.not_found", "Schedule not found") }, { status: 404 });
27
+ const findFilter = {
28
+ id: input.id,
29
+ deletedAt: null
30
+ };
31
+ if (auth.tenantId) {
32
+ findFilter.tenantId = auth.tenantId;
31
33
  }
32
- const access = resolveScheduleAccess(schedule, auth);
33
- if (access === "not_found") {
34
+ if (auth.orgId) {
35
+ findFilter.organizationId = auth.orgId;
36
+ }
37
+ const schedule = await em.findOne(ScheduledJob, findFilter);
38
+ if (!schedule) {
34
39
  return NextResponse.json({ error: translate("scheduler.error.not_found", "Schedule not found") }, { status: 404 });
35
40
  }
36
- if (access === "forbidden") {
41
+ const isSuperAdmin = auth.isSuperAdmin === true;
42
+ if (!schedule.tenantId && !schedule.organizationId && !isSuperAdmin) {
37
43
  return NextResponse.json({ error: translate("scheduler.error.access_denied", "Access denied") }, { status: 403 });
38
44
  }
39
45
  const queueStrategy = process.env.QUEUE_STRATEGY || "local";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/scheduler/api/trigger/route.ts"],
4
- "sourcesContent": ["import { NextRequest, 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/core'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createQueue } from '@open-mercato/queue'\nimport { getRedisUrlOrThrow } from '@open-mercato/shared/lib/redis/connection'\nimport { ScheduledJob } from '../../data/entities.js'\nimport { resolveScheduleAccess } from '../../lib/scheduleAccess.js'\nimport { scheduleTriggerSchema } from '../../data/validators.js'\nimport type { ExecuteSchedulePayload } from '../../workers/execute-schedule.worker.js'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('scheduler').child({ component: 'trigger' })\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['scheduler.jobs.trigger'],\n}\n\n/**\n * POST /api/scheduler/trigger\n * Manually trigger a schedule\n * \n * This enqueues the schedule execution job in BullMQ.\n * Execution history is tracked in BullMQ job state.\n */\nexport async function POST(req: NextRequest) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: translate('scheduler.error.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n try {\n const body = await req.json()\n const input = scheduleTriggerSchema.parse(body)\n\n // Load by id alone, then apply isolation to the loaded row. Folding the actor's\n // tenant/org into the lookup would make every system-scoped schedule unmatchable.\n const schedule = await em.findOne(ScheduledJob, { id: input.id, deletedAt: null })\n\n if (!schedule) {\n return NextResponse.json({ error: translate('scheduler.error.not_found', 'Schedule not found') }, { status: 404 })\n }\n\n const access = resolveScheduleAccess(schedule, auth)\n if (access === 'not_found') {\n return NextResponse.json({ error: translate('scheduler.error.not_found', 'Schedule not found') }, { status: 404 })\n }\n if (access === 'forbidden') {\n return NextResponse.json({ error: translate('scheduler.error.access_denied', 'Access denied') }, { status: 403 })\n }\n\n // Check if using async queue strategy\n const queueStrategy = (process.env.QUEUE_STRATEGY || 'local') as 'local' | 'async'\n \n if (queueStrategy !== 'async') {\n return NextResponse.json(\n { \n error: translate('scheduler.error.trigger_async_required', 'Manual trigger requires QUEUE_STRATEGY=async'),\n message: translate('scheduler.error.trigger_async_hint', 'Execution history and manual triggers are only available with BullMQ (async strategy)')\n },\n { status: 400 }\n )\n }\n\n // Enqueue execution job to scheduler-execution queue\n const executionQueue = createQueue<ExecuteSchedulePayload>('scheduler-execution', queueStrategy, {\n connection: { url: getRedisUrlOrThrow('QUEUE') },\n })\n\n const payload: ExecuteSchedulePayload = {\n scheduleId: schedule.id,\n tenantId: schedule.tenantId,\n organizationId: schedule.organizationId,\n scopeType: schedule.scopeType,\n triggerType: 'manual',\n triggeredByUserId: auth.sub,\n }\n\n const jobId = await executionQueue.enqueue(payload)\n await executionQueue.close()\n\n logger.info('Manually triggered schedule', {\n scheduleId: schedule.id,\n scheduleName: schedule.name,\n jobId,\n triggeredBy: auth.sub,\n })\n\n return NextResponse.json({\n ok: true,\n jobId, // BullMQ job ID\n message: translate('scheduler.success.triggered', 'Schedule queued for execution'),\n })\n\n } catch (error) {\n logger.error('Manual trigger failed', { err: error })\n return NextResponse.json(\n { error: error instanceof Error ? error.message : translate('scheduler.error.trigger_failed', 'Failed to trigger schedule') },\n { status: 400 }\n )\n }\n}\n\n// Response schemas\nconst triggerResponseSchema = z.object({\n ok: z.boolean(),\n jobId: z.string(),\n message: z.string(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n message: z.string().optional(),\n})\n\n// OpenAPI specification\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Scheduler',\n summary: 'Manually trigger a schedule',\n description: 'Execute a schedule immediately by enqueueing it in the scheduler-execution queue. Requires QUEUE_STRATEGY=async.',\n methods: {\n POST: {\n operationId: 'triggerScheduledJob',\n summary: 'Manually trigger a schedule',\n description: 'Executes a scheduled job immediately, bypassing the scheduled time. Only works with async queue strategy.',\n requestBody: {\n schema: scheduleTriggerSchema,\n contentType: 'application/json',\n },\n responses: [\n {\n status: 200,\n description: 'Schedule triggered successfully',\n schema: triggerResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request or local strategy not supported', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Schedule not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAsB,oBAAoB;AAC1C,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AAEtC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,UAAU,CAAC;AAEhE,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,wBAAwB;AAC5C;AASA,eAAsB,KAAK,KAAkB;AAC3C,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,KAAK;AACd,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,gCAAgC,cAAc,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChH;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAuB,IAAI;AAEhD,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,QAAQ,sBAAsB,MAAM,IAAI;AAI9C,UAAM,WAAW,MAAM,GAAG,QAAQ,cAAc,EAAE,IAAI,MAAM,IAAI,WAAW,KAAK,CAAC;AAEjF,QAAI,CAAC,UAAU;AACb,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,6BAA6B,oBAAoB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACnH;AAEA,UAAM,SAAS,sBAAsB,UAAU,IAAI;AACnD,QAAI,WAAW,aAAa;AAC1B,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,6BAA6B,oBAAoB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACnH;AACA,QAAI,WAAW,aAAa;AAC1B,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,iCAAiC,eAAe,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAClH;AAGA,UAAM,gBAAiB,QAAQ,IAAI,kBAAkB;AAErD,QAAI,kBAAkB,SAAS;AAC7B,aAAO,aAAa;AAAA,QAClB;AAAA,UACE,OAAO,UAAU,0CAA0C,8CAA8C;AAAA,UACzG,SAAS,UAAU,sCAAsC,uFAAuF;AAAA,QAClJ;AAAA,QACA,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,iBAAiB,YAAoC,uBAAuB,eAAe;AAAA,MAC/F,YAAY,EAAE,KAAK,mBAAmB,OAAO,EAAE;AAAA,IACjD,CAAC;AAED,UAAM,UAAkC;AAAA,MACtC,YAAY,SAAS;AAAA,MACrB,UAAU,SAAS;AAAA,MACnB,gBAAgB,SAAS;AAAA,MACzB,WAAW,SAAS;AAAA,MACpB,aAAa;AAAA,MACb,mBAAmB,KAAK;AAAA,IAC1B;AAEA,UAAM,QAAQ,MAAM,eAAe,QAAQ,OAAO;AAClD,UAAM,eAAe,MAAM;AAE3B,WAAO,KAAK,+BAA+B;AAAA,MACzC,YAAY,SAAS;AAAA,MACrB,cAAc,SAAS;AAAA,MACvB;AAAA,MACA,aAAa,KAAK;AAAA,IACpB,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,IAAI;AAAA,MACJ;AAAA;AAAA,MACA,SAAS,UAAU,+BAA+B,+BAA+B;AAAA,IACnF,CAAC;AAAA,EAEH,SAAS,OAAO;AACd,WAAO,MAAM,yBAAyB,EAAE,KAAK,MAAM,CAAC;AACpD,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,UAAU,kCAAkC,4BAA4B,EAAE;AAAA,MAC5H,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAGA,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,IAAI,EAAE,QAAQ;AAAA,EACd,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO;AACpB,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAGM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,QAAQ;AAAA,QACR,aAAa;AAAA,MACf;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,mDAAmD,QAAQ,oBAAoB;AAAA,QAC3G,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,QACxE,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,oBAAoB;AAAA,QACzE,EAAE,QAAQ,KAAK,aAAa,sBAAsB,QAAQ,oBAAoB;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextRequest, 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/core'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createQueue } from '@open-mercato/queue'\nimport { getRedisUrlOrThrow } from '@open-mercato/shared/lib/redis/connection'\nimport { ScheduledJob } from '../../data/entities.js'\nimport { scheduleTriggerSchema } from '../../data/validators.js'\nimport type { ExecuteSchedulePayload } from '../../workers/execute-schedule.worker.js'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('scheduler').child({ component: 'trigger' })\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['scheduler.jobs.trigger'],\n}\n\n/**\n * POST /api/scheduler/trigger\n * Manually trigger a schedule\n * \n * This enqueues the schedule execution job in BullMQ.\n * Execution history is tracked in BullMQ job state.\n */\nexport async function POST(req: NextRequest) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: translate('scheduler.error.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n try {\n const body = await req.json()\n const input = scheduleTriggerSchema.parse(body)\n\n // Find the schedule with tenant/org scope filter\n const findFilter: Record<string, unknown> = {\n id: input.id,\n deletedAt: null,\n }\n\n // Apply tenant isolation: scope the query to the user's tenant/org\n if (auth.tenantId) {\n findFilter.tenantId = auth.tenantId\n }\n if (auth.orgId) {\n findFilter.organizationId = auth.orgId\n }\n\n const schedule = await em.findOne(ScheduledJob, findFilter)\n\n if (!schedule) {\n return NextResponse.json({ error: translate('scheduler.error.not_found', 'Schedule not found') }, { status: 404 })\n }\n\n // System-scoped schedules (no tenantId/orgId) require super-admin. Use the\n // immutable `isSuperAdmin` flag \u2014 never compare mutable/spoofable role names.\n const isSuperAdmin = auth.isSuperAdmin === true\n if (!schedule.tenantId && !schedule.organizationId && !isSuperAdmin) {\n return NextResponse.json({ error: translate('scheduler.error.access_denied', 'Access denied') }, { status: 403 })\n }\n\n // Check if using async queue strategy\n const queueStrategy = (process.env.QUEUE_STRATEGY || 'local') as 'local' | 'async'\n \n if (queueStrategy !== 'async') {\n return NextResponse.json(\n { \n error: translate('scheduler.error.trigger_async_required', 'Manual trigger requires QUEUE_STRATEGY=async'),\n message: translate('scheduler.error.trigger_async_hint', 'Execution history and manual triggers are only available with BullMQ (async strategy)')\n },\n { status: 400 }\n )\n }\n\n // Enqueue execution job to scheduler-execution queue\n const executionQueue = createQueue<ExecuteSchedulePayload>('scheduler-execution', queueStrategy, {\n connection: { url: getRedisUrlOrThrow('QUEUE') },\n })\n\n const payload: ExecuteSchedulePayload = {\n scheduleId: schedule.id,\n tenantId: schedule.tenantId,\n organizationId: schedule.organizationId,\n scopeType: schedule.scopeType,\n triggerType: 'manual',\n triggeredByUserId: auth.sub,\n }\n\n const jobId = await executionQueue.enqueue(payload)\n await executionQueue.close()\n\n logger.info('Manually triggered schedule', {\n scheduleId: schedule.id,\n scheduleName: schedule.name,\n jobId,\n triggeredBy: auth.sub,\n })\n\n return NextResponse.json({\n ok: true,\n jobId, // BullMQ job ID\n message: translate('scheduler.success.triggered', 'Schedule queued for execution'),\n })\n\n } catch (error) {\n logger.error('Manual trigger failed', { err: error })\n return NextResponse.json(\n { error: error instanceof Error ? error.message : translate('scheduler.error.trigger_failed', 'Failed to trigger schedule') },\n { status: 400 }\n )\n }\n}\n\n// Response schemas\nconst triggerResponseSchema = z.object({\n ok: z.boolean(),\n jobId: z.string(),\n message: z.string(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n message: z.string().optional(),\n})\n\n// OpenAPI specification\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Scheduler',\n summary: 'Manually trigger a schedule',\n description: 'Execute a schedule immediately by enqueueing it in the scheduler-execution queue. Requires QUEUE_STRATEGY=async.',\n methods: {\n POST: {\n operationId: 'triggerScheduledJob',\n summary: 'Manually trigger a schedule',\n description: 'Executes a scheduled job immediately, bypassing the scheduled time. Only works with async queue strategy.',\n requestBody: {\n schema: scheduleTriggerSchema,\n contentType: 'application/json',\n },\n responses: [\n {\n status: 200,\n description: 'Schedule triggered successfully',\n schema: triggerResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request or local strategy not supported', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Schedule not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -44,7 +44,6 @@ function SchedulerPage() {
44
44
  const [pageSize] = React.useState(20);
45
45
  const [total, setTotal] = React.useState(0);
46
46
  const [totalPages, setTotalPages] = React.useState(1);
47
- const [totalIsCapped, setTotalIsCapped] = React.useState(false);
48
47
  const [search, setSearch] = React.useState("");
49
48
  const [isLoading, setIsLoading] = React.useState(false);
50
49
  const scopeVersion = useOrganizationScopeVersion();
@@ -64,7 +63,6 @@ function SchedulerPage() {
64
63
  setRows(mapped);
65
64
  setTotal(result?.total ?? 0);
66
65
  setTotalPages(result?.totalPages ?? 1);
67
- setTotalIsCapped(result?.totalIsCapped === true);
68
66
  } catch (error) {
69
67
  flash(t("scheduler.error.fetch_failed", "Failed to load schedules"), "error");
70
68
  setRows([]);
@@ -214,7 +212,7 @@ function SchedulerPage() {
214
212
  createLabel: t("scheduler.action.create", "New Schedule")
215
213
  }
216
214
  ),
217
- pagination: { page, pageSize, total, totalPages, totalIsCapped, onPageChange: setPage },
215
+ pagination: { page, pageSize, total, totalPages, onPageChange: setPage },
218
216
  isLoading,
219
217
  searchValue: search,
220
218
  onSearchChange: (value) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/scheduler/backend/config/scheduled-jobs/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\n\ntype ScheduleRow = {\n id: string\n name: string\n description?: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue?: string | null\n targetCommand?: string | null\n isEnabled: boolean\n sourceType: 'user' | 'module'\n sourceModule?: string | null\n nextRunAt?: string | null\n lastRunAt?: string | null\n}\n\ntype SchedulesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n page?: number\n totalPages?: number\n totalIsCapped?: boolean\n}\n\nfunction mapApiItem(item: Record<string, unknown>): ScheduleRow | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n\n return {\n id,\n name: typeof item.name === 'string' ? item.name : '',\n description: typeof item.description === 'string' ? item.description : null,\n scopeType: (item.scopeType === 'system' || item.scopeType === 'organization' || item.scopeType === 'tenant')\n ? item.scopeType\n : 'tenant',\n scheduleType: (item.scheduleType === 'cron' || item.scheduleType === 'interval')\n ? item.scheduleType\n : 'cron',\n scheduleValue: typeof item.scheduleValue === 'string' ? item.scheduleValue : '',\n timezone: typeof item.timezone === 'string' ? item.timezone : 'UTC',\n targetType: (item.targetType === 'queue' || item.targetType === 'command')\n ? item.targetType\n : 'queue',\n targetQueue: typeof item.targetQueue === 'string' ? item.targetQueue : null,\n targetCommand: typeof item.targetCommand === 'string' ? item.targetCommand : null,\n isEnabled: item.isEnabled === true,\n sourceType: (item.sourceType === 'user' || item.sourceType === 'module')\n ? item.sourceType\n : 'user',\n sourceModule: typeof item.sourceModule === 'string' ? item.sourceModule : null,\n nextRunAt: typeof item.nextRunAt === 'string' ? item.nextRunAt : null,\n lastRunAt: typeof item.lastRunAt === 'string' ? item.lastRunAt : null,\n }\n}\n\n\nexport default function SchedulerPage() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const [rows, setRows] = React.useState<ScheduleRow[]>([])\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(20)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(false)\n const scopeVersion = useOrganizationScopeVersion()\n\n const fetchSchedules = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: page.toString(),\n pageSize: pageSize.toString(),\n })\n if (search) params.set('search', search)\n\n const { result } = await apiCallOrThrow<SchedulesResponse>(\n `/api/scheduler/jobs?${params.toString()}`\n )\n\n const items = result?.items ?? []\n const mapped = items.map(mapApiItem).filter((x): x is ScheduleRow => x !== null)\n setRows(mapped)\n setTotal(result?.total ?? 0)\n setTotalPages(result?.totalPages ?? 1)\n setTotalIsCapped(result?.totalIsCapped === true)\n } catch (error) {\n flash(t('scheduler.error.fetch_failed', 'Failed to load schedules'), 'error')\n setRows([])\n setTotal(0)\n setTotalPages(1)\n } finally {\n setIsLoading(false)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [page, pageSize, search, scopeVersion, t])\n\n React.useEffect(() => {\n fetchSchedules()\n }, [fetchSchedules])\n\n const handleDelete = React.useCallback(\n async (row: ScheduleRow) => {\n const confirmed = await confirm({\n title: t('scheduler.confirm.delete', 'Are you sure you want to delete this schedule?'),\n variant: 'destructive',\n })\n if (!confirmed) {\n return\n }\n\n try {\n await apiCallOrThrow(`/api/scheduler/jobs`, {\n method: 'DELETE',\n body: JSON.stringify({ id: row.id }),\n })\n flash(t('scheduler.success.deleted', 'Schedule deleted successfully'), 'success')\n fetchSchedules()\n } catch (error) {\n flash(t('scheduler.error.delete_failed', 'Failed to delete schedule'), 'error')\n }\n },\n [confirm, t, fetchSchedules]\n )\n\n const handleTrigger = React.useCallback(\n async (row: ScheduleRow) => {\n try {\n await apiCallOrThrow(`/api/scheduler/trigger`, {\n method: 'POST',\n body: JSON.stringify({ id: row.id }),\n })\n flash(t('scheduler.success.triggered', 'Schedule triggered successfully'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('scheduler.error.trigger_failed', 'Failed to trigger schedule')\n flash(message, 'error')\n }\n },\n [t]\n )\n\n const columns = React.useMemo<ColumnDef<ScheduleRow>[]>(\n () => [\n {\n id: 'name',\n accessorKey: 'name',\n header: t('scheduler.field.name', 'Name'),\n cell: ({ row }) => (\n <span className=\"font-medium\">{row.original.name}</span>\n ),\n },\n {\n id: 'scheduleType',\n accessorKey: 'scheduleType',\n header: t('scheduler.field.schedule_type', 'Type'),\n cell: ({ row }) => (\n <span className=\"capitalize\">{row.original.scheduleType}</span>\n ),\n },\n {\n id: 'scheduleValue',\n accessorKey: 'scheduleValue',\n header: t('scheduler.field.schedule', 'Schedule'),\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.scheduleValue}\n </span>\n ),\n },\n {\n id: 'targetType',\n accessorKey: 'targetType',\n header: t('scheduler.field.target', 'Target'),\n cell: ({ row }) => {\n const target = row.original.targetType === 'queue'\n ? row.original.targetQueue\n : row.original.targetCommand\n return (\n <div className=\"flex flex-col\">\n <span className=\"text-xs text-muted-foreground capitalize\">{row.original.targetType}</span>\n <span className=\"text-sm\">{target || '-'}</span>\n </div>\n )\n },\n },\n {\n id: 'nextRunAt',\n accessorKey: 'nextRunAt',\n header: t('scheduler.field.next_run', 'Next Run'),\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {formatDateTime(row.original.nextRunAt) || '-'}\n </span>\n ),\n },\n {\n id: 'isEnabled',\n accessorKey: 'isEnabled',\n header: t('scheduler.field.active', 'Active'),\n cell: ({ row }) => <BooleanIcon value={row.original.isEnabled} />,\n },\n {\n id: 'sourceType',\n accessorKey: 'sourceType',\n header: t('scheduler.field.source', 'Source'),\n cell: ({ row }) => (\n <span className=\"text-xs capitalize\">\n {row.original.sourceType}\n {row.original.sourceModule && ` (${row.original.sourceModule})`}\n </span>\n ),\n },\n ],\n [t]\n )\n\n const rowActions = React.useCallback(\n (row: ScheduleRow): RowActionItem[] => [\n {\n id: 'view',\n label: t('scheduler.action.view', 'View Details'),\n onSelect: () => router.push(`/backend/config/scheduled-jobs/${row.id}`),\n },\n {\n id: 'edit',\n label: t('scheduler.action.edit', 'Edit'),\n onSelect: () => router.push(`/backend/config/scheduled-jobs/${row.id}/edit`),\n },\n {\n id: 'trigger',\n label: t('scheduler.action.trigger', 'Run Now'),\n onSelect: () => handleTrigger(row),\n },\n {\n id: 'delete',\n label: t('scheduler.action.delete', 'Delete'),\n onSelect: () => handleDelete(row),\n destructive: true,\n },\n ],\n [t, router, handleDelete, handleTrigger]\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable<ScheduleRow>\n title={t('scheduler.title', 'Scheduled Jobs')}\n actions={\n <Button onClick={() => router.push('/backend/config/scheduled-jobs/new')}>\n {t('scheduler.action.create', 'New Schedule')}\n </Button>\n }\n columns={columns}\n data={rows}\n onRowClick={(row) => router.push(`/backend/config/scheduled-jobs/${row.id}`)}\n rowActions={(row) => <RowActions items={rowActions(row)} />}\n emptyState={(\n <ListEmptyState\n entityName={t('scheduler.title', 'Scheduled Jobs')}\n createHref=\"/backend/config/scheduled-jobs/new\"\n createLabel={t('scheduler.action.create', 'New Schedule')}\n />\n )}\n pagination={{ page, pageSize, total, totalPages, totalIsCapped, onPageChange: setPage }}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('scheduler.search.placeholder', 'Search schedules...')}\n refreshButton={{\n label: t('scheduler.action.refresh', 'Refresh'),\n onRefresh: fetchSchedules,\n }}\n />\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\n\ntype ScheduleRow = {\n id: string\n name: string\n description?: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue?: string | null\n targetCommand?: string | null\n isEnabled: boolean\n sourceType: 'user' | 'module'\n sourceModule?: string | null\n nextRunAt?: string | null\n lastRunAt?: string | null\n}\n\ntype SchedulesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n page?: number\n totalPages?: number\n}\n\nfunction mapApiItem(item: Record<string, unknown>): ScheduleRow | null {\n const id = typeof item.id === 'string' ? item.id : null\n if (!id) return null\n\n return {\n id,\n name: typeof item.name === 'string' ? item.name : '',\n description: typeof item.description === 'string' ? item.description : null,\n scopeType: (item.scopeType === 'system' || item.scopeType === 'organization' || item.scopeType === 'tenant')\n ? item.scopeType\n : 'tenant',\n scheduleType: (item.scheduleType === 'cron' || item.scheduleType === 'interval')\n ? item.scheduleType\n : 'cron',\n scheduleValue: typeof item.scheduleValue === 'string' ? item.scheduleValue : '',\n timezone: typeof item.timezone === 'string' ? item.timezone : 'UTC',\n targetType: (item.targetType === 'queue' || item.targetType === 'command')\n ? item.targetType\n : 'queue',\n targetQueue: typeof item.targetQueue === 'string' ? item.targetQueue : null,\n targetCommand: typeof item.targetCommand === 'string' ? item.targetCommand : null,\n isEnabled: item.isEnabled === true,\n sourceType: (item.sourceType === 'user' || item.sourceType === 'module')\n ? item.sourceType\n : 'user',\n sourceModule: typeof item.sourceModule === 'string' ? item.sourceModule : null,\n nextRunAt: typeof item.nextRunAt === 'string' ? item.nextRunAt : null,\n lastRunAt: typeof item.lastRunAt === 'string' ? item.lastRunAt : null,\n }\n}\n\n\nexport default function SchedulerPage() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const [rows, setRows] = React.useState<ScheduleRow[]>([])\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(20)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(false)\n const scopeVersion = useOrganizationScopeVersion()\n\n const fetchSchedules = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: page.toString(),\n pageSize: pageSize.toString(),\n })\n if (search) params.set('search', search)\n\n const { result } = await apiCallOrThrow<SchedulesResponse>(\n `/api/scheduler/jobs?${params.toString()}`\n )\n\n const items = result?.items ?? []\n const mapped = items.map(mapApiItem).filter((x): x is ScheduleRow => x !== null)\n setRows(mapped)\n setTotal(result?.total ?? 0)\n setTotalPages(result?.totalPages ?? 1)\n } catch (error) {\n flash(t('scheduler.error.fetch_failed', 'Failed to load schedules'), 'error')\n setRows([])\n setTotal(0)\n setTotalPages(1)\n } finally {\n setIsLoading(false)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [page, pageSize, search, scopeVersion, t])\n\n React.useEffect(() => {\n fetchSchedules()\n }, [fetchSchedules])\n\n const handleDelete = React.useCallback(\n async (row: ScheduleRow) => {\n const confirmed = await confirm({\n title: t('scheduler.confirm.delete', 'Are you sure you want to delete this schedule?'),\n variant: 'destructive',\n })\n if (!confirmed) {\n return\n }\n\n try {\n await apiCallOrThrow(`/api/scheduler/jobs`, {\n method: 'DELETE',\n body: JSON.stringify({ id: row.id }),\n })\n flash(t('scheduler.success.deleted', 'Schedule deleted successfully'), 'success')\n fetchSchedules()\n } catch (error) {\n flash(t('scheduler.error.delete_failed', 'Failed to delete schedule'), 'error')\n }\n },\n [confirm, t, fetchSchedules]\n )\n\n const handleTrigger = React.useCallback(\n async (row: ScheduleRow) => {\n try {\n await apiCallOrThrow(`/api/scheduler/trigger`, {\n method: 'POST',\n body: JSON.stringify({ id: row.id }),\n })\n flash(t('scheduler.success.triggered', 'Schedule triggered successfully'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('scheduler.error.trigger_failed', 'Failed to trigger schedule')\n flash(message, 'error')\n }\n },\n [t]\n )\n\n const columns = React.useMemo<ColumnDef<ScheduleRow>[]>(\n () => [\n {\n id: 'name',\n accessorKey: 'name',\n header: t('scheduler.field.name', 'Name'),\n cell: ({ row }) => (\n <span className=\"font-medium\">{row.original.name}</span>\n ),\n },\n {\n id: 'scheduleType',\n accessorKey: 'scheduleType',\n header: t('scheduler.field.schedule_type', 'Type'),\n cell: ({ row }) => (\n <span className=\"capitalize\">{row.original.scheduleType}</span>\n ),\n },\n {\n id: 'scheduleValue',\n accessorKey: 'scheduleValue',\n header: t('scheduler.field.schedule', 'Schedule'),\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.scheduleValue}\n </span>\n ),\n },\n {\n id: 'targetType',\n accessorKey: 'targetType',\n header: t('scheduler.field.target', 'Target'),\n cell: ({ row }) => {\n const target = row.original.targetType === 'queue'\n ? row.original.targetQueue\n : row.original.targetCommand\n return (\n <div className=\"flex flex-col\">\n <span className=\"text-xs text-muted-foreground capitalize\">{row.original.targetType}</span>\n <span className=\"text-sm\">{target || '-'}</span>\n </div>\n )\n },\n },\n {\n id: 'nextRunAt',\n accessorKey: 'nextRunAt',\n header: t('scheduler.field.next_run', 'Next Run'),\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {formatDateTime(row.original.nextRunAt) || '-'}\n </span>\n ),\n },\n {\n id: 'isEnabled',\n accessorKey: 'isEnabled',\n header: t('scheduler.field.active', 'Active'),\n cell: ({ row }) => <BooleanIcon value={row.original.isEnabled} />,\n },\n {\n id: 'sourceType',\n accessorKey: 'sourceType',\n header: t('scheduler.field.source', 'Source'),\n cell: ({ row }) => (\n <span className=\"text-xs capitalize\">\n {row.original.sourceType}\n {row.original.sourceModule && ` (${row.original.sourceModule})`}\n </span>\n ),\n },\n ],\n [t]\n )\n\n const rowActions = React.useCallback(\n (row: ScheduleRow): RowActionItem[] => [\n {\n id: 'view',\n label: t('scheduler.action.view', 'View Details'),\n onSelect: () => router.push(`/backend/config/scheduled-jobs/${row.id}`),\n },\n {\n id: 'edit',\n label: t('scheduler.action.edit', 'Edit'),\n onSelect: () => router.push(`/backend/config/scheduled-jobs/${row.id}/edit`),\n },\n {\n id: 'trigger',\n label: t('scheduler.action.trigger', 'Run Now'),\n onSelect: () => handleTrigger(row),\n },\n {\n id: 'delete',\n label: t('scheduler.action.delete', 'Delete'),\n onSelect: () => handleDelete(row),\n destructive: true,\n },\n ],\n [t, router, handleDelete, handleTrigger]\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable<ScheduleRow>\n title={t('scheduler.title', 'Scheduled Jobs')}\n actions={\n <Button onClick={() => router.push('/backend/config/scheduled-jobs/new')}>\n {t('scheduler.action.create', 'New Schedule')}\n </Button>\n }\n columns={columns}\n data={rows}\n onRowClick={(row) => router.push(`/backend/config/scheduled-jobs/${row.id}`)}\n rowActions={(row) => <RowActions items={rowActions(row)} />}\n emptyState={(\n <ListEmptyState\n entityName={t('scheduler.title', 'Scheduled Jobs')}\n createHref=\"/backend/config/scheduled-jobs/new\"\n createLabel={t('scheduler.action.create', 'New Schedule')}\n />\n )}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('scheduler.search.placeholder', 'Search schedules...')}\n refreshButton={{\n label: t('scheduler.action.refresh', 'Refresh'),\n onRefresh: fetchSchedules,\n }}\n />\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,6 @@
1
1
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
2
  import { ScheduledJob } from "./data/entities.js";
3
3
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
4
- import { auditSchedulerModuleQueueRows } from "./lib/safeQueueTargets.js";
5
4
  const writeLine = (text = "") => {
6
5
  process.stdout.write(`${text}
7
6
  `);
@@ -183,37 +182,7 @@ const startCommand = {
183
182
  }
184
183
  }
185
184
  };
186
- const auditQueueTargetsCommand = {
187
- command: "audit-queue-targets",
188
- async run(rest) {
189
- const args = parseArgs(rest);
190
- const { resolve } = await createRequestContainer();
191
- const em = resolve("em");
192
- const where = { deletedAt: null, sourceType: "module" };
193
- if (args.tenant || args.tenantId) where.tenantId = args.tenant || args.tenantId;
194
- const rows = await em.find(ScheduledJob, where, {
195
- orderBy: { name: "ASC" }
196
- });
197
- const audits = auditSchedulerModuleQueueRows(rows);
198
- writeLine(`Module-authored queue-target schedules: ${audits.length}`);
199
- let allowedCount = 0;
200
- for (const audit of audits) {
201
- const verdict = audit.dispatchAllowed ? "ALLOWED" : "blocked";
202
- if (audit.dispatchAllowed) allowedCount += 1;
203
- writeLine(
204
- `[${verdict}] ${audit.scheduleId}${audit.name ? ` \u2014 ${audit.name}` : ""} module=${audit.sourceModule ?? "(none)"} queue=${audit.targetQueue ?? "(none)"} enabled=${audit.isEnabled ? "yes" : "no"}`
205
- );
206
- }
207
- if (allowedCount > 0) {
208
- writeLine("");
209
- writeLine(`Review the ${allowedCount} ALLOWED row(s) above against your known module integrations.`);
210
- writeLine("Pre-upgrade rows created with a non-user-bound API key cannot be distinguished");
211
- writeLine("from genuine registrations at rest; disable anything unrecognized:");
212
- writeLine(" yarn mercato scheduler list --tenant <tenantId>");
213
- }
214
- }
215
- };
216
- var cli_default = [listCommand, statusCommand, runCommand, startCommand, auditQueueTargetsCommand];
185
+ var cli_default = [listCommand, statusCommand, runCommand, startCommand];
217
186
  export {
218
187
  cli_default as default
219
188
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/scheduler/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { ScheduledJob } from './data/entities.js'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { auditSchedulerModuleQueueRows } from './lib/safeQueueTargets'\n\nconst writeLine = (text = '') => {\n process.stdout.write(`${text}\\n`)\n}\n\nconst writeErrorLine = (text: string) => {\n process.stderr.write(`${text}\\n`)\n}\n\nfunction parseArgs(rest: string[]): Record<string, string> {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n const value = rest[i + 1]\n if (key) args[key] = value ?? ''\n }\n return args\n}\n\nconst listCommand: ModuleCli = {\n command: 'list',\n async run(rest) {\n const args = parseArgs(rest)\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const where: Record<string, unknown> = { deletedAt: null }\n\n // Filter by tenant if provided\n if (args.tenant || args.tenantId) {\n where.tenantId = args.tenant || args.tenantId\n }\n\n // Filter by scope type if provided\n if (args.scope || args.scopeType) {\n where.scopeType = args.scope || args.scopeType\n }\n\n // Filter by enabled status if provided\n if (args.enabled) {\n const parsed = parseBooleanToken(args.enabled)\n if (parsed !== null) {\n where.isEnabled = parsed\n }\n }\n\n const jobs = await em.find(ScheduledJob, where, {\n orderBy: { isEnabled: 'DESC', name: 'ASC' },\n })\n\n if (jobs.length === 0) {\n writeLine('No scheduled jobs found.')\n return\n }\n\n writeLine(`\\nFound ${jobs.length} scheduled job(s):\\n`)\n writeLine('ID'.padEnd(38) + 'Name'.padEnd(35) + 'Type'.padEnd(12) + 'Schedule'.padEnd(20) + 'Status'.padEnd(10) + 'Next Run')\n writeLine('-'.repeat(140))\n\n for (const job of jobs) {\n const id = String(job.id).padEnd(38)\n const name = (job.name || '').substring(0, 33).padEnd(35)\n const type = job.scheduleType.padEnd(12)\n const schedule = (job.scheduleValue || '').substring(0, 18).padEnd(20)\n const status = (job.isEnabled ? '\u2713 Enabled' : '\u2717 Disabled').padEnd(10)\n const nextRun = job.nextRunAt ? new Date(job.nextRunAt).toISOString() : 'N/A'\n writeLine(`${id}${name}${type}${schedule}${status}${nextRun}`)\n }\n\n writeLine('')\n },\n}\n\nconst statusCommand: ModuleCli = {\n command: 'status',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const totalCount = await em.count(ScheduledJob, { deletedAt: null })\n const enabledCount = await em.count(ScheduledJob, { isEnabled: true, deletedAt: null })\n const dueCount = await em.count(ScheduledJob, {\n isEnabled: true,\n deletedAt: null,\n nextRunAt: { $lte: new Date() },\n })\n\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine('\\n\uD83D\uDCCA Scheduler Status\\n')\n writeLine(`Strategy: ${queueStrategy === 'async' ? 'BullMQ (async)' : 'Local (polling)'}`)\n \n if (queueStrategy === 'local') {\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n writeLine(`Poll Interval: ${Math.round(pollInterval / 1000)}s`)\n }\n \n writeLine('')\n writeLine('Schedules:')\n writeLine(` Total: ${totalCount}`)\n writeLine(` Enabled: ${enabledCount}`)\n writeLine(` Due Now: ${dueCount}`)\n writeLine('')\n },\n}\n\nconst runCommand: ModuleCli = {\n command: 'run',\n async run(rest) {\n const scheduleId = rest[0]\n if (!scheduleId) {\n writeErrorLine('Usage: mercato scheduler run <schedule-id>')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const queueService = resolve('queueService') as { getQueue(name: string): { add(name: string, data: unknown): Promise<unknown> } }\n\n const job = await em.findOne(ScheduledJob, { id: scheduleId, deletedAt: null })\n if (!job) {\n writeErrorLine(`Schedule not found: ${scheduleId}`)\n return\n }\n\n writeLine(`\\n\uD83D\uDE80 Manually triggering schedule: ${job.name}\\n`)\n writeLine(` ID: ${job.id}`)\n writeLine(` Type: ${job.scheduleType}`)\n writeLine(` Schedule: ${job.scheduleValue}`)\n writeLine(` Target: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('')\n\n try {\n // Manually enqueue the job (triggering the scheduler-execution worker)\n const schedulerQueue = queueService.getQueue('scheduler-execution')\n await schedulerQueue.add('execute-schedule', { scheduleId: job.id })\n\n writeLine('\u2713 Job successfully triggered via scheduler-execution queue')\n writeLine(` The worker will pick it up and enqueue to: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('\u2713 Manual trigger completed\\n')\n } catch (error: unknown) {\n writeErrorLine(`\u2717 Failed to trigger job: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n },\n}\n\nconst startCommand: ModuleCli = {\n command: 'start',\n async run() {\n const { resolve } = await createRequestContainer()\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine(`\uD83D\uDE80 Starting scheduler (strategy: ${queueStrategy})...\\n`)\n\n if (queueStrategy === 'async') {\n // BullMQ strategy: Sync schedules with BullMQ repeatable jobs\n try {\n const bullmqService = resolve('bullmqSchedulerService') as { syncAll(): Promise<void> } | undefined\n \n if (!bullmqService) {\n writeErrorLine('\u274C BullMQSchedulerService not available.')\n writeErrorLine(' Set QUEUE_STRATEGY=async and configure REDIS_URL.')\n process.exit(1)\n }\n\n // Sync all enabled schedules with BullMQ\n await bullmqService.syncAll()\n\n writeLine('\u2713 Scheduler sync completed')\n writeLine('')\n writeLine('BullMQ is managing all schedules.')\n writeLine('Start workers to process jobs:')\n writeLine(' yarn mercato worker:start')\n writeLine('')\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to sync schedules: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n } else {\n // Local strategy: Start polling engine\n try {\n const localService = resolve('localSchedulerService') as { start(): Promise<void>; stop(): Promise<void> } | undefined\n \n if (!localService) {\n writeErrorLine('\u274C LocalSchedulerService not available.')\n writeErrorLine(' This should not happen in local mode.')\n process.exit(1)\n }\n\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n\n // Start the local polling engine\n await localService.start()\n\n writeLine(`\u2713 Local scheduler started (polling every ${Math.round(pollInterval / 1000)} seconds)`)\n writeLine('Press Ctrl+C to stop.')\n writeLine('')\n writeLine('\uD83D\uDCA1 Tip: For production, use QUEUE_STRATEGY=async with Redis.')\n writeLine('')\n\n // Keep the process alive and handle graceful shutdown\n const gracefulShutdown = async () => {\n writeLine('\\n\uD83D\uDCDB Shutting down...')\n await localService.stop()\n writeLine('\u2713 Stopped')\n process.exit(0)\n }\n\n process.on('SIGINT', gracefulShutdown)\n process.on('SIGTERM', gracefulShutdown)\n\n // Keep process alive\n await new Promise(() => {}) // Never resolves\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to start local scheduler: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n }\n },\n}\n\n// Lists every persisted module-authored queue-target row with its current\n// dispatch verdict (#5213). Rows authored before the hardening by a\n// non-user-bound API key are indistinguishable from genuine registrations at\n// rest, so operators review the ALLOWED entries against their known integrations.\nconst auditQueueTargetsCommand: ModuleCli = {\n command: 'audit-queue-targets',\n async run(rest) {\n const args = parseArgs(rest)\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const where: Record<string, unknown> = { deletedAt: null, sourceType: 'module' }\n if (args.tenant || args.tenantId) where.tenantId = args.tenant || args.tenantId\n\n const rows = await em.find(ScheduledJob, where, {\n orderBy: { name: 'ASC' },\n })\n\n const audits = auditSchedulerModuleQueueRows(rows)\n\n writeLine(`Module-authored queue-target schedules: ${audits.length}`)\n let allowedCount = 0\n for (const audit of audits) {\n const verdict = audit.dispatchAllowed ? 'ALLOWED' : 'blocked'\n if (audit.dispatchAllowed) allowedCount += 1\n writeLine(\n `[${verdict}] ${audit.scheduleId}${audit.name ? ` \u2014 ${audit.name}` : ''}` +\n ` module=${audit.sourceModule ?? '(none)'} queue=${audit.targetQueue ?? '(none)'}` +\n ` enabled=${audit.isEnabled ? 'yes' : 'no'}`,\n )\n }\n\n if (allowedCount > 0) {\n writeLine('')\n writeLine(`Review the ${allowedCount} ALLOWED row(s) above against your known module integrations.`)\n writeLine('Pre-upgrade rows created with a non-user-bound API key cannot be distinguished')\n writeLine('from genuine registrations at rest; disable anything unrecognized:')\n writeLine(' yarn mercato scheduler list --tenant <tenantId>')\n }\n },\n}\n\nexport default [listCommand, statusCommand, runCommand, startCommand, auditQueueTargetsCommand]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { ScheduledJob } from './data/entities.js'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst writeLine = (text = '') => {\n process.stdout.write(`${text}\\n`)\n}\n\nconst writeErrorLine = (text: string) => {\n process.stderr.write(`${text}\\n`)\n}\n\nfunction parseArgs(rest: string[]): Record<string, string> {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n const value = rest[i + 1]\n if (key) args[key] = value ?? ''\n }\n return args\n}\n\nconst listCommand: ModuleCli = {\n command: 'list',\n async run(rest) {\n const args = parseArgs(rest)\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const where: Record<string, unknown> = { deletedAt: null }\n\n // Filter by tenant if provided\n if (args.tenant || args.tenantId) {\n where.tenantId = args.tenant || args.tenantId\n }\n\n // Filter by scope type if provided\n if (args.scope || args.scopeType) {\n where.scopeType = args.scope || args.scopeType\n }\n\n // Filter by enabled status if provided\n if (args.enabled) {\n const parsed = parseBooleanToken(args.enabled)\n if (parsed !== null) {\n where.isEnabled = parsed\n }\n }\n\n const jobs = await em.find(ScheduledJob, where, {\n orderBy: { isEnabled: 'DESC', name: 'ASC' },\n })\n\n if (jobs.length === 0) {\n writeLine('No scheduled jobs found.')\n return\n }\n\n writeLine(`\\nFound ${jobs.length} scheduled job(s):\\n`)\n writeLine('ID'.padEnd(38) + 'Name'.padEnd(35) + 'Type'.padEnd(12) + 'Schedule'.padEnd(20) + 'Status'.padEnd(10) + 'Next Run')\n writeLine('-'.repeat(140))\n\n for (const job of jobs) {\n const id = String(job.id).padEnd(38)\n const name = (job.name || '').substring(0, 33).padEnd(35)\n const type = job.scheduleType.padEnd(12)\n const schedule = (job.scheduleValue || '').substring(0, 18).padEnd(20)\n const status = (job.isEnabled ? '\u2713 Enabled' : '\u2717 Disabled').padEnd(10)\n const nextRun = job.nextRunAt ? new Date(job.nextRunAt).toISOString() : 'N/A'\n writeLine(`${id}${name}${type}${schedule}${status}${nextRun}`)\n }\n\n writeLine('')\n },\n}\n\nconst statusCommand: ModuleCli = {\n command: 'status',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const totalCount = await em.count(ScheduledJob, { deletedAt: null })\n const enabledCount = await em.count(ScheduledJob, { isEnabled: true, deletedAt: null })\n const dueCount = await em.count(ScheduledJob, {\n isEnabled: true,\n deletedAt: null,\n nextRunAt: { $lte: new Date() },\n })\n\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine('\\n\uD83D\uDCCA Scheduler Status\\n')\n writeLine(`Strategy: ${queueStrategy === 'async' ? 'BullMQ (async)' : 'Local (polling)'}`)\n \n if (queueStrategy === 'local') {\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n writeLine(`Poll Interval: ${Math.round(pollInterval / 1000)}s`)\n }\n \n writeLine('')\n writeLine('Schedules:')\n writeLine(` Total: ${totalCount}`)\n writeLine(` Enabled: ${enabledCount}`)\n writeLine(` Due Now: ${dueCount}`)\n writeLine('')\n },\n}\n\nconst runCommand: ModuleCli = {\n command: 'run',\n async run(rest) {\n const scheduleId = rest[0]\n if (!scheduleId) {\n writeErrorLine('Usage: mercato scheduler run <schedule-id>')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const queueService = resolve('queueService') as { getQueue(name: string): { add(name: string, data: unknown): Promise<unknown> } }\n\n const job = await em.findOne(ScheduledJob, { id: scheduleId, deletedAt: null })\n if (!job) {\n writeErrorLine(`Schedule not found: ${scheduleId}`)\n return\n }\n\n writeLine(`\\n\uD83D\uDE80 Manually triggering schedule: ${job.name}\\n`)\n writeLine(` ID: ${job.id}`)\n writeLine(` Type: ${job.scheduleType}`)\n writeLine(` Schedule: ${job.scheduleValue}`)\n writeLine(` Target: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('')\n\n try {\n // Manually enqueue the job (triggering the scheduler-execution worker)\n const schedulerQueue = queueService.getQueue('scheduler-execution')\n await schedulerQueue.add('execute-schedule', { scheduleId: job.id })\n\n writeLine('\u2713 Job successfully triggered via scheduler-execution queue')\n writeLine(` The worker will pick it up and enqueue to: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('\u2713 Manual trigger completed\\n')\n } catch (error: unknown) {\n writeErrorLine(`\u2717 Failed to trigger job: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n },\n}\n\nconst startCommand: ModuleCli = {\n command: 'start',\n async run() {\n const { resolve } = await createRequestContainer()\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine(`\uD83D\uDE80 Starting scheduler (strategy: ${queueStrategy})...\\n`)\n\n if (queueStrategy === 'async') {\n // BullMQ strategy: Sync schedules with BullMQ repeatable jobs\n try {\n const bullmqService = resolve('bullmqSchedulerService') as { syncAll(): Promise<void> } | undefined\n \n if (!bullmqService) {\n writeErrorLine('\u274C BullMQSchedulerService not available.')\n writeErrorLine(' Set QUEUE_STRATEGY=async and configure REDIS_URL.')\n process.exit(1)\n }\n\n // Sync all enabled schedules with BullMQ\n await bullmqService.syncAll()\n\n writeLine('\u2713 Scheduler sync completed')\n writeLine('')\n writeLine('BullMQ is managing all schedules.')\n writeLine('Start workers to process jobs:')\n writeLine(' yarn mercato worker:start')\n writeLine('')\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to sync schedules: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n } else {\n // Local strategy: Start polling engine\n try {\n const localService = resolve('localSchedulerService') as { start(): Promise<void>; stop(): Promise<void> } | undefined\n \n if (!localService) {\n writeErrorLine('\u274C LocalSchedulerService not available.')\n writeErrorLine(' This should not happen in local mode.')\n process.exit(1)\n }\n\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n\n // Start the local polling engine\n await localService.start()\n\n writeLine(`\u2713 Local scheduler started (polling every ${Math.round(pollInterval / 1000)} seconds)`)\n writeLine('Press Ctrl+C to stop.')\n writeLine('')\n writeLine('\uD83D\uDCA1 Tip: For production, use QUEUE_STRATEGY=async with Redis.')\n writeLine('')\n\n // Keep the process alive and handle graceful shutdown\n const gracefulShutdown = async () => {\n writeLine('\\n\uD83D\uDCDB Shutting down...')\n await localService.stop()\n writeLine('\u2713 Stopped')\n process.exit(0)\n }\n\n process.on('SIGINT', gracefulShutdown)\n process.on('SIGTERM', gracefulShutdown)\n\n // Keep process alive\n await new Promise(() => {}) // Never resolves\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to start local scheduler: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n }\n },\n}\n\nexport default [listCommand, statusCommand, runCommand, startCommand]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,11 +7,6 @@ import { ScheduledJob } from "../data/entities.js";
7
7
  import { calculateNextRunForWrite } from "../lib/nextRunCalculator.js";
8
8
  import { enforceTenantActiveScheduleLimit } from "../lib/activeScheduleLimits.js";
9
9
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
10
- import {
11
- assertSchedulerQueueTargetAuthorized,
12
- isSchedulerSafeQueue,
13
- validateSchedulerTargetPayload
14
- } from "../lib/safeQueueTargets.js";
15
10
  async function loadScheduleSnapshot(em, scheduleId) {
16
11
  const schedule = await em.findOne(ScheduledJob, { id: scheduleId });
17
12
  if (!schedule) return null;
@@ -41,48 +36,6 @@ function toDate(value) {
41
36
  if (!value) return null;
42
37
  return value instanceof Date ? value : new Date(value);
43
38
  }
44
- function canonicalJson(value) {
45
- if (value === null || typeof value !== "object") return JSON.stringify(value ?? null);
46
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
47
- const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
48
- return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
49
- }
50
- function normalizeTargetPayloadValue(value) {
51
- if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) return null;
52
- return value;
53
- }
54
- function targetPayloadChanged(inputValue, storedValue) {
55
- return targetValueChanged(
56
- normalizeTargetPayloadValue(inputValue),
57
- normalizeTargetPayloadValue(storedValue)
58
- );
59
- }
60
- function targetValueChanged(inputValue, storedValue) {
61
- if (inputValue === void 0) return false;
62
- return canonicalJson(inputValue ?? null) !== canonicalJson(storedValue ?? null);
63
- }
64
- async function assertQueueTargetAuthorizedForActor(ctx, queue) {
65
- const rbacService = (() => {
66
- try {
67
- return ctx.container?.resolve("rbacService") ?? null;
68
- } catch {
69
- return null;
70
- }
71
- })();
72
- const reason = await assertSchedulerQueueTargetAuthorized({
73
- queue,
74
- actorUserId: resolveCommandActorUserId(ctx),
75
- tenantId: ctx.auth?.tenantId ?? null,
76
- organizationId: ctx.auth?.orgId ?? null,
77
- isSuperAdmin: ctx.auth?.isSuperAdmin === true,
78
- rbacService
79
- });
80
- if (reason) throw new CrudHttpError(403, { error: reason });
81
- }
82
- async function assertQueueTargetPayloadAllowed(queue, targetPayload) {
83
- const reason = validateSchedulerTargetPayload(queue, targetPayload);
84
- if (reason) throw new CrudHttpError(422, { error: `Invalid target payload for queue ${queue}: ${reason}` });
85
- }
86
39
  function resolveCommandActorUserId(ctx) {
87
40
  const auth = ctx.auth;
88
41
  if (!auth) return null;
@@ -167,10 +120,6 @@ const createScheduleCommand = {
167
120
  if (!nextRunAt) {
168
121
  throw new Error("Failed to calculate next run time");
169
122
  }
170
- if (input.targetType === "queue" && input.targetQueue) {
171
- await assertQueueTargetAuthorizedForActor(ctx, input.targetQueue);
172
- await assertQueueTargetPayloadAllowed(input.targetQueue, input.targetPayload);
173
- }
174
123
  const schedule = em.create(ScheduledJob, {
175
124
  name: input.name,
176
125
  description: input.description ?? null,
@@ -186,8 +135,8 @@ const createScheduleCommand = {
186
135
  targetPayload: input.targetPayload ?? null,
187
136
  requireFeature: input.requireFeature ?? null,
188
137
  isEnabled: input.isEnabled ?? true,
189
- sourceType: "user",
190
- sourceModule: null,
138
+ sourceType: input.sourceType ?? "user",
139
+ sourceModule: input.sourceModule ?? null,
191
140
  nextRunAt,
192
141
  createdByUserId: resolveCommandActorUserId(ctx),
193
142
  createdAt: /* @__PURE__ */ new Date(),
@@ -250,40 +199,9 @@ const updateScheduleCommand = {
250
199
  ensureTenantScope(ctx, schedule.tenantId);
251
200
  if (schedule.organizationId) ensureOrganizationScope(ctx, schedule.organizationId);
252
201
  ensureCanManageSystemScopedJob(ctx, schedule);
253
- const touchesModuleOwnedTarget = schedule.sourceType === "module" && (targetValueChanged(input.targetType, schedule.targetType) || targetValueChanged(input.targetQueue, schedule.targetQueue) || targetValueChanged(input.targetCommand, schedule.targetCommand) || targetPayloadChanged(input.targetPayload, schedule.targetPayload));
254
- if (touchesModuleOwnedTarget) {
255
- const { translate } = await resolveTranslations();
256
- throw new CrudHttpError(403, {
257
- error: translate(
258
- "scheduler.errors.module_target_immutable",
259
- "Module-authored schedules own their target; only operational fields can be updated here."
260
- )
261
- });
262
- }
263
202
  if (input.isEnabled === true && schedule.isEnabled !== true) {
264
203
  await enforceTenantActiveScheduleLimit(em, schedule.tenantId);
265
204
  }
266
- const effectiveTargetType = input.targetType ?? schedule.targetType;
267
- const effectiveQueue = input.targetQueue ?? schedule.targetQueue;
268
- const queueChanged = effectiveTargetType === "queue" && (targetValueChanged(input.targetQueue, schedule.targetQueue) || targetValueChanged(input.targetType, schedule.targetType) || targetPayloadChanged(input.targetPayload, schedule.targetPayload));
269
- if (effectiveTargetType === "queue" && effectiveQueue) {
270
- const retargetedToUnapprovedQueue = input.targetQueue !== void 0 && input.targetQueue !== schedule.targetQueue && !isSchedulerSafeQueue(input.targetQueue);
271
- const keepsUnapprovedQueueActive = effectiveQueue != null && !isSchedulerSafeQueue(effectiveQueue) && input.isEnabled !== false && schedule.isEnabled !== false;
272
- if ((retargetedToUnapprovedQueue || keepsUnapprovedQueueActive) && schedule.sourceType !== "module") {
273
- const { translate } = await resolveTranslations();
274
- throw new CrudHttpError(422, {
275
- error: translate(
276
- "scheduler.errors.queue_not_approved",
277
- "Target queue is not an approved scheduler target. Only workers that opted into scheduling can be selected."
278
- )
279
- });
280
- }
281
- if (queueChanged) {
282
- await assertQueueTargetAuthorizedForActor(ctx, effectiveQueue);
283
- const effectivePayload = input.targetPayload !== void 0 ? input.targetPayload : schedule.targetPayload;
284
- await assertQueueTargetPayloadAllowed(effectiveQueue, effectivePayload);
285
- }
286
- }
287
205
  const scheduleChanged = input.scheduleType !== void 0 || input.scheduleValue !== void 0 || input.timezone !== void 0;
288
206
  const nextRunAt = scheduleChanged ? calculateNextRunForWrite(
289
207
  input.scheduleType ?? schedule.scheduleType,