Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Dependencies
node_modules
*.log

# Git
.git
.gitignore

# Environment
.env
.env.*

# OS / Editor
.DS_Store
Thumbs.db
.vscode
.idea

# Build artifacts
dist
build
coverage

# Docker
.dockerignore
28 changes: 28 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# .github/workflows/deploy.yml
name: Deploy to EC2

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest

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

- name: Deploy to EC2
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
port: 22
script: |
# start.sh에 실행 권한 주기
chmod +x /home/ec2-user/3-sprint-mission/infra/ec2/start.sh
# 배포 스크립트 실행
bash /home/ec2-user/3-sprint-mission/infra/ec2/start.sh
59 changes: 59 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: CI

on:
pull_request:
branches: ["**"]

jobs:
panda-market-test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5

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

- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: "lts/*"

- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
run: npm install

- name: Type Check
run: npm run typecheck

- name: Apply Prisma migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: postgres://postgres:password@localhost:5432/test_db

- name: Run Tests
run: npm run test
env:
DATABASE_URL: postgres://postgres:password@localhost:5432/test_db
JWT_ACCESS_SECRET: test_jwt_access_secret
JWT_REFRESH_SECRET: test_jwt_refresh_secret
54 changes: 54 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# ====================================
# 빌드 스테이지 (build stage)
# ====================================
ARG NODE_VERSION=22.16.0
FROM node:${NODE_VERSION} AS my-build-stage

# 작업 디렉터리
WORKDIR /docker-compose-app

# 의존성 모듈 설치
COPY package*.json ./
RUN npm ci

# openssl 설치
RUN apt-get update -y && apt-get install -y openssl

# 애플리케이션 소스 복사
COPY . .

# Prisma 설정 및 생성
RUN npx prisma generate

# 빌드
RUN npm run build

# 프로덕션 의존성만 남기기
RUN npm prune --omit=dev


# ====================================
# 런타임 스테이지 (runtime stage)
# ====================================
FROM node:${NODE_VERSION}-slim AS runtime

# openssl 설치
RUN apt-get update -y && apt-get install -y openssl
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 중복으로 install 해야 하는 이유가 혹시 있었나요 ??


# 보안을 위해 node 사용자 사용
USER node
WORKDIR /docker-compose-app

# 필요한 파일만 복사
COPY --chown=node:node --from=my-build-stage /docker-compose-app/package*.json ./
COPY --chown=node:node --from=my-build-stage /docker-compose-app/node_modules ./node_modules
COPY --chown=node:node --from=my-build-stage /docker-compose-app/dist ./dist
COPY --chown=node:node --from=my-build-stage /docker-compose-app/prisma ./prisma

# 환경
ENV NODE_ENV=production

EXPOSE 3000

# 앱 시작: dist/app.js 기준
ENTRYPOINT [ "sh", "-c", "npx prisma migrate deploy && npm run start" ]
56 changes: 56 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: youngwook-docker-app

services:
my-express-app:
image: youngwookcho/docker-app-image:1.0.0
build:
context: .
dockerfile: ./Dockerfile
args:
- NODE_VERSION=22.16.0
tags:
- youngwookcho/docker-app-image:1.0.0
- youngwookcho/docker-app-image
container_name: docker-app-container
env_file:
- .env
environment:
- PORT=3000
- DATABASE_URL=${POSTGRES_DATABASE_URL}
networks:
- docker-app-network
ports:
- 4000:3000
depends_on:
my-mypostgres-db:
condition: service_healthy
restart: on-failure
my-mypostgres-db:
image: postgres:17.5
container_name: mypostgres-db-container
env_file:
- .env
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
ports:
#로컬환경에서 5432가아닌 5433 사용중이라 컨테이너 포트 5434로 매핑
- 5434:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- docker-app-network
volumes:
- docker-app-volume:/var/lib/postgresql/data

networks:
docker-app-network:
name: docker-app-network

volumes:
docker-app-volume:
name: docker-app-volume
14 changes: 6 additions & 8 deletions infra/ec2/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@ fi

echo "PM2로 앱 실행"
mkdir -p logs
npx pm2 start infra/ec2/ecosystem.config.js --env production

# ecosystem.config.js 기반 실행 (중복 옵션 제거)
pm2 start infra/ec2/ecosystem.config.js --env production
# PM2 프로세스 저장 & 재부팅 자동 실행
npx pm2 save
npx pm2 startup -u ec2-user --hp /home/ec2-user

echo "PM2 프로세스 저장 & 재부팅 자동 실행 등록"
pm2 save
pm2 startup -u ec2-user --hp /home/ec2-user

echo "실행 상태 확인"
pm2 list
# 실행 상태 확인
npx pm2 list
23 changes: 12 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,30 @@
"test:coverage": "dotenv -e .env.test -- jest --coverage",
"prisma:migrate": "prisma migrate dev",
"prisma:reset": "prisma migrate reset --force",
"prisma:seed": "prisma db seed"
"prisma:seed": "prisma db seed",
"typecheck": "tsc --noEmit"
},
"prisma": {
"seed": "node prisma/seed.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.896.0",
"@prisma/client": "^6.10.0",
"@types/jest": "^30.0.0",
"@types/supertest": "^6.0.3",
"bcrypt": "^6.0.0",
"cookie": "^1.0.2",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.6.1",
"dotenv-cli": "^10.0.0",
"events": "^3.3.0",
"express": "^5.1.0",
"express-jwt": "^8.5.1",
"is-email": "^1.0.2",
"is-uuid": "^1.0.2",
"jest": "^30.1.3",
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.1",
"pm2": "^6.0.13",
"prisma": "^6.10.0",
"socket.io": "^4.8.1",
"superstruct": "^2.0.2",
"supertest": "^7.1.4",
"ts-jest": "^29.4.2",
"tslib": "^2.8.1",
"tsx": "^4.20.5",
"uuid": "^11.1.0",
"zod": "^4.0.5"
},
Expand All @@ -53,12 +45,21 @@
"@types/cookie-parser": "^1.4.9",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^30.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^24.1.0",
"@types/supertest": "^6.0.3",
"babel-jest": "^30.1.2",
"dotenv-cli": "^10.0.0",
"jest": "^30.1.3",
"nodemon": "^3.1.10",
"pm2": "^6.0.13",
"prisma": "^6.10.0",
"supertest": "^7.1.4",
"ts-jest": "^29.4.2",
"ts-node": "^10.9.2",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}
}
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x", "linux-arm64-openssl-1.1.x"]
}

datasource db {
Expand Down
13 changes: 9 additions & 4 deletions src/middlewares/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import dotenv from 'dotenv';
dotenv.config();
import { expressjwt } from 'express-jwt'
import { RequestHandler } from 'express';
import { GetResourceFn } from '../types/auth';
Expand All @@ -8,10 +6,17 @@ import ProductService from '../services/productService';
import ArticleService from '../services/articleService';
import CommentService from '../services/commentService';


const getJWTSecret = () => {
const secret = process.env.JWT_ACCESS_SECRET;
console.log('Auth middleware JWT_ACCESS_SECRET:', secret);
if (!secret) {
throw new Error('JWT_ACCESS_SECRET이 설정되지 않았습니다.');
}
return secret;
};

const verifyAccessToken = expressjwt({
secret: (process.env.JWT_ACCESS_SECRET)!,
secret: getJWTSecret(),
algorithms: ['HS256'],
requestProperty: 'user',
});
Expand Down
3 changes: 2 additions & 1 deletion src/repositories/productRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import prisma, { Prisma } from "../config/prisma";
import prisma from "../config/prisma";
import { Prisma } from "@prisma/client";
import { HttpError } from "../types/error";
import { PaginatedResponseDto } from "../types/common";
import {
Expand Down
Loading