@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.
- 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 +7 -6
- 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/index.tsx +1 -0
- 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/.gitattributes +0 -2
- 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,16 +1,40 @@
|
|
|
1
1
|
import { PrismaClient } from '@prisma/client';
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
12
|
-
await
|
|
13
|
-
|
|
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
|
+
}
|
package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts
CHANGED
|
@@ -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: {
|
|
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
|
-
|
|
49
|
-
|
|
42
|
+
description:
|
|
43
|
+
'Webhook endpoint path segment, e.g. "order-paid". Final URL: POST /api/workflow/webhook/{path}',
|
|
50
44
|
},
|
|
51
|
-
|
|
45
|
+
method: {
|
|
52
46
|
type: 'string',
|
|
53
|
-
enum: ['
|
|
54
|
-
description: '
|
|
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
|
-
|
|
50
|
+
secret: {
|
|
61
51
|
type: 'string',
|
|
62
|
-
description:
|
|
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: [
|
|
12
|
-
|
|
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: [
|
|
12
|
-
|
|
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
|
});
|