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

Clova GreenEye API 요청 #241

Merged
merged 9 commits into from
Dec 11, 2023
2 changes: 1 addition & 1 deletion server/src/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class AuthGuard implements CanActivate {
}
try {
const payload = await this.jwtService.verifyAsync(token);
request.user = payload;
request.user = { ...payload };
Copy link
Collaborator

Choose a reason for hiding this comment

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

앗 ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

앗 이건 token 추가하려고 이렇게 했었어요 ㅎㅎ

} catch (e) {
if (e instanceof TokenExpiredError) {
throw new TokenExpiredException();
Expand Down
6 changes: 4 additions & 2 deletions server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export class AuthService {
}

async create(signupRequestDto: SignupRequestDto): Promise<SignupResponseDto> {
const { uuid, profileImageExtension } = signupRequestDto;
const { uuid, profileImageExtension, accessToken } = signupRequestDto;
console.log(accessToken);
await this.checkUserConflict(uuid);
if (
profileImageExtension &&
Expand Down Expand Up @@ -94,7 +95,8 @@ export class AuthService {
}

async signin(signinRequestDto: SigninRequestDto): Promise<SigninResponseDto> {
const { uuid } = signinRequestDto;
const { uuid, accessToken } = signinRequestDto;
console.log(accessToken);
5tarry marked this conversation as resolved.
Show resolved Hide resolved
const user = await this.UserModel.findOne({ uuid });
if (!user) {
throw new LoginFailException();
Expand Down
4 changes: 4 additions & 0 deletions server/src/exceptions/enum/exception.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ enum ErrorCode {
BadThumbnailFormat = 8100,
BadRequestFormat = 8200,
EncodingActionFail = 4280,
GreenEyeApiFail = 4281,
GreenEyeActionFail = 4282,
}

const ErrorMessage = {
Expand All @@ -43,6 +45,8 @@ const ErrorMessage = {
[ErrorCode.VideoUploadRequired]: '비디오를 먼저 업로드 해야합니다.',
[ErrorCode.BadRequestFormat]: '요청 형식이 잘못됨',
[ErrorCode.EncodingActionFail]: '인코딩 액션 실패',
[ErrorCode.GreenEyeApiFail]: 'greeneye api 요청 실패',
[ErrorCode.GreenEyeActionFail]: 'greeneye 액션 실패',
};

export { ErrorCode, ErrorMessage };
9 changes: 9 additions & 0 deletions server/src/exceptions/greeneye-action-fail.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 GreenEyeActionFailException extends BaseException {
constructor() {
super(ErrorCode.GreenEyeActionFail, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
9 changes: 9 additions & 0 deletions server/src/exceptions/greeneye-api-fail.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 GreenEyeApiFailException extends BaseException {
constructor() {
super(ErrorCode.GreenEyeApiFail, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
85 changes: 85 additions & 0 deletions server/src/ncpAPI/cloud-function/greeneye-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-var-requires */
const ffmpeg = require('fluent-ffmpeg');
const fs = require('fs');
const axios = require('axios');

const apiUrl = '비밀';
5tarry marked this conversation as resolved.
Show resolved Hide resolved

async function main(params) {
const results = [];
const { videoUrl, object_name, greenEyeSecret, accessToken } = params;
await createScreanshots(videoUrl);
const files = Array.from(
{ length: 4 },
(_, k) => `${object_name}_${k + 1}.png`,
);
// eslint-disable-next-line no-restricted-syntax
for (const file of files) {
const data = fs.readFileSync(file);
const imageData = data.toString('base64');
// eslint-disable-next-line no-await-in-loop
const result = await greenEye(file, file, imageData, greenEyeSecret);
Comment on lines +22 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

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

for문에서는 await를 하지 않는게 권장이군여
promise 패턴과 재귀를 활용해보면 좋을 수도..? 제가 한번 고민해볼게요

results.push(result);
fs.unlinkSync(file);
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
console.log(results);
if (checkHarmful(results)) {
axios.delete(`http://223.130.136.106/videos/${object_name}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
}
}

const createScreanshots = async (videoUrl) => {
Copy link
Collaborator

@msjang4 msjang4 Dec 10, 2023

Choose a reason for hiding this comment

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

함수 내에서 await를 안하면 async를 안붙여도 되는데 이 함수처럼 promise를 리턴하는 경우 명시성을 위해 일부러 붙여주기도 하더라고요! 그런 점을 고려하신건지 궁금해요~

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 timestamps = Array.from(
{ length: 4 },
() => `${Math.floor(Math.random() * 101)}%`,
);
5tarry marked this conversation as resolved.
Show resolved Hide resolved
console.log(timestamps);
return new Promise((resolve) => {
ffmpeg(videoUrl)
.screenshots({
timestamps,
filename: `%b.png`,
folder: './',
})
.on('end', () => {
resolve();
});
});
};

const greenEye = (requestId, imageName, imageData, greenEyeSecret) => {
const headers = {
'X-GREEN-EYE-SECRET': greenEyeSecret,
'Content-Type': 'application/json',
};

const data = {
version: 'V1',
requestId,
timestamp: Number(new Date()),
images: [
{
name: imageName,
data: imageData,
},
],
};

return axios
.post(apiUrl, data, {
headers,
})
.then((response) => response.data.images.pop().result);
5tarry marked this conversation as resolved.
Show resolved Hide resolved
};

const checkHarmful = (results) => {
return results.some((result) => result.normal.confidence < 0.5);
};
36 changes: 36 additions & 0 deletions server/src/ncpAPI/greenEye.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios from 'axios';
import { GreenEyeApiFailException } from 'src/exceptions/greeneye-api-fail.exception';

export const greenEye = (
requestId: string,
imageName: string,
imageUrl: string,
) => {
const apiUrl = process.env.GREENEYE_API_URL;

const headers = {
'X-GREEN-EYE-SECRET': process.env.GREENEYE_SECRET,
'Content-Type': 'application/json',
};

const data = {
version: 'V1',
requestId,
timestamp: Number(new Date()),
images: [
{
name: imageName,
url: imageUrl,
},
],
};

return axios
.post(apiUrl, data, {
headers,
})
.then((response) => response.data.images.pop().result)
.catch(() => {
throw new GreenEyeApiFailException();
});
};
31 changes: 29 additions & 2 deletions server/src/video/video.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { VideoUploadRequiredException } from 'src/exceptions/video-upload-requir
import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2';
import { BadRequestFormatException } from 'src/exceptions/bad-request-format.exception';
import { EncodingActionFailException } from 'src/exceptions/encoding-action-fail.exception';
import { GreenEyeActionFailException } from 'src/exceptions/greeneye-action-fail.exception';
import { VideoDto } from './dto/video.dto';
import { Video } from './schemas/video.schema';
import { CategoryEnum } from './enum/category.enum';
Expand Down Expand Up @@ -188,7 +189,10 @@ export class VideoService {
thumbnailExtension,
videoExtension,
});
await newVideo.save();
await Promise.all([
newVideo.save(),
this.requestGreenEyeAPI(process.env.INPUT_BUCKET, videoName, videoId),
]);
return { videoId, ...videoDto };
}

Expand All @@ -215,6 +219,26 @@ export class VideoService {
}
}

async requestGreenEyeAPI(
inputBucket: string,
videoName: string,
videoId: string,
) {
const videoUrl = await createPresignedUrl(inputBucket, videoName, 'GET');
const data = {
videoUrl,
object_name: videoId,
greenEyeSecret: process.env.GREENEYE_SECRET,
accessToken: process.env.ADMIN_ACCESS_TOKEN,
};

try {
await axios.post(process.env.GREENEYE_API_URL, data);
} catch (error) {
throw new GreenEyeActionFailException();
}
}

async deleteEncodedVideo(videoId: string) {
const deleteList = await listObjects(process.env.OUTPUT_BUCKET, {
prefix: videoId,
Expand All @@ -234,7 +258,10 @@ export class VideoService {
if (!video) {
throw new VideoNotFoundException();
}
if (video.uploaderId.uuid !== uuid) {
if (
uuid !== process.env.ADMIN_UUID &&
video.uploaderId.uuid !== uuid
) {
5tarry marked this conversation as resolved.
Show resolved Hide resolved
throw new NotYourVideoException();
}
await Promise.all([
Expand Down