@gadmin2n/schematics 0.0.88 → 0.0.90

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 (101) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +16 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.codegen +40 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +76 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.web +53 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +219 -33
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +250 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +4 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/dev/postgres/init.sql +12 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +170 -0
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +254 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +7 -6
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +1 -1
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/prismaModels.ts +1 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +39 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/audit.seed.ts +40 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +56 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +39 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/{scripts/sync-data-mngt-pages.ts → seed/data-mngt.seed.ts} +36 -20
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/game.seed.ts +44 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +30 -6
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +130 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts +60 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +11 -25
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +108 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +1 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +31 -2
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +31 -2
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +41 -57
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +31 -2
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +309 -1
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +31 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +315 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +31 -2
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +312 -2
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +31 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +317 -1
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +31 -2
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +309 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +31 -2
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +299 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +31 -2
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +307 -1
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +31 -2
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +309 -1
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +205 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.ts +116 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +158 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +110 -1
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.spec.ts +79 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.ts +54 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +34 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +457 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +241 -4
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +34 -2
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +24 -30
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +34 -2
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +36 -36
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +34 -2
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +48 -24
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +312 -3
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +152 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +12 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +79 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +270 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +1 -1
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +23 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +70 -5
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +246 -90
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +20 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +96 -8
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/nginx.conf +74 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/ElementInspector.tsx +18 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/promptGenerator.ts +1 -1
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +1 -1
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -3
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -3
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +4 -1
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +9 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +156 -139
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +14 -2
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +62 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/PublishModal.tsx +4 -6
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +18 -27
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +32 -11
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +48 -61
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas-page/index.tsx +3 -6
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +16 -16
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +28 -35
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +1 -0
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -3
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +1 -1
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -1
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +6 -0
  94. package/package.json +1 -1
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/.gitattributes +0 -2
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +0 -63
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/sync-resources.ts +0 -100
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +0 -302
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.spec.ts +0 -20
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +0 -87
  101. /package/dist/lib/application/files/gadmin2-game-angle-demo/{GRACEFUL-DEPLOYMENT.md → server/GRACEFUL-DEPLOYMENT.md} +0 -0
@@ -1,16 +1,40 @@
1
1
  import { PrismaClient } from '@prisma/client';
2
- import { seedPermissions } from './permissions';
2
+ import { seedAgendaModule } from './agenda.seed';
3
+ import { seedAuditModule } from './audit.seed';
4
+ import { seedBootstrap } from './bootstrap';
5
+ import { seedCanvasModule } from './canvas.seed';
6
+ import { seedDataMngtPages } from './data-mngt.seed';
7
+ import { seedGameModule } from './game.seed';
8
+ import { seedPermissionModule } from './permission.seed';
3
9
  import users from './users';
4
- import { seedNodeTypes } from './workflow-node-types';
5
- import { seedWorkflows } from './workflows';
10
+ import { seedWorkflowModule } from './workflow.seed';
6
11
 
7
12
  const prisma = new PrismaClient();
8
13
 
14
+ /**
15
+ * Top-level seed orchestrator.
16
+ *
17
+ * 顺序:
18
+ * ① bootstrap —— admin/dataMngt 根 page + SYSTEM_ADMIN role(全模块前置)
19
+ * ② 各模块 seed —— 每个模块自管自家 page / resource / 授权 / 业务数据
20
+ * ③ data-mngt 自动 CRUD —— 扫描 config/prisma/ 业务模型生成 page/resource/grants
21
+ *
22
+ * 每个模块 seed 函数自检前置(findUniqueOrThrow),缺失即快速失败。
23
+ * 全部幂等,可重复运行。
24
+ */
9
25
  export async function main() {
10
26
  await prisma.user.createMany({ data: users, skipDuplicates: true });
11
- await seedPermissions(prisma);
12
- await seedNodeTypes(prisma);
13
- await seedWorkflows(prisma);
27
+
28
+ await seedBootstrap(prisma);
29
+
30
+ await seedPermissionModule(prisma);
31
+ await seedAgendaModule(prisma);
32
+ await seedAuditModule(prisma);
33
+ await seedCanvasModule(prisma);
34
+ await seedGameModule(prisma);
35
+ await seedWorkflowModule(prisma);
36
+
37
+ await seedDataMngtPages(prisma);
14
38
  }
15
39
 
16
40
  if (require.main === module) {
@@ -0,0 +1,130 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ import {
3
+ bindResourceToPage,
4
+ getPageResourceIds,
5
+ grantRoleAccess,
6
+ upsertPage,
7
+ } from '../scripts/lib/page-helpers';
8
+
9
+ /**
10
+ * Permission 模块 seed
11
+ *
12
+ * 职责(仅本模块自家):
13
+ * 1. permission 父分组 page (admin → permission)
14
+ * 2. 5 个 leaf page: permission_readme / user / role / page / resource
15
+ * 3. 7 个 resource: user / role / rolePages / roleResource / page / pageResource / resource
16
+ * 4. PageResource 绑定(page ↔ resource,多对多)
17
+ * 5. SYSTEM_ADMIN 对上述全部 page + resource 的全量授权
18
+ *
19
+ * 前置:seedBootstrap 已建好 admin 顶级 page 和 SYSTEM_ADMIN role。
20
+ *
21
+ * 多 resource 共享同一 page 的情况(如 role page UI 同时调 role + rolePages + roleResource API)
22
+ * 通过 resourceDefs[].pageCode 表达。
23
+ */
24
+ export async function seedPermissionModule(
25
+ prisma: PrismaClient,
26
+ ): Promise<void> {
27
+ // ── 前置查找 ───────────────────────────────────────────────────────────────
28
+ const systemAdminRole = await prisma.role.findUniqueOrThrow({
29
+ where: { name: 'SYSTEM_ADMIN' },
30
+ });
31
+ const adminPage = await prisma.page.findUniqueOrThrow({
32
+ where: { code: 'admin' },
33
+ });
34
+
35
+ // ── permission 父分组 ──────────────────────────────────────────────────────
36
+ const permissionPage = await upsertPage(prisma, {
37
+ code: 'permission',
38
+ name: 'permission',
39
+ zhName: '权限管理',
40
+ enName: 'Permission Mgnt',
41
+ sortOrder: 1040,
42
+ parentId: adminPage.id,
43
+ });
44
+ console.log(
45
+ `[permission] Page: ${permissionPage.code} (id=${permissionPage.id})`,
46
+ );
47
+
48
+ // ── leaf pages ─────────────────────────────────────────────────────────────
49
+ const leafDefs = [
50
+ {
51
+ code: 'permission_readme',
52
+ zhName: '菜单指引',
53
+ enName: 'Menu Guide',
54
+ sortOrder: 1005,
55
+ },
56
+ { code: 'user', zhName: '用户管理', enName: 'User Mgnt', sortOrder: 1010 },
57
+ { code: 'role', zhName: '角色管理', enName: 'Role Mgnt', sortOrder: 1020 },
58
+ { code: 'page', zhName: '页面管理', enName: 'Page Mgnt', sortOrder: 1030 },
59
+ {
60
+ code: 'resource',
61
+ zhName: '资源管理',
62
+ enName: 'Resource Management',
63
+ sortOrder: 1040,
64
+ },
65
+ ];
66
+
67
+ const pagesByCode = new Map<string, number>();
68
+ pagesByCode.set(permissionPage.code, permissionPage.id);
69
+
70
+ for (const def of leafDefs) {
71
+ const p = await upsertPage(prisma, {
72
+ code: def.code,
73
+ name: def.code,
74
+ zhName: def.zhName,
75
+ enName: def.enName,
76
+ sortOrder: def.sortOrder,
77
+ parentId: permissionPage.id,
78
+ });
79
+ pagesByCode.set(p.code, p.id);
80
+ console.log(`[permission] Page: ${p.code} (id=${p.id})`);
81
+ }
82
+
83
+ // ── resource ↔ page 绑定 ───────────────────────────────────────────────────
84
+ const resourceDefs: {
85
+ code: string;
86
+ description: string;
87
+ pageCode: string;
88
+ }[] = [
89
+ { code: 'user', description: 'User management', pageCode: 'user' },
90
+ { code: 'role', description: 'Role management', pageCode: 'role' },
91
+ {
92
+ code: 'rolePages',
93
+ description: 'Role-page assignments',
94
+ pageCode: 'role',
95
+ },
96
+ {
97
+ code: 'roleResource',
98
+ description: 'Role-resource grants',
99
+ pageCode: 'role',
100
+ },
101
+ { code: 'page', description: 'Page / menu management', pageCode: 'page' },
102
+ {
103
+ code: 'pageResource',
104
+ description: 'Page-resource configuration',
105
+ pageCode: 'page',
106
+ },
107
+ {
108
+ code: 'resource',
109
+ description: 'Resource management',
110
+ pageCode: 'resource',
111
+ },
112
+ ];
113
+
114
+ for (const def of resourceDefs) {
115
+ const pageId = pagesByCode.get(def.pageCode);
116
+ if (!pageId)
117
+ throw new Error(`[permission] Page not found: ${def.pageCode}`);
118
+ await bindResourceToPage(prisma, pageId, def.code, def.description);
119
+ console.log(`[permission] PageResource: ${def.pageCode} ↔ ${def.code}`);
120
+ }
121
+
122
+ // ── SYSTEM_ADMIN 授权(page + resource) ────────────────────────────────────
123
+ for (const [code, pageId] of pagesByCode) {
124
+ const resourceIds = await getPageResourceIds(prisma, pageId);
125
+ await grantRoleAccess(prisma, systemAdminRole.id, pageId, resourceIds);
126
+ console.log(
127
+ `[permission] Grant: SYSTEM_ADMIN → ${code} (+${resourceIds.length} resource)`,
128
+ );
129
+ }
130
+ }
@@ -0,0 +1,60 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ /**
4
+ * Outbox Pattern 触发器函数 + 性能索引
5
+ *
6
+ * 当业务表发生 INSERT/UPDATE/DELETE 时,触发器将事件写入 t_workflow_event_outbox。
7
+ * Worker 的 outbox-poller 定时扫描未处理事件,匹配并触发对应的 workflow。
8
+ *
9
+ * 表结构由 Prisma 模型 WorkflowEventOutbox 维护(见 config/prisma/workflow.prisma)。
10
+ * 本 seed 负责 Prisma 无法 declarative 维护的两个对象:
11
+ * 1. 通用触发器函数 notify_workflow_event() — Prisma 完全不管 PG function
12
+ * 2. 部分索引 idx_outbox_unprocessed — Prisma schema 不支持 WHERE 子句的 partial index
13
+ *
14
+ * 两者均幂等(CREATE OR REPLACE / IF NOT EXISTS)。
15
+ *
16
+ * 使用示例(在业务表上挂载触发器,由各业务模块自行声明):
17
+ * CREATE TRIGGER trg_order_workflow
18
+ * AFTER INSERT OR UPDATE OR DELETE ON t_order
19
+ * FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
20
+ */
21
+ export async function seedWorkflowEventTrigger(prisma: PrismaClient) {
22
+ console.log('Seeding workflow event trigger function...');
23
+
24
+ await prisma.$executeRawUnsafe(`
25
+ CREATE OR REPLACE FUNCTION notify_workflow_event() RETURNS trigger AS $$
26
+ DECLARE
27
+ event_payload jsonb;
28
+ event_name text;
29
+ BEGIN
30
+ -- 拼接事件名:表名.操作 (e.g. "t_order.insert")
31
+ event_name := TG_TABLE_NAME || '.' || lower(TG_OP);
32
+
33
+ -- 构建 payload
34
+ IF (TG_OP = 'DELETE') THEN
35
+ event_payload = row_to_json(OLD)::jsonb;
36
+ ELSE
37
+ event_payload = row_to_json(NEW)::jsonb;
38
+ END IF;
39
+
40
+ -- 写入 outbox(与业务操作同一事务,保证不丢)
41
+ INSERT INTO t_workflow_event_outbox (event_name, payload)
42
+ VALUES (event_name, event_payload);
43
+
44
+ RETURN COALESCE(NEW, OLD);
45
+ END;
46
+ $$ LANGUAGE plpgsql;
47
+ `);
48
+
49
+ console.log('Seeding workflow outbox partial index...');
50
+
51
+ // 部分索引:只索引未处理事件,poller 扫描时索引体积保持很小,
52
+ // 即使 outbox 表积累几百万历史事件也不影响 poller 性能。
53
+ await prisma.$executeRawUnsafe(`
54
+ CREATE INDEX IF NOT EXISTS idx_outbox_unprocessed
55
+ ON t_workflow_event_outbox (created_at)
56
+ WHERE processed = FALSE;
57
+ `);
58
+
59
+ console.log('Workflow event trigger function & index seeded.');
60
+ }
@@ -33,41 +33,27 @@ export const NODE_TYPES = [
33
33
  category: 'TRIGGER',
34
34
  label: 'Webhook Trigger',
35
35
  icon: 'ApiOutlined',
36
- description: 'Trigger workflow via HTTP webhook',
36
+ description: 'Trigger workflow via HTTP webhook (HMAC-signed)',
37
37
  configSchema: {
38
38
  type: 'object',
39
39
  properties: {
40
- path: { type: 'string', description: 'Webhook endpoint path' },
41
- method: {
42
- type: 'string',
43
- enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
44
- description: 'HTTP method',
45
- },
46
- authentication: {
40
+ path: {
47
41
  type: 'string',
48
- enum: ['none', 'basic', 'headerAuth', 'jwt'],
49
- description: 'Authentication method for incoming requests',
42
+ description:
43
+ 'Webhook endpoint path segment, e.g. "order-paid". Final URL: POST /api/workflow/webhook/{path}',
50
44
  },
51
- responseMode: {
45
+ method: {
52
46
  type: 'string',
53
- enum: ['immediately', 'lastNode', 'respondNode'],
54
- description: 'When to send the HTTP response',
55
- },
56
- responseCode: {
57
- type: 'number',
58
- description: 'HTTP response status code (default 200)',
47
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
48
+ description: 'HTTP method the webhook accepts',
59
49
  },
60
- responseBody: {
50
+ secret: {
61
51
  type: 'string',
62
- description: 'Custom response body (JSON string)',
63
- },
64
- allowedOrigins: {
65
- type: 'array',
66
- items: { type: 'string' },
67
- description: 'CORS allowed origins',
52
+ description:
53
+ 'HMAC-SHA256 secret. Caller must send X-Workflow-Signature: sha256=HEX(HMAC(secret, "{ts}.{rawBody}")) and X-Workflow-Timestamp.',
68
54
  },
69
55
  },
70
- required: ['path', 'method'],
56
+ required: ['path', 'method', 'secret'],
71
57
  },
72
58
  inputSchema: {
73
59
  type: 'object',
@@ -0,0 +1,108 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ import {
3
+ bindResourceToPage,
4
+ getPageResourceIds,
5
+ grantRoleAccess,
6
+ upsertPage,
7
+ } from '../scripts/lib/page-helpers';
8
+ import { seedWorkflowEventTrigger } from './workflow-event-trigger';
9
+ import { seedNodeTypes } from './workflow-node-types';
10
+ import { seedWorkflows } from './workflows';
11
+
12
+ /**
13
+ * Workflow 模块 seed
14
+ *
15
+ * 一站式负责 workflow 模块所有 seed:
16
+ * 1. 菜单结构:workflow 父分组 + 2 个 leaf (workflow_list / workflow_node_instance)
17
+ * 2. resource: workflow / workflowNodeInstance
18
+ * 3. PageResource 绑定与 SYSTEM_ADMIN 授权
19
+ * 4. 业务数据:node-types / workflows / event-trigger function & index
20
+ *
21
+ * 业务数据 seed 函数仍来自 ./workflow-{node-types,workflows,event-trigger}.ts
22
+ * (文件较大,保持独立但只被本入口 import,不再由 seed/index.ts 直接调用)。
23
+ *
24
+ * 前置:seedBootstrap 已建好 admin 与 SYSTEM_ADMIN。
25
+ */
26
+ export async function seedWorkflowModule(prisma: PrismaClient): Promise<void> {
27
+ const role = await prisma.role.findUniqueOrThrow({
28
+ where: { name: 'SYSTEM_ADMIN' },
29
+ });
30
+ const admin = await prisma.page.findUniqueOrThrow({
31
+ where: { code: 'admin' },
32
+ });
33
+
34
+ // ── 父分组 page ────────────────────────────────────────────────────────────
35
+ const workflowPage = await upsertPage(prisma, {
36
+ code: 'workflow',
37
+ name: 'workflow',
38
+ zhName: 'Workflow',
39
+ enName: 'Workflow',
40
+ sortOrder: 1030,
41
+ parentId: admin.id,
42
+ });
43
+ console.log(`[workflow] Page: ${workflowPage.code} (id=${workflowPage.id})`);
44
+
45
+ // ── leaf pages + resource 绑定 ─────────────────────────────────────────────
46
+ const leafDefs: {
47
+ code: string;
48
+ zhName: string;
49
+ enName: string;
50
+ sortOrder: number;
51
+ resourceCode: string;
52
+ resourceDescription: string;
53
+ }[] = [
54
+ {
55
+ code: 'workflow_list',
56
+ zhName: '工作流列表',
57
+ enName: 'List',
58
+ sortOrder: 1,
59
+ resourceCode: 'workflow',
60
+ resourceDescription: 'Workflow management',
61
+ },
62
+ {
63
+ code: 'workflow_node_instance',
64
+ zhName: 'Node模版',
65
+ enName: 'Node Instance',
66
+ sortOrder: 2,
67
+ resourceCode: 'workflowNodeInstance',
68
+ resourceDescription: 'Workflow node instance management',
69
+ },
70
+ ];
71
+
72
+ const allPages = [{ id: workflowPage.id, code: workflowPage.code }];
73
+
74
+ for (const def of leafDefs) {
75
+ const p = await upsertPage(prisma, {
76
+ code: def.code,
77
+ name: def.code,
78
+ zhName: def.zhName,
79
+ enName: def.enName,
80
+ sortOrder: def.sortOrder,
81
+ parentId: workflowPage.id,
82
+ });
83
+ allPages.push({ id: p.id, code: p.code });
84
+ console.log(`[workflow] Page: ${p.code} (id=${p.id})`);
85
+
86
+ await bindResourceToPage(
87
+ prisma,
88
+ p.id,
89
+ def.resourceCode,
90
+ def.resourceDescription,
91
+ );
92
+ console.log(`[workflow] PageResource: ${def.code} ↔ ${def.resourceCode}`);
93
+ }
94
+
95
+ // ── SYSTEM_ADMIN 授权 ──────────────────────────────────────────────────────
96
+ for (const p of allPages) {
97
+ const resourceIds = await getPageResourceIds(prisma, p.id);
98
+ await grantRoleAccess(prisma, role.id, p.id, resourceIds);
99
+ console.log(
100
+ `[workflow] Grant: SYSTEM_ADMIN → ${p.code} (+${resourceIds.length} resource)`,
101
+ );
102
+ }
103
+
104
+ // ── 业务数据 ───────────────────────────────────────────────────────────────
105
+ await seedNodeTypes(prisma);
106
+ await seedWorkflows(prisma);
107
+ await seedWorkflowEventTrigger(prisma);
108
+ }
@@ -36,6 +36,7 @@ JSON.stringify = (value: any, _: any, space: string | number | undefined) => {
36
36
  async function bootstrap() {
37
37
  const app = await NestFactory.create(AppModule, {
38
38
  cors: true,
39
+ rawBody: true, // Required for HMAC verification on /api/workflow/webhook/:path
39
40
  });
40
41
  // 添加全局前缀 '/api'
41
42
  app.setGlobalPrefix(`${process.env.DEPLOY_NAME || ''}/api`);
@@ -1,15 +1,44 @@
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 { AgendaJobController } from './agendaJob.controller';
3
6
  import { AgendaJobService } from './agendaJob.service';
4
7
 
8
+ // Mock AgendaJobService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
9
+ // 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
10
+ const mockAgendaJobService = {};
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('AgendaJobController', () => {
6
27
  let controller: AgendaJobController;
7
28
 
8
29
  beforeEach(async () => {
9
30
  const module: TestingModule = await Test.createTestingModule({
10
31
  controllers: [AgendaJobController],
11
- providers: [AgendaJobService],
12
- }).compile();
32
+ providers: [
33
+ { provide: AgendaJobService, useValue: mockAgendaJobService },
34
+ { provide: ConfigService, useValue: mockConfigService },
35
+ { provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
36
+ ],
37
+ })
38
+ // 兜底:自动 mock 任何未显式提供的依赖
39
+ // (例如 ACGuard 内部的 __roles_builder__ token)
40
+ .useMocker(() => ({}))
41
+ .compile();
13
42
 
14
43
  controller = module.get<AgendaJobController>(AgendaJobController);
15
44
  });
@@ -1,15 +1,44 @@
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 { AuditController } from './audit.controller';
3
6
  import { AuditService } from './audit.service';
4
7
 
8
+ // Mock AuditService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
9
+ // 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
10
+ const mockAuditService = {};
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('AuditController', () => {
6
27
  let controller: AuditController;
7
28
 
8
29
  beforeEach(async () => {
9
30
  const module: TestingModule = await Test.createTestingModule({
10
31
  controllers: [AuditController],
11
- providers: [AuditService],
12
- }).compile();
32
+ providers: [
33
+ { provide: AuditService, useValue: mockAuditService },
34
+ { provide: ConfigService, useValue: mockConfigService },
35
+ { provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
36
+ ],
37
+ })
38
+ // 兜底:自动 mock 任何未显式提供的依赖
39
+ // (例如 ACGuard 内部的 __roles_builder__ token)
40
+ .useMocker(() => ({}))
41
+ .compile();
13
42
 
14
43
  controller = module.get<AuditController>(AuditController);
15
44
  });