asset-interceptor.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
  2. import { GqlExecutionContext } from '@nestjs/graphql';
  3. import { Observable } from 'rxjs';
  4. import { map } from 'rxjs/operators';
  5. import { Type } from '../../../../shared/shared-types';
  6. import { AssetStorageStrategy } from '../../config/asset-storage-strategy/asset-storage-strategy';
  7. import { ConfigService } from '../../config/config.service';
  8. import { Asset } from '../../entity/asset/asset.entity';
  9. /**
  10. * Transforms outputs so that any Asset instances are run through the {@link AssetStorageStrategy.toAbsoluteUrl}
  11. * method before being returned in the response.
  12. */
  13. @Injectable()
  14. export class AssetInterceptor implements NestInterceptor {
  15. private readonly toAbsoluteUrl: AssetStorageStrategy['toAbsoluteUrl'] | undefined;
  16. constructor(private configService: ConfigService) {
  17. const { assetOptions } = this.configService;
  18. if (assetOptions.assetStorageStrategy.toAbsoluteUrl) {
  19. this.toAbsoluteUrl = assetOptions.assetStorageStrategy.toAbsoluteUrl.bind(
  20. assetOptions.assetStorageStrategy,
  21. );
  22. }
  23. }
  24. intercept<T = any>(context: ExecutionContext, call$: Observable<T>): Observable<T> {
  25. const toAbsoluteUrl = this.toAbsoluteUrl;
  26. if (toAbsoluteUrl === undefined) {
  27. return call$;
  28. }
  29. const ctx = GqlExecutionContext.create(context).getContext();
  30. const request = ctx.req;
  31. return call$.pipe(
  32. map(data => {
  33. visitType(data, [Asset, 'productPreview', 'productVariantPreview'], asset => {
  34. if (asset instanceof Asset) {
  35. asset.preview = toAbsoluteUrl(request, asset.preview);
  36. asset.source = toAbsoluteUrl(request, asset.source);
  37. } else {
  38. asset = toAbsoluteUrl(request, asset);
  39. }
  40. return asset;
  41. });
  42. return data;
  43. }),
  44. );
  45. }
  46. }
  47. /**
  48. * Traverses the object and when encountering a property with a value which
  49. * is an instance of class T, invokes the visitor function on that value.
  50. */
  51. function visitType<T>(obj: any, types: Array<Type<T> | string>, visit: (instance: T | string) => T | string) {
  52. const keys = Object.keys(obj || {});
  53. for (const key of keys) {
  54. const value = obj[key];
  55. for (const type of types) {
  56. if (typeof type === 'string') {
  57. if (type === key) {
  58. obj[key] = visit(value);
  59. }
  60. } else if (value instanceof type) {
  61. visit(value);
  62. }
  63. }
  64. if (typeof value === 'object') {
  65. visitType(value, types, visit);
  66. }
  67. }
  68. }