Skip to content

Commit 2a9cfe3

Browse files
committed
feat: Implement file upload and processing for listings
- Added `FileService` to handle buffer to base64 conversion and vice versa. - Integrated `FileService` into `ListingService` to convert image buffers to base64 strings before queuing them for processing. - Created `UploadListingImageDto` to define the structure of the image upload payload. - Implemented `ListingProducer` to add image upload jobs to the queue. - Implemented `ListingConsumer` to process image upload jobs, including converting base64 strings back to buffers. - Registered `FileService` in `UtilitiesModule` and imported it in `ListingModule`. - Added unit tests for `FileService` to verify buffer to base64 conversion and vice versa.
1 parent 1c71b24 commit 2a9cfe3

9 files changed

+97
-9
lines changed

src/app.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Module } from '@nestjs/common';
22
import { CoreModule } from './core/core.module';
33
import { ListingModule } from './modules/listing/listing.module';
4+
import { UtilitiesModule } from './utilities/utilities.module';
45

56
@Module({
6-
imports: [CoreModule, ListingModule],
7+
imports: [CoreModule, ListingModule, UtilitiesModule],
78
providers: [],
89
controllers: [],
910
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Listing } from '@prisma/client';
2+
import { IsInt, IsNotEmpty, IsString } from 'class-validator';
3+
4+
export class UploadListingImageDto {
5+
@IsString()
6+
@IsNotEmpty()
7+
base64File: string;
8+
9+
@IsString()
10+
@IsNotEmpty()
11+
mimeType: string;
12+
13+
@IsInt()
14+
listingId: Listing[`id`];
15+
}

src/modules/listing/listing.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ListingConsumer } from './queue/listing.consumer';
77
import { LISTING_QUEUE } from '../../core/queue/queue.constants';
88
import { BullBoardModule } from '@bull-board/nestjs';
99
import { BullAdapter } from '@bull-board/api/bullAdapter';
10+
import { UtilitiesModule } from '../../utilities/utilities.module';
1011

1112
@Module({
1213
imports: [
@@ -15,6 +16,7 @@ import { BullAdapter } from '@bull-board/api/bullAdapter';
1516
name: LISTING_QUEUE,
1617
adapter: BullAdapter,
1718
}),
19+
UtilitiesModule,
1820
],
1921
controllers: [ListingController],
2022
providers: [ListingService, ListingProducer, ListingConsumer],

src/modules/listing/listing.service.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common';
22
import { CreateListingDto } from './dto/create-listing.dto';
33
import { DatabaseService } from '../../database/database.service';
44
import { ListingProducer } from './queue/listing.producer';
5+
import { FileService } from '../../utilities/file/file.service';
56

67
@Injectable()
78
export class ListingService {
89
constructor(
910
private readonly databaseService: DatabaseService,
1011
private readonly listingQueue: ListingProducer,
12+
private readonly fileService: FileService,
1113
) {}
1214

1315
async create({
@@ -21,8 +23,11 @@ export class ListingService {
2123
data,
2224
});
2325
for (const image of images) {
24-
// send image to queue
25-
await this.listingQueue.createListingImage(image);
26+
await this.listingQueue.uploadListingImage({
27+
base64File: this.fileService.bufferToBase64(image.buffer),
28+
mimeType: image.mimetype,
29+
listingId: listing.id,
30+
});
2631
}
2732
return listing;
2833
}
+10-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import { Process, Processor } from '@nestjs/bull';
22
import { Job } from 'bull';
3+
import { UploadListingImageDto } from '../dto/upload-listing-image.dto';
34
import { LISTING_QUEUE } from '../../../core/queue/queue.constants';
45
import { BaseConsumer } from '../../../core/queue/base.consumer';
56
import { LoggerService } from '../../../core/logger/logger.service';
7+
import { FileService } from '../../../utilities/file/file.service';
68

79
@Processor(LISTING_QUEUE)
810
export class ListingConsumer extends BaseConsumer {
9-
constructor(logger: LoggerService) {
11+
constructor(
12+
logger: LoggerService,
13+
private readonly fileService: FileService,
14+
) {
1015
super(logger);
1116
}
1217

1318
@Process(`createListingImage`)
14-
async createListingImage(job: Job<Express.Multer.File>) {
15-
console.log(`createListingImage filename:`, job.data.originalname);
16-
return job.data;
19+
async createListingImage(job: Job<UploadListingImageDto>) {
20+
const buffer = this.fileService.base64ToBuffer(job.data.base64File);
21+
// TODO: upload file to Google Cloud Storage
22+
// TODO: store respective Google Cloud Storage URL in database
1723
}
1824
}

src/modules/listing/queue/listing.producer.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { Injectable } from '@nestjs/common';
22
import { InjectQueue } from '@nestjs/bull';
33
import { Queue } from 'bull';
44
import { LISTING_QUEUE } from '../../../core/queue/queue.constants';
5+
import { UploadListingImageDto } from '../dto/upload-listing-image.dto';
56

67
@Injectable()
78
export class ListingProducer {
89
constructor(@InjectQueue(LISTING_QUEUE) private listingQueue: Queue) {}
910

10-
async createListingImage(image: Express.Multer.File) {
11-
return await this.listingQueue.add(`createListingImage`, image);
11+
async uploadListingImage(payload: UploadListingImageDto) {
12+
return await this.listingQueue.add(`createListingImage`, payload);
1213
}
1314
}
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { FileService } from './file.service';
3+
4+
describe('FileService', () => {
5+
let service: FileService;
6+
7+
beforeEach(async () => {
8+
const module: TestingModule = await Test.createTestingModule({
9+
providers: [FileService],
10+
}).compile();
11+
12+
service = module.get<FileService>(FileService);
13+
});
14+
15+
it('should be defined', () => {
16+
expect(service).toBeDefined();
17+
});
18+
19+
describe(`bufferToBase64`, () => {
20+
it(`should convert a buffer to a base64 string`, () => {
21+
const buffer = Buffer.from(`Hello, World!`);
22+
const base64 = `SGVsbG8sIFdvcmxkIQ==`; // Base64 encoded string of 'Hello, World!'
23+
24+
const result = service.bufferToBase64(buffer);
25+
expect(result).toBe(base64);
26+
});
27+
});
28+
29+
describe(`base64ToBuffer`, () => {
30+
it(`should convert a base64 string to a buffer`, () => {
31+
const base64 = `SGVsbG8sIFdvcmxkIQ==`; // Base64 encoded string of 'Hello, World!'
32+
const buffer = Buffer.from(`Hello, World!`);
33+
34+
const result = service.base64ToBuffer(base64);
35+
expect(result).toEqual(buffer);
36+
});
37+
});
38+
});

src/utilities/file/file.service.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Injectable } from '@nestjs/common';
2+
3+
@Injectable()
4+
export class FileService {
5+
bufferToBase64(buffer: Buffer): string {
6+
return buffer.toString(`base64`);
7+
}
8+
9+
base64ToBuffer(base64String: string): Buffer {
10+
return Buffer.from(base64String, `base64`);
11+
}
12+
}

src/utilities/utilities.module.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Module } from '@nestjs/common';
2+
import { FileService } from './file/file.service';
3+
4+
@Module({
5+
providers: [FileService],
6+
exports: [FileService],
7+
})
8+
export class UtilitiesModule {}

0 commit comments

Comments
 (0)