From 590fd3593bcccc50454c54ed01c5966abd178c97 Mon Sep 17 00:00:00 2001 From: Haesung Date: Tue, 7 Oct 2025 18:26:14 +0900 Subject: [PATCH 01/28] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EC=84=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 조회 쿼리에도 deletedAt: null 조건 추가 --- .../islands/private-island-writer.ts | 4 +++ .../interface/private-island.repository.ts | 1 + .../islands/private-island.service.ts | 22 ++++++++++++- .../private-island-prisma.repository.ts | 20 +++++++++--- .../islands/private-island.controller.ts | 32 ++++++++++++++++++- 5 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/domain/components/islands/private-island-writer.ts b/src/domain/components/islands/private-island-writer.ts index 3125c43c..fa66b10c 100644 --- a/src/domain/components/islands/private-island-writer.ts +++ b/src/domain/components/islands/private-island-writer.ts @@ -26,4 +26,8 @@ export class PrivateIslandWriter { return island; } + + async delete(id: string, now = new Date()) { + await this.privateIslandRepository.delete(id, now); + } } diff --git a/src/domain/interface/private-island.repository.ts b/src/domain/interface/private-island.repository.ts index 1f467662..f76c59d3 100644 --- a/src/domain/interface/private-island.repository.ts +++ b/src/domain/interface/private-island.repository.ts @@ -17,6 +17,7 @@ export interface PrivateIslandRepository { ): Promise<{ id: string; password: string | null } | null>; findPasswordById(id: string): Promise; findOneById(id: string): Promise; + delete(id: string, now?: Date): Promise; } export const PrivateIslandRepository = Symbol('PrivateIslandRepository'); diff --git a/src/domain/services/islands/private-island.service.ts b/src/domain/services/islands/private-island.service.ts index 286590d1..24d9879d 100644 --- a/src/domain/services/islands/private-island.service.ts +++ b/src/domain/services/islands/private-island.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { HttpStatus, Injectable } from '@nestjs/common'; import { CreatePrivateIslandInput } from 'src/domain/types/island.types'; import { generateRandomBase62 } from 'src/utils/random'; import { PrivateIslandWriter } from 'src/domain/components/islands/private-island-writer'; @@ -7,6 +7,9 @@ import { PRIVATE_ISLAND_MAX_MEMBERS } from 'src/common/constants'; import { MapReader } from 'src/domain/components/map/map-reader'; import { UserReader } from 'src/domain/components/users/user-reader'; import { PrivateIslandPasswordChecker } from 'src/domain/components/islands/private-storage/private-island-password-checker'; +import { DomainException } from 'src/domain/exceptions/exceptions'; +import { DomainExceptionType } from 'src/domain/exceptions/enum/domain-exception-type'; +import { FORBIDDEN_MESSAGE } from 'src/domain/exceptions/message'; @Injectable() export class PrivateIslandService { @@ -56,4 +59,21 @@ export class PrivateIslandService { password, ); } + + async remove(id: string, userId: string, now = new Date()): Promise { + await this.checkOwnership(id, userId); + await this.privateIslandWriter.delete(id, now); + } + + async checkOwnership(islandId: string, userId: string): Promise { + const { ownerId } = await this.privateIslandReader.readOne(islandId); + + if (ownerId !== userId) { + throw new DomainException( + DomainExceptionType.FORBIDDEN, + HttpStatus.FORBIDDEN, + FORBIDDEN_MESSAGE, + ); + } + } } diff --git a/src/infrastructure/repositories/private-island-prisma.repository.ts b/src/infrastructure/repositories/private-island-prisma.repository.ts index 617c21f9..229e3997 100644 --- a/src/infrastructure/repositories/private-island-prisma.repository.ts +++ b/src/infrastructure/repositories/private-island-prisma.repository.ts @@ -53,6 +53,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository { }, where: { ownerId, + deletedAt: null, }, skip: (page - 1) * limit, take: limit, @@ -72,7 +73,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository { async countByOwner(ownerId: string): Promise { return await this.txHost.tx.privateIsland.count({ - where: { ownerId }, + where: { ownerId, deletedAt: null }, }); } @@ -81,7 +82,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository { ): Promise<{ id: string; password: string | null } | null> { return await this.txHost.tx.privateIsland.findFirst({ select: { id: true, password: true }, - where: { urlPath }, + where: { urlPath, deletedAt: null }, }); } @@ -89,7 +90,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository { id: string, ): Promise { return await this.txHost.tx.privateIsland.findUnique({ - where: { id }, + where: { id, deletedAt: null }, select: { id: true, password: true, @@ -99,7 +100,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository { async findOneById(id: string): Promise { const result = await this.txHost.tx.privateIsland.findUnique({ - where: { id }, + where: { id, deletedAt: null }, select: { id: true, ownerId: true, @@ -130,4 +131,15 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository { mapKey: map.key, }; } + + async delete(id: string, now = new Date()): Promise { + await this.txHost.tx.privateIsland.update({ + where: { + id, + }, + data: { + deletedAt: now, + }, + }); + } } diff --git a/src/presentation/controller/islands/private-island.controller.ts b/src/presentation/controller/islands/private-island.controller.ts index 32fe7b93..5ad86ddb 100644 --- a/src/presentation/controller/islands/private-island.controller.ts +++ b/src/presentation/controller/islands/private-island.controller.ts @@ -1,4 +1,13 @@ -import { Body, Get, HttpCode, Param, Post, Query } from '@nestjs/common'; +import { + Body, + Delete, + Get, + HttpCode, + Param, + ParseUUIDPipe, + Post, + Query, +} from '@nestjs/common'; import { ApiOperation, ApiResponse } from '@nestjs/swagger'; import { CurrentUser } from 'src/common/decorator/current-user.decorator'; import { LivislandController } from 'src/common/decorator/livisland-controller.decorator'; @@ -118,4 +127,25 @@ export class PrivateIslandController { ): Promise { return await this.privateIslandService.checkPassword(id, dto.password); } + + @ApiOperation({ + summary: '비밀섬 삭제', + description: '비밀섬 삭제', + }) + @ApiResponse({ + status: 204, + description: '삭제 완료', + }) + @ApiResponse({ + status: 403, + description: '삭제 권한 없음', + }) + @HttpCode(204) + @Delete(':id') + async remove( + @Param('id', ParseUUIDPipe) id: string, + @CurrentUser() userId: string, + ) { + await this.privateIslandService.remove(id, userId); + } } From 53e71667b855916f4c562e5c1e7c5ec8e589d105 Mon Sep 17 00:00:00 2001 From: Haesung Date: Tue, 7 Oct 2025 18:27:58 +0900 Subject: [PATCH 02/28] =?UTF-8?q?test:=20=EB=B9=84=EB=B0=80=EC=84=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 섬삭제 - 정상 동작 - 권한 검증 - 존재하지 않는 섬 - 입력값 검증 - 조회 - 삭제된 섬 조회 X --- test/e2e/private-island.e2e-spec.ts | 171 ++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/test/e2e/private-island.e2e-spec.ts b/test/e2e/private-island.e2e-spec.ts index 86212dcb..e50f1e4e 100644 --- a/test/e2e/private-island.e2e-spec.ts +++ b/test/e2e/private-island.e2e-spec.ts @@ -140,6 +140,80 @@ describe('PrivateIslandController (e2e)', () => { expect(island.id).toEqual(body.islands[i].id); }); }); + + it('삭제된 섬은 조회되지 않는다', async () => { + // Given: 첫 번째 섬을 삭제 처리 + const targetIsland = islands[0]; + await db.privateIsland.update({ + where: { id: targetIsland.id }, + data: { deletedAt: new Date() }, + }); + + // When: 내 섬 목록을 조회 + const dto: GetMyPrivateIslandRequest = { + limit: 20, + order: 'desc', + page: 1, + sortBy: 'createdAt', + }; + const response = await request(app.getHttpServer()) + .get('/private-islands/my') + .query(dto) + .set('Authorization', authToken); + + const { + status, + body, + }: ResponseResult = response; + + // Then: 삭제된 섬은 결과에 포함되지 않아야 함 + expect(status).toBe(200); + expect(body.islands.length).toBe(9); // 10개 중 1개가 삭제되어 9개 + + // 삭제된 섬의 ID가 결과에 포함되지 않았는지 확인 + const returnedIslandIds = body.islands.map((island) => island.id); + expect(returnedIslandIds).not.toContain(targetIsland.id); + }); + + it('여러 섬이 삭제된 경우 모두 조회에서 제외된다', async () => { + // Given: 처음 3개 섬을 삭제 처리 + const deletedIslands = islands.slice(0, 3); + await db.privateIsland.updateMany({ + where: { + id: { + in: deletedIslands.map((island) => island.id), + }, + }, + data: { deletedAt: new Date() }, + }); + + // When: 내 섬 목록을 조회 + const dto: GetMyPrivateIslandRequest = { + limit: 20, + order: 'desc', + page: 1, + sortBy: 'createdAt', + }; + const response = await request(app.getHttpServer()) + .get('/private-islands/my') + .query(dto) + .set('Authorization', authToken); + + const { + status, + body, + }: ResponseResult = response; + + // Then: 삭제된 섬들은 모두 결과에 포함되지 않아야 함 + expect(status).toBe(200); + expect(body.islands.length).toBe(7); // 10개 중 3개가 삭제되어 7개 + + // 삭제된 섬들의 ID가 결과에 포함되지 않았는지 확인 + const returnedIslandIds = body.islands.map((island) => island.id); + deletedIslands.forEach((deletedIsland) => { + expect(returnedIslandIds).not.toContain(deletedIsland.id); + }); + }); }); describe('(POST) /private-islands/:id/password', () => { @@ -237,4 +311,101 @@ describe('PrivateIslandController (e2e)', () => { expect(response.status).toBe(400); }); }); + + describe('DELETE /private-islands/:id - 비밀섬 삭제', () => { + let authToken: string; + let userId: string; + let otherAuthToken: string; + let map: { id: string; key: string }; + let privateIsland: PrivateIslandEntity; + + beforeEach(async () => { + const loginResult = await login(app); + authToken = loginResult.accessToken; + userId = loginResult.userId; + + const otherLoginResult = await login(app); + otherAuthToken = otherLoginResult.accessToken; + + map = await db.map.create({ + data: { + id: v4(), + key: 'test-map', + createdAt: new Date(), + description: '테스트 맵', + image: 'https://example.com/image.jpg', + name: '테스트 맵', + }, + }); + + const privateIslandData = generatePrivateIsland(map.id, userId, { + name: '삭제할 섬', + isPublic: true, + description: '삭제 테스트용 섬', + }); + + privateIsland = await db.privateIsland.create({ + data: privateIslandData, + }); + }); + + it('정상 동작', async () => { + const response = await request(app.getHttpServer()) + .delete(`/private-islands/${privateIsland.id}`) + .set('Authorization', authToken); + + expect(response.status).toBe(204); + + // soft delete되어 deletedAt이 설정되었는지 확인 + const deletedIsland = await db.privateIsland.findUnique({ + where: { id: privateIsland.id }, + }); + expect(deletedIsland).not.toBeNull(); + expect(deletedIsland?.deletedAt).not.toBeNull(); + }); + + it('섬의 주인이 아닌 회원이 삭제를 시도하는 경우 예외가 발생한다', async () => { + const response = await request(app.getHttpServer()) + .delete(`/private-islands/${privateIsland.id}`) + .set('Authorization', otherAuthToken); + + expect(response.status).toBe(403); + + // 섬이 삭제되지 않았는지 확인 (deletedAt이 null인지 확인) + const existingIsland = await db.privateIsland.findUnique({ + where: { id: privateIsland.id }, + }); + expect(existingIsland).not.toBeNull(); + expect(existingIsland?.id).toBe(privateIsland.id); + expect(existingIsland?.deletedAt).toBeNull(); + }); + + it('존재하지 않는 섬을 삭제 시도 시 예외가 발생한다', async () => { + const nonExistentIslandId = v4(); + + const response = await request(app.getHttpServer()) + .delete(`/private-islands/${nonExistentIslandId}`) + .set('Authorization', authToken); + + expect(response.status).toBe(404); + }); + + it('인증 없이 삭제 요청 시 예외가 발생한다', async () => { + const response = await request(app.getHttpServer()).delete( + `/private-islands/${privateIsland.id}`, + ); + + expect(response.status).toBe(401); + }); + + it('잘못된 UUID 형식의 ID로 삭제 시도 시 예외가 발생한다', async () => { + const invalidId = 'invalid-uuid'; + + const response = await request(app.getHttpServer()) + .delete(`/private-islands/${invalidId}`) + .set('Authorization', authToken); + + expect(response.status).toBe(400); + }); + }); }); From de0809d68faa8ca416bf4ac40e33ed0c8a50f88c Mon Sep 17 00:00:00 2001 From: Haesung Date: Wed, 8 Oct 2025 23:08:05 +0900 Subject: [PATCH 03/28] =?UTF-8?q?chore:=20deploy=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..58ec89b2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,50 @@ +name: Build & Deploy to EC2 via CodeDeploy + +on: + push: + branches: + ## test + - feat/auto-deploy-config + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + # 1️⃣ 코드 체크아웃 + - name: Checkout + uses: actions/checkout@v4.2.2 + + # 2️⃣ Node.js 설치 + - uses: actions/setup-node@v3 + with: + node-version: 22 + cache: "npm" + + # 3️⃣ 의존성 설치 + - run: npm install --production + + # 4️⃣ Build + - run: npx prisma generate + - run: npm run build + + # 5️⃣ AWS CLI 설정 + - uses: aws-actions/configure-aws-credentials@v3 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + # 6️⃣ 빌드 결과물 zip + - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml + + # 7️⃣ S3 업로드 + - run: aws s3 cp app.zip s3://lia-server-build/app.zip + + # 8️⃣ CodeDeploy 배포 생성 + # - run: | + # aws deploy create-deployment \ + # --application-name YOUR_CODEDEPLOY_APP \ + # --deployment-group-name YOUR_DEPLOYMENT_GROUP \ + # --s3-location bucket=YOUR_BUCKET_NAME,key=app.zip,bundleType=zip \ + # --deployment-config-name CodeDeployDefault.AllAtOnce From 782a124fd1059e2b9eb9c73de5d9c9fa06131f92 Mon Sep 17 00:00:00 2001 From: Haesung Date: Wed, 8 Oct 2025 23:10:46 +0900 Subject: [PATCH 04/28] =?UTF-8?q?chore:=20--production=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 58ec89b2..4712a956 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,7 +22,7 @@ jobs: cache: "npm" # 3️⃣ 의존성 설치 - - run: npm install --production + - run: npm install # 4️⃣ Build - run: npx prisma generate From 28ad80c6578637540c27ee094333a8393c994243 Mon Sep 17 00:00:00 2001 From: Haesung Date: Wed, 8 Oct 2025 23:14:33 +0900 Subject: [PATCH 05/28] =?UTF-8?q?chore:=20=EB=B2=84=ED=82=B7=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20secret=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4712a956..d15f5e08 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml # 7️⃣ S3 업로드 - - run: aws s3 cp app.zip s3://lia-server-build/app.zip + - run: aws s3 cp app.zip ${{ secrets.S3_BUCKET_PATH }} # 8️⃣ CodeDeploy 배포 생성 # - run: | From ed47eb1b00e4cc2eb51e05aad18f936422726da3 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 18:19:02 +0900 Subject: [PATCH 06/28] =?UTF-8?q?chore:=20CodeDeploy=20=EC=BB=A4=EB=A7=A8?= =?UTF-8?q?=EB=93=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d15f5e08..1014247c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,12 +39,12 @@ jobs: - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml # 7️⃣ S3 업로드 - - run: aws s3 cp app.zip ${{ secrets.S3_BUCKET_PATH }} + - run: aws s3 cp app.zip s3//${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }} # 8️⃣ CodeDeploy 배포 생성 - # - run: | - # aws deploy create-deployment \ - # --application-name YOUR_CODEDEPLOY_APP \ - # --deployment-group-name YOUR_DEPLOYMENT_GROUP \ - # --s3-location bucket=YOUR_BUCKET_NAME,key=app.zip,bundleType=zip \ - # --deployment-config-name CodeDeployDefault.AllAtOnce + - run: | + aws deploy create-deployment \ + --application-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \ + --deployment-group-name ${{ secrets.DEPLOY_GROUP_NAME }} \ + --s3-location bucket=${{ secrets.S3_BUCKET_NAME }}/${{secrets.S3_BUILD_DIRECTORY_PATH}},key=app.zip,bundleType=zip \ + --deployment-config-name CodeDeployDefault.OneAtATime From f44bf4644718f9c9d11b049f6bdf8f85ce94ece0 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 18:21:12 +0900 Subject: [PATCH 07/28] =?UTF-8?q?chore:=20=EB=88=84=EB=9D=BD=EB=90=9C=20":?= =?UTF-8?q?"=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1014247c..3f245881 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml # 7️⃣ S3 업로드 - - run: aws s3 cp app.zip s3//${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }} + - run: aws s3 cp app.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }} # 8️⃣ CodeDeploy 배포 생성 - run: | From 5812c8c87eef35d7bd3696b60f631ddc7813e9ea Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 18:24:05 +0900 Subject: [PATCH 08/28] =?UTF-8?q?chore:=20s3=20=EB=94=94=EB=A0=89=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20path=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3f245881..2f3280da 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -46,5 +46,5 @@ jobs: aws deploy create-deployment \ --application-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \ --deployment-group-name ${{ secrets.DEPLOY_GROUP_NAME }} \ - --s3-location bucket=${{ secrets.S3_BUCKET_NAME }}/${{secrets.S3_BUILD_DIRECTORY_PATH}},key=app.zip,bundleType=zip \ + --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},key=${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip,bundleType=zip \ --deployment-config-name CodeDeployDefault.OneAtATime From bb6ec55dbbb2b51b3a28cb1cebf088e535d474f5 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 18:32:16 +0900 Subject: [PATCH 09/28] =?UTF-8?q?chore:=20s3=EC=97=90=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=ED=95=98=EB=8A=94=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2f3280da..d3b560d8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,12 +39,12 @@ jobs: - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml # 7️⃣ S3 업로드 - - run: aws s3 cp app.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }} + - run: aws s3 cp app.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip # 8️⃣ CodeDeploy 배포 생성 - run: | aws deploy create-deployment \ - --application-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \ + --ㅠapplication-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \ --deployment-group-name ${{ secrets.DEPLOY_GROUP_NAME }} \ --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},key=${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip,bundleType=zip \ --deployment-config-name CodeDeployDefault.OneAtATime From d979bd8888f7c49a5d37c4a7ba617c1bd1b29629 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 18:34:40 +0900 Subject: [PATCH 10/28] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d3b560d8..d3fba81b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,7 +44,7 @@ jobs: # 8️⃣ CodeDeploy 배포 생성 - run: | aws deploy create-deployment \ - --ㅠapplication-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \ + --application-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \ --deployment-group-name ${{ secrets.DEPLOY_GROUP_NAME }} \ --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},key=${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip,bundleType=zip \ --deployment-config-name CodeDeployDefault.OneAtATime From 8294e431fa165ca09f37964a3e7e2c8b7d3a8b1b Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 18:47:21 +0900 Subject: [PATCH 11/28] =?UTF-8?q?chore:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appspec.yml | 12 ++++++++++++ scripts/deploy-test.sh | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 appspec.yml create mode 100644 scripts/deploy-test.sh diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 00000000..3d9229c2 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,12 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ec2-user/app/deploy-test + +hooks: + # 수명주기마다 실행할 스크립트를 지정 가능 + AfterInstall: + - location: scripts/deploy-test.sh + timeout: 300 + runas: ec2-user diff --git a/scripts/deploy-test.sh b/scripts/deploy-test.sh new file mode 100644 index 00000000..4deae011 --- /dev/null +++ b/scripts/deploy-test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# 테스트 시작 메시지 +echo "=== Deploy Test: AfterInstall Hook Start ===" + +# 현재 경로 출력 +echo "Current directory: $(pwd)" + +# 파일/폴더 목록 출력 +echo "Directory contents:" +ls -al + +# 환경 변수 출력 (AWS 관련 변수 예시) +echo "USER: $USER" +echo "HOME: $HOME" + +# 테스트 완료 메시지 +echo "=== Deploy Test: AfterInstall Hook End ===" From 2e692782cbef3c483739ffab376af13861240d29 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 19:05:43 +0900 Subject: [PATCH 12/28] =?UTF-8?q?chore:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d3fba81b..2308d922 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,6 +28,12 @@ jobs: - run: npx prisma generate - run: npm run build + # 5️⃣ 환경 변수 생성 (.env) + - name: Create .env file from secrets + run: | + echo "${{ secrets.PRODUCTION_ENV }}" > .env + chmod 600 .env + # 5️⃣ AWS CLI 설정 - uses: aws-actions/configure-aws-credentials@v3 with: @@ -36,7 +42,7 @@ jobs: aws-region: ap-northeast-2 # 6️⃣ 빌드 결과물 zip - - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml + - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml .env # 7️⃣ S3 업로드 - run: aws s3 cp app.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip From 331b973a5b2a23243147f03d78bfd63d2bff6556 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 19:35:59 +0900 Subject: [PATCH 13/28] =?UTF-8?q?chore:=20=EB=B0=B0=ED=8F=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 scripts/deploy.sh diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 00000000..cd837f51 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +APP_BASE="/home/ec2-user/app" +CURRENT_DIR="$APP_BASE/current" +TEMP_DIR="$APP_BASE/deploy-temp" +NEW_DIR="$APP_BASE/new_$(date +%s)" # timestamp 기반 새 버전 +BACKUP_DIR="$APP_BASE/backup" + +echo "=== Deploy Hook Start ===" +echo "Temporary deploy dir: $TEMP_DIR" + +# 1️⃣ 새 버전 이동 +mv "$TEMP_DIR" "$NEW_DIR" + +# 2️⃣ 의존성 설치 +cd "$NEW_DIR" +npm install --production + +# 3️⃣ 새 서버 시작 +pm2 start dist/server.js --name lia-server --update-env || { + echo "=== Deploy Failed, Rolling Back ===" + pm2 delete lia-server || true + if [ -d "$BACKUP_DIR" ]; then + mv "$BACKUP_DIR" "$CURRENT_DIR" + pm2 start "$CURRENT_DIR/dist/server.js" --name lia-server + fi + exit 1 +} + +# 4️⃣ 기존 버전 백업 +if [ -d "$CURRENT_DIR" ]; then + mv "$CURRENT_DIR" "$BACKUP_DIR" +fi + +# 5️⃣ 새 버전을 current로 +mv "$NEW_DIR" "$CURRENT_DIR" + +# 6️⃣ 성공 시 기존 백업 삭제 +rm -rf "$BACKUP_DIR" + +echo "=== Deploy Hook End ===" From b7b0bbf908f2efd440881122aabb20deaca94dc4 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 19:36:02 +0900 Subject: [PATCH 14/28] =?UTF-8?q?chore:=20=EC=84=9C=EB=B2=84=20=ED=8F=AC?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD(=ED=85=8C=EC=8A=A4=ED=8A=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 52c54321..44815f2b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,6 @@ async function bootstrap() { app.useWebSocketAdapter(wsAdapter); setupSwagger(app); - await app.listen(3000); + await app.listen(4400); } bootstrap(); From abfee0cf45d8edde19082b982aa45b7a17b74217 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 19:36:50 +0900 Subject: [PATCH 15/28] =?UTF-8?q?chore:=20=EC=9E=84=EC=8B=9C=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=EC=97=90=20=EB=B0=B0=ED=8F=AC=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appspec.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appspec.yml b/appspec.yml index 3d9229c2..6bd1fc0a 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,12 +1,12 @@ version: 0.0 os: linux + files: - source: / - destination: /home/ec2-user/app/deploy-test + destination: /home/ec2-user/app/deploy-temp hooks: - # 수명주기마다 실행할 스크립트를 지정 가능 AfterInstall: - - location: scripts/deploy-test.sh - timeout: 300 + - location: scripts/deploy.sh + timeout: 600 runas: ec2-user From c083ae350ddaebed4e1c9c734917a93a48f61ca2 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 19:40:00 +0900 Subject: [PATCH 16/28] =?UTF-8?q?chore:=20npm=20install=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index cd837f51..e3ef919e 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -4,20 +4,26 @@ set -e APP_BASE="/home/ec2-user/app" CURRENT_DIR="$APP_BASE/current" TEMP_DIR="$APP_BASE/deploy-temp" -NEW_DIR="$APP_BASE/new_$(date +%s)" # timestamp 기반 새 버전 +NEW_DIR="$APP_BASE/new_$(date +%s)" BACKUP_DIR="$APP_BASE/backup" echo "=== Deploy Hook Start ===" echo "Temporary deploy dir: $TEMP_DIR" -# 1️⃣ 새 버전 이동 +# 권한 문제 예방 +mkdir -p "$APP_BASE" +chown -R ec2-user:ec2-user "$APP_BASE" +chmod -R 755 "$APP_BASE" + +# 새 버전 이동 mv "$TEMP_DIR" "$NEW_DIR" +chown -R ec2-user:ec2-user "$NEW_DIR" -# 2️⃣ 의존성 설치 +# 의존성 설치 cd "$NEW_DIR" -npm install --production +npm install --production --unsafe-perm -# 3️⃣ 새 서버 시작 +# 서버 시작 pm2 start dist/server.js --name lia-server --update-env || { echo "=== Deploy Failed, Rolling Back ===" pm2 delete lia-server || true @@ -28,15 +34,15 @@ pm2 start dist/server.js --name lia-server --update-env || { exit 1 } -# 4️⃣ 기존 버전 백업 +# 기존 버전 백업 if [ -d "$CURRENT_DIR" ]; then mv "$CURRENT_DIR" "$BACKUP_DIR" fi -# 5️⃣ 새 버전을 current로 +# 새 버전을 current로 mv "$NEW_DIR" "$CURRENT_DIR" -# 6️⃣ 성공 시 기존 백업 삭제 +# 성공 시 기존 백업 삭제 rm -rf "$BACKUP_DIR" echo "=== Deploy Hook End ===" From b59c3e5907a29afc4eae6c0c1dc5898ca98d8640 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 19:45:09 +0900 Subject: [PATCH 17/28] =?UTF-8?q?chore:=20=EA=B6=8C=ED=95=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=AA=85=EB=A0=B9=20=EC=A0=9C=EA=B1=B0,=20--unsafe?= =?UTF-8?q?-perm=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index e3ef919e..740f9f84 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -10,22 +10,19 @@ BACKUP_DIR="$APP_BASE/backup" echo "=== Deploy Hook Start ===" echo "Temporary deploy dir: $TEMP_DIR" -# 권한 문제 예방 -mkdir -p "$APP_BASE" -chown -R ec2-user:ec2-user "$APP_BASE" -chmod -R 755 "$APP_BASE" - -# 새 버전 이동 +# 1️⃣ 새 버전 이동 mv "$TEMP_DIR" "$NEW_DIR" -chown -R ec2-user:ec2-user "$NEW_DIR" -# 의존성 설치 +# 2️⃣ 의존성 설치 cd "$NEW_DIR" +# --unsafe-perm 옵션으로 root-owned 파일도 설치 가능 npm install --production --unsafe-perm -# 서버 시작 +# 3️⃣ 새 서버 시작 +# PM2로 기존 앱 이름 그대로 새 디렉토리 환경에서 실행 pm2 start dist/server.js --name lia-server --update-env || { echo "=== Deploy Failed, Rolling Back ===" + # 실패 시 기존 서버 재실행 pm2 delete lia-server || true if [ -d "$BACKUP_DIR" ]; then mv "$BACKUP_DIR" "$CURRENT_DIR" From 515d564d9cc60c04338a699e8307fec0817213e1 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 20:31:02 +0900 Subject: [PATCH 18/28] =?UTF-8?q?chore:=20=EA=B6=8C=ED=95=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=EB=A1=9C=20=ED=8C=8C=EC=9D=BC=20=EB=B3=B5=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 740f9f84..a959ee7f 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -10,8 +10,8 @@ BACKUP_DIR="$APP_BASE/backup" echo "=== Deploy Hook Start ===" echo "Temporary deploy dir: $TEMP_DIR" -# 1️⃣ 새 버전 이동 -mv "$TEMP_DIR" "$NEW_DIR" +# agent로 실행하면 root 소유기 떄문에 복사 +rsync -a "$TEMP_DIR/" "$NEW_DIR/" # 2️⃣ 의존성 설치 cd "$NEW_DIR" From d80a39c32a838bd7dc52a80a7af88e7326699694 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 20:33:59 +0900 Subject: [PATCH 19/28] =?UTF-8?q?chore:=20=EA=B6=8C=ED=95=9C=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EA=B6=8C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index a959ee7f..7252a304 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -11,7 +11,7 @@ echo "=== Deploy Hook Start ===" echo "Temporary deploy dir: $TEMP_DIR" # agent로 실행하면 root 소유기 떄문에 복사 -rsync -a "$TEMP_DIR/" "$NEW_DIR/" +rsync -a --chown=ec2-user:ec2-user "$TEMP_DIR/" "$NEW_DIR/" # 2️⃣ 의존성 설치 cd "$NEW_DIR" From 08a61a274edfbece1ff741f1db3b0c4dd63c1623 Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 20:36:50 +0900 Subject: [PATCH 20/28] =?UTF-8?q?chore:=20.env=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2308d922..247b2f0b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,7 +32,6 @@ jobs: - name: Create .env file from secrets run: | echo "${{ secrets.PRODUCTION_ENV }}" > .env - chmod 600 .env # 5️⃣ AWS CLI 설정 - uses: aws-actions/configure-aws-credentials@v3 From d91243d120d37f23a99958ff447701bc607708ac Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 20:40:40 +0900 Subject: [PATCH 21/28] =?UTF-8?q?chore:=20=EC=84=9C=EB=B2=84=20main=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 7252a304..44389d14 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -20,13 +20,13 @@ npm install --production --unsafe-perm # 3️⃣ 새 서버 시작 # PM2로 기존 앱 이름 그대로 새 디렉토리 환경에서 실행 -pm2 start dist/server.js --name lia-server --update-env || { +pm2 start dist/src/main.js --name lia-server --update-env || { echo "=== Deploy Failed, Rolling Back ===" # 실패 시 기존 서버 재실행 pm2 delete lia-server || true if [ -d "$BACKUP_DIR" ]; then mv "$BACKUP_DIR" "$CURRENT_DIR" - pm2 start "$CURRENT_DIR/dist/server.js" --name lia-server + pm2 start "$CURRENT_DIR/dist/src/main.js" --name lia-server fi exit 1 } From 687ab83e1ad84c3e9f9f91a2d88bb45f8fccdabc Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 20:51:51 +0900 Subject: [PATCH 22/28] =?UTF-8?q?chore:=20=EC=9B=90=EB=9E=98=20=ED=8F=AC?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 44815f2b..52c54321 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,6 @@ async function bootstrap() { app.useWebSocketAdapter(wsAdapter); setupSwagger(app); - await app.listen(4400); + await app.listen(3000); } bootstrap(); From c4c753d2ef8a0fff1650abf6fc1603111c59772d Mon Sep 17 00:00:00 2001 From: Haesung Date: Thu, 9 Oct 2025 21:11:10 +0900 Subject: [PATCH 23/28] =?UTF-8?q?chore:=20prisma=20markdown=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6f1eb1f3..ac302bbb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,11 +8,11 @@ generator client { provider = "prisma-client-js" } -generator markdown { - provider = "prisma-markdown" - output = "./ERD.md" - title = "Metamorn-ERD" -} +// generator markdown { +// provider = "prisma-markdown" +// output = "./ERD.md" +// title = "Metamorn-ERD" +// } generator kysely { provider = "prisma-kysely" From 9160de68f1a4226dd0571bcac20f4e33e029e4c7 Mon Sep 17 00:00:00 2001 From: Haesung Date: Sun, 12 Oct 2025 17:48:33 +0900 Subject: [PATCH 24/28] =?UTF-8?q?chore:=20current=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=ED=83=80=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 105 ++++++++++++++++++++++++++++++++++++++-------- src/main.ts | 2 +- 2 files changed, 88 insertions(+), 19 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 44389d14..78b2c8bb 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,3 +1,49 @@ +# #!/bin/bash +# set -e + +# APP_BASE="/home/ec2-user/app" +# CURRENT_DIR="$APP_BASE/current" +# TEMP_DIR="$APP_BASE/deploy-temp" +# NEW_DIR="$APP_BASE/new_$(date +%s)" +# BACKUP_DIR="$APP_BASE/backup" + +# echo "=== Deploy Hook Start ===" +# echo "Temporary deploy dir: $TEMP_DIR" + +# # agent로 실행하면 root 소유기 떄문에 복사 +# rsync -a --chown=ec2-user:ec2-user "$TEMP_DIR/" "$NEW_DIR/" + +# # 2️⃣ 의존성 설치 +# cd "$NEW_DIR" +# npm install --production + +# # 3️⃣ 새 서버 시작 +# # PM2로 기존 앱 이름 그대로 새 디렉토리 환경에서 실행 +# pm2 start dist/src/main.js --name lia-server --update-env || { +# echo "=== Deploy Failed, Rolling Back ===" +# # 실패 시 기존 서버 재실행 +# pm2 delete lia-server || true +# if [ -d "$BACKUP_DIR" ]; then +# mv "$BACKUP_DIR" "$CURRENT_DIR" +# pm2 start "$CURRENT_DIR/dist/src/main.js" --name lia-server +# fi +# exit 1 +# } + +# # 기존 버전 백업 +# if [ -d "$CURRENT_DIR" ]; then +# mv "$CURRENT_DIR" "$BACKUP_DIR" +# fi + +# # 새 버전을 current로 +# mv "$NEW_DIR" "$CURRENT_DIR" + +# # 성공 시 기존 백업 삭제 +# rm -rf "$BACKUP_DIR" + +# echo "=== Deploy Hook End ===" + + #!/bin/bash set -e @@ -10,36 +56,59 @@ BACKUP_DIR="$APP_BASE/backup" echo "=== Deploy Hook Start ===" echo "Temporary deploy dir: $TEMP_DIR" -# agent로 실행하면 root 소유기 떄문에 복사 +# 1️⃣ CodeDeploy agent가 root로 실행하므로 소유권 변경하며 복사 rsync -a --chown=ec2-user:ec2-user "$TEMP_DIR/" "$NEW_DIR/" # 2️⃣ 의존성 설치 cd "$NEW_DIR" -# --unsafe-perm 옵션으로 root-owned 파일도 설치 가능 -npm install --production --unsafe-perm +npm install --production + +# 3️⃣ Prisma 생성 (필요시) +npx prisma generate -# 3️⃣ 새 서버 시작 -# PM2로 기존 앱 이름 그대로 새 디렉토리 환경에서 실행 -pm2 start dist/src/main.js --name lia-server --update-env || { +# 4️⃣ 기존 PM2 프로세스 중지 및 삭제 +pm2 describe lia-server > /dev/null 2>&1 && { + echo "Stopping existing process..." + pm2 stop lia-server + pm2 delete lia-server +} || echo "No existing process found" + +# 5️⃣ 기존 버전 백업 +if [ -d "$CURRENT_DIR" ]; then + rm -rf "$BACKUP_DIR" + mv "$CURRENT_DIR" "$BACKUP_DIR" +fi + +# 6️⃣ 새 버전을 current로 이동 (PM2 시작 전에!) +mv "$NEW_DIR" "$CURRENT_DIR" + +# 7️⃣ current 디렉토리에서 PM2 시작 +cd "$CURRENT_DIR" +pm2 start dist/src/main.js --name lia-server || { echo "=== Deploy Failed, Rolling Back ===" - # 실패 시 기존 서버 재실행 - pm2 delete lia-server || true + + # 실패한 current 제거 + cd "$APP_BASE" + rm -rf "$CURRENT_DIR" + + # 백업 버전 복구 if [ -d "$BACKUP_DIR" ]; then mv "$BACKUP_DIR" "$CURRENT_DIR" - pm2 start "$CURRENT_DIR/dist/src/main.js" --name lia-server + cd "$CURRENT_DIR" + pm2 start dist/src/main.js --name lia-server + echo "Rollback completed" fi exit 1 } -# 기존 버전 백업 -if [ -d "$CURRENT_DIR" ]; then - mv "$CURRENT_DIR" "$BACKUP_DIR" -fi +# 8️⃣ PM2 설정 저장 +pm2 save -# 새 버전을 current로 -mv "$NEW_DIR" "$CURRENT_DIR" +# 9️⃣ 성공 시 백업은 유지 (선택사항) +# rm -rf "$BACKUP_DIR" -# 성공 시 기존 백업 삭제 -rm -rf "$BACKUP_DIR" +# 🔟 임시 디렉토리 정리 +rm -rf "$TEMP_DIR" -echo "=== Deploy Hook End ===" +echo "=== Deploy Successful ===" +echo "Running from: $CURRENT_DIR" \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 52c54321..44815f2b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,6 @@ async function bootstrap() { app.useWebSocketAdapter(wsAdapter); setupSwagger(app); - await app.listen(3000); + await app.listen(4400); } bootstrap(); From 4685e6558d21d9a57eaec789e1fb2e4c7a82bd88 Mon Sep 17 00:00:00 2001 From: Haesung Date: Sun, 12 Oct 2025 17:54:52 +0900 Subject: [PATCH 25/28] =?UTF-8?q?chore:=20=EC=9E=84=EC=8B=9C=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=82=AD=EC=A0=9C=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 78b2c8bb..f002d259 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -104,11 +104,5 @@ pm2 start dist/src/main.js --name lia-server || { # 8️⃣ PM2 설정 저장 pm2 save -# 9️⃣ 성공 시 백업은 유지 (선택사항) -# rm -rf "$BACKUP_DIR" - -# 🔟 임시 디렉토리 정리 -rm -rf "$TEMP_DIR" - echo "=== Deploy Successful ===" echo "Running from: $CURRENT_DIR" \ No newline at end of file From 4adcadf4c358581f67c244f94c5bf260d7855421 Mon Sep 17 00:00:00 2001 From: Haesung Date: Sun, 12 Oct 2025 18:04:11 +0900 Subject: [PATCH 26/28] =?UTF-8?q?chore:=20=ED=8F=AC=ED=8A=B8=20=EC=9B=90?= =?UTF-8?q?=EB=9E=98=EB=8C=80=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 44815f2b..52c54321 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,6 @@ async function bootstrap() { app.useWebSocketAdapter(wsAdapter); setupSwagger(app); - await app.listen(4400); + await app.listen(3000); } bootstrap(); From f8103849added8235a1a0881da586edf9837cfc3 Mon Sep 17 00:00:00 2001 From: Haesung Date: Sun, 12 Oct 2025 18:16:02 +0900 Subject: [PATCH 27/28] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 65 +++++++---------------------------------------- 1 file changed, 9 insertions(+), 56 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index f002d259..9b31092c 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,49 +1,3 @@ -# #!/bin/bash -# set -e - -# APP_BASE="/home/ec2-user/app" -# CURRENT_DIR="$APP_BASE/current" -# TEMP_DIR="$APP_BASE/deploy-temp" -# NEW_DIR="$APP_BASE/new_$(date +%s)" -# BACKUP_DIR="$APP_BASE/backup" - -# echo "=== Deploy Hook Start ===" -# echo "Temporary deploy dir: $TEMP_DIR" - -# # agent로 실행하면 root 소유기 떄문에 복사 -# rsync -a --chown=ec2-user:ec2-user "$TEMP_DIR/" "$NEW_DIR/" - -# # 2️⃣ 의존성 설치 -# cd "$NEW_DIR" -# npm install --production - -# # 3️⃣ 새 서버 시작 -# # PM2로 기존 앱 이름 그대로 새 디렉토리 환경에서 실행 -# pm2 start dist/src/main.js --name lia-server --update-env || { -# echo "=== Deploy Failed, Rolling Back ===" -# # 실패 시 기존 서버 재실행 -# pm2 delete lia-server || true -# if [ -d "$BACKUP_DIR" ]; then -# mv "$BACKUP_DIR" "$CURRENT_DIR" -# pm2 start "$CURRENT_DIR/dist/src/main.js" --name lia-server -# fi -# exit 1 -# } - -# # 기존 버전 백업 -# if [ -d "$CURRENT_DIR" ]; then -# mv "$CURRENT_DIR" "$BACKUP_DIR" -# fi - -# # 새 버전을 current로 -# mv "$NEW_DIR" "$CURRENT_DIR" - -# # 성공 시 기존 백업 삭제 -# rm -rf "$BACKUP_DIR" - -# echo "=== Deploy Hook End ===" - - #!/bin/bash set -e @@ -56,33 +10,33 @@ BACKUP_DIR="$APP_BASE/backup" echo "=== Deploy Hook Start ===" echo "Temporary deploy dir: $TEMP_DIR" -# 1️⃣ CodeDeploy agent가 root로 실행하므로 소유권 변경하며 복사 +# 1. CodeDeploy agent가 root로 실행하므로 소유권 변경하며 복사 rsync -a --chown=ec2-user:ec2-user "$TEMP_DIR/" "$NEW_DIR/" -# 2️⃣ 의존성 설치 +# 2. 의존성 설치 cd "$NEW_DIR" npm install --production -# 3️⃣ Prisma 생성 (필요시) +# 3. prisma client 생성 npx prisma generate -# 4️⃣ 기존 PM2 프로세스 중지 및 삭제 +# 4. 기존 PM2 프로세스 중지 및 삭제 pm2 describe lia-server > /dev/null 2>&1 && { echo "Stopping existing process..." pm2 stop lia-server pm2 delete lia-server } || echo "No existing process found" -# 5️⃣ 기존 버전 백업 +# 5. 기존 버전 백업 if [ -d "$CURRENT_DIR" ]; then rm -rf "$BACKUP_DIR" mv "$CURRENT_DIR" "$BACKUP_DIR" fi -# 6️⃣ 새 버전을 current로 이동 (PM2 시작 전에!) +# 6. 새 버전을 current로 이동 (PM2 시작 전에), 시작 후 옮기면 경로 꼬임 mv "$NEW_DIR" "$CURRENT_DIR" -# 7️⃣ current 디렉토리에서 PM2 시작 +# 7. current 디렉토리에서 PM2 시작 cd "$CURRENT_DIR" pm2 start dist/src/main.js --name lia-server || { echo "=== Deploy Failed, Rolling Back ===" @@ -101,8 +55,7 @@ pm2 start dist/src/main.js --name lia-server || { exit 1 } -# 8️⃣ PM2 설정 저장 +# 8. PM2 설정 저장 pm2 save -echo "=== Deploy Successful ===" -echo "Running from: $CURRENT_DIR" \ No newline at end of file +echo "=== Deploy Successful ===" \ No newline at end of file From ff0cf432843c775c61bdb657aea88e01c0010439 Mon Sep 17 00:00:00 2001 From: Haesung Date: Sun, 12 Oct 2025 18:20:00 +0900 Subject: [PATCH 28/28] =?UTF-8?q?chore:=20deploy=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0=20=EB=B8=8C=EB=9E=9C=EC=B9=98=20main=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 247b2f0b..f2a327cd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,50 +3,49 @@ name: Build & Deploy to EC2 via CodeDeploy on: push: branches: - ## test - - feat/auto-deploy-config + - main jobs: build-and-deploy: runs-on: ubuntu-latest steps: - # 1️⃣ 코드 체크아웃 + # 1. 코드 체크아웃 - name: Checkout uses: actions/checkout@v4.2.2 - # 2️⃣ Node.js 설치 + # 2. Node.js 설치 - uses: actions/setup-node@v3 with: node-version: 22 cache: "npm" - # 3️⃣ 의존성 설치 + # 3. 의존성 설치 - run: npm install - # 4️⃣ Build + # 4. Build - run: npx prisma generate - run: npm run build - # 5️⃣ 환경 변수 생성 (.env) + # 5. 환경 변수 생성 (.env) - name: Create .env file from secrets run: | echo "${{ secrets.PRODUCTION_ENV }}" > .env - # 5️⃣ AWS CLI 설정 + # 6. AWS CLI 설정 - uses: aws-actions/configure-aws-credentials@v3 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-2 - # 6️⃣ 빌드 결과물 zip + # 7. 빌드 결과물 zip - run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml .env - # 7️⃣ S3 업로드 + # 8. S3 업로드 - run: aws s3 cp app.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip - # 8️⃣ CodeDeploy 배포 생성 + # 9. CodeDeploy 배포 생성 - run: | aws deploy create-deployment \ --application-name ${{ secrets.DEPLOY_APPLICATION_NAME }} \