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

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 (162) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/CLAUDE.md +26 -0
  3. package/dist/commands/seed.command.d.ts.map +1 -1
  4. package/dist/commands/seed.command.js +15 -2
  5. package/dist/commands/seed.command.js.map +1 -1
  6. package/dist/constants/media-file-types.d.ts +5 -0
  7. package/dist/constants/media-file-types.d.ts.map +1 -0
  8. package/dist/constants/media-file-types.js +27 -0
  9. package/dist/constants/media-file-types.js.map +1 -0
  10. package/dist/controllers/media.controller.d.ts +14 -11
  11. package/dist/controllers/media.controller.d.ts.map +1 -1
  12. package/dist/controllers/media.controller.js +72 -33
  13. package/dist/controllers/media.controller.js.map +1 -1
  14. package/dist/dtos/create-media.dto.d.ts +1 -0
  15. package/dist/dtos/create-media.dto.d.ts.map +1 -1
  16. package/dist/dtos/create-media.dto.js +7 -1
  17. package/dist/dtos/create-media.dto.js.map +1 -1
  18. package/dist/dtos/create-role-metadata.dto.d.ts +2 -0
  19. package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
  20. package/dist/dtos/create-role-metadata.dto.js +3 -1
  21. package/dist/dtos/create-role-metadata.dto.js.map +1 -1
  22. package/dist/dtos/update-media.dto.d.ts +1 -0
  23. package/dist/dtos/update-media.dto.d.ts.map +1 -1
  24. package/dist/dtos/update-media.dto.js +7 -1
  25. package/dist/dtos/update-media.dto.js.map +1 -1
  26. package/dist/entities/media.entity.d.ts +1 -0
  27. package/dist/entities/media.entity.d.ts.map +1 -1
  28. package/dist/entities/media.entity.js +9 -1
  29. package/dist/entities/media.entity.js.map +1 -1
  30. package/dist/enums/auth-type.enum.d.ts +2 -1
  31. package/dist/enums/auth-type.enum.d.ts.map +1 -1
  32. package/dist/enums/auth-type.enum.js +1 -0
  33. package/dist/enums/auth-type.enum.js.map +1 -1
  34. package/dist/guards/authentication.guard.d.ts +3 -1
  35. package/dist/guards/authentication.guard.d.ts.map +1 -1
  36. package/dist/guards/authentication.guard.js +5 -1
  37. package/dist/guards/authentication.guard.js.map +1 -1
  38. package/dist/guards/media-signed-url.guard.d.ts +9 -0
  39. package/dist/guards/media-signed-url.guard.d.ts.map +1 -0
  40. package/dist/guards/media-signed-url.guard.js +49 -0
  41. package/dist/guards/media-signed-url.guard.js.map +1 -0
  42. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  43. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +10 -13
  44. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  45. package/dist/interfaces.d.ts +19 -1
  46. package/dist/interfaces.d.ts.map +1 -1
  47. package/dist/interfaces.js +2 -0
  48. package/dist/interfaces.js.map +1 -1
  49. package/dist/repository/media.repository.d.ts.map +1 -1
  50. package/dist/repository/media.repository.js +2 -0
  51. package/dist/repository/media.repository.js.map +1 -1
  52. package/dist/seeders/module-metadata-seeder.service.d.ts +7 -1
  53. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  54. package/dist/seeders/module-metadata-seeder.service.js +87 -3
  55. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  56. package/dist/seeders/permission-metadata-seeder.service.d.ts +1 -0
  57. package/dist/seeders/permission-metadata-seeder.service.d.ts.map +1 -1
  58. package/dist/seeders/permission-metadata-seeder.service.js +6 -1
  59. package/dist/seeders/permission-metadata-seeder.service.js.map +1 -1
  60. package/dist/seeders/seed-data/solid-core-metadata.json +114 -13
  61. package/dist/services/authentication.service.d.ts +1 -0
  62. package/dist/services/authentication.service.d.ts.map +1 -1
  63. package/dist/services/authentication.service.js +19 -1
  64. package/dist/services/authentication.service.js.map +1 -1
  65. package/dist/services/media-download-url.service.d.ts +21 -0
  66. package/dist/services/media-download-url.service.d.ts.map +1 -0
  67. package/dist/services/media-download-url.service.js +67 -0
  68. package/dist/services/media-download-url.service.js.map +1 -0
  69. package/dist/services/media-storage.utils.d.ts +27 -0
  70. package/dist/services/media-storage.utils.d.ts.map +1 -0
  71. package/dist/services/media-storage.utils.js +82 -0
  72. package/dist/services/media-storage.utils.js.map +1 -0
  73. package/dist/services/media.service.d.ts +20 -4
  74. package/dist/services/media.service.d.ts.map +1 -1
  75. package/dist/services/media.service.js +157 -55
  76. package/dist/services/media.service.js.map +1 -1
  77. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +4 -4
  78. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
  79. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +37 -63
  80. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  81. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +5 -5
  82. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  83. package/dist/services/mediaStorageProviders/file-storage-provider.js +37 -38
  84. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  85. package/dist/services/role-metadata.service.d.ts +1 -0
  86. package/dist/services/role-metadata.service.d.ts.map +1 -1
  87. package/dist/services/role-metadata.service.js +8 -5
  88. package/dist/services/role-metadata.service.js.map +1 -1
  89. package/dist/solid-core.module.d.ts.map +1 -1
  90. package/dist/solid-core.module.js +11 -1
  91. package/dist/solid-core.module.js.map +1 -1
  92. package/dist/subscribers/audit.subscriber.js +1 -1
  93. package/dist/subscribers/audit.subscriber.js.map +1 -1
  94. package/package.json +1 -1
  95. package/src/commands/seed.command.ts +17 -3
  96. package/src/constants/media-file-types.ts +44 -0
  97. package/src/controllers/media.controller.ts +44 -24
  98. package/src/dtos/create-media.dto.ts +8 -3
  99. package/src/dtos/create-role-metadata.dto.ts +2 -0
  100. package/src/dtos/update-media.dto.ts +8 -3
  101. package/src/entities/media.entity.ts +4 -1
  102. package/src/enums/auth-type.enum.ts +1 -0
  103. package/src/guards/authentication.guard.ts +3 -0
  104. package/src/guards/media-signed-url.guard.ts +41 -0
  105. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +12 -24
  106. package/src/interfaces.ts +20 -0
  107. package/src/repository/media.repository.ts +2 -0
  108. package/src/seeders/module-metadata-seeder.service.ts +109 -3
  109. package/src/seeders/permission-metadata-seeder.service.ts +6 -1
  110. package/src/seeders/seed-data/solid-core-metadata.json +114 -13
  111. package/src/services/1.js +6 -0
  112. package/src/services/authentication.service.ts +24 -1
  113. package/src/services/media-download-url.service.ts +67 -0
  114. package/src/services/media-storage.utils.ts +79 -0
  115. package/src/services/media.service.ts +189 -59
  116. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +52 -76
  117. package/src/services/mediaStorageProviders/file-storage-provider.ts +44 -48
  118. package/src/services/role-metadata.service.ts +17 -5
  119. package/src/solid-core.module.ts +21 -2
  120. package/src/subscribers/audit.subscriber.ts +1 -1
  121. package/dist-tests/api/authenticate.spec.js +0 -119
  122. package/dist-tests/api/authenticate.spec.js.map +0 -1
  123. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
  124. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
  125. package/dist-tests/api/ping.spec.js +0 -21
  126. package/dist-tests/api/ping.spec.js.map +0 -1
  127. package/dist-tests/helpers/auth.js +0 -41
  128. package/dist-tests/helpers/auth.js.map +0 -1
  129. package/dist-tests/helpers/env.js +0 -11
  130. package/dist-tests/helpers/env.js.map +0 -1
  131. package/docs/agent-builder/notebook-lm-prompt-for-agent-builder.md +0 -136
  132. package/docs/agent-builder/team-ready-note-agent-builder.md +0 -305
  133. package/docs/agent-hub-grooming.md +0 -301
  134. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +0 -438
  135. package/docs/dashboards/dashboard-curl-smoke-tests.txt +0 -146
  136. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +0 -172
  137. package/docs/datasource-introspection-ddl-analysis.md +0 -326
  138. package/docs/datasource-introspection-implementation-plan.md +0 -306
  139. package/docs/grouping-enhancements.md +0 -89
  140. package/docs/java-spring/README.md +0 -3
  141. package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
  142. package/docs/module-package-import-handoff.md +0 -691
  143. package/docs/seed-changes.md +0 -65
  144. package/docs/test-data-workflow.md +0 -200
  145. package/docs/type-declaration-import-issue.md +0 -24
  146. package/docs/workflow/business-automation-example-notes.md +0 -309
  147. package/docs/workflow/data-engineering-pipeline-example-notes.md +0 -330
  148. package/docs/workflow/foreach-example-notes.md +0 -187
  149. package/docs/workflow/hello-world-example-notes.md +0 -271
  150. package/docs/workflow/kestra-concepts-plugins-blueprints.md +0 -315
  151. package/docs/workflow/loop-until-example-notes.md +0 -198
  152. package/docs/workflow/microservices-and-apis-example-notes.md +0 -264
  153. package/docs/workflow/samples.md +0 -25
  154. package/docs/workflow/what-is-kestra.md +0 -79
  155. package/docs/workflow/workflow-core-module-handoff-summary.md +0 -191
  156. package/docs/workflow/workflow-module-brd.md +0 -185
  157. package/docs/workflow/workflow-module-domain-model.md +0 -252
  158. package/docs/workflow/workflow-module-metadata-dsl.md +0 -302
  159. package/docs/workflow/workflow-module-technical-spec.md +0 -293
  160. package/docs/workflow/workflow-node-type-contracts.md +0 -635
  161. package/docs/workflow/workflow-node-ui-metadata-contract.md +0 -1262
  162. package/docs/workflow/workflow-solidx-model-field-spec.md +0 -284
@@ -1 +1 @@
1
- {"version":3,"file":"audit.subscriber.js","sourceRoot":"","sources":["../../src/subscribers/audit.subscriber.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, Logger, Scope } from '@nestjs/common';\nimport { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';\nimport { lowerFirst } from 'src/helpers/string.helper';\nimport { SolidRegistry } from 'src/helpers/solid-registry';\nimport { DataSource, EntityMetadata, EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';\nimport { AuditQueuePayload } from 'src/interfaces';\nimport { RequestContextService } from 'src/services/request-context.service';\nimport { PublisherFactory } from 'src/services/queues/publisher-factory.service';\nconst AUDIT_BEFORE_SNAPSHOT = '__auditBeforeSnapshot';\n\n@Injectable({ scope: Scope.TRANSIENT })\nexport class AuditSubscriber implements EntitySubscriberInterface {\n private readonly logger = new Logger(AuditSubscriber.name);\n private dataSource: DataSource;\n constructor(\n private readonly publisherFactory: PublisherFactory<AuditQueuePayload>,\n private readonly solidRegistry: SolidRegistry,\n private readonly requestContextService: RequestContextService,\n private readonly modelMetadataHelperService: ModelMetadataHelperService,\n ) { }\n\n bindToDataSource(dataSource: DataSource) {\n this.dataSource = dataSource;\n this.dataSource.subscribers.push(this);\n }\n\n // Per-transaction buffer (auto-GC when queryRunner is gone)\n private perTxn = new WeakMap<any, AuditQueuePayload[]>();\n\n private enqueue(event: { queryRunner: any }, payload: AuditQueuePayload) {\n const qr = event.queryRunner;\n const arr = this.perTxn.get(qr) ?? [];\n arr.push(payload);\n this.perTxn.set(qr, arr);\n }\n\n private shouldTrackAudit(metadata: EntityMetadata): boolean {\n return this.solidRegistry.isAuditableModel(lowerFirst(metadata.name));\n }\n\n private activeUserId(): number | null {\n return this.requestContextService.getActiveUser()?.sub ?? null;\n }\n\n async afterInsert(event: InsertEvent<any>) {\n if (!this.shouldTrackAudit(event.metadata)) return;\n this.enqueue(event, {\n eventType: 'insert',\n modelName: event.metadata.name,\n entityId: event.entity?.id ?? null,\n occurredAt: new Date().toISOString(),\n after: event.entity ?? null,\n userId: this.activeUserId(),\n });\n }\n\n async afterUpdate(event: UpdateEvent<any>) {\n if (!this.shouldTrackAudit(event.metadata)) return;\n const entityId = event.entity?.id ?? event.databaseEntity?.id ?? (event as any).entityId ?? null;\n let before = event.databaseEntity ?? null;\n let after = event.entity ?? null;\n const updatedColumnNames = (event.updatedColumns ?? []).map(c => c.propertyName);\n\n const auditResult = await this.prepareManyToManyAuditUpdateSnapshot(event, entityId, updatedColumnNames);\n\n if (auditResult) {\n before = auditResult.before;\n after = auditResult.after;\n }\n\n this.enqueue(event, {\n eventType: 'update',\n modelName: event.metadata.name,\n entityId: entityId,\n occurredAt: new Date().toISOString(),\n after: after,\n // databaseEntity is only populated when the entity was fetched first (save() path).\n // QueryBuilder update() leaves this undefined; postAuditMessageOnUpdate guards for it.\n before: before,\n updatedColumnNames: updatedColumnNames,\n userId: this.activeUserId(),\n });\n }\n\n /**\n * Resolves before/after snapshots for audit-enabled many-to-many relations\n * and updates changed relation field names for audit tracking.\n */\n private async prepareManyToManyAuditUpdateSnapshot(event: UpdateEvent<any>, entityId: number | null, updatedColumnNames: string[]): Promise<{ before: any; after: any } | null> {\n\n const auditRelationFields = (await this.modelMetadataHelperService.loadFieldHierarchy(lowerFirst(event.metadata.name))).filter(field =>\n field.enableAuditTracking &&\n field.type === 'relation' &&\n field.relationType !== 'one-to-many'\n );\n\n if (!entityId || auditRelationFields.length === 0) {\n return null;\n }\n\n const relations: Record<string, boolean> = {};\n\n auditRelationFields.forEach(field => {\n relations[field.name] = true;\n });\n\n const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;\n\n const relationAfter = await this.dataSource.getRepository(event.metadata.target as any).findOne({\n where: { id: entityId } as any,\n relations: relations as any,\n });\n\n if (relationBefore && relationAfter) {\n auditRelationFields.forEach(field => {\n const oldIds = Array.isArray(relationBefore[field.name])\n ? relationBefore[field.name].map(item => item.id).sort()\n : [];\n\n const newIds = Array.isArray(relationAfter[field.name])\n ? relationAfter[field.name].map(item => item.id).sort()\n : [];\n\n if ((oldIds.length !== newIds.length || JSON.stringify(oldIds) !== JSON.stringify(newIds))\n && !updatedColumnNames.includes(field.name)) {\n updatedColumnNames.push(field.name);\n }\n });\n }\n return {\n before: relationBefore ?? event.databaseEntity ?? null,\n after: relationAfter ?? event.entity ?? null,\n };\n }\n async afterRemove(event: RemoveEvent<any>) {\n if (!this.shouldTrackAudit(event.metadata)) return;\n this.enqueue(event, {\n eventType: 'delete',\n modelName: event.metadata.name,\n entityId: event.databaseEntity?.id ?? null,\n occurredAt: new Date().toISOString(),\n before: event.databaseEntity,\n userId: this.activeUserId(),\n });\n }\n\n // --------- transaction lifecycle ----------\n async afterTransactionCommit(event: { queryRunner: any }) {\n const batch = this.perTxn.get(event.queryRunner) ?? [];\n this.perTxn.delete(event.queryRunner);\n\n if (batch.length === 0) return;\n\n await this.publishAuditBatch(batch);\n }\n\n afterTransactionRollback(event: { queryRunner: any }) {\n // Drop buffered payloads; the write never happened.\n this.perTxn.delete(event.queryRunner);\n }\n\n private async publishAuditBatch(batch: AuditQueuePayload[]) {\n try {\n // allSettled: publish in parallel; a single failure does not block the rest.\n const results = await Promise.allSettled(\n batch.map(payload => this.publisherFactory.publish({ payload }, 'ChatterQueuePublisher'))\n );\n\n results.forEach((result, i) => {\n if (result.status === 'rejected') {\n this.logger.error(\n `Failed to publish audit event for ${batch[i].modelName}#${batch[i].entityId}`,\n result.reason,\n );\n }\n });\n } catch (error) {\n this.logger.error('Failed to publish audit batch', error);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"audit.subscriber.js","sourceRoot":"","sources":["../../src/subscribers/audit.subscriber.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, Logger, Scope } from '@nestjs/common';\nimport { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';\nimport { lowerFirst } from 'src/helpers/string.helper';\nimport { SolidRegistry } from 'src/helpers/solid-registry';\nimport { DataSource, EntityMetadata, EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';\nimport { AuditQueuePayload } from 'src/interfaces';\nimport { RequestContextService } from 'src/services/request-context.service';\nimport { PublisherFactory } from 'src/services/queues/publisher-factory.service';\nconst AUDIT_BEFORE_SNAPSHOT = '__auditBeforeSnapshot';\n\n@Injectable({ scope: Scope.TRANSIENT })\nexport class AuditSubscriber implements EntitySubscriberInterface {\n private readonly logger = new Logger(AuditSubscriber.name);\n private dataSource: DataSource;\n constructor(\n private readonly publisherFactory: PublisherFactory<AuditQueuePayload>,\n private readonly solidRegistry: SolidRegistry,\n private readonly requestContextService: RequestContextService,\n private readonly modelMetadataHelperService: ModelMetadataHelperService,\n ) { }\n\n bindToDataSource(dataSource: DataSource) {\n this.dataSource = dataSource;\n this.dataSource.subscribers.push(this);\n }\n\n // Per-transaction buffer (auto-GC when queryRunner is gone)\n private perTxn = new WeakMap<any, AuditQueuePayload[]>();\n\n private enqueue(event: { queryRunner: any }, payload: AuditQueuePayload) {\n const qr = event.queryRunner;\n const arr = this.perTxn.get(qr) ?? [];\n arr.push(payload);\n this.perTxn.set(qr, arr);\n }\n\n private shouldTrackAudit(metadata: EntityMetadata): boolean {\n return this.solidRegistry.isAuditableModel(lowerFirst(metadata.name));\n }\n\n private activeUserId(): number | null {\n return this.requestContextService.getActiveUser()?.sub ?? null;\n }\n\n async afterInsert(event: InsertEvent<any>) {\n if (!this.shouldTrackAudit(event.metadata)) return;\n this.enqueue(event, {\n eventType: 'insert',\n modelName: event.metadata.name,\n entityId: event.entity?.id ?? null,\n occurredAt: new Date().toISOString(),\n after: event.entity ?? null,\n userId: this.activeUserId(),\n });\n }\n\n async afterUpdate(event: UpdateEvent<any>) {\n if (!this.shouldTrackAudit(event.metadata)) return;\n const entityId = event.entity?.id ?? event.databaseEntity?.id ?? (event as any).entityId ?? null;\n let before = event.databaseEntity ?? null;\n let after = event.entity ?? null;\n const updatedColumnNames = (event.updatedColumns ?? []).map(c => c.propertyName);\n\n const auditResult = await this.prepareManyToManyAuditUpdateSnapshot(event, entityId, updatedColumnNames);\n\n if (auditResult) {\n before = auditResult.before;\n after = auditResult.after;\n }\n\n this.enqueue(event, {\n eventType: 'update',\n modelName: event.metadata.name,\n entityId: entityId,\n occurredAt: new Date().toISOString(),\n after: after,\n // databaseEntity is only populated when the entity was fetched first (save() path).\n // QueryBuilder update() leaves this undefined; postAuditMessageOnUpdate guards for it.\n before: before,\n updatedColumnNames: updatedColumnNames,\n userId: this.activeUserId(),\n });\n }\n\n /**\n * Resolves before/after snapshots for audit-enabled many-to-many relations\n * and updates changed relation field names for audit tracking.\n */\n private async prepareManyToManyAuditUpdateSnapshot(event: UpdateEvent<any>, entityId: number | null, updatedColumnNames: string[]): Promise<{ before: any; after: any } | null> {\n\n const auditRelationFields = (await this.modelMetadataHelperService.loadFieldHierarchy(lowerFirst(event.metadata.name))).filter(field =>\n field.enableAuditTracking &&\n field.type === 'relation' &&\n field.relationType !== 'one-to-many'\n );\n\n if (!entityId || auditRelationFields.length === 0) {\n return null;\n }\n\n const relations: Record<string, boolean> = {};\n\n auditRelationFields.forEach(field => {\n relations[field.name] = true;\n });\n\n const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;\n\n const relationAfter = await event.queryRunner.manager.getRepository(event.metadata.target as any).findOne({\n where: { id: entityId } as any,\n relations: relations as any,\n });\n\n if (relationBefore && relationAfter) {\n auditRelationFields.forEach(field => {\n const oldIds = Array.isArray(relationBefore[field.name])\n ? relationBefore[field.name].map(item => item.id).sort()\n : [];\n\n const newIds = Array.isArray(relationAfter[field.name])\n ? relationAfter[field.name].map(item => item.id).sort()\n : [];\n\n if ((oldIds.length !== newIds.length || JSON.stringify(oldIds) !== JSON.stringify(newIds))\n && !updatedColumnNames.includes(field.name)) {\n updatedColumnNames.push(field.name);\n }\n });\n }\n return {\n before: relationBefore ?? event.databaseEntity ?? null,\n after: relationAfter ?? event.entity ?? null,\n };\n }\n async afterRemove(event: RemoveEvent<any>) {\n if (!this.shouldTrackAudit(event.metadata)) return;\n this.enqueue(event, {\n eventType: 'delete',\n modelName: event.metadata.name,\n entityId: event.databaseEntity?.id ?? null,\n occurredAt: new Date().toISOString(),\n before: event.databaseEntity,\n userId: this.activeUserId(),\n });\n }\n\n // --------- transaction lifecycle ----------\n async afterTransactionCommit(event: { queryRunner: any }) {\n const batch = this.perTxn.get(event.queryRunner) ?? [];\n this.perTxn.delete(event.queryRunner);\n\n if (batch.length === 0) return;\n\n await this.publishAuditBatch(batch);\n }\n\n afterTransactionRollback(event: { queryRunner: any }) {\n // Drop buffered payloads; the write never happened.\n this.perTxn.delete(event.queryRunner);\n }\n\n private async publishAuditBatch(batch: AuditQueuePayload[]) {\n try {\n // allSettled: publish in parallel; a single failure does not block the rest.\n const results = await Promise.allSettled(\n batch.map(payload => this.publisherFactory.publish({ payload }, 'ChatterQueuePublisher'))\n );\n\n results.forEach((result, i) => {\n if (result.status === 'rejected') {\n this.logger.error(\n `Failed to publish audit event for ${batch[i].modelName}#${batch[i].entityId}`,\n result.reason,\n );\n }\n });\n } catch (error) {\n this.logger.error('Failed to publish audit batch', error);\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidxai/core",
3
- "version": "0.1.12-beta.2",
3
+ "version": "0.1.12-beta.4",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,6 +1,7 @@
1
1
  import { Logger } from '@nestjs/common';
2
2
  import { Command, CommandRunner, Option } from 'nest-commander';
3
3
  import { SolidRegistry } from 'src/helpers/solid-registry';
4
+ import { ModuleMetadataSeederOptions } from 'src/interfaces';
4
5
 
5
6
  interface SeedCommandOptions {
6
7
  seeder?: string;
@@ -17,20 +18,33 @@ export class SeedCommand extends CommandRunner {
17
18
  }
18
19
 
19
20
  async run(passedParam: string[], options?: SeedCommandOptions): Promise<void> {
20
- let parsedConf: any = null;
21
+ let parsedConf: ModuleMetadataSeederOptions | null = null;
21
22
  if (options?.modulesToSeed) {
22
23
  const modulesToSeed = options.modulesToSeed
23
24
  .split(',')
24
25
  .map((m) => m.trim())
25
26
  .filter(Boolean);
26
- parsedConf = { modulesToSeed };
27
+ parsedConf = {
28
+ modulesToSeed,
29
+ pruneMetadata: true,
30
+ seedGlobalMetadata: true,
31
+ };
27
32
  this.logger.log(`Modules to seed: ${modulesToSeed.join(', ')}`);
28
33
  } else {
29
34
  this.logger.log('No --modules-to-seed flag provided. Running with default seeder behavior.');
35
+ parsedConf = {
36
+ modulesToSeed: null,
37
+ pruneMetadata: true,
38
+ seedGlobalMetadata: true,
39
+ };
30
40
  }
31
41
 
32
42
  if (options?.prune) {
33
- parsedConf = parsedConf ?? {};
43
+ parsedConf = parsedConf ?? {
44
+ modulesToSeed: null,
45
+ pruneMetadata: true,
46
+ seedGlobalMetadata: true,
47
+ };
34
48
  parsedConf.pruneMetadata = true;
35
49
  }
36
50
 
@@ -0,0 +1,44 @@
1
+ export type MediaCategory = 'image' | 'audio' | 'video' | 'file' | 'pdf';
2
+
3
+ const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff', 'heic', 'heif'];
4
+ const AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'webm'];
5
+ const VIDEO_EXTENSIONS = ['mp4', 'mov', 'avi', 'mkv', 'mpeg', 'mpg', '3gp', '3g2', 'webm', 'ogg'];
6
+ const DOCUMENT_EXTENSIONS = ['txt', 'md', 'csv', 'json', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar', '7z'];
7
+ const PDF_EXTENSION = 'pdf';
8
+
9
+ /**
10
+ * Extensions that must never be accepted as media uploads, regardless of declared mimetype or
11
+ * admin-configured mediaTypes allowlist. Checked before any other resolution so a spoofed
12
+ * Content-Type (e.g. application/octet-stream on an .html file) can't bypass this.
13
+ */
14
+ export const DANGEROUS_EXTENSIONS = new Set([
15
+ 'html', 'htm', 'xhtml', 'svg', 'xml',
16
+ 'js', 'mjs', 'php', 'phtml', 'jsp', 'asp', 'aspx',
17
+ 'exe', 'sh', 'bat', 'cmd', 'ps1', 'jar', 'dll',
18
+ ]);
19
+
20
+ /**
21
+ * Single source of truth for extension -> coarse media category. Shared by upload-time
22
+ * validation (MediaFieldCrudManager) and serve-time header hardening (ServeStaticModule), so
23
+ * both agree on which extensions belong to which category and neither drifts out of sync.
24
+ * webm/ogg are shared between AUDIO_EXTENSIONS and VIDEO_EXTENSIONS - last one wins here, they
25
+ * default to 'video' since that's the more common upload case.
26
+ */
27
+ export const EXT_TO_MEDIA_TYPE: Record<string, MediaCategory> = {
28
+ ...Object.fromEntries(IMAGE_EXTENSIONS.map((ext) => [ext, 'image' as const])),
29
+ ...Object.fromEntries(AUDIO_EXTENSIONS.map((ext) => [ext, 'audio' as const])),
30
+ ...Object.fromEntries(VIDEO_EXTENSIONS.map((ext) => [ext, 'video' as const])),
31
+ ...Object.fromEntries(DOCUMENT_EXTENSIONS.map((ext) => [ext, 'file' as const])),
32
+ [PDF_EXTENSION]: 'pdf',
33
+ };
34
+
35
+ /**
36
+ * Extensions safe to serve inline with their natural Content-Type; everything else served from
37
+ * media-files-storage is forced to application/octet-stream + Content-Disposition: attachment.
38
+ */
39
+ export const INLINE_SAFE_EXTENSIONS = new Set<string>([
40
+ ...IMAGE_EXTENSIONS,
41
+ ...AUDIO_EXTENSIONS,
42
+ ...VIDEO_EXTENSIONS,
43
+ PDF_EXTENSION,
44
+ ]);
@@ -1,10 +1,14 @@
1
- import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, UploadedFiles, UseInterceptors } from '@nestjs/common';
1
+ import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Res, UploadedFiles, UseInterceptors } from '@nestjs/common';
2
2
  import { AnyFilesInterceptor } from "@nestjs/platform-express";
3
3
  import { ApiBearerAuth, ApiQuery, ApiTags } from '@nestjs/swagger';
4
- import { Public } from 'src/decorators/public.decorator';
4
+ import { Auth } from 'src/decorators/auth.decorator';
5
+ import { AuthType } from 'src/enums/auth-type.enum';
5
6
  import { CreateMediaDto } from 'src/dtos/create-media.dto';
7
+ import { SolidRequestContextDecorator } from 'src/decorators/solid-request-context.decorator';
8
+ import { SolidRequestContextDto } from 'src/dtos/solid-request-context.dto';
6
9
  import { UpdateMediaDto } from 'src/dtos/update-media.dto';
7
10
  import { MediaService } from 'src/services/media.service';
11
+ import { Response } from 'express';
8
12
 
9
13
  enum ShowSoftDeleted {
10
14
  INCLUSIVE = "inclusive",
@@ -21,30 +25,30 @@ export class MediaController {
21
25
  @ApiBearerAuth("jwt")
22
26
  @Post()
23
27
  @UseInterceptors(AnyFilesInterceptor())
24
- create(@Body() createDto: CreateMediaDto, @UploadedFiles() files: Array<Express.Multer.File>) {
25
- return this.service.create(createDto, files);
28
+ create(@Body() createDto: CreateMediaDto, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
29
+ return this.service.create(createDto, files, solidRequestContext);
26
30
  }
27
31
 
28
32
  @ApiBearerAuth("jwt")
29
33
  @Post('/bulk')
30
34
  @UseInterceptors(AnyFilesInterceptor())
31
- insertMany(@Body() createDtos: CreateMediaDto[], @UploadedFiles() filesArray: Express.Multer.File[][] = []) {
32
- return this.service.insertMany(createDtos, filesArray);
35
+ insertMany(@Body() createDtos: CreateMediaDto[], @UploadedFiles() filesArray: Express.Multer.File[][] = [], @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
36
+ return this.service.insertMany(createDtos, filesArray, solidRequestContext);
33
37
  }
34
38
 
35
39
 
36
40
  @ApiBearerAuth("jwt")
37
41
  @Put(':id')
38
42
  @UseInterceptors(AnyFilesInterceptor())
39
- update(@Param('id') id: number, @Body() updateDto: UpdateMediaDto, @UploadedFiles() files: Array<Express.Multer.File>) {
40
- return this.service.update(id, updateDto, files);
43
+ update(@Param('id') id: number, @Body() updateDto: UpdateMediaDto, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
44
+ return this.service.update(id, updateDto, files, false, solidRequestContext);
41
45
  }
42
46
 
43
47
  @ApiBearerAuth("jwt")
44
48
  @Patch(':id')
45
49
  @UseInterceptors(AnyFilesInterceptor())
46
- partialUpdate(@Param('id') id: number, @Body() updateDto: UpdateMediaDto, @UploadedFiles() files: Array<Express.Multer.File>) {
47
- return this.service.update(id, updateDto, files, true);
50
+ partialUpdate(@Param('id') id: number, @Body() updateDto: UpdateMediaDto, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
51
+ return this.service.update(id, updateDto, files, true, solidRequestContext);
48
52
  }
49
53
 
50
54
  // @Public()
@@ -52,22 +56,22 @@ export class MediaController {
52
56
  @ApiBearerAuth("jwt")
53
57
  @Post('/upload')
54
58
  @UseInterceptors(AnyFilesInterceptor())
55
- upload(@UploadedFiles() files: Array<Express.Multer.File>, @Body() createDto: CreateMediaDto, ) {
59
+ upload(@UploadedFiles() files: Array<Express.Multer.File>, @Body() createDto: CreateMediaDto, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
56
60
  // this.logger.log(`Creating a new model: ${JSON.stringify(createDto)}`);
57
- return this.service.upload(createDto, files);
61
+ return this.service.upload(createDto, files, solidRequestContext);
58
62
  }
59
63
 
60
64
 
61
65
  @ApiBearerAuth("jwt")
62
66
  @Post('/bulk-recover')
63
- async recoverMany(@Body() ids: number[]) {
64
- return this.service.recoverMany(ids);
67
+ async recoverMany(@Body() ids: number[], @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
68
+ return this.service.recoverMany(ids, solidRequestContext);
65
69
  }
66
70
 
67
71
  @ApiBearerAuth("jwt")
68
72
  @Get('/recover/:id')
69
- async recover(@Param('id') id: number) {
70
- return this.service.recover(id);
73
+ async recover(@Param('id') id: number, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
74
+ return this.service.recover(id, solidRequestContext);
71
75
  }
72
76
 
73
77
  @ApiBearerAuth("jwt")
@@ -81,26 +85,42 @@ export class MediaController {
81
85
  @ApiQuery({ name: 'populateMedia', required: false, type: Array })
82
86
  @ApiQuery({ name: 'filters', required: false, type: Array })
83
87
  @Get()
84
- async findMany(@Query() query: any) {
85
- return this.service.find(query);
88
+ async findMany(@Query() query: any, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
89
+ return this.service.find(query, solidRequestContext);
90
+ }
91
+
92
+ @Auth(AuthType.MediaSignedUrl)
93
+ @ApiQuery({ name: 'token', required: true, type: String, description: "Short-lived signed token obtained from a private Media record's URL - not entered manually." })
94
+ @ApiQuery({ name: 'disposition', required: false, enum: ['inline', 'attachment'], description: "'inline' (default) renders/previews in the browser, matching public media. Pass 'attachment' to force a browser download (e.g. from a download button)." })
95
+ @Get(':id/download')
96
+ async download(@Param('id') id: string, @Query('disposition') disposition: string, @Res() res: Response, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
97
+ const media = await this.service.findOne(+id, {}, solidRequestContext);
98
+ const { stream, fileName, mimeType } = await this.service.fileDownloadStream(media);
99
+
100
+ const contentDisposition = disposition === 'attachment' ? 'attachment' : 'inline';
101
+ res.setHeader('Content-Disposition', `${contentDisposition}; filename="${fileName}"`);
102
+ res.setHeader('Content-Type', mimeType);
103
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
104
+ res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition, Content-Type');
105
+ stream.pipe(res);
86
106
  }
87
107
 
88
108
  @ApiBearerAuth("jwt")
89
109
  @Get(':id')
90
- async findOne(@Param('id') id: string, @Query() query: any) {
91
- return this.service.findOne(+id, query);
110
+ async findOne(@Param('id') id: string, @Query() query: any, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
111
+ return this.service.findOne(+id, query, solidRequestContext);
92
112
  }
93
113
 
94
114
  @ApiBearerAuth("jwt")
95
115
  @Delete('/bulk')
96
- async deleteMany(@Body() ids: number[]) {
97
- return this.service.deleteMany(ids);
116
+ async deleteMany(@Body() ids: number[], @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
117
+ return this.service.deleteMany(ids, solidRequestContext);
98
118
  }
99
119
 
100
120
  @ApiBearerAuth("jwt")
101
121
  @Delete(':id')
102
- async delete(@Param('id') id: number) {
103
- return this.service.delete(id);
122
+ async delete(@Param('id') id: number, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
123
+ return this.service.delete(id, solidRequestContext);
104
124
  }
105
125
 
106
126
 
@@ -1,5 +1,5 @@
1
- import { ApiProperty } from '@nestjs/swagger';
2
- import { IsInt } from 'class-validator';
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { IsBoolean, IsInt } from 'class-validator';
3
3
  import { IsNotEmpty, IsString, IsOptional } from 'class-validator';
4
4
  export class CreateMediaDto {
5
5
  @IsNotEmpty()
@@ -7,6 +7,11 @@ export class CreateMediaDto {
7
7
  @ApiProperty()
8
8
  entityId: number;
9
9
 
10
+ @IsOptional()
11
+ @IsBoolean()
12
+ @ApiPropertyOptional({ default: true })
13
+ isPublic?: boolean;
14
+
10
15
  @IsOptional()
11
16
  @IsString()
12
17
  @ApiProperty()
@@ -56,4 +61,4 @@ fieldMetadataId: number;
56
61
  @IsOptional()
57
62
  @ApiProperty()
58
63
  fieldMetadataUserKey: string;
59
- }
64
+ }
@@ -8,6 +8,8 @@ import { ApiProperty } from '@nestjs/swagger';
8
8
 
9
9
  export const ADMIN_ROLE_NAME = 'Admin';
10
10
  export const INTERNAL_ROLE_NAME = 'Internal User';
11
+ export const ALLOWED_TO_IMPORT_ROLE_NAME = 'Allowed To Import';
12
+ export const ALLOWED_TO_EXPORT_ROLE_NAME = 'Allowed To Export';
11
13
  export const PUBLIC_ROLE_NAME = 'Public';
12
14
 
13
15
  export class CreateRoleMetadataDto {
@@ -1,5 +1,5 @@
1
- import { IsInt,IsOptional, IsNotEmpty, IsString } from 'class-validator';
2
- import { ApiProperty } from '@nestjs/swagger';
1
+ import { IsBoolean, IsInt,IsOptional, IsNotEmpty, IsString } from 'class-validator';
2
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
3
3
  export class UpdateMediaDto {
4
4
  @IsOptional()
5
5
  @IsInt()
@@ -11,6 +11,11 @@ export class UpdateMediaDto {
11
11
  @ApiProperty()
12
12
  entityId: number;
13
13
 
14
+ @IsOptional()
15
+ @IsBoolean()
16
+ @ApiPropertyOptional({ default: true })
17
+ isPublic?: boolean;
18
+
14
19
  @IsOptional()
15
20
  @IsString()
16
21
  @ApiProperty()
@@ -60,4 +65,4 @@ fieldMetadataId: number;
60
65
  @IsOptional()
61
66
  @ApiProperty()
62
67
  fieldMetadataUserKey: string;
63
- }
68
+ }
@@ -9,6 +9,9 @@ export class Media extends CommonEntity {
9
9
  @Column({ type: "integer" })
10
10
  entityId: number;
11
11
 
12
+ @Column({ name: "is_public", default: true })
13
+ isPublic: boolean = true;
14
+
12
15
  @Column({ type: "varchar", nullable: true })
13
16
  relativeUri: string;
14
17
 
@@ -36,4 +39,4 @@ export class Media extends CommonEntity {
36
39
  @ManyToOne(() => FieldMetadata, { nullable: false })
37
40
  @JoinColumn()
38
41
  fieldMetadata: FieldMetadata;
39
- }
42
+ }
@@ -2,4 +2,5 @@ export enum AuthType {
2
2
  Bearer,
3
3
  ApiKey,
4
4
  None,
5
+ MediaSignedUrl,
5
6
  }
@@ -9,6 +9,7 @@ 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
11
  import { ApiKeyGuard } from './api-key.guard';
12
+ import { MediaSignedUrlGuard } from './media-signed-url.guard';
12
13
  import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
13
14
  import { PermissionMetadataService } from '../services/permission-metadata.service';
14
15
  import { ClsService } from 'nestjs-cls';
@@ -22,12 +23,14 @@ export class AuthenticationGuard implements CanActivate {
22
23
  [AuthType.Bearer]: this.accessTokenGuard,
23
24
  [AuthType.ApiKey]: this.apiKeyGuard,
24
25
  [AuthType.None]: { canActivate: () => true },
26
+ [AuthType.MediaSignedUrl]: this.mediaSignedUrlGuard,
25
27
  };
26
28
 
27
29
  constructor(
28
30
  private readonly reflector: Reflector,
29
31
  private readonly accessTokenGuard: AccessTokenGuard,
30
32
  private readonly apiKeyGuard: ApiKeyGuard,
33
+ private readonly mediaSignedUrlGuard: MediaSignedUrlGuard,
31
34
  private readonly permissionService: PermissionMetadataService,
32
35
  private readonly cls: ClsService,
33
36
  ) { }
@@ -0,0 +1,41 @@
1
+ import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
2
+ import { Request } from 'express';
3
+ import { MediaDownloadUrlService } from 'src/services/media-download-url.service';
4
+
5
+ @Injectable()
6
+ export class MediaSignedUrlGuard implements CanActivate {
7
+ constructor(private readonly mediaDownloadUrlService: MediaDownloadUrlService) { }
8
+
9
+ async canActivate(context: ExecutionContext): Promise<boolean> {
10
+ const request = context.switchToHttp().getRequest<Request>();
11
+ const token = this.extractTokenFromQuery(request);
12
+ if (!token) {
13
+ throw new UnauthorizedException();
14
+ }
15
+
16
+ let payload: { mediaId: number };
17
+ try {
18
+ payload = await this.mediaDownloadUrlService.verifyDownloadToken(token);
19
+ } catch {
20
+ throw new UnauthorizedException();
21
+ }
22
+
23
+ const routeMediaId = Number(request.params?.id);
24
+ if (!Number.isFinite(routeMediaId) || payload.mediaId !== routeMediaId) {
25
+ throw new UnauthorizedException();
26
+ }
27
+
28
+ // No REQUEST_USER_KEY is ever set for token auth (only Bearer/ApiKey guards set it), so
29
+ // the sibling global APP_GUARD PermissionsGuard would otherwise reject this request for
30
+ // lack of a per-user permission grant. Flag it the same way AuthenticationGuard flags
31
+ // Public-role-exempt requests - token possession IS the full authorization decision here.
32
+ request['isListedInPublicRole'] = true;
33
+
34
+ return true;
35
+ }
36
+
37
+ private extractTokenFromQuery(request: Request): string | undefined {
38
+ const token = request.query?.token;
39
+ return typeof token === 'string' ? token : undefined;
40
+ }
41
+ }
@@ -1,4 +1,5 @@
1
1
  import { FieldCrudManager, ValidationError } from "src/interfaces";
2
+ import { DANGEROUS_EXTENSIONS, EXT_TO_MEDIA_TYPE, MediaCategory } from "src/constants/media-file-types";
2
3
 
3
4
  export enum SolidMediaType {
4
5
  mediaSingle = 'mediaSingle',
@@ -14,7 +15,7 @@ export interface MediaFieldOptions {
14
15
  isUpdate: boolean | undefined | null;
15
16
  }
16
17
 
17
- type MediaType = 'image' | 'audio' | 'video' | 'file' | 'pdf';
18
+ type MediaType = MediaCategory;
18
19
 
19
20
  const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
20
21
  // Images
@@ -25,7 +26,6 @@ const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
25
26
  'image/gif': 'image',
26
27
  'image/bmp': 'image',
27
28
  'image/tiff': 'image',
28
- 'image/svg+xml': 'image',
29
29
  'image/heic': 'image',
30
30
  'image/heif': 'image',
31
31
 
@@ -72,35 +72,23 @@ const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
72
72
  'application/x-zip-compressed': 'file',
73
73
  'application/x-rar-compressed': 'file',
74
74
  'application/x-7z-compressed': 'file',
75
-
76
- // Common binary fallback category
77
- 'application/octet-stream': 'file',
78
75
  };
79
76
 
80
- const EXT_TO_MEDIA_TYPE: Record<string, MediaType> = {
81
- // Images
82
- png: 'image', jpg: 'image', jpeg: 'image', webp: 'image', gif: 'image', bmp: 'image', tiff: 'image', svg: 'image', heic: 'image', heif: 'image',
83
-
84
- // Audio
85
- mp3: 'audio', wav: 'audio', ogg: 'audio', aac: 'audio', m4a: 'audio', flac: 'audio',
86
-
87
- // Video
88
- mp4: 'video', mov: 'video', avi: 'video', mkv: 'video', mpeg: 'video', mpg: 'video', '3gp': 'video', '3g2': 'video',
89
-
90
- // Files
91
- pdf: 'file', txt: 'file', md: 'file', csv: 'file', json: 'file',
92
- doc: 'file', docx: 'file', xls: 'file', xlsx: 'file', ppt: 'file', pptx: 'file',
93
- zip: 'file', rar: 'file', '7z': 'file',
94
- };
95
-
96
-
97
77
  export class MediaFieldCrudManager implements FieldCrudManager {
98
78
 
99
79
  constructor(private readonly options: MediaFieldOptions) {
100
80
  }
101
81
 
102
82
  private resolveMediaType(mimetype?: string, filename?: string): MediaType | null {
83
+ const ext = (filename || '').split('.').pop()?.toLowerCase();
84
+ if (ext && DANGEROUS_EXTENSIONS.has(ext)) {
85
+ return null;
86
+ }
87
+
103
88
  const mt = (mimetype || '').toLowerCase().trim();
89
+ if (mt === 'image/svg+xml' || mt === 'text/html' || mt === 'application/xhtml+xml') {
90
+ return null;
91
+ }
104
92
  if (mt && MIME_TO_MEDIA_TYPE[mt]) {
105
93
  return MIME_TO_MEDIA_TYPE[mt];
106
94
  }
@@ -110,8 +98,8 @@ export class MediaFieldCrudManager implements FieldCrudManager {
110
98
  if (mt.startsWith('audio/')) return 'audio';
111
99
  if (mt.startsWith('video/')) return 'video';
112
100
 
113
- // Fallback to extension if provided
114
- const ext = (filename || '').split('.').pop()?.toLowerCase();
101
+ // Fallback to extension if provided (also covers generic/unrecognized
102
+ // mimetypes such as application/octet-stream).
115
103
  if (ext && EXT_TO_MEDIA_TYPE[ext]) {
116
104
  return EXT_TO_MEDIA_TYPE[ext];
117
105
  }
package/src/interfaces.ts CHANGED
@@ -228,6 +228,8 @@ export interface ISolidDatabaseModule {
228
228
 
229
229
  export enum EventType {
230
230
  USER_REGISTERED = 'user.registered',
231
+ MODULE_METADATA_SEEDER_STARTED = 'module-metadata-seeder.started',
232
+ MODULE_METADATA_SEEDER_FINISHED = 'module-metadata-seeder.finished',
231
233
  }
232
234
 
233
235
  export class EventDetails<T> {
@@ -237,6 +239,24 @@ export class EventDetails<T> {
237
239
  ) { }
238
240
  }
239
241
 
242
+ export interface ModuleMetadataSeederOptions {
243
+ modulesToSeed: string[] | null;
244
+ pruneMetadata: boolean;
245
+ seedGlobalMetadata: boolean;
246
+ }
247
+
248
+ export interface ModuleMetadataSeederEventPayload {
249
+ seedRunId: string;
250
+ options: ModuleMetadataSeederOptions;
251
+ startedAt: string;
252
+ finishedAt?: string;
253
+ durationMs?: number;
254
+ success?: boolean;
255
+ seededModuleNames?: string[];
256
+ currentStep?: string;
257
+ errorMessage?: string;
258
+ }
259
+
240
260
  export interface IExtensionUserCreationProvider<T extends User = User, TDto extends CreateUserDto = CreateUserDto> {
241
261
  readonly repo: Repository<T>;
242
262
  buildExtensionEntity(dto: TDto): Promise<T>;
@@ -7,6 +7,7 @@ import { MediaStorageProviderMetadataRepository } from './media-storage-provider
7
7
  import { ModelMetadataRepository } from './model-metadata.repository';
8
8
  import { SecurityRuleRepository } from './security-rule.repository';
9
9
  import { SolidBaseRepository } from './solid-base.repository';
10
+ import { resolveStoredMediaIsPublic } from 'src/services/media-storage.utils';
10
11
 
11
12
  @Injectable()
12
13
  export class MediaRepository extends SolidBaseRepository<Media> {
@@ -44,6 +45,7 @@ export class MediaRepository extends SolidBaseRepository<Media> {
44
45
  id: createDto['mediaStorageProviderMetadataId']
45
46
  },
46
47
  });
48
+ createDto['isPublic'] = resolveStoredMediaIsPublic(createDto['isPublic'], createDto['mediaStorageProviderMetadata']);
47
49
  const media = this.create(createDto);
48
50
  return this.save(media);
49
51
  }