@gadmin2n/schematics 0.0.150 → 0.0.152

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 (82) 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 +2 -0
  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/CODEBUDDY.local.md +0 -8
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/ac.config.ts +0 -24
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +0 -495
@@ -13,13 +13,16 @@ import {
13
13
  Body,
14
14
  Controller,
15
15
  Delete,
16
+ Get,
16
17
  Inject,
17
18
  NotFoundException,
18
19
  Param,
19
20
  ParseIntPipe,
20
21
  Patch,
21
22
  Post,
23
+ Query,
22
24
  Req,
25
+ UnauthorizedException,
23
26
  UploadedFile,
24
27
  UseGuards,
25
28
  UseInterceptors,
@@ -43,6 +46,9 @@ import {
43
46
  } from '../../generated/resource/dto/resource-types.dto';
44
47
  import { UpdateResourceDto } from '../../generated/resource/dto/update-resource.dto';
45
48
  import { ResourceEntity } from '../../generated/resource/entities/resource.entity';
49
+ import { AllowUnauthorizedRequest } from '../../lib/auth.guard';
50
+ import { ImportPayloadDto } from '../permTransfer/perm-transfer.dto';
51
+ import { ResourceTransferService } from '../permTransfer/resource-transfer.service';
46
52
  import { ResourceService } from './resource.service';
47
53
 
48
54
  import { ConfigService } from '@nestjs/config';
@@ -54,6 +60,7 @@ import { FileInterceptor } from '@nestjs/platform-express';
54
60
  export class ResourceController {
55
61
  constructor(
56
62
  private readonly resourceService: ResourceService,
63
+ private readonly resourceTransferService: ResourceTransferService,
57
64
  private readonly config: ConfigService,
58
65
  @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
59
66
  ) {}
@@ -173,6 +180,54 @@ export class ResourceController {
173
180
  return res;
174
181
  }
175
182
 
183
+ // ─── Export/Import (cross-environment transfer) ────────────────────────────
184
+
185
+ @Post(':code/export-token')
186
+ @UseRoles({
187
+ resource: 'resource',
188
+ action: 'read',
189
+ possession: 'any',
190
+ })
191
+ async generateExportToken(@Param('code') code: string, @Req() req) {
192
+ const baseUrl = `${req.protocol}://${req.get('host')}/api`;
193
+ return this.resourceTransferService.generateExportToken(code, baseUrl);
194
+ }
195
+
196
+ @Get('export/:code')
197
+ @AllowUnauthorizedRequest()
198
+ async getExportPayload(
199
+ @Param('code') code: string,
200
+ @Query('token') token: string,
201
+ ) {
202
+ if (!token) {
203
+ throw new UnauthorizedException('Export token is required');
204
+ }
205
+ return this.resourceTransferService.getExportPayload(code, token);
206
+ }
207
+
208
+ @Post('import-preview')
209
+ @UseRoles({
210
+ resource: 'resource',
211
+ action: 'update',
212
+ possession: 'any',
213
+ })
214
+ async importPreview(@Body() dto: ImportPayloadDto) {
215
+ return this.resourceTransferService.importPreview(dto.payload);
216
+ }
217
+
218
+ @Post('import-confirm')
219
+ @UseRoles({
220
+ resource: 'resource',
221
+ action: 'update',
222
+ possession: 'any',
223
+ })
224
+ async importConfirm(@Body() dto: ImportPayloadDto, @Req() req) {
225
+ return this.resourceTransferService.importConfirm(
226
+ dto.payload,
227
+ req.user.username ?? req.user.userid,
228
+ );
229
+ }
230
+
176
231
  @Post('upload')
177
232
  @UseRoles({
178
233
  resource: 'resource',
@@ -1,8 +1,10 @@
1
1
  import { Module } from '@nestjs/common';
2
+ import { PermTransferModule } from '../permTransfer/perm-transfer.module';
2
3
  import { ResourceController } from './resource.controller';
3
4
  import { ResourceService } from './resource.service';
4
5
 
5
6
  @Module({
7
+ imports: [PermTransferModule],
6
8
  controllers: [ResourceController],
7
9
  providers: [ResourceService],
8
10
  exports: [ResourceService],
@@ -13,13 +13,16 @@ import {
13
13
  Body,
14
14
  Controller,
15
15
  Delete,
16
+ Get,
16
17
  Inject,
17
18
  NotFoundException,
18
19
  Param,
19
20
  ParseIntPipe,
20
21
  Patch,
21
22
  Post,
23
+ Query,
22
24
  Req,
25
+ UnauthorizedException,
23
26
  UploadedFile,
24
27
  UseGuards,
25
28
  UseInterceptors,
@@ -43,6 +46,9 @@ import {
43
46
  } from '../../generated/role/dto/role-types.dto';
44
47
  import { UpdateRoleDto } from '../../generated/role/dto/update-role.dto';
45
48
  import { RoleEntity } from '../../generated/role/entities/role.entity';
49
+ import { AllowUnauthorizedRequest } from '../../lib/auth.guard';
50
+ import { ImportPayloadDto } from '../permTransfer/perm-transfer.dto';
51
+ import { RoleTransferService } from '../permTransfer/role-transfer.service';
46
52
  import { RoleService } from './role.service';
47
53
 
48
54
  import { ConfigService } from '@nestjs/config';
@@ -71,6 +77,7 @@ export class CreateRoleWithPagesDto extends CreateRoleDto {
71
77
  export class RoleController {
72
78
  constructor(
73
79
  private readonly roleService: RoleService,
80
+ private readonly roleTransferService: RoleTransferService,
74
81
  private readonly config: ConfigService,
75
82
  @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
76
83
  ) {}
@@ -187,6 +194,54 @@ export class RoleController {
187
194
  return res;
188
195
  }
189
196
 
197
+ // ─── Export/Import (cross-environment transfer) ────────────────────────────
198
+
199
+ @Post(':name/export-token')
200
+ @UseRoles({
201
+ resource: 'role',
202
+ action: 'read',
203
+ possession: 'any',
204
+ })
205
+ async generateExportToken(@Param('name') name: string, @Req() req) {
206
+ const baseUrl = `${req.protocol}://${req.get('host')}/api`;
207
+ return this.roleTransferService.generateExportToken(name, baseUrl);
208
+ }
209
+
210
+ @Get('export/:name')
211
+ @AllowUnauthorizedRequest()
212
+ async getExportPayload(
213
+ @Param('name') name: string,
214
+ @Query('token') token: string,
215
+ ) {
216
+ if (!token) {
217
+ throw new UnauthorizedException('Export token is required');
218
+ }
219
+ return this.roleTransferService.getExportPayload(name, token);
220
+ }
221
+
222
+ @Post('import-preview')
223
+ @UseRoles({
224
+ resource: 'role',
225
+ action: 'update',
226
+ possession: 'any',
227
+ })
228
+ async importPreview(@Body() dto: ImportPayloadDto) {
229
+ return this.roleTransferService.importPreview(dto.payload);
230
+ }
231
+
232
+ @Post('import-confirm')
233
+ @UseRoles({
234
+ resource: 'role',
235
+ action: 'update',
236
+ possession: 'any',
237
+ })
238
+ async importConfirm(@Body() dto: ImportPayloadDto, @Req() req) {
239
+ return this.roleTransferService.importConfirm(
240
+ dto.payload,
241
+ req.user.username ?? req.user.userid,
242
+ );
243
+ }
244
+
190
245
  @Post('upload')
191
246
  @UseRoles({
192
247
  resource: 'role',
@@ -1,10 +1,12 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import { PrismaService } from 'nestjs-prisma';
3
+ import { PermTransferModule } from '../permTransfer/perm-transfer.module';
3
4
  import { RoleController } from './role.controller';
4
5
  import { RoleService } from './role.service';
5
6
  import { RolesRefresherService } from './roles-refresher.service';
6
7
 
7
8
  @Module({
9
+ imports: [PermTransferModule],
8
10
  providers: [RoleService, RolesRefresherService, PrismaService],
9
11
  controllers: [RoleController],
10
12
  exports: [RoleService],
@@ -0,0 +1,46 @@
1
+ import { UserGroupDomainError } from '../../userGroup/user-group.errors';
2
+ import { UserGroupProvider } from './user-group.provider';
3
+
4
+ function createHarness() {
5
+ const groups = {
6
+ search: jest.fn(),
7
+ resolveUsers: jest.fn(),
8
+ };
9
+ return {
10
+ groups,
11
+ provider: new UserGroupProvider(groups as never),
12
+ };
13
+ }
14
+
15
+ describe('UserGroupProvider', () => {
16
+ it('returns the active state supplied by the user group service', async () => {
17
+ const { groups, provider } = createHarness();
18
+ groups.search.mockResolvedValue([
19
+ {
20
+ code: 'finance',
21
+ name: 'Finance',
22
+ description: null,
23
+ isActive: false,
24
+ revision: 2,
25
+ },
26
+ ]);
27
+
28
+ await expect(provider.search('', { userId: 'admin' })).resolves.toEqual([
29
+ expect.objectContaining({ ref: 'finance', active: false }),
30
+ ]);
31
+ });
32
+
33
+ it('preserves domain errors while resolving a user group', async () => {
34
+ const { groups, provider } = createHarness();
35
+ const error = new UserGroupDomainError(
36
+ 'USER_GROUP_NOT_FOUND',
37
+ 'User group not found: missing',
38
+ 404,
39
+ );
40
+ groups.resolveUsers.mockRejectedValue(error);
41
+
42
+ await expect(
43
+ provider.resolve(['missing'], { actor: { userId: 'admin' } }),
44
+ ).rejects.toBe(error);
45
+ });
46
+ });
@@ -62,7 +62,7 @@ export class UserGroupProvider implements SubjectProvider {
62
62
  ref: group.code,
63
63
  label: group.name,
64
64
  ...(group.description ? { description: group.description } : {}),
65
- active: true,
65
+ active: group.isActive,
66
66
  revision: group.revision,
67
67
  }));
68
68
  } catch (error) {
@@ -177,7 +177,8 @@ export class UserGroupProvider implements SubjectProvider {
177
177
  );
178
178
  }
179
179
  }
180
- } catch {
180
+ } catch (error) {
181
+ if (error instanceof UserGroupDomainError) throw error;
181
182
  throw new SubjectProviderUnavailableError(this.key);
182
183
  }
183
184
  return [...principals.values()].sort((a, b) =>
@@ -13,13 +13,16 @@ import {
13
13
  Body,
14
14
  Controller,
15
15
  Delete,
16
+ Get,
16
17
  Inject,
17
18
  NotFoundException,
18
19
  Param,
19
20
  ParseIntPipe,
20
21
  Patch,
21
22
  Post,
23
+ Query,
22
24
  Req,
25
+ UnauthorizedException,
23
26
  UploadedFile,
24
27
  UseGuards,
25
28
  UseInterceptors,
@@ -43,6 +46,9 @@ import {
43
46
  UserWhereSelect,
44
47
  } from '../../generated/user/dto/user-types.dto';
45
48
  import { UserEntity } from '../../generated/user/entities/user.entity';
49
+ import { AllowUnauthorizedRequest } from '../../lib/auth.guard';
50
+ import { ImportPayloadDto } from '../permTransfer/perm-transfer.dto';
51
+ import { UserTransferService } from '../permTransfer/user-transfer.service';
46
52
  import { UserService } from './user.service';
47
53
 
48
54
  import { ConfigService } from '@nestjs/config';
@@ -55,6 +61,7 @@ import * as path from 'path';
55
61
  export class UserController {
56
62
  constructor(
57
63
  private readonly userService: UserService,
64
+ private readonly userTransferService: UserTransferService,
58
65
  private readonly config: ConfigService,
59
66
  @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
60
67
  ) {
@@ -186,6 +193,54 @@ export class UserController {
186
193
  return this.userService.deletePerson(id);
187
194
  }
188
195
 
196
+ // ─── Export/Import (cross-environment transfer) ────────────────────────────
197
+
198
+ @Post(':userid/export-token')
199
+ @UseRoles({
200
+ resource: 'user',
201
+ action: 'read',
202
+ possession: 'any',
203
+ })
204
+ async generateExportToken(@Param('userid') userid: string, @Req() req) {
205
+ const baseUrl = `${req.protocol}://${req.get('host')}/api`;
206
+ return this.userTransferService.generateExportToken(userid, baseUrl);
207
+ }
208
+
209
+ @Get('export/:userid')
210
+ @AllowUnauthorizedRequest()
211
+ async getExportPayload(
212
+ @Param('userid') userid: string,
213
+ @Query('token') token: string,
214
+ ) {
215
+ if (!token) {
216
+ throw new UnauthorizedException('Export token is required');
217
+ }
218
+ return this.userTransferService.getExportPayload(userid, token);
219
+ }
220
+
221
+ @Post('import-preview')
222
+ @UseRoles({
223
+ resource: 'user',
224
+ action: 'update',
225
+ possession: 'any',
226
+ })
227
+ async importPreview(@Body() dto: ImportPayloadDto) {
228
+ return this.userTransferService.importPreview(dto.payload);
229
+ }
230
+
231
+ @Post('import-confirm')
232
+ @UseRoles({
233
+ resource: 'user',
234
+ action: 'update',
235
+ possession: 'any',
236
+ })
237
+ async importConfirm(@Body() dto: ImportPayloadDto, @Req() req) {
238
+ return this.userTransferService.importConfirm(
239
+ dto.payload,
240
+ req.user.username ?? req.user.userid,
241
+ );
242
+ }
243
+
189
244
  @Post('upload')
190
245
  @UseRoles({
191
246
  resource: 'user',
@@ -1,8 +1,10 @@
1
1
  import { Module } from '@nestjs/common';
2
+ import { PermTransferModule } from '../permTransfer/perm-transfer.module';
2
3
  import { UserController } from './user.controller';
3
4
  import { UserService } from './user.service';
4
5
 
5
6
  @Module({
7
+ imports: [PermTransferModule],
6
8
  controllers: [UserController],
7
9
  providers: [UserService],
8
10
  exports: [UserService],
@@ -57,6 +57,7 @@ describe('Workflow cancellation', () => {
57
57
  temporal as any,
58
58
  {} as any,
59
59
  {} as any,
60
+ {} as any,
60
61
  );
61
62
  const runtime = new WorkflowRuntimeFacade(service);
62
63
  return { prisma, runtime, service, temporal, tx };
@@ -677,6 +677,57 @@ describe('WorkflowCompilerService', () => {
677
677
  });
678
678
  });
679
679
 
680
+ it('validates the failure callback connection on an event trigger', async () => {
681
+ const findConnection = jest.fn().mockResolvedValue({ isEnabled: true });
682
+ const prisma = {
683
+ workflowNodeType: {
684
+ findMany: jest.fn().mockResolvedValue([
685
+ {
686
+ id: BigInt(8),
687
+ type: 'event_trigger',
688
+ runtimeKey: 'event_trigger',
689
+ instancePolicy: 'OPTIONAL',
690
+ schemaVersion: 6,
691
+ configSchema: { type: 'object' },
692
+ uiSchema: {},
693
+ },
694
+ ]),
695
+ },
696
+ workflowNodeInstance: { findMany: jest.fn().mockResolvedValue([]) },
697
+ integrationConnection: { findUnique: findConnection },
698
+ };
699
+ const service = new WorkflowCompilerService(
700
+ prisma as any,
701
+ { validateOrThrow: jest.fn() } as any,
702
+ { assertUiSchemaSupported: jest.fn() } as any,
703
+ { validateSubjects: jest.fn() } as any,
704
+ );
705
+
706
+ await service.compile(
707
+ {
708
+ nodes: [
709
+ {
710
+ id: 'event',
711
+ type: 'event_trigger',
712
+ config: {
713
+ failureCallback: {
714
+ connectionRef: 'hardware-service',
715
+ path: '/api/hardware-disposal/workflow-failed',
716
+ },
717
+ },
718
+ },
719
+ ],
720
+ edges: [],
721
+ },
722
+ { userId: 'author' },
723
+ );
724
+
725
+ expect(findConnection).toHaveBeenCalledWith({
726
+ where: { code: 'hardware-service' },
727
+ select: { isEnabled: true },
728
+ });
729
+ });
730
+
680
731
  it('validates required inputs from the selected published version', async () => {
681
732
  const findWorkflow = jest.fn().mockResolvedValue({
682
733
  status: 'PUBLISHED',
@@ -898,7 +949,7 @@ describe('WorkflowCompilerService', () => {
898
949
  ).resolves.toBeUndefined();
899
950
  });
900
951
 
901
- it('rejects a data-modifying statement when database writes are disabled', async () => {
952
+ it('rejects legacy raw SQL database configuration', async () => {
902
953
  const prisma = {
903
954
  workflowNodeType: {
904
955
  findMany: jest.fn().mockResolvedValue([
@@ -907,7 +958,7 @@ describe('WorkflowCompilerService', () => {
907
958
  type: 'db_execute',
908
959
  runtimeKey: 'db_execute',
909
960
  instancePolicy: 'INLINE_ONLY',
910
- schemaVersion: 2,
961
+ schemaVersion: 5,
911
962
  configSchema: { type: 'object' },
912
963
  uiSchema: {},
913
964
  },
@@ -930,8 +981,8 @@ describe('WorkflowCompilerService', () => {
930
981
  id: 'database-1',
931
982
  type: 'db_execute',
932
983
  config: {
933
- allowWrite: false,
934
- sql: 'WITH removed AS (DELETE FROM users RETURNING id) SELECT * FROM removed',
984
+ allowWrite: true,
985
+ sql: 'UPDATE users SET active = false',
935
986
  },
936
987
  },
937
988
  ],
@@ -941,13 +992,13 @@ describe('WorkflowCompilerService', () => {
941
992
  ),
942
993
  ).rejects.toMatchObject({
943
994
  response: {
944
- message: '当前数据库操作节点只允许单条只读查询',
995
+ message: '数据库节点不再支持 SQL,请选择 workflow_api 中的 Function',
945
996
  nodeId: 'database-1',
946
997
  },
947
998
  });
948
999
  });
949
1000
 
950
- it('accepts a single data write when database writes are enabled', async () => {
1001
+ it('accepts a registered workflow_api Function with matching arguments', async () => {
951
1002
  const prisma = {
952
1003
  workflowNodeType: {
953
1004
  findMany: jest.fn().mockResolvedValue([
@@ -963,6 +1014,18 @@ describe('WorkflowCompilerService', () => {
963
1014
  ]),
964
1015
  },
965
1016
  workflowNodeInstance: { findMany: jest.fn().mockResolvedValue([]) },
1017
+ $queryRaw: jest.fn().mockResolvedValue([
1018
+ {
1019
+ schema: 'workflow_api',
1020
+ name: 'approve_request',
1021
+ identity_arguments: 'p_request_id bigint, p_approved boolean',
1022
+ return_type: 'jsonb',
1023
+ arguments: [
1024
+ { position: 1, name: 'p_request_id', type: 'bigint' },
1025
+ { position: 2, name: 'p_approved', type: 'boolean' },
1026
+ ],
1027
+ },
1028
+ ]),
966
1029
  };
967
1030
  const service = new WorkflowCompilerService(
968
1031
  prisma as any,
@@ -979,8 +1042,15 @@ describe('WorkflowCompilerService', () => {
979
1042
  id: 'database-1',
980
1043
  type: 'db_execute',
981
1044
  config: {
982
- allowWrite: true,
983
- sql: 'UPDATE users SET active = $1 WHERE id = $2',
1045
+ functionRef: {
1046
+ schema: 'workflow_api',
1047
+ name: 'approve_request',
1048
+ identityArguments: 'p_request_id bigint, p_approved boolean',
1049
+ },
1050
+ argumentBindings: [
1051
+ { source: 'INPUT', path: 'requestId' },
1052
+ { source: 'FIXED', valueType: 'BOOLEAN', value: 'true' },
1053
+ ],
984
1054
  },
985
1055
  },
986
1056
  ],
@@ -992,13 +1062,17 @@ describe('WorkflowCompilerService', () => {
992
1062
  nodes: [
993
1063
  expect.objectContaining({
994
1064
  id: 'database-1',
995
- config: expect.objectContaining({ allowWrite: true }),
1065
+ config: expect.objectContaining({
1066
+ functionRef: expect.objectContaining({
1067
+ name: 'approve_request',
1068
+ }),
1069
+ }),
996
1070
  }),
997
1071
  ],
998
1072
  });
999
1073
  });
1000
1074
 
1001
- it('accepts one writable CTE when database writes are enabled', async () => {
1075
+ it('rejects a Function whose signature is not in the database catalog', async () => {
1002
1076
  const prisma = {
1003
1077
  workflowNodeType: {
1004
1078
  findMany: jest.fn().mockResolvedValue([
@@ -1014,6 +1088,7 @@ describe('WorkflowCompilerService', () => {
1014
1088
  ]),
1015
1089
  },
1016
1090
  workflowNodeInstance: { findMany: jest.fn().mockResolvedValue([]) },
1091
+ $queryRaw: jest.fn().mockResolvedValue([]),
1017
1092
  };
1018
1093
  const service = new WorkflowCompilerService(
1019
1094
  prisma as any,
@@ -1030,8 +1105,12 @@ describe('WorkflowCompilerService', () => {
1030
1105
  id: 'database-1',
1031
1106
  type: 'db_execute',
1032
1107
  config: {
1033
- allowWrite: true,
1034
- sql: 'WITH changed AS (UPDATE requests SET status = $1 RETURNING id) UPDATE assets SET state = $2 WHERE request_id IN (SELECT id FROM changed)',
1108
+ functionRef: {
1109
+ schema: 'workflow_api',
1110
+ name: 'missing_function',
1111
+ identityArguments: 'p_id bigint',
1112
+ },
1113
+ argumentBindings: [{ source: 'INPUT', path: 'id' }],
1035
1114
  },
1036
1115
  },
1037
1116
  ],
@@ -1039,8 +1118,11 @@ describe('WorkflowCompilerService', () => {
1039
1118
  },
1040
1119
  { userId: 'author' },
1041
1120
  ),
1042
- ).resolves.toMatchObject({
1043
- nodes: [expect.objectContaining({ id: 'database-1' })],
1121
+ ).rejects.toMatchObject({
1122
+ response: {
1123
+ message: '选择的数据库 Function 不存在或已不再开放给 Workflow',
1124
+ nodeId: 'database-1',
1125
+ },
1044
1126
  });
1045
1127
  });
1046
1128
  });