@gadmin2n/schematics 0.0.149 → 0.0.151

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 (81) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.md +125 -36
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +42 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/gitignore +2 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.seed-timestamp +0 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +3 -1
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/check-dev-env.js +218 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/page-manage.ts +78 -12
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +37 -67
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +0 -17
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +46 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +60 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.ts +3 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.controller.ts +9 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +55 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +2 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/page-transfer.service.ts +491 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/perm-transfer.dto.ts +213 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/perm-transfer.module.ts +28 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/resource-transfer.service.ts +137 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/role-transfer.service.ts +248 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/transfer-token.service.ts +65 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/user-transfer.service.ts +146 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +55 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +2 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +55 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +2 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user-group.provider.spec.ts +46 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user-group.provider.ts +3 -2
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +55 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +2 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +1 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +96 -14
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +41 -41
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-database-function.spec.ts +36 -0
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-database-function.ts +90 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +4 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +1 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +115 -63
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-outbox.service.spec.ts +166 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-outbox.service.ts +217 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.spec.ts +33 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.ts +57 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.spec.ts +196 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.ts +125 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.controller.ts +9 -3
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +148 -4
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.module.ts +12 -1
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +14 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +75 -45
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +25 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/TransferExportModal.tsx +115 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/TransferImportModal.tsx +257 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +0 -2
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +40 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +15 -13
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +40 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +15 -13
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +2 -1
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +89 -65
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +51 -127
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permissionReadme/index.tsx +53 -109
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +45 -41
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +51 -72
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +49 -40
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +16 -5
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ReadonlyNodePanel.test.tsx +28 -10
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +60 -1
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +94 -12
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +13 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/default-widgets.tsx +3 -3
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +8 -1
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +167 -94
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +95 -95
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerLifecycleConfigEditor.test.tsx +81 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerLifecycleConfigEditor.tsx +126 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +48 -52
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/forEachConnection.ts +2 -2
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +1 -3
  79. package/package.json +1 -1
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/ac.config.ts +0 -24
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +0 -495
@@ -1,495 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Modal, Button, Typography, message, Tooltip } from 'antd';
3
- import { CopyOutlined, CheckOutlined } from '@ant-design/icons';
4
-
5
- const { Text } = Typography;
6
-
7
- interface SqlModalProps {
8
- open: boolean;
9
- onClose: () => void;
10
- title: string;
11
- sql: string;
12
- }
13
-
14
- export const SqlModal: React.FC<SqlModalProps> = ({
15
- open,
16
- onClose,
17
- title,
18
- sql,
19
- }) => {
20
- const [copied, setCopied] = useState(false);
21
-
22
- const handleCopy = () => {
23
- navigator.clipboard.writeText(sql).then(() => {
24
- setCopied(true);
25
- message.success('SQL copied to clipboard');
26
- setTimeout(() => setCopied(false), 2000);
27
- });
28
- };
29
-
30
- return (
31
- <Modal
32
- title={title}
33
- open={open}
34
- onCancel={onClose}
35
- width={720}
36
- footer={[
37
- <Tooltip key="copy" title={copied ? 'Copied!' : 'Copy SQL'}>
38
- <Button
39
- icon={copied ? <CheckOutlined /> : <CopyOutlined />}
40
- type="primary"
41
- onClick={handleCopy}
42
- >
43
- {copied ? 'Copied!' : 'Copy SQL'}
44
- </Button>
45
- </Tooltip>,
46
- <Button key="close" onClick={onClose}>
47
- Close
48
- </Button>,
49
- ]}
50
- >
51
- <pre
52
- style={{
53
- background: '#1e1e1e',
54
- color: '#d4d4d4',
55
- padding: '16px',
56
- borderRadius: '6px',
57
- overflowX: 'auto',
58
- overflowY: 'auto',
59
- maxHeight: '500px',
60
- fontSize: '12px',
61
- lineHeight: '1.6',
62
- whiteSpace: 'pre-wrap',
63
- wordBreak: 'break-word',
64
- fontFamily:
65
- "'Fira Code', 'Cascadia Code', 'Consolas', 'Monaco', monospace",
66
- }}
67
- >
68
- <Text
69
- style={{
70
- color: 'inherit',
71
- fontSize: 'inherit',
72
- fontFamily: 'inherit',
73
- }}
74
- >
75
- {sql}
76
- </Text>
77
- </pre>
78
- </Modal>
79
- );
80
- };
81
-
82
- // ─── SQL Generators ────────────────────────────────────────────────────────────
83
-
84
- function escapeStr(val: string | null | undefined): string {
85
- if (val == null) return 'NULL';
86
- return `'${String(val).replace(/'/g, "''")}'`;
87
- }
88
-
89
- function jsonArr(val: string | null | undefined): string {
90
- // roles is stored as a JSON-encoded array string in the DB, e.g. '["GUEST","PRODUCT"]'
91
- if (val == null) return "'{}'";
92
- return `'${String(val).replace(/'/g, "''")}'`;
93
- }
94
-
95
- /**
96
- * Generate an INSERT for t_admin_audit_log.
97
- * authorId / authorName come from the currently logged-in user passed by the caller.
98
- */
99
- function auditInsert(opts: {
100
- action: string;
101
- resource: string;
102
- resourceId: string;
103
- data: string; // JSON string of the record
104
- creator?: string | null;
105
- authorId?: string | null;
106
- authorName?: string | null;
107
- }): string {
108
- const dataJson = escapeStr(opts.data);
109
- const authorId = opts.authorId ?? '<author_id>';
110
- const authorName = opts.authorName ?? '<author_name>';
111
- return `INSERT INTO public.t_admin_audit_log (creator, "authorId", "authorName", action, resource, "resourceId", data, "previousData")
112
- VALUES (
113
- ${escapeStr(authorId)},
114
- ${escapeStr(authorId)},
115
- ${escapeStr(authorName)},
116
- ${escapeStr(opts.action)},
117
- ${escapeStr(opts.resource)},
118
- ${escapeStr(opts.resourceId)},
119
- ${dataJson},
120
- NULL
121
- );`;
122
- }
123
-
124
- /**
125
- * Generate INSERT SQL for a User record.
126
- * Table: t_admin_user_role
127
- */
128
- export function generateUserSql(
129
- user: {
130
- userid: string;
131
- username?: string | null;
132
- roles?: string | null;
133
- isApiKey?: boolean;
134
- apiKey?: string | null;
135
- description?: string | null;
136
- isActive?: boolean;
137
- creator?: string | null;
138
- },
139
- operator?: { authorId?: string | null; authorName?: string | null },
140
- ): string {
141
- const rolesValue = user.roles ?? '[]';
142
- const isApiKey = user.isApiKey ? 'true' : 'false';
143
- const isActive = user.isActive !== false ? 'true' : 'false';
144
-
145
- const userDataJson = JSON.stringify({
146
- userid: user.userid,
147
- username: user.username ?? null,
148
- roles: rolesValue,
149
- isApiKey: user.isApiKey ?? false,
150
- description: user.description ?? null,
151
- isActive: user.isActive !== false,
152
- });
153
-
154
- const mainSql = `-- Insert User: ${user.userid}
155
- INSERT INTO public.t_admin_user_role (userid, username, roles, "isApiKey", "apiKey", description, "isActive", creator, "createdAt", "updatedAt")
156
- VALUES (
157
- ${escapeStr(user.userid)},
158
- ${escapeStr(user.username)},
159
- ${jsonArr(rolesValue)},
160
- ${isApiKey},
161
- ${user.apiKey ? escapeStr(user.apiKey) : 'NULL'},
162
- ${escapeStr(user.description)},
163
- ${isActive},
164
- ${escapeStr(user.creator)},
165
- NOW(),
166
- NOW()
167
- )
168
- ON CONFLICT (userid) DO UPDATE SET
169
- username = EXCLUDED.username,
170
- roles = EXCLUDED.roles,
171
- "isApiKey" = EXCLUDED."isApiKey",
172
- "apiKey" = EXCLUDED."apiKey",
173
- description = EXCLUDED.description,
174
- "isActive" = EXCLUDED."isActive",
175
- "updatedAt" = NOW();`;
176
-
177
- return [
178
- mainSql,
179
- '',
180
- auditInsert({
181
- action: 'create',
182
- resource: 'user',
183
- resourceId: user.userid,
184
- data: userDataJson,
185
- creator: user.creator,
186
- ...operator,
187
- }),
188
- ].join('\n');
189
- }
190
-
191
- /**
192
- * Generate INSERT SQL for a Resource record.
193
- * Table: t_resource
194
- */
195
- export function generateResourceSql(
196
- resource: {
197
- code: string;
198
- name: string;
199
- description?: string | null;
200
- isCommonResource?: boolean;
201
- creator?: string | null;
202
- },
203
- operator?: { authorId?: string | null; authorName?: string | null },
204
- ): string {
205
- const isCommon = resource.isCommonResource ? 'true' : 'false';
206
-
207
- const resourceDataJson = JSON.stringify({
208
- code: resource.code,
209
- name: resource.name,
210
- description: resource.description ?? null,
211
- isCommonResource: resource.isCommonResource ?? false,
212
- });
213
-
214
- const mainSql = `-- Insert Resource: ${resource.code}
215
- INSERT INTO public.t_resource (code, name, description, is_common_resource, creator, "createdAt", "updatedAt")
216
- VALUES (
217
- ${escapeStr(resource.code)},
218
- ${escapeStr(resource.name)},
219
- ${escapeStr(resource.description)},
220
- ${isCommon},
221
- ${escapeStr(resource.creator)},
222
- NOW(),
223
- NOW()
224
- )
225
- ON CONFLICT (code) DO UPDATE SET
226
- name = EXCLUDED.name,
227
- description = EXCLUDED.description,
228
- is_common_resource = EXCLUDED.is_common_resource,
229
- "updatedAt" = NOW();`;
230
-
231
- return [
232
- mainSql,
233
- '',
234
- auditInsert({
235
- action: 'create',
236
- resource: 'resource',
237
- resourceId: resource.code,
238
- data: resourceDataJson,
239
- creator: resource.creator,
240
- ...operator,
241
- }),
242
- ].join('\n');
243
- }
244
-
245
- /**
246
- * Generate INSERT SQL for a Role record, including its page assignments and
247
- * the resulting role_resource entries.
248
- *
249
- * rolePages: list of { pageId, pageName }
250
- * roleResources: list of { resourceCode, resourceName, actions: string[] }
251
- */
252
- export function generateRoleSql(
253
- role: {
254
- name: string;
255
- description?: string | null;
256
- creator?: string | null;
257
- },
258
- rolePages: Array<{ pageId: number; pageName: string; pageCode: string }>,
259
- roleResources: Array<{
260
- resourceCode: string;
261
- resourceName: string;
262
- actions: string[];
263
- }>,
264
- operator?: { authorId?: string | null; authorName?: string | null },
265
- ): string {
266
- const lines: string[] = [];
267
-
268
- // 1. Role
269
- lines.push(`-- ════════════════════════════════════════════════════════`);
270
- lines.push(`-- Role: ${role.name}`);
271
- lines.push(`-- ════════════════════════════════════════════════════════`);
272
- lines.push(
273
- `INSERT INTO public.t_role (name, description, creator, "createdAt", "updatedAt")`,
274
- );
275
- lines.push(`VALUES (`);
276
- lines.push(` ${escapeStr(role.name)},`);
277
- lines.push(` ${escapeStr(role.description)},`);
278
- lines.push(` ${escapeStr(role.creator)},`);
279
- lines.push(` NOW(),`);
280
- lines.push(` NOW()`);
281
- lines.push(`)`);
282
- lines.push(`ON CONFLICT (name) DO UPDATE SET`);
283
- lines.push(` description = EXCLUDED.description,`);
284
- lines.push(` "updatedAt" = NOW();`);
285
-
286
- // 2. Role-page associations
287
- if (rolePages.length > 0) {
288
- lines.push(``);
289
- lines.push(`-- Role-Page Associations for "${role.name}"`);
290
- lines.push(`-- Pages: ${rolePages.map((p) => p.pageName).join(', ')}`);
291
-
292
- for (const rp of rolePages) {
293
- lines.push(``);
294
- lines.push(
295
- `INSERT INTO public.t_role_pages (role_id, page_id, "created_at", "updated_at")`,
296
- );
297
- lines.push(`SELECT r.id, p.id, NOW(), NOW()`);
298
- lines.push(`FROM public.t_role r, public.t_page p`);
299
- lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
300
- lines.push(` AND p.code = ${escapeStr(rp.pageCode)} -- ${rp.pageName}`);
301
- lines.push(`ON CONFLICT (role_id, page_id) DO NOTHING;`);
302
- }
303
- }
304
-
305
- // 3. Role-resource associations
306
- if (roleResources.length > 0) {
307
- lines.push(``);
308
- lines.push(`-- Role-Resource Permissions for "${role.name}"`);
309
-
310
- for (const rr of roleResources) {
311
- const actionsJson = JSON.stringify(rr.actions);
312
- lines.push(``);
313
- lines.push(
314
- `INSERT INTO public.t_role_resource (role_id, resource_id, actions, "created_at", "updated_at")`,
315
- );
316
- lines.push(`SELECT r.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`);
317
- lines.push(`FROM public.t_role r, public.t_resource res`);
318
- lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
319
- lines.push(
320
- ` AND res.code = ${escapeStr(rr.resourceCode)} -- ${rr.resourceName}`,
321
- );
322
- lines.push(`ON CONFLICT (role_id, resource_id) DO UPDATE SET`);
323
- lines.push(` actions = EXCLUDED.actions,`);
324
- lines.push(` "updated_at" = NOW();`);
325
- }
326
- }
327
-
328
- // 4. Audit log
329
- const roleDataJson = JSON.stringify({
330
- name: role.name,
331
- description: role.description ?? null,
332
- });
333
- lines.push(``);
334
- lines.push(
335
- auditInsert({
336
- action: 'create',
337
- resource: 'role',
338
- resourceId: role.name,
339
- data: roleDataJson,
340
- creator: role.creator,
341
- ...operator,
342
- }),
343
- );
344
-
345
- return lines.join('\n');
346
- }
347
-
348
- /**
349
- * Generate INSERT SQL for a Page and (optionally) its children.
350
- *
351
- * When called on a parent page, pass all child pages in `children`.
352
- * Each page includes its pageResources (resource assignments).
353
- *
354
- * pageResources: list of { resourceCode, resourceName, actions }
355
- */
356
- export function generatePageSql(
357
- page: {
358
- id: number;
359
- code: string;
360
- name: string;
361
- path?: string | null;
362
- description?: string | null;
363
- zhName?: string | null;
364
- enName?: string | null;
365
- sortOrder?: number | null;
366
- parentId?: number | null;
367
- /** code of the parent page — used to generate a portable subquery instead of a raw numeric ID */
368
- parentCode?: string | null;
369
- isVirtual?: boolean;
370
- creator?: string | null;
371
- },
372
- pageResources: Array<{
373
- resourceCode: string;
374
- resourceName: string;
375
- actions: string[];
376
- }>,
377
- children: Array<{
378
- page: typeof page;
379
- pageResources: Array<{
380
- resourceCode: string;
381
- resourceName: string;
382
- actions: string[];
383
- }>;
384
- }>,
385
- operator?: { authorId?: string | null; authorName?: string | null },
386
- ): string {
387
- const lines: string[] = [];
388
-
389
- const renderPageInsert = (
390
- p: typeof page,
391
- res: Array<{
392
- resourceCode: string;
393
- resourceName: string;
394
- actions: string[];
395
- }>,
396
- parentRef: string | null,
397
- ) => {
398
- lines.push(`-- ────────────────────────────────────────────────────────`);
399
- lines.push(`-- Page: ${p.name} (code: ${p.code})`);
400
- lines.push(`-- ────────────────────────────────────────────────────────`);
401
- lines.push(`INSERT INTO public.t_page (`);
402
- lines.push(
403
- ` code, name, path, description, zh_name, en_name, sort_order, parent_id, "isVirtual", creator, created_at, updated_at`,
404
- );
405
- lines.push(`)`);
406
- lines.push(`VALUES (`);
407
- lines.push(` ${escapeStr(p.code)},`);
408
- lines.push(` ${escapeStr(p.name)},`);
409
- lines.push(` ${escapeStr(p.path)},`);
410
- lines.push(` ${escapeStr(p.description)},`);
411
- lines.push(` ${escapeStr(p.zhName)},`);
412
- lines.push(` ${escapeStr(p.enName)},`);
413
- lines.push(` ${p.sortOrder != null ? p.sortOrder : 'NULL'},`);
414
- if (parentRef) {
415
- lines.push(` (${parentRef}), -- parent_id resolved by code`);
416
- } else {
417
- lines.push(` NULL,`);
418
- }
419
- lines.push(` ${p.isVirtual ? 'true' : 'false'},`);
420
- lines.push(` ${escapeStr(p.creator)},`);
421
- lines.push(` NOW(),`);
422
- lines.push(` NOW()`);
423
- lines.push(`)`);
424
- lines.push(`ON CONFLICT (code) DO UPDATE SET`);
425
- lines.push(` name = EXCLUDED.name,`);
426
- lines.push(` path = EXCLUDED.path,`);
427
- lines.push(` description = EXCLUDED.description,`);
428
- lines.push(` zh_name = EXCLUDED.zh_name,`);
429
- lines.push(` en_name = EXCLUDED.en_name,`);
430
- lines.push(` sort_order = EXCLUDED.sort_order,`);
431
- lines.push(` parent_id = EXCLUDED.parent_id,`);
432
- lines.push(` updated_at = NOW();`);
433
-
434
- if (res.length > 0) {
435
- lines.push(``);
436
- lines.push(`-- Page-Resource Associations for "${p.name}"`);
437
- for (const pr of res) {
438
- const actionsJson = JSON.stringify(pr.actions);
439
- lines.push(``);
440
- lines.push(
441
- `INSERT INTO public.t_page_resource (page_id, resource_id, actions, "created_at", "updated_at")`,
442
- );
443
- lines.push(
444
- `SELECT pg.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`,
445
- );
446
- lines.push(`FROM public.t_page pg, public.t_resource res`);
447
- lines.push(`WHERE pg.code = ${escapeStr(p.code)}`);
448
- lines.push(
449
- ` AND res.code = ${escapeStr(pr.resourceCode)} -- ${pr.resourceName}`,
450
- );
451
- lines.push(`ON CONFLICT (page_id, resource_id) DO UPDATE SET`);
452
- lines.push(` actions = EXCLUDED.actions,`);
453
- lines.push(` "updated_at" = NOW();`);
454
- }
455
- }
456
-
457
- // Audit log for this page
458
- const pageDataJson = JSON.stringify({
459
- code: p.code,
460
- name: p.name,
461
- path: p.path ?? null,
462
- description: p.description ?? null,
463
- zhName: p.zhName ?? null,
464
- enName: p.enName ?? null,
465
- sortOrder: p.sortOrder ?? null,
466
- isVirtual: p.isVirtual ?? false,
467
- });
468
- lines.push(``);
469
- lines.push(
470
- auditInsert({
471
- action: 'create',
472
- resource: 'page',
473
- resourceId: p.code,
474
- data: pageDataJson,
475
- creator: p.creator,
476
- ...operator,
477
- }),
478
- );
479
- };
480
-
481
- // Parent page — if it has a parentCode, resolve parent_id dynamically by code
482
- const rootParentRef = page.parentCode
483
- ? `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.parentCode)}`
484
- : null;
485
- renderPageInsert(page, pageResources, rootParentRef);
486
-
487
- // Children
488
- for (const child of children) {
489
- lines.push(``);
490
- const parentRef = `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.code)}`;
491
- renderPageInsert(child.page, child.pageResources, parentRef);
492
- }
493
-
494
- return lines.join('\n');
495
- }