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
38 changes: 38 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Node modules
node_modules
npm-debug.log
yarn-error.log

# Logs
logs
*.log
*.pid
*.seed
*.pid.lock

# Environment files
.env
.env.*

# OS / Editor files
.DS_Store
.vscode
.idea

# Git
.git
.gitignore

# Build output
dist
build
coverage

# Docker-related
Dockerfile.dev
docker-compose*.yml

# Tests
jest/
tests/
__tests__/
35 changes: 35 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: 🚀 Deploy to AWS EC2

on:
push:
branches:
- dev

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

# 1. SSH 접속 및 배포 명령 실행
- name: Deploy to EC2 via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.AWS_SSH_KEY }}
script: |
# 1. 프로젝트 디렉토리로 이동
cd /home/ec2-user/4-sprint-mission

# 2. 최신 코드 반영
git pull origin main

# 3. 의존성 설치 및 빌드
yarn install --frozen-lockfile
yarn build

# 4. PM2를 이용하여 앱 재시작
pm2 reload ecosystem.config.cjs --env production
41 changes: 0 additions & 41 deletions .github/workflows/ec2.yml

This file was deleted.

60 changes: 60 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: 🧪 Run Tests on Pull Request

on:
pull_request:
branches:
- "*" # 모든 브랜치에서 PR 발생 시 동작

jobs:
test:
runs-on: ubuntu-latest

# PostgreSQL 서비스 컨테이너 추가
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
NODE_ENV: test
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
JWT_SECRET: test-jwt-secret-key-for-ci
REFRESH_SECRET: test-refresh-secret-key-for-ci
PORT: 3000

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Generate Prisma Client
run: yarn prisma generate

- name: Setup Database Schema
run: yarn prisma db push
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

- name: Run All Tests
run: yarn test
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
JWT_SECRET: test-jwt-secret-key-for-ci
REFRESH_SECRET: test-refresh-secret-key-for-ci
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 1. 빌드 스테이지
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build

# 2. 실행 스테이지
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json

# 파일 업로드 폴더 생성 (볼륨 마운트 대상)
RUN mkdir -p /app/uploads

EXPOSE 3000

# 서버 실행 명령어
CMD ["node", "dist/app.js"]
26 changes: 26 additions & 0 deletions __mocks__/prisma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Jest가 @prisma/client import 시 이 파일을 대신 사용
export const prisma = {
user: {
findMany: jest.fn().mockResolvedValue([]),
findUnique: jest.fn().mockResolvedValue(null),
create: jest.fn().mockResolvedValue({}),
update: jest.fn().mockResolvedValue({}),
delete: jest.fn().mockResolvedValue({}),
},
post: {
findMany: jest.fn().mockResolvedValue([]),
findUnique: jest.fn().mockResolvedValue(null),
create: jest.fn().mockResolvedValue({}),
update: jest.fn().mockResolvedValue({}),
delete: jest.fn().mockResolvedValue({}),
},
product: {
findMany: jest.fn().mockResolvedValue([]),
findUnique: jest.fn().mockResolvedValue(null),
create: jest.fn().mockResolvedValue({}),
update: jest.fn().mockResolvedValue({}),
delete: jest.fn().mockResolvedValue({}),
},
};

export default prisma;
32 changes: 30 additions & 2 deletions app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,36 @@ app.use("/api/comment", commentRouter);
app.use("/api/like", likeRouter);
app.use("/api/notification", notificationRouter);

app.get('/', (req, res) => {
res.send('서버 정상 동작!');
// 1. 프론트엔드 빌드 파일의 경로 정의
const FRONTEND_BUILD_PATH = path.join(__dirname, '..', 'front', 'dist');

// 2. 정적 파일 제공 미들웨어 설정
if (process.env.NODE_ENV !== 'test') {
app.use(express.static(FRONTEND_BUILD_PATH));
}

// 3. SPA 라우팅 핸들러 설정
app.use((req, res, next) => {
// 테스트 환경에서는 정적 파일 서빙 로직을 건너뜀
if (process.env.NODE_ENV === 'test') {
return next();
}

// 1. 요청이 API 경로로 시작하지 않아야 하며,
// 2. 요청 메서드가 GET (페이지 요청) 이어야 합니다.
// 이 조건을 만족하면 index.html을 반환합니다.

if (req.method === 'GET' && !req.path.startsWith('/api')) {
// 이미 위에 express.static(FRONTEND_BUILD_PATH) 설정이 있어야 합니다!

// 브라우저 캐싱 문제 방지를 위해 Cache-Control 헤더 추가
res.setHeader('Cache-Control', 'no-cache');

res.sendFile(path.join(FRONTEND_BUILD_PATH, 'index.html'));
} else {
// API 요청이나 다른 메서드(POST, PUT 등)는 다음 미들웨어로 전달
next();
}
});

app.use("/auth", googleauthRouter);
Expand Down
62 changes: 34 additions & 28 deletions config/passport.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,43 @@ dotenv.config();

const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
const GOOGLE_CALLBACK_URL = process.env.GOOGLE_CALLBACK_URL || "http://52.79.236.229/auth/google/callback";
if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
console.warn("⚠️ Google OAuth 환경변수가 설정되지 않았습니다. GOOGLE_CLIENT_ID와 GOOGLE_CLIENT_SECRET을 설정해주세요.");
}
const GOOGLE_CALLBACK_URL =
process.env.GOOGLE_CALLBACK_URL || "http://52.79.236.229/auth/google/callback";

// ✅ 테스트 환경이면 Google OAuth 등록 건너뛰기
if (process.env.NODE_ENV !== "test") {
if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
console.warn(
"⚠️ Google OAuth 환경변수가 설정되지 않았습니다. GOOGLE_CLIENT_ID와 GOOGLE_CLIENT_SECRET을 설정해주세요."
);
}

passport.use(
new GoogleStrategy(
{
clientID: GOOGLE_CLIENT_ID || "",
clientSecret: GOOGLE_CLIENT_SECRET || "",
callbackURL: GOOGLE_CALLBACK_URL,
},
async (accessToken, refreshToken, profile, done) => {
try {
// Google 프로필 정보를 콜백으로 전달
return done(null, profile);
} catch (error) {
return done(error, false);
passport.use(
new GoogleStrategy(
{
clientID: GOOGLE_CLIENT_ID || "DUMMY_CLIENT_ID", // test 환경 대비 안전하게 dummy
clientSecret: GOOGLE_CLIENT_SECRET || "DUMMY_CLIENT_SECRET",
callbackURL: GOOGLE_CALLBACK_URL,
},
async (accessToken, refreshToken, profile, done) => {
try {
// Google 프로필 정보를 콜백으로 전달
return done(null, profile);
} catch (error) {
return done(error, false);
}
}
}
)
);
)
);

// 세션 직렬화 (필요한 경우)
passport.serializeUser((user: any, done) => {
done(null, user);
});
// 세션 직렬화 (필요한 경우)
passport.serializeUser((user: any, done) => {
done(null, user);
});

passport.deserializeUser((user: any, done) => {
done(null, user);
});
passport.deserializeUser((user: any, done) => {
done(null, user);
});
}

export default passport;

23 changes: 23 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: '3.8'

services:
# 1. Express 서버 컨테이너
app:
build: .
container_name: express-server
restart: always
ports:
- "3000:3000" # 호스트(EC2)의 3000번 포트로 연결
environment:
# 컨테이너 내부에서 db 서비스 이름으로 PostgreSQL에 접속
DATABASE_URL: postgresql://porstgres:1234qwer@arn:aws:rds:ap-northeast-2:516861152013:db:panda/panda
PORT: 3000
NODE_ENV: production
# 다른 환경 변수들 추가
volumes:
# 파일 업로드 폴더를 호스트 머신에 영구 저장 (Volume)
- uploads_data:/app/uploads

# 3. 볼륨 정의
volumes:
uploads_data:
15 changes: 15 additions & 0 deletions ecosystem.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
apps: [
{
name: "app",
script: "dist/app.js",
cwd: "/home/ec2-user/4-sprint-mission",
watch: false,
env: {
NODE_ENV: "production",
PORT: 3000,
}
}
]
};

Loading