Skip to content

Commit

Permalink
feat : wasabi storage
Browse files Browse the repository at this point in the history
  • Loading branch information
kangjuhyup committed Oct 23, 2024
1 parent 124a9f9 commit 6ea22db
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 13 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ jobs:
run: |
echo "NODE_ENV=${{ vars.NODE_ENV }}" >> .env
echo "PORT=${{ vars.PORT }}" >> .env
echo "WASABI_ACCESS_KEY=${{ secrets.WASABI_ACCESS_KEY }}" >> .env
echo "WASABI_SECRET_KEY=${{ secrets.WASABI_SECRET_KEY }}" >> .env
echo "WASABI_REGION=${{ vars.WASABI_REGION }}" >> .env
echo "WASABI_ENDPOINT=${{ vars.WASABI_ENDPOINT }}" >> .env
echo "THUMB_BUCKET=${{ vars.THUMB_BUCKET }}" >> .env
echo "BACKGROUND_BUCKET=${{ vars.BACKGROUND_BUCKET }}" >> .env
echo "COMPONENT_BUCKET=${{ vars.COMPONENT_BUCKET }}" >> .env
echo "LETTER_BUCKET=${{ vars.LETTER_BUCKET }}" >> .env
- name: docker create
run : docker buildx create --use

Expand Down Expand Up @@ -69,5 +77,5 @@ jobs:
docker pull ghcr.io/${{ github.repository }}/service:latest
docker stop invite-api || true
docker rm invite-api || true
docker run -d -p 3003:3003 --network my-network --name invite-api ghcr.io/${{ github.repository }}/service:latest
docker run -d -p ${{ vars.PORT }}:${{ vars.PORT }} --network my-network --name invite-api ghcr.io/${{ github.repository }}/service:latest
EOF
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
"dependencies": {
"@imgly/background-removal-node": "^1.4.5",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.4.2",
"aws-sdk": "^2.1691.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"reflect-metadata": "^0.2.0",
Expand Down
26 changes: 24 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,35 @@ import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LetterModule } from './domain/letter/letter.module';
import { ConfigModule } from '@nestjs/config';
import { plainToClass } from 'class-transformer';
import { Enviroments } from './domain/dto/env';
import { validateSync } from 'class-validator';
import { StorageModule } from '@storage/storage.module';

const routers = [
LetterModule
LetterModule,
]

const modules = [
ConfigModule.forRoot({
isGlobal : true,
validate: (config) => {
const validatedConfig = plainToClass(Enviroments, config, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig);
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
},
}),
StorageModule
]

@Module({
imports: [...routers],
imports: [...routers, ...modules],
controllers: [AppController],
providers: [AppService],
})
Expand Down
35 changes: 35 additions & 0 deletions src/domain/dto/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IsString, IsUrl, IsNotEmpty } from 'class-validator';

export class Enviroments {
@IsString()
@IsNotEmpty()
WASABI_ACCESS_KEY: string;

@IsString()
@IsNotEmpty()
WASABI_SECRET_KEY: string;

@IsString()
@IsNotEmpty()
WASABI_REGION: string;

@IsUrl()
@IsNotEmpty()
WASABI_ENDPOINT: string;

@IsString()
@IsNotEmpty()
THUMB_BUCKET: string;

@IsString()
@IsNotEmpty()
BACKGROUND_BUCKET: string;

@IsString()
@IsNotEmpty()
COMPONENT_BUCKET: string;

@IsString()
@IsNotEmpty()
LETTER_BUCKET: string;
}
15 changes: 12 additions & 3 deletions src/domain/letter/dto/response/get.page.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiProperty } from "@nestjs/swagger";
import { LetterCategory } from "@util/category";
import { Type } from "class-transformer";
import { IsArray, IsNotEmpty, IsNumber, IsString, ValidateNested } from "class-validator";
import { LetterCategory } from "src/domain/util/category";
import { IsArray, IsIn, IsNotEmpty, IsNumber, IsString, IsUrl, ValidateNested } from "class-validator";

class LetterPageItem {
@ApiProperty({
Expand All @@ -22,11 +22,20 @@ class LetterPageItem {

@ApiProperty({
description: '카테고리',
example: LetterCategory.ANNIVERSARY,
example: LetterCategory.ANNIVERSARY,
enum: Object.values(LetterCategory)
})
@IsNotEmpty()
@IsIn(Object.values(LetterCategory))
category: LetterCategory;

@ApiProperty({
description: '썸네일 이미지 경로',
example : 'https://s3.ap-northeast-1.wasabisys.com/thm/00001',
})
@IsNotEmpty()
@IsUrl()
thumbnail : string
}

export class GetLetterPageResponse {
Expand Down
21 changes: 20 additions & 1 deletion src/domain/letter/letter.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,35 @@ import { GetLetterDetailResponse } from "./dto/response/get.detail";
import { ApiOkResponse, ApiOperation } from "@nestjs/swagger";
import { GetLetterPageRequest } from "./dto/request/get.page";
import { GetLetterPageResponse } from "./dto/response/get.page";
import { LetterService } from "./letter.service";

@Controller('letter')
export class LetterController {

constructor(
private readonly letterService : LetterService
) {}

@Get()
@ApiOperation({ summary : '초대장 페이지 조회'})
@ApiOkResponse({ status : 200, description : '성공', type : GetLetterPageResponse})
@UseInterceptors(new ResponseValidationInterceptor(GetLetterPageResponse))
async getLetters(
@Query() dto : GetLetterPageRequest
){}
): Promise<HttpResponse<GetLetterPageResponse>>{
return {
result : true,
data : {
totalCount: 1,
items: [{
id: 1,
title: "Mock초대장",
category: "WED",
thumbnail : 'https://s3.ap-northeast-1.wasabisys.com/thm/00001'
}]
}
}
}

@Get(':id')
@ApiOperation({ summary: '초대장 상세 정보 조회' })
Expand Down
5 changes: 4 additions & 1 deletion src/domain/letter/letter.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { StorageService } from "@storage/storage.service";
import { Injectable } from "@nestjs/common";

@Injectable()
export class LetterService {

constructor(
private readonly storage: StorageService
) {}
}
9 changes: 9 additions & 0 deletions src/storage/storage.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Global, Module } from "@nestjs/common";
import { StorageService } from "./storage.service";

@Global()
@Module({
providers : [StorageService],
exports : [StorageService]
})
export class StorageModule{}
39 changes: 39 additions & 0 deletions src/storage/storage.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as AWS from 'aws-sdk';

@Injectable()
export class StorageService {
private s3: AWS.S3;

constructor(private readonly configService: ConfigService) {
this.s3 = new AWS.S3({
accessKeyId: this.configService.get<string>('WASABI_ACCESS_KEY'),
secretAccessKey: this.configService.get<string>('WASABI_SECRET_KEY'),
region: this.configService.get<string>('WASABI_REGION'),
endpoint: this.configService.get<string>('WASABI_ENDPOINT'),
s3ForcePathStyle: true,
});
}

async generateUploadPresignedUrl(param : {bucket: string, key: string, expires: number }): Promise<string> {
const params = {
Bucket: param.bucket,
Key: param.key,
Expires: param.expires,
ContentType: 'application/octet-stream',
};

return await this.s3.getSignedUrlPromise('putObject', params);
}

async generateDownloadPresignedUrl(param : {bucket: string, key: string, expires: number }): Promise<string> {
const params = {
Bucket: param.bucket,
Key: param.key,
Expires: param.expires,
};

return await this.s3.getSignedUrlPromise('getObject', params);
}
}
File renamed without changes.
9 changes: 8 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
"noFallthroughCasesInSwitch": false,
"paths": {
"@auth/*": ["src/auth/*"],
"@interceptor/*": ["src/interceptor/*"],
"@storage/*": ["src/storage/*"],
"@util/*": ["src/util/*"],
"@app/*": ["src/*"],
}
}
}
Loading

0 comments on commit 6ea22db

Please sign in to comment.