@solidxai/core 0.1.12-beta.2 → 0.1.12-beta.3

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 (128) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/CLAUDE.md +26 -0
  3. package/dist/constants/media-file-types.d.ts +5 -0
  4. package/dist/constants/media-file-types.d.ts.map +1 -0
  5. package/dist/constants/media-file-types.js +27 -0
  6. package/dist/constants/media-file-types.js.map +1 -0
  7. package/dist/controllers/media.controller.d.ts +14 -11
  8. package/dist/controllers/media.controller.d.ts.map +1 -1
  9. package/dist/controllers/media.controller.js +72 -33
  10. package/dist/controllers/media.controller.js.map +1 -1
  11. package/dist/dtos/create-media.dto.d.ts +1 -0
  12. package/dist/dtos/create-media.dto.d.ts.map +1 -1
  13. package/dist/dtos/create-media.dto.js +7 -1
  14. package/dist/dtos/create-media.dto.js.map +1 -1
  15. package/dist/dtos/update-media.dto.d.ts +1 -0
  16. package/dist/dtos/update-media.dto.d.ts.map +1 -1
  17. package/dist/dtos/update-media.dto.js +7 -1
  18. package/dist/dtos/update-media.dto.js.map +1 -1
  19. package/dist/entities/media.entity.d.ts +1 -0
  20. package/dist/entities/media.entity.d.ts.map +1 -1
  21. package/dist/entities/media.entity.js +9 -1
  22. package/dist/entities/media.entity.js.map +1 -1
  23. package/dist/enums/auth-type.enum.d.ts +2 -1
  24. package/dist/enums/auth-type.enum.d.ts.map +1 -1
  25. package/dist/enums/auth-type.enum.js +1 -0
  26. package/dist/enums/auth-type.enum.js.map +1 -1
  27. package/dist/guards/authentication.guard.d.ts +3 -1
  28. package/dist/guards/authentication.guard.d.ts.map +1 -1
  29. package/dist/guards/authentication.guard.js +5 -1
  30. package/dist/guards/authentication.guard.js.map +1 -1
  31. package/dist/guards/media-signed-url.guard.d.ts +9 -0
  32. package/dist/guards/media-signed-url.guard.d.ts.map +1 -0
  33. package/dist/guards/media-signed-url.guard.js +49 -0
  34. package/dist/guards/media-signed-url.guard.js.map +1 -0
  35. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  36. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +10 -13
  37. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  38. package/dist/repository/media.repository.d.ts.map +1 -1
  39. package/dist/repository/media.repository.js +2 -0
  40. package/dist/repository/media.repository.js.map +1 -1
  41. package/dist/seeders/seed-data/solid-core-metadata.json +88 -3
  42. package/dist/services/media-download-url.service.d.ts +21 -0
  43. package/dist/services/media-download-url.service.d.ts.map +1 -0
  44. package/dist/services/media-download-url.service.js +67 -0
  45. package/dist/services/media-download-url.service.js.map +1 -0
  46. package/dist/services/media-storage.utils.d.ts +27 -0
  47. package/dist/services/media-storage.utils.d.ts.map +1 -0
  48. package/dist/services/media-storage.utils.js +82 -0
  49. package/dist/services/media-storage.utils.js.map +1 -0
  50. package/dist/services/media.service.d.ts +20 -4
  51. package/dist/services/media.service.d.ts.map +1 -1
  52. package/dist/services/media.service.js +157 -55
  53. package/dist/services/media.service.js.map +1 -1
  54. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +4 -4
  55. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
  56. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +37 -63
  57. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  58. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +5 -5
  59. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  60. package/dist/services/mediaStorageProviders/file-storage-provider.js +37 -38
  61. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  62. package/dist/solid-core.module.d.ts.map +1 -1
  63. package/dist/solid-core.module.js +11 -1
  64. package/dist/solid-core.module.js.map +1 -1
  65. package/dist/subscribers/audit.subscriber.js +1 -1
  66. package/dist/subscribers/audit.subscriber.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/constants/media-file-types.ts +44 -0
  69. package/src/controllers/media.controller.ts +44 -24
  70. package/src/dtos/create-media.dto.ts +8 -3
  71. package/src/dtos/update-media.dto.ts +8 -3
  72. package/src/entities/media.entity.ts +4 -1
  73. package/src/enums/auth-type.enum.ts +1 -0
  74. package/src/guards/authentication.guard.ts +3 -0
  75. package/src/guards/media-signed-url.guard.ts +41 -0
  76. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +12 -24
  77. package/src/repository/media.repository.ts +2 -0
  78. package/src/seeders/seed-data/solid-core-metadata.json +88 -3
  79. package/src/services/1.js +6 -0
  80. package/src/services/media-download-url.service.ts +67 -0
  81. package/src/services/media-storage.utils.ts +79 -0
  82. package/src/services/media.service.ts +189 -59
  83. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +52 -76
  84. package/src/services/mediaStorageProviders/file-storage-provider.ts +44 -48
  85. package/src/solid-core.module.ts +21 -2
  86. package/src/subscribers/audit.subscriber.ts +1 -1
  87. package/dist-tests/api/authenticate.spec.js +0 -119
  88. package/dist-tests/api/authenticate.spec.js.map +0 -1
  89. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
  90. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
  91. package/dist-tests/api/ping.spec.js +0 -21
  92. package/dist-tests/api/ping.spec.js.map +0 -1
  93. package/dist-tests/helpers/auth.js +0 -41
  94. package/dist-tests/helpers/auth.js.map +0 -1
  95. package/dist-tests/helpers/env.js +0 -11
  96. package/dist-tests/helpers/env.js.map +0 -1
  97. package/docs/agent-builder/notebook-lm-prompt-for-agent-builder.md +0 -136
  98. package/docs/agent-builder/team-ready-note-agent-builder.md +0 -305
  99. package/docs/agent-hub-grooming.md +0 -301
  100. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +0 -438
  101. package/docs/dashboards/dashboard-curl-smoke-tests.txt +0 -146
  102. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +0 -172
  103. package/docs/datasource-introspection-ddl-analysis.md +0 -326
  104. package/docs/datasource-introspection-implementation-plan.md +0 -306
  105. package/docs/grouping-enhancements.md +0 -89
  106. package/docs/java-spring/README.md +0 -3
  107. package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
  108. package/docs/module-package-import-handoff.md +0 -691
  109. package/docs/seed-changes.md +0 -65
  110. package/docs/test-data-workflow.md +0 -200
  111. package/docs/type-declaration-import-issue.md +0 -24
  112. package/docs/workflow/business-automation-example-notes.md +0 -309
  113. package/docs/workflow/data-engineering-pipeline-example-notes.md +0 -330
  114. package/docs/workflow/foreach-example-notes.md +0 -187
  115. package/docs/workflow/hello-world-example-notes.md +0 -271
  116. package/docs/workflow/kestra-concepts-plugins-blueprints.md +0 -315
  117. package/docs/workflow/loop-until-example-notes.md +0 -198
  118. package/docs/workflow/microservices-and-apis-example-notes.md +0 -264
  119. package/docs/workflow/samples.md +0 -25
  120. package/docs/workflow/what-is-kestra.md +0 -79
  121. package/docs/workflow/workflow-core-module-handoff-summary.md +0 -191
  122. package/docs/workflow/workflow-module-brd.md +0 -185
  123. package/docs/workflow/workflow-module-domain-model.md +0 -252
  124. package/docs/workflow/workflow-module-metadata-dsl.md +0 -302
  125. package/docs/workflow/workflow-module-technical-spec.md +0 -293
  126. package/docs/workflow/workflow-node-type-contracts.md +0 -635
  127. package/docs/workflow/workflow-node-ui-metadata-contract.md +0 -1262
  128. package/docs/workflow/workflow-solidx-model-field-spec.md +0 -284
@@ -1,29 +1,25 @@
1
1
  import { Injectable, Logger } from "@nestjs/common";
2
- import { ConfigService } from "@nestjs/config";
3
- import { CommonEntity } from "src/entities/common.entity";
4
2
  import { FieldMetadata } from "src/entities/field-metadata.entity";
5
3
  import { Media } from "src/entities/media.entity";
4
+ import { MediaStorageProviderMetadata } from "src/entities/media-storage-provider-metadata.entity";
6
5
  import { MediaStorageProvider } from "src/interfaces";
7
6
  import { MediaRepository } from "src/repository/media.repository";
8
7
  import { DiskFileService } from "src/services/file";
9
8
  import { Readable } from "stream";
10
- import * as path from "path";
11
9
  import * as fs from "fs";
12
10
  import { SettingService } from "../setting.service";
13
- import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
14
- import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
11
+ import { MediaDownloadUrlService } from "src/services/media-download-url.service";
12
+ import { buildDiskMediaPath, buildMediaRecordCreateInput, buildStoredMediaFileName, resolveStoredMediaIsPublic, } from "src/services/media-storage.utils";
15
13
 
16
14
  @Injectable()
17
15
  export class FileStorageProvider<T> implements MediaStorageProvider<T> {
18
16
  private logger = new Logger(FileStorageProvider.name);
19
17
 
20
18
  constructor(
21
- // @Inject(appBuilderConfig.KEY)
22
- // private readonly appBuilderConfiguration: ConfigType<typeof appBuilderConfig>,
23
- private readonly configService: ConfigService,
24
19
  readonly fileService: DiskFileService,
25
20
  readonly mediaRepository: MediaRepository,
26
- private readonly settingService: SettingService
21
+ private readonly settingService: SettingService,
22
+ private readonly mediaDownloadUrlService: MediaDownloadUrlService,
27
23
 
28
24
  ) { }
29
25
 
@@ -32,12 +28,17 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
32
28
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
33
29
  // }
34
30
  //@ts-ignore
35
- const media = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
31
+ const media = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(this.getEntityId(entity), mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
36
32
  // Add the full URL to the media
37
33
  // media.forEach(m => {
38
34
  // });
39
35
  for (const m of media) {
40
- m['_full_url'] = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePath(m.relativeUri)}`;
36
+ const storageProvider = this.resolveMediaStorageProvider(m, mediaFieldMetadata);
37
+ const isPublic = resolveStoredMediaIsPublic(m.isPublic, storageProvider);
38
+ m.isPublic = isPublic;
39
+ m['_full_url'] = isPublic === false
40
+ ? await this.mediaDownloadUrlService.resolveDownloadUrl(m.id, m.relativeUri, storageProvider)
41
+ : await this.fileService.getUrl(buildDiskMediaPath(m.relativeUri, this.settingService, storageProvider));
41
42
  }
42
43
 
43
44
 
@@ -49,24 +50,24 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
49
50
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
50
51
  // }
51
52
  const result: Media[] = [];
53
+ const storageProvider = mediaFieldMetadata.mediaStorageProvider;
52
54
  for (const file of files) {
53
55
  // Store the file in the configured file storage directory
54
- const fileStoragePath = this.getFullFilePath(this.getFileName(file));
56
+ const fileName = buildStoredMediaFileName(file);
57
+ const fileStoragePath = buildDiskMediaPath(fileName, this.settingService, storageProvider);
55
58
  await this.fileService.copy(file.path, fileStoragePath);
56
59
  await this.fileService.delete(file.path);
57
60
 
58
61
  // Create an entry in the media table
59
- const mediaEntity = await this.mediaRepository.createMedia({
60
- //@ts-ignore
61
- entityId: entity.id,
62
- modelMetadataId: mediaFieldMetadata.model.id,
63
- relativeUri: this.getFileName(file),
64
- mimeType: file.mimetype,
65
- fileSize: file.size,
66
- originalFileName: file.originalname,
67
- mediaStorageProviderMetadataId: mediaFieldMetadata.mediaStorageProvider.id,
68
- fieldMetadataId: mediaFieldMetadata.id
69
- }) as unknown as Media;
62
+ const mediaEntity = await this.mediaRepository.createMedia(
63
+ buildMediaRecordCreateInput(this.getEntityId(entity), mediaFieldMetadata, storageProvider, {
64
+ relativeUri: fileName,
65
+ mimeType: file.mimetype,
66
+ fileSize: file.size,
67
+ originalFileName: file.originalname,
68
+ })
69
+ ) as unknown as Media;
70
+ mediaEntity.isPublic = resolveStoredMediaIsPublic(mediaEntity.isPublic, storageProvider);
70
71
  result.push(mediaEntity);
71
72
  this.logger.debug(`Stored media with`, mediaEntity);
72
73
  };
@@ -78,21 +79,21 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
78
79
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
79
80
  // }
80
81
  const result: Media[] = [];
82
+ const storageProvider = mediaFieldMetadata.mediaStorageProvider;
81
83
  for (const pair of streamPairs) {
82
84
  const stream = pair[0];
83
85
  const fileName = pair[1];
84
- const fullPath = this.getFullFilePath(fileName);
86
+ const fullPath = buildDiskMediaPath(fileName, this.settingService, storageProvider);
85
87
  await this.fileService.writeStream(fullPath, stream);
86
88
  const { size: fileSize } = await fs.promises.stat(fullPath);
87
- const mediaEntity = await this.mediaRepository.createMedia({
88
- //@ts-ignore
89
- entityId: entity.id,
90
- modelMetadataId: mediaFieldMetadata.model.id,
91
- relativeUri: fileName,
92
- fileSize,
93
- mediaStorageProviderMetadataId: mediaFieldMetadata.mediaStorageProvider.id,
94
- fieldMetadataId: mediaFieldMetadata.id
95
- }) as unknown as Media;
89
+ const mediaEntity = await this.mediaRepository.createMedia(
90
+ buildMediaRecordCreateInput(this.getEntityId(entity), mediaFieldMetadata, storageProvider, {
91
+ relativeUri: fileName,
92
+ fileSize,
93
+ })
94
+ ) as unknown as Media;
95
+ mediaEntity.isPublic = resolveStoredMediaIsPublic(mediaEntity.isPublic, storageProvider);
96
+ result.push(mediaEntity);
96
97
  this.logger.debug(`Stored media with`, mediaEntity);
97
98
  };
98
99
  return result;
@@ -102,13 +103,13 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
102
103
  // if (!(entity instanceof CommonEntity)) {
103
104
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
104
105
  // }
105
- //@ts-ignore
106
- const existingMedia = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
107
- //@ts-ignore
108
- this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
106
+ const entityId = this.getEntityId(entity);
107
+ const existingMedia = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
108
+ await this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
109
109
 
110
110
  for (const media of existingMedia) {
111
- await this.fileService.delete(this.getFullFilePath(media.relativeUri));
111
+ const storageProvider = this.resolveMediaStorageProvider(media, mediaFieldMetadata);
112
+ await this.fileService.delete(buildDiskMediaPath(media.relativeUri, this.settingService, storageProvider));
112
113
  }
113
114
  // existingMedia.forEach(media => {
114
115
  // });
@@ -118,19 +119,14 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
118
119
  if (!media?.relativeUri) {
119
120
  return;
120
121
  }
121
- await this.fileService.delete(this.getFullFilePath(media.relativeUri));
122
+ await this.fileService.delete(buildDiskMediaPath(media.relativeUri, this.settingService, media.mediaStorageProviderMetadata));
122
123
  }
123
124
 
124
- private getFullFilePath(fileName: string): string {
125
- const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
126
- || DEFAULT_MEDIA_FILE_STORAGE_DIR;
127
- if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
128
- return fileName;
129
- }
130
- return `${base}/${fileName}`;
125
+ private resolveMediaStorageProvider(media: Media, mediaFieldMetadata?: FieldMetadata): MediaStorageProviderMetadata | undefined {
126
+ return media.mediaStorageProviderMetadata || mediaFieldMetadata?.mediaStorageProvider;
131
127
  }
132
128
 
133
- private getFileName(file: Express.Multer.File): string {
134
- return `${file.filename}-${file.originalname}`;
129
+ private getEntityId(entity: T): number {
130
+ return (entity as any).id;
135
131
  }
136
132
  }
@@ -47,6 +47,7 @@ import { ListOfValuesService } from "./services/list-of-values.service";
47
47
  // import { MediaStorageProviderMetadataSeederService } from './services/media-storage-provider-metadata-seeder.service';
48
48
  import { MediaStorageProviderMetadataService } from "./services/media-storage-provider-metadata.service";
49
49
  import { MediaService } from "./services/media.service";
50
+ import { MediaDownloadUrlService } from "./services/media-download-url.service";
50
51
  import { ModelMetadataService } from "./services/model-metadata.service";
51
52
  import { RemovedFieldMigrationService } from "./services/removed-field-migration.service";
52
53
  import { ModuleMetadataExplorerService } from "./services/module-metadata-explorer.service";
@@ -55,9 +56,10 @@ import { ModulePackageService } from "./services/module-package.service";
55
56
  import { SolidIntrospectService } from "./services/solid-introspect.service";
56
57
  // import { ListOfComputedFieldProvider } from './providers/list-of-computed-field-provider.service';
57
58
  import { ServeStaticModule } from "@nestjs/serve-static";
58
- import { join } from "path";
59
+ import { extname, join } from "path";
59
60
  import { RefreshModelCommand } from "./commands/refresh-model.command";
60
61
  import { MediaController } from "./controllers/media.controller";
62
+ import { INLINE_SAFE_EXTENSIONS } from "./constants/media-file-types";
61
63
 
62
64
  import { RefreshModuleCommand } from "./commands/refresh-module.command";
63
65
  import { ModelMetadataSubscriber } from "./subscribers/model-metadata.subscriber";
@@ -106,6 +108,7 @@ import { MqMessage } from "./entities/mq-message.entity";
106
108
  import { SmsTemplate } from "./entities/sms-template.entity";
107
109
  import { AccessTokenGuard } from "./guards/access-token.guard";
108
110
  import { ApiKeyGuard } from "./guards/api-key.guard";
111
+ import { MediaSignedUrlGuard } from "./guards/media-signed-url.guard";
109
112
  import { AuthenticationGuard } from "./guards/authentication.guard";
110
113
  import { PermissionsGuard } from "./guards/permissions.guard";
111
114
  import { SolidRegistry } from "./helpers/solid-registry";
@@ -444,7 +447,7 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
444
447
  rootPath: join(process.cwd(), "media-files-storage"),
445
448
  serveRoot: "/media-files-storage",
446
449
  serveStaticOptions: {
447
- setHeaders: (res /*, path, stat*/) => {
450
+ setHeaders: (res, path) => {
448
451
  // Allow use of these files from a different origin (e.g., :3000 UI)
449
452
  // Use 'same-site' if both origins are on the same site (localhost:* counts as same-site)
450
453
  res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); // or 'same-site'
@@ -452,6 +455,20 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
452
455
  // If you need to load into <canvas> without tainting or fetch images via XHR,
453
456
  // you can also expose CORS here (not needed for simple <img>):
454
457
  // res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
458
+
459
+ // Prevent the browser from ever content-sniffing a served file into something
460
+ // more dangerous than its declared Content-Type (e.g. rendering a mislabeled
461
+ // upload as HTML and executing embedded script).
462
+ res.setHeader("X-Content-Type-Options", "nosniff");
463
+
464
+ // Only a small allowlist of media types is ever safe to render inline. Everything
465
+ // else (including svg, html, and any other uploaded file) is forced to download
466
+ // rather than be displayed/executed by the browser, regardless of what mimetype or
467
+ // extension it was uploaded with.
468
+ if (!INLINE_SAFE_EXTENSIONS.has(extname(path).toLowerCase().replace(/^\./, ''))) {
469
+ res.setHeader("Content-Type", "application/octet-stream");
470
+ res.setHeader("Content-Disposition", "attachment");
471
+ }
455
472
  },
456
473
  },
457
474
  }),
@@ -570,6 +587,7 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
570
587
  SchematicService,
571
588
  MediaStorageProviderMetadataService,
572
589
  MediaService,
590
+ MediaDownloadUrlService,
573
591
  // MediaStorageProviderMetadataSeederService,
574
592
  ModuleMetadataSeederService,
575
593
  ModuleTestDataService,
@@ -654,6 +672,7 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
654
672
  SoftDeleteAwareEventSubscriber,
655
673
  AccessTokenGuard,
656
674
  ApiKeyGuard,
675
+ MediaSignedUrlGuard,
657
676
  ApiKeyService,
658
677
  ActiveSessionStorageService,
659
678
  AuthenticationService,
@@ -106,7 +106,7 @@ export class AuditSubscriber implements EntitySubscriberInterface {
106
106
 
107
107
  const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;
108
108
 
109
- const relationAfter = await this.dataSource.getRepository(event.metadata.target as any).findOne({
109
+ const relationAfter = await event.queryRunner.manager.getRepository(event.metadata.target as any).findOne({
110
110
  where: { id: entityId } as any,
111
111
  relations: relations as any,
112
112
  });
@@ -1,119 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_1 = require("@playwright/test");
4
- const env_1 = require("../helpers/env");
5
- const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
6
- const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
7
- const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
8
- function base64UrlDecode(input) {
9
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
10
- const padded = normalized.length % 4 === 0
11
- ? normalized
12
- : normalized.padEnd(normalized.length + (4 - (normalized.length % 4)), "=");
13
- return Buffer.from(padded, "base64").toString("utf-8");
14
- }
15
- function validateJwt(token) {
16
- const parts = token.split(".");
17
- if (parts.length !== 3) {
18
- throw new Error("JWT must have three dot-separated parts.");
19
- }
20
- const headerJson = JSON.parse(base64UrlDecode(parts[0]));
21
- const payloadJson = JSON.parse(base64UrlDecode(parts[1]));
22
- if (!headerJson || typeof headerJson !== "object") {
23
- throw new Error("JWT header must be a JSON object.");
24
- }
25
- if (!payloadJson || typeof payloadJson !== "object") {
26
- throw new Error("JWT payload must be a JSON object.");
27
- }
28
- if (typeof payloadJson.exp !== "number") {
29
- throw new Error("JWT payload.exp must be a number.");
30
- }
31
- return payloadJson;
32
- }
33
- (0, test_1.test)("API: authenticate succeeds with valid credentials", async () => {
34
- const api = await test_1.request.newContext({
35
- baseURL,
36
- extraHTTPHeaders: {
37
- accept: "*/*",
38
- "content-type": "application/json",
39
- },
40
- });
41
- try {
42
- const res = await api.post("/api/iam/authenticate", {
43
- data: {
44
- email: TEST_USER_EMAIL,
45
- username: "",
46
- password: TEST_USER_PASSWORD,
47
- },
48
- });
49
- (0, test_1.expect)(res.status()).toBe(200);
50
- const json = await res.json();
51
- (0, test_1.expect)(json.statusCode).toBe(200);
52
- (0, test_1.expect)(Array.isArray(json.message)).toBe(true);
53
- (0, test_1.expect)(json.message.length).toBe(0);
54
- (0, test_1.expect)(json.error).toBe("");
55
- const user = json.data?.user;
56
- (0, test_1.expect)(user, "Expected data.user to be an object.").toBeTruthy();
57
- (0, test_1.expect)(typeof user).toBe("object");
58
- const email = user?.email;
59
- (0, test_1.expect)(typeof email).toBe("string");
60
- if (email === TEST_USER_EMAIL) {
61
- (0, test_1.expect)(email).toBe(TEST_USER_EMAIL);
62
- }
63
- else {
64
- (0, test_1.expect)(email.length).toBeGreaterThan(0);
65
- }
66
- (0, test_1.expect)(typeof user?.mobile).toBe("string");
67
- (0, test_1.expect)(typeof user?.username).toBe("string");
68
- (0, test_1.expect)(typeof user?.forcePasswordChange).toBe("boolean");
69
- (0, test_1.expect)(typeof user?.id).toBe("number");
70
- const roles = user?.roles;
71
- (0, test_1.expect)(Array.isArray(roles)).toBe(true);
72
- if (Array.isArray(roles)) {
73
- (0, test_1.expect)(roles.every((role) => typeof role === "string")).toBe(true);
74
- (0, test_1.expect)(roles).toContain("Admin");
75
- }
76
- const accessToken = json.data?.accessToken;
77
- const refreshToken = json.data?.refreshToken;
78
- (0, test_1.expect)(typeof accessToken).toBe("string");
79
- (0, test_1.expect)(typeof refreshToken).toBe("string");
80
- const accessPayload = validateJwt(accessToken);
81
- const refreshPayload = validateJwt(refreshToken);
82
- (0, test_1.expect)(typeof accessPayload.exp).toBe("number");
83
- (0, test_1.expect)(typeof refreshPayload.exp).toBe("number");
84
- }
85
- finally {
86
- await api.dispose();
87
- }
88
- });
89
- (0, test_1.test)("API: authenticate fails with wrong password", async () => {
90
- const api = await test_1.request.newContext({
91
- baseURL,
92
- extraHTTPHeaders: {
93
- accept: "*/*",
94
- "content-type": "application/json",
95
- },
96
- });
97
- try {
98
- const res = await api.post("/api/iam/authenticate", {
99
- data: {
100
- email: TEST_USER_EMAIL,
101
- username: "",
102
- password: `${TEST_USER_PASSWORD}__wrong`,
103
- },
104
- });
105
- (0, test_1.expect)(res.status()).toBe(401);
106
- const json = await res.json();
107
- (0, test_1.expect)(json.statusCode).toBe(401);
108
- (0, test_1.expect)(json.statusCodeMessage).toBe("Unauthorized");
109
- (0, test_1.expect)(json.message).toBe("Invalid credentials");
110
- (0, test_1.expect)(json.error).toBe("Invalid credentials");
111
- (0, test_1.expect)(json.data?.statusCode).toBe(401);
112
- (0, test_1.expect)(json.data?.error).toBe("Unauthorized");
113
- (0, test_1.expect)(json.data?.message).toBe("Invalid credentials");
114
- }
115
- finally {
116
- await api.dispose();
117
- }
118
- });
119
- //# sourceMappingURL=authenticate.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../../tests/api/authenticate.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getRequiredEnv } from \"../helpers/env\";\n\ntype JwtPayload = {\n exp?: number;\n [key: string]: unknown;\n};\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\nconst TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\nconst TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded =\n normalized.length % 4 === 0\n ? normalized\n : normalized.padEnd(\n normalized.length + (4 - (normalized.length % 4)),\n \"=\"\n );\n return Buffer.from(padded, \"base64\").toString(\"utf-8\");\n}\n\nfunction validateJwt(token: string): JwtPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"JWT must have three dot-separated parts.\");\n }\n\n const headerJson = JSON.parse(base64UrlDecode(parts[0]));\n const payloadJson = JSON.parse(base64UrlDecode(parts[1]));\n\n if (!headerJson || typeof headerJson !== \"object\") {\n throw new Error(\"JWT header must be a JSON object.\");\n }\n\n if (!payloadJson || typeof payloadJson !== \"object\") {\n throw new Error(\"JWT payload must be a JSON object.\");\n }\n\n if (typeof payloadJson.exp !== \"number\") {\n throw new Error(\"JWT payload.exp must be a number.\");\n }\n\n return payloadJson as JwtPayload;\n}\n\ntest(\"API: authenticate succeeds with valid credentials\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n expect(json.statusCode).toBe(200);\n expect(Array.isArray(json.message)).toBe(true);\n expect(json.message.length).toBe(0);\n expect(json.error).toBe(\"\");\n\n const user = json.data?.user as Record<string, unknown> | undefined;\n expect(user, \"Expected data.user to be an object.\").toBeTruthy();\n expect(typeof user).toBe(\"object\");\n\n const email = user?.email;\n expect(typeof email).toBe(\"string\");\n if (email === TEST_USER_EMAIL) {\n expect(email).toBe(TEST_USER_EMAIL);\n } else {\n // If your API returns a fixed system email, replace this with an exact match.\n expect((email as string).length).toBeGreaterThan(0);\n }\n\n expect(typeof user?.mobile).toBe(\"string\");\n expect(typeof user?.username).toBe(\"string\");\n expect(typeof user?.forcePasswordChange).toBe(\"boolean\");\n expect(typeof user?.id).toBe(\"number\");\n\n const roles = user?.roles;\n expect(Array.isArray(roles)).toBe(true);\n if (Array.isArray(roles)) {\n expect(roles.every((role) => typeof role === \"string\")).toBe(true);\n expect(roles).toContain(\"Admin\");\n }\n\n const accessToken = json.data?.accessToken;\n const refreshToken = json.data?.refreshToken;\n expect(typeof accessToken).toBe(\"string\");\n expect(typeof refreshToken).toBe(\"string\");\n\n const accessPayload = validateJwt(accessToken as string);\n const refreshPayload = validateJwt(refreshToken as string);\n\n expect(typeof accessPayload.exp).toBe(\"number\");\n expect(typeof refreshPayload.exp).toBe(\"number\");\n } finally {\n await api.dispose();\n }\n});\n\ntest(\"API: authenticate fails with wrong password\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: `${TEST_USER_PASSWORD}__wrong`,\n },\n });\n\n expect(res.status()).toBe(401);\n const json = await res.json();\n\n expect(json.statusCode).toBe(401);\n expect(json.statusCodeMessage).toBe(\"Unauthorized\");\n expect(json.message).toBe(\"Invalid credentials\");\n expect(json.error).toBe(\"Invalid credentials\");\n expect(json.data?.statusCode).toBe(401);\n expect(json.data?.error).toBe(\"Unauthorized\");\n expect(json.data?.message).toBe(\"Invalid credentials\");\n } finally {\n await api.dispose();\n }\n});\n"]}
@@ -1,97 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_1 = require("@playwright/test");
4
- const auth_1 = require("../helpers/auth");
5
- const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
6
- async function getCityByName(name) {
7
- const api = await test_1.request.newContext({
8
- baseURL,
9
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
10
- });
11
- try {
12
- const res = await api.get(`/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`);
13
- (0, test_1.expect)(res.status()).toBe(200);
14
- const json = await res.json();
15
- const record = json?.data?.records?.[0];
16
- (0, test_1.expect)(record, `Expected city '${name}' in testData`).toBeTruthy();
17
- (0, test_1.expect)(record.id, "Expected record to have id").toBeTruthy();
18
- return record;
19
- }
20
- finally {
21
- await api.dispose();
22
- }
23
- }
24
- test_1.test.describe("CRUDService.findOne (cityMaster)", () => {
25
- (0, test_1.test)("returns a city by id", async () => {
26
- const city = await getCityByName("Mumbai");
27
- const api = await test_1.request.newContext({
28
- baseURL,
29
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
30
- });
31
- try {
32
- const res = await api.get(`/api/city-master/${city.id}`);
33
- (0, test_1.expect)(res.status()).toBe(200);
34
- const json = await res.json();
35
- (0, test_1.expect)(json?.data?.id).toBe(city.id);
36
- (0, test_1.expect)(json?.data?.name).toBe("Mumbai");
37
- (0, test_1.expect)(json?.data?.description).toBe("Mumbai city");
38
- }
39
- finally {
40
- await api.dispose();
41
- }
42
- });
43
- (0, test_1.test)("supports fields[] selection", async () => {
44
- const city = await getCityByName("Pune");
45
- const api = await test_1.request.newContext({
46
- baseURL,
47
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
48
- });
49
- try {
50
- const res = await api.get(`/api/city-master/${city.id}?fields[]=id&fields[]=name`);
51
- (0, test_1.expect)(res.status()).toBe(200);
52
- const json = await res.json();
53
- (0, test_1.expect)(json?.data?.id).toBe(city.id);
54
- (0, test_1.expect)(json?.data?.name).toBe("Pune");
55
- (0, test_1.expect)(json?.data?.description).toBeUndefined();
56
- (0, test_1.expect)(json?.data?.state).toBeUndefined();
57
- }
58
- finally {
59
- await api.dispose();
60
- }
61
- });
62
- (0, test_1.test)("supports populate=state", async () => {
63
- const city = await getCityByName("Bengaluru");
64
- const api = await test_1.request.newContext({
65
- baseURL,
66
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
67
- });
68
- try {
69
- const res = await api.get(`/api/city-master/${city.id}?populate=state`);
70
- (0, test_1.expect)(res.status()).toBe(200);
71
- const json = await res.json();
72
- (0, test_1.expect)(json?.data?.name).toBe("Bengaluru");
73
- (0, test_1.expect)(json?.data?.state).toBeTruthy();
74
- (0, test_1.expect)(json?.data?.state?.name).toBe("Karnataka");
75
- }
76
- finally {
77
- await api.dispose();
78
- }
79
- });
80
- (0, test_1.test)("returns 404 for missing id", async () => {
81
- const api = await test_1.request.newContext({
82
- baseURL,
83
- extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
84
- });
85
- try {
86
- const res = await api.get(`/api/city-master/99999999`);
87
- (0, test_1.expect)(res.status()).toBe(404);
88
- const json = await res.json();
89
- (0, test_1.expect)(String(json?.message)).toContain("cityMaster");
90
- (0, test_1.expect)(String(json?.message)).toContain("not found");
91
- }
92
- finally {
93
- await api.dispose();
94
- }
95
- });
96
- });
97
- //# sourceMappingURL=crud-service.findOne.cityMaster.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"crud-service.findOne.cityMaster.spec.js","sourceRoot":"","sources":["../../tests/api/crud-service.findOne.cityMaster.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getAuthHeaders } from \"../helpers/auth\";\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\n\nasync function getCityByName(name: string) {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`\n );\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n const record = json?.data?.records?.[0];\n expect(record, `Expected city '${name}' in testData`).toBeTruthy();\n expect(record.id, \"Expected record to have id\").toBeTruthy();\n\n return record;\n } finally {\n await api.dispose();\n }\n}\n\ntest.describe(\"CRUDService.findOne (cityMaster)\", () => {\n test(\"returns a city by id\", async () => {\n const city = await getCityByName(\"Mumbai\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Mumbai\");\n expect(json?.data?.description).toBe(\"Mumbai city\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports fields[] selection\", async () => {\n const city = await getCityByName(\"Pune\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master/${city.id}?fields[]=id&fields[]=name`\n );\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Pune\");\n expect(json?.data?.description).toBeUndefined();\n expect(json?.data?.state).toBeUndefined();\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports populate=state\", async () => {\n const city = await getCityByName(\"Bengaluru\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}?populate=state`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.name).toBe(\"Bengaluru\");\n expect(json?.data?.state).toBeTruthy();\n expect(json?.data?.state?.name).toBe(\"Karnataka\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"returns 404 for missing id\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/99999999`);\n expect(res.status()).toBe(404);\n\n const json = await res.json();\n expect(String(json?.message)).toContain(\"cityMaster\");\n expect(String(json?.message)).toContain(\"not found\");\n } finally {\n await api.dispose();\n }\n });\n});\n"]}
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const test_1 = require("@playwright/test");
4
- (0, test_1.test)("GET /api/ping returns pong", async () => {
5
- const baseURL = process.env.BASE_URL || "http://localhost:3000";
6
- if (!baseURL) {
7
- throw new Error("baseURL is not configured. Set API_BASE_URL or use the default.");
8
- }
9
- const api = await test_1.request.newContext({ baseURL });
10
- const res = await api.get("/api/ping");
11
- (0, test_1.expect)(res.status()).toBe(200);
12
- const body = await res.json();
13
- (0, test_1.expect)(body).toEqual({
14
- statusCode: 200,
15
- message: [],
16
- error: "",
17
- data: { pong: "v1.0.2" },
18
- });
19
- await api.dispose();
20
- });
21
- //# sourceMappingURL=ping.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ping.spec.js","sourceRoot":"","sources":["../../tests/api/ping.spec.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD,IAAA,WAAI,EAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvC,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAA,aAAM,EAAC,IAAI,CAAC,CAAC,OAAO,CAAC;QACnB,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACzB,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\n\ntest(\"GET /api/ping returns pong\", async () => {\n const baseURL = process.env.BASE_URL || \"http://localhost:3000\";\n if (!baseURL) {\n throw new Error(\"baseURL is not configured. Set API_BASE_URL or use the default.\");\n }\n const api = await request.newContext({ baseURL });\n\n const res = await api.get(\"/api/ping\");\n\n expect(res.status()).toBe(200);\n\n const body = await res.json();\n expect(body).toEqual({\n statusCode: 200,\n message: [],\n error: \"\",\n data: { pong: \"v1.0.2\" },\n });\n\n await api.dispose();\n});\n"]}
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAccessToken = getAccessToken;
4
- exports.getAuthHeaders = getAuthHeaders;
5
- const test_1 = require("@playwright/test");
6
- const env_1 = require("./env");
7
- async function getAccessToken(baseURL) {
8
- const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
9
- const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
10
- const api = await test_1.request.newContext({
11
- baseURL,
12
- extraHTTPHeaders: {
13
- accept: "*/*",
14
- "content-type": "application/json",
15
- },
16
- });
17
- try {
18
- const res = await api.post("/api/iam/authenticate", {
19
- data: {
20
- email: TEST_USER_EMAIL,
21
- username: "",
22
- password: TEST_USER_PASSWORD,
23
- },
24
- });
25
- (0, test_1.expect)(res.status()).toBe(200);
26
- const json = await res.json();
27
- const token = json?.data?.accessToken;
28
- (0, test_1.expect)(token, "Expected access token from authenticate endpoint.").toBeTruthy();
29
- return token;
30
- }
31
- finally {
32
- await api.dispose();
33
- }
34
- }
35
- async function getAuthHeaders(baseURL) {
36
- const token = await getAccessToken(baseURL);
37
- return {
38
- authorization: `Bearer ${token}`,
39
- };
40
- }
41
- //# sourceMappingURL=auth.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../tests/helpers/auth.ts"],"names":[],"mappings":";;AAGA,wCA6BC;AAED,wCAKC;AAvCD,2CAAmD;AACnD,+BAAuC;AAEhC,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,eAAe,GAAG,IAAA,oBAAc,EAAC,iBAAiB,CAAC,CAAC;IAC1D,MAAM,kBAAkB,GAAG,IAAA,oBAAc,EAAC,oBAAoB,CAAC,CAAC;IAEhE,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC;QACnC,OAAO;QACP,gBAAgB,EAAE;YAChB,MAAM,EAAE,KAAK;YACb,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE;YAClD,IAAI,EAAE;gBACJ,KAAK,EAAE,eAAe;gBACtB,QAAQ,EAAE,EAAE;gBACZ,QAAQ,EAAE,kBAAkB;aAC7B;SACF,CAAC,CAAC;QAEH,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,WAAiC,CAAC;QAC5D,IAAA,aAAM,EAAC,KAAK,EAAE,mDAAmD,CAAC,CAAC,UAAU,EAAE,CAAC;QAChF,OAAO,KAAe,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO;QACL,aAAa,EAAE,UAAU,KAAK,EAAE;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { expect, request } from \"@playwright/test\";\nimport { getRequiredEnv } from \"./env\";\n\nexport async function getAccessToken(baseURL: string) {\n const TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\n const TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n const token = json?.data?.accessToken as string | undefined;\n expect(token, \"Expected access token from authenticate endpoint.\").toBeTruthy();\n return token as string;\n } finally {\n await api.dispose();\n }\n}\n\nexport async function getAuthHeaders(baseURL: string) {\n const token = await getAccessToken(baseURL);\n return {\n authorization: `Bearer ${token}`,\n };\n}\n"]}
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getRequiredEnv = getRequiredEnv;
4
- function getRequiredEnv(name) {
5
- const value = process.env[name];
6
- if (!value) {
7
- throw new Error(`Missing required env var: ${name}`);
8
- }
9
- return value;
10
- }
11
- //# sourceMappingURL=env.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"env.js","sourceRoot":"","sources":["../../tests/helpers/env.ts"],"names":[],"mappings":";;AAAA,wCAMC;AAND,SAAgB,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["export function getRequiredEnv(name: string): string {\n const value = process.env[name];\n if (!value) {\n throw new Error(`Missing required env var: ${name}`);\n }\n return value;\n}\n"]}