Skip to content

EO2-WAVY/WavyBackend

Repository files navigation


Wavy - API Server

인곡지λŠ₯ 기반의 λ§žμΆ€ν˜• K-POP λŒ„μŠ€ ν•™μŠ΅ μ„œλΉ„μŠ€






Introduction

Npm NestJS TypeORM Prettier PostgreSQL PostgreSQL ESLint pm2

Wavy API μ„œλ²„λŠ” NestJSλ₯Ό 기반으둜 μœ„ 개발 μŠ€νƒμ„ μ‚¬μš©ν•˜μ—¬ κ°œλ°œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Getting Started

WavyλŠ” www.wavy.danceμ—μ„œ λ§Œλ‚˜λ³΄μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€.
Swagger둜 μž‘μ„±λœ API λ¬Έμ„œλŠ” api.prod.wavy.dance/api/docsμ—μ„œ ν™•μΈν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€.

Project Clone

git clone https://github.com/EO2-WAVY/WavyBackend.git

Install Packages

npm insall

Environment

μ‹€ν–‰ν•  ν™˜κ²½μ— μ•Œλ§žμ€ 파일λͺ…μœΌλ‘œ ν™˜κ²½λ³€μˆ˜λ₯Ό μ„ΈνŒ…ν•΄μ£Όμ„Έμš”.

개발용 ν™˜κ²½λ³€μˆ˜ 파일λͺ… : .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

Features

Config

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(),
                ...

ORM

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;
    ...

Authentication

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);
    }
}

Analysis

μ‚¬μš©μž μ˜μƒ 뢄석과정은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. 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 };
  1. AWS Lambda에 μ˜μƒ 포맷 λ³€ν™˜ μš”μ²­
  2. 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: '뢄석 μš”μ²­μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.' };
}
  1. MachineLearningAPIServerμ—μ„œ 뢄석 μ™„λ£Œμ‹œ 뢄석 μ™„λ£Œ APIλ₯Ό 호좜

Directory Structure

src
β”œβ”€β”€ [Module Name]
β”‚   └── dtos
β”‚       └── *.dto.ts
β”‚   └── entities (optional)
β”‚       └── *.entity.ts
β”‚   └── *.service.ts
β”‚   └── *.controller.ts
β”‚   └── *.module.ts
β”œβ”€β”€ .env
β”œβ”€β”€ app.module.ts
└── main.ts

Packages

β”œβ”€β”€ @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]

Developer

ν•΄λ‹Ή ν”„λ‘œμ νŠΈλŠ” μ†Œν”„νŠΈμ›¨μ–΄ λ§ˆμ—μŠ€νŠΈλ‘œ μ‚¬μ—…μ˜ 지원을 λ°›μ•„ κ°œλ°œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.

FE: hyesungoh AI: haeseoklee BE: Yeonwu

License

WavyλŠ” MITλΌμ΄μ„ μŠ€λ₯Ό λ”°λ¦…λ‹ˆλ‹€.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published