Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

비디오 업로드 예외처리 #164

Merged
merged 8 commits into from
Dec 1, 2023
4 changes: 4 additions & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ lerna-debug.log*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

src/test.py
src/video/common.response.dto.ts
src/video/video.decorator.ts
24 changes: 21 additions & 3 deletions server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Controller, Post, Body, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Controller, Post, Body, Get, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { ApiSuccessResponse } from 'src/decorators/api-succes-response';
import { ApiFailResponse } from 'src/decorators/api-fail-response';
Expand All @@ -8,6 +7,9 @@ import { OAuthFailedException } from 'src/exceptions/oauth-failed.exception';
import { LoginFailException } from 'src/exceptions/login-fail.exception';
import { InvalidRefreshTokenException } from 'src/exceptions/invalid-refresh-token.exception';
import { ProfileUploadRequiredException } from 'src/exceptions/profile-upload-required-exception';
import { PresignedUrlResponseDto } from 'src/presigned-url/dto/presigned-url-response.dto';
import { ProfilePresignedUrlRequestDto } from 'src/presigned-url/dto/profile-presigned-url-request.dto';
import { PresignedUrlService } from 'src/presigned-url/presigned-url.service';
import { AuthService } from './auth.service';
import { SignupRequestDto } from './dto/signup-request.dto';
import { SignupResponseDto } from './dto/signup-response.dto';
Expand All @@ -18,7 +20,10 @@ import { RefreshResponseDto } from './dto/refresh-response.dto';

@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
constructor(
private authService: AuthService,
private presignedUrlService: PresignedUrlService,
) {}

/**
* 회원가입
Expand Down Expand Up @@ -60,4 +65,17 @@ export class AuthController {
): Promise<RefreshResponseDto> {
return this.authService.refresh(refreshRequestDto);
}

/**
* 회원가입 시 프로필 이미지를 PUT하는 url 발급
*/
@Get('signup/presigned-url/profile')
Comment on lines +69 to +72
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 프로필이미지 put presigned Url 발급을 나눈게 굉장히 좋은 거 같아요!
프로필 변경시 발급에서는 본인 Id만 발급가능하도록 할 수 있고, 회원가입시 발급에서는 DB에 없는 Id만 발급가능하게 해서 예외처리 할 수 있으니까요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎㅎ

@ApiSuccessResponse(
200,
'프로필 이미지를 업로드하는 url 발급 성공',
PresignedUrlResponseDto,
)
putProfilePresignedUrl(@Query() query: ProfilePresignedUrlRequestDto) {
return this.presignedUrlService.putProfilePresignedUrl(query);
}
}
3 changes: 2 additions & 1 deletion server/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from 'src/user/schemas/user.schema';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule } from '@nestjs/config';
import { PresignedUrlService } from 'src/presigned-url/presigned-url.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';

Expand All @@ -17,6 +18,6 @@ import { AuthService } from './auth.service';
}),
],
controllers: [AuthController],
providers: [AuthService],
providers: [AuthService, PresignedUrlService],
})
export class AuthModule {}
2 changes: 2 additions & 0 deletions server/src/exceptions/enum/exception.enum.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
enum ErrorCode {
UserConflict = 3001,
VideoConflict = 3002,
BadRequest = 2000,
LoginFail = 1000,
TokenExpired = 1001,
Expand All @@ -21,6 +22,7 @@ enum ErrorCode {

const ErrorMessage = {
[ErrorCode.UserConflict]: '이미 가입된 회원',
[ErrorCode.VideoConflict]: '중복된 Video Id',
[ErrorCode.BadRequest]: '잘못된 요청 형식',
[ErrorCode.LoginFail]: '가입되지 않은 회원',
[ErrorCode.TokenExpired]: 'AccessToken 만료',
Expand Down
9 changes: 9 additions & 0 deletions server/src/exceptions/video-conflict.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { HttpStatus } from '@nestjs/common';
import { ErrorCode } from 'src/exceptions/enum/exception.enum';
import { BaseException } from './base.exception';

export class VideoConflictException extends BaseException {
constructor() {
super(ErrorCode.VideoConflict, HttpStatus.CONFLICT);
}
}
2 changes: 1 addition & 1 deletion server/src/presigned-url/presigned-url.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class PresignedUrlController {
}

/**
* 프로필 이미지를 PUT하는 url 발급
* 프로필 이미지 변경 시 이미지를 PUT하는 url 발급
*/
@Get('profile')
@ApiSuccessResponse(
Expand Down
9 changes: 9 additions & 0 deletions server/src/video/video.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { ActionService } from 'src/action/action.service';
import { NeverViewVideoException } from 'src/exceptions/never-view-video.exception';
import { IgnoreInterceptor } from 'src/decorators/ignore-interceptor';
import { SeedQueryDto } from 'src/action/dto/manifest-query.dto';
import { VideoConflictException } from 'src/exceptions/video-conflict.exception';
import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2';
import { VideoUploadRequiredException } from 'src/exceptions/video-upload-required-exception copy';
import { VideoService } from './video.service';
import { VideoDto } from './dto/video.dto';
import { VideoRatingDTO } from './dto/video-rating.dto';
Expand Down Expand Up @@ -77,6 +80,12 @@ export class VideoController {
)
@Post(':videoId')
@ApiSuccessResponse(201, '비디오 업로드 성공', VideoSummaryResponseDto)
@ApiFailResponse('중복된 비디오 ID', [VideoConflictException])
@ApiFailResponse('잘못된 비디오 ID', [VideoNotFoundException])
@ApiFailResponse('비디오가 업로드 되지 않음', [VideoUploadRequiredException])
@ApiFailResponse('썸네일이 업로드 되지 않음', [
ThumbnailUploadRequiredException,
])
uploadVideo(
@Body() videoDto: VideoDto,
@RequestUser() user: User,
Expand Down
18 changes: 16 additions & 2 deletions server/src/video/video.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* eslint-disable class-methods-use-this */
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Model, Types } from 'mongoose';
import { requestEncoding } from 'src/ncpAPI/requestEncoding';
import { User } from 'src/user/schemas/user.schema';
import { deleteObject } from 'src/ncpAPI/deleteObject';
Expand All @@ -13,6 +13,10 @@ import axios from 'axios';
import * as _ from 'lodash';
import { ActionService } from 'src/action/action.service';
import { createPresignedUrl } from 'src/ncpAPI/presignedURL';
import { VideoConflictException } from 'src/exceptions/video-conflict.exception';
import { checkUpload } from 'src/ncpAPI/listObjects';
import { VideoUploadRequiredException } from 'src/exceptions/video-upload-required-exception copy';
import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2';
import { VideoDto } from './dto/video.dto';
import { Video } from './schemas/video.schema';
import { CategoryEnum } from './enum/category.enum';
Expand Down Expand Up @@ -150,10 +154,20 @@ export class VideoService {
}

async uploadVideo(videoDto: VideoDto, uuid: string, videoId: string) {
if (!Types.ObjectId.isValid(videoId)) throw new VideoNotFoundException();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비디오 업로드 시 비디오를 찾을 수 없는 오류는 조금 어색해보여요
id가 잘못된거니까 BadRequestFormat이 좋아보이는데 어떠신가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 좋아요~!

const checkDuplicate = await this.VideoModel.findOne({ _id: videoId });
if (checkDuplicate) throw new VideoConflictException();

const { videoExtension, thumbnailExtension } = videoDto;
const videoName = `${videoId}.${videoExtension}`;
const thumbnailName = `${videoId}.${thumbnailExtension}`;
// TODO 비디오, 썸네일 업로드 확인

if (!(await checkUpload(process.env.INPUT_BUCKET, videoName))) {
throw new VideoUploadRequiredException();
}
if (!(await checkUpload(process.env.THUMBNAIL_BUCKET, thumbnailName))) {
throw new ThumbnailUploadRequiredException();
}

await requestEncoding(process.env.INPUT_BUCKET, [videoName]);

Expand Down