@solidxai/core 0.1.11-beta.9 → 0.1.12-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 (169) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/dist/controllers/authentication.controller.d.ts +1 -1
  3. package/dist/controllers/chatter-message.controller.d.ts +0 -2
  4. package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
  5. package/dist/controllers/chatter-message.controller.js +0 -16
  6. package/dist/controllers/chatter-message.controller.js.map +1 -1
  7. package/dist/controllers/datasource-introspection.controller.d.ts +8 -0
  8. package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
  9. package/dist/controllers/media.controller.js +0 -2
  10. package/dist/controllers/media.controller.js.map +1 -1
  11. package/dist/guards/access-token.guard.d.ts +5 -1
  12. package/dist/guards/access-token.guard.d.ts.map +1 -1
  13. package/dist/guards/access-token.guard.js +31 -3
  14. package/dist/guards/access-token.guard.js.map +1 -1
  15. package/dist/guards/authentication.guard.d.ts +1 -0
  16. package/dist/guards/authentication.guard.d.ts.map +1 -1
  17. package/dist/guards/authentication.guard.js +16 -1
  18. package/dist/guards/authentication.guard.js.map +1 -1
  19. package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
  20. package/dist/helpers/bootstrap.helper.js +3 -1
  21. package/dist/helpers/bootstrap.helper.js.map +1 -1
  22. package/dist/helpers/environment.helper.d.ts +0 -1
  23. package/dist/helpers/environment.helper.d.ts.map +1 -1
  24. package/dist/helpers/environment.helper.js +0 -4
  25. package/dist/helpers/environment.helper.js.map +1 -1
  26. package/dist/helpers/model-metadata-helper.service.d.ts +1 -2
  27. package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
  28. package/dist/helpers/model-metadata-helper.service.js +2 -6
  29. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  30. package/dist/helpers/module-metadata-helper.service.js +1 -1
  31. package/dist/helpers/module-metadata-helper.service.js.map +1 -1
  32. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
  33. package/dist/helpers/solid-core-error-codes-provider.service.js +19 -0
  34. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
  35. package/dist/helpers/solid-registry.d.ts +1 -2
  36. package/dist/helpers/solid-registry.d.ts.map +1 -1
  37. package/dist/helpers/solid-registry.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/interfaces/active-user-data.interface.d.ts +1 -0
  43. package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
  44. package/dist/interfaces/active-user-data.interface.js.map +1 -1
  45. package/dist/interfaces.d.ts +1 -0
  46. package/dist/interfaces.d.ts.map +1 -1
  47. package/dist/interfaces.js.map +1 -1
  48. package/dist/repository/scheduled-job.repository.d.ts +2 -2
  49. package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
  50. package/dist/repository/scheduled-job.repository.js +3 -6
  51. package/dist/repository/scheduled-job.repository.js.map +1 -1
  52. package/dist/repository/security-rule.repository.d.ts +2 -2
  53. package/dist/repository/security-rule.repository.d.ts.map +1 -1
  54. package/dist/repository/security-rule.repository.js +3 -6
  55. package/dist/repository/security-rule.repository.js.map +1 -1
  56. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  57. package/dist/seeders/module-metadata-seeder.service.js +18 -1
  58. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  59. package/dist/seeders/seed-data/solid-core-metadata.json +62 -34
  60. package/dist/services/active-session-storage.service.d.ts +10 -0
  61. package/dist/services/active-session-storage.service.d.ts.map +1 -0
  62. package/dist/services/active-session-storage.service.js +41 -0
  63. package/dist/services/active-session-storage.service.js.map +1 -0
  64. package/dist/services/authentication.service.d.ts +8 -3
  65. package/dist/services/authentication.service.d.ts.map +1 -1
  66. package/dist/services/authentication.service.js +61 -16
  67. package/dist/services/authentication.service.js.map +1 -1
  68. package/dist/services/chatter-message.service.d.ts +2 -0
  69. package/dist/services/chatter-message.service.d.ts.map +1 -1
  70. package/dist/services/chatter-message.service.js +25 -2
  71. package/dist/services/chatter-message.service.js.map +1 -1
  72. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
  73. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +5 -7
  74. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
  75. package/dist/services/crud-helper.service.d.ts.map +1 -1
  76. package/dist/services/crud-helper.service.js +7 -0
  77. package/dist/services/crud-helper.service.js.map +1 -1
  78. package/dist/services/crud.service.d.ts.map +1 -1
  79. package/dist/services/crud.service.js +1 -0
  80. package/dist/services/crud.service.js.map +1 -1
  81. package/dist/services/datasource-introspection.service.d.ts +10 -0
  82. package/dist/services/datasource-introspection.service.d.ts.map +1 -1
  83. package/dist/services/datasource-introspection.service.js +109 -17
  84. package/dist/services/datasource-introspection.service.js.map +1 -1
  85. package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
  86. package/dist/services/menu-item-metadata.service.js +1 -0
  87. package/dist/services/menu-item-metadata.service.js.map +1 -1
  88. package/dist/services/model-metadata.service.d.ts +2 -0
  89. package/dist/services/model-metadata.service.d.ts.map +1 -1
  90. package/dist/services/model-metadata.service.js +49 -16
  91. package/dist/services/model-metadata.service.js.map +1 -1
  92. package/dist/services/module-metadata.service.d.ts.map +1 -1
  93. package/dist/services/module-metadata.service.js +0 -28
  94. package/dist/services/module-metadata.service.js.map +1 -1
  95. package/dist/services/refresh-token-ids-storage.service.d.ts +6 -1
  96. package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
  97. package/dist/services/refresh-token-ids-storage.service.js +0 -2
  98. package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
  99. package/dist/services/settings/default-settings-provider.service.d.ts +230 -0
  100. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  101. package/dist/services/settings/default-settings-provider.service.js +116 -0
  102. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  103. package/dist/services/solid-ts-morph.service.d.ts +6 -0
  104. package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
  105. package/dist/services/solid-ts-morph.service.js +43 -0
  106. package/dist/services/solid-ts-morph.service.js.map +1 -1
  107. package/dist/solid-core.module.d.ts.map +1 -1
  108. package/dist/solid-core.module.js +2 -0
  109. package/dist/solid-core.module.js.map +1 -1
  110. package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
  111. package/dist/subscribers/audit.subscriber.js +2 -8
  112. package/dist/subscribers/audit.subscriber.js.map +1 -1
  113. package/dist/subscribers/computed-entity-field.subscriber.d.ts +0 -8
  114. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
  115. package/dist/subscribers/computed-entity-field.subscriber.js +3 -36
  116. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
  117. package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
  118. package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
  119. package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
  120. package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
  121. package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
  122. package/dist/subscribers/scheduled-job.subscriber.js +2 -3
  123. package/dist/subscribers/scheduled-job.subscriber.js.map +1 -1
  124. package/dist/subscribers/security-rule.subscriber.d.ts.map +1 -1
  125. package/dist/subscribers/security-rule.subscriber.js +3 -5
  126. package/dist/subscribers/security-rule.subscriber.js.map +1 -1
  127. package/package.json +1 -1
  128. package/src/controllers/chatter-message.controller.ts +7 -7
  129. package/src/controllers/media.controller.ts +1 -1
  130. package/src/guards/access-token.guard.ts +43 -3
  131. package/src/guards/authentication.guard.ts +22 -1
  132. package/src/helpers/bootstrap.helper.ts +9 -3
  133. package/src/helpers/environment.helper.ts +0 -11
  134. package/src/helpers/model-metadata-helper.service.ts +2 -7
  135. package/src/helpers/module-metadata-helper.service.ts +1 -1
  136. package/src/helpers/solid-core-error-codes-provider.service.ts +20 -1
  137. package/src/helpers/solid-registry.ts +1 -7
  138. package/src/index.ts +1 -1
  139. package/src/interfaces/active-user-data.interface.ts +6 -0
  140. package/src/interfaces.ts +1 -0
  141. package/src/repository/scheduled-job.repository.ts +4 -10
  142. package/src/repository/security-rule.repository.ts +4 -10
  143. package/src/seeders/module-metadata-seeder.service.ts +23 -2
  144. package/src/seeders/seed-data/solid-core-metadata.json +62 -34
  145. package/src/services/active-session-storage.service.ts +24 -0
  146. package/src/services/authentication.service.ts +76 -15
  147. package/src/services/chatter-message.service.ts +32 -2
  148. package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +5 -11
  149. package/src/services/crud-helper.service.ts +7 -0
  150. package/src/services/crud.service.ts +1 -0
  151. package/src/services/datasource-introspection.service.ts +176 -16
  152. package/src/services/menu-item-metadata.service.ts +2 -1
  153. package/src/services/model-metadata.service.ts +63 -22
  154. package/src/services/module-metadata.service.ts +0 -36
  155. package/src/services/refresh-token-ids-storage.service.ts +9 -8
  156. package/src/services/settings/default-settings-provider.service.ts +116 -1
  157. package/src/services/solid-ts-morph.service.ts +61 -1
  158. package/src/solid-core.module.ts +2 -0
  159. package/src/subscribers/audit.subscriber.ts +2 -11
  160. package/src/subscribers/computed-entity-field.subscriber.ts +6 -63
  161. package/src/subscribers/created-by-updated-by.subscriber.ts +0 -3
  162. package/src/subscribers/field-metadata.subscriber.ts +0 -3
  163. package/src/subscribers/scheduled-job.subscriber.ts +2 -3
  164. package/src/subscribers/security-rule.subscriber.ts +3 -8
  165. package/dist/helpers/nodemon-heartbeat.d.ts +0 -5
  166. package/dist/helpers/nodemon-heartbeat.d.ts.map +0 -1
  167. package/dist/helpers/nodemon-heartbeat.js +0 -67
  168. package/dist/helpers/nodemon-heartbeat.js.map +0 -1
  169. package/src/helpers/nodemon-heartbeat.ts +0 -59
@@ -2,23 +2,26 @@ import type { SolidCoreSetting } from "src/services/settings/default-settings-pr
2
2
  import {
3
3
  CanActivate,
4
4
  ExecutionContext,
5
- Inject,
6
5
  Injectable,
7
6
  UnauthorizedException,
8
7
  } from '@nestjs/common';
9
8
  import { JwtService } from '@nestjs/jwt';
10
9
  import { Request } from 'express';
10
+ import { ERROR_MESSAGES } from "src/constants/error-messages";
11
11
  import { ActiveUserData } from '../interfaces/active-user-data.interface';
12
12
  import { REQUEST_USER_KEY } from "../constants";
13
13
  import { PermissionMetadataService } from '../services/permission-metadata.service';
14
14
  import { ClsService } from 'nestjs-cls';
15
+ import { ActiveSessionStorageService } from "../services/active-session-storage.service";
15
16
  import { SettingService } from '../services/setting.service';
17
+ import { createHash } from "crypto";
16
18
 
17
19
  @Injectable()
18
20
  export class AccessTokenGuard implements CanActivate {
19
21
  constructor(
20
22
  private readonly jwtService: JwtService,
21
23
  private readonly permissionsService: PermissionMetadataService,
24
+ private readonly activeSessionStorage: ActiveSessionStorageService,
22
25
  private readonly settingService: SettingService,
23
26
  private readonly cls: ClsService
24
27
  ) { }
@@ -44,6 +47,9 @@ export class AccessTokenGuard implements CanActivate {
44
47
  jwtConfiguration
45
48
  );
46
49
 
50
+ // Prevent Concurrent Login Feature
51
+ await this.validateConcurrentLoginSession(payload, token);
52
+
47
53
  // Load permissions given the user.
48
54
  const permissions = await this.permissionsService.findAllUsingRoles(payload.roles);
49
55
  payload.permissions = permissions.map((permission) => permission.name);
@@ -52,8 +58,10 @@ export class AccessTokenGuard implements CanActivate {
52
58
  this.cls.set(REQUEST_USER_KEY, payload);
53
59
  // console.log(`About to set payload in the request user key:`);
54
60
  // console.log(payload);
55
- } catch {
56
- throw new UnauthorizedException();
61
+ } catch (err) {
62
+ throw err instanceof UnauthorizedException
63
+ ? err
64
+ : new UnauthorizedException();
57
65
  }
58
66
  return true;
59
67
  }
@@ -64,4 +72,36 @@ export class AccessTokenGuard implements CanActivate {
64
72
  const [_, token] = request.headers.authorization?.split(' ') ?? [];
65
73
  return token;
66
74
  }
75
+
76
+ private resolveSessionId(payload: ActiveUserData, token: string): string {
77
+ if (payload.sessionId) {
78
+ return payload.sessionId;
79
+ }
80
+
81
+ // Legacy tokens (issued before preventConcurrentLogins was enabled)
82
+ // have no sessionId claim, so derive a stable fallback from the token.
83
+ return createHash('sha256').update(token).digest('hex');
84
+ }
85
+
86
+ private async validateConcurrentLoginSession(payload: ActiveUserData, token: string,): Promise<void> {
87
+ if (!this.settingService.getConfigValue<SolidCoreSetting>("preventConcurrentLogins",)) {
88
+ return;
89
+ }
90
+
91
+ const activeSessionId = await this.activeSessionStorage.getActiveSession(payload.sub);
92
+ const currentSessionId = this.resolveSessionId(payload, token);
93
+
94
+ if (!activeSessionId) {
95
+ // No active session recorded yet for this user — this is the first
96
+ // request we've seen since the feature was enabled (or storage was
97
+ // cleared). Adopt this request's session as the active one so this
98
+ // browser stays logged in going forward.
99
+ await this.activeSessionStorage.setActiveSession(payload.sub, currentSessionId,);
100
+ return;
101
+ }
102
+
103
+ if (currentSessionId !== activeSessionId) {
104
+ throw new UnauthorizedException(ERROR_MESSAGES.SESSION_INVALID);
105
+ }
106
+ }
67
107
  }
@@ -32,6 +32,22 @@ export class AuthenticationGuard implements CanActivate {
32
32
  private readonly cls: ClsService,
33
33
  ) { }
34
34
 
35
+ private isGenericUnauthorizedError(error: unknown): boolean {
36
+ if (!(error instanceof UnauthorizedException)) {
37
+ return false;
38
+ }
39
+
40
+ const response = error.getResponse();
41
+ const message =
42
+ typeof response === 'string'
43
+ ? response
44
+ : Array.isArray((response as any)?.message)
45
+ ? (response as any).message[0]
46
+ : (response as any)?.message;
47
+
48
+ return !message || message === 'Unauthorized';
49
+ }
50
+
35
51
  async canActivate(context: ExecutionContext): Promise<boolean> {
36
52
  // If method marked as public, then we return with true, else go ahead and apply the access token guard.
37
53
  const contextLog = context.getHandler();
@@ -72,7 +88,12 @@ export class AuthenticationGuard implements CanActivate {
72
88
  const canActivate = await Promise.resolve(
73
89
  instance.canActivate(context),
74
90
  ).catch((err) => {
75
- error = err;
91
+ if (
92
+ this.isGenericUnauthorizedError(error) ||
93
+ !this.isGenericUnauthorizedError(err)
94
+ ) {
95
+ error = err;
96
+ }
76
97
  });
77
98
 
78
99
  if (canActivate) {
@@ -1,5 +1,5 @@
1
- import { ValidationPipe } from '@nestjs/common';
2
- import { NestFactory } from '@nestjs/core';
1
+ import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
2
+ import { NestFactory, Reflector } from '@nestjs/core';
3
3
  import { WsAdapter } from '@nestjs/platform-ws';
4
4
  import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
5
5
  import { NextFunction, Request, Response } from 'express';
@@ -169,7 +169,13 @@ export async function bootstrapSolidApp(
169
169
  }
170
170
 
171
171
  // Global interceptor
172
- app.useGlobalInterceptors(new WrapResponseInterceptor());
172
+ app.useGlobalInterceptors(
173
+ new ClassSerializerInterceptor(app.get(Reflector), {
174
+ enableImplicitConversion: true, // optional but handy
175
+ // groups: ['client'], // if you use groups
176
+ }),
177
+ new WrapResponseInterceptor()
178
+ );
173
179
 
174
180
  // CORS
175
181
  app.enableCors(buildDefaultCorsOptions());
@@ -13,15 +13,4 @@ export function isRedisConfigured(configService: ConfigService): boolean {
13
13
  const host = configService.get<string>('REDIS_HOST');
14
14
  const port = configService.get<string>('REDIS_PORT');
15
15
  return host && port && isNumber(parseInt(port));
16
- }
17
-
18
- /**
19
- * Returns true when the project is configured to use an embedded PGlite
20
- * database with a single-connection pool. Several TypeORM subscriber and
21
- * code-generation workarounds are only necessary (and only applied) under
22
- * this constraint; on a regular multi-connection Postgres pool the original
23
- * behaviour is preserved.
24
- */
25
- export function isEmbeddedDb(): boolean {
26
- return process.env.DEFAULT_DATABASE_DRIVER === 'pglite';
27
16
  }
@@ -4,10 +4,8 @@ import { forwardRef, Inject, Injectable, Logger } from "@nestjs/common";
4
4
  import { _ } from "lodash";
5
5
  import { LEGACY_TABLE_FIELDS_PREFIX } from "src/entities/legacy-common.entity-with-existing-id.entity";
6
6
  import { LegacyTableType } from "src/enums/legacy-table-type.enum";
7
- import { ModelMetadata } from "src/entities/model-metadata.entity";
8
7
  import { ModelMetadataRepository } from "src/repository/model-metadata.repository";
9
8
  import { SolidRegistry } from "./solid-registry";
10
- import { EntityManager } from "typeorm";
11
9
 
12
10
  @Injectable()
13
11
  export class ModelMetadataHelperService {
@@ -145,11 +143,8 @@ export class ModelMetadataHelperService {
145
143
  return systemFieldsMetadata;
146
144
  }
147
145
 
148
- async loadFieldHierarchy(modelName: any, manager?: EntityManager) {
149
- const repo = manager
150
- ? manager.getRepository(ModelMetadata)
151
- : this.modelMetadataRepo;
152
- const model = await repo.findOne({
146
+ async loadFieldHierarchy(modelName: any) {
147
+ const model = await this.modelMetadataRepo.findOne({
153
148
  where: {
154
149
  singularName: modelName,
155
150
  },
@@ -34,7 +34,7 @@ export class ModuleMetadataHelperService {
34
34
  if (!moduleName) {
35
35
  return '';
36
36
  }
37
- return path.resolve(process.cwd(), 'src', moduleName);
37
+ return path.resolve(process.cwd(), 'src', kebabCase(moduleName));
38
38
  }
39
39
 
40
40
  async getSolidUiModulePath(moduleName: string): Promise<string> {
@@ -1,5 +1,6 @@
1
1
  // src/common/errors/providers/solidcore-error-code.provider.ts
2
2
  import { Injectable } from '@nestjs/common';
3
+ import { ERROR_MESSAGES } from 'src/constants/error-messages';
3
4
  import { ErrorCodeProvider } from 'src/decorators/error-codes-provider.decorator';
4
5
  import { ErrorMeta, ErrorRule, IErrorCodeProvider } from 'src/interfaces';
5
6
 
@@ -13,6 +14,24 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
13
14
 
14
15
  rules(): ReadonlyArray<ErrorRule> {
15
16
  return [
17
+ {
18
+ code: 'solidx-session-invalid',
19
+ priority: 110,
20
+ match: (txt) => txt.includes(ERROR_MESSAGES.SESSION_INVALID.toLowerCase()),
21
+ meta: {
22
+ message: ERROR_MESSAGES.SESSION_INVALID,
23
+ httpStatus: 401,
24
+ },
25
+ },
26
+ {
27
+ code: 'solidx-session-expired',
28
+ priority: 110,
29
+ match: (txt) => txt.includes(ERROR_MESSAGES.SESSION_EXPIRED.toLowerCase()),
30
+ meta: {
31
+ message: ERROR_MESSAGES.SESSION_EXPIRED,
32
+ httpStatus: 401,
33
+ },
34
+ },
16
35
  {
17
36
  code: 'solidx-mcp-server-unavailable',
18
37
  priority: 100,
@@ -60,4 +79,4 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
60
79
  const rule = this.rules().find((r) => r.code === code);
61
80
  return rule?.meta;
62
81
  }
63
- }
82
+ }
@@ -8,7 +8,7 @@ import { Locale } from 'src/entities/locale.entity';
8
8
  import { SecurityRule } from 'src/entities/security-rule.entity';
9
9
  import { IScheduledJob } from 'src/services/scheduled-jobs/scheduled-job.interface';
10
10
  import { IDashboardWidgetDataProvider, IErrorCodeProvider, ISecurityRuleConfigProvider, ISelectionProvider, ISelectionProviderContext, ISolidDatabaseModule } from "../interfaces";
11
- import { EntityManager, ObjectLiteral } from 'typeorm';
11
+ import { ObjectLiteral } from 'typeorm';
12
12
 
13
13
  type ControllerMetadata = {
14
14
  name: string;
@@ -49,12 +49,6 @@ export interface TypeOrmEventContext {
49
49
  metadataName?: string;
50
50
  updatedColumns?: string[];
51
51
  updatedRelations?: string[];
52
- // Transaction-bound EntityManager from the originating TypeORM event. Providers that
53
- // need to query the DB during a pre-compute hook must use this (not an injected
54
- // DataSource/EntityManager) so the query runs on the active transaction's connection.
55
- // On single-threaded embedded engines (PGlite), a second connection mid-transaction
56
- // can deadlock.
57
- manager?: EntityManager;
58
52
  }
59
53
 
60
54
  export interface ComputedFieldMetadata<TContext = any> {
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export * from './commands/run-tests.command'
8
8
  export * from './commands/test.command'
9
9
 
10
10
  export * from './config/cache.options'
11
+ export * from './services/active-session-storage.service'
11
12
 
12
13
  export * from './decorators/active-user.decorator'
13
14
  export * from './decorators/solid-request-context.decorator'
@@ -171,7 +172,6 @@ export * from './guards/permissions.guard'
171
172
  export * from './guards/roles.guard'
172
173
 
173
174
  export * from './helpers/command.service'
174
- export * from './helpers/nodemon-heartbeat'
175
175
  export * from './helpers/module.helper'
176
176
  export * from './helpers/schematic.service'
177
177
  export * from './helpers/solid-registry'
@@ -16,6 +16,12 @@ export interface ActiveUserData {
16
16
  */
17
17
  email: string;
18
18
 
19
+ /**
20
+ * Logical login session identifier used to invalidate older sessions when
21
+ * concurrent logins are disabled.
22
+ */
23
+ sessionId?: string;
24
+
19
25
  /**
20
26
  * The subject's (user) roles.
21
27
  * These are part of the JWT token, we simply decode them.
package/src/interfaces.ts CHANGED
@@ -89,6 +89,7 @@ export interface SettingDefinition<T = any> {
89
89
  encrypted?: boolean;
90
90
  label?: string;
91
91
  description?: string;
92
+ helpText?: string;
92
93
  placeholder?: string;
93
94
  group?: string;
94
95
  sortOrder?: number;
@@ -3,7 +3,7 @@ import { CreateScheduledJobDto } from "src/dtos/create-scheduled-job.dto";
3
3
  import { ModuleMetadata } from "src/entities/module-metadata.entity";
4
4
  import { ScheduledJob } from "src/entities/scheduled-job.entity";
5
5
  import { RequestContextService } from "src/services/request-context.service";
6
- import { DataSource, EntityManager } from "typeorm";
6
+ import { DataSource } from "typeorm";
7
7
  import { SecurityRuleRepository } from "./security-rule.repository";
8
8
  import { SolidBaseRepository } from "./solid-base.repository";
9
9
 
@@ -101,25 +101,19 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
101
101
 
102
102
  /**
103
103
  * Converts an entity to a plain DTO object.
104
- * When manager is provided (e.g. from a TypeORM subscriber event), the
105
- * fallback relation load uses it so the query runs on the active
106
- * transaction's connection instead of the default DataSource pool.
107
104
  */
108
- async toDto(scheduledJob: ScheduledJob, manager?: EntityManager): Promise<CreateScheduledJobDto> {
105
+ async toDto(scheduledJob: ScheduledJob): Promise<CreateScheduledJobDto> {
109
106
  let populatedScheduledJob: ScheduledJob = scheduledJob;
110
107
  // If the scheduled job does not have the module relation loaded, load it
111
108
  if (!scheduledJob.module) {
112
- const repo = manager
113
- ? manager.getRepository(ScheduledJob)
114
- : this;
115
- populatedScheduledJob = await repo.findOne({
109
+ populatedScheduledJob = await this.findOne({
116
110
  where: {
117
111
  id: scheduledJob.id,
118
112
  },
119
113
  relations: {
120
114
  module: true,
121
115
  },
122
- }) ?? scheduledJob;
116
+ });
123
117
  }
124
118
 
125
119
  if (!populatedScheduledJob.module) {
@@ -9,7 +9,7 @@ import { SecurityRule } from 'src/entities/security-rule.entity';
9
9
  import { SolidRegistry } from 'src/helpers/solid-registry';
10
10
  import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
11
11
  import { CrudHelperService } from 'src/services/crud-helper.service';
12
- import { Brackets, DataSource, EntityManager, SelectQueryBuilder } from 'typeorm';
12
+ import { Brackets, DataSource, SelectQueryBuilder } from 'typeorm';
13
13
  import { SolidBaseRepository } from './solid-base.repository';
14
14
 
15
15
  @Injectable()
@@ -84,18 +84,12 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
84
84
  return configString.replace('$activeUserId', activeUser.sub.toString());
85
85
  }
86
86
 
87
- async toDto(securityRule: SecurityRule, manager?: EntityManager): Promise<UpdateSecurityRuleDto> {
87
+ async toDto(securityRule: SecurityRule): Promise<UpdateSecurityRuleDto> {
88
88
  // load the role and model relations for the security rule
89
89
  let populatedSecurityRule: SecurityRule = securityRule;
90
90
  // If the security rule does not have the role and model relations loaded, load them
91
91
  if (!securityRule.role || !securityRule.modelMetadata) {
92
- // When a manager is provided (e.g. from a TypeORM subscriber event),
93
- // use it so the query runs on the active transaction's connection
94
- // instead of trying to acquire a second connection from the pool.
95
- const repo = manager
96
- ? manager.getRepository(SecurityRule)
97
- : this;
98
- populatedSecurityRule = await repo.findOne({
92
+ populatedSecurityRule = await this.findOne({
99
93
  where: {
100
94
  id: securityRule.id,
101
95
  },
@@ -103,7 +97,7 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
103
97
  role: true,
104
98
  modelMetadata: true,
105
99
  },
106
- }) ?? securityRule;
100
+ });
107
101
  }
108
102
 
109
103
  return {
@@ -1536,8 +1536,9 @@ export class ModuleMetadataSeederService {
1536
1536
  const { fields: fieldsMetadata, ...modelMetaDataWithoutFields } = modelMetadata;
1537
1537
 
1538
1538
  // Load and set the parent model if it exists.
1539
+ let parentModel: ModelMetadata | null = null;
1539
1540
  if (modelMetadata.isChild && modelMetadata.parentModelUserKey) {
1540
- const parentModel = await this.getModelByUserKeyCached(modelMetadata.parentModelUserKey, {
1541
+ parentModel = await this.getModelByUserKeyCached(modelMetadata.parentModelUserKey, {
1541
1542
  moduleName: moduleMetadata.name,
1542
1543
  component: 'module-model-fields',
1543
1544
  details: `parentModel=${modelMetadata.parentModelUserKey}`,
@@ -1605,7 +1606,27 @@ export class ModuleMetadataSeederService {
1605
1606
  }
1606
1607
  }
1607
1608
 
1608
- // Now that we have created fields & model update the model to stamp the userKeyField.
1609
+ // If userKeyField wasn't found among this model's own fields (e.g. STI "extension" models
1610
+ // whose userKeyField is a column inherited from the parent entity), look it up on the
1611
+ // parent model by the same name.
1612
+ if (!userKeyField && modelMetadata.isChild && parentModel && userKeyFieldName) {
1613
+ const inheritedUserKeyField = await this.timeOperation('field-find-inherited-user-key', () => fieldMetadataRepo.findOne({
1614
+ where: {
1615
+ model: { id: parentModel.id },
1616
+ name: userKeyFieldName,
1617
+ },
1618
+ }), {
1619
+ moduleName: moduleMetadata.name,
1620
+ component: 'module-model-fields',
1621
+ serviceCall: 'fieldMetadataRepo.findOne',
1622
+ details: `model=${modelMetadata.singularName} parentModel=${modelMetadata.parentModelUserKey} field=${userKeyFieldName}`,
1623
+ });
1624
+ if (inheritedUserKeyField) {
1625
+ userKeyField = inheritedUserKeyField;
1626
+ }
1627
+ }
1628
+
1629
+ // Now that we have created fields & model update the model to stamp the userKeyField.
1609
1630
  if (userKeyField) {
1610
1631
  modelMetaDataWithoutFields['userKeyField'] = userKeyField;
1611
1632
  await this.timeOperation('model-user-key-field-upsert', () => this.modelMetadataService.upsert(modelMetaDataWithoutFields), {