diff --git a/README.md b/README.md
index 8b9b615..bd13aca 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,8 @@
# ๐ณ Welive
-[]()
-[]()
-[]()
-[]()
-[]()
-[]()
-[]()
-[]()
+
+
+
## ๐ ํ๋ก์ ํธ ์๊ฐ
@@ -15,491 +10,531 @@
์๋ฆฌ๋ธ(Welive)๋ ์
์ฃผ๋ฏผ๊ณผ ๊ด๋ฆฌ ๋จ์ฒด๊ฐ ํ์๋ฆฌ์์ ์ํตํ๊ณ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ฉฐ, ๋ ๋์ ๊ณต๋์ฒด๋ฅผ ๋ง๋ค์ด๊ฐ๋ ์ํํธ ์ํธ ๊ด๋ฆฌ ํ๋ซํผ์
๋๋ค.
๋น์ ์ ์ผ์์ด ๋ ํธํด์ง๋ ๊ทธ ์๊ฐ๊น์ง, ์๋ฆฌ๋ธ๊ฐ ํจ๊ปํฉ๋๋ค.
----
-
-## ๐ ํ๋ก์ ํธ ๊ฐ์
+## ๐ ๋ฐ๋ชจ & ์ฃผ์ ๋งํฌ
-- **ํ๋ก์ ํธ ๊ธฐ๊ฐ**: 2025.10.17 ~ 2025.11.28
-- **๋ชฉํ**: ๋ ์ด์ด๋ ์ํคํ
์ฒ ๊ธฐ๋ฐ์ ์์ ์ ์ด๊ณ ํ์ฅ ๊ฐ๋ฅํ ํ๋ก์ ํธ ๊ด๋ฆฌ ๋ฐฑ์๋ ๊ตฌ์ถ
-- **์ฃผ์ ๊ธฐ๋ฅ**:
-- ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ์น์ธ ํ๋ก์ธ์ค
- - ์ํผ๊ด๋ฆฌ์ / ๊ด๋ฆฌ์ / ์
์ฃผ๋ฏผ์ ์ญํ ๊ธฐ๋ฐ ์น์ธ ๊ตฌ์กฐ ๋ฐ 2์ฐจ ์ธ์ฆ
- - (๊ด๋ฆฌ์ยท์
์ฃผ๋ฏผ ์น์ธ ์์ฒญ, ์น์ธ/๊ฑฐ์ , ์๋ ๋งค์นญ)
+- Frontend: https://welive.mimu.live
+- Backend: https://api.mimu.live
+- Swagger ๋ฌธ์: https://api.mimu.live/api/docs
+- GitHub Repository: https://github.com/codeit-welive/welive
+- ์ต์ข
๋ฐํ ์๋ฃ: ์ค๋น ์ค
+- ์์ฐ ์์: ์ค๋น ์ค
+- ํ ๋
ธ์
๋ฌธ์: ์ค๋น ์ค
-> [!NOTE]
-> ์ธ์ฆ์ **JWT Access / Refresh Token** ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ฉฐ, Refresh Token์ DB์์ ์ํ๋ฅผ ๊ด๋ฆฌํด ๋ก๊ทธ์์ยท์ฌ๋ฐ๊ธ ์ ๊ธฐ์กด ํ ํฐ์ ๋ฌดํจํํ๋๋ก ์ค๊ณํ์ต๋๋ค.
+## ๐ ๊ธฐ์ ์คํ
-- ์
์ฃผ๋ฏผ ๋ช
๋ถ ๊ด๋ฆฌ
- - ๊ฐ๋ณ ๋ฑ๋ก / CSV ์
๋ก๋ / ๊ฒ์ยทํํฐยทํ์ด์ง๋ค์ด์
/ ์ผ๊ด ์ ๋ฆฌ ๊ธฐ๋ฅ ์ ๊ณต
+### Backend
+[]()
+[]()
+[]()
+[]()
+[]()
-> [!WARNING]
-> CSV ์
๋ก๋ ์ **ํค๋ ๋ช
์นญ์ด ์ ํํ ์ผ์น**ํด์ผ ํ๋ฉฐ, ์ธ์ฝ๋ฉ์ **UTF-8**๋ง ์ง์๋ฉ๋๋ค.
-> ์๋ชป๋ ์นผ๋ผ ๋๋ ๋๋ฝ์ด ์๋ ๊ฒฝ์ฐ, ํด๋น ํ์ผ์ ์ ์ฒด ์
๋ก๋๊ฐ ๊ฑฐ๋ถ๋ฉ๋๋ค.
+### Infra / DevOps
+[]()
+[]()
+[]()
+[]()
+[]()
-- ๋ฏผ์(๋ฌธ์) ๊ด๋ฆฌ
- - ์ํ(์ฒ๋ฆฌ ์ ยท์คยท์๋ฃ) ๋ณ๊ฒฝ, ์ค์๊ฐ ์๋ฆผ, ๋น๊ณต๊ฐ ์ฒ๋ฆฌ, ์์ ยท์ญ์ ์ ํ ๋ก์ง ํฌํจ
-- ์ฃผ๋ฏผ ํฌํ ์์คํ
- - ํฌํ๊ถ์ ๋ฒ์ ์ค์ , ์ผ์ ๊ธฐ๋ฐ ์๋ ์คํ/๋ง๊ฐ, ๊ฒฐ๊ณผ ์๋ ๊ณต์ง ๋ฑ๋ก, ์ค์๊ฐ ์ฐธ์ฌ ๊ฐ๋ฅ
+### Testing & Quality
+[]()
+[]()
+[]()
+[]()
-- ๊ณต์ง์ฌํญ ๋ฐ ์ผ์ ๊ด๋ฆฌ
- - ์นดํ
๊ณ ๋ฆฌ, ์ค์๋, ๋๊ธ, ์กฐํ์, ์ผ์ ์๋ ๋ฐ์ ๋ฑ ๊ด๋ฆฌ์ ์ค์ฌ์ ๊ณต์ง ์ด์ ๊ธฐ๋ฅ
-- ๋๊ธ ์์คํ
- - ๊ณต์ง/๋ฏผ์ ๊ธฐ๋ฐ ๋๊ธ ๋ฑ๋กยท์์ ยท์ญ์ ๊ธฐ๋ฅ ์ ๊ณต
-- ์๋ฆผ ์์คํ
(SSE)
- - ๋ฏผ์ยท๊ณต์งยทํฌํ ์ํ ๋ณ๊ฒฝ ์ ์ค์๊ฐ ์๋ฆผ ์ ์ก
-- ํ์ผ ์
๋ก๋ (ํ๋กํ ์ฌ์ง)
- - S3 ์
๋ก๋ ๊ธฐ๋ฐ์ ์์ ์ ์ธ ์ด๋ฏธ์ง ์ฒ๋ฆฌ
+### Frontend (ํ์
/ ์ฐธ๊ณ )
+[]()
-> [!WARNING]
-> ์
๋ก๋๋ ํ์ผ์ ํ์ฅ์๊ฐ ์๋๋ผ **์ค์ ๋ฐ์ด๋๋ฆฌ(Magic Number)** ๊ธฐ๋ฐ์ผ๋ก ๊ฒ์ฆ๋๋ฉฐ,
-> ์ด๋ฏธ์ง๊ฐ ์๋ ํ์ผ์ ์์ฅํ์ฌ ์
๋ก๋ํ ๊ฒฝ์ฐ ์๋ฒ์์ ์ฆ์ ์ฐจ๋จ๋ฉ๋๋ค.
+---
- - DB ํธ๋์ญ์
๋ฐ ๋์์ฑ ์ต์ ํ ๊ตฌ์กฐ ์ค๊ณ
- - ๋๋ ์
๋ก๋(CSV), ํฌํ ๋ง๊ฐ ์ค์ผ์ค๋ฌ ๋ฑ์์์ ํธ๋์ญ์
ํน์ ๋์์ฑ ์ ํ ์ฒ๋ฆฌ ๋ฐ ์ฑ๋ฅ ๊ณ ๋ ค
+### ๐งญ ๊ธฐ์ ์คํ ์ฑํ ์ด์
- > [!NOTE]
- > Poll ์๋ ํ์ฑํ/๋ง๋ฃ ๋ก์ง์ **Limiter ๊ธฐ๋ฐ ๋์์ฑ ์ ์ด**๋ก ๋ณดํธ๋๋ฉฐ,
- > ๋์ผ Poll์ ๋ํ ์ค๋ณต ์ค์ผ์ค ์คํ์ ์๋์ผ๋ก ๋ฐฉ์ง๋ฉ๋๋ค.
+**Node.js + Express 5**
+- ๋๊ท๋ชจ ์๋น์ค์์๋ ๊ฒ์ฆ๋ ๊ฒฝ๋ ์น ํ๋ ์์ํฌ๋ก, ๋ผ์ฐํ
ยท๋ฏธ๋ค์จ์ด ๊ธฐ๋ฐ ๊ตฌ์กฐ๋ฅผ ์ธ๋ฐํ๊ฒ ์ค๊ณํ๊ธฐ ์ฉ์ดํจ
+- ๋น๋๊ธฐ I/O ๊ธฐ๋ฐ์ผ๋ก SSEยทSocket.io ๋ฑ ์ค์๊ฐ ๊ธฐ๋ฅ ๊ตฌํ์ ์ ํฉ
+- Layered Architecture ๊ตฌ์ฑ ์ Router โ Controller โ Service โ Repository ํ๋ฆ์ ๊ฐ์ฅ ์ ์ฐํ๊ฒ ์ค๊ณํ ์ ์์
----
+**TypeScript**
+- ๋๋ฉ์ธ ๋จ์๋ก ํ์
์ ์๊ฒฉํ๊ฒ ์ ์ํด ๋๊ท๋ชจ ๋ชจ๋ ๊ฐ ์์กด์ฑ์ ์์ ์ ์ผ๋ก ์ ์ง
+- DTOยทValidatorยทRepository ๋ฑ ๊ณ์ธต ๊ฐ ๋ฐ์ดํฐ ๊ณ์ฝ์ ๋ช
ํํ ๋ณด์ฅ
+- Prisma์ ๊ฒฐํฉํ์ฌ ์ปดํ์ผ ๋จ๊ณ์์ DB ์คํค๋ง ์ค๋ฅ๋ฅผ ์กฐ๊ธฐ ๊ฐ์งํ ์ ์์
-## ๐ฅ ํ ๊ตฌ์ฑ ๋ฐ ์ญํ
+**Prisma + PostgreSQL**
+- Prisma์ ์คํค๋ง ๊ธฐ๋ฐ ORM์ ๋ชจ๋ธ ๋ณ๊ฒฝ ๋ฐ ๋ง์ด๊ทธ๋ ์ด์
๊ด๋ฆฌ๊ฐ ์ง๊ด์ ์ด๋ฉฐ, ๋๊ท๋ชจ ํ
์ด๋ธ ์กฐ์ธ ๊ตฌ์กฐ์์๋ ํ์
์์ ์ฑ์ ์ ๊ณต
+- PostgreSQL์ ํธ๋์ญ์
๊ฐ๋๊ฐ ๋์ Poll Schedulerยท์๋ฆผยท๋๋ CSV ์ฒ๋ฆฌ ๋ฑ ๋์์ฑ ์๊ตฌ์ ์์ ์ ์ผ๋ก ๋์
-| ๋ด๋น์ | ์ญํ | ๋ด๋น ๋ชจ๋ | ์ฃผ์ ์ฑ
์ |
-| ------------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
-| [์ ์งํธ(๋ฆฌ๋)](https://github.com/selentia) | Backend Lead ยท System Architect ยท DevOps | Core, Infra/DevOps, Users, Notifications/SSE, Poll Scheduler, ์ ์ฒด ์คํค๋ง | ์์คํ
์ํคํ
์ฒ ๋ฐ ์คํค๋ง ์ค๊ณ / Coreยท์๋ฒ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ / ์ผ๋ถ ๋๋ฉ์ธ ๊ฐ๋ฐ ๋ฐ ํ
์คํธยท๋ฐฐํฌ ํ๊ฒฝ ๊ตฌ์ถ / ํ๋ก์ ํธ ๋ฌธ์ยท์ฐ์ถ๋ฌผ(์๊ตฌยท์ค๊ณยท๊ตฌํยทํ
์คํธยท๋ฐฐํฌ) ๋ฐ ์์ฐ ์์ ํตํฉ ์ ๋ฆฌ |
-| [ํ์ ์ฌ(ํ์ฅ)](https://github.com/HSunJ) | Auth & Residents & Apartments | Auth, Residents, Apartments | Auth ๊ธฐ๋ฅ ์ ๋ฐ ๊ตฌํ / ResidentsยทApartments CRUDยทCSV / ์
์ฃผ๋ฏผยท์ํํธ ๊ถํ ๋ก์ง |
-| [๊น์ค์ฒ ](https://github.com/nodejun) | Complaints & Comments & Chat | Complaints, Comments, Chat(Socket.io) | ComplaintsยทComments CRUD / ์ค์๊ฐ Chat API ๊ฐ๋ฐ |
-| [๊น๋์ฐ](https://github.com/luciakim22) | Polls & Notices | Polls, PollsVote, Notices, Events | PollsยทPollsVote CRUD / Notices CRUD, Events CRUD |
+**Docker + Nginx**
+- ๊ฐ๋ฐยทํ
์คํธยท์ด์ ํ๊ฒฝ์ ์๋ฒฝํ๊ฒ ๋์ผํ๊ฒ ์ ์งํ ์ ์์ด ๋ฐฐํฌ ์์ ์ฑ์ด ๋์
+- Nginx Reverse Proxy๋ฅผ ํตํด SSL, ์ ์ ์์ ์๋น์ค, CORS, ํค๋ ๋ณด์ ๋ฑ์ ๋จ์ผ ์ง์
์ ์์ ์ ์ด ๊ฐ๋ฅ
+- BE(์ปจํ
์ด๋) + FE(PM2) ์กฐํฉ์ผ๋ก ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ์ ์ต์ ํํ๋ฉด์๋ CI/CD ์๋ํ์ ์ ๋ฆฌ
----
+**AWS(EC2, S3)**
+- EC2๋ Docker ํ๊ฒฝ ๋ฐฐํฌ์ ์ง๊ด์ ์ด๋ฉฐ ํ์ฅ ๋ฐ ์ ์ง๋ณด์๊ฐ ์ฉ์ด
+- S3๋ ์ด๋ฏธ์งยท์ ์ ํ์ผ ์ ์ฅ์ ์์ ์ ์ด๋ฉฐ, Presigned URL ๊ธฐ๋ฐ ์
๋ก๋๋ก ๋ณด์ ์์ค์ ํฌ๊ฒ ๋์ผ ์ ์์
-## ๐ ๊ธฐ์ ์คํ
+**GitHub Actions + GHCR**
+- ํธ์ ์ด๋ฒคํธ ๊ธฐ๋ฐ์ผ๋ก ํ
์คํธ โ ๋น๋ โ ์ด๋ฏธ์ง ์์ฑ โ ์๋ฒ ์๋ ๋ฐฐํฌ๊น์ง ์์ ์๋ํ
+- GHCR ์ฌ์ฉ์ผ๋ก ์ด๋ฏธ์ง ๋ฒ์ ๊ด๋ฆฌ ๋ฐ ๋กค๋ฐฑ์ด ์ฉ์ดํ๋ฉฐ, ์ฌ์ค ๋ ์ง์คํธ๋ฆฌ๋ณด๋ค ์ค์ ์ด ๋จ์
-### โ๏ธ ๋ฐฑ์๋ ์๋ฒ
-
-| ๊ตฌ๋ถ | ๊ธฐ์ |
-| --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
-| **์ธ์ด/๋ฐํ์** | TypeScript, Node.js(Express 5) |
-| **์๋ฒ ํ๋ ์์ํฌยท๊ตฌ์กฐ** | Express 5 ๊ธฐ๋ฐ ์ปค์คํ
์๋ฒ(app.ts), ๋ผ์ฐํฐ ๋ชจ๋ ๊ตฌ์กฐ, ๋ฏธ๋ค์จ์ด ํ์ดํ๋ผ์ธ ์ค๊ณ |
-| **ORM/DB** | Prisma, PostgreSQL |
-| **์ค์ผ์ค๋ฌ** | cron (ํฌํ ํ์ฑํ/๋ง๋ฃ ์์
), **Limiter ๊ธฐ๋ฐ Poll Scheduler(์ง์ ๊ตฌํ)** |
-| **์ ํจ์ฑ ๊ฒ์ฆ** | Zod(DTOยทBodyยทQueryยทParams, CSV ๋ฐ์ดํฐ ๊ฒ์ฆ ํฌํจ) |
-| **์
๋ ฅ ์ ํ(Sanitize)** | isomorphic-dompurify + jsdom |
-| **ํ์ผ ์
๋ก๋/์ด๋ฏธ์ง ์ฒ๋ฆฌ** | multer, sharp |
-| **AWS ์ฐ๋** | AWS SDK v3 (@aws-sdk/client-s3, credential-providers, s3-request-presigner) โ S3 ์
๋ก๋ |
-| **๋ณด์/์์ ํ** | helmet(CSP ํฌํจ), hpp, express-rate-limit, CORS(ํ์ดํธ๋ฆฌ์คํธ ๊ธฐ๋ฐ), bcrypt(+pepper), jsonwebtoken(access/refresh) |
-| **๋ก๊ทธ/๋ชจ๋ํฐ๋ง** | Pino(์ ํ๋ฆฌ์ผ์ด์
๋ก๊ทธ), Morgan(HTTP ์์ฒญ ๋ก๊ทธ) |
-| **ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌ** | dotenv, dotenv-cli, cross-env |
+**Jest + Supertest**
+- DB ์ฐ๋ ํ
์คํธ, SSE ์ ๋ฌ ํ
์คํธ, ์ค์ผ์ค๋ฌ ๋์๊น์ง ๊ฒ์ฆ ๊ฐ๋ฅํ ๊ตฌ์กฐ
+- ํ
์คํธ DB๋ฅผ ๋ณ๋๋ก ๊ตฌ์ฑํด ์ค์ Prisma ํธ๋์ญ์
ํ๋ฆ์ ๊ทธ๋๋ก ์ฌํ ๊ฐ๋ฅ
---
-### ๐งช ๊ฐ๋ฐ ๋ฐ ํ
์คํธ
+### ๐งฉ ์ฃผ์ ํจํค์ง ์ฑํ ์ด์
-| ๊ตฌ๋ถ | ๊ธฐ์ |
-| ---------------------- | ----------------------------------- |
-| **ํ
์คํธ ํ๋ ์์ํฌ** | Jest, ts-jest |
-| **HTTP ํ
์คํธ** | supertest |
-| **๋ชจํน/๋๊ตฌ** | jest.mock, jsdom |
-| **์ ์ ๋ถ์** | ESLint, TypeScript ESLint, Prettier |
-| **๊ฒฝ๋ก/๋น๋ ์ ํธ๋ฆฌํฐ** | tsc-alias, tsconfig-paths, rimraf |
+- **Zod**: DTO/Query/Params๋ฅผ ์คํค๋ง ๊ธฐ๋ฐ์ผ๋ก ํตํฉ ๊ด๋ฆฌํ์ฌ ๊ฒ์ฆ ์ผ๊ด์ฑ์ ๋ณด์ฅ
+- **DOMPurify + jsdom**: ์๋ฒ๋จ Sanitization์ผ๋ก XSS ๊ณต๊ฒฉ ์ฐจ๋จ (FE์์ ์กฐ์ํ๋๋ผ๋ ๋ฌดํจํ)
+- **sharp**: ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ฆ ๋ฐ Magic Number ๊ธฐ๋ฐ MIME ๊ฒ์ฆ์ ํตํ ํ์ผ ์์ฅ ๊ณต๊ฒฉ ๋ฐฉ์ง
+- **Limiter(์ง์ ๊ตฌํ)**: Poll ์ค์ผ์ค๋ฌ ๋์์ฑ ์ ์ด(๋จ์ผ ์คํ ๋ณด์ฅ)
+- **Socket.io / SSE**: ์ฑํ
+ ์ค์๊ฐ ์๋ฆผ ๊ฐ๊ฐ์ ํน์ฑ์ ํ์ฉํ ์ด์ค ์ค์๊ฐ ์ฒ๋ฆฌ ๊ตฌ์กฐ
---
-### ๐ ์ด์ยท๋ฐฐํฌ(Infra / DevOps)
+## ๐ ํต์ฌ ๊ธฐ๋ฅ ์์ฝ
-| ๊ตฌ๋ถ | ๊ธฐ์ |
-| ----------------------------- | ------------------------------------------------------- |
-| **์ปจํ
์ด๋/๋ฐฐํฌ** | Docker, docker-compose, GitHub Actions(CI/CD ์๋ ๋ฐฐํฌ) |
-| **๋ฆฌ๋ฒ์ค ํ๋ก์** | Nginx |
-| **๋ฐฑ์๋ ํ๋ก์ธ์ค ์คํ ๋ฐฉ์** | Docker ์ปจํ
์ด๋ ๋จ์ผ ์คํ (PM2 ๋ฏธ์ฌ์ฉ) |
-| **ํ๋ก ํธ์๋ ๋ฐฐํฌ(์ฐธ๊ณ )** | Next.js โ PM2 ๊ธฐ๋ฐ ํ๋ก์ธ์ค ์คํ |
-| **๋ก๊น
/์ด์** | Pino ๋ก๊ทธ, Nginx Access/Error ๋ก๊ทธ |
+- **์ญํ ๊ธฐ๋ฐ ์ธ์ฆ/์น์ธ ํ๋ก์ธ์ค**
+ - ์
์ฃผ๋ฏผ ยท ๊ด๋ฆฌ์ ยท ์ํผ๊ด๋ฆฌ์ ๋ฑ ์ญํ ๋ณ ํ์๊ฐ์
, ์น์ธ ์์ฒญ, ์น์ธ/๊ฑฐ์ ํ๋ฆ
----
+- **์
์ฃผ๋ฏผ ๋ช
๋ถ ๊ด๋ฆฌ**
+ - CSV ์
๋ก๋, ๊ฒ์/ํํฐ๋ง, ํ์ด์ง, ์ผ๊ด ์ ๋ฆฌ ๊ธฐ๋ฅ ์ ๊ณต
-### ๐งฐ ๊ฐ๋ฐ ํธ์
+- **๊ณต์ง์ฌํญ / ๋ฏผ์ / ๋๊ธ ์์คํ
**
+ - CRUD, ์นดํ
๊ณ ๋ฆฌ, ์กฐํ์, ๋น๊ณต๊ฐ ์ฒ๋ฆฌ, ์์ ์ ํ ๋ฑ ์ด์ ๊ธฐ๋ฅ ํฌํจ
-| ๊ตฌ๋ถ | ๊ธฐ์ |
-| ------------------------------- | ----------------------------------- |
-| **์๋ ์ฌ์์** | nodemon |
-| **๋์ ์คํ ์คํฌ๋ฆฝํธ** | concurrently |
-| **Swagger ๋ฌธ์ ์๋ ์์ฑ/์๋น** | swagger-autogen, swagger-ui-express |
+- **์ค์๊ฐ ์๋ฆผ ์์คํ
(SSE)**
+ - ๋ฏผ์/๊ณต์ง/ํฌํ ์ํ ๋ณ๊ฒฝ ์ ์ฆ์ ์๋ฆผ ์ด๋ฒคํธ ์ ๋ฌ
-## ๐ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ
+- **์ฃผ๋ฏผ ํฌํ ์์คํ
(Polls)**
+ - ํฌํ๊ถ์ ๋ฒ์ ์ค์ , ์๋ ์คํ/๋ง๊ฐ, ์ค์๊ฐ ์ฐธ์ฌ, ๊ฒฐ๊ณผ ์๋ ์ง๊ณ
-ํ๋ก์ ํธ ์ ์ฒด ๊ตฌ์กฐ๋ ๋๊ท๋ชจ์ด๊ธฐ ๋๋ฌธ์, ์ฃผ์ ์ฑ
์ ๋จ์ ์ค์ฌ์ผ๋ก ์์ฝํ ํํ์
๋๋ค.
+- **์ด๋ฒคํธ ์บ๋ฆฐ๋ (๊ด๋ฆฌ์์ฉ)**
+ - ์ํํธ ๋ด ์ผ์ ๊ด๋ฆฌ ๋ฐ ๊ณต์ง์ ์ฐ๋๋๋ ์ด๋ฒคํธ CRUD ์ ๊ณต
-```
-welive/
-โโโ .github/workflows/ # CI/CD ํ์ดํ๋ผ์ธ (CI, CD)
-โโโ infra/ # ๋ฐฐํฌ/์ธํ๋ผ ๊ตฌ์ฑ
-โ โโโ docker/ # Dockerfile, docker-compose, ์๋ฒ ๊ตฌ์ฑ
-โ โโโ nginx/ # Nginx Reverse Proxy ์ค์
-โ
-โโโ prisma/ # Prisma ์คํค๋ง & ๋ง์ด๊ทธ๋ ์ด์
-โ โโโ schema.prisma
-โ โโโ migrations/
-โ โโโ seed.ts
-โ
-โโโ public/ # ์ ์ ํ์ผ (ํ
ํ๋ฆฟ ๋ฑ)
-โ โโโ templates/
-โ
-โโโ scripts/ # Swagger ์๋ ์์ฑ ๋ฑ ์คํฌ๋ฆฝํธ
-โ โโโ genSwagger.js
-โ โโโ pinTree.js
-โ
-โโโ src/
-โ โโโ @types/ # Express ํ์
ํ์ฅ
-โ โ
-โ โโโ common/ # ๊ณตํต ์ ํธ/์์/Error/Helper
-โ โ โโโ constants/
-โ โ โโโ errors/
-โ โ โโโ helpers/
-โ โ
-โ โโโ core/ # ์ฑ ์ฝ์ด ๋ ์ด์ด
-โ โ โโโ aws/ # S3 ์
๋ก๋/์ญ์ ์ ํธ
-โ โ โโโ files/ # ํ์ผ MIME/MagicNumber ๊ฒ์ฆ
-โ โ โโโ health/ # /health API
-โ โ โโโ middlewares/ # ์ธ์ฆ/CORS/CSV/Error ํธ๋ค๋ฌ
-โ โ โโโ sanitize/ # ์
๋ ฅ ์ ํ(DOMPurify)
-โ โ โโโ socket/ # Socket.io ์ด๊ธฐํ
-โ โ โโโ sse/ # SSE ๋ผ์ฐํฐ/Emitter
-โ โ โโโ utils/ # Limiter, Zod wrapper ๋ฑ
-โ โ โโโ app.ts # Express ์ฑ ์ด๊ธฐํ
-โ โ โโโ env.ts # ํ๊ฒฝ ๋ณ์ ๋ก๋(Zod ๊ฒ์ฆ)
-โ โ โโโ httpLogger.ts
-โ โ โโโ logger.ts
-โ โ โโโ prisma.ts
-โ โ โโโ router.ts
-โ โ
-โ โโโ jobs/ # Poll Scheduler(์๋ ํ์ฑํ/๋ง๋ฃ)
-โ โ โโโ poll/
-โ โ
-โ โโโ modules/ # ๋๋ฉ์ธ ๋ชจ๋ (RouterยทServiceยทRepoยทValidatorยทDto)
-โ โ โโโ apartments/
-โ โ โโโ auth/
-โ โ โโโ chats/
-โ โ โโโ comments/
-โ โ โโโ complaints/
-โ โ โโโ events/
-โ โ โโโ notices/
-โ โ โโโ notifications/
-โ โ โโโ poll-scheduler/
-โ โ โโโ polls/
-โ โ โโโ residents/
-โ โ โโโ users/
-โ โ
-โ โโโ index.ts # ์๋ฒ ์ํธ๋ฆฌ
-โ
-โโโ swagger/ # Swagger JSON ๊ฒฐ๊ณผ๋ฌผ
-โ
-โโโ tests/ # ํตํฉ ํ
์คํธ ๋ฐ Core ํ
์คํธ
-โ โโโ core/
-โ โโโ modules/
-โ โโโ jobs/
-โ โโโ setup/
-โ
-โโโ logs/ # ์๋ฒ/ํ
์คํธ ๋ก๊ทธ
-โโโ .env* # ํ๊ฒฝ ๋ณ์ ํ์ผ๋ค
-โโโ jest.config.ts
-โโโ tsconfig.json
-โโโ package.json
+- **์ค์๊ฐ ์ฑํ
(Socket.io)**
+ - ์
์ฃผ๋ฏผโ๊ด๋ฆฌ์ ๊ฐ ๋น ๋ฅธ ์๋๋ฅผ ์ํ ์ค์๊ฐ ๋ฉ์์ง ๊ธฐ๋ฅ
-```
+- **ํ๋กํ ์ด๋ฏธ์ง ์
๋ก๋ (S3 Presigned URL)**
+ - Magic Number ๊ธฐ๋ฐ ๊ฒ์ฆ์ผ๋ก ์์ฅ ํ์ผ ์ฐจ๋จ, ์์ ํ ํ์ผ ์ฒ๋ฆฌ
----
+- **๋์์ฑ ์ ์ด ๊ธฐ๋ฐ ์ค์ผ์ค๋ฌ (Limiter)**
+ - Poll ์๋ ํ์ฑํ/๋ง๋ฃ ์ ๋จ์ผ ์คํ ๋ณด์ฅ ๋ฐ ๊ฒฝ์ ์กฐ๊ฑด ๋ฐฉ์ง
-## โ๏ธ ์คํ ๋ฐฉ๋ฒ
+> ์๋ฆฌ๋ธ๋ ํ์ต์ฉ ์์ ๊ฐ ์๋๋ผ, ์ค์ ์ํํธ ๊ด๋ฆฌ ๋๋ฉ์ธ์ ๊ณ ๋ คํ
+> โ๊ธฐ๋ฅยท๋ณด์ยทํ์ฅ์ฑ ์ค์ฌ์ ์ค์ ํ๋ก์ ํธโ๋ก ์ค๊ณ๋์์ต๋๋ค.
-### 1) ํ๊ฒฝ ๋ณ์ ์์ (`.env.example`)
+## ๐ ์์คํ
์ํคํ
์ฒ
-`.env.example` ํ์ผ์ ์ฐธ๊ณ ํ์ฌ ์ค์ ํ๊ฒฝ์ ๋ง๊ฒ `.env`, `.env.production`, `.env.test` ํ์ผ์ ๊ตฌ์ฑํฉ๋๋ค.
+```mermaid
+flowchart LR
+ subgraph CLIENT["Client"]
+ A[Browser / Next.js]
+ end
-```env
-# DATABASE
-DATABASE_URL=postgresql://:@localhost:5432/welive
+ subgraph PROXY["Nginx Reverse Proxy"]
+ B[Nginx]
+ end
-# PORT
-PORT=3001
-FE_PORT=3000
+ subgraph BACKEND["Backend (Node.js ยท Express 5)"]
+ C[Router]
+ D[Controller]
+ E[Service]
+ F[Repository]
+ G[Prisma Client]
+ end
-# BASE URL (API ์ค๋ฆฌ์ง)
-BASE_URL=https://your.domain.com
+ subgraph DATABASE["PostgreSQL (RDS)"]
+ H[(PostgreSQL)]
+ end
-# FRONT (ํ๋ก ํธ์๋ ์ค๋ฆฌ์ง)
-FRONT_URL=https://your.domain.com
+ subgraph STORAGE["AWS S3"]
+ S3[S3 Bucket - Profile Images]
+ end
-# RUNTIME
-CORS_ORIGIN=https://your.domain.com
-ACCESS_TOKEN_SECRET=your_accesstoken_secret
-REFRESH_TOKEN_SECRET=your_refreshtoken_secret
-PASSWORD_PEPPER=your_password_pepper
-DEFAULT_AVATAR_URL=https://your_bucket_name.s3.ap-northeast-2.amazonaws.com/default_avatar.png
+ subgraph CICD["CI/CD Pipeline"]
+ GA[GitHub Actions]
+ GHCR[GHCR - Docker Image]
+ EC2[EC2 - Docker Runtime]
+ end
-# AWS S3
-AWS_REGION=ap-northeast-2
-AWS_S3_BUCKET_NAME=your_bucket_name
-AWS_S3_BASE_URL=https://your_bucket_name.s3.ap-northeast-2.amazonaws.com
+ %% Client -> Backend
+ A --> B --> C
+ C --> D --> E --> F --> G --> H
+
+ %% File Upload
+ E -->|Upload/Delete| S3
+
+ %% CI/CD Flow
+ GA --> GHCR --> EC2 --> B
```
-> [!WARNING]
-> ํ
์คํธ ํ๊ฒฝ(`.env.test`)์ Jest ์คํฌ๋ฆฝํธ ์คํ ์ ์๋์ผ๋ก ๋ก๋๋๋ฉฐ,
-> ํ
์คํธ์ฉ DB๋ `test:reset`/`test:migrate` ๊ณผ์ ์์ ์ด๊ธฐํ๋ฉ๋๋ค.
-> ์ด์ DB ์ฐ๊ฒฐ ์ ๋ณด๊ฐ `.env.test`์ ๋ค์ด๊ฐ์ง ์๋๋ก ๋ฐ๋์ ์ฃผ์ํด์ผ ํฉ๋๋ค.
+## ๐ Database ERD
-### 2) ํจํค์ง ์ค์น
+
+
+
-```bash
-npm install
-```
+### ๐ ์ฃผ์ ํ
์ด๋ธ ๊ด๊ณ ์์ฝ
-### 3) ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ (Prisma)
+- **Apartments โ Residents** : ํ ์ํํธ์๋ ์ฌ๋ฌ ์
์ฃผ๋ฏผ์ด ์์
+- **Residents โ Complaints / Notices / Comments** : ์
์ฃผ๋ฏผ์ด ์์ฑํ ๋ฏผ์ยท๊ณต์งยท๋๊ธ๊ณผ ์ฐ๊ฒฐ
+- **Polls โ PollVotes** : ํฌํ 1๊ฐ์ ์ฌ๋ฌ ํฌํ ๊ธฐ๋ก์ด ์ข
์
+- **Complaints / Notices / Polls โ Events** : ๋ชจ๋ ์ฃผ์ ํ๋์ ์ด๋ฒคํธ ๋ก๊ทธ๋ก ๊ธฐ๋ก
+- **Events โ Notifications** : ์ํ ๋ณ๊ฒฝ ์ ์๋ฆผ(SSE)๋ก ์ฐ๋
+- **Users โ Residents** : ์ฃผ๋ฏผ ๊ณ์ ๊ณผ ์ฌ์ฉ์ ๊ณ์ ์ 1:1 ๋งคํ ๊ตฌ์กฐ
-๊ฐ๋ฐ/ํ
์คํธ ํ๊ฒฝ ๋ชจ๋ Prisma ์คํค๋ง๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด๊ธฐํํด์ผ ํฉ๋๋ค.
+> ERD๋ Prisma Schema ๊ธฐ๋ฐ์ผ๋ก ์๋ ์์ฑ๋๋ฉฐ,
+> ๊ด๊ณํ ๋ชจ๋ธ์ ํตํด ๊ถํ/์ญํ ๊ธฐ๋ฐ ๊ด๋ฆฌ, ์ค์๊ฐ ์๋ฆผ, ํฌํ ์์คํ
๋ฑ
+> ์ฃผ์ ๊ธฐ๋ฅ์ ์ผ๊ด๋ ๊ตฌ์กฐ๋ก ํ์ฅํ ์ ์๋๋ก ์ค๊ณํ์ต๋๋ค.
-```bash
-# ๋ง์ด๊ทธ๋ ์ด์
+ ์๋ ์ ์ฒด ์ํ
-npm run prisma:migrate
-
-# ๋๋ ๊ฐ๋ณ ์คํ:
-npm run prisma:generate # ํ๋ฆฌ์ฆ๋ง ํด๋ผ์ด์ธํธ ์์ฑ
-npm run prisma:reset # DB ์ด๊ธฐํ (dev)
-npm run seed # seed.ts ์คํ
-npm run prisma:studio # Prisma Studio(์น ๊ธฐ๋ฐ DB UI)
-```
+## ๐ ์์คํ
๊ตฌ์กฐ (Layered Architecture)
-### 4) ๊ฐ๋ฐ ์๋ฒ ์คํ
+์๋ฆฌ๋ธ ๋ฐฑ์๋๋ **Express 5 ๊ธฐ๋ฐ์ Layered Architecture**๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ,
+๊ฐ ๋ ์ด์ด๊ฐ ๋ช
ํํ ์ญํ ์ ๊ฐ์ง๋๋ก ์ค๊ณํด ์ ์ง๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ์ ๋์์ต๋๋ค.
-์ด ํ๋ก์ ํธ๋ ๋ฐฑ์๋(Express), ํ๋ก ํธ์๋(Next.js) ๋ฅผ ๋์์ ๊ฐ๋ฐํ๊ธฐ ์ํด `concurrently`๋ฅผ ์ฌ์ฉํฉ๋๋ค.
+### ๐น Layer ๊ตฌ์ฑ
-```bash
-npm run dev
-```
+- **Router Layer**
+ - ์์ฒญ ์๋ํฌ์ธํธ ์ ์ ๋ฐ ๋ชจ๋ ๋จ์ ๋ผ์ฐํ
๊ตฌ์ฑ
+ - ์ธ์ฆ/์ธ๊ฐ, CSV ๊ฒ์ฆ, Sanitization ๋ฑ ๊ณตํต ๋ฏธ๋ค์จ์ด ์ ์ฉ ์ง์
-#### ๊ฐ๋ณ ์คํ ๋ฐฉ๋ฒ
+- **Controller Layer**
+ - HTTP ์์ฒญ/์๋ต ์ฒ๋ฆฌ
+ - ์๋น์ค ํธ์ถ ์ ์
๋ ฅ๊ฐ ๊ฒ์ฆ(Zod) ๋ฐ ์์ธ ํ๋ฆ ํต์
+ - Swagger ์๋ ๋ฌธ์ํ ์ฃผ์ ์์น
-**๋ฐฑ์๋ ๋จ๋
์คํ**
+- **Service Layer**
+ - ๋น์ฆ๋์ค ๋ก์ง ์ค์ฌ
+ - ๊ถํ ๊ฒ์ฆ, ํธ๋์ญ์
์ฒ๋ฆฌ, ๋๋ฉ์ธ ๊ท์น ์ ์ฉ
+ - SSE ์๋ฆผ ํธ๋ฆฌ๊ฑฐ, Poll ์ค์ผ์ค๋ฌ ํธ์ถ ๋ฑ ํต์ฌ ๋ก์ง ๋ด๋น
-```bash
-npm run dev:be
-```
+- **Repository Layer**
+ - Prisma ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ
+ - ๋ณต์กํ ์ฟผ๋ฆฌ/์กฐ์ธ/ํธ๋์ญ์
์ ์บก์ํํ์ฌ Service์ ๋ถ๋ฆฌ
-**ํ๋ก ํธ ๋จ๋
์คํ**
+- **Prisma Client Layer**
+ - PostgreSQL๊ณผ ์ง์ ํต์
+ - ํ์
์ธ์ดํํฐ ์ ๊ณต ๋ฐ ์คํค๋ง ๊ธฐ๋ฐ ๋ชจ๋ธ ๊ด๋ฆฌ
-```bash
-npm run dev:fe
-```
+### ๐น ์ถ๊ฐ ์๋ธ ์์คํ
-### 5) ํ
์คํธ ์คํ
+- **Input Sanitization (DOMPurify + jsdom)**
+ ๋๊ธ/๊ณต์ง/๋ฏผ์ ๋ฑ ์ฌ์ฉ์ ์
๋ ฅ ํ๋์ ๋ํด ์๋ฒ ๋จ์์ HTML ์ ํ ์ํ
-jest ํ๊ฒฝ์ `.env.test`๋ฅผ ์๋ ๋ก๋ํ๋ฉฐ,
-Prisma ํ
์คํธ DB๋ `test:migrate`/`test:reset`์ผ๋ก ์ ์ดํ ์ ์์ต๋๋ค.
+- **Validation Layer (Zod)**
+ Params/Query/Body๋ฅผ ์คํค๋ง ๊ธฐ๋ฐ์ผ๋ก ํตํฉ ๊ฒ์ฆํ์ฌ Controller์ ๋๋ฌํ๊ธฐ ์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ
-```bash
-npm test # ์ ์ฒด ํ
์คํธ ์คํ (--runInBand)
-npm run test:watch # ๋ณ๊ฒฝ ๊ฐ์ง
-npm run test:cov # ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง
-```
+- **SSE Notification Layer**
+ ์๋ฆผ emitter๋ฅผ ๋ณ๋ ๋ชจ๋๋ก ๋ถ๋ฆฌํด Poll/๋ฏผ์/๊ณต์ง ๋ฑ์ ์ด๋ฒคํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ์ก
-ํ
์คํธ์ฉ DB ์ด๊ธฐํ:
+- **Poll Scheduler (Limiter ๊ธฐ๋ฐ ๋จ์ผ ์คํ ๋ณด์ฅ)**
+ Poll ์๋ ์คํ/๋ง๋ฃ๋ฅผ Cron + Limiter ์กฐํฉ์ผ๋ก ๊ตฌํํด ๊ฒฝ์ ์กฐ๊ฑด์ ๋ฐฉ์ง
-```bash
-npm run test:reset
-npm run test:migrate
-```
+- **Socket Layer (์ค์๊ฐ ์ฑํ
)**
+ ์
์ฃผ๋ฏผโ๊ด๋ฆฌ์ ๊ฐ ์ค์๊ฐ ๋ฉ์์ง์ Socket.io๋ก ์ฒ๋ฆฌ (JWT ์ธ์ฆ ๋ฏธ๋ค์จ์ด ํฌํจ)
-> [!WARNING]
-> ํ
์คํธ ์ด๊ธฐํ(`test:reset`)๋ ํ
์คํธ์ฉ DB๋ฅผ ์ ๋ถ ๋น์ฐ๊ณ ๋ง์ด๊ทธ๋ ์ด์
์ ๋ค์ ์ ์ฉํ๋ฏ๋ก,
-> ์ค์ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋ DB์๋ ๋ฐ๋์ ๋ถ๋ฆฌ๋ ํ๊ฒฝ์์ ์คํํด์ผ ํฉ๋๋ค.
+> ์ด ๋ ์ด์ด ๊ตฌ์กฐ๋ **๋๋ฉ์ธ ํ์ฅยทํ
์คํธยท๋ณด์ยท๋ฐฐํฌ**์ ๋ชจ๋ ๊ณผ์ ์์ ์ผ๊ด๋ ํ๋ฆ์ ์ ์งํ๋ฉฐ,
+> ๋๊ท๋ชจ ์๋น์ค ๊ตฌ์กฐ๋ก ํ์ฅํด๋ ์์ ์ ์ผ๋ก ์ ์ง๋ ์ ์๋๋ก ์ค๊ณ๋์์ต๋๋ค.
-### 6) ๋น๋
+## ๐ ๋ณด์ ์ค๊ณ (Security Overview)
-```bash
-npm run build
-```
+์๋ฆฌ๋ธ๋ **Express 5 ๊ธฐ๋ฐ ๋ค์ธต ๋ณด์ ๊ตฌ์กฐ**๋ฅผ ์ ์ฉํด
+์
๋ ฅ ๊ฒ์ฆ๋ถํฐ ํ์ผ ์
๋ก๋, ์ธ์ฆ, ์๋ฒ ํค๋๊น์ง ์ ๋ฐ์ ์ธ ๋ณด์ ๋ฆฌ์คํฌ๋ฅผ ์ต์ํํ์ต๋๋ค.
-๋น๋๋ฅผ ์ํํ๋ฉด `dist/` ๋๋ ํฐ๋ฆฌ๊ฐ ์์ฑ๋๊ณ ,
-`tsc-alias`๋ก ๊ฒฝ๋ก alias๊ฐ ์๋ ๋ณํ๋ฉ๋๋ค.
+### ๐น HTTP ๋ณด์ ํค๋ & ์๋ฒ ๋ณดํธ
+- **helmet(CSP ์ ์ฉ)**: ์คํฌ๋ฆฝํธ/์ด๋ฏธ์ง/ํ๋ ์ ์ ์ฑ
๊ฐํ๋ก XSSยทClickjacking ๋ฐฉ์ง
+- **X-Powered-By ์ ๊ฑฐ**, **ETag ๋นํ์ฑํ**๋ก ํ๋ ์์ํฌ ๋
ธ์ถ ๋ฐ ์บ์ฑ ๊ธฐ๋ฐ ๊ณต๊ฒฉ ์ํ
+- **HPP(HTTP Parameter Pollution) ์ฐจ๋จ** ์ ์ฉ
-### 7) ํ๋ก๋์
์คํ
+### ๐น ์ธ์ฆ & ๊ถํ
+- **JWT Access / Refresh Token ๊ตฌ์กฐ**
+- Refresh Token์ **DB ์ํ ๊ด๋ฆฌ ๋ฐฉ์**์ผ๋ก ๊ตฌํํด ์ฌ๋ฐ๊ธ ์ ๊ธฐ์กด ํ ํฐ ๋ฌดํจํ
+- ๋ผ์ฐํฐ ๋จ์๋ก **์ญํ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด(Role-based Access Control)** ์ ์ฉ
-ํ๋ก ํธ๋ PM2๋ก ์คํํ๊ณ ,
-๋ฐฑ์๋๋ Docker ๊ธฐ๋ฐ์ผ๋ก ์ด์ํ๋ ํ๊ฒฝ๊ณผ PM2 ๊ธฐ๋ฐ ํ๊ฒฝ ๋ชจ๋ ์ง์ํ๋๋ก ์คํฌ๋ฆฝํธ๊ฐ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
+### ๐น CORS & Proxy ํ๊ฒฝ
+- Strict Origin ๊ธฐ๋ฐ CORS ํ์ดํธ๋ฆฌ์คํธ
+- `trust proxy` ์ค์ ์ผ๋ก Nginx ๋ค์์ IP/ํ๋กํ ์ฝ ์ ๋ณด ์ ํํ ํ์ฑ
-**PM2 ๊ธฐ๋ฐ ์คํ (๋ก์ปฌ / ์๋ฒ ๊ณต์ฉ)**
+### ๐น ์
๋ ฅ ๊ฒ์ฆ & Sanitization
+- **Zod ์คํค๋ง** ๊ธฐ๋ฐ Params/Query/Body ์ ํ์ ๊ฒ์ฆ
+- ์ฌ์ฉ์ ์
๋ ฅ๊ฐ์ **DOMPurify + jsdom** ๊ธฐ๋ฐ ์๋ฒ๋จ Sanitization์ผ๋ก XSS ์ฐจ๋จ
+- CSV ์
๋ก๋ ์ **์ ํํ ํค๋ยทํ์
๊ฒ์ฆ ๋ฐ ์ ์ฒด ํธ๋์ญ์
์ฒ๋ฆฌ**
-```bash
-npm start # (= start:be && start:fe)
-```
+### ๐น ํ์ผ ์
๋ก๋ ๋ณด์
+- ์ด๋ฏธ์ง ์
๋ก๋๋ ํ์ฅ์๊ฐ ์๋ **Magic Number(MIME) ๊ธฐ๋ฐ ๊ฒ์ฆ**
+- sharp๋ฅผ ์ด์ฉํ **๊ฐ์ ์ฌ์ธ์ฝ๋ฉ(์ฌ์์ถ)์ผ๋ก โํด๋ฆฌ๊ธ๋กฏ(polyglot) ํ์ผโ ์ฐจ๋จ**
+ - ์: JPEG/PNG๋ก ์์ฅํ ์คํฌ๋ฆฝํธยท์คํํ์ผ ํผํฉ ํ์ด๋ก๋ ๊ฐ์ ์ ๊ฑฐ
+- ์ฌ์ธ์ฝ๋ฉ ํ์๋ง Presigned URL ๊ธฐ๋ฐ S3 ์
๋ก๋ ํ์ฉ
+- ์์ฅ ํ์ผ(์: PNG ํค๋ + JS ํ์ด๋ก๋) ์ฆ์ ๊ฑฐ๋ถ
-**๋ฐฑ์๋๋ง ์คํ**
+### ๐น Rate Limiting & ์๋ฒ ๋ณดํธ
+- **express-rate-limit**: IP ๋จ์ ์์ฒญ ์ ํ ์ ์ฉ
+- JSON Body ์ฌ์ด์ฆ ์ ํ(2MB), URL-encoded ์ ํ
-```bash
-npm run start:be
-```
+### ๐น ์ค์๊ฐ ํต์ ๋ณดํธ
+- **SSE ์๋ฆผ**: JWT ๊ธฐ๋ฐ ์ธ์ฆ
+- **Socket.io ์ฑํ
**: Connection middleware๋ก ์ธ์ฆ ํ ์์ผ ํ๋ฆฝ
-**ํ๋ก ํธ๋ง ์คํ**
+### ๐น ์๋ฌ/๋ก๊น
+- Pino ๊ธฐ๋ฐ ๊ตฌ์กฐ์ ๋ก๊น
+- ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ๋ก ๋ด๋ถ ์คํ ์จ๊น & ์ผ๊ด๋ ์๋ฌ ์๋ต ํฌ๋งท ์ ์ง
-```bash
-npm run start:fe
-```
+> ์
๋ ฅ ๊ฒ์ฆ, Sanitization, ์ฌ์ธ์ฝ๋ฉ ๊ธฐ๋ฐ ํ์ผ ๋ณด์, JWT ์ธ์ฆ, CSP ๋ฑ
+> ์ฌ๋ฌ ๋ณด์ ๊ณ์ธต์ ์ ์ฉํ์ฌ ์์ ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๊ฐํํ์ต๋๋ค.
-**์ค์ง:**
+## ๐ข CI/CD & ๋ฐฐํฌ ๊ตฌ์กฐ
-```bash
-npm run stop
-```
+์๋ฆฌ๋ธ๋ **Docker ๊ธฐ๋ฐ ํ๋ก๋์
ํ๊ฒฝ + GitHub Actions ์๋ ๋ฐฐํฌ ํ์ดํ๋ผ์ธ**์ ๊ตฌ์ถํ์ฌ
+๊ฐ๋ฐ โ ๋น๋ โ ํ
์คํธ โ ๋ฐฐํฌ์ ์ ๊ณผ์ ์ ์๋ํํ์ต๋๋ค.
-> [!WARNING]
-> `npm run stop` ์คํฌ๋ฆฝํธ๋ `pm2 delete` ์คํจ ์์๋ ํ๋ก์ธ์ค๋ฅผ ๊ณ์ ์งํํ๋๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
-> PM2 ํ๋ก์ธ์ค๊ฐ ์๋์ผ๋ก ์ข
๋ฃ๋ ์ํ๋ผ๋ฉด ๊ฒฝ๊ณ ๊ฐ ์ถ๋ ฅ๋ ์ ์์ง๋ง, ์ด๋ ์ ์ ๋์์
๋๋ค.
+### ๐น CI/CD ํ๋ฆ
-### 8) Swagger ๋ฌธ์ ์์ฑ
+1. **GitHub Actions(CI)**
+ - PR/commit ์ ์๋์ผ๋ก ํ
์คํธ ์คํ(Jest)
+ - ํ์
์ฒดํฌ/ESLint ๊ฒ์ฌ ํตํด ํ์ง ์ ์ง
+ - ๋ชจ๋ ํ
์คํธ ํต๊ณผ ์ Docker ์ด๋ฏธ์ง ๋น๋
-Swagger๋ ์๋ ์์ฑ ์คํฌ๋ฆฝํธ(genSwagger.js) ๋ฅผ ํตํด /swagger/swagger.json์ผ๋ก ๋น๋๋ฉ๋๋ค.
+2. **GHCR(GitHub Container Registry)**
+ - ๋น๋๋ Docker ์ด๋ฏธ์ง๋ฅผ ํ๊ทธ ๋ฒ์ ์ผ๋ก ์
๋ก๋
+ - ์ด๋ฏธ์ง ๋ฒ์ ๊ด๋ฆฌ ๋ฐ ๋กค๋ฐฑ์ด ์ฉ์ด
-```bash
-npm run swagger:generate
-```
+3. **GitHub Actions(CD) โ EC2 ์๋ ๋ฐฐํฌ**
+ - SSH๋ฅผ ํตํด EC2์ ์ ์
+ - ์ต์ ์ด๋ฏธ์ง Pull
+ - `docker-compose.prod.yaml` ๊ธฐ๋ฐ์ผ๋ก ์ปจํ
์ด๋ ์ฌ๊ธฐ๋
+ - ํ๊ฒฝ ๋ณ์ ํ์ผ(.env.prod) ์๋ ์์ฑ/๋๊ธฐํ
-์ดํ /api/docs ๊ฒฝ๋ก์์ Swagger UI๋ก ํ์ธํ ์ ์์ต๋๋ค.
+4. **EC2(Docker Runtime)**
+ - Backend ์ปจํ
์ด๋ ๋จ์ผ ์คํ
+ - ๋ก๊ทธ ๊ด๋ฆฌ(Pino ํ์ผ ์ ์ฅ), ํฌ์ค์ฒดํฌ ํฌํจ
-> [!NOTE]
-> `swagger/swagger.json` ํ์ผ์ด ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ, ์๋ฒ๋ ๋ถ์ ํํ ๋ฌธ์ ์ํ๋ก์ ๋ฐฐํฌ๋ฅผ ๋ง๊ธฐ ์ํด ์ฆ์ ์ข
๋ฃ๋๋๋ก ์ค๊ณ๋์ด ์์ต๋๋ค.
-> ๋ฐฐํฌ ์ ์ ๋ฐ๋์ `npm run swagger:generate`๋ฅผ ์คํํด์ผ ํฉ๋๋ค.
+5. **Nginx Reverse Proxy**
+ - ์์ฒญ ๋ผ์ฐํ
: `FE โ / (Next.js)` / `BE โ /api`
+ - Cloudflare Origin ๋ณต์, gzip ์์ถ, CORS/๋ณด์ ํค๋ ์ผ๊ด ๊ด๋ฆฌ
+ - SSL(TLS) ์ข
๋จ ์ฒ๋ฆฌ
----
+### ๐น ๋ฐฐํฌ ๊ตฌ์กฐ ์์ฝ
-## ๐ ๋ณด์ ๊ธฐ๋ณธ ์ค์
+```mermaid
+flowchart LR
+ Dev[Developer Push] --> GA[GitHub Actions]
+ GA --> Test[Tests & Lint]
+ GA --> Build[Docker Build]
+ Build --> GHCR[Push to GHCR]
+ GHCR --> Deploy[GA Deploy Script]
+ Deploy --> EC2[(EC2)]
+ EC2 --> DC[Docker Compose Up]
+ DC --> Nginx[Nginx Reverse Proxy]
+ Nginx --> Backend[Backend Container]
+```
-WeLive ์๋ฒ๋ Express 5 ๊ธฐ๋ฐ์ผ๋ก, ๋ค์๊ณผ ๊ฐ์ ๋ค์ธต ๋ณด์ ์์๋ฅผ ์ ์ฉํ์ฌ ์์ ์ฑ๊ณผ ์์ ์ฑ์ ๊ฐํํ์ต๋๋ค.
+### ๐น ์ฃผ์ ํน์ง
-### 1) HTTP ๋ณด์ ํค๋
+- **์์ ์๋ ๋ฐฐํฌ**: Git push๋ง์ผ๋ก ํ
์คํธ โ ๋น๋ โ ๋ฐฐํฌ๊น์ง ์๋ ์ฒ๋ฆฌ
+- **ํ๊ฒฝ ๋ถ๋ฆฌ**: `.env`, `.env.test`, `.env.production` ๋ถ๋ฆฌ ๋ก๋
+- **์ ํ์ ์ฌ๋ฐฐํฌ**: API ๋ณ๊ฒฝ ์์ด ํน์ ๋ชจ๋๋ง ์ฌ๋ฐฐํฌ ๊ฐ๋ฅ
+- **์๋ฌ ๋ฐฉ์ง ์ฅ์น**: Swagger ๋ฏธ์์ฑ ์ ์๋ฒ ๊ธฐ๋ ์ฐจ๋จ, ํ๋ก์/ํฌํธ ์๋ ๊ฒ์ฆ
+- **์ด์ ์์ ์ฑ ๊ฐํ**: Docker ์ด๋ฏธ์ง ๊ธฐ๋ฐ์ผ๋ก ๋์ผํ ์คํ ํ๊ฒฝ ๋ณด์ฅ
-- **helmet** ์ ์ฉ
- - CSP(Content Security Policy) ์ปค์คํ
๊ตฌ์ฑ
- - `frame-ancestors: none`, `object-src: none` ๋ฑ ํด๋ฆญ์ฌํน ๋ฐ ์๋ณ์กฐ ๋ฐฉ์ง
- - ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง `img-src`์ `blob:` ํ์ฉ
+## ๐ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ (์์ฝ)
-### 2) X-Powered-By & ETag ์ ๊ฑฐ
+ํ๋ก์ ํธ๋ **Layered Architecture + ๋๋ฉ์ธ ๋ชจ๋ ๊ตฌ์กฐ**๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ,
+ํต์ฌ ์ฑ
์ ๋จ์๊ฐ ๋ช
ํํ๊ฒ ๋ถ๋ฆฌ๋์ด ์ ์ง๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ์ ๋์์ต๋๋ค.
-- `app.disable('x-powered-by')` ๋ก Express ๋
ธ์ถ ์ ๊ฑฐ
-- `app.set('etag', false)` ๋ก ETag ๊ธฐ๋ฐ ์บ์ฑ ๋ฌดํจํ (๋ณด์ ์ทจ์ฝ์ ์ํ)
+```
+welive/
+โโโ infra/ # Docker, nginx, ๋ฐฐํฌ ์ค์
+โ โโโ docker/
+โ โโโ nginx/
+โ
+โโโ prisma/ # Prisma ์คํค๋ง ๋ฐ ๋ง์ด๊ทธ๋ ์ด์
+โ โโโ schema.prisma
+โ โโโ migrations/
+โ
+โโโ src/
+โ โโโ core/ # ์ฑ ์ฝ์ด (envยทloggerยทmiddlewaresยทsseยทsocket ๋ฑ)
+โ โโโ modules/ # ๋๋ฉ์ธ ๋ชจ๋ (auth, residents, polls, notices ๋ฑ)
+โ โโโ jobs/ # Poll Scheduler
+โ โโโ @types/ # ๊ธ๋ก๋ฒ ํ์
ํ์ฅ
+โ โโโ index.ts # ์๋ฒ ์ํธ๋ฆฌํฌ์ธํธ
+โ
+โโโ tests/ # Core / Modules / Scheduler ํตํฉ ํ
์คํธ
+โ โโโ core/
+โ โโโ modules/
+โ โโโ jobs/
+โ
+โโโ swagger/ # ์๋ ์์ฑ๋ Swagger JSON
+โโโ scripts/ # Swagger ์์ฑ ๋ฑ ์คํฌ๋ฆฝํธ
+โโโ package.json
+```
-### 3) Rate Limiting (์์ฒญ ์ ํ)
+### ๐ ๊ตฌ์กฐ ํน์ง
+- **core**: ์ธ์ฆ/CORS/Sanitize/Error ํธ๋ค๋ฌ, SSE, Socket, Limiter ๋ฑ ๊ณตํต ์์คํ
๋ ์ด์ด
+- **modules**: Router โ Controller โ Service โ Repository โ Prisma ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅด๋ ๋๋ฉ์ธ ๋จ์ ๊ตฌํ
+- **jobs**: Poll ์๋ ์คํ/๋ง๋ฃ ์ค์ผ์ค๋ฌ
+- **tests**: ์ค์ DB ๊ธฐ๋ฐ ํตํฉ ํ
์คํธ (ํ
์คํธ์ฉ DB ์๋ ์ด๊ธฐํ)
-- **express-rate-limit** ์ ์ฉ
- - 15๋ถ ๊ธฐ์ค ์ต๋ 1000 ์์ฒญ
- - ํ์ค ํค๋ ์ ์ฉ(429 ๋ฉ์์ง ๊ฐ์ )
+## โ๏ธ ์คํ ๊ฐ์ด๋ (Run & Setup Guide)
-### 4) HTTP Parameter Pollution ๋ฐฉ์ง
+### 1) ํ๊ฒฝ ๋ณ์ ์ค์ (`.env`)
+`.env.example` ํ์ผ์ ์ฐธ๊ณ ํด ๊ฐ๋ฐ/ํ
์คํธ/์ด์ ํ๊ฒฝ์ ๋ง๊ฒ `.env`, `.env.test`, `.env.production`์ ๊ตฌ์ฑํฉ๋๋ค.
-- **hpp** ์ฌ์ฉ
- - ์ค๋ณต๋ ํ๋ผ๋ฏธํฐ๋ก ์ธํ HPP ๊ณต๊ฒฉ ์ฐจ๋จ
+```env
+# DATABASE
+DATABASE_URL=postgresql://:@localhost:5432/welive
-### 5) CORS ํ์ดํธ๋ฆฌ์คํธ ๊ธฐ๋ฐ ์ ํ
+# SERVER
+PORT=3001
+FE_PORT=3000
+CORS_ORIGIN=http://localhost:3000
-- ํ๊ฒฝ ๋ณ์ ๊ธฐ๋ฐ **์ ์ ์ค๋ฆฌ์ง ํ์ดํธ๋ฆฌ์คํธ ๊ตฌ์ฑ**
-- ์ธ์ฆ ์ฟ ํค ๋ฐ JWT ํค๋ ๋
ธ์ถ ์ ์ด
+# JWT / AUTH
+ACCESS_TOKEN_SECRET=your_access_token_secret
+REFRESH_TOKEN_SECRET=your_refresh_token_secret
+PASSWORD_PEPPER=your_pepper
-### 6) Proxy Trust ์ค์
+# AWS S3
+AWS_REGION=ap-northeast-2
+AWS_S3_BUCKET_NAME=your_bucket
+AWS_S3_BASE_URL=https://your_bucket.s3.ap-northeast-2.amazonaws.com
+```
-- `app.set('trust proxy', [...])`
- - Nginx ํ๋ก์ ๋ค์์ ์คํ๋ ๋ ์ ํํ IP/ํ๋กํ ์ฝ ์ ๋ณด ์ฌ์ฉ ๊ฐ๋ฅ
+### 2) ํจํค์ง ์ค์น
+```bash
+npm install
+```
+
+### 3) ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ (Prisma)
+๊ฐ๋ฐ/ํ
์คํธ ํ๊ฒฝ ๋ชจ๋ Prisma ์คํค๋ง ๊ธฐ๋ฐ์ผ๋ก ์ด๊ธฐํํฉ๋๋ค.
+
+```bash
+npm run prisma:migrate # ๋ง์ด๊ทธ๋ ์ด์
+ ์๋ ์ ์ฒด ์ํ
+npm run prisma:generate # Prisma Client ์์ฑ
+npm run prisma:reset # ๊ฐ๋ฐ DB ์ด๊ธฐํ
+npm run prisma:studio # DB UI (์น)
+```
+
+ํ
์คํธ ํ๊ฒฝ์์ DB ์ด๊ธฐํ:
+```bash
+npm run test:reset
+npm run test:migrate
+```
-### 7) ์์ฒญ Body ๋ณดํธ
+### 4) ๊ฐ๋ฐ ์๋ฒ ์คํ
+๋ฐฑ์๋/ํ๋ก ํธ๋ฅผ ๋์์ ๊ฐ๋ฐํ๊ธฐ ์ํ ์คํฌ๋ฆฝํธ ํฌํจ.
+```bash
+npm run dev # BE + FE ๋์ ์คํ
+npm run dev:be # ๋ฐฑ์๋ ๋จ๋
์คํ
+npm run dev:fe # ํ๋ก ํธ ๋จ๋
์คํ
+```
-- `express.json({ limit: '2mb' })` ๋ก ๋ณธ๋ฌธ ํฌ๊ธฐ ์ ํ
-- URL-encoded payload๋ ์ ํ์ ํ์ฉ
+### 5) ํ
์คํธ ์คํ
+ํ
์คํธ ํ๊ฒฝ(`.env.test`)์ด ์๋ ๋ก๋๋๋ฉฐ,
+ํ
์คํธ์ฉ DB๋ ๋งค ์คํ๋ง๋ค ์ด๊ธฐํ๋ฉ๋๋ค.
-### 8) ์ฟ ํค ์ฒ๋ฆฌ
+```bash
+npm test # ์ ์ฒด ํ
์คํธ
+npm run test:watch # ๋ณ๊ฒฝ ๊ฐ์ง
+npm run test:cov # ์ปค๋ฒ๋ฆฌ์ง
+```
-- **cookie-parser** ์ฌ์ฉ
- - ๋ก๊ทธ์ธยท์ธ์
ยท๋ณด์กฐ ๋ฐ์ดํฐ ํ์ฑ
- - (ํ์ํ ์ต์ ๋ฒ์๋ก ์ ํํ์ฌ ์ฌ์ฉ)
+### 6) ๋น๋
+```bash
+npm run build
+```
+๋น๋ ํ ๊ฒฐ๊ณผ๋ `dist/`์ ์์ฑ๋๋ฉฐ `tsc-alias`๋ก ๊ฒฝ๋ก alias๊ฐ ์๋ ๋ณํ๋ฉ๋๋ค.
-### 9) ์๋ต ์์ถ
+### 7) ํ๋ก๋์
์คํ
+- **๋ฐฑ์๋**: Docker ๊ธฐ๋ฐ (EC2)
+- **ํ๋ก ํธ์๋**: PM2 ๊ธฐ๋ฐ Next.js ์คํ
-- **compression(gzip)** ์ ์ฉ
- - ์๋ต ํฌ๊ธฐ ๊ฐ์ โ DDoS ํ๋ฉด ๊ฐ์
+```bash
+npm start # BE + FE
+npm run start:be # ๋ฐฑ์๋๋ง
+npm run start:fe # ํ๋ก ํธ๋ง
+```
-### 10) ์ ์ ํ์ผ ๋ณด์
+์ค์ง:
+```bash
+npm run stop
+```
-- MIME ๊ธฐ๋ฐ ํ์ผ ์ ํจ์ฑ ๊ฒ์ฌ
-- sharp ๊ธฐ๋ฐ ์ด๋ฏธ์ง ์ฒ๋ฆฌ๋ก ์
์ฑ ์ด๋ฏธ์งยทPayload ์จ๊ธฐ๊ธฐ ๋ฐฉ์ง
-- Magic Number ๊ธฐ๋ฐ MIME ๊ฒ์ฆ(`assertAllowedByMagic.ts`)
+ํ
์คํธ/์ด์ DB ํผ์ฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด
+`.env.test`์๋ ์ด์ DB ์ ๋ณด๋ฅผ ์ ๋ ์ ์ฅํ์ง ์๋๋ก ์ค๊ณ๋์ด ์์ต๋๋ค.
-### 11) ์
๋ ฅ ์ ํ(Input Sanitization)
+## โจ ์ฃผ์ ๊ตฌํ ํ์ด๋ผ์ดํธ (Key Implementations)
-- **DOMPurify(isomorphic-dompurify + jsdom)**
- - ๋๊ธ/๊ฒ์๊ธ์์ ์
์ฑ ์คํฌ๋ฆฝํธ ์ ๊ฑฐ (์๋ฒ๋จ Sanitization)
+### ๐น 1) ์ญํ ๊ธฐ๋ฐ ์ธ์ฆ/์ธ๊ฐ ๊ตฌ์กฐ (RBAC)
+- JWT Access/Refresh Token + DB ๊ธฐ๋ฐ Refresh ์ํ ๊ด๋ฆฌ
+- SUPER_ADMIN / ADMIN / USER 3๋จ ์ญํ ์ ๋ผ์ฐํฐ ๋จ์๋ก ์ ์ด
+- ์น์ธ ๋๊ธฐ/๊ฑฐ์ /์ถ๊ฐ ์ ๋ณด ํ์ ๋ฑ ์
์ฃผ๋ฏผ ์ํ ํ๋ฆ์ ์์ธํ๊ฒ ๋ชจ๋ธ๋ง
-### 12) ์ธ์ฆ ๋ณด์
+### ๐น 2) ์ค์๊ฐ ์๋ฆผ ์์คํ
(SSE)
+- ๋ฏผ์ยท๊ณต์งยทํฌํ ๋ฑ ์ฃผ์ ์ด๋ฒคํธ ๋ฐ์ ์ ์ฆ์ ์๋ฆผ ์ ์ก
+- SSE ์ ์ญ Emitter ๋ชจ๋ํ โ ์๋น์ค ๋ ์ด์ด์์ ๋จ์ผ API๋ก ์ฌ์ฉ
+- ํ๋ก ํธ๋ ์๋ฆผ Store์ ์ฐ๊ฒฐ๋์ด ์ค์๊ฐ UI ๋ฐ์
-- ๋ก๊ทธ์ธ/ํ์๊ฐ์
/ํ ํฐ ์ ์ก์ **JWT Access + Refresh Token ๊ตฌ์กฐ**
-- ๋น๋ฐ๋ฒํธ๋ **bcrypt + pepper** ์กฐํฉ์ผ๋ก ๊ฐํ ํด์ฑ
-- Refresh Token์ DB์์ ์ํ ๊ด๋ฆฌ
-- TokenUtils์์ ์๋ช
ยท๋ง๋ฃยท๊ฐฑ์ ๋ก์ง ํตํฉ ๊ด๋ฆฌ
+### ๐น 3) Poll ์ค์ผ์ค๋ฌ (์๋ ์คํ/๋ง๊ฐ)
+- Cron ๊ธฐ๋ฐ ์๋ ์ค์ผ์ค๋ฌ ๊ตฌํ
+- **Limiter(์ง์ ๊ตฌํ)** ๋ก ๋จ์ผ ์คํ ๋ณด์ฅ โ ์ค๋ณต ์ฒ๋ฆฌ/๊ฒฝ์ ์กฐ๊ฑด ์ ๊ฑฐ
+- ๋ง๋ฃ ์์ ์ ์๋ ์ง๊ณ + ์๋ฆผ๊น์ง ์ผ๊ด ์ฒ๋ฆฌ
-### 13) SSE & WebSocket ๋ณดํธ
+### ๐น 4) ํ์ผ ์
๋ก๋ ๋ณด์ ์ค๊ณ
+- ํ์ฅ์ ๊ธฐ๋ฐ์ด ์๋ **Magic Number(MIME)** ๊ฒ์ฆ
+- `sharp` ์ฌ์ธ์ฝ๋ฉ์ผ๋ก **ํด๋ฆฌ๊ธ๋กฏ(polyglot) ํ์ผ ๊ณต๊ฒฉ** ์ฐจ๋จ
+- Presigned URL ๋ฐฉ์์ผ๋ก S3 ์ง์ ์
๋ก๋
-- SSE ์ฐ๊ฒฐ ์ **JWT ๊ธฐ๋ฐ ํค๋ ์ธ์ฆ**
-- WebSocket(Socket.io)๋ ๋ฏธ๋ค์จ์ด๋ก JWT ์ธ์ฆ ์ ์ฉ
+### ๐น 5) HTML Sanitization & ์
๋ ฅ ๊ฒ์ฆ
+- ์๋ฒ๋จ DOMPurify + jsdom์ผ๋ก XSS ๋ฐฉ์ง
+- Zod ์คํค๋ง ๊ธฐ๋ฐ์ผ๋ก Params/Query/Body ํตํฉ ๊ฒ์ฆ
+- ๊ฒ์ฆ(Zod) โ Sanitize โ Controller ํ๋ฆ์ผ๋ก ์ ๊ณ์ธต ๋ณดํธ
-### 14) ์๋ฒ ์ค๋ฅ ๋
ธ์ถ ๋ฐฉ์ง
+### ๐น 6) Socket.io ๊ธฐ๋ฐ ์ค์๊ฐ ์ฑํ
+- JWT ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ก ์ฌ์ฉ์ ๊ฒ์ฆ
+- ๊ด๋ฆฌ์ โ ์
์ฃผ๋ฏผ ๊ฐ ๋น ๋ฅธ ์๋ ๊ตฌ์กฐ ํ๋ฆฝ
+- ๋ฉ์์ง ์ ์ฅ, ์ฝ์ ์ฒ๋ฆฌ, ์ฑํ
๋ฐฉ ๋จ์ ๊ด๋ฆฌ
-- ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ๋ก
- - ๋ด๋ถ ์คํํธ๋ ์ด์ค ์จ๊น
- - ์ผ์ ํ ์๋ฌ ์๋ต ํ์ ์ ์ง(ApiError)
+### ๐น 7) CSV ๊ธฐ๋ฐ ์
์ฃผ๋ฏผ ๋ช
๋ถ ์
๋ก๋
+- CSV ํค๋/ํ์
์ ํ ๊ฒ์ฆ
+- ๋๋ ์
๋ก๋ ์ ์ ์ฒด๋ฅผ ๋จ์ผ ํธ๋์ญ์
์ผ๋ก ์ฒ๋ฆฌ
+- ์๋ชป๋ ๋ฐ์ดํฐ๋ line ๋จ์๋ก ์ฆ์ ์ค๋ฅ ์ ๊ณต
-### 15) Swagger ๋ณด์
+### ๐น 8) ํ
์คํธ ์ฝ๋ & ์์ ์ฑ
+- ์ ์ฒด ๋๋ฉ์ธ ๋ชจ๋ ํตํฉ ํ
์คํธ ๊ตฌ์ฑ (Jest + Supertest)
+- ํ
์คํธ ํ๊ฒฝ ์ ์ฉ DB(`setupTestDB`) ์๋ ์ด๊ธฐํ
+- SchedulerยทSSE ์ด๋ฒคํธ๊น์ง ๋
๋ฆฝ์ ์ผ๋ก ๊ฒ์ฆ ๊ฐ๋ฅํ๋๋ก ๊ตฌ์กฐํ
-- Swagger ํ์ผ์ด ์กด์ฌํ์ง ์์ ๊ฒฝ์ฐ ์๋ฒ ์๋ ์ค๋จ
- โ ์๋ชป ๊ตฌ์ฑ๋ ๋ฌธ์/๋ฐฐํฌ ๋ฐฉ์ง
+### ๐น 9) ์์ ์ ์ธ ์ด์ ํ๊ฒฝ
+- Swagger ๋ฏธ์์ฑ ์ ์๋ฒ ๋ถํ
์ฐจ๋จ
+- healthcheck ๊ธฐ๋ฐ Docker ์ปจํ
์ด๋ ์ํ ํ์ธ
+- Nginx + Cloudflare Origin Restore + gzip ์์ถ ์กฐํฉ
----
+> ๋ณธ ํ๋ก์ ํธ๋ Layered Architecture, ๋๋ฉ์ธ ๊ธฐ๋ฐ ์ค๊ณ, ํ
์คํธยท๋ณด์ยท๋ฐฐํฌ ์๋ํ๋ฅผ ๋ชจ๋ ๊ฐ์ถ
+> ์ค์ ์ด์ ์์ค์ ๋ฐฑ์๋ ์ํคํ
์ฒ๋ฅผ ๋ชฉํ๋ก ๊ตฌํ๋์์ต๋๋ค.
+>
+> ๋ํ ๋ชจ๋ ์ฑ
์ ๋ถ๋ฆฌ, ์คํ๋ฆฐํธ ๋จ์ ์ผ์ ๊ด๋ฆฌ, GitHub PR ๊ธฐ๋ฐ ํ์
์ผ๋ก
+> ์ค์ ํ ๊ฐ๋ฐ ํ๋ฆ์ ๋ง์ถ ์ด์ ํ๋ก์ธ์ค๋ฅผ ์ ์งํ์ต๋๋ค.
-## โ
์งํ ๊ฒฐ๊ณผ ํ์ด๋ผ์ดํธ
+## ๐ ๋ถ๋ก & ์ฐ์ถ๋ฌผ (Official Project Documents)
-- **์ธํ๋ผ/๋ฐฐํฌ**
- - Docker ๊ธฐ๋ฐ ๋ฐฑ์๋ ์ปจํ
์ด๋ ๊ตฌ์ถ ๋ฐ Nginx Reverse Proxy ๊ตฌ์ฑ
- - GitHub Actions ๊ธฐ๋ฐ CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ (ํ
์คํธ โ ๋น๋ โ GHCR Push โ EC2 ์๋ ๋ฐฐํฌ)
- - RDS/EC2 ํ๊ฒฝ์์ ์ต์ข
ํ๋ก๋์
๋ฐฐํฌ ์๋ฃ ๋ฐ ์์ ์ ์ด์ ์ค
+์๋ ๋ฌธ์๋ ํ๋ก์ ํธ์ ์ ๊ณผ์ ์ ์ ๋ฆฌํ **๊ตฌ๊ธ ๋
์ค ๊ธฐ๋ฐ ๊ณต์ ์ฐ์ถ๋ฌผ**์
๋๋ค.
-- **๋ฐฑ์๋ ์ํคํ
์ฒ**
- - ์ ์ฒด Prisma ์คํค๋ง ์ํคํ
์ฒ ์ค๊ณ ๋ฐ Core ๋ ์ด์ด ๊ตฌ์ถ(env/logger/middlewares/router)
- - Express 5 ๊ธฐ๋ฐ ์๋ฒ(app.ts) ์ค๊ณ: CSP/๋ณด์ ํค๋, rate-limit, HPP, CORS, Proxy ์ ๋ขฐ, gzip ์์ถ ๋ฑ ์ ์ฒด ๋ณด์ ํ์ดํ๋ผ์ธ ๊ตฌ์ฑ
- - SSE ๊ธฐ๋ฐ ์ค์๊ฐ ์๋ฆผ ์์คํ
๋ฐ Socket.io ์ค์๊ฐ ์ฑํ
๋ชจ๋ ๊ตฌ์ถ
- - Poll ์๋ ํ์ฑํ/๋ง๋ฃ ์ค์ผ์ค๋ฌ ์ค๊ณ(Limiter ๊ธฐ๋ฐ ๋์์ฑ ์ ์ด ํฌํจ)
+- **1. ์๊ตฌ์ฌํญ ๋ถ์**
+ https://docs.google.com/document/d/11Kl92rqan2iA_5K8Q_3UCQXxkDiuzfVb10B0d-X-JTQ/edit?usp=sharing
-- **๋ฐฑ์๋ ์ฃผ์ ๊ธฐ๋ฅ**
- - ์ฌ์ฉ์ ํ๋กํ/์ด๋ฏธ์ง ์
๋ก๋(S3 Presigned URL), ์ ์ ์ ๋ณด ์์ ๊ธฐ๋ฅ
- - ๊ณต์ง/๋ฏผ์/๋๊ธ/์
์ฃผ๋ฏผ/์ํํธ/ํฌํ(Polls) CRUD ์ ์ฒด ๊ธฐ๋ฅ ์์ฑ
- - Notifications REST + SSE ์๋ฆผ ํตํฉ ์์คํ
๊ตฌํ
- - ์ด๋ฒคํธ(Calendar) ๋ชจ๋ ๊ตฌ์ถ ๋ฐ ๊ด๋ฆฌ์/์ ์ ๊ถํ ํ๋ฆ ์ ๋ฆฝ
+- **2. ์์คํ
/DB ์ค๊ณ**
+ https://docs.google.com/document/d/1cymp9U-yK94KXXUE7jiBzh5EP23w9luxXiKN50FrFGA/edit?usp=sharing
-- **ํ
์คํธ/ํ์ง**
- - Core, Modules, Jobs ์ ์ฒด์ ๊ฑธ์น ํตํฉ ํ
์คํธ ์์ฑ(Jest + Supertest)
- - SSE, Scheduler, Prisma ํธ๋์ญ์
/์บ์ค์ผ์ด๋ ํ
์คํธ ํฌํจ
- - ESLint/Prettier/tsconfig ๊ตฌ์ฑ ์ ๋น ๋ฐ ์ ์ฒด ์ฝ๋ ํ์ง ์์ ํ
+- **3. ๊ตฌํ ๋ฌธ์**
+ https://docs.google.com/document/d/1hgKMj29_KXsSXFceEgQGOPSlGxUAqYg32ujF5OWZd2U/edit?usp=sharing
-- **์์ฐ/์ด์**
- - Swagger ์๋ ์์ฑ ๋ฐ API ๋ฌธ์ํ ์๋น
- - ๊ฐ๋ฐยทํ
์คํธยทํ๋ก๋์
ํ๊ฒฝ ๋ถ๋ฆฌ(.env/.env.test/.env.production)
- - ํ๋ก ํธ์๋ ์ฐ๋ ํ
์คํธ ์๋ฃ ๋ฐ ์ต์ข
๋ฐฐํฌ ์ฑ๊ณต
+- **4. ํ
์คํธ ๋ฌธ์**
+ https://docs.google.com/document/d/1Dnkksd0Oa1b5dHiuudu5tm-BDBxiOFkTnExfLzwxJVw/edit?usp=sharing
----
+- **5. ๋ฐฐํฌ ๋ฐ ์ ์ง๋ณด์ ๋ฌธ์**
+ https://docs.google.com/document/d/1Pq4H3DNUBoLCKIp0Jdt3oD8kg3pb66YLEaR4uXr8KJg/edit?usp=sharing
-## ๐ ๋งํฌ
-
-- ํ ํ์
๋ฌธ์(Notion): ๊ฐฑ์ ์์
-- ์ต์ข
๋ฐํ ์๋ฃ: ๊ฐฑ์ ์์
-- ์์ฐ ์์: ๊ฐฑ์ ์์
-- ์ ์ฅ์: https://github.com/codeit-welive/welive
-- ์ฐ์ถ๋ฌผ:
- - 1. ์๊ตฌ์ฌํญ ๋ถ์: [๋งํฌ](https://docs.google.com/document/d/11Kl92rqan2iA_5K8Q_3UCQXxkDiuzfVb10B0d-X-JTQ/edit?usp=sharing)
- - 2. ์ค๊ณ: [๋งํฌ](https://docs.google.com/document/d/1cymp9U-yK94KXXUE7jiBzh5EP23w9luxXiKN50FrFGA/edit?usp=sharing)
- - 3. ๊ตฌํ: [๋งํฌ](https://docs.google.com/document/d/1hgKMj29_KXsSXFceEgQGOPSlGxUAqYg32ujF5OWZd2U/edit?usp=sharing)
- - 4. ํ
์คํธ: [๋งํฌ](https://docs.google.com/document/d/1Dnkksd0Oa1b5dHiuudu5tm-BDBxiOFkTnExfLzwxJVw/edit?usp=sharing)
- - 5. ๋ฐฐํฌ ๋ฐ ์ ์ง๋ณด์: [๋งํฌ](https://docs.google.com/document/d/1Pq4H3DNUBoLCKIp0Jdt3oD8kg3pb66YLEaR4uXr8KJg/edit?usp=sharing)
+> [!NOTE]
+> ์๋จ์ โ๐ ๋ฐ๋ชจ & ์ฃผ์ ๋งํฌโ๋ ์คํ ๊ฐ๋ฅํ ์๋น์ค/์ฝ๋ ์ ๊ทผ์ฉ,
+> ๋ณธ ์น์
์ ํ๋ก์ ํธ ์งํ ์ ์ฒด๋ฅผ ๋ค๋ฃฌ **์ ์ ๋ฌธ์ ์ฐ์ถ๋ฌผ ๋ชจ์**์
๋๋ค.
diff --git a/docs/assets/erd.png b/docs/assets/erd.png
new file mode 100644
index 0000000..245fcb3
Binary files /dev/null and b/docs/assets/erd.png differ
diff --git a/docs/assets/logo.png b/docs/assets/logo.png
new file mode 100644
index 0000000..cc004b0
Binary files /dev/null and b/docs/assets/logo.png differ
diff --git a/package.json b/package.json
index 5b3c7a2..1651fdf 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,8 @@
"build": "npm run clean && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
"_section4": "Test",
"test": "cross-env NODE_ICU_DATA=node_modules/full-icu NODE_ENCODING=utf8 NODE_ENV=test dotenv -e .env.test -- jest --runInBand",
+ "test:watch": "cross-env NODE_ICU_DATA=node_modules/full-icu NODE_ENCODING=utf8 NODE_ENV=test jest --watch",
+ "test:cov": "cross-env NODE_ICU_DATA=node_modules/full-icu NODE_ENCODING=utf8 NODE_ENV=test jest --coverage",
"test:migrate": "ts-node -r tsconfig-paths/register scripts/test/migrateTestDB.ts",
"test:reset": "ts-node -r tsconfig-paths/register scripts/test/resetTestDB.ts",
"_section5": "Development",
diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts
index c3bc3d6..546efed 100644
--- a/src/modules/users/users.service.ts
+++ b/src/modules/users/users.service.ts
@@ -1,5 +1,6 @@
import { hashPassword, isPasswordValid } from '#helpers/passwordUtils';
import ApiError from '#errors/ApiError';
+import env from '#core/env';
import { findUserById, updateUser } from './users.repo';
import { USER_MESSAGES as MSG } from '#constants/user.constants';
import { assertAllowedByMagic } from '#core/files/assertAllowedByMagic';
@@ -53,7 +54,7 @@ export const updateUserService = async (userId: string, { body, file }: UpdateUs
const updatedUser = await updateUser(userId, updates);
// ๊ธฐ์กด ์๋ฐํ ์ญ์
- if (updates.avatar && user.avatar) deleteImageFromS3(user.avatar); // fire-and-forget
+ if (updates.avatar && user.avatar !== env.DEFAULT_AVATAR_URL) deleteImageFromS3(user.avatar); // fire-and-forget
return {
message: `${updatedUser.name}${MSG.UPDATE_SUCCESS}`,