Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
590fd35
feat: 비밀섬 삭제 api 구현
koh1260 Oct 7, 2025
53e7166
test: 비밀섬 삭제 관련 테스트 추가
koh1260 Oct 7, 2025
de0809d
chore: deploy 스크립트 테스트
koh1260 Oct 8, 2025
782a124
chore: --production 태그 제거
koh1260 Oct 8, 2025
28ad80c
chore: 버킷 경로 secret으로 설정
koh1260 Oct 8, 2025
ed47eb1
chore: CodeDeploy 커맨드 설정
koh1260 Oct 9, 2025
f44bf46
chore: 누락된 ":" 추가
koh1260 Oct 9, 2025
5812c8c
chore: s3 디렉토리 path 파라미터 위치 변경
koh1260 Oct 9, 2025
bb6ec55
chore: s3에 업로드하는 파일명 수정
koh1260 Oct 9, 2025
d979bd8
chore: 오타 수정
koh1260 Oct 9, 2025
8294e43
chore: 테스트 스크립트 추가
koh1260 Oct 9, 2025
2e69278
chore: 환경변수 추가
koh1260 Oct 9, 2025
331b973
chore: 배포 스크립트 추가
koh1260 Oct 9, 2025
b7b0bbf
chore: 서버 포트 변경(테스트)
koh1260 Oct 9, 2025
abfee0c
chore: 임시 폴더에 배포 파일 로드
koh1260 Oct 9, 2025
c083ae3
chore: npm install 권한 문제 해결
koh1260 Oct 9, 2025
b59c3e5
chore: 권한 변경 명령 제거, --unsafe-perm 추가
koh1260 Oct 9, 2025
515d564
chore: 권한 문제로 파일 복사
koh1260 Oct 9, 2025
d80a39c
chore: 권한권한 권한
koh1260 Oct 9, 2025
08a61a2
chore: .env 권한 변경 제거
koh1260 Oct 9, 2025
d91243d
chore: 서버 main 파일 경로 수정
koh1260 Oct 9, 2025
687ab83
chore: 원래 포트로 변경
koh1260 Oct 9, 2025
c4c753d
chore: prisma markdown 주석처리
koh1260 Oct 9, 2025
9160de6
chore: current로 변경 타이밍 수정
koh1260 Oct 12, 2025
4685e65
chore: 임시 폴더 삭제 스크립트 제거
koh1260 Oct 12, 2025
4adcadf
chore: 포트 원래대로 변경
koh1260 Oct 12, 2025
f810384
chore: 주석 수정
koh1260 Oct 12, 2025
ff0cf43
chore: deploy 트리거 브랜치 main으로 변경
koh1260 Oct 12, 2025
fa24bb0
Merge pull request #140 from Metamorn-Team/feat/private-feature
koh1260 Oct 12, 2025
01ba6a7
Merge pull request #141 from Metamorn-Team/feat/auto-deploy-config
koh1260 Oct 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Build & Deploy to EC2 via CodeDeploy

on:
push:
branches:
- main

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

# 4. Build
- run: npx prisma generate
- run: npm run build

# 5. 환경 변수 생성 (.env)
- name: Create .env file from secrets
run: |
echo "${{ secrets.PRODUCTION_ENV }}" > .env

# 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

# 7. 빌드 결과물 zip
- run: zip -r app.zip dist package.json package-lock.json prisma scripts appspec.yml .env

# 8. S3 업로드
- run: aws s3 cp app.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip

# 9. CodeDeploy 배포 생성
- 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 }},key=${{ secrets.S3_BUILD_DIRECTORY_PATH }}/app.zip,bundleType=zip \
--deployment-config-name CodeDeployDefault.OneAtATime
12 changes: 12 additions & 0 deletions appspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 0.0
os: linux

files:
- source: /
destination: /home/ec2-user/app/deploy-temp

hooks:
AfterInstall:
- location: scripts/deploy.sh
timeout: 600
runas: ec2-user
10 changes: 5 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 18 additions & 0 deletions scripts/deploy-test.sh
Original file line number Diff line number Diff line change
@@ -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 ==="
61 changes: 61 additions & 0 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/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"

# 1. CodeDeploy agent가 root로 실행하므로 소유권 변경하며 복사
rsync -a --chown=ec2-user:ec2-user "$TEMP_DIR/" "$NEW_DIR/"

# 2. 의존성 설치
cd "$NEW_DIR"
npm install --production

# 3. prisma client 생성
npx prisma generate

# 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 ==="

# 실패한 current 제거
cd "$APP_BASE"
rm -rf "$CURRENT_DIR"

# 백업 버전 복구
if [ -d "$BACKUP_DIR" ]; then
mv "$BACKUP_DIR" "$CURRENT_DIR"
cd "$CURRENT_DIR"
pm2 start dist/src/main.js --name lia-server
echo "Rollback completed"
fi
exit 1
}

# 8. PM2 설정 저장
pm2 save

echo "=== Deploy Successful ==="
4 changes: 4 additions & 0 deletions src/domain/components/islands/private-island-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ export class PrivateIslandWriter {

return island;
}

async delete(id: string, now = new Date()) {
await this.privateIslandRepository.delete(id, now);
}
}
1 change: 1 addition & 0 deletions src/domain/interface/private-island.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PrivateIslandRepository {
): Promise<{ id: string; password: string | null } | null>;
findPasswordById(id: string): Promise<PrivateIslandForCheckPassword | null>;
findOneById(id: string): Promise<PrivateIsland | null>;
delete(id: string, now?: Date): Promise<void>;
}

export const PrivateIslandRepository = Symbol('PrivateIslandRepository');
22 changes: 21 additions & 1 deletion src/domain/services/islands/private-island.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -56,4 +59,21 @@ export class PrivateIslandService {
password,
);
}

async remove(id: string, userId: string, now = new Date()): Promise<void> {
await this.checkOwnership(id, userId);
await this.privateIslandWriter.delete(id, now);
}

async checkOwnership(islandId: string, userId: string): Promise<void> {
const { ownerId } = await this.privateIslandReader.readOne(islandId);

if (ownerId !== userId) {
throw new DomainException(
DomainExceptionType.FORBIDDEN,
HttpStatus.FORBIDDEN,
FORBIDDEN_MESSAGE,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository {
},
where: {
ownerId,
deletedAt: null,
},
skip: (page - 1) * limit,
take: limit,
Expand All @@ -72,7 +73,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository {

async countByOwner(ownerId: string): Promise<number> {
return await this.txHost.tx.privateIsland.count({
where: { ownerId },
where: { ownerId, deletedAt: null },
});
}

Expand All @@ -81,15 +82,15 @@ 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 },
});
}

async findPasswordById(
id: string,
): Promise<PrivateIslandForCheckPassword | null> {
return await this.txHost.tx.privateIsland.findUnique({
where: { id },
where: { id, deletedAt: null },
select: {
id: true,
password: true,
Expand All @@ -99,7 +100,7 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository {

async findOneById(id: string): Promise<PrivateIsland | null> {
const result = await this.txHost.tx.privateIsland.findUnique({
where: { id },
where: { id, deletedAt: null },
select: {
id: true,
ownerId: true,
Expand Down Expand Up @@ -130,4 +131,15 @@ export class PrivateIslandPrismaRepository implements PrivateIslandRepository {
mapKey: map.key,
};
}

async delete(id: string, now = new Date()): Promise<void> {
await this.txHost.tx.privateIsland.update({
where: {
id,
},
data: {
deletedAt: now,
},
});
}
}
32 changes: 31 additions & 1 deletion src/presentation/controller/islands/private-island.controller.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -118,4 +127,25 @@ export class PrivateIslandController {
): Promise<void> {
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);
}
}
Loading