@solidstarters/solid-core 1.2.202 → 1.2.204
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/dtos/create-role-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-role-metadata.dto.js +1 -0
- package/dist/dtos/create-role-metadata.dto.js.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +3 -0
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +99 -0
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
- package/dist/helpers/image-encoding.helper.d.ts +10 -0
- package/dist/helpers/image-encoding.helper.d.ts.map +1 -0
- package/dist/helpers/image-encoding.helper.js +44 -0
- package/dist/helpers/image-encoding.helper.js.map +1 -0
- package/dist/helpers/module.helper.d.ts.map +1 -1
- package/dist/helpers/module.helper.js +2 -0
- package/dist/helpers/module.helper.js.map +1 -1
- package/dist/helpers/solid-microservice-adapter.service.d.ts +31 -0
- package/dist/helpers/solid-microservice-adapter.service.d.ts.map +1 -0
- package/dist/helpers/solid-microservice-adapter.service.js +53 -0
- package/dist/helpers/solid-microservice-adapter.service.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mappers/list-of-values-mapper.d.ts.map +1 -1
- package/dist/mappers/list-of-values-mapper.js +1 -1
- package/dist/mappers/list-of-values-mapper.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +7 -2
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.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 +41 -0
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +5 -5
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +6 -1
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/selection-providers/list-of-roles-selectionproviders.service.d.ts +15 -0
- package/dist/services/selection-providers/list-of-roles-selectionproviders.service.d.ts.map +1 -0
- package/dist/services/selection-providers/list-of-roles-selectionproviders.service.js +59 -0
- package/dist/services/selection-providers/list-of-roles-selectionproviders.service.js.map +1 -0
- package/dist/services/user.service.d.ts +8 -1
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +15 -4
- package/dist/services/user.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +9 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/dtos/create-role-metadata.dto.ts +3 -0
- package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +138 -4
- package/src/helpers/image-encoding.helper.ts +71 -0
- package/src/helpers/module.helper.ts +3 -0
- package/src/helpers/solid-microservice-adapter.service.ts +68 -0
- package/src/index.ts +2 -0
- package/src/mappers/list-of-values-mapper.ts +2 -1
- package/src/repository/security-rule.repository.ts +7 -2
- package/src/seeders/module-metadata-seeder.service.ts +3 -1
- package/src/seeders/seed-data/solid-core-metadata.json +41 -0
- package/src/services/1.js +6 -0
- package/src/services/authentication.service.ts +7 -5
- package/src/services/crud.service.ts +6 -1
- package/src/services/selection-providers/list-of-roles-selectionproviders.service.ts +69 -0
- package/src/services/user.service.ts +19 -0
- package/src/solid-core.module.ts +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.204",
|
|
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",
|
|
@@ -91,6 +91,9 @@ export const INTERNAL_ROLE_PERMISSIONS = [
|
|
|
91
91
|
'AuthenticationController.logout',
|
|
92
92
|
'AuthenticationController.me',
|
|
93
93
|
|
|
94
|
+
// Field Metadata permissions
|
|
95
|
+
'ModelMetadataController.navigation',
|
|
96
|
+
|
|
94
97
|
// Field Metadata permissions
|
|
95
98
|
'FieldMetadataController.getSelectionDynamicValues',
|
|
96
99
|
'FieldMetadataController.getSelectionDynamicValue',
|
|
@@ -9,20 +9,122 @@ export interface MediaFieldOptions {
|
|
|
9
9
|
type: SolidMediaType;
|
|
10
10
|
required: boolean | undefined | null;
|
|
11
11
|
fieldName: string | undefined | null;
|
|
12
|
+
mediaMaxSizeKb: number | undefined | null;
|
|
13
|
+
mediaTypes: string[];
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
type MediaType = 'image' | 'audio' | 'video' | 'file';
|
|
17
|
+
|
|
18
|
+
const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
19
|
+
// Images
|
|
20
|
+
'image/png': 'image',
|
|
21
|
+
'image/jpeg': 'image',
|
|
22
|
+
'image/jpg': 'image',
|
|
23
|
+
'image/webp': 'image',
|
|
24
|
+
'image/gif': 'image',
|
|
25
|
+
'image/bmp': 'image',
|
|
26
|
+
'image/tiff': 'image',
|
|
27
|
+
'image/svg+xml': 'image',
|
|
28
|
+
'image/heic': 'image',
|
|
29
|
+
'image/heif': 'image',
|
|
30
|
+
|
|
31
|
+
// Audio
|
|
32
|
+
'audio/mpeg': 'audio', // mp3
|
|
33
|
+
'audio/mp3': 'audio',
|
|
34
|
+
'audio/wav': 'audio',
|
|
35
|
+
'audio/x-wav': 'audio',
|
|
36
|
+
'audio/webm': 'audio',
|
|
37
|
+
'audio/ogg': 'audio',
|
|
38
|
+
'audio/aac': 'audio',
|
|
39
|
+
'audio/mp4': 'audio', // m4a often shows as audio/mp4
|
|
40
|
+
'audio/x-m4a': 'audio',
|
|
41
|
+
'audio/flac': 'audio',
|
|
42
|
+
|
|
43
|
+
// Video
|
|
44
|
+
'video/mp4': 'video',
|
|
45
|
+
'video/mpeg': 'video',
|
|
46
|
+
'video/webm': 'video',
|
|
47
|
+
'video/ogg': 'video',
|
|
48
|
+
'video/quicktime': 'video', // mov
|
|
49
|
+
'video/x-msvideo': 'video', // avi
|
|
50
|
+
'video/x-matroska': 'video',// mkv
|
|
51
|
+
'video/3gpp': 'video',
|
|
52
|
+
'video/3gpp2': 'video',
|
|
53
|
+
|
|
54
|
+
// Documents / files (treat as "file")
|
|
55
|
+
'application/pdf': 'file',
|
|
56
|
+
'text/plain': 'file',
|
|
57
|
+
'text/markdown': 'file',
|
|
58
|
+
'application/json': 'file',
|
|
59
|
+
'text/csv': 'file',
|
|
60
|
+
|
|
61
|
+
// Office
|
|
62
|
+
'application/msword': 'file', // doc
|
|
63
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'file', // docx
|
|
64
|
+
'application/vnd.ms-excel': 'file', // xls
|
|
65
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'file', // xlsx
|
|
66
|
+
'application/vnd.ms-powerpoint': 'file', // ppt
|
|
67
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'file', // pptx,
|
|
68
|
+
|
|
69
|
+
// Archives (optional)
|
|
70
|
+
'application/zip': 'file',
|
|
71
|
+
'application/x-zip-compressed': 'file',
|
|
72
|
+
'application/x-rar-compressed': 'file',
|
|
73
|
+
'application/x-7z-compressed': 'file',
|
|
74
|
+
|
|
75
|
+
// Common binary fallback category
|
|
76
|
+
'application/octet-stream': 'file',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const EXT_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
80
|
+
// Images
|
|
81
|
+
png: 'image', jpg: 'image', jpeg: 'image', webp: 'image', gif: 'image', bmp: 'image', tiff: 'image', svg: 'image', heic: 'image', heif: 'image',
|
|
82
|
+
|
|
83
|
+
// Audio
|
|
84
|
+
mp3: 'audio', wav: 'audio', ogg: 'audio', aac: 'audio', m4a: 'audio', flac: 'audio',
|
|
85
|
+
|
|
86
|
+
// Video
|
|
87
|
+
mp4: 'video', mov: 'video', avi: 'video', mkv: 'video', mpeg: 'video', mpg: 'video', '3gp': 'video', '3g2': 'video',
|
|
88
|
+
|
|
89
|
+
// Files
|
|
90
|
+
pdf: 'file', txt: 'file', md: 'file', csv: 'file', json: 'file',
|
|
91
|
+
doc: 'file', docx: 'file', xls: 'file', xlsx: 'file', ppt: 'file', pptx: 'file',
|
|
92
|
+
zip: 'file', rar: 'file', '7z': 'file',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
|
|
14
96
|
export class MediaFieldCrudManager implements FieldCrudManager {
|
|
15
97
|
|
|
16
98
|
constructor(private readonly options: MediaFieldOptions) {
|
|
17
99
|
}
|
|
18
100
|
|
|
19
|
-
|
|
101
|
+
private resolveMediaType(mimetype?: string, filename?: string): MediaType | null {
|
|
102
|
+
const mt = (mimetype || '').toLowerCase().trim();
|
|
103
|
+
if (mt && MIME_TO_MEDIA_TYPE[mt]) {
|
|
104
|
+
return MIME_TO_MEDIA_TYPE[mt];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Some libs may send "image/*" etc. Treat broad families safely.
|
|
108
|
+
if (mt.startsWith('image/')) return 'image';
|
|
109
|
+
if (mt.startsWith('audio/')) return 'audio';
|
|
110
|
+
if (mt.startsWith('video/')) return 'video';
|
|
111
|
+
|
|
112
|
+
// Fallback to extension if provided
|
|
113
|
+
const ext = (filename || '').split('.').pop()?.toLowerCase();
|
|
114
|
+
if (ext && EXT_TO_MEDIA_TYPE[ext]) {
|
|
115
|
+
return EXT_TO_MEDIA_TYPE[ext];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
validate(dto: any, files: Array<Express.Multer.File>): ValidationError[] {
|
|
20
122
|
const isValidateForUpdate = dto.id !== undefined; //FIXME: This is a hack, since we are using PUT for update. Once we support PATCH, this will be removed
|
|
21
123
|
const fieldFiles = files.filter(file => file.fieldname === this.options.fieldName);
|
|
22
124
|
return this.applyValidations(fieldFiles, isValidateForUpdate);
|
|
23
125
|
}
|
|
24
126
|
|
|
25
|
-
private applyValidations(fieldFiles:Array<Express.Multer.File>, isValidateForUpdate: boolean): ValidationError[] {
|
|
127
|
+
private applyValidations(fieldFiles: Array<Express.Multer.File>, isValidateForUpdate: boolean): ValidationError[] {
|
|
26
128
|
switch (this.options.type) {
|
|
27
129
|
case SolidMediaType.mediaSingle:
|
|
28
130
|
return this.validateMediaSingle(fieldFiles, isValidateForUpdate);
|
|
@@ -33,7 +135,7 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
33
135
|
}
|
|
34
136
|
}
|
|
35
137
|
|
|
36
|
-
private validateMediaSingle(fieldFiles:Array<Express.Multer.File>, isValidateForUpdate: boolean): ValidationError[] {
|
|
138
|
+
private validateMediaSingle(fieldFiles: Array<Express.Multer.File>, isValidateForUpdate: boolean): ValidationError[] {
|
|
37
139
|
const errors: ValidationError[] = [];
|
|
38
140
|
if (!isValidateForUpdate && this.options.required && fieldFiles.length === 0) {
|
|
39
141
|
errors.push({
|
|
@@ -47,10 +149,42 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
47
149
|
error: `${this.options.fieldName} must be a single file`
|
|
48
150
|
});
|
|
49
151
|
}
|
|
152
|
+
// validate size
|
|
153
|
+
if (this.options.mediaMaxSizeKb) {
|
|
154
|
+
for (let i = 0; i < fieldFiles.length; i++) {
|
|
155
|
+
const fieldFile = fieldFiles[i];
|
|
156
|
+
const fieldFileSizeInBytes = Math.ceil(fieldFile.size / 1024);
|
|
157
|
+
if (fieldFileSizeInBytes > this.options.mediaMaxSizeKb) {
|
|
158
|
+
errors.push({
|
|
159
|
+
field: this.options.fieldName,
|
|
160
|
+
error: `${this.options.fieldName} with size ${fieldFileSizeInBytes} KB exceeds max size limit of ${this.options.mediaMaxSizeKb} KB`
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// validate type
|
|
166
|
+
if (this.options.mediaTypes && this.options.mediaTypes.length > 0) {
|
|
167
|
+
const allowedFileTypes = this.options.mediaTypes as MediaType[];
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < fieldFiles.length; i++) {
|
|
170
|
+
const fieldFile = fieldFiles[i];
|
|
171
|
+
|
|
172
|
+
const resolvedType = this.resolveMediaType(fieldFile.mimetype, fieldFile.originalname ?? fieldFile.filename ?? '');
|
|
173
|
+
if (!resolvedType || !allowedFileTypes.includes(resolvedType)) {
|
|
174
|
+
errors.push({
|
|
175
|
+
field: this.options.fieldName,
|
|
176
|
+
error: `${this.options.fieldName} file type not allowed. ` +
|
|
177
|
+
`Allowed: ${allowedFileTypes.join(', ')}. ` +
|
|
178
|
+
`Received mimetype: ${fieldFile.mimetype}${resolvedType ? ` (mapped to ${resolvedType})` : ''}`
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
50
184
|
return errors;
|
|
51
185
|
}
|
|
52
186
|
|
|
53
|
-
private validateMediaMultiple(fieldFiles:Array<Express.Multer.File>, isValidateForUpdate: boolean): ValidationError[] {
|
|
187
|
+
private validateMediaMultiple(fieldFiles: Array<Express.Multer.File>, isValidateForUpdate: boolean): ValidationError[] {
|
|
54
188
|
const errors: ValidationError[] = [];
|
|
55
189
|
if (!isValidateForUpdate && this.options.required && fieldFiles.length === 0) {
|
|
56
190
|
errors.push({
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
export interface ImageToBase64Result {
|
|
4
|
+
base64: string;
|
|
5
|
+
mediaType: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class ImageEncodingService {
|
|
10
|
+
private readonly logger = new Logger(ImageEncodingService.name);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fetch an image from a URL and return its Base64 representation.
|
|
14
|
+
*
|
|
15
|
+
* @param url - Public or signed image URL
|
|
16
|
+
* @param mimeTypeOverride - Optional MIME type (image/png, image/jpeg)
|
|
17
|
+
* @param maxSizeBytes - Optional max size guard (default 5MB)
|
|
18
|
+
*/
|
|
19
|
+
async imageUrlToBase64(
|
|
20
|
+
url: string,
|
|
21
|
+
mimeTypeOverride?: string,
|
|
22
|
+
maxSizeBytes = 5 * 1024 * 1024,
|
|
23
|
+
): Promise<ImageToBase64Result> {
|
|
24
|
+
this.logger.debug(`Fetching image from URL: ${url}`);
|
|
25
|
+
|
|
26
|
+
const response = await fetch(url);
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Failed to fetch image (${response.status} ${response.statusText})`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const contentType =
|
|
35
|
+
mimeTypeOverride ||
|
|
36
|
+
response.headers.get('content-type') ||
|
|
37
|
+
'image/jpeg';
|
|
38
|
+
|
|
39
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
40
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
41
|
+
|
|
42
|
+
if (buffer.length > maxSizeBytes) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Image size ${buffer.length} bytes exceeds limit of ${maxSizeBytes} bytes`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
base64: buffer.toString('base64'),
|
|
50
|
+
mediaType: contentType,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convenience method that returns a data URL
|
|
56
|
+
* (useful for OpenAI Responses API).
|
|
57
|
+
*/
|
|
58
|
+
async imageUrlToDataUrl(
|
|
59
|
+
url: string,
|
|
60
|
+
mimeTypeOverride?: string,
|
|
61
|
+
maxSizeBytes?: number,
|
|
62
|
+
): Promise<string> {
|
|
63
|
+
const { base64, mediaType } = await this.imageUrlToBase64(
|
|
64
|
+
url,
|
|
65
|
+
mimeTypeOverride,
|
|
66
|
+
maxSizeBytes,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return `data:${mediaType};base64,${base64}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -32,6 +32,8 @@ export const getDynamicModuleNames = (): string[] => {
|
|
|
32
32
|
.map(dirent => dirent.name);
|
|
33
33
|
|
|
34
34
|
// logger.debug(`Enabled dynamic modules:`, enabledModules);
|
|
35
|
+
console.log(`Dynamic Modules Are:`, enabledModules);
|
|
36
|
+
|
|
35
37
|
return enabledModules;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -65,6 +67,7 @@ export const getDynamicModuleNamesBasedOnMetadata = (): string[] => {
|
|
|
65
67
|
.map(dirent => dirent.name);
|
|
66
68
|
|
|
67
69
|
// logger.debug(`Enabled dynamic modules basis src:`, enabledModules);
|
|
70
|
+
console.log(`Src Based Dynamic Modules Are:`, enabledModules);
|
|
68
71
|
return enabledModules;
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Injectable, Logger, Scope } from '@nestjs/common';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
export interface SolidxIamAuthRequest {
|
|
5
|
+
email?: string;
|
|
6
|
+
username?: string;
|
|
7
|
+
password: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SolidxIamUser {
|
|
11
|
+
email: string;
|
|
12
|
+
mobile: string;
|
|
13
|
+
username: string;
|
|
14
|
+
forcePasswordChange: boolean;
|
|
15
|
+
id: number;
|
|
16
|
+
roles: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SolidxIamAuthData {
|
|
20
|
+
user: SolidxIamUser;
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SolidxIamAuthResponse {
|
|
26
|
+
statusCode: number;
|
|
27
|
+
message: string[];
|
|
28
|
+
error: string;
|
|
29
|
+
data: SolidxIamAuthData;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Injectable({ scope: Scope.TRANSIENT })
|
|
33
|
+
export class SolidMicroserviceAdapter {
|
|
34
|
+
private readonly logger = new Logger(SolidMicroserviceAdapter.name);
|
|
35
|
+
private baseUrl?: string;
|
|
36
|
+
|
|
37
|
+
setBaseUrl(baseUrl: string): this {
|
|
38
|
+
this.baseUrl = baseUrl;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async authenticate({ email, username, password }: SolidxIamAuthRequest): Promise<SolidxIamAuthResponse> {
|
|
43
|
+
if (!password) {
|
|
44
|
+
throw new Error('password is required for IAM authentication');
|
|
45
|
+
}
|
|
46
|
+
if (!email && !username) {
|
|
47
|
+
throw new Error('email or username is required for IAM authentication');
|
|
48
|
+
}
|
|
49
|
+
if (!this.baseUrl) {
|
|
50
|
+
throw new Error('baseUrl must be set before IAM authentication');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const payload: SolidxIamAuthRequest = { password };
|
|
54
|
+
if (email) payload.email = email;
|
|
55
|
+
if (username) payload.username = username;
|
|
56
|
+
|
|
57
|
+
this.logger.debug(`Requesting IAM access token from ${this.baseUrl}/api/iam/authenticate`);
|
|
58
|
+
|
|
59
|
+
const response = await axios.post(`${this.baseUrl}/api/iam/authenticate`, payload, {
|
|
60
|
+
timeout: 10000,
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return response.data as SolidxIamAuthResponse;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -178,6 +178,8 @@ export * from './helpers/environment.helper'
|
|
|
178
178
|
export * from './helpers/cors.helper'
|
|
179
179
|
export * from './helpers/security.helper'
|
|
180
180
|
export * from './helpers/model-metadata-helper.service'
|
|
181
|
+
export * from './helpers/image-encoding.helper'
|
|
182
|
+
export * from './helpers/solid-microservice-adapter.service'
|
|
181
183
|
|
|
182
184
|
export * from './services/crud.service'
|
|
183
185
|
export * from './interceptors/logging.interceptor'
|
|
@@ -11,7 +11,8 @@ export class ListOfValuesMapper {
|
|
|
11
11
|
description: listOfValue.description,
|
|
12
12
|
default: listOfValue.default,
|
|
13
13
|
sequence: listOfValue.sequence,
|
|
14
|
-
module: listOfValue.module ? listOfValue.module.id : null
|
|
14
|
+
// module: listOfValue.module ? listOfValue.module.id : null
|
|
15
|
+
moduleUserKey: listOfValue.module ? listOfValue.module.name : null
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -43,6 +43,9 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
43
43
|
if (rule.securityRuleConfigProvider) {
|
|
44
44
|
// TODO: Evaluation of the securityRuleConfig Provider should happen outside first...
|
|
45
45
|
const securityRuleConfigProviderInstance = this.solidRegistry.getSecurityRuleConfigProviderInstance(rule.securityRuleConfigProvider);
|
|
46
|
+
if (!securityRuleConfigProviderInstance) {
|
|
47
|
+
throw new Error(`Unable to resolve instance for security rule config provider: ${rule.securityRuleConfigProvider}`);
|
|
48
|
+
}
|
|
46
49
|
evaluatedRule = await securityRuleConfigProviderInstance.securityRuleConfig(activeUser, rule);
|
|
47
50
|
}
|
|
48
51
|
else {
|
|
@@ -54,7 +57,9 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
54
57
|
evaluatedRules.push(evaluatedRule);
|
|
55
58
|
|
|
56
59
|
} catch (error) {
|
|
57
|
-
this.logger.
|
|
60
|
+
this.logger.error(`Error parsing security rule: ${rule.securityRuleConfig}`, error);
|
|
61
|
+
this.logger.error(error.stack);
|
|
62
|
+
throw error;
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -104,7 +109,7 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
104
109
|
modelMetadataId: populatedSecurityRule.modelMetadata.id,
|
|
105
110
|
modelMetadataUserKey: populatedSecurityRule.modelMetadata.singularName,
|
|
106
111
|
securityRuleConfig: populatedSecurityRule.securityRuleConfig,
|
|
107
|
-
securityRuleConfigProvider:
|
|
112
|
+
securityRuleConfigProvider: populatedSecurityRule.securityRuleConfigProvider,
|
|
108
113
|
};
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -196,7 +196,9 @@ export class ModuleMetadataSeederService {
|
|
|
196
196
|
// Setup default roles with permissions.
|
|
197
197
|
await this.setupDefaultRolesWithPermissions();
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
// Add a console log indicating seeding is finished. This needs to be console.log so that it looks proper when this code is run via CLI.
|
|
200
|
+
console.log(`Seeding completed.`);
|
|
201
|
+
//this.logger.log(`All Seeders finished`);
|
|
200
202
|
|
|
201
203
|
//FIXME: Handle displaying the created users credentials in a better way.
|
|
202
204
|
// this.logger.log(`Newly created username is: ${usersDetail?.length > 0 ? usersDetail[0]?.username : ''} and password is ${usersDetail?.length > 0 ? usersDetail[0]?.password : ''}`);
|
|
@@ -8980,6 +8980,13 @@
|
|
|
8980
8980
|
"widget": "SolidUserNameAvatarWidget"
|
|
8981
8981
|
}
|
|
8982
8982
|
},
|
|
8983
|
+
{
|
|
8984
|
+
"type": "field",
|
|
8985
|
+
"attrs": {
|
|
8986
|
+
"name": "fullName",
|
|
8987
|
+
"isSearchable": true
|
|
8988
|
+
}
|
|
8989
|
+
},
|
|
8983
8990
|
{
|
|
8984
8991
|
"type": "field",
|
|
8985
8992
|
"attrs": {
|
|
@@ -11013,6 +11020,22 @@
|
|
|
11013
11020
|
"sortable": true,
|
|
11014
11021
|
"filterable": true
|
|
11015
11022
|
}
|
|
11023
|
+
},
|
|
11024
|
+
{
|
|
11025
|
+
"type": "field",
|
|
11026
|
+
"attrs": {
|
|
11027
|
+
"name": "event",
|
|
11028
|
+
"sortable": true,
|
|
11029
|
+
"filterable": true
|
|
11030
|
+
}
|
|
11031
|
+
},
|
|
11032
|
+
{
|
|
11033
|
+
"type": "field",
|
|
11034
|
+
"attrs": {
|
|
11035
|
+
"name": "ipAddress",
|
|
11036
|
+
"sortable": true,
|
|
11037
|
+
"filterable": true
|
|
11038
|
+
}
|
|
11016
11039
|
}
|
|
11017
11040
|
]
|
|
11018
11041
|
}
|
|
@@ -11057,6 +11080,24 @@
|
|
|
11057
11080
|
"attrs": {
|
|
11058
11081
|
"name": "user"
|
|
11059
11082
|
}
|
|
11083
|
+
},
|
|
11084
|
+
{
|
|
11085
|
+
"type": "field",
|
|
11086
|
+
"attrs": {
|
|
11087
|
+
"name": "event"
|
|
11088
|
+
}
|
|
11089
|
+
},
|
|
11090
|
+
{
|
|
11091
|
+
"type": "field",
|
|
11092
|
+
"attrs": {
|
|
11093
|
+
"name": "ipAddress"
|
|
11094
|
+
}
|
|
11095
|
+
},
|
|
11096
|
+
{
|
|
11097
|
+
"type": "field",
|
|
11098
|
+
"attrs": {
|
|
11099
|
+
"name": "userAgent"
|
|
11100
|
+
}
|
|
11060
11101
|
}
|
|
11061
11102
|
]
|
|
11062
11103
|
},
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
1. Do i need to create a storeStreams method for aws service too?
|
|
2
|
+
- Handle later
|
|
3
|
+
2. queues handling -> if queues is enabled by default, i.e triggerExport(exportTransactionEntity.id).
|
|
4
|
+
- startExport should either return the data or return the transaction id
|
|
5
|
+
3. How to handle scenarios wherein, nested related exist.(do i need to only get the userkey)
|
|
6
|
+
- show the userKey
|
|
@@ -176,7 +176,7 @@ export class AuthenticationService {
|
|
|
176
176
|
const savedUser = await this.userRepository.save(user);
|
|
177
177
|
// Also assign a default role to the newly created user.
|
|
178
178
|
const userRoles = signUpDto.roles ?? [];
|
|
179
|
-
if (this.iamConfiguration.defaultRole) {
|
|
179
|
+
if (signUpDto.username !== 'sa' && this.iamConfiguration.defaultRole) {
|
|
180
180
|
userRoles.push(this.iamConfiguration.defaultRole);
|
|
181
181
|
}
|
|
182
182
|
await this.handlePostSignup(savedUser, userRoles, pwd, autoGeneratedPwd);
|
|
@@ -842,14 +842,16 @@ export class AuthenticationService {
|
|
|
842
842
|
}
|
|
843
843
|
|
|
844
844
|
// Update Password
|
|
845
|
-
const
|
|
845
|
+
const pwdData = await this.userService.hashPassword(
|
|
846
|
+
changePasswordDto.newPassword,
|
|
847
|
+
);
|
|
846
848
|
user.password = changePasswordDto.newPassword;
|
|
847
|
-
user.passwordScheme = this.hashingService.name(); // e.g. bcrypt
|
|
848
|
-
user.passwordSchemeVersion = this.hashingService.currentVersion(); // e.g. 1, 2, 3 ...
|
|
849
849
|
|
|
850
|
+
user.password = pwdData.password;
|
|
851
|
+
user.passwordScheme = pwdData.passwordScheme;
|
|
852
|
+
user.passwordSchemeVersion = pwdData.passwordSchemeVersion;
|
|
850
853
|
// Everytime the user changes the password we reset the forcePasswordChange flag back to false.
|
|
851
854
|
user.forcePasswordChange = false;
|
|
852
|
-
user.password = newPwd;
|
|
853
855
|
|
|
854
856
|
await this.userRepository.save(user);
|
|
855
857
|
|
|
@@ -339,7 +339,12 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
339
339
|
// Use the EntityController to extract uploaded content & pass to the entity service.
|
|
340
340
|
// If embedded media, then the media uri will saved in the entity table, else the uri will be saved in the media table
|
|
341
341
|
// Plan the media table schema e.g id, uri, storageProvider, entity_id, entity_name, createdAt, updatedAt
|
|
342
|
-
const options = {
|
|
342
|
+
const options = {
|
|
343
|
+
...commonOptions,
|
|
344
|
+
mediaMaxSizeKb: fieldMetadata.mediaMaxSizeKb,
|
|
345
|
+
mediaTypes: fieldMetadata.mediaTypes,
|
|
346
|
+
type: fieldMetadata.type as unknown as SolidMediaType
|
|
347
|
+
};
|
|
343
348
|
return new MediaFieldCrudManager(options);
|
|
344
349
|
}
|
|
345
350
|
case SolidFieldType.relation: {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Injectable, BadRequestException } from "@nestjs/common";
|
|
2
|
+
import { SelectionProvider } from "src/decorators/selection-provider.decorator";
|
|
3
|
+
import {
|
|
4
|
+
ISelectionProvider,
|
|
5
|
+
ISelectionProviderContext,
|
|
6
|
+
ISelectionProviderValues
|
|
7
|
+
} from "../../interfaces";
|
|
8
|
+
import { BasicFilterDto } from "src/dtos/basic-filters.dto";
|
|
9
|
+
import { RoleMetadataService } from "../role-metadata.service";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_LIMIT = 100;
|
|
12
|
+
|
|
13
|
+
interface ListOfRolesProviderContext extends ISelectionProviderContext {
|
|
14
|
+
filters?: Record<string, any>;
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@SelectionProvider()
|
|
19
|
+
@Injectable()
|
|
20
|
+
export class ListOfRolesSelectionProvider implements ISelectionProvider<ListOfRolesProviderContext> {
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private readonly roleMetadataService: RoleMetadataService
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
name(): string {
|
|
27
|
+
return 'ListOfRolesSelectionProvider';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
help(): string {
|
|
31
|
+
return '# Simple Role selection provider (search + include/exclude)';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async value( optionValue: string, ctxt: ListOfRolesProviderContext ): Promise<ISelectionProviderValues> {
|
|
35
|
+
|
|
36
|
+
const basicFilterQuery = new BasicFilterDto(1, 0);
|
|
37
|
+
basicFilterQuery.filters = {
|
|
38
|
+
name: { $eq: optionValue }
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const roles = await this.roleMetadataService.find(basicFilterQuery);
|
|
42
|
+
|
|
43
|
+
if (!roles.records || roles.records.length === 0) {
|
|
44
|
+
throw new BadRequestException(
|
|
45
|
+
`Invalid role name: ${optionValue}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const role = roles.records[0];
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
label: role.name,
|
|
53
|
+
value: role.name
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async values( query: string, ctxt: ListOfRolesProviderContext ): Promise<readonly ISelectionProviderValues[]> {
|
|
58
|
+
|
|
59
|
+
const basicFilterQuery = new BasicFilterDto(DEFAULT_LIMIT, 0);
|
|
60
|
+
basicFilterQuery.filters = ctxt.filters || {};
|
|
61
|
+
|
|
62
|
+
const roles = await this.roleMetadataService.find(basicFilterQuery);
|
|
63
|
+
|
|
64
|
+
return roles.records.map(role => ({
|
|
65
|
+
label: role.name,
|
|
66
|
+
value: role.name
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -18,6 +18,7 @@ import { iamConfig } from 'src/config/iam.config';
|
|
|
18
18
|
import { ERROR_MESSAGES } from 'src/constants/error-messages';
|
|
19
19
|
import { UserRepository } from 'src/repository/user.repository';
|
|
20
20
|
import { RoleMetadataRepository } from 'src/repository/role-metadata.repository';
|
|
21
|
+
import { HashingService } from './hashing.service';
|
|
21
22
|
|
|
22
23
|
@Injectable()
|
|
23
24
|
export class UserService extends CRUDService<User> {
|
|
@@ -29,6 +30,8 @@ export class UserService extends CRUDService<User> {
|
|
|
29
30
|
readonly fileService: FileService,
|
|
30
31
|
readonly discoveryService: DiscoveryService,
|
|
31
32
|
readonly crudHelperService: CrudHelperService,
|
|
33
|
+
readonly hashingService: HashingService,
|
|
34
|
+
|
|
32
35
|
@InjectEntityManager()
|
|
33
36
|
readonly entityManager: EntityManager,
|
|
34
37
|
// @InjectRepository(User, 'default')
|
|
@@ -283,4 +286,20 @@ export class UserService extends CRUDService<User> {
|
|
|
283
286
|
}
|
|
284
287
|
}
|
|
285
288
|
|
|
289
|
+
async hashPassword(password: string): Promise<{
|
|
290
|
+
password: string;
|
|
291
|
+
passwordScheme: string;
|
|
292
|
+
passwordSchemeVersion: number;
|
|
293
|
+
}> {
|
|
294
|
+
const hashedPassword = await this.hashingService.hash(password);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
password: hashedPassword,
|
|
298
|
+
passwordScheme: this.hashingService.name(),
|
|
299
|
+
passwordSchemeVersion: this.hashingService.currentVersion(),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
286
304
|
}
|
|
305
|
+
|