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λΌμ΄μ μ€λ₯Ό λ°λ¦ λλ€.