Skip to content
Closed
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
32 changes: 32 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 환경 변수 파일
.env
.env.*
!.env.example

# 의존성 및 빌드 관련
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log

# 빌드 결과물
build
dist
coverage

# 버전 관리
.git
.gitignore

# 로그 파일
*.log

# IDE 설정
.idea
.vscode
*.swp
*.swo

# OS 관련
.DS_Store
Thumbs.db
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/panda-market
BASE_URL=http://localhost:3000
PORT=3000
JWT_ACCESS_TOKEN_SECRET=your-secret-key
JWT_REFRESH_TOKEN_SECRET=your-refresh-secret-key
AWS_REGION=ap-northeast-2
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_S3_BUCKET_NAME=panda-market
27 changes: 27 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Deploy

on:
push:
branches: [ "main" ]

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Configure SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.EC2_PEM_KEY }}" > ~/.ssh/deploy_key.pem
chmod 600 ~/.ssh/deploy_key.pem

- name: Deploy to EC2
run: |
ssh -i ~/.ssh/deploy_key.pem -o StrictHostKeyChecking=no ec2-user@${{ secrets.EC2_HOST }} '
cd /home/ec2-user/panda-market &&
git pull origin main &&
chmod +x start.sh &&
./start.sh
'
40 changes: 40 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Test

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

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432

steps:
- uses: actions/checkout@v4

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

- name: Install dependencies
run: npm install && npx prisma generate

- name: Run tests
env:
NODE_ENV: test
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
PORT: 3000
JWT_ACCESS_TOKEN_SECRET: test-secret-key
JWT_REFRESH_TOKEN_SECRET: test-refresh-secret-key
run: npm test
10 changes: 4 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
.vscode/
node_modules/
.env*
!env.example
!.env.example
build/
coverage/
*.csv
dist/
bun.lock*

# Uploaded files
public/*
!public/.gitkeep
!public/socket-client-test.html

# Generated & upload dirs
generated/prisma/
uploads/
17 changes: 8 additions & 9 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"printWidth": 100,
"endOfLine": "auto",
"arrowParens": "always",
"tabWidth": 2
}

"singleQuote": true,
"trailingComma": "all",
"semi": true,
"printWidth": 100,
"endOfLine": "auto",
"arrowParens": "always",
"tabWidth": 2
}
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Build stage
FROM node:21-alpine AS build
# bcrypt를 위한 빌드 도구 설치
RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npx prisma generate
RUN npm run build

# Development stage
FROM node:21-alpine AS development
WORKDIR /app
# 실행에 필요한 파일만 복사
COPY --from=build /app/{package*.json,node_modules,build,prisma} ./
ENV NODE_ENV=development
ENV PORT=3000
ENV BASE_URL=http://localhost:3000
ENV PUBLIC_PATH=/app/public
ENV STATIC_PATH=/public
VOLUME /app/public
EXPOSE 3000
CMD ["sh", "-c", "npx prisma migrate dev && npm start"]
146 changes: 28 additions & 118 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,130 +1,40 @@
# 코드잇 백엔드 - Sprint 8 미션
# Panda Market API

---
## 환경 변수 설정
`.env.example` 파일을 참고해서 `.env`와 `.env.test`에 필요한 환경 변수를 설정해 주세요.

## ✅ 미션 목표
## 설치

---
의존성 패키지를 설치합니다.

## 📌 기본 요구 사항
```
npm install
```

> ✅ 아래 요구사항을 **모두 충족**하도록 구현했습니다. 각 항목별 **REST API + 실시간 수신**이 연결됩니다.
Prisma와 데이터베이스를 준비합니다.
```
npx prisma generate
npx prisma migrate dev
```

### 1) 알림
## 실행

* **내 알림 목록 조회**
`npm dev`로 개발 모드로 실행할 수 있습니다.

* `GET /notifications/me?page=1&pageSize=10`
* **내 안 읽은 알림 개수**
## 스프린트 미션 11 관련 설명

* `GET /notifications/me/unread-count`
* **알림 읽음 처리**
### Github Actions

* `PATCH /notifications/:id/read`
* body 없음(토글 아님, 읽음으로 고정)
- `test.yaml`
- Github actions에서 제공하는 Postgres를 사용해서 테스트를 진행합니다.
- `deploy.yaml`
- SSH로 접속해 AWS EC2에 배포합니다.
- 이때 EC2 접속에 쓰는 키페어를 환경 변수인 `EC2_PEM_KEY`에 설정하고 public IP를 환경 변수 `EC2_HOST`에 설정해야 합니다.
- 코드의 `/panda-market`은 EC2 안에 있는 git repo 경로입니다.
- `start.sh` 파일을 만들어서 pm2를 실행하도록 했습니다. 여기서 사용한 `#!/bin/bash`의 의미는 "Shebang"을 찾아보세요!

### 2) 알림 전송(트리거)
### Docker

* **좋아요한 상품의 가격이 변동**되었을 때 해당 **좋아요 유저**에게 알림

* `PATCH /products/:id` 로 price 변경 시
* **내가 작성한 글에 댓글**이 달렸을 때 **글 작성자**에게 알림

* `POST /articles/:articleId/comments`
* `POST /products/:productId/comments`

### 3) 실시간 수신 (Socket.IO)

* 클라이언트 연결:

* `io("…", { auth: { token: "<AccessToken>" } })`
* 서버 게이트웨이:

* 핸드셰이크에서 **JWT 검증**, `socket.data.user = { id… }`
* 접속 시 `socket.join("user-{id}")`
* 서버 유틸 `sendNotification(userId, payload)` → `io.to("user-{id}").emit("notification", payload)`

---

## 🧱 심화 요구 사항


---

## 🔧 주요 변경사항

### 1) 데이터 모델(Prisma)

* `Notification` 모델 추가

* `id, userId, type('PRICE_CHANGE'|'COMMENT'), message, meta(JSON), isRead, createdAt`
* `userId` 인덱스, `createdAt` desc 정렬 기본
* `ProductLike` 복합 키 보강, `Product`/`Article` 관계 정리
* 마이그레이션

```bash
npx prisma migrate dev
```

### 2) 인증/미들웨어

* `authenticateUser` / `optionalAuthenticateUser`

* `Authorization: Bearer <token>` 파싱 → `verifyAccessToken` → `req.user` 주입
* `errorHandler` 표준화

* Validation(`StructError`) 400, 커스텀 `HttpError`류는 지정 상태코드, 나머지 500

### 3) Socket.IO 게이트웨이

* `notification.gateway.ts`

* `initNotificationGateway(httpServer)`로 초기화
* 핸드셰이크 인증(`handshake.auth.token` 또는 `Authorization` 헤더)
* 유저별 룸 조인, `sendNotification(userId, data)` 유틸 제공
* 서버 부팅부(`main.ts`)에서 **HTTP 서버 생성 → 게이트웨이 주입**

### 4) 알림 서비스/리포지토리

* 생성: `create({ userId, type, message, meta })`
* 조회: `listByUser({ userId, page, pageSize })`
* 안 읽은 개수: `countUnread(userId)`
* 읽음 처리: `markAsRead(id, userId)`
* **비즈니스 트리거 연동**

* 가격 변경 시: `ProductService.update()` 안에서 price 변경 감지 → 좋아요 유저 목록 조회 → 알림 생성 + 소켓 푸시
* 댓글 작성 시: `CommentService.create()` 안에서 대상 글 작성자에게 알림 생성 + 소켓 푸시

### 5) 레이어드/DTO 정리 (예: Products)

* `src/products/`

* `dtos/product.request.dto.ts`, `dtos/product.response.dto.ts`
* `productRepository.ts`(Prisma 접근)
* `productService.ts`(비즈니스)
* `productsController.ts`(입출력 + 미들웨어)
* **컨트롤러에서 서비스 생성 시** `new ProductService(new PrismaProductRepository())` 주입

### 6) 문서/개발 편의

* `docs/*`(API 명세) 업데이트

---

## 🖼 스크린샷

---

## 🙋 멘토에게

저번 스프린트에서 미처 적용하지 못했던 **레이어드 아키텍처와 DTO 구조**를 이번에 전체적으로 반영했습니다.
처음에는 **Socket.IO를 이벤트 기반으로만** 단순 연결할까 고민했는데, 어차피 구조를 정리하면서 DTO와 서비스 계층까지 다듬는 김에 **알림(Notification) 기능 자체를 비즈니스 로직으로 통합**하는 방식으로 구현했습니다.

* 컨트롤러는 입출력만 담당하고
* 서비스 계층에서 알림 생성/전송 로직을 처리하며
* DTO로 요청/응답을 명확히 정의했습니다.

이 과정에서 알림도 단순한 이벤트가 아니라 **하나의 도메인 기능**으로 다뤄지게 되어, 이후 유지보수나 확장성 측면에서도 더 깔끔하게 관리할 수 있을 것 같습니다.


---
- `Dockerfile`과 `docker-compose.yaml` 파일을 참고해 주세요.
- 이 프로젝트에서는 bcrypt 패키지를 사용하고 있어서, 빌드에 필요한 도구들을 설치하는 코드가 `Dockerfile`에 있습니다.
- `COPY --from=build /app/{package*.json,node_modules,build,prisma} ./`에서 사용한 `{ ... }` 문법은 "Brace Expansion" 문법을 찾아 보세요!
30 changes: 30 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
services:
db:
image: postgres:latest
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=panda_market
volumes:
- postgres_data:/var/lib/postgresql/data

app:
build: .
ports:
- "3000:3000"
volumes:
- ./public:/app/public
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:postgres@db:5432/panda_market
- PORT=3000
- BASE_URL=http://localhost:3000
- PUBLIC_PATH=/app/public
- STATIC_PATH=/public
depends_on:
- db

volumes:
postgres_data:
9 changes: 9 additions & 0 deletions infra/ec2/ecosystem.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
apps: [{
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'production'
}
}]
};
Loading
Loading