@gadmin2n/schematics 0.0.87 → 0.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +16 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.codegen +40 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +76 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.web +53 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +219 -33
- package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +250 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +4 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/dev/postgres/init.sql +12 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +170 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +254 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +8 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/prismaModels.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +39 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/audit.seed.ts +40 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +56 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +39 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/{scripts/sync-data-mngt-pages.ts → seed/data-mngt.seed.ts} +36 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/game.seed.ts +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +30 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +130 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts +60 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +11 -25
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +108 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +41 -57
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +309 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +315 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +312 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +317 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +309 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +299 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +307 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +31 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +309 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +205 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.ts +116 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +158 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +110 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.spec.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.ts +54 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +34 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +457 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +241 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +34 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +24 -30
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +34 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +36 -36
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +34 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +48 -24
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +312 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +152 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +12 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +270 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +23 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +70 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +246 -90
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +96 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/nginx.conf +74 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/ElementInspector.tsx +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/promptGenerator.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +4 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +9 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +156 -139
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +14 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/PublishModal.tsx +4 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +18 -27
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +32 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +48 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas-page/index.tsx +3 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +16 -16
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +28 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +6 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +0 -63
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/sync-resources.ts +0 -100
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +0 -302
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.spec.ts +0 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +0 -87
- /package/dist/lib/application/files/gadmin2-game-angle-demo/{GRACEFUL-DEPLOYMENT.md → server/GRACEFUL-DEPLOYMENT.md} +0 -0
|
@@ -1,10 +1,56 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Injectable,
|
|
4
|
+
Logger,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
UnauthorizedException,
|
|
7
|
+
} from '@nestjs/common';
|
|
2
8
|
import { Prisma } from '@prisma/client';
|
|
3
9
|
import { PrismaService } from 'nestjs-prisma';
|
|
10
|
+
import { validateDslGraph } from './dsl-validate.util';
|
|
11
|
+
import { TemporalService } from './temporal.service';
|
|
12
|
+
import { verifyWebhookSignature } from './webhook-signature.util';
|
|
4
13
|
|
|
5
14
|
@Injectable()
|
|
6
15
|
export class WorkflowService {
|
|
7
|
-
|
|
16
|
+
private readonly logger = new Logger(WorkflowService.name);
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
private prisma: PrismaService,
|
|
20
|
+
private temporalService: TemporalService,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Find the cron_trigger node config in a DSL, if any.
|
|
25
|
+
* Returns null when no cron_trigger node or when its `cron` field is missing.
|
|
26
|
+
*/
|
|
27
|
+
private extractCronConfig(
|
|
28
|
+
dsl: any,
|
|
29
|
+
): { cron: string; timezone?: string } | null {
|
|
30
|
+
const node = dsl?.nodes?.find((n: any) => n?.type === 'cron_trigger');
|
|
31
|
+
if (!node?.config?.cron) return null;
|
|
32
|
+
return { cron: node.config.cron, timezone: node.config.timezone };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Find all webhook_trigger configs in a DSL.
|
|
37
|
+
* Each entry includes path + method; used for collision detection on publish.
|
|
38
|
+
*/
|
|
39
|
+
private extractWebhookConfigs(
|
|
40
|
+
dsl: any,
|
|
41
|
+
): Array<{ path: string; method: string; secret?: string }> {
|
|
42
|
+
if (!Array.isArray(dsl?.nodes)) return [];
|
|
43
|
+
return dsl.nodes
|
|
44
|
+
.filter(
|
|
45
|
+
(n: any) =>
|
|
46
|
+
n?.type === 'webhook_trigger' && n?.config?.path && n?.config?.method,
|
|
47
|
+
)
|
|
48
|
+
.map((n: any) => ({
|
|
49
|
+
path: n.config.path,
|
|
50
|
+
method: n.config.method,
|
|
51
|
+
secret: n.config.secret,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
8
54
|
|
|
9
55
|
async findMany(params: {
|
|
10
56
|
status?: string;
|
|
@@ -143,15 +189,44 @@ export class WorkflowService {
|
|
|
143
189
|
async toggleEnabled(id: bigint, enabled: boolean) {
|
|
144
190
|
const workflow = await this.prisma.workflow.findUnique({ where: { id } });
|
|
145
191
|
if (!workflow) throw new NotFoundException('Workflow not found');
|
|
146
|
-
|
|
192
|
+
const updated = await this.prisma.workflow.update({
|
|
147
193
|
where: { id },
|
|
148
194
|
data: { isEnabled: enabled },
|
|
149
195
|
});
|
|
196
|
+
|
|
197
|
+
// Cron schedule lifecycle: pause when disabled, resume when re-enabled
|
|
198
|
+
try {
|
|
199
|
+
if (enabled) {
|
|
200
|
+
await this.temporalService.unpauseCronSchedule(Number(id));
|
|
201
|
+
} else {
|
|
202
|
+
await this.temporalService.pauseCronSchedule(Number(id));
|
|
203
|
+
}
|
|
204
|
+
} catch (err: any) {
|
|
205
|
+
this.logger.warn(
|
|
206
|
+
`Failed to ${
|
|
207
|
+
enabled ? 'unpause' : 'pause'
|
|
208
|
+
} cron schedule for workflow ${id}: ${err?.message ?? err}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return updated;
|
|
150
213
|
}
|
|
151
214
|
|
|
152
215
|
async remove(id: bigint) {
|
|
153
216
|
const workflow = await this.prisma.workflow.findUnique({ where: { id } });
|
|
154
217
|
if (!workflow) throw new NotFoundException('Workflow not found');
|
|
218
|
+
|
|
219
|
+
// Best-effort: delete cron schedule before removing the workflow row
|
|
220
|
+
try {
|
|
221
|
+
await this.temporalService.deleteCronSchedule(Number(id));
|
|
222
|
+
} catch (err: any) {
|
|
223
|
+
this.logger.warn(
|
|
224
|
+
`Failed to delete cron schedule for workflow ${id}: ${
|
|
225
|
+
err?.message ?? err
|
|
226
|
+
}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
155
230
|
return this.prisma.workflow.delete({ where: { id } });
|
|
156
231
|
}
|
|
157
232
|
|
|
@@ -202,7 +277,41 @@ export class WorkflowService {
|
|
|
202
277
|
throw new NotFoundException('No draft DSL to publish');
|
|
203
278
|
}
|
|
204
279
|
|
|
205
|
-
|
|
280
|
+
// Structural + cycle validation. Reject malformed/cyclic DSL up-front so the
|
|
281
|
+
// worker's executeGraph never has to deal with them at runtime.
|
|
282
|
+
try {
|
|
283
|
+
validateDslGraph(dslToPublish);
|
|
284
|
+
} catch (err: any) {
|
|
285
|
+
throw new BadRequestException(`Invalid DSL: ${err?.message ?? err}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate webhook path collisions across other published workflows
|
|
289
|
+
const webhookConfigs = this.extractWebhookConfigs(dslToPublish);
|
|
290
|
+
if (webhookConfigs.length > 0) {
|
|
291
|
+
const otherPublished = await this.prisma.workflow.findMany({
|
|
292
|
+
where: { status: 'PUBLISHED', id: { not: workflowId } },
|
|
293
|
+
select: { id: true, name: true, dsl: true },
|
|
294
|
+
});
|
|
295
|
+
for (const cfg of webhookConfigs) {
|
|
296
|
+
if (!cfg.secret) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
`webhook_trigger path="${cfg.method} ${cfg.path}" requires a secret (HMAC strict mode)`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const conflict = otherPublished.find((w) =>
|
|
302
|
+
this.extractWebhookConfigs(w.dsl).some(
|
|
303
|
+
(other) => other.path === cfg.path && other.method === cfg.method,
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
if (conflict) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`webhook path "${cfg.method} ${cfg.path}" already used by workflow #${conflict.id} (${conflict.name})`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const result = await this.prisma.$transaction(async (tx) => {
|
|
206
315
|
const lastVersion = await tx.workflowVersion.findFirst({
|
|
207
316
|
where: { workflowId },
|
|
208
317
|
orderBy: { version: 'desc' },
|
|
@@ -228,6 +337,31 @@ export class WorkflowService {
|
|
|
228
337
|
|
|
229
338
|
return version;
|
|
230
339
|
});
|
|
340
|
+
|
|
341
|
+
// Cron schedule lifecycle: register/update if cron_trigger present, delete otherwise
|
|
342
|
+
const cronConfig = this.extractCronConfig(dslToPublish);
|
|
343
|
+
try {
|
|
344
|
+
if (cronConfig) {
|
|
345
|
+
await this.temporalService.upsertCronSchedule({
|
|
346
|
+
workflowId: Number(workflowId),
|
|
347
|
+
versionId: Number(result.id),
|
|
348
|
+
dsl: dslToPublish,
|
|
349
|
+
cron: cronConfig.cron,
|
|
350
|
+
timezone: cronConfig.timezone,
|
|
351
|
+
});
|
|
352
|
+
} else {
|
|
353
|
+
// No cron_trigger in this version → clean up any leftover schedule
|
|
354
|
+
await this.temporalService.deleteCronSchedule(Number(workflowId));
|
|
355
|
+
}
|
|
356
|
+
} catch (err: any) {
|
|
357
|
+
this.logger.warn(
|
|
358
|
+
`Cron schedule sync failed for workflow ${workflowId}: ${
|
|
359
|
+
err?.message ?? err
|
|
360
|
+
}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return result;
|
|
231
365
|
}
|
|
232
366
|
|
|
233
367
|
async getStats(workflowId: bigint) {
|
|
@@ -418,6 +552,109 @@ export class WorkflowService {
|
|
|
418
552
|
};
|
|
419
553
|
}
|
|
420
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Webhook trigger entry point. Looks up published workflows whose DSL contains a
|
|
557
|
+
* webhook_trigger node matching (path, method), verifies HMAC signature against
|
|
558
|
+
* each match's secret, and starts a workflow instance per verified match.
|
|
559
|
+
*
|
|
560
|
+
* Returns the list of created instance IDs. Throws:
|
|
561
|
+
* - NotFoundException when no published workflow listens on this path/method
|
|
562
|
+
* - UnauthorizedException on bad signature or stale timestamp
|
|
563
|
+
*/
|
|
564
|
+
async receiveWebhook(input: {
|
|
565
|
+
path: string;
|
|
566
|
+
method: string;
|
|
567
|
+
signatureHeader: string | undefined;
|
|
568
|
+
timestampHeader: string | undefined;
|
|
569
|
+
rawBody: Buffer;
|
|
570
|
+
body: any;
|
|
571
|
+
headers: Record<string, any>;
|
|
572
|
+
query: Record<string, any>;
|
|
573
|
+
}): Promise<{ instanceIds: string[] }> {
|
|
574
|
+
// 1) Timestamp window — reject stale requests (±5 min)
|
|
575
|
+
const tsMs = Number(input.timestampHeader) * 1000;
|
|
576
|
+
if (
|
|
577
|
+
!input.timestampHeader ||
|
|
578
|
+
!Number.isFinite(tsMs) ||
|
|
579
|
+
Math.abs(Date.now() - tsMs) > 5 * 60 * 1000
|
|
580
|
+
) {
|
|
581
|
+
throw new UnauthorizedException(
|
|
582
|
+
'Invalid or expired X-Workflow-Timestamp',
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// 2) Lookup matching published + enabled workflows
|
|
587
|
+
const published = await this.prisma.workflow.findMany({
|
|
588
|
+
where: { status: 'PUBLISHED', isEnabled: true },
|
|
589
|
+
include: {
|
|
590
|
+
versions: { orderBy: { version: 'desc' }, take: 1 },
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
type Match = {
|
|
595
|
+
workflowId: bigint;
|
|
596
|
+
versionId: bigint;
|
|
597
|
+
dsl: any;
|
|
598
|
+
secret: string;
|
|
599
|
+
};
|
|
600
|
+
const matches: Match[] = [];
|
|
601
|
+
for (const wf of published) {
|
|
602
|
+
const version = wf.versions[0];
|
|
603
|
+
const dsl = version?.dsl ?? wf.dsl;
|
|
604
|
+
if (!dsl) continue;
|
|
605
|
+
const cfgs = this.extractWebhookConfigs(dsl);
|
|
606
|
+
const cfg = cfgs.find(
|
|
607
|
+
(c) => c.path === input.path && c.method === input.method,
|
|
608
|
+
);
|
|
609
|
+
if (!cfg || !cfg.secret) continue;
|
|
610
|
+
matches.push({
|
|
611
|
+
workflowId: wf.id,
|
|
612
|
+
versionId: version?.id ?? wf.id,
|
|
613
|
+
dsl,
|
|
614
|
+
secret: cfg.secret,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (matches.length === 0) {
|
|
619
|
+
throw new NotFoundException(
|
|
620
|
+
`No published workflow listens on ${input.method} /${input.path}`,
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// 3) Verify HMAC against each match's secret. Strict mode: every match must verify.
|
|
625
|
+
for (const m of matches) {
|
|
626
|
+
const ok = verifyWebhookSignature(
|
|
627
|
+
m.secret,
|
|
628
|
+
input.timestampHeader!,
|
|
629
|
+
input.rawBody,
|
|
630
|
+
input.signatureHeader,
|
|
631
|
+
);
|
|
632
|
+
if (!ok) {
|
|
633
|
+
throw new UnauthorizedException('Invalid X-Workflow-Signature');
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// 4) Trigger executions in parallel
|
|
638
|
+
const context = {
|
|
639
|
+
headers: input.headers,
|
|
640
|
+
query: input.query,
|
|
641
|
+
body: input.body,
|
|
642
|
+
receivedAt: new Date().toISOString(),
|
|
643
|
+
};
|
|
644
|
+
const results = await Promise.all(
|
|
645
|
+
matches.map((m) =>
|
|
646
|
+
this.executeWorkflow(
|
|
647
|
+
m.workflowId,
|
|
648
|
+
{ context },
|
|
649
|
+
'webhook',
|
|
650
|
+
this.temporalService,
|
|
651
|
+
),
|
|
652
|
+
),
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
return { instanceIds: results.map((r) => r.instanceId) };
|
|
656
|
+
}
|
|
657
|
+
|
|
421
658
|
async getInstanceDetail(instanceId: bigint) {
|
|
422
659
|
const instance = await this.prisma.workflowInstance.findUnique({
|
|
423
660
|
where: { id: instanceId },
|
|
@@ -1,15 +1,47 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
1
3
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
4
|
+
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
2
5
|
import { WorkflowEventOutboxController } from './workflowEventOutbox.controller';
|
|
3
6
|
import { WorkflowEventOutboxService } from './workflowEventOutbox.service';
|
|
4
7
|
|
|
8
|
+
// Mock WorkflowEventOutboxService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
|
|
9
|
+
// 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
|
|
10
|
+
const mockWorkflowEventOutboxService = {};
|
|
11
|
+
|
|
12
|
+
// Mock ConfigService
|
|
13
|
+
const mockConfigService = {
|
|
14
|
+
get: jest.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Mock Logger
|
|
18
|
+
const mockLogger = {
|
|
19
|
+
child: jest.fn().mockReturnThis(),
|
|
20
|
+
info: jest.fn(),
|
|
21
|
+
error: jest.fn(),
|
|
22
|
+
warn: jest.fn(),
|
|
23
|
+
debug: jest.fn(),
|
|
24
|
+
};
|
|
25
|
+
|
|
5
26
|
describe('WorkflowEventOutboxController', () => {
|
|
6
27
|
let controller: WorkflowEventOutboxController;
|
|
7
28
|
|
|
8
29
|
beforeEach(async () => {
|
|
9
30
|
const module: TestingModule = await Test.createTestingModule({
|
|
10
31
|
controllers: [WorkflowEventOutboxController],
|
|
11
|
-
providers: [
|
|
12
|
-
|
|
32
|
+
providers: [
|
|
33
|
+
{
|
|
34
|
+
provide: WorkflowEventOutboxService,
|
|
35
|
+
useValue: mockWorkflowEventOutboxService,
|
|
36
|
+
},
|
|
37
|
+
{ provide: ConfigService, useValue: mockConfigService },
|
|
38
|
+
{ provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
// 兜底:自动 mock 任何未显式提供的依赖
|
|
42
|
+
// (例如 ACGuard 内部的 __roles_builder__ token)
|
|
43
|
+
.useMocker(() => ({}))
|
|
44
|
+
.compile();
|
|
13
45
|
|
|
14
46
|
controller = module.get<WorkflowEventOutboxController>(
|
|
15
47
|
WorkflowEventOutboxController,
|
|
@@ -77,10 +77,9 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
77
77
|
payload: {},
|
|
78
78
|
processed: false,
|
|
79
79
|
processedAt: new Date(),
|
|
80
|
-
retryCount: 0,
|
|
81
80
|
},
|
|
82
|
-
};
|
|
83
|
-
const expectedResult = { id:
|
|
81
|
+
} as any;
|
|
82
|
+
const expectedResult = { id: 1, ...createArgs.data };
|
|
84
83
|
|
|
85
84
|
prisma.workflowEventOutbox.create.mockResolvedValue(expectedResult);
|
|
86
85
|
|
|
@@ -100,9 +99,8 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
100
99
|
payload: {},
|
|
101
100
|
processed: false,
|
|
102
101
|
processedAt: new Date(),
|
|
103
|
-
retryCount: 0,
|
|
104
102
|
},
|
|
105
|
-
};
|
|
103
|
+
} as any;
|
|
106
104
|
const error = new Error('Database connection failed');
|
|
107
105
|
|
|
108
106
|
prisma.workflowEventOutbox.create.mockRejectedValue(error);
|
|
@@ -121,16 +119,14 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
121
119
|
payload: {},
|
|
122
120
|
processed: false,
|
|
123
121
|
processedAt: new Date(),
|
|
124
|
-
retryCount: 0,
|
|
125
122
|
},
|
|
126
123
|
{
|
|
127
124
|
eventName: 'test_eventName',
|
|
128
125
|
payload: {},
|
|
129
126
|
processed: false,
|
|
130
127
|
processedAt: new Date(),
|
|
131
|
-
retryCount: 0,
|
|
132
128
|
},
|
|
133
|
-
];
|
|
129
|
+
] as any;
|
|
134
130
|
const expectedResult = { count: 2 };
|
|
135
131
|
|
|
136
132
|
prisma.workflowEventOutbox.createMany.mockResolvedValue(expectedResult);
|
|
@@ -151,8 +147,8 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
151
147
|
where: {},
|
|
152
148
|
take: 10,
|
|
153
149
|
skip: 0,
|
|
154
|
-
};
|
|
155
|
-
const mockEntities = [{ id:
|
|
150
|
+
} as any;
|
|
151
|
+
const mockEntities = [{ id: 1 }, { id: 2 }];
|
|
156
152
|
|
|
157
153
|
prisma.workflowEventOutbox.count.mockResolvedValue(2);
|
|
158
154
|
prisma.workflowEventOutbox.findMany.mockResolvedValue(mockEntities);
|
|
@@ -172,7 +168,7 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
172
168
|
});
|
|
173
169
|
|
|
174
170
|
it('should return empty result when no records found', async () => {
|
|
175
|
-
const findArgs = { where: {} };
|
|
171
|
+
const findArgs = { where: {} } as any;
|
|
176
172
|
|
|
177
173
|
prisma.workflowEventOutbox.count.mockResolvedValue(0);
|
|
178
174
|
prisma.workflowEventOutbox.findMany.mockResolvedValue([]);
|
|
@@ -185,9 +181,9 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
185
181
|
|
|
186
182
|
describe('findUnique', () => {
|
|
187
183
|
it('should find a single workflowEventOutbox record by id', async () => {
|
|
188
|
-
const id =
|
|
184
|
+
const id = 1;
|
|
189
185
|
const select = { id: true };
|
|
190
|
-
const expectedResult = { id:
|
|
186
|
+
const expectedResult = { id: 1 };
|
|
191
187
|
|
|
192
188
|
prisma.workflowEventOutbox.findUnique.mockResolvedValue(expectedResult);
|
|
193
189
|
|
|
@@ -203,7 +199,7 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
203
199
|
it('should return null when record not found', async () => {
|
|
204
200
|
prisma.workflowEventOutbox.findUnique.mockResolvedValue(null);
|
|
205
201
|
|
|
206
|
-
const result = await service.findUnique(
|
|
202
|
+
const result = await service.findUnique(1, { id: true });
|
|
207
203
|
|
|
208
204
|
expect(result).toBeNull();
|
|
209
205
|
});
|
|
@@ -211,15 +207,14 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
211
207
|
|
|
212
208
|
describe('updateUnique', () => {
|
|
213
209
|
it('should update a single workflowEventOutbox record', async () => {
|
|
214
|
-
const id =
|
|
210
|
+
const id = 1;
|
|
215
211
|
const data = {
|
|
216
212
|
eventName: 'test_eventName',
|
|
217
213
|
payload: {},
|
|
218
214
|
processed: false,
|
|
219
215
|
processedAt: new Date(),
|
|
220
|
-
|
|
221
|
-
};
|
|
222
|
-
const expectedResult = { id: BigInt(1), ...data };
|
|
216
|
+
} as any;
|
|
217
|
+
const expectedResult = { id: 1, ...data };
|
|
223
218
|
|
|
224
219
|
prisma.workflowEventOutbox.update.mockResolvedValue(expectedResult);
|
|
225
220
|
|
|
@@ -242,9 +237,8 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
242
237
|
payload: {},
|
|
243
238
|
processed: false,
|
|
244
239
|
processedAt: new Date(),
|
|
245
|
-
retryCount: 0,
|
|
246
240
|
},
|
|
247
|
-
};
|
|
241
|
+
} as any;
|
|
248
242
|
const expectedResult = { count: 5 };
|
|
249
243
|
|
|
250
244
|
prisma.workflowEventOutbox.updateMany.mockResolvedValue(expectedResult);
|
|
@@ -260,9 +254,9 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
260
254
|
|
|
261
255
|
describe('deleteUnique', () => {
|
|
262
256
|
it('should delete a single workflowEventOutbox record and return count', async () => {
|
|
263
|
-
const id =
|
|
257
|
+
const id = 1;
|
|
264
258
|
|
|
265
|
-
prisma.workflowEventOutbox.delete.mockResolvedValue({ id:
|
|
259
|
+
prisma.workflowEventOutbox.delete.mockResolvedValue({ id: 1 });
|
|
266
260
|
|
|
267
261
|
const result = await service.deleteUnique(id);
|
|
268
262
|
|
|
@@ -275,7 +269,7 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
275
269
|
|
|
276
270
|
describe('deleteMany', () => {
|
|
277
271
|
it('should delete multiple workflowEventOutbox records', async () => {
|
|
278
|
-
const deleteArgs = { where: {} };
|
|
272
|
+
const deleteArgs = { where: {} } as any;
|
|
279
273
|
const expectedResult = { count: 3 };
|
|
280
274
|
|
|
281
275
|
prisma.workflowEventOutbox.deleteMany.mockResolvedValue(expectedResult);
|
|
@@ -291,7 +285,7 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
291
285
|
|
|
292
286
|
describe('count', () => {
|
|
293
287
|
it('should count workflowEventOutbox records with filter', async () => {
|
|
294
|
-
const countArgs = { where: {} };
|
|
288
|
+
const countArgs = { where: {} } as any;
|
|
295
289
|
|
|
296
290
|
prisma.workflowEventOutbox.count.mockResolvedValue(10);
|
|
297
291
|
|
|
@@ -316,12 +310,12 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
316
310
|
const groupByArgs = {
|
|
317
311
|
by: ['id'],
|
|
318
312
|
_count: { id: true },
|
|
319
|
-
};
|
|
320
|
-
const expectedResult = [{ id:
|
|
313
|
+
} as any;
|
|
314
|
+
const expectedResult = [{ id: 1, _count: { id: 50 } }];
|
|
321
315
|
|
|
322
316
|
prisma.workflowEventOutbox.groupBy.mockResolvedValue(expectedResult);
|
|
323
317
|
|
|
324
|
-
const result = await service.groupBy(groupByArgs
|
|
318
|
+
const result = await service.groupBy(groupByArgs);
|
|
325
319
|
|
|
326
320
|
expect(prisma.workflowEventOutbox.groupBy).toHaveBeenCalledWith(
|
|
327
321
|
groupByArgs,
|
|
@@ -336,11 +330,11 @@ describe('WorkflowEventOutboxService', () => {
|
|
|
336
330
|
_count: true,
|
|
337
331
|
_max: { id: true },
|
|
338
332
|
_min: { id: true },
|
|
339
|
-
} as
|
|
333
|
+
} as any;
|
|
340
334
|
const expectedResult = {
|
|
341
335
|
_count: 100,
|
|
342
|
-
_max: { id:
|
|
343
|
-
_min: { id:
|
|
336
|
+
_max: { id: 1 },
|
|
337
|
+
_min: { id: 1 },
|
|
344
338
|
};
|
|
345
339
|
|
|
346
340
|
prisma.workflowEventOutbox.aggregate.mockResolvedValue(expectedResult);
|
|
@@ -1,15 +1,47 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
1
3
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
4
|
+
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
2
5
|
import { WorkflowNodeInstanceController } from './workflowNodeInstance.controller';
|
|
3
6
|
import { WorkflowNodeInstanceService } from './workflowNodeInstance.service';
|
|
4
7
|
|
|
8
|
+
// Mock WorkflowNodeInstanceService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
|
|
9
|
+
// 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
|
|
10
|
+
const mockWorkflowNodeInstanceService = {};
|
|
11
|
+
|
|
12
|
+
// Mock ConfigService
|
|
13
|
+
const mockConfigService = {
|
|
14
|
+
get: jest.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Mock Logger
|
|
18
|
+
const mockLogger = {
|
|
19
|
+
child: jest.fn().mockReturnThis(),
|
|
20
|
+
info: jest.fn(),
|
|
21
|
+
error: jest.fn(),
|
|
22
|
+
warn: jest.fn(),
|
|
23
|
+
debug: jest.fn(),
|
|
24
|
+
};
|
|
25
|
+
|
|
5
26
|
describe('WorkflowNodeInstanceController', () => {
|
|
6
27
|
let controller: WorkflowNodeInstanceController;
|
|
7
28
|
|
|
8
29
|
beforeEach(async () => {
|
|
9
30
|
const module: TestingModule = await Test.createTestingModule({
|
|
10
31
|
controllers: [WorkflowNodeInstanceController],
|
|
11
|
-
providers: [
|
|
12
|
-
|
|
32
|
+
providers: [
|
|
33
|
+
{
|
|
34
|
+
provide: WorkflowNodeInstanceService,
|
|
35
|
+
useValue: mockWorkflowNodeInstanceService,
|
|
36
|
+
},
|
|
37
|
+
{ provide: ConfigService, useValue: mockConfigService },
|
|
38
|
+
{ provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
// 兜底:自动 mock 任何未显式提供的依赖
|
|
42
|
+
// (例如 ACGuard 内部的 __roles_builder__ token)
|
|
43
|
+
.useMocker(() => ({}))
|
|
44
|
+
.compile();
|
|
13
45
|
|
|
14
46
|
controller = module.get<WorkflowNodeInstanceController>(
|
|
15
47
|
WorkflowNodeInstanceController,
|