@solidxai/core 0.1.8-beta.1 → 0.1.8-beta.11
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/README.md +197 -0
- package/dist/controllers/authentication.controller.d.ts +32 -2
- package/dist/controllers/authentication.controller.d.ts.map +1 -1
- package/dist/controllers/authentication.controller.js +80 -3
- package/dist/controllers/authentication.controller.js.map +1 -1
- package/dist/dtos/create-api-key.dto.d.ts +5 -0
- package/dist/dtos/create-api-key.dto.d.ts.map +1 -0
- package/dist/dtos/create-api-key.dto.js +34 -0
- package/dist/dtos/create-api-key.dto.js.map +1 -0
- package/dist/dtos/post-chatter-message.dto.d.ts +1 -0
- package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -1
- package/dist/dtos/post-chatter-message.dto.js +6 -1
- package/dist/dtos/post-chatter-message.dto.js.map +1 -1
- package/dist/dtos/register-private.dto.d.ts +3 -5
- package/dist/dtos/register-private.dto.d.ts.map +1 -1
- package/dist/dtos/register-private.dto.js +6 -18
- package/dist/dtos/register-private.dto.js.map +1 -1
- package/dist/dtos/sso-exchange.dto.d.ts +4 -0
- package/dist/dtos/sso-exchange.dto.d.ts.map +1 -0
- package/dist/dtos/sso-exchange.dto.js +26 -0
- package/dist/dtos/sso-exchange.dto.js.map +1 -0
- package/dist/dtos/update-api-key.dto.d.ts +4 -0
- package/dist/dtos/update-api-key.dto.d.ts.map +1 -0
- package/dist/dtos/update-api-key.dto.js +28 -0
- package/dist/dtos/update-api-key.dto.js.map +1 -0
- package/dist/entities/agent-event.entity.d.ts +3 -12
- package/dist/entities/agent-event.entity.d.ts.map +1 -1
- package/dist/entities/agent-event.entity.js +21 -46
- package/dist/entities/agent-event.entity.js.map +1 -1
- package/dist/entities/agent-session.entity.d.ts +2 -11
- package/dist/entities/agent-session.entity.d.ts.map +1 -1
- package/dist/entities/agent-session.entity.js +15 -40
- package/dist/entities/agent-session.entity.js.map +1 -1
- package/dist/entities/field-metadata.entity.js +1 -1
- package/dist/entities/field-metadata.entity.js.map +1 -1
- package/dist/entities/legacy-common.entity.d.ts +9 -9
- package/dist/entities/legacy-common.entity.d.ts.map +1 -1
- package/dist/entities/legacy-common.entity.js +7 -7
- package/dist/entities/legacy-common.entity.js.map +1 -1
- package/dist/entities/setting.entity.d.ts +1 -0
- package/dist/entities/setting.entity.d.ts.map +1 -1
- package/dist/entities/setting.entity.js +5 -1
- package/dist/entities/setting.entity.js.map +1 -1
- package/dist/entities/sms-template.entity.d.ts.map +1 -1
- package/dist/entities/sms-template.entity.js +2 -1
- package/dist/entities/sms-template.entity.js.map +1 -1
- package/dist/entities/user-api-key.entity.d.ts +12 -0
- package/dist/entities/user-api-key.entity.d.ts.map +1 -0
- package/dist/entities/user-api-key.entity.js +62 -0
- package/dist/entities/user-api-key.entity.js.map +1 -0
- package/dist/entities/user.entity.d.ts +3 -0
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +12 -1
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/enums/auth-type.enum.d.ts +2 -1
- package/dist/enums/auth-type.enum.d.ts.map +1 -1
- package/dist/enums/auth-type.enum.js +2 -1
- package/dist/enums/auth-type.enum.js.map +1 -1
- package/dist/guards/api-key.guard.d.ts +11 -0
- package/dist/guards/api-key.guard.d.ts.map +1 -0
- package/dist/guards/api-key.guard.js +43 -0
- package/dist/guards/api-key.guard.js.map +1 -0
- package/dist/guards/authentication.guard.d.ts +4 -2
- package/dist/guards/authentication.guard.d.ts.map +1 -1
- package/dist/guards/authentication.guard.js +7 -3
- package/dist/guards/authentication.guard.js.map +1 -1
- package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
- package/dist/helpers/bootstrap.helper.js +12 -1
- package/dist/helpers/bootstrap.helper.js.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js +15 -6
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js.map +1 -1
- package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +9 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- 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/interfaces.d.ts +12 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/jobs/database/chatter-queue-publisher-database.service.d.ts +1 -1
- package/dist/jobs/database/chatter-queue-publisher-database.service.d.ts.map +1 -1
- package/dist/jobs/database/chatter-queue-publisher-database.service.js.map +1 -1
- package/dist/jobs/database/chatter-queue-subscriber-database.service.d.ts +1 -1
- package/dist/jobs/database/chatter-queue-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/chatter-queue-subscriber-database.service.js.map +1 -1
- package/dist/jobs/rabbitmq/chatter-queue-publisher.service.d.ts +1 -12
- package/dist/jobs/rabbitmq/chatter-queue-publisher.service.d.ts.map +1 -1
- package/dist/jobs/rabbitmq/chatter-queue-publisher.service.js.map +1 -1
- package/dist/jobs/rabbitmq/chatter-queue-subscriber.service.d.ts +1 -1
- package/dist/jobs/rabbitmq/chatter-queue-subscriber.service.d.ts.map +1 -1
- package/dist/jobs/rabbitmq/chatter-queue-subscriber.service.js.map +1 -1
- package/dist/jobs/redis/chatter-queue-subscriber-redis.service.d.ts +1 -1
- package/dist/jobs/redis/chatter-queue-subscriber-redis.service.d.ts.map +1 -1
- package/dist/jobs/redis/chatter-queue-subscriber-redis.service.js.map +1 -1
- package/dist/repository/user-api-key.repository.d.ts +12 -0
- package/dist/repository/user-api-key.repository.d.ts.map +1 -0
- package/dist/repository/user-api-key.repository.js +34 -0
- package/dist/repository/user-api-key.repository.js.map +1 -0
- package/dist/seeders/module-test-data.service.d.ts +5 -0
- package/dist/seeders/module-test-data.service.d.ts.map +1 -1
- package/dist/seeders/module-test-data.service.js +131 -4
- package/dist/seeders/module-test-data.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +287 -197
- package/dist/services/api-key.service.d.ts +20 -0
- package/dist/services/api-key.service.d.ts.map +1 -0
- package/dist/services/api-key.service.js +98 -0
- package/dist/services/api-key.service.js.map +1 -0
- package/dist/services/authentication.service.d.ts +19 -1
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +31 -5
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +6 -0
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/encryption.service.d.ts +8 -0
- package/dist/services/encryption.service.d.ts.map +1 -0
- package/dist/services/encryption.service.js +75 -0
- package/dist/services/encryption.service.js.map +1 -0
- package/dist/services/export-transaction.service.d.ts.map +1 -1
- package/dist/services/export-transaction.service.js +0 -23
- package/dist/services/export-transaction.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts +1 -3
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +6 -13
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/file/disk-file.service.d.ts +1 -0
- package/dist/services/file/disk-file.service.d.ts.map +1 -1
- package/dist/services/file/disk-file.service.js +11 -3
- package/dist/services/file/disk-file.service.js.map +1 -1
- package/dist/services/media.service.d.ts +0 -1
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +10 -11
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/setting.service.d.ts +1 -0
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +35 -7
- package/dist/services/setting.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +12 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +7 -3
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/sso-code-storage.service.d.ts +15 -0
- package/dist/services/sso-code-storage.service.d.ts.map +1 -0
- package/dist/services/sso-code-storage.service.js +47 -0
- package/dist/services/sso-code-storage.service.js.map +1 -0
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +3 -2
- 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 +10 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +1 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/package.json +1 -1
- package/src/controllers/authentication.controller.ts +59 -3
- package/src/dtos/create-api-key.dto.ts +14 -0
- package/src/dtos/post-chatter-message.dto.ts +4 -0
- package/src/dtos/register-private.dto.ts +5 -14
- package/src/dtos/sso-exchange.dto.ts +7 -0
- package/src/dtos/update-api-key.dto.ts +9 -0
- package/src/entities/agent-event.entity.ts +21 -55
- package/src/entities/agent-session.entity.ts +15 -47
- package/src/entities/field-metadata.entity.ts +1 -1
- package/src/entities/legacy-common.entity.ts +15 -15
- package/src/entities/setting.entity.ts +3 -0
- package/src/entities/sms-template.entity.ts +3 -2
- package/src/entities/user-api-key.entity.ts +37 -0
- package/src/entities/user.entity.ts +8 -0
- package/src/enums/auth-type.enum.ts +1 -0
- package/src/guards/api-key.guard.ts +32 -0
- package/src/guards/authentication.guard.ts +6 -3
- package/src/helpers/bootstrap.helper.ts +16 -1
- package/src/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.ts +17 -6
- package/src/helpers/typeorm-db-helper.ts +11 -0
- package/src/index.ts +2 -0
- package/src/interfaces.ts +16 -0
- package/src/jobs/database/chatter-queue-publisher-database.service.ts +1 -1
- package/src/jobs/database/chatter-queue-subscriber-database.service.ts +1 -1
- package/src/jobs/rabbitmq/chatter-queue-publisher.service.ts +1 -15
- package/src/jobs/rabbitmq/chatter-queue-subscriber.service.ts +1 -1
- package/src/jobs/redis/chatter-queue-subscriber-redis.service.ts +1 -1
- package/src/repository/user-api-key.repository.ts +17 -0
- package/src/seeders/module-test-data.service.ts +165 -6
- package/src/seeders/seed-data/solid-core-metadata.json +287 -197
- package/src/services/api-key.service.ts +111 -0
- package/src/services/authentication.service.ts +35 -3
- package/src/services/chatter-message.service.ts +7 -0
- package/src/services/encryption.service.ts +43 -0
- package/src/services/export-transaction.service.ts +0 -26
- package/src/services/field-metadata.service.ts +5 -12
- package/src/services/file/disk-file.service.ts +15 -7
- package/src/services/media.service.ts +12 -51
- package/src/services/setting.service.ts +38 -9
- package/src/services/settings/default-settings-provider.service.ts +7 -3
- package/src/services/sso-code-storage.service.ts +36 -0
- package/src/services/user.service.ts +3 -2
- package/src/solid-core.module.ts +10 -0
- package/src/subscribers/audit.subscriber.ts +1 -1
|
@@ -90,6 +90,21 @@ export async function bootstrapSolidApp(
|
|
|
90
90
|
// Security headers
|
|
91
91
|
app.use(helmet(buildDefaultSecurityHeaderOptions()));
|
|
92
92
|
|
|
93
|
+
// Nest's Swagger UI HTML injects inline styles; keep CSP strict elsewhere.
|
|
94
|
+
const isSwaggerPath = (path: string) =>
|
|
95
|
+
path === '/docs' ||
|
|
96
|
+
path === '/docs/' ||
|
|
97
|
+
path.startsWith('/docs/') ||
|
|
98
|
+
path === '/docs-json' ||
|
|
99
|
+
path === '/docs-yaml';
|
|
100
|
+
|
|
101
|
+
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
102
|
+
if (isSwaggerPath(req.path)) {
|
|
103
|
+
res.removeHeader('Content-Security-Policy');
|
|
104
|
+
}
|
|
105
|
+
next();
|
|
106
|
+
});
|
|
107
|
+
|
|
93
108
|
// Permissions-Policy header
|
|
94
109
|
app.use((_req: Request, res: Response, next: NextFunction) => {
|
|
95
110
|
res.setHeader('Permissions-Policy', buildPermissionsPolicyHeader(permissionsPolicyOverrides));
|
|
@@ -127,7 +142,7 @@ export async function bootstrapSolidApp(
|
|
|
127
142
|
|
|
128
143
|
// Swagger
|
|
129
144
|
if (swagger !== false) {
|
|
130
|
-
const { title =
|
|
145
|
+
const { title = process.env.SOLID_APP_NAME, description = process.env.SOLID_APP_DESCRIPTION, version = '1.0' } = swagger;
|
|
131
146
|
const swaggerConfig = new DocumentBuilder()
|
|
132
147
|
.setTitle(title)
|
|
133
148
|
.setDescription(description)
|
|
@@ -128,14 +128,25 @@ export class SelectionDynamicFieldCrudManager implements FieldCrudManager {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
private providerInstance<T extends ISelectionProviderContext>(selectionDynamicProvider: string): ISelectionProvider<T> {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
const providers = this.options.discoveryService.getProviders();
|
|
132
|
+
|
|
133
|
+
const byToken = providers.find((p) => p.name === selectionDynamicProvider);
|
|
134
|
+
if (byToken) {
|
|
135
|
+
return byToken.instance as ISelectionProvider<T>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const byName = providers.find((p) => {
|
|
139
|
+
try {
|
|
140
|
+
return typeof p.instance?.name === 'function' && p.instance.name() === selectionDynamicProvider;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!byName) {
|
|
136
147
|
throw new Error(`Provider for ${selectionDynamicProvider} not found`);
|
|
137
148
|
}
|
|
138
|
-
return
|
|
149
|
+
return byName.instance as ISelectionProvider<T>;
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
private isApplyRequiredValidation(): boolean {
|
|
@@ -32,6 +32,14 @@ const SIMPLE_JSON_LARGE_TEXT_MAP: Record<DatasourceType, ColumnOptions> = {
|
|
|
32
32
|
[DatasourceType.oracle]: { type: "clob" },
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
const DECIMAL_MAP: Record<DatasourceType, ColumnOptions> = {
|
|
36
|
+
[DatasourceType.postgres]: { type: "float4" },
|
|
37
|
+
[DatasourceType.mssql]: { type: "float" },
|
|
38
|
+
[DatasourceType.mysql]: { type: "float" },
|
|
39
|
+
[DatasourceType.mariadb]: { type: "float" },
|
|
40
|
+
[DatasourceType.oracle]: { type: "float" },
|
|
41
|
+
};
|
|
42
|
+
|
|
35
43
|
const solidCoreDbType: DatasourceType =
|
|
36
44
|
Object.values(DatasourceType).includes(process.env.SOLID_CORE_DB_TYPE as DatasourceType)
|
|
37
45
|
? (process.env.SOLID_CORE_DB_TYPE as DatasourceType)
|
|
@@ -46,6 +54,9 @@ export function getColumnType(solidType: string): ColumnOptions {
|
|
|
46
54
|
case "simpleJsonLargeText":
|
|
47
55
|
return SIMPLE_JSON_LARGE_TEXT_MAP[solidCoreDbType];
|
|
48
56
|
|
|
57
|
+
case "decimal":
|
|
58
|
+
return DECIMAL_MAP[solidCoreDbType];
|
|
59
|
+
|
|
49
60
|
default:
|
|
50
61
|
return {};
|
|
51
62
|
}
|
package/src/index.ts
CHANGED
|
@@ -126,6 +126,7 @@ export * from './entities/permission-metadata.entity'
|
|
|
126
126
|
export * from './entities/role-metadata.entity'
|
|
127
127
|
export * from './entities/sms-template.entity'
|
|
128
128
|
export * from './entities/user.entity'
|
|
129
|
+
export * from './entities/user-api-key.entity'
|
|
129
130
|
export * from './entities/view-metadata.entity'
|
|
130
131
|
export * from './entities/setting.entity'
|
|
131
132
|
export * from './entities/saved-filters.entity'
|
|
@@ -314,6 +315,7 @@ export * from './services/user.service'
|
|
|
314
315
|
export * from './services/view-metadata.service'
|
|
315
316
|
export * from './services/whatsapp/Msg91WhatsappService' //rename
|
|
316
317
|
export * from './services/setting.service'
|
|
318
|
+
export * from './services/encryption.service'
|
|
317
319
|
export * from './services/info.service'
|
|
318
320
|
export * from './controllers/info.controller'
|
|
319
321
|
export * from './services/settings/default-settings-provider.service'
|
package/src/interfaces.ts
CHANGED
|
@@ -70,6 +70,7 @@ export interface SettingDefinition<T = any> {
|
|
|
70
70
|
key: string;
|
|
71
71
|
value: T;
|
|
72
72
|
level: SettingLevel;
|
|
73
|
+
encrypted?: boolean;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
// solid-core/settings/settings-provider.interface.ts
|
|
@@ -395,3 +396,18 @@ export interface AwsS3Config {
|
|
|
395
396
|
|
|
396
397
|
// Prevents inference so callers must provide explicit type arguments; reusable for other APIs.
|
|
397
398
|
export type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
399
|
+
|
|
400
|
+
export type AuditEventType = 'insert' | 'update' | 'delete';
|
|
401
|
+
|
|
402
|
+
export interface AuditQueuePayload {
|
|
403
|
+
eventType: AuditEventType;
|
|
404
|
+
modelName: string;
|
|
405
|
+
entityId: string | number | null;
|
|
406
|
+
occurredAt: string;
|
|
407
|
+
after?: any;
|
|
408
|
+
before?: any;
|
|
409
|
+
updatedColumnNames?: string[];
|
|
410
|
+
userId?: number | null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
@@ -4,7 +4,7 @@ import { DatabasePublisher } from 'src/services/queues/database-publisher.servic
|
|
|
4
4
|
import { MqMessageQueueService } from '../../services/mq-message-queue.service';
|
|
5
5
|
import { MqMessageService } from '../../services/mq-message.service';
|
|
6
6
|
import { QueuesModuleOptions } from "../../interfaces";
|
|
7
|
-
import { AuditQueuePayload } from '
|
|
7
|
+
import { AuditQueuePayload } from '../../interfaces';
|
|
8
8
|
import chatterQueueOptionsDatabase from './chatter-queue-options-database';
|
|
9
9
|
|
|
10
10
|
@Injectable()
|
|
@@ -6,7 +6,7 @@ import { MqMessageService } from '../../services/mq-message.service';
|
|
|
6
6
|
import { MqMessageQueueService } from '../../services/mq-message-queue.service';
|
|
7
7
|
import { QueuesModuleOptions } from "../../interfaces";
|
|
8
8
|
import { PollerService } from 'src/services/poller.service';
|
|
9
|
-
import { AuditQueuePayload } from '
|
|
9
|
+
import { AuditQueuePayload } from '../../interfaces';
|
|
10
10
|
import { ChatterMessageService } from 'src/services/chatter-message.service';
|
|
11
11
|
import chatterQueueOptionsDatabase from './chatter-queue-options-database';
|
|
12
12
|
|
|
@@ -4,21 +4,7 @@ import { RabbitMqPublisher } from 'src/services/queues/rabbitmq-publisher.servic
|
|
|
4
4
|
import chatterQueueOptions from './chatter-queue-options';
|
|
5
5
|
import { MqMessageQueueService } from '../../services/mq-message-queue.service';
|
|
6
6
|
import { MqMessageService } from '../../services/mq-message.service';
|
|
7
|
-
import { QueuesModuleOptions } from "../../interfaces";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export type AuditEventType = 'insert' | 'update' | 'delete';
|
|
11
|
-
|
|
12
|
-
export interface AuditQueuePayload {
|
|
13
|
-
eventType: AuditEventType;
|
|
14
|
-
modelName: string; // TypeORM entity class name (e.g. 'Order')
|
|
15
|
-
entityId: string | number | null;
|
|
16
|
-
occurredAt: string; // ISO timestamp, captured at event time
|
|
17
|
-
after?: any; // entity state after operation (insert/update)
|
|
18
|
-
before?: any; // entity state before operation (update/delete)
|
|
19
|
-
updatedColumnNames?: string[]; // propertyNames of changed columns (update only)
|
|
20
|
-
userId?: number | null; // active user captured at event time
|
|
21
|
-
}
|
|
7
|
+
import { AuditQueuePayload, QueuesModuleOptions } from "../../interfaces";
|
|
22
8
|
|
|
23
9
|
@Injectable()
|
|
24
10
|
export class ChatterQueuePublisherRabbitmq extends RabbitMqPublisher<AuditQueuePayload> {
|
|
@@ -6,7 +6,7 @@ import { MqMessageService } from '../../services/mq-message.service';
|
|
|
6
6
|
import { MqMessageQueueService } from '../../services/mq-message-queue.service';
|
|
7
7
|
import { QueuesModuleOptions } from "../../interfaces";
|
|
8
8
|
import chatterQueueOptions from './chatter-queue-options';
|
|
9
|
-
import { AuditQueuePayload } from '
|
|
9
|
+
import { AuditQueuePayload } from '../../interfaces';
|
|
10
10
|
import { ChatterMessageService } from 'src/services/chatter-message.service';
|
|
11
11
|
|
|
12
12
|
@Injectable()
|
|
@@ -6,7 +6,7 @@ import chatterQueueConfig from './chatter-queue-options-redis';
|
|
|
6
6
|
import { MqMessageService } from '../../services/mq-message.service';
|
|
7
7
|
import { MqMessageQueueService } from '../../services/mq-message-queue.service';
|
|
8
8
|
import { QueuesModuleOptions } from "../../interfaces";
|
|
9
|
-
import { AuditQueuePayload } from '
|
|
9
|
+
import { AuditQueuePayload } from '../../interfaces';
|
|
10
10
|
import { ChatterMessageService } from '../../services/chatter-message.service';
|
|
11
11
|
|
|
12
12
|
@Injectable()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { UserApiKey } from 'src/entities/user-api-key.entity';
|
|
3
|
+
import { RequestContextService } from 'src/services/request-context.service';
|
|
4
|
+
import { DataSource } from 'typeorm';
|
|
5
|
+
import { SecurityRuleRepository } from './security-rule.repository';
|
|
6
|
+
import { SolidBaseRepository } from './solid-base.repository';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class UserApiKeyRepository extends SolidBaseRepository<UserApiKey> {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly dataSource: DataSource,
|
|
12
|
+
readonly requestContextService: RequestContextService,
|
|
13
|
+
readonly securityRuleRepository: SecurityRuleRepository,
|
|
14
|
+
) {
|
|
15
|
+
super(UserApiKey, dataSource, requestContextService, securityRuleRepository);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -2,15 +2,19 @@ import { Injectable, Logger } from '@nestjs/common';
|
|
|
2
2
|
import { DiscoveryService, ModuleRef } from '@nestjs/core';
|
|
3
3
|
import { getDataSourceToken } from '@nestjs/typeorm';
|
|
4
4
|
import { classify } from '@angular-devkit/core/src/utils/strings';
|
|
5
|
-
import { DataSource } from 'typeorm';
|
|
5
|
+
import { DataSource, EntityManager } from 'typeorm';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
|
|
9
9
|
import solidCoreMetadata from './seed-data/solid-core-metadata.json';
|
|
10
10
|
import { CreateModuleMetadataDto } from 'src/dtos/create-module-metadata.dto';
|
|
11
11
|
import { CreateModelMetadataDto } from 'src/dtos/create-model-metadata.dto';
|
|
12
|
+
import { MediaStorageProviderType } from 'src/dtos/create-media-storage-provider-metadata.dto';
|
|
12
13
|
import { getDynamicModuleNamesBasedOnMetadata } from 'src/helpers/module.helper';
|
|
13
14
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
15
|
+
import { MediaRepository } from 'src/repository/media.repository';
|
|
16
|
+
import { ModelMetadataService } from 'src/services/model-metadata.service';
|
|
17
|
+
import { getMediaStorageProvider } from 'src/services/mediaStorageProviders';
|
|
14
18
|
|
|
15
19
|
@Injectable()
|
|
16
20
|
export class ModuleTestDataService {
|
|
@@ -185,7 +189,7 @@ export class ModuleTestDataService {
|
|
|
185
189
|
throw new Error('Module metadata missing from test data payload.');
|
|
186
190
|
}
|
|
187
191
|
|
|
188
|
-
// console.log(JSON.stringify(moduleMetadata, null, 2));
|
|
192
|
+
// console.log(JSON.stringify(moduleMetadata, null, 2));
|
|
189
193
|
|
|
190
194
|
const testingData: Array<{ modelUserKey: string; data: Record<string, any> }> = overallMetadata?.testing?.data ?? [];
|
|
191
195
|
if (testingData.length === 0) {
|
|
@@ -243,19 +247,174 @@ export class ModuleTestDataService {
|
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
|
|
250
|
+
// Strip media fields from entity payload — file paths cannot be saved as columns
|
|
251
|
+
const mediaPayload: Record<string, string> = {};
|
|
252
|
+
for (const field of modelDef.fields ?? []) {
|
|
253
|
+
if ((field.type === 'mediaSingle' || field.type === 'mediaMultiple') && payload[field.name] !== undefined) {
|
|
254
|
+
mediaPayload[field.name] = payload[field.name] as string;
|
|
255
|
+
delete payload[field.name];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Strip many-to-many and one-to-many fields — these are resolved post-save via the relation builder
|
|
260
|
+
const multiRelationPayload: Array<{ field: any; userKeys: string[] }> = [];
|
|
261
|
+
for (const field of modelDef.fields ?? []) {
|
|
262
|
+
if (field.type !== 'relation') continue;
|
|
263
|
+
if (field.relationType !== 'many-to-many' && field.relationType !== 'one-to-many') continue;
|
|
264
|
+
|
|
265
|
+
const userKeysProp = `${field.name}UserKeys`;
|
|
266
|
+
if (userKeysProp in payload && Array.isArray(payload[userKeysProp])) {
|
|
267
|
+
multiRelationPayload.push({ field, userKeys: payload[userKeysProp] });
|
|
268
|
+
delete payload[userKeysProp];
|
|
269
|
+
}
|
|
270
|
+
// Remove raw field value if accidentally present
|
|
271
|
+
delete payload[field.name];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Upsert entity, capturing the saved result for post-save steps
|
|
275
|
+
let savedEntity: any;
|
|
246
276
|
const userKeyField = modelDef.userKeyFieldUserKey;
|
|
247
277
|
if (userKeyField && payload[userKeyField] !== undefined) {
|
|
248
278
|
const existing = await entityRepo.findOne({
|
|
249
279
|
where: { [userKeyField]: payload[userKeyField] },
|
|
250
280
|
});
|
|
251
281
|
if (existing) {
|
|
252
|
-
await entityRepo.save(entityRepo.merge(existing, payload));
|
|
253
|
-
|
|
282
|
+
savedEntity = await entityRepo.save(entityRepo.merge(existing, payload));
|
|
283
|
+
} else {
|
|
284
|
+
savedEntity = await entityRepo.save(entityRepo.create(payload));
|
|
254
285
|
}
|
|
286
|
+
} else {
|
|
287
|
+
savedEntity = await entityRepo.save(entityRepo.create(payload));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (multiRelationPayload.length > 0) {
|
|
291
|
+
await this.seedMultiRelations(savedEntity.id, modelUserKey, multiRelationPayload, modelsByName);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (Object.keys(mediaPayload).length > 0) {
|
|
295
|
+
await this.seedEntityMedia(savedEntity.id, modelUserKey, mediaPayload);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async seedMultiRelations(
|
|
301
|
+
entityId: number,
|
|
302
|
+
modelUserKey: string,
|
|
303
|
+
relations: Array<{ field: any; userKeys: string[] }>,
|
|
304
|
+
modelsByName: Map<string, CreateModelMetadataDto>,
|
|
305
|
+
): Promise<void> {
|
|
306
|
+
for (const { field, userKeys } of relations) {
|
|
307
|
+
if (!userKeys.length) continue;
|
|
308
|
+
|
|
309
|
+
const coModelName = field.relationCoModelSingularName;
|
|
310
|
+
const coModelDef = modelsByName.get(coModelName);
|
|
311
|
+
if (!coModelDef) {
|
|
312
|
+
throw new Error(`Relation model "${coModelName}" not found in metadata for field ${modelUserKey}.${field.name}`);
|
|
313
|
+
}
|
|
314
|
+
const coUserKeyField = coModelDef.userKeyFieldUserKey;
|
|
315
|
+
if (!coUserKeyField) {
|
|
316
|
+
throw new Error(`Relation model "${coModelName}" is missing userKeyFieldUserKey, needed to resolve ${modelUserKey}.${field.name}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const coRepo = this.resolveRepository(coModelName);
|
|
320
|
+
const resolvedIds: number[] = [];
|
|
321
|
+
for (const uk of userKeys) {
|
|
322
|
+
const related = typeof coRepo.findOneByUserKey === 'function'
|
|
323
|
+
? await coRepo.findOneByUserKey(uk)
|
|
324
|
+
: await coRepo.findOne({ where: { [coUserKeyField]: uk } });
|
|
325
|
+
if (!related) {
|
|
326
|
+
throw new Error(`Related entity not found: ${coModelName}.${coUserKeyField}=${uk}`);
|
|
327
|
+
}
|
|
328
|
+
resolvedIds.push(related.id);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Load currently associated entities to diff (set semantics — idempotent)
|
|
332
|
+
const existingRelated: any[] = await this.entityManager
|
|
333
|
+
.createQueryBuilder()
|
|
334
|
+
.relation(classify(modelUserKey), field.name)
|
|
335
|
+
.of(entityId)
|
|
336
|
+
.loadMany();
|
|
337
|
+
const existingIds: number[] = existingRelated.map((e) => e.id);
|
|
338
|
+
|
|
339
|
+
const toAdd = resolvedIds.filter((id) => !existingIds.includes(id));
|
|
340
|
+
const toRemove = existingIds.filter((id) => !resolvedIds.includes(id));
|
|
341
|
+
|
|
342
|
+
if (toAdd.length > 0 || toRemove.length > 0) {
|
|
343
|
+
await this.entityManager
|
|
344
|
+
.createQueryBuilder()
|
|
345
|
+
.relation(classify(modelUserKey), field.name)
|
|
346
|
+
.of(entityId)
|
|
347
|
+
.addAndRemove(toAdd, toRemove);
|
|
255
348
|
}
|
|
256
349
|
|
|
257
|
-
|
|
350
|
+
this.logger.debug(`Seeded ${field.relationType} relation ${modelUserKey}.${field.name} entityId=${entityId}: +${toAdd.length} -${toRemove.length}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private async seedEntityMedia(
|
|
355
|
+
entityId: number,
|
|
356
|
+
modelUserKey: string,
|
|
357
|
+
mediaPayload: Record<string, string>,
|
|
358
|
+
): Promise<void> {
|
|
359
|
+
const mediaBasePath = process.env.TEST_UPLOADS_MEDIA_FILE_PATH;
|
|
360
|
+
if (!mediaBasePath) {
|
|
361
|
+
throw new Error('TEST_UPLOADS_MEDIA_FILE_PATH is not set. Cannot seed test media.');
|
|
258
362
|
}
|
|
363
|
+
|
|
364
|
+
const modelMetadata = await this.modelMetadataService.findOneBySingularName(modelUserKey, {
|
|
365
|
+
fields: {
|
|
366
|
+
model: { userKeyField: true },
|
|
367
|
+
mediaStorageProvider: true,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
for (const [fieldName, fileName] of Object.entries(mediaPayload)) {
|
|
372
|
+
if (!fileName) continue;
|
|
373
|
+
|
|
374
|
+
const fieldMetadata = modelMetadata.fields.find((f) => f.name === fieldName);
|
|
375
|
+
if (!fieldMetadata) {
|
|
376
|
+
throw new Error(`Media field "${fieldName}" not found in loaded metadata for model ${modelUserKey}`);
|
|
377
|
+
}
|
|
378
|
+
if (!fieldMetadata.mediaStorageProvider) {
|
|
379
|
+
throw new Error(`Media field "${fieldName}" in model ${modelUserKey} has no storage provider configured`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const storageProviderType = fieldMetadata.mediaStorageProvider.type as MediaStorageProviderType;
|
|
383
|
+
if (storageProviderType !== MediaStorageProviderType.Filesystem) {
|
|
384
|
+
throw new Error(`Test media seeding supports filesystem storage only. Field "${fieldName}" uses "${storageProviderType}".`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Idempotency: skip if media already exists for this entity + field
|
|
388
|
+
const existing = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(
|
|
389
|
+
entityId, fieldMetadata.id, fieldMetadata.model.id,
|
|
390
|
+
);
|
|
391
|
+
if (existing.length > 0) {
|
|
392
|
+
this.logger.debug(`Media already seeded for ${modelUserKey}.${fieldName} entityId=${entityId}, skipping`);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const sourcePath = path.join(mediaBasePath, fileName);
|
|
397
|
+
if (!fs.existsSync(sourcePath)) {
|
|
398
|
+
throw new Error(`Test media file not found: ${sourcePath}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const storageProvider = await getMediaStorageProvider(this.moduleRef, storageProviderType);
|
|
402
|
+
const stream = fs.createReadStream(sourcePath);
|
|
403
|
+
await storageProvider.storeStreams([[stream, fileName]], { id: entityId }, fieldMetadata);
|
|
404
|
+
this.logger.debug(`Seeded media for ${modelUserKey}.${fieldName} entityId=${entityId} file=${fileName}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private get entityManager(): EntityManager {
|
|
409
|
+
return this.moduleRef.get(EntityManager, { strict: false });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private get modelMetadataService(): ModelMetadataService {
|
|
413
|
+
return this.moduleRef.get(ModelMetadataService, { strict: false });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private get mediaRepository(): MediaRepository {
|
|
417
|
+
return this.moduleRef.get(MediaRepository, { strict: false });
|
|
259
418
|
}
|
|
260
419
|
|
|
261
420
|
private resolveRepository(modelUserKey: string): any {
|
|
@@ -308,7 +467,7 @@ export class ModuleTestDataService {
|
|
|
308
467
|
if (!trimmed || trimmed.startsWith('#') || !trimmed.includes('=')) {
|
|
309
468
|
return line;
|
|
310
469
|
}
|
|
311
|
-
const [rawKey
|
|
470
|
+
const [rawKey] = line.split('=');
|
|
312
471
|
const key = rawKey.trim();
|
|
313
472
|
if (!key.endsWith('_DATABASE_NAME')) {
|
|
314
473
|
return line;
|