Skip to content

Commit 7630c9e

Browse files
authored
Merge pull request #197 from boostcampwm-2024/feature-be-#85
Object Storage에 이미지를 업로드하는 API
2 parents eda692a + 00bb7f4 commit 7630c9e

File tree

14 files changed

+1381
-2
lines changed

14 files changed

+1381
-2
lines changed

.vscode/settings.json

Whitespace-only changes.

apps/backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"test:e2e": "jest --config ./test/jest-e2e.json"
2121
},
2222
"dependencies": {
23+
"@aws-sdk/client-s3": "^3.693.0",
2324
"@nestjs/common": "^10.0.0",
2425
"@nestjs/config": "^3.3.0",
2526
"@nestjs/core": "^10.0.0",
@@ -31,6 +32,7 @@
3132
"@nestjs/swagger": "^8.0.5",
3233
"@nestjs/typeorm": "^10.0.2",
3334
"@nestjs/websockets": "^10.4.8",
35+
"@types/multer": "^1.4.12",
3436
"class-transformer": "^0.5.1",
3537
"class-validator": "^0.14.1",
3638
"lib0": "^0.2.98",

apps/backend/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Node } from './node/node.entity';
1212
import { YjsModule } from './yjs/yjs.module';
1313
import * as path from 'path';
1414
import { ServeStaticModule } from '@nestjs/serve-static';
15+
import { UploadModule } from './upload/upload.module';
1516

1617
@Module({
1718
imports: [
@@ -37,6 +38,7 @@ import { ServeStaticModule } from '@nestjs/serve-static';
3738
PageModule,
3839
EdgeModule,
3940
YjsModule,
41+
UploadModule,
4042
],
4143
controllers: [AppController],
4244
providers: [AppService],
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { BadRequestException } from '@nestjs/common';
2+
3+
export class InvalidFileException extends BadRequestException {
4+
constructor() {
5+
super(`유효하지 않은 파일입니다.`);
6+
}
7+
}

apps/backend/src/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AppModule } from './app.module';
33
import { HttpExceptionFilter } from './filter/http-exception.filter';
44
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
55
import { IoAdapter } from '@nestjs/platform-socket.io';
6+
import * as express from 'express';
67

78
import * as dotenv from 'dotenv';
89
dotenv.config();
@@ -13,12 +14,13 @@ async function bootstrap() {
1314
app.useWebSocketAdapter(new IoAdapter(app));
1415
app.useGlobalFilters(new HttpExceptionFilter());
1516
app.setGlobalPrefix('api');
17+
app.use(express.urlencoded({ extended: true }));
1618

1719
const config = new DocumentBuilder()
1820
.setTitle('OctoDocs')
1921
.setDescription('OctoDocs API 명세서')
2022
.build();
21-
console.log(process.env.origin);
23+
2224
const documentFactory = () => SwaggerModule.createDocument(app, config);
2325
SwaggerModule.setup('api', app, documentFactory);
2426
app.enableCors({
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString } from 'class-validator';
3+
4+
export class ImageUploadResponseDto {
5+
@ApiProperty({
6+
example: '이미지 업로드 성공',
7+
description: 'api 요청 결과 메시지',
8+
})
9+
@IsString()
10+
message: string;
11+
12+
@ApiProperty({
13+
example: 'https://kr.object.ncloudstorage.com/octodocs-static/uploads/name',
14+
description: '업로드된 이미지 url',
15+
})
16+
@IsString()
17+
url: string;
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { S3Client } from '@aws-sdk/client-s3';
2+
import { Provider } from '@nestjs/common';
3+
import { ConfigService } from '@nestjs/config';
4+
5+
export const S3_CLIENT = 'S3_CLIENT';
6+
7+
export const s3ClientProvider: Provider = {
8+
provide: S3_CLIENT,
9+
useFactory: (configService: ConfigService) => {
10+
return new S3Client({
11+
region: configService.get('CLOUD_REGION'),
12+
endpoint: configService.get('CLOUD_ENDPOINT'),
13+
credentials: {
14+
accessKeyId: configService.get('CLOUD_ACCESS_KEY_ID'),
15+
secretAccessKey: configService.get('CLOUD_SECRET_ACCESS_KEY'),
16+
},
17+
});
18+
},
19+
inject: [ConfigService],
20+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { InvalidFileException } from '../exception/upload.exception';
2+
3+
export const MAX_FILE_SIZE = 1024 * 1024 * 5; // 5MB
4+
5+
export const imageFileFilter = (
6+
req: any,
7+
file: Express.Multer.File,
8+
callback: (error: Error | null, acceptFile: boolean) => void,
9+
) => {
10+
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
11+
return callback(new InvalidFileException(), false);
12+
}
13+
callback(null, true);
14+
};
15+
16+
export const uploadOptions = {
17+
fileFilter: imageFileFilter,
18+
limits: {
19+
fileSize: MAX_FILE_SIZE,
20+
},
21+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
Controller,
3+
Post,
4+
UploadedFile,
5+
UseInterceptors,
6+
} from '@nestjs/common';
7+
import { FileInterceptor } from '@nestjs/platform-express';
8+
import { UploadService } from './upload.service';
9+
import { uploadOptions } from './upload.config';
10+
import { ImageUploadResponseDto } from './dtos/imageUploadResponse.dto';
11+
12+
export enum UploadResponseMessage {
13+
UPLOAD_IMAGE_SUCCESS = '이미지 업로드 성공',
14+
}
15+
16+
@Controller('upload')
17+
export class UploadController {
18+
constructor(private readonly uploadService: UploadService) {}
19+
20+
@Post('image')
21+
@UseInterceptors(FileInterceptor('file', uploadOptions))
22+
async uploadImage(
23+
@UploadedFile() file: Express.Multer.File,
24+
): Promise<ImageUploadResponseDto> {
25+
const result = await this.uploadService.uploadImageToCloud(file);
26+
return {
27+
message: UploadResponseMessage.UPLOAD_IMAGE_SUCCESS,
28+
url: result,
29+
};
30+
}
31+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { UploadService } from './upload.service';
3+
import { UploadController } from './upload.controller';
4+
import { ConfigModule } from '@nestjs/config';
5+
import { s3ClientProvider } from './s3-client.provider';
6+
7+
@Module({
8+
imports: [ConfigModule],
9+
controllers: [UploadController],
10+
providers: [UploadService, s3ClientProvider],
11+
exports: [UploadService],
12+
})
13+
export class UploadModule {}

0 commit comments

Comments
 (0)