@solidstarters/solid-core 1.2.206 → 1.2.208
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/commands/seed.command.js +1 -1
- package/dist/commands/seed.command.js.map +1 -1
- package/dist/controllers/test.controller.d.ts.map +1 -1
- package/dist/controllers/test.controller.js.map +1 -1
- package/dist/dtos/create-scheduled-job.dto.d.ts +1 -0
- package/dist/dtos/create-scheduled-job.dto.d.ts.map +1 -1
- package/dist/dtos/create-scheduled-job.dto.js +7 -1
- package/dist/dtos/create-scheduled-job.dto.js.map +1 -1
- package/dist/dtos/update-scheduled-job.dto.d.ts +1 -0
- package/dist/dtos/update-scheduled-job.dto.d.ts.map +1 -1
- package/dist/dtos/update-scheduled-job.dto.js +7 -1
- package/dist/dtos/update-scheduled-job.dto.js.map +1 -1
- package/dist/entities/scheduled-job.entity.d.ts +1 -0
- package/dist/entities/scheduled-job.entity.d.ts.map +1 -1
- package/dist/entities/scheduled-job.entity.js +5 -1
- package/dist/entities/scheduled-job.entity.js.map +1 -1
- package/dist/helpers/command.service.d.ts +6 -1
- package/dist/helpers/command.service.d.ts.map +1 -1
- package/dist/helpers/command.service.js +34 -11
- package/dist/helpers/command.service.js.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js +2 -2
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/schematic.service.d.ts +3 -2
- package/dist/helpers/schematic.service.d.ts.map +1 -1
- package/dist/helpers/schematic.service.js +32 -27
- package/dist/helpers/schematic.service.js.map +1 -1
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +1 -0
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1 -1
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +21 -1
- package/dist/seeders/system-fields-seeder.service.d.ts.map +1 -1
- package/dist/seeders/system-fields-seeder.service.js +3 -0
- package/dist/seeders/system-fields-seeder.service.js.map +1 -1
- package/dist/services/authentication.service.js +0 -3
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/file.service.d.ts.map +1 -1
- package/dist/services/file.service.js +1 -1
- package/dist/services/file.service.js.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.d.ts +1 -0
- package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.js +29 -0
- package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/type-declaration-import-issue.md +24 -0
- package/package.json +2 -1
- package/src/commands/seed.command.ts +1 -1
- package/src/controllers/test.controller.ts +0 -18
- package/src/dtos/create-scheduled-job.dto.ts +4 -0
- package/src/dtos/update-scheduled-job.dto.ts +4 -0
- package/src/entities/scheduled-job.entity.ts +2 -0
- package/src/helpers/command.service.ts +60 -12
- package/src/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.ts +2 -2
- package/src/helpers/schematic.service.ts +41 -35
- package/src/repository/scheduled-job.repository.ts +1 -0
- package/src/seeders/module-metadata-seeder.service.ts +1 -1
- package/src/seeders/seed-data/solid-core-metadata.json +21 -1
- package/src/seeders/system-fields-seeder.service.ts +3 -0
- package/src/services/authentication.service.ts +3 -3
- package/src/services/file.service.ts +4 -5
- package/src/services/scheduled-jobs/scheduler.service.ts +40 -3
- package/# Password field.md +0 -8
- package/dist/services/file/file-service.interface.d.ts +0 -22
- package/dist/services/file/file-service.interface.d.ts.map +0 -1
- package/dist/services/file/file-service.interface.js +0 -5
- package/dist/services/file/file-service.interface.js.map +0 -1
- package/sql-server-changes.txt +0 -88
- package/src/services/file/file-service.interface.ts +0 -74
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Issue: Consumer sees `Property 'id' does not exist on type 'VenueUser'`
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
The published declaration files in `dist/` import internal types using `src/...` module specifiers. In a consuming project, TypeScript resolves those imports against the consumer's `src` path (or fails), which can cause `User` to extend a different `CommonEntity` that does not define `id`. This leads to `Property 'id' does not exist on type 'VenueUser'` even though `CommonEntity` in the library defines it.
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
- `dist/entities/user.entity.d.ts` starts with:
|
|
8
|
+
- `import { CommonEntity } from "src/entities/common.entity";`
|
|
9
|
+
- `dist/entities/common.entity.d.ts` correctly contains:
|
|
10
|
+
- `id: number;`
|
|
11
|
+
|
|
12
|
+
Because the import is `src/...`, the consumer may resolve it to their own `src` tree instead of the library's `dist` types.
|
|
13
|
+
|
|
14
|
+
## Root Cause
|
|
15
|
+
Library declarations are not portable because internal imports are using absolute `src/...` aliases. These aliases are not part of Node module resolution, so consumers resolve them differently.
|
|
16
|
+
|
|
17
|
+
## Fix Options
|
|
18
|
+
1. **Preferred:** Replace internal imports in `src/` with relative paths so emitted `.d.ts` files are portable.
|
|
19
|
+
2. **Alternative:** Keep `src/*` aliases but add a post-build step (e.g., `tsc-alias`) to rewrite `dist/**/*.d.ts` imports to relative paths.
|
|
20
|
+
|
|
21
|
+
## Next Steps
|
|
22
|
+
- Decide on option 1 or 2.
|
|
23
|
+
- Rebuild and verify `dist/**/*.d.ts` no longer import from `src/...`.
|
|
24
|
+
- Publish a new package version and ensure the consuming app resolves `User` and `CommonEntity` from the same package instance.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.208",
|
|
4
4
|
"description": "This module is a NestJS module containing all the required core providers required by a Solid application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"cache-manager-redis-store": "^3.0.1",
|
|
50
50
|
"class-transformer": "^0.5.1",
|
|
51
51
|
"class-validator": "^0.14.1",
|
|
52
|
+
"cron-parser": "^5.5.0",
|
|
52
53
|
"dayjs": "^1.11.18",
|
|
53
54
|
"exceljs": "^4.4.0",
|
|
54
55
|
"fast-csv": "^5.0.2",
|
|
@@ -42,7 +42,7 @@ export class SeedCommand extends CommandRunner {
|
|
|
42
42
|
this.logger.error(`Seeder service ${options.seeder} not found. Does your service have a seed() method?`);
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
this.logger.log(`Running the seed() method for seeder
|
|
45
|
+
this.logger.log(`Running the seed() method for seeder: ${seeder.constructor.name}`);
|
|
46
46
|
await seeder.seed(parsedConf);
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -44,22 +44,4 @@ export class TestController {
|
|
|
44
44
|
await this.ingestMetadataService.ingest();
|
|
45
45
|
return { ok: true };
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
// @Public()
|
|
49
|
-
// @Post('seed')
|
|
50
|
-
// async seedData(@Body() seedData: any) {
|
|
51
|
-
// const seeder = this.solidRegistry
|
|
52
|
-
// .getSeeders()
|
|
53
|
-
// .filter((seeder) => seeder.name === seedData.seeder)
|
|
54
|
-
// .map((seeder) => seeder.instance)
|
|
55
|
-
// .pop();
|
|
56
|
-
// if (!seeder) {
|
|
57
|
-
// this.logger.error(`Seeder service ${seedData.seeder} not found. Does your service have a seed() method?`);
|
|
58
|
-
// return;
|
|
59
|
-
// }
|
|
60
|
-
// this.logger.log(`Running the seed() method for seeder :${seeder.constructor.name}`);
|
|
61
|
-
// await seeder.seed();
|
|
62
|
-
// return { message: `seed data for ${seedData.seeder}` };
|
|
63
|
-
// }
|
|
64
|
-
|
|
65
47
|
}
|
|
@@ -29,6 +29,8 @@ export class ScheduledJob extends CommonEntity {
|
|
|
29
29
|
dayOfWeek: string;
|
|
30
30
|
@Column({ type: "varchar" })
|
|
31
31
|
job: string;
|
|
32
|
+
@Column({ type: "varchar", nullable: true })
|
|
33
|
+
cronExpression: string;
|
|
32
34
|
@Index()
|
|
33
35
|
@ManyToOne(() => ModuleMetadata, { nullable: false })
|
|
34
36
|
@JoinColumn({ referencedColumnName: 'id' })
|
|
@@ -1,22 +1,70 @@
|
|
|
1
1
|
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
|
|
4
|
+
export type CommandWithArgs = {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
};
|
|
3
8
|
|
|
4
9
|
@Injectable()
|
|
5
10
|
export class CommandService {
|
|
6
11
|
private readonly logger = new Logger(CommandService.name);
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Escape an argument for Windows CMD shell
|
|
15
|
+
* Wraps in double quotes and escapes internal double quotes
|
|
16
|
+
*/
|
|
17
|
+
private escapeArgForWindows(arg: string): string {
|
|
18
|
+
// If arg contains special characters, wrap in double quotes
|
|
19
|
+
// and escape internal double quotes with backslash
|
|
20
|
+
if (/[{}\s"^&|<>]/.test(arg)) {
|
|
21
|
+
// Escape internal double quotes with backslash for CMD
|
|
22
|
+
const escaped = arg.replace(/"/g, '\\"');
|
|
23
|
+
return `"${escaped}"`;
|
|
24
|
+
}
|
|
25
|
+
return arg;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute a command with arguments array (cross-platform compatible)
|
|
30
|
+
*/
|
|
31
|
+
async executeCommandWithArgs(commandWithArgs: CommandWithArgs): Promise<string> {
|
|
32
|
+
const { command, args } = commandWithArgs;
|
|
33
|
+
this.logger.debug(`Executing command: ${command} ${args.join(' ')}`);
|
|
34
|
+
|
|
10
35
|
return new Promise<string>((resolve, reject) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
const isWindows = process.platform === 'win32';
|
|
37
|
+
|
|
38
|
+
// On Windows with shell: true, we need to escape args containing special characters
|
|
39
|
+
const spawnArgs = isWindows
|
|
40
|
+
? args.map(arg => this.escapeArgForWindows(arg))
|
|
41
|
+
: args;
|
|
42
|
+
|
|
43
|
+
const child = spawn(command, spawnArgs, {
|
|
44
|
+
shell: isWindows, // Use shell on Windows to handle .cmd files
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
let stdout = '';
|
|
49
|
+
let stderr = '';
|
|
50
|
+
|
|
51
|
+
child.stdout.on('data', (data) => {
|
|
52
|
+
stdout += data.toString();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.stderr.on('data', (data) => {
|
|
56
|
+
stderr += data.toString();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
child.on('error', (error) => {
|
|
60
|
+
this.logger.error(`Error executing command: ${command}`, error);
|
|
61
|
+
reject(error);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
child.on('close', (code) => {
|
|
65
|
+
if (code !== 0) {
|
|
66
|
+
this.logger.error(`Command failed with code ${code}: ${command}`, stderr);
|
|
67
|
+
reject(new Error(stderr || `Command failed with exit code ${code}`));
|
|
20
68
|
return;
|
|
21
69
|
}
|
|
22
70
|
resolve(stdout);
|
|
@@ -77,13 +77,13 @@ export class ManyToOneRelationFieldCrudManager implements FieldCrudManager {
|
|
|
77
77
|
if (isNotEmpty(fieldId)) {
|
|
78
78
|
dto[this.options.fieldName] = await this.options.entityManager.getRepository(coModelEntityName).findOneBy({ id: fieldId });
|
|
79
79
|
if (this.options.required && isEmpty(dto[this.options.fieldName])) {
|
|
80
|
-
throw new Error(`
|
|
80
|
+
throw new Error(`Field ${this.options.fieldName} resolution failed. Record with id: ${fieldId} not found in ${this.options.relationCoModelSingularName}`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
84
|
dto[this.options.fieldName] = await this.options.entityManager.getRepository(coModelEntityName).findOneBy({ [this.options.relationCoModelUserKeyFieldName]: fieldUserKeyValue });
|
|
85
85
|
if (this.options.required && isEmpty(dto[this.options.fieldName])) {
|
|
86
|
-
throw new Error(`
|
|
86
|
+
throw new Error(`Field ${this.options.fieldName} resolution failed. Record with userKey: ${this.options.relationCoModelUserKeyFieldName}: ${fieldUserKeyValue} not found in ${this.options.relationCoModelSingularName}`);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import { snakeCase } from "lodash";
|
|
3
3
|
import { FieldMetadata } from 'src/entities/field-metadata.entity';
|
|
4
|
-
import { CommandService } from './command.service';
|
|
4
|
+
import { CommandService, CommandWithArgs } from './command.service';
|
|
5
5
|
import { SolidRegistry } from './solid-registry';
|
|
6
6
|
|
|
7
7
|
export const ADD_MODULE_COMMAND = 'add-module';
|
|
@@ -17,7 +17,7 @@ type ModelAndFieldGenerationOptions = {
|
|
|
17
17
|
table?: string;
|
|
18
18
|
dataSource: string;
|
|
19
19
|
modelEnableSoftDelete?: boolean;
|
|
20
|
-
parentModel?: string;
|
|
20
|
+
parentModel?: string;
|
|
21
21
|
parentModule?: string;
|
|
22
22
|
draftPublishWorkflowEnabled?: boolean;
|
|
23
23
|
isLegacyTable?: boolean;
|
|
@@ -33,6 +33,8 @@ export const REFRESH_MODEL_COMMAND = 'refresh-model';
|
|
|
33
33
|
export class SchematicService {
|
|
34
34
|
private readonly logger = new Logger(SchematicService.name);
|
|
35
35
|
private readonly SCHEMATIC_PROJECT = '@solidstarters/solid-code-builder';
|
|
36
|
+
private readonly SCHEMATICS_COMMAND = 'schematics';
|
|
37
|
+
|
|
36
38
|
constructor(private readonly commandService: CommandService, private readonly solidRegistry: SolidRegistry) { }
|
|
37
39
|
|
|
38
40
|
async executeSchematicCommand(
|
|
@@ -40,91 +42,95 @@ export class SchematicService {
|
|
|
40
42
|
options: GenerateModuleOptions | ModelAndFieldGenerationOptions,
|
|
41
43
|
debug = false,
|
|
42
44
|
): Promise<string> {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
45
|
+
const commandWithArgs = this.generateSchematicCommand(command, options, debug);
|
|
46
|
+
return await this.commandService.executeCommandWithArgs(commandWithArgs);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
private generateSchematicCommand(
|
|
49
50
|
command: string,
|
|
50
51
|
options: GenerateModuleOptions | ModelAndFieldGenerationOptions,
|
|
51
52
|
debug: boolean,
|
|
52
|
-
):
|
|
53
|
-
const
|
|
53
|
+
): CommandWithArgs {
|
|
54
|
+
const schematicName = `${this.SCHEMATIC_PROJECT}:${command}`;
|
|
55
|
+
const baseArgs = [schematicName, `--debug=${debug}`];
|
|
56
|
+
|
|
54
57
|
if (
|
|
55
58
|
command === REMOVE_FIELDS_COMMAND ||
|
|
56
59
|
command === REFRESH_MODEL_COMMAND
|
|
57
60
|
) {
|
|
58
|
-
const {fields, ...modelSpecificOptions} = options as ModelAndFieldGenerationOptions;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
this.logger.debug('schematicCommand',
|
|
63
|
-
return
|
|
61
|
+
const { fields, ...modelSpecificOptions } = options as ModelAndFieldGenerationOptions;
|
|
62
|
+
const modelArgs = this.buildModelGenerationArgs(modelSpecificOptions);
|
|
63
|
+
const fieldArgs = this.buildFieldGenerationArgs(fields);
|
|
64
|
+
const args = [...baseArgs, ...modelArgs, ...fieldArgs];
|
|
65
|
+
this.logger.debug('schematicCommand args', args);
|
|
66
|
+
return { command: this.SCHEMATICS_COMMAND, args };
|
|
64
67
|
} else if (command === ADD_MODULE_COMMAND) {
|
|
65
68
|
const moduleOptions = options as GenerateModuleOptions;
|
|
66
|
-
const
|
|
67
|
-
this.logger.debug('schematicCommand',
|
|
68
|
-
return
|
|
69
|
+
const args = [...baseArgs, `--module=${moduleOptions.module}`];
|
|
70
|
+
this.logger.debug('schematicCommand args', args);
|
|
71
|
+
return { command: this.SCHEMATICS_COMMAND, args };
|
|
69
72
|
} else {
|
|
70
73
|
throw new Error('Schematic command not supported.');
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
private
|
|
77
|
+
private buildFieldGenerationArgs(fields: FieldMetadata[]): string[] {
|
|
75
78
|
return fields
|
|
76
79
|
.filter((field) => {
|
|
77
80
|
return !this.solidRegistry.getCommonEntityKeys().map(key => key.toString()).includes(field.name);
|
|
78
81
|
})
|
|
79
82
|
.map((field) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
// Using argument array eliminates the need for shell-specific quoting
|
|
84
|
+
return `--fields=${JSON.stringify(field)}`;
|
|
85
|
+
});
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
private
|
|
86
|
-
|
|
88
|
+
private buildModelGenerationArgs(modelSpecificOptions: ModelGenerationOptions): string[] {
|
|
89
|
+
const args: string[] = [
|
|
90
|
+
`--module=${modelSpecificOptions.module}`,
|
|
91
|
+
`--model=${modelSpecificOptions.model}`,
|
|
92
|
+
];
|
|
87
93
|
|
|
88
|
-
// Make below options code generate i.e if option exists then add to command with proper casing
|
|
89
94
|
if (modelSpecificOptions.moduleDisplayName) {
|
|
90
|
-
|
|
95
|
+
args.push(`--module-display-name=${snakeCase(modelSpecificOptions.moduleDisplayName)}`);
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
if (modelSpecificOptions.table) {
|
|
94
|
-
|
|
99
|
+
args.push(`--table=${modelSpecificOptions.table}`);
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
if (modelSpecificOptions.dataSource) {
|
|
98
|
-
|
|
103
|
+
args.push(`--data-source=${modelSpecificOptions.dataSource}`);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
if (modelSpecificOptions.modelEnableSoftDelete) {
|
|
102
|
-
|
|
107
|
+
args.push(`--model-enable-soft-delete=${modelSpecificOptions.modelEnableSoftDelete}`);
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
if (modelSpecificOptions.parentModel) {
|
|
106
|
-
|
|
111
|
+
args.push(`--parent-model=${modelSpecificOptions.parentModel}`);
|
|
107
112
|
}
|
|
113
|
+
|
|
108
114
|
if (modelSpecificOptions.parentModule) {
|
|
109
|
-
|
|
115
|
+
args.push(`--parent-module=${modelSpecificOptions.parentModule}`);
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
if (modelSpecificOptions.draftPublishWorkflowEnabled) {
|
|
113
|
-
|
|
119
|
+
args.push(`--draft-publish-workflow-enabled=${modelSpecificOptions.draftPublishWorkflowEnabled}`);
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
if (modelSpecificOptions.isLegacyTable) {
|
|
117
|
-
|
|
123
|
+
args.push(`--is-legacy-table=${modelSpecificOptions.isLegacyTable}`);
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
if (modelSpecificOptions.isLegacyTableWithId) {
|
|
121
|
-
|
|
127
|
+
args.push(`--is-legacy-table-with-id=${modelSpecificOptions.isLegacyTableWithId}`);
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
if (modelSpecificOptions.dataSourceType) {
|
|
125
|
-
|
|
131
|
+
args.push(`--data-source-type=${modelSpecificOptions.dataSourceType}`);
|
|
126
132
|
}
|
|
127
|
-
|
|
128
|
-
return
|
|
133
|
+
|
|
134
|
+
return args;
|
|
129
135
|
}
|
|
130
136
|
}
|
|
@@ -70,6 +70,7 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
|
|
|
70
70
|
job: populatedScheduledJob.job,
|
|
71
71
|
moduleUserKey: populatedScheduledJob.module.name,
|
|
72
72
|
moduleId: populatedScheduledJob.module.id,
|
|
73
|
+
cronExpression: populatedScheduledJob.cronExpression,
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -88,7 +88,7 @@ export class ModuleMetadataSeederService {
|
|
|
88
88
|
// Module specific seeding steps.
|
|
89
89
|
// Get all the module metadata files which needs to be seeded.
|
|
90
90
|
const seedDataFiles = this.seedDataFiles;
|
|
91
|
-
this.logger.debug(`
|
|
91
|
+
this.logger.debug(`Found seed data for modules: ${seedDataFiles.map(s => s.moduleMetadata?.name)}`);
|
|
92
92
|
|
|
93
93
|
/** -------------------------------------------------------------
|
|
94
94
|
* Selective module seeding via:
|
|
@@ -2613,7 +2613,8 @@
|
|
|
2613
2613
|
"Hourly:Hourly",
|
|
2614
2614
|
"Daily:Daily",
|
|
2615
2615
|
"Weekly:Weekly",
|
|
2616
|
-
"Monthly:Monthly"
|
|
2616
|
+
"Monthly:Monthly",
|
|
2617
|
+
"Custom:Custom"
|
|
2617
2618
|
],
|
|
2618
2619
|
"selectionValueType": "string",
|
|
2619
2620
|
"required": true,
|
|
@@ -2787,6 +2788,19 @@
|
|
|
2787
2788
|
"selectionDynamicProvider": "ListOfScheduledJobsSelectionProvider",
|
|
2788
2789
|
"selectionDynamicProviderCtxt": "{}"
|
|
2789
2790
|
},
|
|
2791
|
+
{
|
|
2792
|
+
"name": "cronExpression",
|
|
2793
|
+
"displayName": "Cron Expression",
|
|
2794
|
+
"type": "shortText",
|
|
2795
|
+
"ormType": "varchar",
|
|
2796
|
+
"length": 512,
|
|
2797
|
+
"required": false,
|
|
2798
|
+
"unique": false,
|
|
2799
|
+
"index": false,
|
|
2800
|
+
"private": false,
|
|
2801
|
+
"encrypt": false,
|
|
2802
|
+
"isSystem": false
|
|
2803
|
+
},
|
|
2790
2804
|
{
|
|
2791
2805
|
"name": "module",
|
|
2792
2806
|
"displayName": "Module",
|
|
@@ -10306,6 +10320,12 @@
|
|
|
10306
10320
|
"name": "frequency"
|
|
10307
10321
|
}
|
|
10308
10322
|
},
|
|
10323
|
+
{
|
|
10324
|
+
"type": "field",
|
|
10325
|
+
"attrs": {
|
|
10326
|
+
"name": "cronExpression"
|
|
10327
|
+
}
|
|
10328
|
+
},
|
|
10309
10329
|
{
|
|
10310
10330
|
"type": "field",
|
|
10311
10331
|
"attrs": {
|
|
@@ -155,9 +155,9 @@ export class AuthenticationService {
|
|
|
155
155
|
|
|
156
156
|
async signUp(signUpDto: SignUpDto, activeUser: ActiveUserData = null): Promise<User> {
|
|
157
157
|
// If public registrations are disabled and no activeUser is present when invoking signUp then we throw an exception.
|
|
158
|
-
if (!(this.settingService.getConfigValue<SolidCoreSetting>('allowPublicRegistration')) && !activeUser) {
|
|
159
|
-
|
|
160
|
-
}
|
|
158
|
+
// if (!(this.settingService.getConfigValue<SolidCoreSetting>('allowPublicRegistration')) && !activeUser) {
|
|
159
|
+
// throw new BadRequestException(ERROR_MESSAGES.PUBLIC_REGISTRATION_DISABLED);
|
|
160
|
+
// }
|
|
161
161
|
|
|
162
162
|
try {
|
|
163
163
|
const onForcePasswordChange = this.settingService.getConfigValue<SolidCoreSetting>('forceChangePasswordOnFirstLogin');
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
// import * as AWS from 'aws-sdk';
|
|
4
|
-
import {
|
|
5
|
-
import { ConfigType } from '@nestjs/config';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { Readable } from 'stream';
|
|
4
|
+
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
8
5
|
import { getSignedUrl as awsGetSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
6
|
+
import path from 'path';
|
|
9
7
|
import { ERROR_MESSAGES } from 'src/constants/error-messages';
|
|
10
8
|
import { AwsS3Config } from 'src/interfaces';
|
|
9
|
+
import { Readable } from 'stream';
|
|
11
10
|
|
|
12
11
|
@Injectable()
|
|
13
12
|
export class FileService {
|
|
@@ -6,6 +6,7 @@ import { ScheduledJob } from 'src/entities/scheduled-job.entity';
|
|
|
6
6
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
7
7
|
import { ScheduledJobRepository } from 'src/repository/scheduled-job.repository';
|
|
8
8
|
import { ISchedulerService } from './scheduler.interface';
|
|
9
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
9
10
|
|
|
10
11
|
@Injectable()
|
|
11
12
|
export class SchedulerServiceImpl implements ISchedulerService {
|
|
@@ -93,6 +94,13 @@ export class SchedulerServiceImpl implements ISchedulerService {
|
|
|
93
94
|
if (timeNow > jobEnd) return false;
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
// 3. Check custom frequency
|
|
98
|
+
if (job.frequency.toLowerCase() === 'custom') {
|
|
99
|
+
// Custom cron expressions handle their own scheduling logic
|
|
100
|
+
// Just check if nextRunAt is due, which was already checked in the query
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
96
104
|
// 3. Check dayOfWeek (for weekly)
|
|
97
105
|
if (job.frequency.toLowerCase() === 'weekly' && job.dayOfWeek) {
|
|
98
106
|
const todayName = now.toLocaleString('en-US', { weekday: 'long' }); // e.g., "Monday"
|
|
@@ -110,6 +118,36 @@ export class SchedulerServiceImpl implements ISchedulerService {
|
|
|
110
118
|
return true;
|
|
111
119
|
}
|
|
112
120
|
|
|
121
|
+
private computeNextRunForCustomCron(job: ScheduledJob, from: Date): Date {
|
|
122
|
+
const base = new Date(from);
|
|
123
|
+
|
|
124
|
+
if (!job.cronExpression) {
|
|
125
|
+
this.logger.error(`Custom frequency requires cronExpression for job ${job.scheduleName}`);
|
|
126
|
+
// Fallback to daily if cron expression is missing
|
|
127
|
+
return new Date(base.getTime() + 24 * 60 * 60 * 1000);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const interval = CronExpressionParser.parse(job.cronExpression, {
|
|
132
|
+
currentDate: from,
|
|
133
|
+
tz: 'UTC'
|
|
134
|
+
});
|
|
135
|
+
const nextRun = interval.next().toDate();
|
|
136
|
+
|
|
137
|
+
// Validate minimum 1 minute interval
|
|
138
|
+
if (nextRun.getTime() - from.getTime() < 60000) {
|
|
139
|
+
throw new Error('Cron expression interval must be at least 1 minute');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.logger.log(`Custom cron '${job.cronExpression}' next run: ${nextRun}`);
|
|
143
|
+
return nextRun;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.logger.error(`Invalid cron expression for job ${job.scheduleName}: ${job.cronExpression}`, error);
|
|
146
|
+
// Fallback to daily if cron parsing fails
|
|
147
|
+
return new Date(base.getTime() + 24 * 60 * 60 * 1000);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
private computeNextRunAt(job: ScheduledJob, from: Date): Date {
|
|
114
152
|
const base = new Date(from);
|
|
115
153
|
|
|
@@ -128,9 +166,8 @@ export class SchedulerServiceImpl implements ISchedulerService {
|
|
|
128
166
|
const next = new Date(base);
|
|
129
167
|
next.setMonth(base.getMonth() + 1);
|
|
130
168
|
return next;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// return new Date(base.getTime() + 24 * 60 * 60 * 1000);
|
|
169
|
+
case 'custom':
|
|
170
|
+
return this.computeNextRunForCustomCron(job, from);
|
|
134
171
|
default:
|
|
135
172
|
return new Date(base.getTime() + 24 * 60 * 60 * 1000);
|
|
136
173
|
}
|
package/# Password field.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Password field
|
|
2
|
-
## Create Scenario
|
|
3
|
-
- password
|
|
4
|
-
- passwordConfirm (stuck)
|
|
5
|
-
## Update Scenario
|
|
6
|
-
- A button will show up instead & password fields won't be shown in the form
|
|
7
|
-
- Clicking on the button will open a modal containing the password & passwordConfirm fields, with a button named as "Update". clicking on Update will patch the record with the password & passwordConfirm fields
|
|
8
|
-
- Add an extra in validateAndTranform in crud service i.e isUpdate i.e default false. this will true when update method of crud service is called. Add logic in PasswordFieldCrudManager to throw required error only if !isUpdate
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Readable } from 'stream';
|
|
2
|
-
export interface WriteOptions {
|
|
3
|
-
contentType?: string;
|
|
4
|
-
}
|
|
5
|
-
export interface CopyOptions {
|
|
6
|
-
contentType?: string;
|
|
7
|
-
overwrite?: boolean;
|
|
8
|
-
}
|
|
9
|
-
export interface UrlOptions {
|
|
10
|
-
expiresIn?: number;
|
|
11
|
-
}
|
|
12
|
-
export interface IFileService {
|
|
13
|
-
read(path: string): Promise<Buffer>;
|
|
14
|
-
write(path: string, data: Buffer | string, options?: WriteOptions): Promise<void>;
|
|
15
|
-
writeStream(path: string, stream: Readable, options?: WriteOptions): Promise<void>;
|
|
16
|
-
delete(path: string): Promise<void>;
|
|
17
|
-
exists(path: string): Promise<boolean>;
|
|
18
|
-
copy(sourcePath: string, destinationPath: string, options?: CopyOptions): Promise<void>;
|
|
19
|
-
getUrl(path: string, options?: UrlOptions): Promise<string>;
|
|
20
|
-
}
|
|
21
|
-
export declare const FILE_SERVICE: unique symbol;
|
|
22
|
-
//# sourceMappingURL=file-service.interface.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-service.interface.d.ts","sourceRoot":"","sources":["../../../src/services/file/file-service.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAEzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,YAAY;IAK3B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAQpC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAQlF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAMnF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAMpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAQvC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAQxF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,YAAY,eAAyB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-service.interface.js","sourceRoot":"","sources":["../../../src/services/file/file-service.interface.ts"],"names":[],"mappings":";;;AAyEa,QAAA,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC","sourcesContent":["import { Readable } from 'stream';\n\nexport interface WriteOptions {\n contentType?: string;\n}\n\nexport interface CopyOptions {\n contentType?: string;\n /** Whether to overwrite existing file (default: true) */\n overwrite?: boolean;\n}\n\nexport interface UrlOptions {\n /** Expiry time in seconds (relevant for S3 signed URLs) */\n expiresIn?: number;\n}\n\n/**\n * Unified interface for file operations.\n * Implementations: DiskFileService, S3FileService\n */\nexport interface IFileService {\n /**\n * Read file contents as Buffer\n * @param path - File path (disk) or key (S3)\n */\n read(path: string): Promise<Buffer>;\n\n /**\n * Write data to a file\n * @param path - File path (disk) or key (S3)\n * @param data - Content to write\n * @param options - Optional write options (e.g., contentType for S3)\n */\n write(path: string, data: Buffer | string, options?: WriteOptions): Promise<void>;\n\n /**\n * Write a stream to a file\n * @param path - File path (disk) or key (S3)\n * @param stream - Readable stream\n * @param options - Optional write options (e.g., contentType for S3)\n */\n writeStream(path: string, stream: Readable, options?: WriteOptions): Promise<void>;\n\n /**\n * Delete a file\n * @param path - File path (disk) or key (S3)\n */\n delete(path: string): Promise<void>;\n\n /**\n * Check if file exists\n * @param path - File path (disk) or key (S3)\n */\n exists(path: string): Promise<boolean>;\n\n /**\n * Copy a file from source to destination\n * @param sourcePath - Source file path\n * @param destinationPath - Destination file path\n * @param options - Optional copy options\n */\n copy(sourcePath: string, destinationPath: string, options?: CopyOptions): Promise<void>;\n\n /**\n * Get an accessible URL for the file\n * For S3: returns signed URL; For disk: returns the file path\n * @param path - File path (disk) or key (S3)\n * @param options - URL options (e.g., expiresIn for S3)\n */\n getUrl(path: string, options?: UrlOptions): Promise<string>;\n}\n\nexport const FILE_SERVICE = Symbol('FILE_SERVICE');\n"]}
|