Skip to content

Commit fcecc31

Browse files
committed
Add file upload functionality to listing creation
- Implement MaxFileCountValidationPipe for limiting file uploads - Update ListingController to handle file uploads with FilesInterceptor - Modify ListingService to process uploaded images - Add file validation for type and size in ListingController - Update CreateListingDto with proper validation and type conversion
1 parent 337f028 commit fcecc31

7 files changed

+118
-6
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@types/cache-manager-redis-store": "^2.0.4",
5454
"@types/express": "^4.17.17",
5555
"@types/jest": "^29.5.2",
56+
"@types/multer": "^1.4.12",
5657
"@types/node": "^20.3.1",
5758
"@types/supertest": "^6.0.0",
5859
"@typescript-eslint/eslint-plugin": "^8.0.0",

pnpm-lock.yaml

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { BadRequestException } from '@nestjs/common';
2+
import { MaxFileCountValidationPipe } from './max-file-count-validation.pipe';
3+
4+
describe(`MaxFileCountPipe`, () => {
5+
let maxFileCountPipe: MaxFileCountValidationPipe;
6+
7+
beforeEach(async () => {
8+
maxFileCountPipe = new MaxFileCountValidationPipe(2);
9+
});
10+
11+
it(`should throw an exception if number of files is greater than the max allowed`, () => {
12+
// Arrange
13+
const file = {} as Express.Multer.File;
14+
15+
// Act
16+
const result = () => maxFileCountPipe.transform([file, file, file]);
17+
18+
// Assert
19+
expect(result).toThrow(BadRequestException);
20+
});
21+
22+
it(`should return files if count less than the max allowed`, () => {
23+
// Arrange
24+
const file = {} as Express.Multer.File;
25+
const payload = [file];
26+
27+
// Act
28+
const result = maxFileCountPipe.transform(payload);
29+
30+
// Assert
31+
expect(result).toEqual(payload);
32+
});
33+
34+
it(`should return files if count equal to max allowed`, () => {
35+
// Arrange
36+
const file = {} as Express.Multer.File;
37+
const payload = [file, file];
38+
39+
// Act
40+
const result = maxFileCountPipe.transform(payload);
41+
42+
// Assert
43+
expect(result).toEqual(payload);
44+
});
45+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
2+
3+
@Injectable()
4+
export class MaxFileCountValidationPipe implements PipeTransform {
5+
constructor(private readonly maxFiles: number) {}
6+
7+
transform(files: Express.Multer.File[]) {
8+
if (files.length > this.maxFiles) {
9+
throw new BadRequestException(
10+
`Maximum of ${this.maxFiles} file uploads allowed`,
11+
);
12+
}
13+
return files;
14+
}
15+
}

src/modules/listing/dto/create-listing.dto.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IsInt, IsNotEmpty, IsOptional, IsString, Min } from 'class-validator';
22
import { CreateListingInput } from '../listing.types';
3+
import { Type } from 'class-transformer';
34

45
export class CreateListingDto implements CreateListingInput {
56
@IsString()
@@ -29,17 +30,21 @@ export class CreateListingDto implements CreateListingInput {
2930

3031
@IsInt()
3132
@Min(0)
33+
@Type(() => Number)
3234
price: number;
3335

3436
@IsInt()
3537
@Min(0)
38+
@Type(() => Number)
3639
bathrooms: number;
3740

3841
@IsInt()
3942
@Min(0)
43+
@Type(() => Number)
4044
bedrooms: number;
4145

4246
@IsInt()
4347
@Min(0)
48+
@Type(() => Number)
4449
squareMeters: number;
4550
}
+30-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
1-
import { Controller, Post, Body } from '@nestjs/common';
1+
import {
2+
Controller,
3+
Post,
4+
Body,
5+
UseInterceptors,
6+
UploadedFiles,
7+
FileTypeValidator,
8+
ParseFilePipe,
9+
MaxFileSizeValidator,
10+
} from '@nestjs/common';
211
import { ListingService } from './listing.service';
312
import { CreateListingDto } from './dto/create-listing.dto';
13+
import { FilesInterceptor } from '@nestjs/platform-express';
14+
import { MaxFileCountValidationPipe } from '../../common/pipes/max-file-count-validation/max-file-count-validation.pipe';
415

516
@Controller('listing')
617
export class ListingController {
718
constructor(private readonly listingService: ListingService) {}
819

920
@Post()
10-
create(@Body() createListingDto: CreateListingDto) {
11-
return this.listingService.create(createListingDto);
21+
@UseInterceptors(FilesInterceptor(`images`))
22+
create(
23+
@Body() createListingDto: CreateListingDto,
24+
@UploadedFiles(
25+
new ParseFilePipe({
26+
validators: [
27+
new FileTypeValidator({ fileType: `.(png|jpeg|jpg)` }),
28+
new MaxFileSizeValidator({ maxSize: 1024 * 1024 }),
29+
],
30+
}),
31+
new MaxFileCountValidationPipe(10),
32+
)
33+
files: Express.Multer.File[],
34+
) {
35+
return this.listingService.create({
36+
data: createListingDto,
37+
images: files,
38+
});
1239
}
1340
}

src/modules/listing/listing.service.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,19 @@ import { DatabaseService } from '../../database/database.service';
66
export class ListingService {
77
constructor(private readonly databaseService: DatabaseService) {}
88

9-
async create(createListingDto: CreateListingDto) {
10-
return await this.databaseService.listing.create({
11-
data: createListingDto,
9+
async create({
10+
data,
11+
images,
12+
}: {
13+
data: CreateListingDto;
14+
images: Express.Multer.File[];
15+
}) {
16+
const listing = await this.databaseService.listing.create({
17+
data,
1218
});
19+
for (const image of images) {
20+
console.log(`image:`, image);
21+
}
22+
return listing;
1323
}
1424
}

0 commit comments

Comments
 (0)