diff --git a/README.md b/README.md index 8b9b615..bd13aca 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,8 @@ # ๐ŸŒณ Welive -[![Node.js](https://img.shields.io/badge/Node.js-20.x-339933?logo=node.js&logoColor=white)]() -[![Express](https://img.shields.io/badge/Express-5.1-000000?logo=express&logoColor=white)]() -[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178C6?logo=typescript&logoColor=white)]() -[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-17-4169E1?logo=postgresql&logoColor=white)]() -[![Prisma](https://img.shields.io/badge/Prisma-6.18-2D3748?logo=prisma&logoColor=white)]() -[![Docker](https://img.shields.io/badge/Docker-Build%20%26%20Deploy-2496ED?logo=docker&logoColor=white)]() -[![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-CI%2FCD-2088FF?logo=githubactions&logoColor=white)]() -[![AWS](https://img.shields.io/badge/AWS-EC2%20%7C%20S3-F8991C?logo=amazon-aws&logoColor=white)]() +

+ Welive Logo +

## ๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ @@ -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 +[![Node.js](https://img.shields.io/badge/Node.js-20.x-339933?logo=node.js&logoColor=white)]() +[![Express](https://img.shields.io/badge/Express-5.x-000000?logo=express&logoColor=white)]() +[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178C6?logo=typescript&logoColor=white)]() +[![Prisma](https://img.shields.io/badge/Prisma-ORM-2D3748?logo=prisma&logoColor=white)]() +[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-17-4169E1?logo=postgresql&logoColor=white)]() -> [!WARNING] -> CSV ์—…๋กœ๋“œ ์‹œ **ํ—ค๋” ๋ช…์นญ์ด ์ •ํ™•ํžˆ ์ผ์น˜**ํ•ด์•ผ ํ•˜๋ฉฐ, ์ธ์ฝ”๋”ฉ์€ **UTF-8**๋งŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค. -> ์ž˜๋ชป๋œ ์นผ๋Ÿผ ๋˜๋Š” ๋ˆ„๋ฝ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ํŒŒ์ผ์€ ์ „์ฒด ์—…๋กœ๋“œ๊ฐ€ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. +### Infra / DevOps +[![Docker](https://img.shields.io/badge/Docker-Containerization-2496ED?logo=docker&logoColor=white)]() +[![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-CI/CD-2088FF?logo=githubactions&logoColor=white)]() +[![AWS EC2](https://img.shields.io/badge/AWS-EC2-F8991C?logo=amazonaws&logoColor=white)]() +[![AWS S3](https://img.shields.io/badge/AWS-S3-569A31?logo=amazonaws&logoColor=white)]() +[![Nginx](https://img.shields.io/badge/Nginx-Reverse_Proxy-009639?logo=nginx&logoColor=white)]() -- ๋ฏผ์›(๋ฌธ์˜) ๊ด€๋ฆฌ - - ์ƒํƒœ(์ฒ˜๋ฆฌ ์ „ยท์ค‘ยท์™„๋ฃŒ) ๋ณ€๊ฒฝ, ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ, ๋น„๊ณต๊ฐœ ์ฒ˜๋ฆฌ, ์ˆ˜์ •ยท์‚ญ์ œ ์ œํ•œ ๋กœ์ง ํฌํ•จ -- ์ฃผ๋ฏผ ํˆฌํ‘œ ์‹œ์Šคํ…œ - - ํˆฌํ‘œ๊ถŒ์ž ๋ฒ”์œ„ ์„ค์ •, ์ผ์ • ๊ธฐ๋ฐ˜ ์ž๋™ ์˜คํ”ˆ/๋งˆ๊ฐ, ๊ฒฐ๊ณผ ์ž๋™ ๊ณต์ง€ ๋“ฑ๋ก, ์‹ค์‹œ๊ฐ„ ์ฐธ์—ฌ ๊ฐ€๋Šฅ +### Testing & Quality +[![Jest](https://img.shields.io/badge/Jest-Testing-C21325?logo=jest&logoColor=white)]() +[![Supertest](https://img.shields.io/badge/Supertest-HTTP_Testing-75AADB)]() +[![ESLint](https://img.shields.io/badge/ESLint-Linting-4B32C3?logo=eslint&logoColor=white)]() +[![Prettier](https://img.shields.io/badge/Prettier-Formatting-F7B93E?logo=prettier&logoColor=black)]() -- ๊ณต์ง€์‚ฌํ•ญ ๋ฐ ์ผ์ • ๊ด€๋ฆฌ - - ์นดํ…Œ๊ณ ๋ฆฌ, ์ค‘์š”๋„, ๋Œ“๊ธ€, ์กฐํšŒ์ˆ˜, ์ผ์ • ์ž๋™ ๋ฐ˜์˜ ๋“ฑ ๊ด€๋ฆฌ์ž ์ค‘์‹ฌ์˜ ๊ณต์ง€ ์šด์˜ ๊ธฐ๋Šฅ -- ๋Œ“๊ธ€ ์‹œ์Šคํ…œ - - ๊ณต์ง€/๋ฏผ์› ๊ธฐ๋ฐ˜ ๋Œ“๊ธ€ ๋“ฑ๋กยท์ˆ˜์ •ยท์‚ญ์ œ ๊ธฐ๋Šฅ ์ œ๊ณต -- ์•Œ๋ฆผ ์‹œ์Šคํ…œ (SSE) - - ๋ฏผ์›ยท๊ณต์ง€ยทํˆฌํ‘œ ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์ „์†ก -- ํŒŒ์ผ ์—…๋กœ๋“œ (ํ”„๋กœํ•„ ์‚ฌ์ง„) - - S3 ์—…๋กœ๋“œ ๊ธฐ๋ฐ˜์˜ ์•ˆ์ •์ ์ธ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ +### Frontend (ํ˜‘์—… / ์ฐธ๊ณ ) +[![Next.js](https://img.shields.io/badge/Next.js-Frontend-000000?logo=nextdotjs&logoColor=white)]() -> [!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) ํŒจํ‚ค์ง€ ์„ค์น˜ +

+ Welive ERD +

-```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}`,