@solidxai/core 0.1.8-beta.9 → 0.1.9-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +197 -0
  2. package/dist/controllers/authentication.controller.d.ts +32 -2
  3. package/dist/controllers/authentication.controller.d.ts.map +1 -1
  4. package/dist/controllers/authentication.controller.js +80 -3
  5. package/dist/controllers/authentication.controller.js.map +1 -1
  6. package/dist/dtos/create-api-key.dto.d.ts +5 -0
  7. package/dist/dtos/create-api-key.dto.d.ts.map +1 -0
  8. package/dist/dtos/create-api-key.dto.js +34 -0
  9. package/dist/dtos/create-api-key.dto.js.map +1 -0
  10. package/dist/dtos/register-private.dto.d.ts +3 -5
  11. package/dist/dtos/register-private.dto.d.ts.map +1 -1
  12. package/dist/dtos/register-private.dto.js +6 -18
  13. package/dist/dtos/register-private.dto.js.map +1 -1
  14. package/dist/dtos/sso-exchange.dto.d.ts +4 -0
  15. package/dist/dtos/sso-exchange.dto.d.ts.map +1 -0
  16. package/dist/dtos/sso-exchange.dto.js +26 -0
  17. package/dist/dtos/sso-exchange.dto.js.map +1 -0
  18. package/dist/dtos/update-api-key.dto.d.ts +4 -0
  19. package/dist/dtos/update-api-key.dto.d.ts.map +1 -0
  20. package/dist/dtos/update-api-key.dto.js +28 -0
  21. package/dist/dtos/update-api-key.dto.js.map +1 -0
  22. package/dist/entities/setting.entity.d.ts +1 -0
  23. package/dist/entities/setting.entity.d.ts.map +1 -1
  24. package/dist/entities/setting.entity.js +5 -1
  25. package/dist/entities/setting.entity.js.map +1 -1
  26. package/dist/entities/user-api-key.entity.d.ts +12 -0
  27. package/dist/entities/user-api-key.entity.d.ts.map +1 -0
  28. package/dist/entities/user-api-key.entity.js +62 -0
  29. package/dist/entities/user-api-key.entity.js.map +1 -0
  30. package/dist/entities/user.entity.d.ts +3 -0
  31. package/dist/entities/user.entity.d.ts.map +1 -1
  32. package/dist/entities/user.entity.js +12 -1
  33. package/dist/entities/user.entity.js.map +1 -1
  34. package/dist/enums/auth-type.enum.d.ts +2 -1
  35. package/dist/enums/auth-type.enum.d.ts.map +1 -1
  36. package/dist/enums/auth-type.enum.js +2 -1
  37. package/dist/enums/auth-type.enum.js.map +1 -1
  38. package/dist/guards/api-key.guard.d.ts +11 -0
  39. package/dist/guards/api-key.guard.d.ts.map +1 -0
  40. package/dist/guards/api-key.guard.js +43 -0
  41. package/dist/guards/api-key.guard.js.map +1 -0
  42. package/dist/guards/authentication.guard.d.ts +4 -2
  43. package/dist/guards/authentication.guard.d.ts.map +1 -1
  44. package/dist/guards/authentication.guard.js +7 -3
  45. package/dist/guards/authentication.guard.js.map +1 -1
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +2 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/interfaces.d.ts +12 -0
  51. package/dist/interfaces.d.ts.map +1 -1
  52. package/dist/interfaces.js.map +1 -1
  53. package/dist/jobs/database/chatter-queue-publisher-database.service.d.ts +1 -1
  54. package/dist/jobs/database/chatter-queue-publisher-database.service.d.ts.map +1 -1
  55. package/dist/jobs/database/chatter-queue-publisher-database.service.js.map +1 -1
  56. package/dist/jobs/database/chatter-queue-subscriber-database.service.d.ts +1 -1
  57. package/dist/jobs/database/chatter-queue-subscriber-database.service.d.ts.map +1 -1
  58. package/dist/jobs/database/chatter-queue-subscriber-database.service.js.map +1 -1
  59. package/dist/jobs/rabbitmq/chatter-queue-publisher.service.d.ts +1 -12
  60. package/dist/jobs/rabbitmq/chatter-queue-publisher.service.d.ts.map +1 -1
  61. package/dist/jobs/rabbitmq/chatter-queue-publisher.service.js.map +1 -1
  62. package/dist/jobs/rabbitmq/chatter-queue-subscriber.service.d.ts +1 -1
  63. package/dist/jobs/rabbitmq/chatter-queue-subscriber.service.d.ts.map +1 -1
  64. package/dist/jobs/rabbitmq/chatter-queue-subscriber.service.js.map +1 -1
  65. package/dist/jobs/redis/chatter-queue-subscriber-redis.service.d.ts +1 -1
  66. package/dist/jobs/redis/chatter-queue-subscriber-redis.service.d.ts.map +1 -1
  67. package/dist/jobs/redis/chatter-queue-subscriber-redis.service.js.map +1 -1
  68. package/dist/repository/user-api-key.repository.d.ts +12 -0
  69. package/dist/repository/user-api-key.repository.d.ts.map +1 -0
  70. package/dist/repository/user-api-key.repository.js +34 -0
  71. package/dist/repository/user-api-key.repository.js.map +1 -0
  72. package/dist/seeders/module-test-data.service.d.ts +3 -0
  73. package/dist/seeders/module-test-data.service.d.ts.map +1 -1
  74. package/dist/seeders/module-test-data.service.js +92 -0
  75. package/dist/seeders/module-test-data.service.js.map +1 -1
  76. package/dist/seeders/seed-data/solid-core-metadata.json +128 -0
  77. package/dist/services/api-key.service.d.ts +20 -0
  78. package/dist/services/api-key.service.d.ts.map +1 -0
  79. package/dist/services/api-key.service.js +98 -0
  80. package/dist/services/api-key.service.js.map +1 -0
  81. package/dist/services/authentication.service.d.ts +19 -1
  82. package/dist/services/authentication.service.d.ts.map +1 -1
  83. package/dist/services/authentication.service.js +31 -5
  84. package/dist/services/authentication.service.js.map +1 -1
  85. package/dist/services/encryption.service.d.ts +8 -0
  86. package/dist/services/encryption.service.d.ts.map +1 -0
  87. package/dist/services/encryption.service.js +75 -0
  88. package/dist/services/encryption.service.js.map +1 -0
  89. package/dist/services/setting.service.d.ts +1 -0
  90. package/dist/services/setting.service.d.ts.map +1 -1
  91. package/dist/services/setting.service.js +35 -7
  92. package/dist/services/setting.service.js.map +1 -1
  93. package/dist/services/settings/default-settings-provider.service.d.ts +12 -0
  94. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  95. package/dist/services/settings/default-settings-provider.service.js +4 -3
  96. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  97. package/dist/services/sso-code-storage.service.d.ts +15 -0
  98. package/dist/services/sso-code-storage.service.d.ts.map +1 -0
  99. package/dist/services/sso-code-storage.service.js +47 -0
  100. package/dist/services/sso-code-storage.service.js.map +1 -0
  101. package/dist/solid-core.module.d.ts.map +1 -1
  102. package/dist/solid-core.module.js +10 -0
  103. package/dist/solid-core.module.js.map +1 -1
  104. package/dist/subscribers/audit.subscriber.d.ts +1 -1
  105. package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
  106. package/dist/subscribers/audit.subscriber.js.map +1 -1
  107. package/dist/testing/contracts/testing-metadata.types.d.ts +14 -0
  108. package/dist/testing/contracts/testing-metadata.types.d.ts.map +1 -1
  109. package/dist/testing/contracts/testing-metadata.types.js.map +1 -1
  110. package/package.json +1 -1
  111. package/src/controllers/authentication.controller.ts +59 -3
  112. package/src/dtos/create-api-key.dto.ts +14 -0
  113. package/src/dtos/register-private.dto.ts +5 -14
  114. package/src/dtos/sso-exchange.dto.ts +7 -0
  115. package/src/dtos/update-api-key.dto.ts +9 -0
  116. package/src/entities/setting.entity.ts +3 -0
  117. package/src/entities/user-api-key.entity.ts +37 -0
  118. package/src/entities/user.entity.ts +8 -0
  119. package/src/enums/auth-type.enum.ts +1 -0
  120. package/src/guards/api-key.guard.ts +32 -0
  121. package/src/guards/authentication.guard.ts +6 -3
  122. package/src/index.ts +2 -0
  123. package/src/interfaces.ts +16 -0
  124. package/src/jobs/database/chatter-queue-publisher-database.service.ts +1 -1
  125. package/src/jobs/database/chatter-queue-subscriber-database.service.ts +1 -1
  126. package/src/jobs/rabbitmq/chatter-queue-publisher.service.ts +1 -15
  127. package/src/jobs/rabbitmq/chatter-queue-subscriber.service.ts +1 -1
  128. package/src/jobs/redis/chatter-queue-subscriber-redis.service.ts +1 -1
  129. package/src/repository/user-api-key.repository.ts +17 -0
  130. package/src/seeders/module-test-data.service.ts +106 -0
  131. package/src/seeders/seed-data/solid-core-metadata.json +128 -0
  132. package/src/services/api-key.service.ts +111 -0
  133. package/src/services/authentication.service.ts +35 -3
  134. package/src/services/encryption.service.ts +43 -0
  135. package/src/services/setting.service.ts +38 -9
  136. package/src/services/settings/default-settings-provider.service.ts +4 -3
  137. package/src/services/sso-code-storage.service.ts +36 -0
  138. package/src/solid-core.module.ts +10 -0
  139. package/src/subscribers/audit.subscriber.ts +1 -1
  140. package/src/testing/contracts/testing-metadata.types.ts +16 -0
@@ -1,17 +1,8 @@
1
- import { IsEmail, IsNotEmpty, IsOptional, MinLength } from 'class-validator';
2
-
3
- export class RegisterPrivateDto {
4
- @IsNotEmpty()
5
- username: string;
6
-
7
- @IsEmail()
8
- @IsNotEmpty()
9
- email: string;
10
-
11
- @IsOptional()
12
- mobile?: string;
1
+ import { IsBoolean, IsOptional } from 'class-validator';
2
+ import { SignUpDto } from './sign-up.dto';
13
3
 
4
+ export class RegisterPrivateDto extends SignUpDto {
5
+ @IsBoolean()
14
6
  @IsOptional()
15
- @MinLength(10)
16
- password?: string;
7
+ isAllowedToGenerateApiKeys?: boolean;
17
8
  }
@@ -0,0 +1,7 @@
1
+ import { IsNotEmpty, IsString } from 'class-validator';
2
+
3
+ export class SsoExchangeDto {
4
+ @IsNotEmpty()
5
+ @IsString()
6
+ code: string;
7
+ }
@@ -0,0 +1,9 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+ import { IsBoolean, IsNotEmpty } from 'class-validator';
3
+
4
+ export class UpdateApiKeyDto {
5
+ @ApiProperty({ example: true })
6
+ @IsBoolean()
7
+ @IsNotEmpty()
8
+ isActive: boolean;
9
+ }
@@ -19,6 +19,9 @@ export class Setting extends CommonEntity {
19
19
  @Column({ name: "level", type: "varchar", nullable: true })
20
20
  level: string;
21
21
 
22
+ @Column({ name: "encrypted", default: false })
23
+ encrypted: boolean;
24
+
22
25
  @Index()
23
26
  @ManyToOne(() => User, { nullable: true })
24
27
  @JoinColumn()
@@ -0,0 +1,37 @@
1
+ import { Exclude, Expose } from "class-transformer";
2
+ import { CommonEntity } from "src/entities/common.entity";
3
+ import { Column, Entity, Index, ManyToOne } from "typeorm";
4
+ import { User } from "./user.entity";
5
+
6
+ @Entity("ss_user_api_key")
7
+ @Exclude()
8
+ export class UserApiKey extends CommonEntity {
9
+
10
+ @Expose()
11
+ @Column()
12
+ name: string;
13
+
14
+ // SHA-256 hash of the raw key — never exposed, same treatment as User.password
15
+ @Index({ unique: true })
16
+ @Column()
17
+ hashedKey: string;
18
+
19
+ @Expose()
20
+ @Column()
21
+ maskedKey: string;
22
+
23
+ @Expose()
24
+ @Column({ default: true })
25
+ isActive: boolean;
26
+
27
+ @Expose()
28
+ @Column({ nullable: true })
29
+ expiresAt: Date;
30
+
31
+ @Expose()
32
+ @Column({ nullable: true })
33
+ lastUsedAt: Date;
34
+
35
+ @ManyToOne(() => User, user => user.apiKeys)
36
+ user: User;
37
+ }
@@ -2,6 +2,7 @@ import { CommonEntity } from "src/entities/common.entity"
2
2
  import { Entity, Column, Index, JoinTable, ManyToMany, OneToMany, TableInheritance } from "typeorm";
3
3
  import { RoleMetadata } from 'src/entities/role-metadata.entity';
4
4
  import { UserViewMetadata } from 'src/entities/user-view-metadata.entity'
5
+ import { UserApiKey } from 'src/entities/user-api-key.entity'
5
6
  import { Exclude, Expose } from "class-transformer";
6
7
 
7
8
  @Entity("ss_user")
@@ -151,4 +152,11 @@ export class User extends CommonEntity {
151
152
  @Expose()
152
153
  _media: any;
153
154
 
155
+ @Column({ default: false })
156
+ @Expose()
157
+ isAllowedToGenerateApiKeys: boolean = false;
158
+
159
+ @OneToMany(() => UserApiKey, key => key.user)
160
+ apiKeys: UserApiKey[];
161
+
154
162
  }
@@ -1,4 +1,5 @@
1
1
  export enum AuthType {
2
2
  Bearer,
3
+ ApiKey,
3
4
  None,
4
5
  }
@@ -0,0 +1,32 @@
1
+ import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
2
+ import { Request } from 'express';
3
+ import { REQUEST_USER_KEY } from 'src/constants';
4
+ import { ApiKeyService } from 'src/services/api-key.service';
5
+ import { ClsService } from 'nestjs-cls';
6
+
7
+ @Injectable()
8
+ export class ApiKeyGuard implements CanActivate {
9
+ constructor(
10
+ private readonly apiKeyService: ApiKeyService,
11
+ private readonly cls: ClsService,
12
+ ) {}
13
+
14
+ async canActivate(context: ExecutionContext): Promise<boolean> {
15
+ const request = context.switchToHttp().getRequest<Request>();
16
+ const rawKey = this.extractKeyFromHeader(request);
17
+
18
+ if (!rawKey) {
19
+ throw new UnauthorizedException();
20
+ }
21
+
22
+ const activeUser = await this.apiKeyService.validate(rawKey);
23
+ request[REQUEST_USER_KEY] = activeUser;
24
+ this.cls.set(REQUEST_USER_KEY, activeUser);
25
+
26
+ return true;
27
+ }
28
+
29
+ private extractKeyFromHeader(request: Request): string | undefined {
30
+ return request.headers['solidx-api-key'] as string | undefined;
31
+ }
32
+ }
@@ -8,23 +8,26 @@ import { Reflector } from '@nestjs/core';
8
8
  import { AUTH_TYPE_KEY } from '../decorators/auth.decorator';
9
9
  import { AuthType } from '../enums/auth-type.enum';
10
10
  import { AccessTokenGuard } from './access-token.guard';
11
+ import { ApiKeyGuard } from './api-key.guard';
11
12
  import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
12
13
  import { PermissionMetadataService } from '../services/permission-metadata.service';
13
14
  import { ClsService } from 'nestjs-cls';
14
15
 
15
16
  @Injectable()
16
17
  export class AuthenticationGuard implements CanActivate {
17
- private static readonly defaultAuthType = AuthType.Bearer;
18
+ private static readonly defaultAuthTypes = [AuthType.Bearer, AuthType.ApiKey];
18
19
  private readonly authTypeGuardMap: Record<
19
20
  AuthType,
20
21
  CanActivate | CanActivate[]> = {
21
22
  [AuthType.Bearer]: this.accessTokenGuard,
23
+ [AuthType.ApiKey]: this.apiKeyGuard,
22
24
  [AuthType.None]: { canActivate: () => true },
23
25
  };
24
26
 
25
27
  constructor(
26
28
  private readonly reflector: Reflector,
27
29
  private readonly accessTokenGuard: AccessTokenGuard,
30
+ private readonly apiKeyGuard: ApiKeyGuard,
28
31
  private readonly permissionService: PermissionMetadataService,
29
32
  private readonly cls: ClsService,
30
33
  ) { }
@@ -49,7 +52,7 @@ export class AuthenticationGuard implements CanActivate {
49
52
  return true;
50
53
  }
51
54
 
52
- // TODO: Check if this permission viz. contextPermission is listed in the Public role.
55
+ // TODO: Check if this permission viz. contextPermission is listed in the Public role.
53
56
  const contextPermission = `${context.getClass().name}.${context.getHandler().name}`;
54
57
 
55
58
  const permissionExistsInRole = await this.permissionService.permissionExistsInRole('Public', contextPermission)
@@ -61,7 +64,7 @@ export class AuthenticationGuard implements CanActivate {
61
64
  const authTypes = this.reflector.getAllAndOverride<AuthType[]>(
62
65
  AUTH_TYPE_KEY,
63
66
  [context.getHandler(), context.getClass()],
64
- ) ?? [AuthenticationGuard.defaultAuthType];
67
+ ) ?? AuthenticationGuard.defaultAuthTypes;
65
68
  const guards = authTypes.map((type) => this.authTypeGuardMap[type]).flat();
66
69
  let error = new UnauthorizedException();
67
70
 
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 '../rabbitmq/chatter-queue-publisher.service';
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 '../rabbitmq/chatter-queue-publisher.service';
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 './chatter-queue-publisher.service';
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 '../rabbitmq/chatter-queue-publisher.service';
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
+ }
@@ -13,8 +13,13 @@ import { MediaStorageProviderType } from 'src/dtos/create-media-storage-provider
13
13
  import { getDynamicModuleNamesBasedOnMetadata } from 'src/helpers/module.helper';
14
14
  import { SolidRegistry } from 'src/helpers/solid-registry';
15
15
  import { MediaRepository } from 'src/repository/media.repository';
16
+ import { PermissionMetadataRepository } from 'src/repository/permission-metadata.repository';
17
+ import { AuthenticationService } from 'src/services/authentication.service';
16
18
  import { ModelMetadataService } from 'src/services/model-metadata.service';
19
+ import { RoleMetadataService } from 'src/services/role-metadata.service';
20
+ import { UserService } from 'src/services/user.service';
17
21
  import { getMediaStorageProvider } from 'src/services/mediaStorageProviders';
22
+ import { TestingRoleSpec, TestingUserSpec } from 'src/testing/contracts/testing-metadata.types';
18
23
 
19
24
  @Injectable()
20
25
  export class ModuleTestDataService {
@@ -191,7 +196,17 @@ export class ModuleTestDataService {
191
196
 
192
197
  // console.log(JSON.stringify(moduleMetadata, null, 2));
193
198
 
199
+ const testingRoles: TestingRoleSpec[] = overallMetadata?.testing?.roles ?? [];
200
+ const testingUsers: TestingUserSpec[] = overallMetadata?.testing?.users ?? [];
194
201
  const testingData: Array<{ modelUserKey: string; data: Record<string, any> }> = overallMetadata?.testing?.data ?? [];
202
+
203
+ if (testingRoles.length > 0) {
204
+ await this.seedTestRoles(testingRoles);
205
+ }
206
+ if (testingUsers.length > 0) {
207
+ await this.seedTestUsers(testingUsers);
208
+ }
209
+
195
210
  if (testingData.length === 0) {
196
211
  this.logger.debug(`No test data found for ${moduleMetadata.name}`);
197
212
  return;
@@ -297,6 +312,97 @@ export class ModuleTestDataService {
297
312
  }
298
313
  }
299
314
 
315
+ private async seedTestRoles(roles: TestingRoleSpec[]): Promise<void> {
316
+ const roleService = this.moduleRef.get(RoleMetadataService, { strict: false });
317
+ if (!roleService) {
318
+ throw new Error('RoleMetadataService not available — cannot seed test roles.');
319
+ }
320
+
321
+ await roleService.createRolesIfNotExists(roles.map((r) => ({ name: r.name } as any)));
322
+
323
+ for (const role of roles) {
324
+ const perms = role.permissions ?? [];
325
+ if (perms.length === 0) continue;
326
+
327
+ if (perms.some((p) => p === '*')) {
328
+ await roleService.addAllPermissionsToRole(role.name);
329
+ this.logger.log(`Bound all permissions to test role "${role.name}"`);
330
+ continue;
331
+ }
332
+
333
+ const expanded = await this.expandPermissionNames(perms);
334
+ if (expanded.length === 0) {
335
+ this.logger.warn(`Test role "${role.name}" has permissions declared but none resolved — skipping binding.`);
336
+ continue;
337
+ }
338
+
339
+ try {
340
+ await roleService.addPermissionsToRole(role.name, expanded);
341
+ } catch (err: any) {
342
+ throw new Error(
343
+ `Failed to bind permissions to test role "${role.name}": ${err?.message ?? err}. ` +
344
+ `Did you run "solid seed" first so controller permissions are registered?`,
345
+ );
346
+ }
347
+ this.logger.log(`Bound ${expanded.length} permissions to test role "${role.name}"`);
348
+ }
349
+ }
350
+
351
+ private async expandPermissionNames(permissions: string[]): Promise<string[]> {
352
+ const permissionRepo = this.moduleRef.get(PermissionMetadataRepository, { strict: false });
353
+ if (!permissionRepo) {
354
+ throw new Error('PermissionMetadataRepository not available — cannot resolve test role permissions.');
355
+ }
356
+
357
+ const exact = new Set<string>();
358
+ const prefixes: string[] = [];
359
+ for (const entry of permissions) {
360
+ if (!entry) continue;
361
+ if (entry.endsWith('.*')) {
362
+ prefixes.push(entry.slice(0, -1));
363
+ } else {
364
+ exact.add(entry);
365
+ }
366
+ }
367
+
368
+ if (prefixes.length > 0) {
369
+ const allPermissions = await permissionRepo.find();
370
+ for (const p of allPermissions) {
371
+ if (prefixes.some((prefix) => p.name.startsWith(prefix))) {
372
+ exact.add(p.name);
373
+ }
374
+ }
375
+ }
376
+
377
+ return Array.from(exact);
378
+ }
379
+
380
+ private async seedTestUsers(users: TestingUserSpec[]): Promise<void> {
381
+ const userService = this.moduleRef.get(UserService, { strict: false });
382
+ const authService = this.moduleRef.get(AuthenticationService, { strict: false });
383
+ if (!userService || !authService) {
384
+ throw new Error('UserService / AuthenticationService not available — cannot seed test users.');
385
+ }
386
+
387
+ for (const user of users) {
388
+ const existing = await userService.findOneByUsername(user.username);
389
+ if (existing) {
390
+ this.logger.debug(`Test user "${user.username}" already exists — skipping signUp.`);
391
+ continue;
392
+ }
393
+
394
+ await authService.signUp({
395
+ username: user.username,
396
+ email: user.email,
397
+ password: user.password,
398
+ fullName: user.fullName,
399
+ mobile: user.mobile,
400
+ roles: user.roles,
401
+ });
402
+ this.logger.log(`Created test user "${user.username}"${user.roles?.length ? ` with roles [${user.roles.join(', ')}]` : ''}`);
403
+ }
404
+ }
405
+
300
406
  private async seedMultiRelations(
301
407
  entityId: number,
302
408
  modelUserKey: string,
@@ -2085,6 +2085,35 @@
2085
2085
  "relationModelModuleName": "solid-core",
2086
2086
  "isSystem": true
2087
2087
  },
2088
+ {
2089
+ "name": "isAllowedToGenerateApiKeys",
2090
+ "displayName": "Is Allowed To Generate API Keys",
2091
+ "type": "boolean",
2092
+ "required": true,
2093
+ "unique": false,
2094
+ "index": false,
2095
+ "private": false,
2096
+ "encrypt": false,
2097
+ "defaultValue": "false",
2098
+ "isSystem": true,
2099
+ "enableAuditTracking": true
2100
+ },
2101
+ {
2102
+ "name": "apiKeys",
2103
+ "displayName": "API Keys",
2104
+ "type": "relation",
2105
+ "required": false,
2106
+ "unique": false,
2107
+ "index": false,
2108
+ "private": false,
2109
+ "encrypt": false,
2110
+ "relationType": "one-to-many",
2111
+ "relationCoModelSingularName": "userApiKey",
2112
+ "relationCoModelFieldName": "user",
2113
+ "relationCreateInverse": true,
2114
+ "relationModelModuleName": "solid-core",
2115
+ "isSystem": true
2116
+ },
2088
2117
  {
2089
2118
  "name": "passwordScheme",
2090
2119
  "displayName": "Password Scheme",
@@ -2129,6 +2158,105 @@
2129
2158
  }
2130
2159
  ]
2131
2160
  },
2161
+ {
2162
+ "singularName": "userApiKey",
2163
+ "tableName": "ss_user_api_key",
2164
+ "pluralName": "userApiKeys",
2165
+ "displayName": "User API Key",
2166
+ "description": "API keys for programmatic server-to-server access",
2167
+ "dataSource": "default",
2168
+ "dataSourceType": "postgres",
2169
+ "userKeyFieldUserKey": "name",
2170
+ "isSystem": true,
2171
+ "fields": [
2172
+ {
2173
+ "name": "name",
2174
+ "displayName": "Name",
2175
+ "type": "shortText",
2176
+ "length": 512,
2177
+ "required": true,
2178
+ "unique": false,
2179
+ "index": false,
2180
+ "private": false,
2181
+ "encrypt": false,
2182
+ "isSystem": true
2183
+ },
2184
+ {
2185
+ "name": "hashedKey",
2186
+ "displayName": "Hashed Key",
2187
+ "type": "shortText",
2188
+ "length": 512,
2189
+ "required": true,
2190
+ "unique": true,
2191
+ "index": true,
2192
+ "private": true,
2193
+ "encrypt": false,
2194
+ "isSystem": true
2195
+ },
2196
+ {
2197
+ "name": "maskedKey",
2198
+ "displayName": "Masked Key",
2199
+ "type": "shortText",
2200
+ "length": 512,
2201
+ "required": true,
2202
+ "unique": false,
2203
+ "index": false,
2204
+ "private": false,
2205
+ "encrypt": false,
2206
+ "isSystem": true
2207
+ },
2208
+ {
2209
+ "name": "isActive",
2210
+ "displayName": "Is Active",
2211
+ "type": "boolean",
2212
+ "required": true,
2213
+ "unique": false,
2214
+ "index": false,
2215
+ "private": false,
2216
+ "encrypt": false,
2217
+ "defaultValue": "true",
2218
+ "isSystem": true
2219
+ },
2220
+ {
2221
+ "name": "expiresAt",
2222
+ "displayName": "Expires At",
2223
+ "type": "datetime",
2224
+ "required": false,
2225
+ "unique": false,
2226
+ "index": false,
2227
+ "private": false,
2228
+ "encrypt": false,
2229
+ "isSystem": true
2230
+ },
2231
+ {
2232
+ "name": "lastUsedAt",
2233
+ "displayName": "Last Used At",
2234
+ "type": "datetime",
2235
+ "required": false,
2236
+ "unique": false,
2237
+ "index": false,
2238
+ "private": false,
2239
+ "encrypt": false,
2240
+ "isSystem": true
2241
+ },
2242
+ {
2243
+ "name": "user",
2244
+ "displayName": "User",
2245
+ "type": "relation",
2246
+ "required": true,
2247
+ "unique": false,
2248
+ "index": false,
2249
+ "private": false,
2250
+ "encrypt": false,
2251
+ "relationType": "many-to-one",
2252
+ "relationCoModelSingularName": "user",
2253
+ "relationCoModelFieldName": "apiKeys",
2254
+ "relationCreateInverse": true,
2255
+ "relationModelModuleName": "solid-core",
2256
+ "isSystem": true
2257
+ }
2258
+ ]
2259
+ },
2132
2260
  {
2133
2261
  "singularName": "userActivityHistory",
2134
2262
  "tableName": "ss_user_activity_history",