Split DamModule into composable sub-modules - #6151
Conversation
65935f2 to
4eb9d81
Compare
## Problem `FilesService` and the DAM files controller injected `ContentScopeService` only to call its `scopesAreEqual` method — a stateless deep comparison. That ties the DAM to the user-permissions layer for a check that needs no state. `FoldersService` ran the same comparison inline via `lodash.isequal`, so the rule existed twice. ## Solution The comparison lives in `contentScopesAreEqual` now, next to the `ContentScope` interface in `user-permissions/`: - `ContentScopeService.scopesAreEqual` delegates to it, so its callers are unaffected. - The DAM services and the files controller call it directly and no longer inject `ContentScopeService`. No changeset because `contentScopesAreEqual` is internal and `ContentScopeService.scopesAreEqual` keeps its signature, so the public API is unchanged. ## Verification - New unit tests in `content-scopes-are-equal.spec.ts` cover a class instance against a plain object plus differing values, differing keys and `undefined` scopes ## Outlook First of three stacked pull requests that let `DamFilesModule` be registered without `UserPermissionsModule`: #6151 splits `DamModule` into sub-modules, #6153 makes the DAM's scope-based access control optional. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Computing the dominant color of an uploaded image was the only part of FilesService that used imgproxy, which made ImgproxyModule a hard dependency of the whole DAM file handling. Move the computation into DamDominantColorService, so ImgproxyService is injected in one small service instead. FilesService.calculateDominantColor remains as a deprecated delegator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…BlocksModule DamModule registered file handling, image scaling, the block transformers and the dependents resolver as one unit, so a project that only needs DAM file upload and storage had to bring ImgproxyModule and DependenciesModule along with it. Move the registrations into four sub-modules and turn DamModule into a facade that composes all of them, so DamModule.register() behaves as before. FilesService receives the dominant color calculator through the optional DAM_DOMINANT_COLOR_CALCULATOR token, which DamImagesModule provides. Without that module, uploads skip the color. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4eb9d81 to
0c0c331
Compare
DamModule registers the four DAM sub-modules internally, so registering one of them alongside DamModule goes wrong in a different way each time: DamFilesModule and DamImagesModule mount their routes twice, DamDependentsModule adds a second resolver for the dependents field and breaks the schema, DamBlocksModule leaves two instances of each transformer service with one silently unused. Throw on the second register() call instead, naming the module and what to do about it, the way UserPermissionsModule already guards its permission enum. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DamImagesModule, DamBlocksModule and DamDependentsModule are composition details of DamModule, and registering one of them next to DamModule is now an error. Exporting them advertises a setup that isn't supported, so keep them internal. DamFilesModule stays exported because it is the one that can be registered on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The service is an implementation detail of DamImagesModule and is not meant to be injected by projects, so it does not belong in the public API. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The last use moved out with the dominant color computation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BlobStorageBackendService imports nothing from dam/, so there is no import cycle the forwardRef would resolve here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both new modules pulled a symbol from the package index, which imports the modules back. Importing the source files directly avoids the cycle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Registering a DAM module twice now throws, so the changeset can no longer claim that existing setups behave identically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DamModule accepts a damConfig without a basePath and falls back to "dam". DamFilesModule required it, so a project registering the module on its own could not pass the damConfig it already had. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
c2b3edb to
05fd2a4
Compare
| export { HashImageParams, ImageParams } from "./dam/images/dto/image.params"; | ||
| export { ImageCropAreaInput } from "./dam/images/dto/image-crop-area.input"; | ||
| export { ImageCropArea } from "./dam/images/entities/image-crop-area.entity"; | ||
| export { FileImagesResolver } from "./dam/images/file-image.resolver"; |
There was a problem hiding this comment.
Out-of-scope: I don't remember why we initially exported this resolver, but IMO this shouldn't be part of the public API.
|
Needs human review — large diff, foundational for an auth-related change. 10 commits, +408/-216 across 16 files, splitting Generated by Claude Code |
Problem
DamModuleregistered file handling, image scaling, the block transformers and the dependents resolver as a single unit. A project that only needs DAM file upload and storage therefore had to bringImgproxyModuleandDependenciesModulealong with it.Computing the dominant color of an uploaded image was the one place where
FilesServiceused imgproxy, which is what made that dependency reach into file handling at all.Solution
DamDominantColorServicenow holds the dominant color computation, andDamModuleis a facade composing four sub-modules.DamModule.register()keeps its signature and still composes all of them.DamFilesModuleBlobStorageModule,UserPermissionsModule, MikroORMDamImagesModuleFileImagesResolverImgproxyModule,BlobStorageModule,UserPermissionsModule, MikroORM,DamFilesModuleDamBlocksModuleDamFilesModule,DamImagesModuleDamDependentsModuledependentsfield onDamFileDependenciesModuleFilesServicereceives the dominant color calculator through the optionalDAM_DOMINANT_COLOR_CALCULATORtoken, whichDamImagesModuleprovides. Without that module, uploads skip the color andFilesService.calculateDominantColorreturnsundefined. The contract is a standalone interface (DominantColorCalculatorInterface), so the files code has no runtime link to the imgproxy-backed service — the emittedfiles.service.jscontains norequireof anything underdam/images.DamModuleand each sub-module throw when they are registered more than once in the same process. A second registration would mount the DAM routes twice and add thedependentsfield to the file type again, so it never worked as intended.FileImagesResolvermoved fromdam/files/todam/images/, so its folder matches the module that registers it.Decisions
DamFilesModuleis the only sub-module that is exported. It is the one a project can register on its own.DamImagesModule,DamBlocksModuleandDamDependentsModuleeach need providers thatDamFilesModuleregisters, so exporting them would offer combinations that fail at DI time.DamDominantColorServicestays internal for the same reason — it is an implementation detail ofDamImagesModule, reachable throughFilesService.calculateDominantColor.Verification
DamFilesModule,DamImagesModule,DamBlocksModuleandDamDependentsModuleall reporting initialized dependencies, and registers thecms.dam.calculateDominantImageColorcommandschema.gqlandblock-meta.jsonregenerate unchanged, so the split does not affect the GraphQL schemaOutlook
The goal is that
DamFilesModulecan be registered on its own with as few dependencies as possible, for a service that only stores and serves DAM files:What
DamFilesModulestill needs to resolve:BlobStorageBackendServiceFilesService,FoldersService,FilesControllerBlobStorageModuleEntityManager/MikroORMMikroOrmModule.forRootACCESS_CONTROL_SERVICEFilesController,FoldersControllerUserPermissionsModule