Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
70f6808
Merge pull request #3 from kimhansol2/mission10
kimhansol2 Jun 2, 2025
0dc49bd
'data_rollback'
kimhansol2 Jun 11, 2025
cf8b4b2
Merge pull request #14 from kimhansol2/test
kimhansol2 Jun 11, 2025
a09b1df
'data'
kimhansol2 Jun 12, 2025
390da01
'automatic'
kimhansol2 Jun 12, 2025
291df55
Merge pull request #15 from kimhansol2/test
kimhansol2 Jun 12, 2025
5f90e50
'start.sh_change'
kimhansol2 Jun 12, 2025
b57a014
Merge pull request #16 from kimhansol2/test
kimhansol2 Jun 12, 2025
e30516d
'start.sh_change_code'
kimhansol2 Jun 12, 2025
d59543c
Merge pull request #17 from kimhansol2/test
kimhansol2 Jun 12, 2025
a0efb88
'start.sh
kimhansol2 Jun 12, 2025
97feb78
Merge pull request #18 from kimhansol2/test
kimhansol2 Jun 12, 2025
e1199e9
'data'
kimhansol2 Jun 12, 2025
e992ea2
Merge pull request #19 from kimhansol2/test
kimhansol2 Jun 12, 2025
1a54042
'start.sh'
kimhansol2 Jun 12, 2025
9bbc1af
Merge pull request #20 from kimhansol2/test
kimhansol2 Jun 12, 2025
b1b38b4
'data
kimhansol2 Jun 12, 2025
8f97516
Merge pull request #21 from kimhansol2/test
kimhansol2 Jun 12, 2025
ad0bd2a
'start'
kimhansol2 Jun 12, 2025
645dfaf
Merge pull request #22 from kimhansol2/test
kimhansol2 Jun 12, 2025
6e52899
'data
kimhansol2 Jun 12, 2025
7776aef
Merge pull request #23 from kimhansol2/test
kimhansol2 Jun 12, 2025
3367b1d
'plz'
kimhansol2 Jun 12, 2025
f916192
Merge pull request #24 from kimhansol2/test
kimhansol2 Jun 12, 2025
ae576e9
'data'
kimhansol2 Jun 12, 2025
89980bf
Merge pull request #25 from kimhansol2/test
kimhansol2 Jun 12, 2025
d74f130
'data'
kimhansol2 Jun 12, 2025
3642e39
Merge pull request #26 from kimhansol2/test
kimhansol2 Jun 12, 2025
6759a58
'data_rollback'
kimhansol2 Jun 11, 2025
036449a
'data'
kimhansol2 Jun 12, 2025
a56fa89
'automatic'
kimhansol2 Jun 12, 2025
0174614
'start.sh_change'
kimhansol2 Jun 12, 2025
117f2be
'start.sh_change_code'
kimhansol2 Jun 12, 2025
f4a49c6
'start.sh
kimhansol2 Jun 12, 2025
03676b8
'data'
kimhansol2 Jun 12, 2025
feb9bf3
'start.sh'
kimhansol2 Jun 12, 2025
43a0ad5
'data
kimhansol2 Jun 12, 2025
0d61647
'start'
kimhansol2 Jun 12, 2025
17076d7
'data
kimhansol2 Jun 12, 2025
34b624a
'plz'
kimhansol2 Jun 12, 2025
2cbc68a
'data'
kimhansol2 Jun 12, 2025
de4bbc2
'data'
kimhansol2 Jun 12, 2025
134a64a
Merge pull request #27 from kimhansol2/test
kimhansol2 Jun 26, 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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
coverage
dist
.git
.env*
21 changes: 0 additions & 21 deletions .github/PULL_REQUEST_TEMPLATE.md

This file was deleted.

27 changes: 27 additions & 0 deletions .github/workflows/depoly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Deploy to EC2

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{secrets.EC2_HOST}}
username: ${{secrets.EC2_USER}}
key: ${{secrets.EC2_PRIVATE_KEY}}
port: 22
script: |
cd ~/1-sprint-mission/5-sprint-mission # EC2 인스턴스 내 프로젝트 경로로 진입
git reset --hard HEAD
git pull origin main # 코드 최신화
cd ~/1-sprint-mission
chmod +x ./5-sprint-mission/infra/ec2/start.sh
./5-sprint-mission/infra/ec2/start.sh
echo "Deployment successful!"
67 changes: 67 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 이 github actions 워크플로우는 main 브랜치로의 푸시 또는 pull request가 생기면 자동으로 실행
# 통합 테스트를 위해 postgresql 데이터베이스를 함께 실행한다.

name: Test

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest # github에서 제공하는 ubuntu 리눅스 머신에서 실행

defaults:
run:
working-directory: 5-sprint-mission

# Postgresql 서비스를 docker 컨테이너로 함께 실행
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 8s --health-retries 5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb
BASE_URL: http://localhost:3000
PORT: 3000

JWT_ACCESS_TOKEN_SECRET: ${{secrets.JWT_ACCESS_TOKEN_SECRET}}
JWT_REFRESH_TOKEN_SECRET: ${{secrets.JWT_REFRESH_TOKEN_SECRET}}

AWS_REGION: ap-northeast-2
AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
AWS_S3_BUCKET_NAME: sprintmission10

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "22.13.0"

- name: Install dependencies

run: npm install

- name: Apply DB Schema
run: npm run prisma:migrate #Prisma 마이그레이션 적용

- name: Type Check
run: npm run typecheck #TypeScript 타입 검사 수행

- name: Run Tests
run: npm run test:ci #테스트 실행
7 changes: 4 additions & 3 deletions 5-sprint-mission/.gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
node_modules
.env
.env*
ERD.MD
study.MD
tests
dist/
README.MD
request/
.env.test
coverage/
coverage/
test1.yml

5 changes: 2 additions & 3 deletions 5-sprint-mission/infra/ec2/ecosystem.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
module.exports = {
apps: [
{
name: 'mission10',
script: 'npx',
args: 'ts-node src/main.ts',
name: 'mission11',
script: 'dist/main.js',
env: {
NODE_ENV: 'production',
},
Expand Down
14 changes: 11 additions & 3 deletions 5-sprint-mission/infra/ec2/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
server {
events {}

http {
upstream express_app {
server express_app:3000;
}
server {
listen 80;

location / {
proxy_pass http://localhost:3000;
proxy_pass http://express_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
}

39 changes: 33 additions & 6 deletions 5-sprint-mission/infra/ec2/start.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
sudo npm install -g pm2
pm2 -v
pm2 start "npm run dev" --name mission-app
pm2 list
pm2 startup
pm2 save
#!/bin/bash
set -eux

cd "$(dirname "$0")/../../.."

echo "Docker 설치"
sudo yum update -y
sudo dnf install -y docker
sudo systemctl enable --now docker
sudo usermod -aG docker $USER

echo "Docker Compose 설치"
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version

echo "AWS CLI + jq 설치"
sudo yum install -y awscli jq

echo "SSM 파리미터로 .env.production 생성"
aws ssm get-parameters-by-path \
--path "/MyApp/prod/" \
--with-decryption \
--query "Parameters[*].{Name:Name,Value:Value}" \
--output json > /tmp/env.json

cat /tmp/env.json \
| jq -r '.[] | (.Name | split("/")[-1]) + "=" + .Value' \
> .env.production

echo "Docker Compose 실행"
docker-compose -f docker-compose.prod.yml down || true
docker-compose -f docker-compose.prod.yml up -d --build
11 changes: 6 additions & 5 deletions 5-sprint-mission/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
},
"scripts": {
"dev": "nodemon --watch src --ext ts --exec ts-node src/main.ts",
"start": "node dist/app.js",
"start": "node dist/main.js",
"build": "tsc",
"test": "dotenv -e .env.test -- prisma migrate dev && dotenv -e .env.test -- jest -i --coverage"
},
"prisma": {
"seed": "ts-node --esm prisma/seed.ts"
"typecheck": "tsc --noEmit",
"prisma:migrate": "npx prisma migrate dev",
"prisma:deploy": "npx prisma migrate deploy && npx prisma generate",
"test:local": "dotenv -e .env.test -- prisma migrate dev && dotenv -e .env.test -- jest -i --coverage",
"test:ci": "jest --runInBand --forceExit"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.8",
Expand Down
2 changes: 1 addition & 1 deletion 5-sprint-mission/request/user.http
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ POST http://localhost:3000/users
Content-Type: application/json

{
"email": "you12@example.com",
"email": "you123@example.com",
"password": "password1234",
"nickname": "도준",
"image": "https://example.com/profile.jpg"
Expand Down
13 changes: 8 additions & 5 deletions 5-sprint-mission/src/controller/imagesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import { uploadImageToS3 } from '../lib/upload';

const ALLOWED_MIME_TYPES = ['image/png', 'image/jpg', 'image/jpeg'];
const FILE_SIZE_LIMIT = 5 * 1024 * 1024;
// 허용할 이미지 타입 설정
// 업로드 최대 용량은 5MB

export const upload = multer({
storage:
NODE_ENV === 'production'
? multer.memoryStorage()
? multer.memoryStorage() //운영 환경에 사용
: multer.diskStorage({
// 개발 환경이면 사용
destination(req: Request, file: Express.Multer.File, cb: Function) {
cb(null, PUBLIC_PATH);
},
Expand All @@ -32,7 +35,7 @@ export const upload = multer({
if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) {
const err = new BadRequestError('Only png, jpeg, and jpg are allowed');
return cb(err);
}
} //파일 타입 필터링 PNG, JPG, JPEG만 허용

cb(null, true);
},
Expand All @@ -48,8 +51,8 @@ export async function uploadImage(req: Request, res: Response) {

if (NODE_ENV === 'production') {
try {
const url = await uploadImageToS3(file);
res.status(200).json({ url });
const url = await uploadImageToS3(file); //S3 업로드
res.status(200).json({ url }); //S3 URL 반환
return;
} catch (err) {
console.error(err);
Expand All @@ -58,7 +61,7 @@ export async function uploadImage(req: Request, res: Response) {
}
}

const url = `${BASE_URL}/uploads/${file.filename}`;
const url = `${BASE_URL}/uploads/${file.filename}`; //개발 환경에서는 로컬에 저장된 이미지 경로를 URL로 반환
res.status(200).send({ url });
return;
}
5 changes: 3 additions & 2 deletions 5-sprint-mission/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import path from 'path';
import dotenv from 'dotenv';
dotenv.config();

const envPath = `.env.${process.env.NODE_ENV || 'development'}`;
dotenv.config({ path: envPath });

if (!process.env.JWT_ACCESS_TOKEN_SECRET) {
throw new Error('JWT_ACCESS_TOKEN_SECRET 환경 변수가 설정되지 않았습니다.');
Expand All @@ -15,7 +17,6 @@ export const REFRESH_TOKEN_COOKIE_NAME: string = 'refreshToken';
export const DATABASE_URL: string | undefined = process.env.DATABASE_URL;
export const JWT_ACCESS_TOKEN_SECRET = process.env.JWT_ACCESS_TOKEN_SECRET;
export const JWT_REFRESH_TOKEN_SECRET = process.env.JWT_REFRESH_TOKEN_SECRET;
export const PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 3000;
export const NODE_ENV: string = process.env.NODE_ENV || 'development';
export const PUBLIC_PATH: string = path.resolve('public');
export const STATIC_PATH: string = '/uploads';
Expand Down
15 changes: 11 additions & 4 deletions 5-sprint-mission/src/lib/upload.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
//AWS SDK의 S3 클라이언트와 파일 업로드 명령어를 사용하기 위한 임포트
import { v4 as uuidv4 } from 'uuid';
// 고유한 파일명을 만들기 위해 UUID를 사용
import path from 'path';
import { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, S3_BUCKET_NAME } from './constants';

//AWS 인증 정보 및 환경 설정을 상수로 불러온다.
export const s3Client = new S3Client({
region: AWS_REGION,
credentials: {
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
},
});
}); //AWS S3와 통신할 클라이언트를 생성 REGION, credentials를 설정하여 인증된 상태로 사용

function generateKey(file: Express.Multer.File): string {
const ext = path.extname(file.originalname);
return `uploads/${uuidv4()}${ext}`;
}
} // 파일 이름이 중복되지 않도록 UUID를 사용해서 S3의 파일 키를 생성
// 업로드 폴더 아래에 저장되도록 경로를 지정

export async function uploadImageToS3(file: Express.Multer.File): Promise<string> {
const key = generateKey(file);
Expand All @@ -24,7 +27,9 @@ export async function uploadImageToS3(file: Express.Multer.File): Promise<string
Body: file.buffer,
ContentType: file.mimetype,
});

// 업로드할 파일을 PutObjectCommand로 감싸 명령 객체를 만든다
// body는 실제 파일의 바이너리 데이터
// contentType은 image/jpeg, image/png 같은 mime타입이다.
try {
await s3Client.send(command);
return `https://${S3_BUCKET_NAME}.s3.${AWS_REGION}.amazonaws.com/${key}`;
Expand All @@ -33,3 +38,5 @@ export async function uploadImageToS3(file: Express.Multer.File): Promise<string
throw new Error('S3 업로드 실패');
}
}
// s3로 명령을 보내 업로드를 시도하고 성공하면 해당 이미지의 접근 가능한 url을 반환
// 실패하면 에러를 throw하여 에러 처리 로직이 동작
6 changes: 4 additions & 2 deletions 5-sprint-mission/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import app from './app';

app.listen(3000, () => {
console.log('Server is running on port 3000');
const port = Number(process.env.SERVER_PORT) || 3000;

app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#빌드 스테이지
FROM node:20 AS build-stage
WORKDIR /build
COPY 5-sprint-mission/package*.json ./
RUN npm ci
COPY 5-sprint-mission ./
RUN npm run build

#실행 스테이지
FROM node:20-slim
WORKDIR /app
COPY --from=build-stage /build/dist ./dist
COPY --from=build-stage /build/package*.json ./
RUN npm ci --omit=dev
ENV SERVER_PORT=3000
ENTRYPOINT [ "npm","run","start" ]
Loading