Wavy API 서버는 NestJS를 기반으로 위 개발 스택을 사용하여 개발되었습니다.
Wavy는 www.wavy.dance에서 만나보실 수 있습니다.
Swagger로 작성된 API 문서는 api.prod.wavy.dance/api/docs에서 확인하실 수 있습니다.
git clone https://github.com/EO2-WAVY/WavyBackend.git
npm insall
실행할 환경에 알맞은 파일명으로 환경변수를 세팅해주세요.
개발용 환경변수 파일명 : .env.dev
테스트용 환경변수 파일명 : .env.test
운영용 환경변수 파일명 : .env.prod
DB_HOST=[DB 호스트]
DB_PORT=[DB 포트]
DB_USER=[DB User]
DB_PW=[DB Password]
DB_NAME=[DB명]
SECRET_KEY=[JWT 암호화에 사용되는 키]
SYSTEM_MBR_SEQ=[시스템 유저 일련번호]
KAKAO_LOGIN_HOST=kauth.kakao.com
KAKAO_LOGOUT_HOST=kapi.kakao.com
KAKAO_CLIENT_ID=[카카오 클라이언트 아이디]
KAKAO_GRANT_TYPE=authorization_code
AWS_REGION=[AWS 리전]
AWS_PROFILE=[AWS Profile명]
AWS_PRIVATE_KEY_LOCATION=[Cloud Front 접근용 ssh키 파일 경로]
AWS_USER_VIDEO_UPLOAD_S3_BUCKET=[사용자 녹화 영상 업로드 버킷명]
AWS_USER_VIDEO_CF_ENDPOINT=[사용자 녹화 영상 Cloud Front 엔드포인트 URL]
AWS_USER_VIDEO_CONVERTED_BUCKET=[변환 완료된 사용자 녹화 영상 버킷명]
AWS_USER_IMAGE_S3_BUCKET = [사용자 프로필 이미지 버킷명]
DEFAULT_USER_IMAGE = [사용자 기본 이미지 S3 키]
AWS_AN_JSON_BUCKET_ENDPOINT=[분석 결과 JSON 버킷 엔드포인트 URL]
AWS_EXT_JSON_BUCKET_ENDPOINT=[학습용 영상 JSON 버킷 엔드포인트 URL]
# 각 힝목은 ' '으로 구분합니다.
# ex) http://localhost:3000 https://wavy.dance https://www.wavy.dance
CORS_ORIGINS=[CORS Allowed Origins]
CORS_METHODS=[CORS Allowed Methods]
CORS_HEADERS=[CORS Allowed Headers]
USER_VIDEO_ANALYST_REQ_URL=[사용자 영상 분석 요청 API URL]
# Run as devleopment
npm run start:dev
# Run as test
npm run start:test
#Run as production
npm run start:prod
NestJS의 Config Module과 Joi의 Validation 기능을 이용해 설정을 관리하고 있습니다. NODE_ENV의 값에 알맞은 환경변수 파일을 자동으로 불러와 사용합니다.
src/app.module.ts line 44
ConfigModule.forRoot({
isGlobal: true,
envFilePath: getEnvFilePath(process.env.NODE_ENV),
validationSchema: Joi.object({
NODE_ENV: Joi.string().valid('dev', 'test', 'prod').required(),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.string().required(),
DB_USER: Joi.string().required(),
DB_PW: Joi.string().required(),
DB_NAME: Joi.string().required(),
...
TypeORM 모듈을 사용해 DB를 ORM 방식으로 관리합니다. Swagger에 표시되는 API 프로퍼티에 대한 설명도 함께 관리합니다.
src/members/entities/members.entity.ts line 45
@Entity()
@Unique(['mbrKakaoSeq'])
export class Member extends CoreEntity {
@PrimaryGeneratedColumn({ name: 'mbr_seq', type: 'bigint' })
@IsNumberString()
@ApiProperty({ name: 'mbrSeq', description: '회원 일련번호', type: String })
mbrSeq: string;
@Column({ name: 'mbr_email', type: 'varchar', length: 255 })
@ApiProperty({ description: '회원 이메일', type: String })
@IsEmail()
mbrEmail: string;
...
Kakao OAuth를 사용해 로그인하고 JWT로 사용자를 인증합니다. MiddleWare를 사용하여 Authorization 헤더를 파싱하고 Guard를 사용해 API에 대한 접근 권한을 관리합니다.
src/auth/auth.service.ts line 44
// Jwt 발급 코드
const { code, redirectUrl } = getJwtInput;
const kakaoTokens = await this.getKakaoToken(code, redirectUrl);
const mbrKakaoSeq = await this.getMbrKakaoSeq(kakaoTokens.accessToken);
const { member } = await this.memberService.getMemberByKakaoSeq(mbrKakaoSeq);
const jwtToken = this.createJwt(
member?.mbrSeq,
kakaoTokens.accessToken,
this.UnixEpochTimestamp() + kakaoTokens.expiresIn,
);
return { ok: true, token: jwtToken };
src/auth/auth-jwt.middleware.ts line 18
// Authorization 헤더 파싱 코드
const token = req.headers['authorization'].toString().split(' ')[1];
const decoded = this.jwtService.verify(token);
if (typeof decoded === 'object') {
req.headers['x-jwt-decoded'] = JSON.stringify(decoded);
if (decoded.hasOwnProperty('mbrSeq')) {
const member = await this.member.findOne({
mbrSeq: decoded.mbrSeq,
});
if (!member) {
throw new Error(`Cannot find Member by mbrSeq(${decoded.mbrSeq}).`);
}
req.headers['x-member'] = JSON.stringify(member);
}
}
사용자 영상 분석과정은 다음과 같습니다.
- AWS S3 Signed URL을 사용해 사용자 영상 업로드
src/aws/aws-user-video.service.ts line 46
const bucket = this.config.get('AWS_USER_VIDEO_UPLOAD_S3_BUCKET');
const s3ObjectName = await this.getS3ObjectName(bucket);
const contentType = 'video/webm';
const signedUrl = await this.getS3SignedUrl(
bucket,
s3ObjectName,
contentType,
'putObject',
);
return { ok: true, s3ObjectName, signedUrl };
- AWS Lambda에 영상 포맷 변환 요청
- MachineLearningAPIServer에 분석 요청
src/analyses/analyses.service.ts line 293
const response = await this.registerAnalysisInQueue(
savedAnalysis,
refVideo,
jwt,
mirrorEffect,
);
if (response) {
newAnalysis.anStatusCode = AnalysisStatusCode.PROCESSING;
await this.analyses.save(newAnalysis);
} else {
newAnalysis.anStatusCode = AnalysisStatusCode.FAIL;
await this.analyses.save(newAnalysis);
return { ok: false, error: '분석 요청에 실패했습니다.' };
}
- MachineLearningAPIServer에서 분석 완료시 분석 완료 API를 호출
src
├── [Module Name]
│ └── dtos
│ └── *.dto.ts
│ └── entities (optional)
│ └── *.entity.ts
│ └── *.service.ts
│ └── *.controller.ts
│ └── *.module.ts
├── .env
├── app.module.ts
└── main.ts
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @nestjs/[email protected]
├── @types/[email protected]
├── @types/[email protected]
├── @types/[email protected]
├── @types/[email protected]
├── @typescript-eslint/[email protected]
├── @typescript-eslint/[email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected]
해당 프로젝트는 소프트웨어 마에스트로 사업의 지원을 받아 개발되었습니다.
FE: hyesungoh | AI: haeseoklee | BE: Yeonwu |
---|---|---|
Wavy는 MIT라이선스를 따릅니다.