@gadmin2n/schematics 0.0.135 → 0.0.137
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/Dockerfile.server +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
- package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +7 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/gen-jwt.ts +85 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
import { UserModule } from '../user/user.module';
|
|
3
|
+
import { CanvasExportService } from './canvas-export.service';
|
|
3
4
|
import { CanvasController } from './canvas.controller';
|
|
4
5
|
import { CanvasService } from './canvas.service';
|
|
5
6
|
|
|
6
7
|
@Module({
|
|
7
8
|
imports: [UserModule],
|
|
8
9
|
controllers: [CanvasController],
|
|
9
|
-
providers: [CanvasService],
|
|
10
|
+
providers: [CanvasService, CanvasExportService],
|
|
10
11
|
})
|
|
11
12
|
export class CanvasModule {}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
4
|
+
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
5
|
+
import { TemporalTenantConfigController } from './temporalTenantConfig.controller';
|
|
6
|
+
import { TemporalTenantConfigService } from './temporalTenantConfig.service';
|
|
7
|
+
|
|
8
|
+
// Mock TemporalTenantConfigService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
|
|
9
|
+
// 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
|
|
10
|
+
const mockTemporalTenantConfigService = {};
|
|
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
|
+
|
|
26
|
+
describe('TemporalTenantConfigController', () => {
|
|
27
|
+
let controller: TemporalTenantConfigController;
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
31
|
+
controllers: [TemporalTenantConfigController],
|
|
32
|
+
providers: [
|
|
33
|
+
{
|
|
34
|
+
provide: TemporalTenantConfigService,
|
|
35
|
+
useValue: mockTemporalTenantConfigService,
|
|
36
|
+
},
|
|
37
|
+
{ provide: ConfigService, useValue: mockConfigService },
|
|
38
|
+
{ provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
// 兜底:自动 mock 任何未显式提供的依赖
|
|
42
|
+
// (例如 ACGuard 内部的 __roles_builder__ token)
|
|
43
|
+
.useMocker(() => ({}))
|
|
44
|
+
.compile();
|
|
45
|
+
|
|
46
|
+
controller = module.get<TemporalTenantConfigController>(
|
|
47
|
+
TemporalTenantConfigController,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should be defined', () => {
|
|
52
|
+
expect(controller).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACAttrFilter,
|
|
3
|
+
ACGuard,
|
|
4
|
+
ApiPaginatedResponse,
|
|
5
|
+
diskStorage,
|
|
6
|
+
PageDto,
|
|
7
|
+
PageMetaDto,
|
|
8
|
+
PageOptionsDto,
|
|
9
|
+
pick,
|
|
10
|
+
UserType,
|
|
11
|
+
} from '@gadmin2n/nest-common';
|
|
12
|
+
import {
|
|
13
|
+
BadRequestException,
|
|
14
|
+
Body,
|
|
15
|
+
Controller,
|
|
16
|
+
Delete,
|
|
17
|
+
Inject,
|
|
18
|
+
NotFoundException,
|
|
19
|
+
Param,
|
|
20
|
+
ParseIntPipe,
|
|
21
|
+
Patch,
|
|
22
|
+
Post,
|
|
23
|
+
Req,
|
|
24
|
+
UploadedFile,
|
|
25
|
+
UseGuards,
|
|
26
|
+
UseInterceptors,
|
|
27
|
+
} from '@nestjs/common';
|
|
28
|
+
import {
|
|
29
|
+
ApiCreatedResponse,
|
|
30
|
+
ApiNotFoundResponse,
|
|
31
|
+
ApiOkResponse,
|
|
32
|
+
ApiTags,
|
|
33
|
+
} from '@nestjs/swagger';
|
|
34
|
+
import { Prisma } from '@prisma/client';
|
|
35
|
+
import { UseRoles } from 'nest-access-control';
|
|
36
|
+
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
37
|
+
import { Logger } from 'winston';
|
|
38
|
+
import { CreateTemporalTenantConfigDto } from '../../generated/temporalTenantConfig/dto/create-temporalTenantConfig.dto';
|
|
39
|
+
import {
|
|
40
|
+
EffectedCount,
|
|
41
|
+
TemporalTenantConfigFindManyArgs,
|
|
42
|
+
TemporalTenantConfigWhereSelect,
|
|
43
|
+
UploadResponse,
|
|
44
|
+
} from '../../generated/temporalTenantConfig/dto/temporalTenantConfig-types.dto';
|
|
45
|
+
import { UpdateTemporalTenantConfigDto } from '../../generated/temporalTenantConfig/dto/update-temporalTenantConfig.dto';
|
|
46
|
+
import { TemporalTenantConfigEntity } from '../../generated/temporalTenantConfig/entities/temporalTenantConfig.entity';
|
|
47
|
+
import { TemporalTenantConfigService } from './temporalTenantConfig.service';
|
|
48
|
+
|
|
49
|
+
import { ConfigService } from '@nestjs/config';
|
|
50
|
+
import { FileInterceptor } from '@nestjs/platform-express';
|
|
51
|
+
|
|
52
|
+
@UseGuards(ACGuard)
|
|
53
|
+
@Controller('temporalTenantConfig')
|
|
54
|
+
@ApiTags('temporalTenantConfig')
|
|
55
|
+
export class TemporalTenantConfigController {
|
|
56
|
+
constructor(
|
|
57
|
+
private readonly temporalTenantConfigService: TemporalTenantConfigService,
|
|
58
|
+
private readonly config: ConfigService,
|
|
59
|
+
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
@Post('createOne')
|
|
63
|
+
@UseRoles({
|
|
64
|
+
resource: 'temporalTenantConfig',
|
|
65
|
+
action: 'create',
|
|
66
|
+
possession: 'any',
|
|
67
|
+
})
|
|
68
|
+
@ApiCreatedResponse({ type: TemporalTenantConfigEntity })
|
|
69
|
+
async createOne(
|
|
70
|
+
@Body() body: CreateTemporalTenantConfigDto,
|
|
71
|
+
@ACAttrFilter() acAttrFilter,
|
|
72
|
+
@Req() request: Request & { user: UserType },
|
|
73
|
+
) {
|
|
74
|
+
body.creator = request.user.username;
|
|
75
|
+
|
|
76
|
+
// 先调 shared temporal admin API(原子:失败则不写 DB)
|
|
77
|
+
const res = await fetch(`${body.adminUrl}/admin/tenants`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'X-Admin-Token': body.adminToken,
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
namespace: body.namespace,
|
|
85
|
+
displayName: body.displayName,
|
|
86
|
+
dbUrl: body.dbUrl,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
const detail = await res.text().catch(() => '');
|
|
91
|
+
throw new BadRequestException(
|
|
92
|
+
`shared temporal API 注册失败 (${res.status}): ${detail}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return new TemporalTenantConfigEntity(
|
|
97
|
+
await this.temporalTenantConfigService.createOne({
|
|
98
|
+
data: acAttrFilter(body),
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Post('findMany')
|
|
104
|
+
@UseRoles({
|
|
105
|
+
resource: 'temporalTenantConfig',
|
|
106
|
+
action: 'read',
|
|
107
|
+
possession: 'any',
|
|
108
|
+
})
|
|
109
|
+
@ApiPaginatedResponse(TemporalTenantConfigEntity)
|
|
110
|
+
async findMany(
|
|
111
|
+
@Body() body: TemporalTenantConfigFindManyArgs,
|
|
112
|
+
@ACAttrFilter() acAttrFilter,
|
|
113
|
+
): Promise<PageDto<TemporalTenantConfigEntity>> {
|
|
114
|
+
const { itemCount, entities } =
|
|
115
|
+
await this.temporalTenantConfigService.findMany(
|
|
116
|
+
body as Prisma.TemporalTenantConfigFindManyArgs,
|
|
117
|
+
);
|
|
118
|
+
const { skip = 0, take = 10 } =
|
|
119
|
+
body as Prisma.TemporalTenantConfigFindManyArgs;
|
|
120
|
+
const pageOptionsDto: PageOptionsDto = { skip, take };
|
|
121
|
+
const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
|
|
122
|
+
|
|
123
|
+
return new PageDto(
|
|
124
|
+
entities.map(
|
|
125
|
+
(item) => new TemporalTenantConfigEntity(acAttrFilter(item)),
|
|
126
|
+
),
|
|
127
|
+
pageMetaDto,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Post('/findUnique/:id')
|
|
132
|
+
@UseRoles({
|
|
133
|
+
resource: 'temporalTenantConfig',
|
|
134
|
+
action: 'read',
|
|
135
|
+
possession: 'any',
|
|
136
|
+
})
|
|
137
|
+
@ApiOkResponse({ type: TemporalTenantConfigEntity })
|
|
138
|
+
@ApiNotFoundResponse({ description: 'Cannot found this record.' })
|
|
139
|
+
async findUnique(
|
|
140
|
+
@Param('id', ParseIntPipe) id: number,
|
|
141
|
+
@Body() body: TemporalTenantConfigWhereSelect,
|
|
142
|
+
@ACAttrFilter() acAttrFilter,
|
|
143
|
+
) {
|
|
144
|
+
const item = await this.temporalTenantConfigService.findUnique(id, body);
|
|
145
|
+
if (!item) {
|
|
146
|
+
throw new NotFoundException();
|
|
147
|
+
}
|
|
148
|
+
return new TemporalTenantConfigEntity(acAttrFilter(item));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Patch('updateUnique/:id')
|
|
152
|
+
@UseRoles({
|
|
153
|
+
resource: 'temporalTenantConfig',
|
|
154
|
+
action: 'update',
|
|
155
|
+
possession: 'any',
|
|
156
|
+
})
|
|
157
|
+
@ApiOkResponse({ type: TemporalTenantConfigEntity })
|
|
158
|
+
@ApiNotFoundResponse({ description: 'Cannot found this record.' })
|
|
159
|
+
async updateUnique(
|
|
160
|
+
@Param('id', ParseIntPipe) id: number,
|
|
161
|
+
@Body() body: UpdateTemporalTenantConfigDto,
|
|
162
|
+
@ACAttrFilter() acAttrFilter,
|
|
163
|
+
) {
|
|
164
|
+
const existing = await this.temporalTenantConfigService.findUnique(id, {
|
|
165
|
+
adminUrl: true,
|
|
166
|
+
adminToken: true,
|
|
167
|
+
namespace: true,
|
|
168
|
+
});
|
|
169
|
+
if (!existing) throw new NotFoundException();
|
|
170
|
+
|
|
171
|
+
const adminUrl = (body.adminUrl ?? existing.adminUrl) as string;
|
|
172
|
+
const adminToken = (body.adminToken ?? existing.adminToken) as string;
|
|
173
|
+
const namespace = (body.namespace ?? existing.namespace) as string;
|
|
174
|
+
|
|
175
|
+
const patchBody: Record<string, string> = {};
|
|
176
|
+
if (body.dbUrl) patchBody.dbUrl = body.dbUrl;
|
|
177
|
+
if (body.displayName) patchBody.displayName = body.displayName;
|
|
178
|
+
|
|
179
|
+
if (Object.keys(patchBody).length > 0) {
|
|
180
|
+
const res = await fetch(
|
|
181
|
+
`${adminUrl}/admin/tenants/${encodeURIComponent(namespace)}`,
|
|
182
|
+
{
|
|
183
|
+
method: 'PATCH',
|
|
184
|
+
headers: {
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
'X-Admin-Token': adminToken,
|
|
187
|
+
},
|
|
188
|
+
body: JSON.stringify(patchBody),
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const detail = await res.text().catch(() => '');
|
|
193
|
+
throw new BadRequestException(
|
|
194
|
+
`shared temporal API 更新失败 (${res.status}): ${detail}`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const item = await this.temporalTenantConfigService.updateUnique(
|
|
200
|
+
id,
|
|
201
|
+
acAttrFilter(body),
|
|
202
|
+
);
|
|
203
|
+
return new TemporalTenantConfigEntity(acAttrFilter(item));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Delete('deleteUnique/:id')
|
|
207
|
+
@UseRoles({
|
|
208
|
+
resource: 'temporalTenantConfig',
|
|
209
|
+
action: 'delete',
|
|
210
|
+
possession: 'any',
|
|
211
|
+
})
|
|
212
|
+
@ApiOkResponse({ type: EffectedCount })
|
|
213
|
+
@ApiNotFoundResponse({ description: 'Cannot found this record.' })
|
|
214
|
+
async deleteUnique(@Param('id', ParseIntPipe) id: number) {
|
|
215
|
+
const res = await this.temporalTenantConfigService.deleteUnique(id);
|
|
216
|
+
if (!res.count) {
|
|
217
|
+
throw new NotFoundException();
|
|
218
|
+
}
|
|
219
|
+
return res;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Post('upload')
|
|
223
|
+
@UseRoles({
|
|
224
|
+
resource: 'temporalTenantConfig',
|
|
225
|
+
action: 'create',
|
|
226
|
+
possession: 'any',
|
|
227
|
+
})
|
|
228
|
+
@ApiCreatedResponse({ type: UploadResponse })
|
|
229
|
+
@UseInterceptors(FileInterceptor('file'))
|
|
230
|
+
async uploadFile(@UploadedFile() file: Express.Multer.File) {
|
|
231
|
+
const fileName = await diskStorage(
|
|
232
|
+
file,
|
|
233
|
+
this.config.get('media')['rootDir'] + '/temporalTenantConfig/',
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
...pick(file, ['mimetype', 'originalname', 'size']),
|
|
238
|
+
url:
|
|
239
|
+
this.config.get('media')['address'] +
|
|
240
|
+
'/temporalTenantConfig/' +
|
|
241
|
+
fileName,
|
|
242
|
+
name: fileName,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TemporalTenantConfigController } from './temporalTenantConfig.controller';
|
|
3
|
+
import { TemporalTenantConfigService } from './temporalTenantConfig.service';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
controllers: [TemporalTenantConfigController],
|
|
7
|
+
providers: [TemporalTenantConfigService],
|
|
8
|
+
exports: [TemporalTenantConfigService],
|
|
9
|
+
})
|
|
10
|
+
export class TemporalTenantConfigModule {}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
4
|
+
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
5
|
+
|
|
6
|
+
// Mock nestjs-prisma 模块
|
|
7
|
+
jest.mock('nestjs-prisma', () => ({
|
|
8
|
+
PrismaService: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { PrismaService } from 'nestjs-prisma';
|
|
12
|
+
import { TemporalTenantConfigService } from './temporalTenantConfig.service';
|
|
13
|
+
|
|
14
|
+
// Mock PrismaService
|
|
15
|
+
const mockPrismaService = {
|
|
16
|
+
temporalTenantConfig: {
|
|
17
|
+
create: jest.fn(),
|
|
18
|
+
createMany: jest.fn(),
|
|
19
|
+
findMany: jest.fn(),
|
|
20
|
+
findUnique: jest.fn(),
|
|
21
|
+
update: jest.fn(),
|
|
22
|
+
updateMany: jest.fn(),
|
|
23
|
+
delete: jest.fn(),
|
|
24
|
+
deleteMany: jest.fn(),
|
|
25
|
+
count: jest.fn(),
|
|
26
|
+
groupBy: jest.fn(),
|
|
27
|
+
aggregate: jest.fn(),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Mock Logger
|
|
32
|
+
const mockLogger = {
|
|
33
|
+
child: jest.fn().mockReturnThis(),
|
|
34
|
+
info: jest.fn(),
|
|
35
|
+
error: jest.fn(),
|
|
36
|
+
warn: jest.fn(),
|
|
37
|
+
debug: jest.fn(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Mock ConfigService
|
|
41
|
+
const mockConfigService = {
|
|
42
|
+
get: jest.fn(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe('TemporalTenantConfigService', () => {
|
|
46
|
+
let service: TemporalTenantConfigService;
|
|
47
|
+
let prisma: typeof mockPrismaService;
|
|
48
|
+
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
// 每个测试前重置所有 mock
|
|
51
|
+
jest.clearAllMocks();
|
|
52
|
+
|
|
53
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
54
|
+
providers: [
|
|
55
|
+
TemporalTenantConfigService,
|
|
56
|
+
{ provide: PrismaService, useValue: mockPrismaService },
|
|
57
|
+
{ provide: ConfigService, useValue: mockConfigService },
|
|
58
|
+
{ provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
|
|
59
|
+
],
|
|
60
|
+
})
|
|
61
|
+
// 兜底:自动 mock 任何未显式提供的依赖
|
|
62
|
+
// (例如手工给 service 增加的额外依赖:RoleService、其他 Service 等)
|
|
63
|
+
.useMocker(() => ({}))
|
|
64
|
+
.compile();
|
|
65
|
+
|
|
66
|
+
service = module.get<TemporalTenantConfigService>(
|
|
67
|
+
TemporalTenantConfigService,
|
|
68
|
+
);
|
|
69
|
+
prisma = mockPrismaService;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should be defined', () => {
|
|
73
|
+
expect(service).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('createOne', () => {
|
|
77
|
+
it('should create a single temporalTenantConfig record', async () => {
|
|
78
|
+
const createArgs = {
|
|
79
|
+
data: {
|
|
80
|
+
adminUrl: 'test_adminUrl',
|
|
81
|
+
adminToken: 'test_adminToken',
|
|
82
|
+
namespace: 'test_namespace',
|
|
83
|
+
dbUrl: 'test_dbUrl',
|
|
84
|
+
displayName: 'test_displayName',
|
|
85
|
+
creator: 'test_creator',
|
|
86
|
+
},
|
|
87
|
+
} as any;
|
|
88
|
+
const expectedResult = { id: 1, ...createArgs.data };
|
|
89
|
+
|
|
90
|
+
prisma.temporalTenantConfig.create.mockResolvedValue(expectedResult);
|
|
91
|
+
|
|
92
|
+
const result = await service.createOne(createArgs);
|
|
93
|
+
|
|
94
|
+
expect(prisma.temporalTenantConfig.create).toHaveBeenCalledWith(
|
|
95
|
+
createArgs,
|
|
96
|
+
);
|
|
97
|
+
expect(prisma.temporalTenantConfig.create).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(result).toEqual(expectedResult);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should throw error when create fails', async () => {
|
|
102
|
+
const createArgs = {
|
|
103
|
+
data: {
|
|
104
|
+
adminUrl: 'test_adminUrl',
|
|
105
|
+
adminToken: 'test_adminToken',
|
|
106
|
+
namespace: 'test_namespace',
|
|
107
|
+
dbUrl: 'test_dbUrl',
|
|
108
|
+
displayName: 'test_displayName',
|
|
109
|
+
creator: 'test_creator',
|
|
110
|
+
},
|
|
111
|
+
} as any;
|
|
112
|
+
const error = new Error('Database connection failed');
|
|
113
|
+
|
|
114
|
+
prisma.temporalTenantConfig.create.mockRejectedValue(error);
|
|
115
|
+
|
|
116
|
+
await expect(service.createOne(createArgs)).rejects.toThrow(
|
|
117
|
+
'Database connection failed',
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('createMany', () => {
|
|
123
|
+
it('should create multiple temporalTenantConfig records with skipDuplicates', async () => {
|
|
124
|
+
const data = [
|
|
125
|
+
{
|
|
126
|
+
adminUrl: 'test_adminUrl',
|
|
127
|
+
adminToken: 'test_adminToken',
|
|
128
|
+
namespace: 'test_namespace',
|
|
129
|
+
dbUrl: 'test_dbUrl',
|
|
130
|
+
displayName: 'test_displayName',
|
|
131
|
+
creator: 'test_creator',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
adminUrl: 'test_adminUrl',
|
|
135
|
+
adminToken: 'test_adminToken',
|
|
136
|
+
namespace: 'test_namespace',
|
|
137
|
+
dbUrl: 'test_dbUrl',
|
|
138
|
+
displayName: 'test_displayName',
|
|
139
|
+
creator: 'test_creator',
|
|
140
|
+
},
|
|
141
|
+
] as any;
|
|
142
|
+
const expectedResult = { count: 2 };
|
|
143
|
+
|
|
144
|
+
prisma.temporalTenantConfig.createMany.mockResolvedValue(expectedResult);
|
|
145
|
+
|
|
146
|
+
const result = await service.createMany(data);
|
|
147
|
+
|
|
148
|
+
expect(prisma.temporalTenantConfig.createMany).toHaveBeenCalledWith({
|
|
149
|
+
data,
|
|
150
|
+
skipDuplicates: true,
|
|
151
|
+
});
|
|
152
|
+
expect(result).toEqual(expectedResult);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('findMany', () => {
|
|
157
|
+
it('should return entities with itemCount', async () => {
|
|
158
|
+
const findArgs = {
|
|
159
|
+
where: {},
|
|
160
|
+
take: 10,
|
|
161
|
+
skip: 0,
|
|
162
|
+
} as any;
|
|
163
|
+
const mockEntities = [{ id: 1 }, { id: 2 }];
|
|
164
|
+
|
|
165
|
+
prisma.temporalTenantConfig.count.mockResolvedValue(2);
|
|
166
|
+
prisma.temporalTenantConfig.findMany.mockResolvedValue(mockEntities);
|
|
167
|
+
|
|
168
|
+
const result = await service.findMany(findArgs);
|
|
169
|
+
|
|
170
|
+
expect(prisma.temporalTenantConfig.count).toHaveBeenCalledWith({
|
|
171
|
+
where: findArgs.where,
|
|
172
|
+
});
|
|
173
|
+
expect(prisma.temporalTenantConfig.findMany).toHaveBeenCalledWith(
|
|
174
|
+
findArgs,
|
|
175
|
+
);
|
|
176
|
+
expect(result).toEqual({
|
|
177
|
+
itemCount: 2,
|
|
178
|
+
entities: mockEntities,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should return empty result when no records found', async () => {
|
|
183
|
+
const findArgs = { where: {} } as any;
|
|
184
|
+
|
|
185
|
+
prisma.temporalTenantConfig.count.mockResolvedValue(0);
|
|
186
|
+
prisma.temporalTenantConfig.findMany.mockResolvedValue([]);
|
|
187
|
+
|
|
188
|
+
const result = await service.findMany(findArgs);
|
|
189
|
+
|
|
190
|
+
expect(result).toEqual({ itemCount: 0, entities: [] });
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('findUnique', () => {
|
|
195
|
+
it('should find a single temporalTenantConfig record by id', async () => {
|
|
196
|
+
const id = 1;
|
|
197
|
+
const select = { id: true };
|
|
198
|
+
const expectedResult = { id: 1 };
|
|
199
|
+
|
|
200
|
+
prisma.temporalTenantConfig.findUnique.mockResolvedValue(expectedResult);
|
|
201
|
+
|
|
202
|
+
const result = await service.findUnique(id, select);
|
|
203
|
+
|
|
204
|
+
expect(prisma.temporalTenantConfig.findUnique).toHaveBeenCalledWith({
|
|
205
|
+
where: { id },
|
|
206
|
+
select,
|
|
207
|
+
});
|
|
208
|
+
expect(result).toEqual(expectedResult);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should return null when record not found', async () => {
|
|
212
|
+
prisma.temporalTenantConfig.findUnique.mockResolvedValue(null);
|
|
213
|
+
|
|
214
|
+
const result = await service.findUnique(1, { id: true });
|
|
215
|
+
|
|
216
|
+
expect(result).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('updateUnique', () => {
|
|
221
|
+
it('should update a single temporalTenantConfig record', async () => {
|
|
222
|
+
const id = 1;
|
|
223
|
+
const data = {
|
|
224
|
+
adminUrl: 'test_adminUrl',
|
|
225
|
+
adminToken: 'test_adminToken',
|
|
226
|
+
namespace: 'test_namespace',
|
|
227
|
+
dbUrl: 'test_dbUrl',
|
|
228
|
+
displayName: 'test_displayName',
|
|
229
|
+
creator: 'test_creator',
|
|
230
|
+
} as any;
|
|
231
|
+
const expectedResult = { id: 1, ...data };
|
|
232
|
+
|
|
233
|
+
prisma.temporalTenantConfig.update.mockResolvedValue(expectedResult);
|
|
234
|
+
|
|
235
|
+
const result = await service.updateUnique(id, data);
|
|
236
|
+
|
|
237
|
+
expect(prisma.temporalTenantConfig.update).toHaveBeenCalledWith({
|
|
238
|
+
where: { id },
|
|
239
|
+
data,
|
|
240
|
+
});
|
|
241
|
+
expect(result).toEqual(expectedResult);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('updateMany', () => {
|
|
246
|
+
it('should update multiple temporalTenantConfig records', async () => {
|
|
247
|
+
const updateArgs = {
|
|
248
|
+
where: {},
|
|
249
|
+
data: {
|
|
250
|
+
adminUrl: 'test_adminUrl',
|
|
251
|
+
adminToken: 'test_adminToken',
|
|
252
|
+
namespace: 'test_namespace',
|
|
253
|
+
dbUrl: 'test_dbUrl',
|
|
254
|
+
displayName: 'test_displayName',
|
|
255
|
+
creator: 'test_creator',
|
|
256
|
+
},
|
|
257
|
+
} as any;
|
|
258
|
+
const expectedResult = { count: 5 };
|
|
259
|
+
|
|
260
|
+
prisma.temporalTenantConfig.updateMany.mockResolvedValue(expectedResult);
|
|
261
|
+
|
|
262
|
+
const result = await service.updateMany(updateArgs);
|
|
263
|
+
|
|
264
|
+
expect(prisma.temporalTenantConfig.updateMany).toHaveBeenCalledWith(
|
|
265
|
+
updateArgs,
|
|
266
|
+
);
|
|
267
|
+
expect(result).toEqual(expectedResult);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('deleteUnique', () => {
|
|
272
|
+
it('should delete a single temporalTenantConfig record and return count', async () => {
|
|
273
|
+
const id = 1;
|
|
274
|
+
|
|
275
|
+
prisma.temporalTenantConfig.delete.mockResolvedValue({ id: 1 });
|
|
276
|
+
|
|
277
|
+
const result = await service.deleteUnique(id);
|
|
278
|
+
|
|
279
|
+
expect(prisma.temporalTenantConfig.delete).toHaveBeenCalledWith({
|
|
280
|
+
where: { id },
|
|
281
|
+
});
|
|
282
|
+
expect(result).toEqual({ count: 1 });
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('deleteMany', () => {
|
|
287
|
+
it('should delete multiple temporalTenantConfig records', async () => {
|
|
288
|
+
const deleteArgs = { where: {} } as any;
|
|
289
|
+
const expectedResult = { count: 3 };
|
|
290
|
+
|
|
291
|
+
prisma.temporalTenantConfig.deleteMany.mockResolvedValue(expectedResult);
|
|
292
|
+
|
|
293
|
+
const result = await service.deleteMany(deleteArgs);
|
|
294
|
+
|
|
295
|
+
expect(prisma.temporalTenantConfig.deleteMany).toHaveBeenCalledWith(
|
|
296
|
+
deleteArgs,
|
|
297
|
+
);
|
|
298
|
+
expect(result).toEqual(expectedResult);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('count', () => {
|
|
303
|
+
it('should count temporalTenantConfig records with filter', async () => {
|
|
304
|
+
const countArgs = { where: {} } as any;
|
|
305
|
+
|
|
306
|
+
prisma.temporalTenantConfig.count.mockResolvedValue(10);
|
|
307
|
+
|
|
308
|
+
const result = await service.count(countArgs);
|
|
309
|
+
|
|
310
|
+
expect(prisma.temporalTenantConfig.count).toHaveBeenCalledWith(countArgs);
|
|
311
|
+
expect(result).toBe(10);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should count all records when no filter provided', async () => {
|
|
315
|
+
prisma.temporalTenantConfig.count.mockResolvedValue(100);
|
|
316
|
+
|
|
317
|
+
const result = await service.count();
|
|
318
|
+
|
|
319
|
+
expect(prisma.temporalTenantConfig.count).toHaveBeenCalledWith(undefined);
|
|
320
|
+
expect(result).toBe(100);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('groupBy', () => {
|
|
325
|
+
it('should group temporalTenantConfig records by specified field', async () => {
|
|
326
|
+
const groupByArgs = {
|
|
327
|
+
by: ['id'],
|
|
328
|
+
_count: { id: true },
|
|
329
|
+
} as any;
|
|
330
|
+
const expectedResult = [{ id: 1, _count: { id: 50 } }];
|
|
331
|
+
|
|
332
|
+
prisma.temporalTenantConfig.groupBy.mockResolvedValue(expectedResult);
|
|
333
|
+
|
|
334
|
+
const result = await service.groupBy(groupByArgs);
|
|
335
|
+
|
|
336
|
+
expect(prisma.temporalTenantConfig.groupBy).toHaveBeenCalledWith(
|
|
337
|
+
groupByArgs,
|
|
338
|
+
);
|
|
339
|
+
expect(result).toEqual(expectedResult);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('aggregate', () => {
|
|
344
|
+
it('should aggregate temporalTenantConfig records', async () => {
|
|
345
|
+
const aggregateArgs = {
|
|
346
|
+
_count: true,
|
|
347
|
+
_max: { id: true },
|
|
348
|
+
_min: { id: true },
|
|
349
|
+
} as any;
|
|
350
|
+
const expectedResult = {
|
|
351
|
+
_count: 100,
|
|
352
|
+
_max: { id: 1 },
|
|
353
|
+
_min: { id: 1 },
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
prisma.temporalTenantConfig.aggregate.mockResolvedValue(expectedResult);
|
|
357
|
+
|
|
358
|
+
const result = await service.aggregate(aggregateArgs);
|
|
359
|
+
|
|
360
|
+
expect(prisma.temporalTenantConfig.aggregate).toHaveBeenCalledWith(
|
|
361
|
+
aggregateArgs,
|
|
362
|
+
);
|
|
363
|
+
expect(result).toEqual(expectedResult);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|