@postxl/generator 0.34.0 → 0.35.0
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/generator.js +40 -22
- package/dist/generators/indices/businesslogic-actiontypes.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-actiontypes.generator.js +39 -0
- package/dist/generators/indices/businesslogic-update-index.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-update-index.generator.js +20 -0
- package/dist/generators/indices/businesslogic-update-module.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-update-module.generator.js +69 -0
- package/dist/generators/indices/businesslogic-update-service.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-update-service.generator.js +34 -0
- package/dist/generators/indices/businesslogic-view-index.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-view-index.generator.js +19 -0
- package/dist/generators/indices/{businesslogicindex.generator.d.ts → businesslogic-view-module.generator.d.ts} +2 -2
- package/dist/generators/indices/{businesslogicmodule.generator.js → businesslogic-view-module.generator.js} +22 -26
- package/dist/generators/indices/{businesslogicservice.generator.d.ts → businesslogic-view-service.generator.d.ts} +1 -1
- package/dist/generators/indices/{businesslogicservice.generator.js → businesslogic-view-service.generator.js} +9 -10
- package/dist/generators/indices/{datamockmodule.generator.js → datamock-module.generator.js} +8 -16
- package/dist/generators/indices/datamocker.generator.js +3 -7
- package/dist/generators/indices/datamodule.generator.js +7 -13
- package/dist/generators/indices/{businesslogicmodule.generator.d.ts → dispatcher-service.generator.d.ts} +2 -2
- package/dist/generators/indices/dispatcher-service.generator.js +81 -0
- package/dist/generators/indices/seed-migration.generator.d.ts +9 -0
- package/dist/generators/indices/seed-migration.generator.js +35 -0
- package/dist/generators/indices/seed-service.generator.d.ts +1 -1
- package/dist/generators/indices/seed-service.generator.js +327 -123
- package/dist/generators/indices/seed-template-decoder.generator.js +22 -6
- package/dist/generators/indices/{seed.generator.d.ts → seeddata-type.generator.d.ts} +2 -2
- package/dist/generators/indices/seeddata-type.generator.js +42 -0
- package/dist/generators/indices/types.generator.d.ts +1 -1
- package/dist/generators/indices/types.generator.js +8 -6
- package/dist/generators/models/businesslogic-update.generator.d.ts +10 -0
- package/dist/generators/models/businesslogic-update.generator.js +243 -0
- package/dist/generators/models/businesslogic-view.generator.d.ts +10 -0
- package/dist/generators/models/{businesslogic.generator.js → businesslogic-view.generator.js} +23 -72
- package/dist/generators/models/react.generator/modals.generator.js +2 -2
- package/dist/generators/models/repository.generator.d.ts +9 -0
- package/dist/generators/models/repository.generator.js +420 -131
- package/dist/generators/models/route.generator.js +45 -55
- package/dist/generators/models/seed.generator.js +6 -2
- package/dist/generators/models/types.generator.js +60 -13
- package/dist/lib/attributes.d.ts +5 -0
- package/dist/lib/imports.d.ts +23 -2
- package/dist/lib/imports.js +19 -1
- package/dist/lib/meta.d.ts +287 -34
- package/dist/lib/meta.js +87 -16
- package/dist/lib/schema/schema.d.ts +24 -6
- package/dist/lib/schema/types.d.ts +4 -0
- package/dist/lib/utils/jsdoc.d.ts +1 -1
- package/dist/lib/utils/jsdoc.js +8 -6
- package/dist/lib/utils/string.js +2 -1
- package/dist/prisma/attributes.js +7 -3
- package/dist/prisma/parse.js +25 -5
- package/package.json +1 -1
- package/dist/generators/indices/businesslogicindex.generator.js +0 -19
- package/dist/generators/indices/seed.generator.js +0 -17
- package/dist/generators/indices/testdataservice.generator.d.ts +0 -9
- package/dist/generators/indices/testdataservice.generator.js +0 -78
- package/dist/generators/models/businesslogic.generator.d.ts +0 -9
- /package/dist/generators/indices/{datamockmodule.generator.d.ts → datamock-module.generator.d.ts} +0 -0
|
@@ -30,17 +30,10 @@ const Types = __importStar(require("../../lib/schema/types"));
|
|
|
30
30
|
/**
|
|
31
31
|
* Generates a data module class.
|
|
32
32
|
*/
|
|
33
|
-
// TODO: https://github.com/PostXL/PostXL/issues/347
|
|
34
33
|
function generateDataModule({ models, meta }) {
|
|
35
34
|
const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
|
|
36
|
-
const imports = imports_1.ImportsGenerator.from(meta.data.dataModuleFilePath)
|
|
37
|
-
.
|
|
38
|
-
items: [Types.toVariableName('TestDataService')],
|
|
39
|
-
from: meta.data.testDataServiceFilePath,
|
|
40
|
-
})
|
|
41
|
-
.addImport({
|
|
42
|
-
items: [Types.toVariableName('DataService')],
|
|
43
|
-
from: meta.data.dataServiceFilePath,
|
|
35
|
+
const imports = imports_1.ImportsGenerator.from(meta.data.dataModuleFilePath).addImports({
|
|
36
|
+
[meta.data.dataServiceFilePath]: [Types.toVariableName('DataService')],
|
|
44
37
|
});
|
|
45
38
|
for (const { meta } of mm) {
|
|
46
39
|
imports.addImport({
|
|
@@ -65,7 +58,9 @@ export class ${moduleName} {
|
|
|
65
58
|
* The getInstance method should be called by any module that needs the module.
|
|
66
59
|
*/
|
|
67
60
|
static getInstance(): DynamicModule {
|
|
68
|
-
if (!${moduleName}.cachedModule)
|
|
61
|
+
if (!${moduleName}.cachedModule) {
|
|
62
|
+
throw new Error('${moduleName} must be called via .provide first!')
|
|
63
|
+
}
|
|
69
64
|
return ${moduleName}.cachedModule
|
|
70
65
|
}
|
|
71
66
|
|
|
@@ -106,11 +101,10 @@ export class ${moduleName} {
|
|
|
106
101
|
return ${moduleName}.cachedModule
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
const
|
|
104
|
+
const providers = [
|
|
110
105
|
${mm.map(({ meta }) => meta.data.repositoryClassName).join(',')}
|
|
111
106
|
]
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
|
|
114
108
|
${moduleName}.cachedModule = {
|
|
115
109
|
module: ${moduleName},
|
|
116
110
|
global: true,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SchemaMetaData } from '../../lib/meta';
|
|
2
2
|
import { Model } from '../../lib/schema/schema';
|
|
3
3
|
/**
|
|
4
|
-
* Generates
|
|
4
|
+
* Generates the action dispatcher service.
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function generateActionsDispatcherService({ models, meta }: {
|
|
7
7
|
models: Model[];
|
|
8
8
|
meta: SchemaMetaData;
|
|
9
9
|
}): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateActionsDispatcherService = void 0;
|
|
4
|
+
const imports_1 = require("../../lib/imports");
|
|
5
|
+
const meta_1 = require("../../lib/meta");
|
|
6
|
+
/**
|
|
7
|
+
* Generates the action dispatcher service.
|
|
8
|
+
*/
|
|
9
|
+
function generateActionsDispatcherService({ models, meta }) {
|
|
10
|
+
const imports = imports_1.ImportsGenerator.from(meta.seed.serviceFilePath).addImports({
|
|
11
|
+
[meta.seed.importPath]: [meta.seed.serviceClassName],
|
|
12
|
+
[meta.businessLogic.update.importPath]: [meta.businessLogic.update.serviceClassName],
|
|
13
|
+
[meta.types.importPath]: meta.config.userType,
|
|
14
|
+
});
|
|
15
|
+
const dataLoader = [];
|
|
16
|
+
for (const model of models) {
|
|
17
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
18
|
+
dataLoader.push(`await this.upload({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}, repo: this.dataService.${modelMeta.data.dataServiceName}, log })`);
|
|
19
|
+
}
|
|
20
|
+
return `
|
|
21
|
+
import { Injectable } from '@nestjs/common'
|
|
22
|
+
|
|
23
|
+
import { ExhaustiveSwitchCheck } from '@pxl/common'
|
|
24
|
+
import { DbService } from '@pxl/db'
|
|
25
|
+
|
|
26
|
+
${imports.generate()}
|
|
27
|
+
|
|
28
|
+
import { ActionExecution, createActionExecution } from './actionExecution.class'
|
|
29
|
+
import { Action, ResultOfAction } from './actions.types'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@Injectable()
|
|
33
|
+
export class DispatcherService {
|
|
34
|
+
// as the SeedModule gets instantiated after the ActionsModule, we use this hack to avoid a circular dependency:
|
|
35
|
+
// we set the seedService to a dummy value and then overwrite it in the ${meta.seed.serviceClassName} constructor.
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
37
|
+
public seedService: ${meta.seed.serviceClassName} = {} as unknown as any
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly updateService: ${meta.businessLogic.update.serviceClassName},
|
|
40
|
+
private readonly dbService: DbService
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
public async dispatch<A extends Action>({ action, user }: {
|
|
44
|
+
action: A;
|
|
45
|
+
user: ${meta.config.userType}
|
|
46
|
+
}): Promise<ResultOfAction<A>> {
|
|
47
|
+
const execution = await createActionExecution({ action, dbService: this.dbService, user })
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result = await (this.execute({ action, execution }) as Promise<ResultOfAction<A>>)
|
|
51
|
+
|
|
52
|
+
await execution.success()
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
} catch (e) {
|
|
56
|
+
await execution.error(e)
|
|
57
|
+
throw e
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async execute({ action, execution }: { action: Action; execution: ActionExecution }) {
|
|
62
|
+
switch (action.scope) {
|
|
63
|
+
${models
|
|
64
|
+
.map((model) => {
|
|
65
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
66
|
+
return `
|
|
67
|
+
case '${modelMeta.businessLogic.scopeName}':
|
|
68
|
+
return this.updateService.${modelMeta.businessLogic.update.serviceVariableName}.dispatch({ action, execution })
|
|
69
|
+
`;
|
|
70
|
+
})
|
|
71
|
+
.join('\n')}
|
|
72
|
+
case 'seed':
|
|
73
|
+
return this.seedService.dispatch({ action, execution })
|
|
74
|
+
default:
|
|
75
|
+
throw new ExhaustiveSwitchCheck(action)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
exports.generateActionsDispatcherService = generateActionsDispatcherService;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SchemaMetaData } from '../../lib/meta';
|
|
2
|
+
import { Model } from '../../lib/schema/schema';
|
|
3
|
+
/**
|
|
4
|
+
* Generates the initial migration based on the generated seed data.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateSeedMigration({ models, meta }: {
|
|
7
|
+
models: Model[];
|
|
8
|
+
meta: SchemaMetaData;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSeedMigration = void 0;
|
|
4
|
+
const imports_1 = require("../../lib/imports");
|
|
5
|
+
const meta_1 = require("../../lib/meta");
|
|
6
|
+
/**
|
|
7
|
+
* Generates the initial migration based on the generated seed data.
|
|
8
|
+
*/
|
|
9
|
+
function generateSeedMigration({ models, meta }) {
|
|
10
|
+
const imports = imports_1.ImportsGenerator.from(meta.seedData.initialMigrationFilePath);
|
|
11
|
+
const modelTypes = [];
|
|
12
|
+
for (const model of models) {
|
|
13
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
14
|
+
imports.addImport({
|
|
15
|
+
items: [modelMeta.seed.constantName],
|
|
16
|
+
from: modelMeta.seed.filePath,
|
|
17
|
+
});
|
|
18
|
+
modelTypes.push(`${modelMeta.seed.constantName}: { create: ${modelMeta.seed.constantName} }`);
|
|
19
|
+
}
|
|
20
|
+
return `
|
|
21
|
+
import { createActionSeedData } from '${meta.seed.importPath}'
|
|
22
|
+
${imports.generate()}
|
|
23
|
+
|
|
24
|
+
export const MIGRATION_001_BASEDATA = createActionSeedData({
|
|
25
|
+
name: 'Base data',
|
|
26
|
+
order: 1,
|
|
27
|
+
data: [
|
|
28
|
+
{
|
|
29
|
+
${modelTypes.join(',\n')}
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
})
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
exports.generateSeedMigration = generateSeedMigration;
|
|
@@ -3,7 +3,7 @@ import { Model } from '../../lib/schema/schema';
|
|
|
3
3
|
/**
|
|
4
4
|
* Generates index file for all seed files.
|
|
5
5
|
*/
|
|
6
|
-
export declare function generateSeedService({ models
|
|
6
|
+
export declare function generateSeedService({ models }: {
|
|
7
7
|
models: Model[];
|
|
8
8
|
meta: SchemaMetaData;
|
|
9
9
|
}): string;
|
|
@@ -1,152 +1,356 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateSeedService = void 0;
|
|
4
|
-
const imports_1 = require("../../lib/imports");
|
|
5
4
|
const meta_1 = require("../../lib/meta");
|
|
6
5
|
/**
|
|
7
6
|
* Generates index file for all seed files.
|
|
8
7
|
*/
|
|
9
|
-
function generateSeedService({ models
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const dataLoader = [];
|
|
8
|
+
function generateSeedService({ models }) {
|
|
9
|
+
const creates = [];
|
|
10
|
+
const updates = [];
|
|
11
|
+
const upserts = [];
|
|
12
|
+
const deletes = [];
|
|
15
13
|
for (const model of models) {
|
|
16
14
|
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
17
|
-
|
|
15
|
+
creates.push(`await this.create({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.create, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
|
|
16
|
+
updates.push(`await this.update({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.update, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
|
|
17
|
+
upserts.push(`await this.upsert({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.upsert, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
|
|
18
|
+
deletes.push(`await this.delete({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.delete, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
|
|
18
19
|
}
|
|
19
20
|
return `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
22
|
+
import { ExhaustiveSwitchCheck, format, pluralize } from '@pxl/common'
|
|
23
|
+
import { DataService, Repository } from '@pxl/data'
|
|
24
|
+
import { DbService } from '@pxl/db'
|
|
25
|
+
import { XlPortService } from '@pxl/xlport'
|
|
26
|
+
|
|
27
|
+
import { ActionExecution, DispatcherService } from '@pxl/actions'
|
|
28
|
+
import { RootUserService } from '@pxl/root-user'
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
ActionPreparation_Seed,
|
|
32
|
+
ActionPreparation_Seed_Excel,
|
|
33
|
+
Action_Seed,
|
|
34
|
+
Action_Seed_CustomLogic,
|
|
35
|
+
Action_Seed_Excel,
|
|
36
|
+
createActionSeedExcel,
|
|
37
|
+
} from './action.types'
|
|
38
|
+
import { SeedData } from './seed-data.type'
|
|
39
|
+
import { CreateDTO, UpdateDTO } from '@pxl/types'
|
|
40
|
+
import { SEED_MIGRATIONS } from '@pxl/seed-data'
|
|
41
|
+
|
|
42
|
+
// If true, the seed data will be created/updated one by one, instead of all at once (which is the default).
|
|
43
|
+
// Also, each item will be logged to the console.
|
|
44
|
+
// This should help to quickly identify the item that causes an error in a bulk operation.
|
|
45
|
+
const DEBUG = false
|
|
46
|
+
|
|
47
|
+
@Injectable()
|
|
48
|
+
export class SeedService {
|
|
49
|
+
private readonly logger = new Logger(SeedService.name)
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
private readonly dataService: DataService,
|
|
53
|
+
private readonly xlPortService: XlPortService,
|
|
54
|
+
private readonly dbService: DbService,
|
|
55
|
+
private readonly dispatcherService: DispatcherService,
|
|
56
|
+
private readonly rootUserService: RootUserService,
|
|
57
|
+
) {
|
|
58
|
+
dispatcherService.seedService = this
|
|
59
|
+
}
|
|
60
|
+
|
|
29
61
|
/**
|
|
30
|
-
*
|
|
62
|
+
* To be called on application startup. Will validate the seed data migrations and execute any pending migrations.
|
|
63
|
+
*
|
|
64
|
+
* Each migration is an action, i.e. will be dispatched via the default dispatcher service and logged like any other action.
|
|
31
65
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
public async seed() {
|
|
67
|
+
this.verifyIntegrity()
|
|
68
|
+
|
|
69
|
+
let executed = 0
|
|
70
|
+
for (const migration of SEED_MIGRATIONS) {
|
|
71
|
+
// Skip any migrations that have already been executed
|
|
72
|
+
const migrationExists = await this.dbService.action.findFirst({
|
|
73
|
+
where: { migrationOrder: migration.order, status: 'Success' },
|
|
74
|
+
})
|
|
75
|
+
if (migrationExists) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const action = await this.convertToAction(migration)
|
|
80
|
+
|
|
81
|
+
this.logger.log(\`Executing seed migration \${migration.order} - \${migration.name}\`)
|
|
82
|
+
await this.dispatcherService.dispatch({ action, user: this.rootUserService.rootUser })
|
|
83
|
+
|
|
84
|
+
executed++
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (executed === 0) {
|
|
88
|
+
if (SEED_MIGRATIONS.length > 0) {
|
|
89
|
+
this.logger.log(\`✅ All \${format(SEED_MIGRATIONS.length)} seed migrations have already been applied executed\`)
|
|
90
|
+
} else {
|
|
91
|
+
this.logger.log(\`No seed migrations found.\`)
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
if (executed === SEED_MIGRATIONS.length) {
|
|
95
|
+
this.logger.log(\`✅ Executed all \${format(executed)} seed migrations\`)
|
|
96
|
+
} else {
|
|
97
|
+
this.logger.log(
|
|
98
|
+
\`✅ Executed \${format(executed)} seed migrations, skipped \${format(SEED_MIGRATIONS.length - executed)} previous migrations\`,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
43
102
|
}
|
|
44
103
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* Seeds the database with the given data. Any data not provided will be taken from the default seed data.
|
|
56
|
-
*/
|
|
57
|
-
public async seed({ data, options = DEFAULTOPTIONS }: { data?: Partial<typeof SEEDDATA>; options?: SeedServiceOptions }) {
|
|
58
|
-
return this._seed({ data: { ...SEEDDATA, ...data }, options })
|
|
104
|
+
private async convertToAction(migration: ActionPreparation_Seed): Promise<Action_Seed> {
|
|
105
|
+
switch (migration.type) {
|
|
106
|
+
case 'data':
|
|
107
|
+
return migration
|
|
108
|
+
case 'excel':
|
|
109
|
+
return await this.convertExcelToAction(migration)
|
|
110
|
+
case 'custom':
|
|
111
|
+
return migration
|
|
112
|
+
default:
|
|
113
|
+
throw new ExhaustiveSwitchCheck(migration)
|
|
59
114
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
throw new
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Will be call by the dispatcher service to dispatch the given action.
|
|
119
|
+
*/
|
|
120
|
+
public async dispatch({ action, execution }: { action: Action_Seed; execution: ActionExecution }) {
|
|
121
|
+
switch (action.type) {
|
|
122
|
+
case 'data':
|
|
123
|
+
return this.uploadDataSteps({ steps: action.payload, execution })
|
|
124
|
+
case 'excel':
|
|
125
|
+
return this.uploadData({ data: action.payload.data, execution })
|
|
126
|
+
case 'custom':
|
|
127
|
+
return this.handleCustomLogic({ action, execution })
|
|
128
|
+
|
|
129
|
+
default:
|
|
130
|
+
throw new ExhaustiveSwitchCheck(action)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Checks if the order of the migrations is correct and if there are any missing migrations.
|
|
136
|
+
*/
|
|
137
|
+
private verifyIntegrity() {
|
|
138
|
+
let startOrder = 0
|
|
139
|
+
for (const { name, order } of SEED_MIGRATIONS) {
|
|
140
|
+
if (order <= startOrder) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
\`Invalid migration order. The order of migration "\${name}" must be after \${startOrder} but is \${order}. Is the order field correct and the order in the SEED_MIGRATIONS array correct?\`,
|
|
143
|
+
)
|
|
76
144
|
}
|
|
77
|
-
|
|
145
|
+
startOrder = order
|
|
78
146
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Reads the Excel file and parses it using the provided decoder.
|
|
151
|
+
* If the parsing is successful, the data is packaged into an Action_Seed_Excel.
|
|
152
|
+
*/
|
|
153
|
+
private async convertExcelToAction({
|
|
154
|
+
name,
|
|
155
|
+
order,
|
|
156
|
+
filename,
|
|
157
|
+
decoder,
|
|
158
|
+
}: ActionPreparation_Seed_Excel): Promise<Action_Seed_Excel> {
|
|
159
|
+
const xlData = await this.xlPortService.importFromFile(filename)
|
|
160
|
+
if (xlData.status === 'error') {
|
|
161
|
+
this.logger.error(xlData.message)
|
|
162
|
+
throw new Error(\`Error importing Excel data\`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const dataParsed = decoder.safeParse(xlData.data.tables)
|
|
166
|
+
if (!dataParsed.success) {
|
|
167
|
+
this.logger.error(dataParsed.error.message)
|
|
168
|
+
throw new Error(\`Error parsing Excel seed data\`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return createActionSeedExcel({ name, order, filename, data: dataParsed.data })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Executes the custom logic of the given migration.
|
|
176
|
+
* To do this, we retrieve the handler from the migration and call it, injecting the data service into the handler.
|
|
177
|
+
*/
|
|
178
|
+
private handleCustomLogic({ action, execution }: { action: Action_Seed_CustomLogic; execution: ActionExecution }) {
|
|
179
|
+
const actionPreparation = SEED_MIGRATIONS.find((m) => m.order === action.order)
|
|
180
|
+
if (!actionPreparation) {
|
|
181
|
+
throw new Error(\`Cannot find custom handler for migration with order \${action.order}\`)
|
|
182
|
+
}
|
|
183
|
+
if (actionPreparation.type !== 'custom') {
|
|
184
|
+
throw new Error(\`Cannot find custom handler for migration with order \${action.order}\`)
|
|
185
|
+
}
|
|
186
|
+
return actionPreparation.handler({ action, data: this.dataService, execution })
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Executes the data loading step by step
|
|
190
|
+
*/
|
|
191
|
+
private async uploadDataSteps({ steps, execution }: { steps: SeedData[]; execution: ActionExecution }) {
|
|
192
|
+
let index = 0
|
|
193
|
+
for (const step of steps) {
|
|
194
|
+
this.logger.log(\`Uploading data step \${++index}/\${steps.length}\`)
|
|
195
|
+
await this.uploadData({ data: step, execution })
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async uploadData({ data, execution }: { data: SeedData; execution: ActionExecution }) {
|
|
200
|
+
// NOTE: the order of these calls is important, because of foreign key constraints
|
|
201
|
+
// The current order is based on the order of the models in the schema
|
|
202
|
+
// Change the order based on your needs.
|
|
203
|
+
// Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
|
|
204
|
+
// e.g. by removing any foreign key first - and then update these after all data is created.
|
|
205
|
+
// Alternatively, you can also do this in multiple steps
|
|
206
|
+
${creates.join('\n')}
|
|
207
|
+
|
|
208
|
+
${updates.join('\n')}
|
|
209
|
+
|
|
210
|
+
${upserts.join('\n')}
|
|
211
|
+
|
|
212
|
+
${deletes.join('\n')}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private async create<T extends { id: ID }, ID extends number | string | boolean>({
|
|
216
|
+
name,
|
|
217
|
+
data,
|
|
218
|
+
repo,
|
|
219
|
+
execution,
|
|
220
|
+
}: {
|
|
221
|
+
name: string
|
|
222
|
+
data: CreateDTO<T, ID>[] | undefined
|
|
223
|
+
repo: Repository<T, ID>
|
|
224
|
+
execution: ActionExecution
|
|
225
|
+
}): Promise<void> {
|
|
226
|
+
if (!data) {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
if (!DEBUG) {
|
|
230
|
+
await repo.createMany?.({ items: data, execution })
|
|
231
|
+
} else {
|
|
232
|
+
let i = 0
|
|
233
|
+
for (const item of data) {
|
|
234
|
+
i++
|
|
235
|
+
this.logger.log(\`Creating \${name} \${i} - \${getItemDescription(item)}\`)
|
|
236
|
+
try {
|
|
237
|
+
await repo.create?.({ item, execution })
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.logger.error(\`Error creating \${name} \${i} - \${getItemDescription(item)}\`, error)
|
|
240
|
+
throw error
|
|
92
241
|
}
|
|
93
242
|
}
|
|
94
|
-
|
|
95
|
-
await this.uploadData({ data, log })
|
|
96
|
-
|
|
97
|
-
await this.dbService.config.update({
|
|
98
|
-
where: { id: true },
|
|
99
|
-
data: { isSeeded: true },
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
this.logger.log('✅ Done')
|
|
103
243
|
}
|
|
244
|
+
this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
|
|
245
|
+
}
|
|
104
246
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
247
|
+
private async update<T extends { id: ID }, ID extends number | string | boolean>({
|
|
248
|
+
name,
|
|
249
|
+
data,
|
|
250
|
+
repo,
|
|
251
|
+
execution,
|
|
252
|
+
}: {
|
|
253
|
+
name: string
|
|
254
|
+
data: UpdateDTO<T, ID>[] | undefined
|
|
255
|
+
repo: Repository<T, ID>
|
|
256
|
+
execution: ActionExecution
|
|
257
|
+
}): Promise<void> {
|
|
258
|
+
if (!data) {
|
|
259
|
+
return
|
|
112
260
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}): Promise<void> {
|
|
127
|
-
if (repo.count() > 0 && onlyOnReset) {
|
|
128
|
-
this.logger.log(\`\${pluralize(name)} already exist, skipping\`)
|
|
129
|
-
return
|
|
261
|
+
if (!DEBUG) {
|
|
262
|
+
await repo.updateMany?.({ items: data, execution })
|
|
263
|
+
} else {
|
|
264
|
+
let i = 0
|
|
265
|
+
for (const item of data) {
|
|
266
|
+
i++
|
|
267
|
+
this.logger.log(\`Updating \${name} \${i} - \${getItemDescription(item)}\`)
|
|
268
|
+
try {
|
|
269
|
+
await repo.update?.({ item, execution })
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.logger.error(\`Error updating \${name} \${i} - \${getItemDescription(item)}\`, error)
|
|
272
|
+
throw error
|
|
273
|
+
}
|
|
130
274
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
275
|
+
}
|
|
276
|
+
this.logger.log(\`✅ Updated \${format(data.length)} \${pluralize(name)}\`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private async upsert<T extends { id: ID }, ID extends number | string | boolean>({
|
|
280
|
+
name,
|
|
281
|
+
data,
|
|
282
|
+
repo,
|
|
283
|
+
execution,
|
|
284
|
+
}: {
|
|
285
|
+
name: string
|
|
286
|
+
data: (CreateDTO<T, ID> | UpdateDTO<T, ID>)[] | undefined
|
|
287
|
+
repo: Repository<T, ID>
|
|
288
|
+
execution: ActionExecution
|
|
289
|
+
}): Promise<void> {
|
|
290
|
+
if (!data) {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
if (!DEBUG) {
|
|
294
|
+
await repo.upsertMany?.({ items: data, execution })
|
|
295
|
+
} else {
|
|
296
|
+
let i = 0
|
|
297
|
+
for (const item of data) {
|
|
298
|
+
i++
|
|
299
|
+
this.logger.log(\`Upserting \${name} \${i} - \${getItemDescription(item)}\`)
|
|
300
|
+
try {
|
|
301
|
+
await repo.upsert?.({ item, execution })
|
|
302
|
+
} catch (error) {
|
|
303
|
+
this.logger.error(\`Error upserting \${name} \${i} - \${getItemDescription(item)}\`, error)
|
|
304
|
+
throw error
|
|
144
305
|
}
|
|
145
306
|
}
|
|
146
|
-
|
|
147
|
-
this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
|
|
148
307
|
}
|
|
149
|
-
|
|
150
|
-
|
|
308
|
+
this.logger.log(\`✅ Upserted \${format(data.length)} \${pluralize(name)}\`)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private async delete<T extends { id: ID }, ID extends number | string | boolean>({
|
|
312
|
+
name,
|
|
313
|
+
data,
|
|
314
|
+
repo,
|
|
315
|
+
execution,
|
|
316
|
+
}: {
|
|
317
|
+
name: string
|
|
318
|
+
data: ID[] | undefined
|
|
319
|
+
repo: Repository<T, ID>
|
|
320
|
+
execution: ActionExecution
|
|
321
|
+
}): Promise<void> {
|
|
322
|
+
if (!data) {
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
if (!DEBUG) {
|
|
326
|
+
await repo.deleteMany?.({ ids: data, execution })
|
|
327
|
+
} else {
|
|
328
|
+
let i = 0
|
|
329
|
+
for (const id of data) {
|
|
330
|
+
i++
|
|
331
|
+
this.logger.log(\`Deleting \${name} \${i} - id: \${id.toString()}\`)
|
|
332
|
+
try {
|
|
333
|
+
await repo.delete?.({ id, execution })
|
|
334
|
+
} catch (error) {
|
|
335
|
+
this.logger.error(\`Error deleting \${name} \${i} - id: \${id.toString()}\`, error)
|
|
336
|
+
throw error
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
this.logger.log(\`✅ Deleted \${format(data.length)} \${pluralize(name)}\`)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function getItemDescription<T extends Partial<{ id: ID }>, ID extends number | string | boolean>(item: T): string {
|
|
345
|
+
const id = \`id: \${item.id !== undefined ? item.id.toString() : 'not provided'}\`
|
|
346
|
+
|
|
347
|
+
// If the item has a name, we can assume it is a string and add it to the description
|
|
348
|
+
if ('name' in item) {
|
|
349
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
350
|
+
return \`\${id}, name: \${item.name}\`
|
|
351
|
+
}
|
|
352
|
+
return id
|
|
353
|
+
}
|
|
354
|
+
`;
|
|
151
355
|
}
|
|
152
356
|
exports.generateSeedService = generateSeedService;
|