Sfoglia il codice sorgente

refactor(server): move asset upload job to domain (#1434)

* refactor: move to domain

* refactor: rename method

* Update comments

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Jason Rasmussen 2 anni fa
parent
commit
42a3149fe3

+ 5 - 42
server/apps/microservices/src/processors/asset-uploaded.processor.ts

@@ -1,50 +1,13 @@
-import { AssetType } from '@app/infra';
-import {
-  IAssetUploadedJob,
-  IMetadataExtractionJob,
-  IThumbnailGenerationJob,
-  IVideoTranscodeJob,
-  QueueName,
-  JobName,
-} from '@app/domain';
-import { InjectQueue, Process, Processor } from '@nestjs/bull';
-import { Job, Queue } from 'bull';
+import { IAssetUploadedJob, JobName, JobService, QueueName } from '@app/domain';
+import { Process, Processor } from '@nestjs/bull';
+import { Job } from 'bull';
 
 @Processor(QueueName.ASSET_UPLOADED)
 export class AssetUploadedProcessor {
-  constructor(
-    @InjectQueue(QueueName.THUMBNAIL_GENERATION)
-    private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
+  constructor(private jobService: JobService) {}
 
-    @InjectQueue(QueueName.METADATA_EXTRACTION)
-    private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
-
-    @InjectQueue(QueueName.VIDEO_CONVERSION)
-    private videoConversionQueue: Queue<IVideoTranscodeJob>,
-  ) {}
-
-  /**
-   * Post processing uploaded asset to perform the following function if missing
-   * 1. Generate JPEG Thumbnail
-   * 2. Generate Webp Thumbnail
-   * 3. EXIF extractor
-   * 4. Reverse Geocoding
-   *
-   * @param job asset-uploaded
-   */
   @Process(JobName.ASSET_UPLOADED)
   async processUploadedVideo(job: Job<IAssetUploadedJob>) {
-    const { asset, fileName } = job.data;
-
-    await this.thumbnailGeneratorQueue.add(JobName.GENERATE_JPEG_THUMBNAIL, { asset });
-
-    // Video Conversion
-    if (asset.type == AssetType.VIDEO) {
-      await this.videoConversionQueue.add(JobName.VIDEO_CONVERSION, { asset });
-      await this.metadataExtractionQueue.add(JobName.EXTRACT_VIDEO_METADATA, { asset, fileName });
-    } else {
-      // Extract Metadata/Exif for Images - Currently the EXIF library on the web cannot extract EXIF for video yet
-      await this.metadataExtractionQueue.add(JobName.EXIF_EXTRACTION, { asset, fileName });
-    }
+    await this.jobService.handleUploadedAsset(job);
   }
 }

+ 3 - 1
server/libs/domain/src/domain.module.ts

@@ -1,14 +1,16 @@
 import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
 import { APIKeyService } from './api-key';
-import { ShareService } from './share';
 import { AuthService } from './auth';
+import { JobService } from './job';
 import { OAuthService } from './oauth';
+import { ShareService } from './share';
 import { INITIAL_SYSTEM_CONFIG, SystemConfigService } from './system-config';
 import { UserService } from './user';
 
 const providers: Provider[] = [
   APIKeyService,
   AuthService,
+  JobService,
   OAuthService,
   SystemConfigService,
   UserService,

+ 1 - 0
server/libs/domain/src/job/index.ts

@@ -1,3 +1,4 @@
 export * from './interfaces';
 export * from './job.constants';
 export * from './job.repository';
+export * from './job.service';

+ 4 - 0
server/libs/domain/src/job/job.repository.ts

@@ -20,6 +20,10 @@ export interface JobCounts {
   waiting: number;
 }
 
+export interface Job<T> {
+  data: T;
+}
+
 export type JobItem =
   | { name: JobName.ASSET_UPLOADED; data: IAssetUploadedJob }
   | { name: JobName.VIDEO_CONVERSION; data: IVideoConversionProcessor }

+ 54 - 0
server/libs/domain/src/job/job.service.spec.ts

@@ -0,0 +1,54 @@
+import { AssetEntity, AssetType } from '@app/infra/db/entities';
+import { newJobRepositoryMock } from '../../test';
+import { IAssetUploadedJob } from './interfaces';
+import { JobName } from './job.constants';
+import { IJobRepository, Job } from './job.repository';
+import { JobService } from './job.service';
+
+const jobStub = {
+  upload: {
+    video: Object.freeze<Job<IAssetUploadedJob>>({
+      data: { asset: { type: AssetType.VIDEO } as AssetEntity, fileName: 'video.mp4' },
+    }),
+    image: Object.freeze<Job<IAssetUploadedJob>>({
+      data: { asset: { type: AssetType.IMAGE } as AssetEntity, fileName: 'image.jpg' },
+    }),
+  },
+};
+
+describe(JobService.name, () => {
+  let sut: JobService;
+  let jobMock: jest.Mocked<IJobRepository>;
+
+  it('should work', () => {
+    expect(sut).toBeDefined();
+  });
+
+  beforeEach(async () => {
+    jobMock = newJobRepositoryMock();
+    sut = new JobService(jobMock);
+  });
+
+  describe('handleUploadedAsset', () => {
+    it('should process a video', async () => {
+      await expect(sut.handleUploadedAsset(jobStub.upload.video)).resolves.toBeUndefined();
+
+      expect(jobMock.add).toHaveBeenCalledTimes(3);
+      expect(jobMock.add.mock.calls).toEqual([
+        [{ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset: { type: AssetType.VIDEO } } }],
+        [{ name: JobName.VIDEO_CONVERSION, data: { asset: { type: AssetType.VIDEO } } }],
+        [{ name: JobName.EXTRACT_VIDEO_METADATA, data: { asset: { type: AssetType.VIDEO }, fileName: 'video.mp4' } }],
+      ]);
+    });
+
+    it('should process an image', async () => {
+      await sut.handleUploadedAsset(jobStub.upload.image);
+
+      expect(jobMock.add).toHaveBeenCalledTimes(2);
+      expect(jobMock.add.mock.calls).toEqual([
+        [{ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset: { type: AssetType.IMAGE } } }],
+        [{ name: JobName.EXIF_EXTRACTION, data: { asset: { type: AssetType.IMAGE }, fileName: 'image.jpg' } }],
+      ]);
+    });
+  });
+});

+ 17 - 0
server/libs/domain/src/job/job.service.ts

@@ -0,0 +1,17 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { IAssetUploadedJob } from './interfaces';
+import { JobUploadCore } from './job.upload.core';
+import { IJobRepository, Job } from './job.repository';
+
+@Injectable()
+export class JobService {
+  private uploadCore: JobUploadCore;
+
+  constructor(@Inject(IJobRepository) repository: IJobRepository) {
+    this.uploadCore = new JobUploadCore(repository);
+  }
+
+  async handleUploadedAsset(job: Job<IAssetUploadedJob>) {
+    await this.uploadCore.handleAsset(job);
+  }
+}

+ 32 - 0
server/libs/domain/src/job/job.upload.core.ts

@@ -0,0 +1,32 @@
+import { AssetType } from '@app/infra/db/entities';
+import { IAssetUploadedJob } from './interfaces';
+import { JobName } from './job.constants';
+import { IJobRepository, Job } from './job.repository';
+
+export class JobUploadCore {
+  constructor(private repository: IJobRepository) {}
+
+  /**
+   * Post processing uploaded asset to perform the following function
+   * 1. Generate JPEG Thumbnail
+   * 2. Generate Webp Thumbnail
+   * 3. EXIF extractor
+   * 4. Reverse Geocoding
+   *
+   * @param job asset-uploaded
+   */
+  async handleAsset(job: Job<IAssetUploadedJob>) {
+    const { asset, fileName } = job.data;
+
+    await this.repository.add({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset } });
+
+    // Video Conversion
+    if (asset.type == AssetType.VIDEO) {
+      await this.repository.add({ name: JobName.VIDEO_CONVERSION, data: { asset } });
+      await this.repository.add({ name: JobName.EXTRACT_VIDEO_METADATA, data: { asset, fileName } });
+    } else {
+      // Extract Metadata/Exif for Images - Currently the EXIF library on the web cannot extract EXIF for video yet
+      await this.repository.add({ name: JobName.EXIF_EXTRACTION, data: { asset, fileName } });
+    }
+  }
+}