diff --git a/README.md b/README.md new file mode 100644 index 0000000..6713ff3 --- /dev/null +++ b/README.md @@ -0,0 +1,747 @@ +
+ +# ๐Ÿ  PinHouse + +**๊ณต๊ณต ์ž„๋Œ€์ฃผํƒ ์ •๋ณด ๋ฐ ์ฒญ์•ฝ ์ง„๋‹จ ํ”Œ๋žซํผ** + +[![Java](https://img.shields.io/badge/Java-21-ED8B00?style=flat&logo=openjdk&logoColor=white)](https://openjdk.org/) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4.3-6DB33F?style=flat&logo=spring-boot)](https://spring.io/projects/spring-boot) +[![Gradle](https://img.shields.io/badge/Gradle-8.x-02303A?style=flat&logo=gradle)](https://gradle.org/) +[![MongoDB](https://img.shields.io/badge/MongoDB-47A248?style=flat&logo=mongodb&logoColor=white)](https://www.mongodb.com/) +[![MySQL](https://img.shields.io/badge/MySQL-8.0-4479A1?style=flat&logo=mysql&logoColor=white)](https://www.mysql.com/) +[![Redis](https://img.shields.io/badge/Redis-7.2.5-DC382D?style=flat&logo=redis&logoColor=white)](https://redis.io/) + +[๊ธฐ์ˆ  ์Šคํƒ](#-๊ธฐ์ˆ -์Šคํƒ) โ€ข [์•„ํ‚คํ…์ฒ˜](#-์•„ํ‚คํ…์ฒ˜) โ€ข [์ฃผ์š” ๊ตฌํ˜„ ์‚ฌํ•ญ](#-์ฃผ์š”-๊ตฌํ˜„-์‚ฌํ•ญ) + +
+ +--- + +## ๐Ÿ“‹ ๋ชฉ์ฐจ + +- [ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ](#-ํ”„๋กœ์ ํŠธ-์†Œ๊ฐœ) +- [์ฃผ์š” ๊ธฐ๋Šฅ](#-์ฃผ์š”-๊ธฐ๋Šฅ) +- [๊ธฐ์ˆ  ์Šคํƒ](#-๊ธฐ์ˆ -์Šคํƒ) +- [์•„ํ‚คํ…์ฒ˜](#-์•„ํ‚คํ…์ฒ˜) +- [๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„](#-๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค-์„ค๊ณ„) +- [ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ](#-ํ”„๋กœ์ ํŠธ-๊ตฌ์กฐ) +- [์ฃผ์š” ๊ตฌํ˜„ ์‚ฌํ•ญ](#-์ฃผ์š”-๊ตฌํ˜„-์‚ฌํ•ญ) +- [์„ฑ๋Šฅ ์ตœ์ ํ™”](#-์„ฑ๋Šฅ-์ตœ์ ํ™”) +- [๋ณด์•ˆ](#-๋ณด์•ˆ) +- [CI/CD](#-cicd) + +--- + +## ๐ŸŽฏ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ + +**PinHouse**๋Š” ๊ณต๊ณต ์ž„๋Œ€์ฃผํƒ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์‚ฌ์šฉ์ž์˜ ์ฒญ์•ฝ ์ž๊ฒฉ์„ ์ง„๋‹จํ•˜๋Š” ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. +๋ณต์žกํ•œ ์ฒญ์•ฝ ์ •๋ณด๋ฅผ ์ง๊ด€์ ์œผ๋กœ ์ œ๊ณตํ•˜๋ฉฐ, ์œ„์น˜ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ๋งž์ถคํ˜• ์ฃผํƒ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +### ๐ŸŒŸ ํ•ต์‹ฌ ๊ฐ€์น˜ + +- **์ •๋ณด ํˆฌ๋ช…์„ฑ**: ๊ณต๊ณต ์ž„๋Œ€์ฃผํƒ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ํˆฌ๋ช…ํ•˜๊ฒŒ ์ œ๊ณต +- **๋งž์ถคํ˜• ์ง„๋‹จ**: ์‚ฌ์šฉ์ž ์ƒํ™ฉ์— ๋งž๋Š” ์ฒญ์•ฝ ์ž๊ฒฉ ์ž๋™ ์ง„๋‹จ +- **์œ„์น˜ ๊ธฐ๋ฐ˜ ์ถ”์ฒœ**: ์ง์žฅ/ํ•™๊ต ๋“ฑ ๊ด€์‹ฌ ์œ„์น˜ ๊ธฐ๋ฐ˜ ์ตœ์  ์ฃผํƒ ์ถ”์ฒœ +- **์‚ฌ์šฉ์ž ๊ฒฝํ—˜**: ๋ณต์žกํ•œ ์ •๋ณด๋ฅผ ์ง๊ด€์ ์ด๊ณ  ํŽธ๋ฆฌํ•˜๊ฒŒ ์ œ๊ณต + +--- + +## โœจ ์ฃผ์š” ๊ธฐ๋Šฅ + +### ๐Ÿ” ์ธ์ฆ ๋ฐ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ +- **์†Œ์…œ ๋กœ๊ทธ์ธ**: Kakao, Naver OAuth2 ์ธ์ฆ +- **JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ**: Access/Refresh Token์„ ํ†ตํ•œ ๋ณด์•ˆ ๊ฐ•ํ™” +- **๋งˆ์ดํŽ˜์ด์ง€**: ํ”„๋กœํ•„ ๊ด€๋ฆฌ, ์ข‹์•„์š” ๋ชฉ๋ก, ์ง„๋‹จ ์ด๋ ฅ ์กฐํšŒ + +### ๐Ÿ˜๏ธ ์ฃผํƒ ์ •๋ณด ๊ด€๋ฆฌ +- **๋‹จ์ง€ ์ •๋ณด**: ์ž„๋Œ€์ฃผํƒ ๋‹จ์ง€์˜ ์ƒ์„ธ ์ •๋ณด ์ œ๊ณต + - ๋‚œ๋ฐฉ๋ฐฉ์‹, ์ด์„ธ๋Œ€์ˆ˜, ๊ณต๊ธ‰ํ˜ธ์ˆ˜ + - ๋ฐฉ ํƒ€์ž…๋ณ„ ์ •๋ณด (๋ฉด์ , ์ž„๋Œ€๋ฃŒ, ๊ด€๋ฆฌ๋น„) + - ์ฃผ๋ณ€ ์ธํ”„๋ผ ์ •๋ณด (์นดํŽ˜, ํŽธ์˜์ , ๋ณ‘์›, ํ•™๊ต ๋“ฑ) + +- **๊ณต๊ณ  ๊ฒ€์ƒ‰**: ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ๋ง ๋ฐ ์ •๋ ฌ ์˜ต์…˜ + - ์ง€์—ญ, ๊ณต๊ธ‰ ์œ ํ˜•, ๊ฐ€๊ฒฉ ๋ฒ”์œ„, ๋ฉด์ ๋ณ„ ๊ฒ€์ƒ‰ + - ์ปค์„œ ๊ธฐ๋ฐ˜ ๋ฌดํ•œ ์Šคํฌ๋กค ํŽ˜์ด์ง€๋„ค์ด์…˜ + - ์ธ๊ธฐ์ˆœ, ์ตœ์‹ ์ˆœ ์ •๋ ฌ + +- **๋ฐฉ ๋น„๊ต ๊ธฐ๋Šฅ**: ์—ฌ๋Ÿฌ ๋ฐฉ ํƒ€์ž…์„ ํ•œ๋ˆˆ์— ๋น„๊ต + +### ๐Ÿ“ ์œ„์น˜ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค +- **ํ•€ํฌ์ธํŠธ**: ์‚ฌ์šฉ์ž์˜ ๊ด€์‹ฌ ์œ„์น˜ ๋“ฑ๋ก (์ง์žฅ, ํ•™๊ต, ๋ถ€๋ชจ ์ง‘ ๋“ฑ) +- **๊ฑฐ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ**: Odsay API๋ฅผ ํ™œ์šฉํ•œ ๋Œ€์ค‘๊ตํ†ต ๊ฒฝ๋กœ ๊ณ„์‚ฐ + - ์ด ์†Œ์š” ์‹œ๊ฐ„, ๊ฑฐ๋ฆฌ, ์š”๊ธˆ ์ •๋ณด + - ๊ตฌ๊ฐ„๋ณ„ ์ƒ์„ธ ๊ฒฝ๋กœ (์Šน์ฐจ/ํ•˜์ฐจ ์ •๋ณด, ๋…ธ์„  ์ƒ‰์ƒ) + - ์ตœ๋Œ€ 3๊ฐœ ๊ฒฝ๋กœ ์ œ๊ณต + +### ๐ŸŽ“ ์ฒญ์•ฝ ์ง„๋‹จ ์‹œ์Šคํ…œ +- **์ž๊ฒฉ ์ง„๋‹จ**: ์‚ฌ์šฉ์ž์˜ ์†Œ๋“, ์ž์‚ฐ, ๊ฐ€์กฑ ๊ตฌ์„ฑ์› ์ •๋ณด ๊ธฐ๋ฐ˜ ์ฒญ์•ฝ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํŒ๋‹จ +- **๊ทœ์น™ ์—”์ง„**: ๊ณต๊ธ‰ ์œ ํ˜•๋ณ„ ๋ณต์žกํ•œ ์ฒญ์•ฝ ์ž๊ฒฉ ๊ทœ์น™ ์ž๋™ ์ ์šฉ + - ์‹ ํ˜ผ๋ถ€๋ถ€ ํŠน๋ณ„๊ณต๊ธ‰ + - ๋‹ค์ž๋…€ ๊ฐ€๊ตฌ ํŠน๋ณ„๊ณต๊ธ‰ + - ์ƒ์• ์ตœ์ดˆ ํŠน๋ณ„๊ณต๊ธ‰ + - ์ฒญ๋…„ ํŠน๋ณ„๊ณต๊ธ‰ +- **์ง„๋‹จ ์ด๋ ฅ ๊ด€๋ฆฌ**: ๊ณผ๊ฑฐ ์ง„๋‹จ ๊ฒฐ๊ณผ ์ €์žฅ ๋ฐ ์กฐํšŒ + +### ๐Ÿ” ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ๋ง +- **๋น ๋ฅธ ๊ฒ€์ƒ‰**: ์ง€์—ญ, ๋ฐฉ ํƒ€์ž…, ์ž„๋Œ€๋ฃŒ ๋ฒ”์œ„๋กœ ๊ฐ„ํŽธ ๊ฒ€์ƒ‰ +- **๊ณ ๊ธ‰ ๊ฒ€์ƒ‰**: ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰ ๋ฐ ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ ์กฐํ•ฉ +- **์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด**: ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์ œ๊ณต +- **๊ฒ€์ƒ‰ ๊ธฐ๋ก**: MongoDB ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž๋ณ„ ๊ฒ€์ƒ‰ ์ด๋ ฅ ์ถ”์  + +### โค๏ธ ์ข‹์•„์š” ๋ฐ ๋ถ๋งˆํฌ +- ๊ณต๊ณ , ๋‹จ์ง€, ๋ฐฉ ํƒ€์ž…์— ๋Œ€ํ•œ ์ข‹์•„์š” ๊ธฐ๋Šฅ +- ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ณ„ ์ข‹์•„์š” ๋ชฉ๋ก ๊ด€๋ฆฌ + +--- + +## ๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ + +### Backend +- **Language**: Java 21 +- **Framework**: Spring Boot 3.4.3 +- **Build Tool**: Gradle 8.x +- **Architecture**: Clean Architecture (4-Layer) + +### Database +- **MySQL 8.0**: ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ (์‚ฌ์šฉ์ž, ๊ณต๊ณ , ๋‹จ์ง€, ์ง„๋‹จ ๊ฒฐ๊ณผ) +- **MongoDB**: ๋ฌธ์„œ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ (ํ•€ํฌ์ธํŠธ, ๊ฒ€์ƒ‰ ์ด๋ ฅ, ์ฃผ๋ณ€์‹œ์„ค) +- **Redis 7.2.5**: ์„ธ์…˜ ๊ด€๋ฆฌ (JWT Refresh Token, ์ž„์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด) + +### Security & Auth +- **Spring Security**: ๋ณด์•ˆ ํ”„๋ ˆ์ž„์›Œํฌ +- **OAuth2 Client**: ์†Œ์…œ ๋กœ๊ทธ์ธ (Kakao, Naver) +- **JWT**: `io.jsonwebtoken:jjwt-api:0.11.5` + +### Infrastructure +- **Docker & Docker Compose**: ์ปจํ…Œ์ด๋„ˆํ™” +- **Nginx**: ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ +- **Certbot**: SSL/TLS ์ธ์ฆ์„œ ๊ด€๋ฆฌ + +### DevOps +- **GitHub Actions**: CI/CD ์ž๋™ํ™” +- **JUnit 5**: ๋‹จ์œ„ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ + +### External APIs +- **Odsay API**: ๋Œ€์ค‘๊ตํ†ต ๊ฒฝ๋กœ ๋ฐ ์‹œ๊ฐ„ ๊ณ„์‚ฐ +- **Kakao Local API**: ์ฃผ์†Œ-์ขŒํ‘œ ๋ณ€ํ™˜ +- **OAuth2 Providers**: Kakao, Naver + +### Documentation & Monitoring +- **Swagger/OpenAPI**: `springdoc-openapi-starter-webmvc-ui:2.8.6` +- **Spring Actuator**: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ชจ๋‹ˆํ„ฐ๋ง + +--- + +## ๐Ÿ— ์•„ํ‚คํ…์ฒ˜ + +### ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜ + +```mermaid +graph TB + subgraph "Client Layer" + Client[Client Application] + end + + subgraph "API Gateway" + Nginx[Nginx Reverse Proxy] + end + + subgraph "Application Server" + Spring[Spring Boot 3.4.3] + + subgraph "Clean Architecture Layers" + Presentation[Presentation Layer
REST Controllers] + Application[Application Layer
UseCase + Services] + Domain[Domain Layer
Entities + Repositories] + Infrastructure[Infrastructure Layer
DB + External APIs] + end + end + + subgraph "Data Layer" + MySQL[(MySQL 8.0
Relational Data)] + MongoDB[(MongoDB
Document Data)] + Redis[(Redis 7.2.5
Cache & Session)] + end + + subgraph "External Services" + Odsay[Odsay API] + KakaoLocal[Kakao Local API] + OAuth2[OAuth2 Providers
Kakao, Naver] + end + + Client -->|HTTPS| Nginx + Nginx -->|HTTP| Spring + Spring --> Presentation + Presentation --> Application + Application --> Domain + Domain --> Infrastructure + Infrastructure --> MySQL + Infrastructure --> MongoDB + Infrastructure --> Redis + Infrastructure --> Odsay + Infrastructure --> KakaoLocal + Infrastructure --> OAuth2 + + style Spring fill:#6DB33F + style MySQL fill:#4479A1 + style MongoDB fill:#47A248 + style Redis fill:#DC382D +``` + +### Clean Architecture (4-Layer) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Presentation Layer โ”‚ +โ”‚ (REST API Controllers - *Api.java) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ depends on +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Application Layer โ”‚ +โ”‚ UseCase Interfaces + Service Implementations โ”‚ +โ”‚ (Business Logic) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ depends on +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Domain Layer โ”‚ +โ”‚ Entities + Repository Interfaces โ”‚ +โ”‚ (Business Rules) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ depends on +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Infrastructure Layer โ”‚ +โ”‚ Repository Implementations + External APIs โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### ๋„๋ฉ”์ธ ๋ชจ๋“ˆ ๊ตฌ์กฐ + +๊ฐ ๋„๋ฉ”์ธ์€ ์ผ๊ด€๋œ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค: + +``` +/ +โ”œโ”€โ”€ presentation/ # REST API ์—”๋“œํฌ์ธํŠธ +โ”‚ โ”œโ”€โ”€ *Api.java # Controller +โ”‚ โ””โ”€โ”€ swagger/ # API ๋ช…์„ธ +โ”œโ”€โ”€ application/ +โ”‚ โ”œโ”€โ”€ usecase/ # UseCase ์ธํ„ฐํŽ˜์ด์Šค +โ”‚ โ”œโ”€โ”€ service/ # UseCase ๊ตฌํ˜„ +โ”‚ โ””โ”€โ”€ dto/ # Request/Response DTO +โ”‚ โ”œโ”€โ”€ request/ +โ”‚ โ””โ”€โ”€ response/ +โ”œโ”€โ”€ domain/ +โ”‚ โ”œโ”€โ”€ entity/ # JPA/MongoDB Entity +โ”‚ โ””โ”€โ”€ repository/ # Repository Interface +โ””โ”€โ”€ external/ # ์™ธ๋ถ€ API ํ†ตํ•ฉ (์„ ํƒ) +``` + +### ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์•„ํ‚คํ…์ฒ˜ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ MySQL โ”‚ โ”‚ MongoDB โ”‚ โ”‚ Redis โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ - User โ”‚ โ”‚ - PinPoint โ”‚ โ”‚ - JWT Token โ”‚ +โ”‚ - Complex โ”‚ โ”‚ - Search โ”‚ โ”‚ - TempUser โ”‚ +โ”‚ - Notice โ”‚ โ”‚ History โ”‚ โ”‚ โ”‚ +โ”‚ - Diagnosis โ”‚ โ”‚ - Facility โ”‚ โ”‚ โ”‚ +โ”‚ - Like โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿ’พ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„ + +### Entity Relationship Diagram + +```mermaid +erDiagram + USER ||--o{ DIAGNOSIS : has + USER ||--o{ LIKE : creates + USER ||--o{ PINPOINT : owns + USER ||--o{ SEARCH_HISTORY : performs + + NOTICE ||--o{ NOTICE_COMPLEX : contains + COMPLEX ||--o{ NOTICE_COMPLEX : included_in + COMPLEX ||--o{ UNIT_TYPE : has + COMPLEX ||--o{ FACILITY : near + + LIKE }o--|| NOTICE : targets + LIKE }o--|| COMPLEX : targets + LIKE }o--|| UNIT_TYPE : targets + + USER { + uuid id PK + string name + string profile_image_url + string oauth_provider + string oauth_id + datetime created_at + datetime updated_at + } + + NOTICE { + string id PK + string title + date recruit_start_date + date recruit_end_date + string region + string supply_type + } + + COMPLEX { + string id PK + string name + string address + string heating + int total_households + } + + UNIT_TYPE { + string id PK + string complex_id FK + string type_code + int area + int rent + int deposit + } + + DIAGNOSIS { + uuid id PK + uuid user_id FK + json diagnosis_result + datetime created_at + } + + LIKE { + uuid id PK + uuid user_id FK + string target_type + string target_id + } + + PINPOINT { + string id PK + uuid user_id + string name + coordinate location + boolean is_first + } + + FACILITY { + string id PK + string complex_id + string facility_type + array facilities + } + + SEARCH_HISTORY { + string id PK + uuid user_id + string keyword + json filters + datetime created_at + } + + NOTICE_COMPLEX { + string notice_id FK + string complex_id FK + } +``` + +### MySQL (Relational Data) + +**User ํ…Œ์ด๋ธ”** +- ์‚ฌ์šฉ์ž ๊ธฐ๋ณธ ์ •๋ณด (์ด๋ฆ„, ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, OAuth ์ •๋ณด) +- ์ง„๋‹จ ๊ฒฐ๊ณผ์™€ 1:N ๊ด€๊ณ„ +- ์ข‹์•„์š”์™€ 1:N ๊ด€๊ณ„ + +**Complex ํ…Œ์ด๋ธ”** +- ์ž„๋Œ€์ฃผํƒ ๋‹จ์ง€ ์ •๋ณด +- Notice์™€ N:M ๊ด€๊ณ„ (ํ•˜๋‚˜์˜ ๊ณต๊ณ ์— ์—ฌ๋Ÿฌ ๋‹จ์ง€) +- UnitType๊ณผ 1:N ๊ด€๊ณ„ + +**Notice ํ…Œ์ด๋ธ”** +- ์ฒญ์•ฝ ๊ณต๊ณ  ์ •๋ณด +- ๋ชจ์ง‘ ๊ธฐ๊ฐ„, ๋‹น์ฒจ์ž ๋ฐœํ‘œ์ผ, ๊ณ„์•ฝ์ผ ๋“ฑ + +**Diagnosis ํ…Œ์ด๋ธ”** +- ์ฒญ์•ฝ ์ง„๋‹จ ๊ฒฐ๊ณผ ์ €์žฅ +- ์‚ฌ์šฉ์ž ์†Œ๋“, ์ž์‚ฐ, ๊ฐ€์กฑ ๊ตฌ์„ฑ์› ์ •๋ณด + +**Like ํ…Œ์ด๋ธ”** +- ์‚ฌ์šฉ์ž๋ณ„ ์ข‹์•„์š” ์ •๋ณด +- ๊ณต๊ณ , ๋‹จ์ง€, ๋ฐฉ ํƒ€์ž… ๊ตฌ๋ถ„ + +### MongoDB (Document-based Data) + +**PinPoint Collection** +- ์‚ฌ์šฉ์ž์˜ ๊ด€์‹ฌ ์œ„์น˜ ์ •๋ณด +- Kakao Local API๋ฅผ ํ†ตํ•œ ์ขŒํ‘œ ๋ฐ์ดํ„ฐ +- ์ฆ๊ฒจ์ฐพ๊ธฐ(first) ์—ฌ๋ถ€ + +**SearchHistory Collection** +- ์‚ฌ์šฉ์ž๋ณ„ ๊ฒ€์ƒ‰ ๊ธฐ๋ก +- ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ, ํ•„ํ„ฐ ์กฐ๊ฑด, ํƒ€์ž„์Šคํƒฌํ”„ + +**Facility Collection** +- ๋‹จ์ง€ ์ฃผ๋ณ€ ์‹œ์„ค ์ •๋ณด +- ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์‹œ์„ค ๋ชฉ๋ก + +### Redis (Cache & Session) + +- **JWT Refresh Token**: ํ† ํฐ ๊ฐฑ์‹ ์šฉ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์ €์žฅ +- **TempUserInfo**: OAuth2 ์ธ์ฆ ํ›„ ํšŒ์›๊ฐ€์ž… ์ „ ์ž„์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด + +--- + +## ๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ + +``` +server/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ main/ +โ”‚ โ”‚ โ”œโ”€โ”€ java/com/pinHouse/server/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ core/ # ๊ณตํ†ต ๊ด€์‹ฌ์‚ฌ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ aop/ # @CheckLogin AOP +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ config/ # Swagger, DB ์„ค์ • +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ exception/ # ์˜ˆ์™ธ ์ฒ˜๋ฆฌ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ response/ # ํ‘œ์ค€ ์‘๋‹ต +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ util/ # ์œ ํ‹ธ๋ฆฌํ‹ฐ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ security/ # ๋ณด์•ˆ ๋ฐ ์ธ์ฆ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # OAuth2 ์ฒ˜๋ฆฌ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ jwt/ # JWT ํ† ํฐ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ oauth2/ # OAuth2 ์„ค์ • +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ config/ # Security ์„ค์ • +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ platform/ # ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ user/ # ์‚ฌ์šฉ์ž +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ housing/ # ์ฃผํƒ ์ •๋ณด +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ complex/ # ๋‹จ์ง€ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ notice/ # ๊ณต๊ณ  +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ facility/ # ์ฃผ๋ณ€์‹œ์„ค +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ diagnostic/ # ์ฒญ์•ฝ ์ง„๋‹จ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ diagnosis/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ school/ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ rule/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ pinPoint/ # ํ•€ํฌ์ธํŠธ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ like/ # ์ข‹์•„์š” +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ search/ # ๊ฒ€์ƒ‰ +โ”‚ โ”‚ โ””โ”€โ”€ resources/ +โ”‚ โ”‚ โ”œโ”€โ”€ application.yml +โ”‚ โ”‚ โ”œโ”€โ”€ application-local.yml +โ”‚ โ”‚ โ””โ”€โ”€ application-dev.yml +โ”‚ โ””โ”€โ”€ test/ # ํ…Œ์ŠคํŠธ ์ฝ”๋“œ +โ”œโ”€โ”€ pinhouse_docker/ # Docker ์„ค์ • +โ”‚ โ””โ”€โ”€ docker-compose.yml +โ”œโ”€โ”€ build.gradle +โ”œโ”€โ”€ CLAUDE.md # ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ +โ””โ”€โ”€ README.md +``` + +--- + +## ๐Ÿ”ง ์ฃผ์š” ๊ตฌํ˜„ ์‚ฌํ•ญ + +### OAuth2 ์ธ์ฆ ํ๋ฆ„ + +```mermaid +sequenceDiagram + participant Client + participant Backend + participant OAuth2 as OAuth2 Provider
(Kakao/Naver) + participant Redis + participant MySQL + + Client->>Backend: 1. OAuth2 ๋กœ๊ทธ์ธ ์š”์ฒญ + Backend->>OAuth2: 2. ์ธ์ฆ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ + OAuth2->>Client: 3. ์‚ฌ์šฉ์ž ๋™์˜ ํ›„ Authorization Code ๋ฐ˜ํ™˜ + Client->>Backend: 4. Authorization Code ์ „๋‹ฌ + Backend->>OAuth2: 5. Access Token ์š”์ฒญ + OAuth2->>Backend: 6. Access Token ๋ฐ˜ํ™˜ + Backend->>OAuth2: 7. ์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ + OAuth2->>Backend: 8. ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ˜ํ™˜ + + alt ์‹ ๊ทœ ์‚ฌ์šฉ์ž + Backend->>Redis: 9. ์ž„์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ (tempUserKey) + Backend->>Client: 10. tempUserKey ๋ฐ˜ํ™˜ + Client->>Backend: 11. ํšŒ์›๊ฐ€์ž… ์š”์ฒญ (tempUserKey + ์ถ”๊ฐ€ ์ •๋ณด) + Backend->>Redis: 12. ์ž„์‹œ ์ •๋ณด ์กฐํšŒ + Backend->>MySQL: 13. ์‚ฌ์šฉ์ž ์ƒ์„ฑ + Backend->>Client: 14. JWT Token ๋ฐœ๊ธ‰ (Access + Refresh) + else ๊ธฐ์กด ์‚ฌ์šฉ์ž + Backend->>MySQL: 9. ์‚ฌ์šฉ์ž ์กฐํšŒ + Backend->>Redis: 10. Refresh Token ์ €์žฅ + Backend->>Client: 11. JWT Token ๋ฐœ๊ธ‰ (Access + Refresh) + end +``` + +### 1. Clean Architecture ์ ์šฉ + +**์˜์กด์„ฑ ์—ญ์ „ ์›์น™ (DIP) ์ค€์ˆ˜** +```java +// UseCase Interface (Application Layer) +public interface ComplexUseCase { + ComplexDetailResponse getComplex(String id, String pinPointId); +} + +// Service Implementation +@Service +@RequiredArgsConstructor +public class ComplexService implements ComplexUseCase { + @Override + public ComplexDetailResponse getComplex(String id, String pinPointId) { + // ๊ตฌํ˜„ + } +} + +// Controller๋Š” UseCase ์ธํ„ฐํŽ˜์ด์Šค์— ์˜์กด +@RestController +@RequiredArgsConstructor +public class ComplexApi { + private final ComplexUseCase complexUseCase; // ๊ตฌํ˜„์ด ์•„๋‹Œ ์ธํ„ฐํŽ˜์ด์Šค์— ์˜์กด +} +``` + +### 2. AOP๋ฅผ ํ†ตํ•œ ์ธ์ฆ ์ฒ˜๋ฆฌ + +```java +@CheckLogin // ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ +@GetMapping("/mypage") +public ApiResponse getMyPage( + @AuthenticationPrincipal PrincipalDetails principalDetails +) { + UUID userId = principalDetails.getId(); + // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง +} +``` + +**LoginCheckAspect**๊ฐ€ `@CheckLogin` ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „์— ์ธ์ฆ ์—ฌ๋ถ€๋ฅผ ์ž๋™ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. + +### 3. ๋‹ค์ธต ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ „๋žต + +**MySQL** - ์ •ํ˜• ๋ฐ์ดํ„ฐ (JPA) +```java +@Entity +@Table(name = "users") +public class User extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private UUID id; + + @OneToMany(mappedBy = "user") + private List diagnoses; +} +``` + +**MongoDB** - ๋น„์ •ํ˜• ๋ฐ์ดํ„ฐ +```java +@Document(collection = "pinpoints") +public class PinPointDocument { + @Id + private String id; + + private Coordinate coordinate; // Nested Document + private List tags; // ์œ ์—ฐํ•œ ์Šคํ‚ค๋งˆ +} +``` + +**Redis** - ์„ธ์…˜ ๋ฐ ์บ์‹ฑ +```java +@RedirectAttributes +public void saveRefreshToken(UUID userId, String refreshToken) { + redisTemplate.opsForValue() + .set("RT:" + userId, refreshToken, 7, TimeUnit.DAYS); +} +``` + +### 4. ๋ณต์žกํ•œ ๊ทœ์น™ ์—”์ง„ ๊ตฌํ˜„ + +**Chain of Responsibility ํŒจํ„ด์„ ํ™œ์šฉํ•œ ์ฒญ์•ฝ ์ž๊ฒฉ ํŒ๋‹จ** + +```java +public interface DiagnosisRule { + boolean matches(RuleCriteria criteria); + DiagnosisResult apply(UserInfo userInfo); +} + +@Component +public class RuleChain { + private final List rules; + + public DiagnosisResult diagnose(UserInfo userInfo, RuleCriteria criteria) { + return rules.stream() + .filter(rule -> rule.matches(criteria)) + .findFirst() + .map(rule -> rule.apply(userInfo)) + .orElse(DiagnosisResult.notEligible()); + } +} +``` + +### 5. ํšจ์œจ์ ์ธ ํŽ˜์ด์ง€๋„ค์ด์…˜ + +**Cursor ๊ธฐ๋ฐ˜ ๋ฌดํ•œ ์Šคํฌ๋กค** +```java +public record SliceRequest( + int page, + int size +) { + public Pageable toPageable() { + return PageRequest.of(page, size + 1); // N+1 ์กฐํšŒ + } +} + +public record SliceResponse( + List content, + boolean hasNext +) { + public static SliceResponse from(Page page) { + boolean hasNext = page.getContent().size() > page.getSize(); + List content = hasNext + ? page.getContent().subList(0, page.getSize()) + : page.getContent(); + return new SliceResponse<>(content, hasNext); + } +} +``` + +### 6. ํ‘œ์ค€ํ™”๋œ API ์‘๋‹ต + +```java +public record ApiResponse( + boolean success, + T data, + ErrorResponse error +) { + public static ApiResponse ok(T data) { + return new ApiResponse<>(true, data, null); + } + + public static ApiResponse error(ErrorCode errorCode) { + return new ApiResponse<>(false, null, + new ErrorResponse(errorCode.getCode(), errorCode.getMessage())); + } +} +``` + +### 7. ๋Œ€์ค‘๊ตํ†ต ๊ฒฝ๋กœ ์ •๋ณด ์ตœ์ ํ™” + +**TransitInfoResponse** - ์ „์ฒด ๊ฒฝ๋กœ ์ •๋ณด์™€ ๊ตฌ๊ฐ„๋ณ„ ์ •๋ณด ๋ถ„๋ฆฌ +```java +public record TransitInfoResponse( + String totalTime, // "์•ฝ 1์‹œ๊ฐ„ 23๋ถ„" + Integer totalTimeMinutes, // 83 + Double totalDistance, // 39.6 (km) + List segments // ๊ตฌ๊ฐ„๋ณ„ ์ƒ์„ธ ์ •๋ณด +) {} +``` + +์ด๋ฅผ ํ†ตํ•ด ์ค‘๋ณต ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  API ์‘๋‹ต ํฌ๊ธฐ๋ฅผ ์ตœ์ ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. + +--- + +## โšก๏ธ ์„ฑ๋Šฅ ์ตœ์ ํ™” + +### 1. ๋‹ค์ธต ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ „๋žต +- **MySQL**: ์ •ํ˜•ํ™”๋œ ํŠธ๋žœ์žญ์…˜ ๋ฐ์ดํ„ฐ +- **MongoDB**: ๋ณต์žกํ•œ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ๋ฐ ์œ ์—ฐํ•œ ์Šคํ‚ค๋งˆ +- **Redis**: ์„ธ์…˜ ์บ์‹ฑ์œผ๋กœ DB ๋ถ€ํ•˜ ๊ฐ์†Œ + +### 2. ์ปค์„œ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ +- Offset ๋ฐฉ์‹ ๋Œ€์‹  Cursor ๊ธฐ๋ฐ˜ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„ +- ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ ์„ฑ๋Šฅ ์ตœ์ ํ™” (O(n) โ†’ O(1)) + +### 3. DTO ์ตœ์ ํ™” +- Java Record ํƒ€์ž…์œผ๋กœ ๋ถˆ๋ณ€ ๊ฐ์ฒด ์ƒ์„ฑ +- ์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ (TransitInfoResponse ๊ตฌ์กฐ ๊ฐœ์„ ) + +### 4. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ +- JPA Fetch Join ๋ฐ EntityGraph ํ™œ์šฉ +- ์—ฐ๊ด€ ๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ํ•œ ๋ฒˆ์— ์กฐํšŒ + +### 5. AOP๋ฅผ ํ†ตํ•œ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ +- `@CheckLogin` ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ธ์ฆ ๋กœ์ง ์ค‘๋ณต ์ œ๊ฑฐ +- ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ ๊ฐ€๋Šฅ + +--- + +## ๐Ÿ”’ ๋ณด์•ˆ + +### ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ +- **JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ**: Stateless ์ธ์ฆ์œผ๋กœ ํ™•์žฅ์„ฑ ํ™•๋ณด +- **Refresh Token**: Redis์— ์ €์žฅํ•˜์—ฌ ๋ณด์•ˆ ๊ฐ•ํ™” +- **OAuth2**: ์†Œ์…œ ๋กœ๊ทธ์ธ์œผ๋กœ ํŒจ์Šค์›Œ๋“œ ๊ด€๋ฆฌ ๋ถ€๋‹ด ์ œ๊ฑฐ + +### ๋ณด์•ˆ ์„ค์ • +- **CORS ์„ค์ •**: ํ—ˆ์šฉ๋œ ๋„๋ฉ”์ธ๋งŒ API ์ ‘๊ทผ ๊ฐ€๋Šฅ +- **HTTPS**: Nginx + Certbot์œผ๋กœ SSL/TLS ์ ์šฉ +- **SQL Injection ๋ฐฉ์ง€**: JPA Prepared Statement ์‚ฌ์šฉ + +--- + +## ๐Ÿ”„ CI/CD + +### GitHub Actions Workflows + +**1. CI Test (`ci-test.yml`)** +- **ํŠธ๋ฆฌ๊ฑฐ**: Pull Request to `develop` +- **์ž‘์—…**: + - MySQL, Redis, MongoDB ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ + - `./gradlew clean build` ์‹คํ–‰ (ํ…Œ์ŠคํŠธ ํฌํ•จ) + - JUnit ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋ฐœํ–‰ + +**2. Dev Deployment (`dev-ci-cd.yml`)** +- **ํŠธ๋ฆฌ๊ฑฐ**: Push to `develop` +- **์ž‘์—…**: + - Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ + - Dev ์„œ๋ฒ„ ๋ฐฐํฌ + +### ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์šฐ + +``` +feature/* โ†’ PR โ†’ develop (CI Test) โ†’ merge โ†’ develop (Deploy to Dev) +``` + +--- + +## ๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท (Screenshots) + +> ๐Ÿ“ **Note**: ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์Šคํฌ๋ฆฐ์ƒท์„ ์ถ”๊ฐ€ํ•˜๋ฉด ํฌํŠธํด๋ฆฌ์˜ค ํšจ๊ณผ๊ฐ€ ๊ทน๋Œ€ํ™”๋ฉ๋‹ˆ๋‹ค. +> - Swagger API ๋ฌธ์„œ ํ™”๋ฉด +> - ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ +> - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ERD ๋‹ค์ด์–ด๊ทธ๋žจ +> - ์ฃผ์š” ๊ธฐ๋Šฅ ๋™์ž‘ ํ™”๋ฉด + +--- + +## ๐Ÿ‘ฅ ๊ฐœ๋ฐœ ํŒ€ + +**Backend Team** +- Spring Boot, Clean Architecture, Multi-Database Design +- OAuth2, JWT ์ธ์ฆ/์ธ๊ฐ€ ๊ตฌํ˜„ +- ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์„ค๊ณ„ (์ฒญ์•ฝ ์ง„๋‹จ ๊ทœ์น™ ์—”์ง„) +- Odsay API ์—ฐ๋™ ๋ฐ ๋Œ€์ค‘๊ตํ†ต ๊ฒฝ๋กœ ์ตœ์ ํ™” +- Kakao Local API๋ฅผ ํ™œ์šฉํ•œ ์ขŒํ‘œ ๋ณ€ํ™˜ ์‹œ์Šคํ…œ + +--- + +## ๐Ÿ“„ ๋ผ์ด์„ผ์Šค + +์ด ํ”„๋กœ์ ํŠธ๋Š” MIT ๋ผ์ด์„ผ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. + +--- + +## ๐Ÿ“ž ๋ฌธ์˜ + +ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์ด๋‚˜ ์ œ์•ˆ์ด ์žˆ์œผ์‹œ๋ฉด ์ด์Šˆ๋ฅผ ๋“ฑ๋กํ•ด์ฃผ์„ธ์š”. + +**Repository**: [PinHouse GitHub](https://github.com/PinHouse/PinHouse_BE) + +--- + +
+ +**Built with โค๏ธ by PinHouse Team** + +
diff --git a/src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/result/SubwayLineType.java b/src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/result/SubwayLineType.java index 42a7465..945a853 100644 --- a/src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/result/SubwayLineType.java +++ b/src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/result/SubwayLineType.java @@ -23,44 +23,45 @@ public enum SubwayLineType { SEOUL_LINE_7(7, "์ˆ˜๋„๊ถŒ 7ํ˜ธ์„ ", "#69702D"), SEOUL_LINE_8(8, "์ˆ˜๋„๊ถŒ 8ํ˜ธ์„ ", "#E5046C"), SEOUL_LINE_9(9, "์ˆ˜๋„๊ถŒ 9ํ˜ธ์„ ", "#CEA43A"), + GTX_A(91, "GTX-A", "#9A6292"), AIRPORT(101, "๊ณตํ•ญ์ฒ ๋„", "#3CA8EA"), - MAGLEV(102, "์ž๊ธฐ๋ถ€์ƒ์ฒ ๋„", "#BBBBBB"), + MAGLEV(102, "์ž๊ธฐ๋ถ€์ƒ์ฒ ๋„", "#8FC31F"), GYEONGUI_JUNGANG(104, "๊ฒฝ์˜์ค‘์•™์„ ", "#69CCCC"), EVERLINE(107, "์—๋ฒ„๋ผ์ธ", "#6CAB2A"), - GYEONGCHUN(108, "๊ฒฝ์ถ˜์„ ", "#BBBBBB"), + GYEONGCHUN(108, "๊ฒฝ์ถ˜์„ ", "#0C8E72"), SINBUNDANG(109, "์‹ ๋ถ„๋‹น์„ ", "#C12B2F"), UIJEONGBU(110, "์˜์ •๋ถ€๊ฒฝ์ „์ฒ ", "#DF7503"), - GYEONGGANG(112, "๊ฒฝ๊ฐ•์„ ", "#BBBBBB"), + GYEONGGANG(112, "๊ฒฝ๊ฐ•์„ ", "#003DA5"), UISINSEOL(113, "์šฐ์ด์‹ ์„ค์„ ", "#AFBF04"), SEOHAE(114, "์„œํ•ด์„ ", "#4DA635"), GIMPO_GOLDLINE(115, "๊น€ํฌ๊ณจ๋“œ๋ผ์ธ", "#957326"), SUIN_BUNDANG(116, "์ˆ˜์ธ๋ถ„๋‹น์„ ", "#FFCE32"), - SILLIM(117, "์‹ ๋ฆผ์„ ", "#BBBBBB"), + SILLIM(117, "์‹ ๋ฆผ์„ ", "#6789CA"), // ์ธ์ฒœ INCHEON_LINE_1(21, "์ธ์ฒœ 1ํ˜ธ์„ ", "#7EAAD6"), - INCHEON_LINE_2(22, "์ธ์ฒœ 2ํ˜ธ์„ ", "#BBBBBB"), + INCHEON_LINE_2(22, "์ธ์ฒœ 2ํ˜ธ์„ ", "#F5A200"), // ๋Œ€์ „ - DAEJEON_LINE_1(31, "๋Œ€์ „ 1ํ˜ธ์„ ", "#BBBBBB"), + DAEJEON_LINE_1(31, "๋Œ€์ „ 1ํ˜ธ์„ ", "#007448"), // ๋Œ€๊ตฌ - DAEGU_LINE_1(41, "๋Œ€๊ตฌ 1ํ˜ธ์„ ", "#BBBBBB"), - DAEGU_LINE_2(42, "๋Œ€๊ตฌ 2ํ˜ธ์„ ", "#BBBBBB"), - DAEGU_LINE_3(43, "๋Œ€๊ตฌ 3ํ˜ธ์„ ", "#BBBBBB"), - DAEGYEONG(48, "๋Œ€๊ฒฝ์„ ", "#BBBBBB"), + DAEGU_LINE_1(41, "๋Œ€๊ตฌ 1ํ˜ธ์„ ", "#D93F3F"), + DAEGU_LINE_2(42, "๋Œ€๊ตฌ 2ํ˜ธ์„ ", "#00AA80"), + DAEGU_LINE_3(43, "๋Œ€๊ตฌ 3ํ˜ธ์„ ", "#F7B500"), + DAEGYEONG(48, "๋Œ€๊ฒฝ์„ ", "#4B6CB7"), // ๊ด‘์ฃผ - GWANGJU_LINE_1(51, "๊ด‘์ฃผ 1ํ˜ธ์„ ", "#BBBBBB"), + GWANGJU_LINE_1(51, "๊ด‘์ฃผ 1ํ˜ธ์„ ", "#0094D8"), // ๋ถ€์‚ฐ - BUSAN_LINE_1(71, "๋ถ€์‚ฐ 1ํ˜ธ์„ ", "#BBBBBB"), - BUSAN_LINE_2(72, "๋ถ€์‚ฐ 2ํ˜ธ์„ ", "#BBBBBB"), - BUSAN_LINE_3(73, "๋ถ€์‚ฐ 3ํ˜ธ์„ ", "#BBBBBB"), - BUSAN_LINE_4(74, "๋ถ€์‚ฐ 4ํ˜ธ์„ ", "#BBBBBB"), - DONGHAE(78, "๋™ํ•ด์„ ", "#BBBBBB"), - BUSAN_GIMHAE(79, "๋ถ€์‚ฐ-๊น€ํ•ด๊ฒฝ์ „์ฒ ", "#BBBBBB"), + BUSAN_LINE_1(71, "๋ถ€์‚ฐ 1ํ˜ธ์„ ", "#F06A00"), + BUSAN_LINE_2(72, "๋ถ€์‚ฐ 2ํ˜ธ์„ ", "#6AC6B8"), + BUSAN_LINE_3(73, "๋ถ€์‚ฐ 3ํ˜ธ์„ ", "#CFA32E"), + BUSAN_LINE_4(74, "๋ถ€์‚ฐ 4ํ˜ธ์„ ", "#4E67A1"), + DONGHAE(78, "๋™ํ•ด์„ ", "#005BAC"), + BUSAN_GIMHAE(79, "๋ถ€์‚ฐ-๊น€ํ•ด๊ฒฝ์ „์ฒ ", "#875CAC"), UNKNOWN(-1, "UNKNOWN", "#BBBBBB");