diff --git a/README.md b/README.md
index 91177c5..f64a262 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,453 @@
-# carrot_server
+# ๐ฅ๋น๊ทผ๋ง์ผ๐ฅ
+
+
+
+- ๋ฐฐํฌ URL : https://d1elknx4d22bup.cloudfront.net (์ ๊ฒ ์ค)
+- ๋ฐฐํฌ ์๋ฒ : http://3.35.219.116:8080 (์ ๊ฒ ์ค)
+- ์์ฐ ์์ : https://youtu.be/Ou8EJn5kFhk
+
+
+
+## ํ๋ก์ ํธ ์๊ฐ
+
+- โ์ง์ญ ๊ธฐ๋ฐ ๊ฑฐ๋ ํ๋ซํผ์ ์ค์ ์๋น์ค ์ํคํ
์ฒ๋ก ๊ตฌํํ ๋น๊ทผ๋ง์ผ ํด๋ก ํ๋ก์ ํธ์
๋๋ค.โ
+- โ์ค์ ์๋น์ค ์์ค์ ๋ก๊ทธ์ธยท์ํยท์ฑํ
ยท์์ฝยท์ด๋ฏธ์ง ์
๋ก๋ ๊ธฐ๋ฅ์ ๊ฐ์ถ ํ์คํ ํ๋ก์ ํธ์
๋๋ค.โ
+- "์๋น์ค ์ ์ฒด ํ๋ฆ์ ๊ฒฝํํ๊ธฐ ์ํด ์งํํ ํด๋ก ์ฝ๋ฉ์
๋๋ค.โ
+
+
+
+## ํ์ ๊ตฌ์ฑ
+
+
+
+| **์ด๋๊ตญ** | **ํ์ฌํธ** | **์ด์์ค** |
+| :------: | :------: | :------: |
+| [
@LeeDongGuk](https://github.com/leedongguk) | [
@ariana9rande](https://github.com/ariana9rande) | [
@euijunlee98](https://github.com/euijunlee98) |
+
+
+
+
+
+## 1. ๊ฐ๋ฐ ํ๊ฒฝ
+
+- Front : HTML, React, styled-components
+- Back-end : Spring-Boot
+- ๋ฒ์ ๋ฐ ์ด์๊ด๋ฆฌ : Github, Github Issues, Github Project
+- ํ์
ํด : Discord, Notion, Github Wiki
+- ์๋น์ค ๋ฐฐํฌ ํ๊ฒฝ : AWS
+- ์ฌ์ฉ ๊ธฐ์ ๋ฐ API: COOL SMS API, KAKAO MAP, KAKAO PAY, SWAGGER, CI/CD, CHATGPT
+- ๋์์ธ : Figma
+
+
+## 2. ์ฑํํ ๊ฐ๋ฐ ๊ธฐ์ ๊ณผ ๋ธ๋์น ์ ๋ต
+
+### React, styled-component
+
+- React
+ - ์ปดํฌ๋ํธํ๋ฅผ ํตํด ์ถํ ์ ์ง๋ณด์์ ์ฌ์ฌ์ฉ์ฑ์ ๊ณ ๋ คํ์ต๋๋ค.
+ - ์ ์ ๋ฐฐ๋, ์๋จ๊ณผ ํ๋จ ๋ฐฐ๋ ๋ฑ ์ค๋ณต๋์ด ์ฌ์ฉ๋๋ ๋ถ๋ถ์ด ๋ง์ ์ปดํฌ๋ํธํ๋ฅผ ํตํด ๋ฆฌ์์ค ์ ์ฝ์ด ๊ฐ๋ฅํ์ต๋๋ค.
+
+- AWS ๊ธฐ๋ฐ ๋ฐฐํฌ(EC2, S3, RDS)
+ - EC2๋ฅผ ํ์ฉํด React ํ๋ก ํธ์๋์ Spring Boot ๋ฐฑ์๋๋ฅผ ์ค์๊ฐ์ผ๋ก ์ด์ ๊ฐ๋ฅํ ํํ๋ก ๋ฐฐํฌํ์ต๋๋ค.
+ - S3๋ฅผ ์ด์ฉํด ์ํ ์ด๋ฏธ์ง ์
๋ก๋ ๋ฐ ์ ์ ํ์ผ ๊ด๋ฆฌ๋ฅผ ์งํํ๋ฉฐ ์ ์ฅ ๋น์ฉ๊ณผ ์์ ์ฑ์ ํ๋ณดํ์ต๋๋ค.
+ - RDS(MySQL)๋ฅผ ๋์
ํ์ฌ ๋ฐ์ดํฐ์ ์์ ์ ์ธ ์ ์ฅ ๋ฐ ๋ฐฑ์
/๊ด๋ฆฌ ํ๊ฒฝ์ ๊ตฌ์ถํ์ต๋๋ค.
+ - CI/CD ํ์ดํ๋ผ์ธ์ ๊ตฌ์ฑํด GitHub Actions ๊ธฐ๋ฐ ์๋ ๋ฐฐํฌ ํ๊ฒฝ์ ๊ตฌ์ถํจ์ผ๋ก์จ ์ฝ๋๋ฅผ pushํ๋ ์ฆ์ ์๋ ๋น๋ยท๋ฐฐํฌ๊ฐ ์ด๋ฃจ์ด์ง๋ ์ง์์ ๋ฐฐํฌ ํ๊ฒฝ์ ์์ฑํ์ต๋๋ค.
+
+ - COOL SMS API (๋ฌธ์ ๋ณธ์ธ์ธ์ฆ)
+ - ํ์๊ฐ์
๊ณผ์ ์์ ๋ฌธ์ ์ธ์ฆ ๋ฒํธ ๋ฐ์ก ๊ธฐ๋ฅ์ ๊ตฌํํด ์ฌ์ฉ์ ์ ๋ขฐ์ฑ๊ณผ ๋ณด์ ๋ ๋ฒจ์ ๊ฐํํ์ต๋๋ค.
+ - ์ธ์ฆ ์ ์ฐจ๋ฅผ REST API์ ์ฐ๋ํ์ฌ, ์ ์ ๊ฒฝํ(UX) ์์ ์์ด ๋น ๋ฅด๊ฒ ์ธ์ฆ ์ ์ฐจ๋ฅผ ์๋ฃํ ์ ์๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์์ต๋๋ค.
+
+ - Kakao Map API
+ - ์ฌ์ฉ์ ์์น ๊ธฐ๋ฐ์ผ๋ก ์ํ์ ์กฐํํ๊ฑฐ๋ ๋ฑ๋กํ ์ ์๋๋ก Kakao Map์ ์ด์ฉํด ์ง๋ ๊ธฐ๋ฐ UI ๊ธฐ๋ฅ์ ๊ตฌํํ์ต๋๋ค.
+ - ์ฃผ์ ๊ฒ์, ์ขํ ๋ณํ, ๋ง์ปค ์ปค์คํฐ๋ง์ด์ง ๋ฑ ์ง๋ ์๋น์ค์ ํต์ฌ ๊ธฐ๋ฅ์ ์ฐ๋ํด ๋๋ค ๊ธฐ๋ฐ ํ๋ซํผ์ผ๋ก์์ ํน์ฑ์ ์์ฑํ์ต๋๋ค.
+
+ - Swagger๋ฅผ ํ์ฉํ API ๋ฌธ์ํ
+ - Swagger UI๋ฅผ ํตํด ๋ฐฑ์๋ API ์ ์ฒด๋ฅผ ๋ฌธ์ํํ์ฌ, ํ์ ๊ฐ์ API ์ํต ๋น์ฉ์ ํฌ๊ฒ ์ค์ด๊ณ ๊ฐ๋ฐ ํจ์จ์ฑ์ ํฅ์์์ผฐ์ต๋๋ค.
+ - API ์คํ ๋ณ๊ฒฝ ์ ์๋ ๋ฌธ์ ์
๋ฐ์ดํธ๊ฐ ๊ฐ๋ฅํด ์ ์ง๋ณด์ ๊ณผ์ ์์๋ ๋์ ์์ฐ์ฑ์ ํ๋ณดํ์ต๋๋ค.
+
+ - ChatGPT๋ฅผ ํ์ฉํ ์๋ฌ ๋ก๊ทธ ์๋ ๋ถ์
+ - ํ๋ก ํธยท๋ฐฑ์๋์์ ๋ฐ์ํ๋ ์ค๋ฅ ๋ก๊ทธ๋ฅผ ChatGPT API๋ก ์ ๋ฌํ์ฌ ์๋์ผ๋ก ์๋ฌ ์์ธ์ ๋ถ์ํ๊ณ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ์๋ฐ๋ ์์คํ
์ ๊ตฌ์ถํ์ต๋๋ค.
+ - ์ด๋ฅผ ํตํด ๋๋ฒ๊น
์๊ฐ์ ํฌ๊ฒ ๋จ์ถ์ํค๊ณ , ๊ฐ๋ฐ ์์ฐ์ฑ๊ณผ ์์ ์ฑ์ ํฅ์ํ์ต๋๋ค.
+
+
+
+## ๋
ธ์
๊ด๋ฆฌ
+
+
+
+
+
+## 3. ์ญํ ๋ถ๋ด
+
+### ๐์ด๋๊ตญ
+
+- **FULL-STACK**
+
+
+
+### ๐ปํ์ฌํธ
+
+- **BACK-END**
+
+
+
+### ๐์ด์์ค
+
+- **BACK-END**
+
+
+
+## 4. ๊ฐ๋ฐ ๊ธฐ๊ฐ ๋ฐ ์์
๊ด๋ฆฌ
+
+### ๊ฐ๋ฐ ๊ธฐ๊ฐ
+
+- ์ ์ฒด ๊ฐ๋ฐ ๊ธฐ๊ฐ : 2025-09-01 ~ 2025-11-27
+- UI ๊ตฌํ : 2025-09-01 ~ 2025-09-08
+- ๊ธฐ๋ฅ ๊ตฌํ : 2025-09-09 ~ 2025-11-05
+- ํ
์คํธ : 2025-11-05 ~ 2025-11-27
+
+
+
+### ์์
๊ด๋ฆฌ
+
+- GitHub ๋ฐ NOTION์ ํตํด ์งํ ์ํฉ์ ๊ณต์ ํ์ต๋๋ค.
+- ์ฃผ๊ฐํ์๋ฅผ ์งํํ๋ฉฐ ์์
์์์ ๋ฐฉํฅ์ฑ์ ๋ํ ๊ณ ๋ฏผ์ ๋๋๊ณ NOTION์ ํ์ ๋ด์ฉ์ ๊ธฐ๋กํ์ต๋๋ค.
+
+
+
+## 5. ์ ๊ฒฝ ์ด ๋ถ๋ถ
+
+- ChatGPT๋ฅผ ํ์ฉํ ์๋ฌ ๋ก๊ทธ ์๋ ๋ถ์
+
+
+- SWAGGER๋ฅผ ํ์ฉํ API ๋ฌธ์
+
+
+
+
+## 6. ํ์ด์ง๋ณ ๊ธฐ๋ฅ
+
+### [์ด๊ธฐํ๋ฉด]
+- ๊ธฐ๋ฅ ์ค๋ช
+ - ์๋น์ค ์ ์ ์ ๊ฐ์ฅ ๋จผ์ ๋ํ๋๋ ํ๋ฉด
+ - ์์ํ๊ธฐ(ํ์๊ฐ์
)
+ - ๋ก๊ทธ์ธ(ํธ๋ํฐ ์ธ์ฆ๊ธฐ๋ฐ ๋ก๊ทธ์ธ)
+
+| ์ด๊ธฐํ๋ฉด |
+|----------|
+||
+
+
+
+### [ํ์๊ฐ์
(๋๋ค์ค์ )]
+- ์
๋ ฅ์ฐฝ์ ์ง์ญ์ ์
๋ ฅํ๋ฉด ์ง์ญ์ด ๊ฒ์๋ฉ๋๋ค.
+- ํ์ฌ์์น๋ก ์ฐพ๊ธฐ๋ก ํด๋ฆญ ์ GPS๊ธฐ๋ฐ์ผ๋ก ๋๋ค์ค์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
+
+| ํ์๊ฐ์
(๋๋ค์ค์ ) |
+|----------|
+||
+
+
+
+### [ํ์๊ฐ์
(ํธ๋ํฐ๋ฒํธ ์
๋ ฅ)]
+- ํธ๋ํฐ ๋ฒํธ๋ฅผ ์
๋ ฅํ๋ฉด ํธ๋ํฐ ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
+- ํธ๋ํฐ ๋ฒํธ๋ฅผ ์
๋ ฅํ์ง ์์ผ๋ฉด ๋ค์ ํ๋ฉด์ผ๋ก ๋์ด ๊ฐ ์ ์
์ต๋๋ค.
+
+| ํ์๊ฐ์
(ํธ๋ํฐ๋ฒํธ ์
๋ ฅ) |
+|----------|
+||
+
+
+
+### [ํ์๊ฐ์
(ํธ๋ํฐ๋ฒํธ ์ธ์ฆ)]
+- ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ํด๋ํฐ ๋ฒํธ๋ก CoolSMS API๋ฅผ ํตํด ์ธ์ฆ๋ฒํธ(6์๋ฆฌ)๋ฅผ ๋ฐ์กํ๊ณ ,
+ ์ด ์ธ์ฆ๋ฒํธ๋ฅผ Redis ์๋ฒ์ ์์ ์ ์ฅ(5๋ถ TTL)ํ ๋ค ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ธ์ฆ๋ฒํธ์ Redis์ ์ ์ฅ๋ ์ธ์ฆ๋ฒํธ๋ฅผ ๋น๊ตํ์ฌ ๋ณธ์ธ์ธ์ฆ์ ์๋ฃํ๋ ๊ตฌ์กฐ์
๋๋ค.
+- ๊ธฐ์ ํ๋ฆ
+ 1.์ฌ์ฉ์๊ฐ ํด๋ํฐ ๋ฒํธ ์
๋ ฅ โ ์ธ์ฆ๋ฒํธ ์์ฒญ
+ 2.์๋ฒ๋ ๋๋ค 6์๋ฆฌ ์ธ์ฆ๋ฒํธ ์์ฑ
+ 3.CoolSMS API๋ก ํด๋น ๋ฒํธ๋ก ์ธ์ฆ๋ฒํธ ๋ฐ์ก
+ 4.์ธ์ฆ๋ฒํธ๋ฅผ REDIS์ ์ ์ฅ
+ 5. ์ฌ์ฉ์๊ฐ ์ธ์ฆ๋ฒํธ ์
๋ ฅ
+ 6. ์๋ฒ๋ REDIS์ ์ ์ฅ๋ ๊ฐ๊ณผ ๋น๊ต
+ 7. ์ผ์น -> ๋ณธ์ธ์ธ์ฆ ์๋ ์ฑ๊ณต
+ ๋ถ์ผ์น/๋ง๋ฃ -> ์ธ์ฆ ์คํจ ์ฒ๋ฆฌ
+
+| ํ์๊ฐ์
(ํธ๋ํฐ๋ฒํธ ์ธ์ฆ) |
+|----------|
+||
+
+
+
+### [ํ๋กํ ์ค์ ]
+- ์นด๋ฉ๋ผ ์์ด์ฝ ํด๋ฆญ ์ ์ด๋ฏธ์ง ์ ํ
+- ์ ํ๋ ์ด๋ฏธ์ง ํ์ผ์ ํ๋ก ํธ์์ FormData๋ก ๋ฐฑ์๋ ์ ์ก
+- ๋ฐฑ์๋๋ ํ์ผ์ AWS S3 ๋ฒํท์ ์
๋ก๋
+- S3์ ์ ์ฅ๋ ํ์ผ์ URL์ DB์ ์ ์ฅํ์ฌ ํ๋กํ ์ด๋ฏธ์ง๋ก ์ฌ์ฉ
+- ์ด๋ฏธ์ง๋ฅผ ๋ฐ๊พธ๋ฉด ์๋์ผ๋ก โ์๋ฃโ ๋ฒํผ์ด ํ์ฑํ๋จ
+- ์
๋ ฅ์ฐฝ์ ์ ๋๋ค์์ ์
๋ ฅํ๋ฉด ์ค๋ณตํ์ธ ๋ฒํผ ํ์ฑํ
+- ์ค๋ณตํ์ธ API ํธ์ถ โ ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ ๋ฐํ
+- ์ฌ์ฉ ๊ฐ๋ฅ: ์ด๋ก์ ๋ฉ์์ง, ์ ์ฅ ๊ฐ๋ฅ
+- ์ค๋ณต๋จ: ๋นจ๊ฐ์ ๋ฉ์์ง, ์ ์ฅ ๋ถ๊ฐ
+- ์ค์ ์๋น์ค์ ๋์ผํ๊ฒ โ๋๋ค์ ๊ณ ์ ์ฑโ์ ํ๋ณด
+
+| ํ๋กํ ์ค์ |
+|----------|
+||
+
+
+
+### [ํ ํ๋ฉด]
+- ์๋จ ์นดํ
๊ณ ๋ฆฌ ํํฐ
+- ์ง์ญ(๋๋ค) ๊ธฐ๋ฐ ์ํ ๋ฆฌ์คํธ
+- ์ํ ์ธ๋ค์ผ + ์ ๋ณด ํ์
+- ํ๋จ Floating Button (์ํ ๋ฑ๋ก ๋ฒํผ)
+- ๊ฒ์ ๋ฒํผ
+
+| ํ ํ๋ฉด |
+|----------|
+||
+
+
+
+### [์ํ ์์ธ]
+- ์ํ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋
+- ํ๋งค์ ์ ๋ณด ํ์
+- ์ข์์ ๊ธฐ๋ฅ
+- ์กฐํ์ ๊ธฐ๋ฅ
+- ์ฑํ
ํ๊ธฐ ๊ธฐ๋ฅ
+- ์ ํธ ๊ฑฐ๋ ์ง์ญ(์ง๋ ํ์)
+
+| ์ํ ์์ธ |
+|----------|
+|.gif?raw=true)|
+
+
+
+### [์ํ ๊ฒ์]
+- ์ํ ๊ฒ์
+
+| ์ํ ๊ฒ์ |
+|----------|
+|.gif?raw=true)|
+
+
+
+### [๋ด ๋ฌผ๊ฑด ํ๊ธฐ]
+- ์ํ ์ด๋ฏธ์ง ์
๋ก๋ (0/5)
+- ์ค์ ์ด๋ฏธ์ง๋ ๋ฐฑ์๋๋ก ์ ์ก๋์ด AWS S3 ๋ฒํท์ ์ ์ฅ
+- ์นดํ
๊ณ ๋ฆฌ ์ ํ
+- ๊ฑฐ๋ ๋ฐฉ์ ์ ํ (ํ๋งคํ๊ธฐ / ๋๋ํ๊ธฐ)
+- ์ญ์ ์ ๋ฐ๊ธฐ(๊ฐ๊ฒฉ ์ ์) ์ต์
+- ๊ฑฐ๋ ํฌ๋ง ์ฅ์ ์ค์ (Kakao Map API ํ์
)
+
+
+| ๋ด ๋ฌผ๊ฑด ํ๊ธฐ | ์์น ์ฐพ๊ธฐ |
+|----------|----------|
+|||
+
+
+
+### [๋๋ค์ํ ํผ๋]
+- ๋๋ค ๋ชจ์/์ฃผ์ ์นดํ
๊ณ ๋ฆฌ ๋
ธ์ถ
+- ์ ์ฒด / ์นดํ
๊ณ ๋ฆฌ ํํฐ
+- ๊ฒ์๊ธ ๋ฆฌ์คํธ
+- ์ธ๊ธฐ ๊ฒ์๊ธ(โ๋ง์ง ์ถ์ฒํฉ๋๋ค!โ ๋ฑ)
+- ์ข์์ ๊ธฐ๋ฅ
+- ์กฐํ์ ์ฆ๊ฐ
+- ๊ฒ์๊ธ ๋ณธ๋ฌธ + ์ด๋ฏธ์ง ํ์
+- ์ง๋ ๊ธฐ๋ฐ ์์น ํ์
+- ๋๊ธ ๊ธฐ๋ฅ (CRUD)
+- ๋ณธ์ธ ๊ธ ์์ /์ญ์ ๋ฉ๋ด
+
+
+| ๋๋ค์ํ ํผ๋ | ๋๋ค์ํ ๊ฒ์๋ฌผ |
+|----------|----------|
+|||
+
+
+
+### [๋๋ค์ํ ๊ธ์ฐ๊ธฐ]
+- ๊ฒ์๊ธ ์ฃผ์ ์ ํ Ex)๋๋ค์ ๋ณด, ์ด์๊ณผํจ๊ป, ์์
+- ์ง๋ฌธ๊ธ ์ฌ๋ถ ์ฒดํฌ๋ฐ์ค
+- ์ฃผ์ ํด๋ฆญ ์ ์ฆ์ ์ ํ & ํ์
๋ซํ
+- ์ ํ๋ ์ฃผ์ ๋ ๊ธ์ฐ๊ธฐ ์๋จ์ ํ์
+
+
+| ๋๋ค์ํ ๊ธ์ฐ๊ธฐ | ์นดํ
๊ณ ๋ฆฌ ํ์
|
+|----------|----------|
+|||
+
+
+
+### [๋ชจ์ ๋ง๋ค๊ธฐ]
+- ๋ํ ์ด๋ฏธ์ง ์
๋ก๋
+- ๋ํ ๋๋ค ํ์
+- ๋ชจ์ ์ด๋ฆ ์
๋ ฅ
+- ๊ณต๊ฐ๋ฒ์ ์ค์ (PUBLIC, PRIVATE)
+- ๊ฐ์
์ ์ฑ
์ ํ(OPEN, APPROVAL, CLOSED)
+
+
+| ๋ชจ์ ๋ง๋ค๊ธฐ | ๋ชจ์ ์ธ๋ค์ผ |
+|----------|----------|
+|||
+
+
+
+
+
+### [๋๋ค๋ชจ์ ์์ธ ํ๋ฉด]
+- ๋ชจ์ ๋ํ ์ ๋ณด
+- ๊ด๋ฆฌ๊ธฐ๋ฅ
+- ๋ชจ์ ํํด ๊ธฐ๋ฅ
+- ์ด๋ฒคํธ ๋ฆฌ์คํธ ํ์
+- ๊ฐ์
์ ์ฑ
์ ํ(OPEN, APPROVAL, CLOSED)
+- ๋ชจ์ ๋ง๋ค๊ธฐ
+- ์ง๋์์ ์์น ์ ํ ๊ธฐ๋ฅ
+
+
+| ๋ชจ์ ๋ง๋ค๊ธฐ | ๋ชจ์ ์ธ๋ค์ผ | ๋ชจ์ ์ธ๋ค์ผ |
+|----------|----------|----------|
+||||
+
+
+
+### [๊ฑฐ๋ ์ฑํ
]
+- ์ค์๊ฐ ๋ฉ์์ง ์ ์ก
+- ํ๋งค์ ๋งค๋์จ๋ ํ์
+- ์ํ ์ ๋ณด ๊ณ ์ ์์ญ
+- ์ฝ์์ก๊ธฐ ๊ธฐ๋ฅ(ํต์ฌ)
+- ์ฝ์์ก๊ธฐ ์
๋ ฅ ํผ ๊ธฐ๋ฅ
+- ์ฝ์ ์ ์ ๋ฉ์์ง ์๋ ์์ฑ
+- ์ฝ์ ์ทจ์ ๊ธฐ๋ฅ
+- ๋น๊ทผํ์ด(์นด์นด์คํ์ด ์ฐ๋)
+
+
+| ์ฑํ
| ์ฝ์์ก๊ธฐ | ๋ชจ์ ์ธ๋ค์ผ |
+|----------|----------|----------|
+||||
+
+| ๊ฑฐ๋1 | ๊ฑฐ๋2 |
+|----------|----------|
+|||![login]
+
+| ๊ฑฐ๋3 | ๊ฑฐ๋4 |
+|----------|----------|
+|||![login]
+
+| ๊ฑฐ๋5 |
+|----------|
+||
+
+
+
+### [๋๋ค์ง๋]
+- ๋๋ค์ง๋ ์ถ์ฒ ์๋น์ค
+- KAKAO MAP ์ฌ์ฉ
+
+| ๋๋ค์ง๋ |
+|----------|
+||
+
+
+
+### [ํ๋กํ ํ๋ฉด]
+- ํ๋กํ ์ด๋ฏธ์ง / ๋๋ค์ / ๊ณ์ ์ ๋ณด ํ์
+- ๋งค๋์จ๋ ๊ทธ๋ํ ์๊ฐํ
+
+| ํ๋กํ ํ๋ฉด |
+|----------|
+||
+
+
+
+### [๋์ ํ๋งค๋ด์ญ]
+- ํ๋งค์ค / ์์ฝ์ค / ๊ฑฐ๋์๋ฃ ํญ ๊ตฌ๋ถ
+- ์ํ ์์ / ์ญ์ / ๊ฑฐ๋์๋ฃ ์ฒ๋ฆฌ ๋ฒํผ ์ ๊ณต
+- ์ฑํ
์ยท๊ด์ฌ์ยท์กฐํ์ ํ์
+
+| ๋์ ํ๋งค๋ด์ญ |
+|----------|
+||
+
+
+
+### [๋์ ๊ตฌ๋งค๋ด์ญ]
+- ๋ด๊ฐ ๊ตฌ๋งคํ ์ํ ๋ฆฌ์คํธ ํ์
+- ๋ฆฌ๋ทฐ ์์ฑ ๋ฒํผ ์ ๊ณต
+- ๋ฆฌ๋ทฐ ์์ฑ ์ ๋ณ์ + ํ๊ธฐ ์
๋ ฅ ๊ฐ๋ฅ
+
+| ๋์ ๊ตฌ๋งค๋ด์ญ | ๋์ ๊ตฌ๋งค๋ด์ญ ํ๊ธฐ์์ฑ | ๋์ ๊ตฌ๋งค๋ด์ญ ํ๊ธฐ์๋ฃ |
+|----------|----------|----------|
+||||
+
+
+
+### [๋์ ๊ด์ฌ๋ชฉ๋ก]
+- ๊ด์ฌ ๋ฑ๋กํ ์ํ ๋ชฉ๋ก ํ์
+- ์ํ ์์ธ ํ์ด์ง๋ก ์ด๋ ๊ฐ๋ฅ
+- ์๊ฐ ์ ๋ณด(์: 78์ผ ์ ) ํ์
+
+| ๋์ ๊ด์ฌ๋ชฉ๋ก |
+|----------|
+||
+
+
+
+### [๋์ ์ฝ์ํ๋ฉด]
+- ์บ๋ฆฐ๋ ๊ธฐ๋ฐ ์ผ์ ํ์ธ
+- ์ฝ์ ์ํ๋ณ ํํฐ(์ ์ฒด / ์ ์๋จ / ์๋ฝ๋จ / ๊ฑฐ์ ๋จ / ์ทจ์๋จ)
+- ์ฝ์ ์์ธ ์ ๋ณด ์ ๊ณต
+
+| ๋์ ์ฝ์ํ๋ฉด |
+|----------|
+||
+
+
+
+## ์ด๋ ค์ ๋์
+
+### ๐์ด๋๊ตญ
+
+### ๋ฌธ์ ์ํฉ
+- Chrome, Android์์๋ ์์น ๊ถํ ์์ฒญ์ด ์ ๋์ํจ
+- ํ์ง๋ง iPhone Safari์์๋ ํ์ฌ์์น๋ก ์ฐพ๊ธฐ ๋ฒํผ์ ๋๋ฌ๋ ์๋ฌด ๋ฐ์ ์์
+- ์ฝ์์ "GPS ์ ๊ทผ์ด ๊ฑฐ๋ถ๋์์ต๋๋ค.", "์ฃผ์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."๋ง ์ถ๋ ฅ๋จ
+- Kakao API์์ 401 Unauthorized ์ค๋ฅ ๋ฐ์
+
+### ์์ธ
+ - iOS Safari๋ HTTPS ํ๊ฒฝ์ด ์๋ ๊ฒฝ์ฐ navigator.geolocation ์ ๊ทผ์ ์ฐจ๋จํฉ๋๋ค.
+
+
+### ํด๊ฒฐ๋ฐฉ๋ฒ
+ - LocalTunnel ์ฌ์ฉํ๊ธฐ
+
+### ๐ํ์ฌํธ
+-
+### ๐์ด์์ค
+
+### ๋ฌธ์ ์ํฉ
+ 1. GitHub Actions ์ํฌํ๋ก์ฐ๋ ์ ์์ ์ผ๋ก ์ฑ๊ณต, ํ์ง๋ง ๋ฐฐํฌ ์๋ฃ ํ ํผ๋ธ๋ฆญ IP:8080 ์ ์ ๋ถ๊ฐ
+ 2. ๋ฐฐํฌ ์๋ฃ ํ ์๋ฒ์์ ์ ํ๋ฆฌ์ผ์ด์
์ด ์ ์ ์คํ๋์ง ์์
+ 3. SMS ๋ฑ ์ธ๋ถ API ์ฐ๋ ๊ธฐ๋ฅ ์ถ๊ฐ ๋๋ ํธ์ถ ์ ๋ฐํ์ ์๋ฌ ๋ฐ์
+
+### ์์ธ
+ 1. AWS ๋ณด์ ๊ทธ๋ฃน์ 8080 ํฌํธ ์ธ๋ฐ์ด๋ ๊ท์น ๋๋ฝ
+ 2. ๋ก์ปฌ ํ๊ฒฝ ๊ธฐ์ค ์ค์ (application.yml)ํ์ผ์ด ๊ทธ๋๋ก ๋ฐฐํฌ๋์ด ์๋ฒ ํ๊ฒฝ๊ณผ ๋ถ์ผ์น
+ 3. ๋ฏผ๊ฐ ์ ๋ณด(API Key, Secret)๊ฐ ์๋ฒ ํ๊ฒฝ ๋ณ์๋ก ์ค์ ๋์ง ์์. ๋ก์ปฌ ์ธํ
๋ฆฌ์ ์ด ํ๋ก์ ํธ ํ๊ฒฝ๋ณ์ ์ค์ ์๋ง ์
๋ ฅ ํด๋์.
+
+### ํด๊ฒฐ๋ฐฉ๋ฒ
+ 1. AWS ๋ณด์ ๊ทธ๋ฃน์ 8080 ํฌํธ ์คํ
+ 2. ๋ฐฐํฌ ์๋ฒ ํ๊ฒฝ์ ๋ง์ถ application.yml ์ค์ ๋ฐ ํ๊ฒฝ(๋ก์ปฌ,๋ฐฐํฌ)๋ณ ํ๋กํ์ผ ์ ์ฉ
+ 3. ์๋ฒ ํ๊ฒฝ ๋ณ์ ๋ฑ๋ก ๋ฐ GitHub Actions์์ ํ๊ฒฝ ๋ณ์ ์ฃผ์
๋ฐฉ์์ผ๋ก ๊ด๋ฆฌ
+
+## ์๊ฐ
+
+### ๐์ด๋๊ตญ
+- ใ
ใ
ใ
+
+### ๐์ด์์ค
+- CI/CD ํ๊ฒฝ์ ์ง์ ๊ตฌ์ถํ๋ฉฐ ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌ์ ์ค์์ฑ์ ๋งค์ฐ ํฌ๊ฒ ์ฒด๊ฐํ๋ค. ์ด๋ฐ๋ถํฐ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ํ์๋ผ๋ฆฌ ๊ณต์ ํ๋ฉฐ ๊ด๋ฆฌํด์ผํ๋ค๋ ๋ง์ ๋จธ๋ฆฌ๊ฐ ์๋ ํผ๋ถ๋ก ๋๋ผ๊ฒ ๋์๋ค. ๋ฐฐํฌ ๊ณผ์ ์์๋ ์ฌ์ํ ์ค์ ํ๋๊ฐ ์๋น์ค ์ ์ฒด์ ์ํฅ์ ์ค ๋์ค์ ์ด๋์๋ถํฐ ๊ณ ์ณ์ผํ ์ง ๊ฐ๋ ์ ์ค๊ณ ๋ง๋งํ๋ค. ์์ผ๋ก ๊ผผ๊ผผํ๊ฒ ํ๋ํ๋ ์ค์ ํ๊ณ ์ ๊ฒํ๋ฉฐ ๋์ด๊ฐ์.
+- ์ค์๊ฐ ์ฑํ
๊ธฐ๋ฅ ๊ตฌํ + ์ํํธ ์ญ์ ๋ก์ง ์ค๊ณ๋ฅผ ํตํด ์ผ์์ ์ผ๋ก ์ฌ์ฉํ๋ ์ฑํ
์๋น์ค์ ์ ๊ตํจ์ ์ฒด๊ฐํ๋ค. ์ญ์ ์์ ๊ธฐ์ค ๋ฐ์ดํฐ ์กฐํ, ์ฝ์ ์ฒ๋ฆฌ ๋ฑ ์ํ ๊ด๋ฆฌ ๋ก์ง์ ๊ตฌํํ๋ฉด์ ๋ณต์กํ ์ฟผ๋ฆฌ ์ค๊ณ์ ๋ฐฑ์๋ ๋ก์ง์ ๋ํ
์ผํจ์ ๊ฒฝํํ๋ค. ๋ฟ๋ฏํ๋ค.
+
+### ๐ํ์ฌํธ
+- ใ
ใ
ใ
diff --git a/src/main/java/com/mrokga/carrot_server/.DS_Store b/src/main/java/com/mrokga/carrot_server/.DS_Store
index 8725dc6..fe5e08c 100644
Binary files a/src/main/java/com/mrokga/carrot_server/.DS_Store and b/src/main/java/com/mrokga/carrot_server/.DS_Store differ
diff --git a/src/main/java/com/mrokga/carrot_server/auth/controller/AuthController.java b/src/main/java/com/mrokga/carrot_server/auth/controller/AuthController.java
index 44096f5..33cbfda 100644
--- a/src/main/java/com/mrokga/carrot_server/auth/controller/AuthController.java
+++ b/src/main/java/com/mrokga/carrot_server/auth/controller/AuthController.java
@@ -34,6 +34,11 @@ public class AuthController {
private final AuthService authService;
private final UserService userService;
+ /**
+ * ํด๋ํฐ ๋ฒํธ๋ก ์ธ์ฆ๋ฒํธ SMS๋ฅผ ๋ฐ์กํ๋ api
+ * @param phoneNumber ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฐ์ ํด๋ํฐ ๋ฒํธ
+ * @return ์ฑ๊ณต ์๋ต DTO
+ */
@PostMapping("/send")
@Operation(summary = "์ธ์ฆ๋ฒํธ sms ๋ฐ์ก", description = "์ฌ์ฉ์ ํด๋ํฐ ๋ฒํธ๋ก ์ธ์ฆ๋ฒํธ sms ๋ฐ์ก")
@ApiResponses(value = {
@@ -45,6 +50,12 @@ public ResponseEntity> sendSms(@Parameter(description = "
return ResponseEntity.ok(ApiResponseDto.success(HttpStatus.OK.value(), "success"));
}
+ /**
+ * ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ธ์ฆ๋ฒํธ๋ฅผ ๊ฒ์ฆํ๋ api
+ * Redis์ ์ ์ฅ๋ ์ธ์ฆ๋ฒํธ์ ๋น๊ตํ์ฌ ๊ฒฐ๊ณผ๋ฅผ return
+ * @param request ํด๋ํฐ๋ฒํธ์ ์ธ์ฆ๋ฒํธ
+ * @return ์ธ์ฆ ๊ฒฐ๊ณผ์ ๋ฐ๋ฅธ ์๋ต (200 OK, 400 BAD_REQUEST, 410 GONE)
+ */
@PostMapping("/verify")
@Operation(summary = "์ธ์ฆ๋ฒํธ ์ธ์ฆ", description = "์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ธ์ฆ๋ฒํธ์ redis์ ์ ์ฅ๋ ๊ฐ ๋น๊ต")
@ApiResponses(value = {
@@ -75,6 +86,11 @@ public ResponseEntity> verifyCode(@RequestBody VerifyCodeRe
};
}
+ /**
+ * ๋๋ค์ ์ค๋ณต ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ๋ api
+ * @param nickname ๊ฒ์ฌํ ๋๋ค์
+ * @return ์ค๋ณต ์ฌ๋ถ์ ๋ฐ๋ฅธ ์๋ต (์ค๋ณต ์ 400 BAD_REQUEST, ์ฌ์ฉ ๊ฐ๋ฅ ์ 200 OK)
+ */
@PostMapping("/validate-nickname")
@Operation(summary = "๋๋ค์ ์ค๋ณต๊ฒ์ฌ", description = "์ฌ์ฉ์๊ฐ ์
๋ ฅํ ๋๋ค์์ด ์ค๋ณต๋์๋์ง ๊ฒ์ฌ")
@ApiResponses(value = {
@@ -100,6 +116,11 @@ public ResponseEntity> validateNickname(@Parameter(descri
}
+ /**
+ * ์๋ก์ด ์ฌ์ฉ์ ํ์๊ฐ์
์ ์ฒ๋ฆฌํ๋ api
+ * @param request ํ์๊ฐ์
์ ๋ณด
+ * @return ์์ฑ๋ user entity ํฌํจ๋ ์๋ต DTO
+ */
@PostMapping("/signup")
@Operation(summary = "ํ์๊ฐ์
์์ฒญ", description = "ํ์๊ฐ์
์์ฒญ")
@ApiResponses(value = {
@@ -115,6 +136,11 @@ public ResponseEntity> signup(@RequestBody SignupRequestDto
return ResponseEntity.ok(ApiResponseDto.success(HttpStatus.OK.value(), "success", user));
}
+ /**
+ * ์ธ์ฆ๋ฒํธ SMS๋ฅผ ์ฌ๋ฐ์กํ๋ api
+ * @param phoneNumber ์ฌ๋ฐ์ก์ ์์ฒญํ ํด๋ํฐ ๋ฒํธ
+ * @return ์ฑ๊ณต ์๋ต DTO
+ */
@PostMapping("/resend")
@Operation(summary = "์ธ์ฆ๋ฒํธ sms ์ฌ๋ฐ์ก", description = "์ฌ์ฉ์ ํด๋ํฐ ๋ฒํธ๋ก ์ธ์ฆ๋ฒํธ sms ์ฌ๋ฐ์ก")
@ApiResponses(value = {
@@ -126,6 +152,12 @@ public ResponseEntity> resendSms(@Parameter(description = "
return ResponseEntity.ok(ApiResponseDto.success(HttpStatus.OK.value(), "success"));
}
+ /**
+ * ๋ก๊ทธ์ธ ์ฒ๋ฆฌ api
+ * ์ธ์ฆ ์ฑ๊ณต ์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์กฐํํ๊ณ jwt๋ฅผ ๋ฐ๊ธํ ๋ค return
+ * @param request ์ ํ๋ฒํธ ๋ฐ ์
๋ ฅ๋ ์ธ์ฆ๋ฒํธ
+ * @return ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ํ ํฐ๊ณผ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํฌํจํ ์๋ต DTO, ์คํจ ์ ์๋ฌ ์๋ต
+ */
@PostMapping("/login")
@Operation(summary = "๋ก๊ทธ์ธ ์์ฒญ", description = "๋ก๊ทธ์ธ ์์ฒญ")
@ApiResponses(value = {
diff --git a/src/main/java/com/mrokga/carrot_server/auth/service/AuthService.java b/src/main/java/com/mrokga/carrot_server/auth/service/AuthService.java
index ab1f05b..52496cc 100644
--- a/src/main/java/com/mrokga/carrot_server/auth/service/AuthService.java
+++ b/src/main/java/com/mrokga/carrot_server/auth/service/AuthService.java
@@ -35,9 +35,14 @@ public class AuthService {
private static final String ACCESS_TOKEN_PREFIX = "access_token:";
private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
+ // SMS ๋ฐ์ ๋ฒํธ
@Value("${sms.sender}")
private String sender;
+ /**
+ * ์ง์ ๋ ๋ฐ์ ๋ฒํธ๋ก ์ธ์ฆ๋ฒํธ SMS๋ฅผ ์ ์กํ๊ณ , ์ธ์ฆ๋ฒํธ๋ฅผ Redis์ ์ ์ฅ
+ * @param phoneNumber ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฐ์ ์ ํ๋ฒํธ
+ */
public void sendSms(String phoneNumber) {
String code = generateCode();
@@ -53,24 +58,35 @@ public void sendSms(String phoneNumber) {
try {
messageService.send(message);
} catch (NurigoMessageNotReceivedException e) {
+ redisTemplate.delete(key);
log.info("failed message list = {}", e.getFailedMessageList());
log.info("exception = {}", e.getMessage());
} catch (Exception e) {
+ redisTemplate.delete(key);
log.info("exception = {}", e.getMessage());
}
}
+ /**
+ * ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ธ์ฆ๋ฒํธ์ ์ ํจ์ฑ ๊ฒ์ฆ
+ * @param phoneNumber ์ ํ๋ฒํธ (Redis Key ์กฐํ์ฉ)
+ * @param code ์ฌ์ฉ์ ์
๋ ฅ ์ธ์ฆ๋ฒํธ
+ * @return ์ธ์ฆ ๊ฒฐ๊ณผ {@link VerifyCodeResult}
+ */
public VerifyCodeResult verifyCode(String phoneNumber, String code) {
log.info("[AuthService] verifyCode starts");
String key = SMS_PREFIX + phoneNumber;
+ // 1. Redis์์ ํด๋น ํด๋ํฐ๋ฒํธ๋ก ์ ์ฅ๋ ์ธ์ฆ๋ฒํธ ์กฐํ
String saved = redisTemplate.opsForValue().get(key);
log.info("saved = {}", saved);
+ // 2. ์ ์ฅ๋ ์ธ์ฆ๋ฒํธ๊ฐ ์๋ ๊ฒฝ์ฐ (๋ง๋ฃ๋ก ํ๋จ)
if (saved == null) {
return VerifyCodeResult.EXPIRED;
}
+ // 3. Redis์์ ์กฐํํ ์ธ์ฆ๋ฒํธ์ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ธ์ฆ๋ฒํธ๊ฐ ์ผ์นํ์ง ์๋ ๊ฒฝ์ฐ
if(!saved.equals(code)) {
return VerifyCodeResult.MISMATCH;
}
@@ -91,6 +107,10 @@ public static String generateCode() {
return String.format("%06d", number);
}
+ /**
+ * ๊ธฐ์กด ์ธ์ฆ๋ฒํธ๋ฅผ ์ญ์ ํ๊ณ ์๋ก์ด ์ธ์ฆ๋ฒํธ SMS๋ฅผ ์ฌ์ ์ก
+ * @param phoneNumber ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฐ์ ์ ํ๋ฒํธ
+ */
public void resendSms(String phoneNumber) {
redisTemplate.delete(SMS_PREFIX + phoneNumber);
@@ -98,6 +118,11 @@ public void resendSms(String phoneNumber) {
sendSms(phoneNumber);
}
+ /**
+ * Access Token๊ณผ Refresh Token์ ๋ฐ๊ธํ๊ณ , Refresh Token์ Redis์ ์ ์ฅ ํ token์ด ๋ด๊ธด DTO๋ฅผ ๋ฐํ
+ * @param user ํ ํฐ์ ๋ฐ๊ธ๋ฐ์ ์ ์
+ * @return ๋ฐ๊ธ๋ ํ ํฐ ์ ๋ณด๊ฐ ๋ด๊ธด DTO
+ */
public TokenResponseDto issueAndReturnTokens(User user) {
String accessToken = tokenProvider.generateAccessToken(user);
String refreshToken = tokenProvider.generateRefreshToken(user);
@@ -111,17 +136,30 @@ public TokenResponseDto issueAndReturnTokens(User user) {
.build();
}
+ /**
+ * Refresh Token์ ์ฌ์ฉํ์ฌ Access Token๊ณผ Refresh Token ๊ฐฑ์
+ * @param user ํ ํฐ์ ๊ฐฑ์ ํ ์ ์
+ * @param oldRefreshToken ๊ฐฑ์ ์์ฒญ ์ ์ฌ์ฉ๋ ๊ธฐ์กด Refresh Token
+ * @return ์๋กญ๊ฒ ๋ฐ๊ธ๋ ํ ํฐ ์ ๋ณด๊ฐ ๋ด๊ธด DTO
+ */
public TokenResponseDto renew(User user, String oldRefreshToken) {
String key = REFRESH_TOKEN_PREFIX + user.getId();
String storedRefreshToken = redisTemplate.opsForValue().get(key);
+ // 1. ์ ์ฅ๋ ํ ํฐ์ด ์๊ฑฐ๋, ์์ฒญ๋ ํ ํฐ๊ณผ ์ ์ฅ๋ ํ ํฐ์ด ์ผ์นํ์ง ์๊ฑฐ๋, ํ ํฐ ์์ฒด์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์คํจํ ๊ฒฝ์ฐ
if(storedRefreshToken == null || !storedRefreshToken.equals(oldRefreshToken) || !tokenProvider.validToken(oldRefreshToken)) {
throw new RuntimeException("INVALID REFRESH TOKEN");
}
+ // 2. ์ ํจํ ๊ฒฝ์ฐ, ์๋ก์ด ํ ํฐ ๋ฐ๊ธ ๋ฐ ์ ์ฅ
return issueAndReturnTokens(user);
}
+ /**
+ * Refresh Token์ Redis์ ์ ์ฅ
+ * @param user
+ * @param refreshToken ์ ์ฅํ Refresh Token
+ */
public void saveRefreshToken(User user, String refreshToken) {
String key = REFRESH_TOKEN_PREFIX + user.getId();
diff --git a/src/main/java/com/mrokga/carrot_server/chat/controller/ChatMessageController.java b/src/main/java/com/mrokga/carrot_server/chat/controller/ChatMessageController.java
index 56bb41e..a01bcc5 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/controller/ChatMessageController.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/controller/ChatMessageController.java
@@ -51,7 +51,7 @@ public List getMessages(@PathVariable Integer roomId) {
/**
- * โ
WebSocket/STOMP ์ฉ Controller
+ * WebSocket/STOMP ์ฉ Controller
* - @RestController ๋์ @Controller ์ฌ์ฉ
* - /pub/chat/message ๋ก ๋ฐํ๋ STOMP ๋ฉ์์ง๋ฅผ ์์
*/
diff --git a/src/main/java/com/mrokga/carrot_server/chat/entity/ChatRoom.java b/src/main/java/com/mrokga/carrot_server/chat/entity/ChatRoom.java
index 1e70f53..1322201 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/entity/ChatRoom.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/entity/ChatRoom.java
@@ -20,14 +20,17 @@ public class ChatRoom {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
+ // ์ํ
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
+ // ํ๋งค์
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "seller_id", nullable = false)
private User seller;
+ // ๊ตฌ๋งค ํฌ๋ง์
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "buyer_id", nullable = false)
private User buyer;
diff --git a/src/main/java/com/mrokga/carrot_server/chat/entity/QuickReply.java b/src/main/java/com/mrokga/carrot_server/chat/entity/QuickReply.java
index 4350f18..021d390 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/entity/QuickReply.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/entity/QuickReply.java
@@ -18,6 +18,7 @@ public class QuickReply {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
+ // ์ ์ ์์ด๋
@Column(name = "user_id", nullable = false)
private Integer userId;
diff --git a/src/main/java/com/mrokga/carrot_server/chat/repository/AppointmentRepository.java b/src/main/java/com/mrokga/carrot_server/chat/repository/AppointmentRepository.java
index 77862bb..44c43d0 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/repository/AppointmentRepository.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/repository/AppointmentRepository.java
@@ -16,7 +16,7 @@ Optional findByChatRoom_IdAndStatusIn(Integer chatRoomId,
Optional findByChatRoom_Id(Integer chatRoomId);
- // โ ๋์ ์ฝ์(์ฑํ
๋ฐฉ ์ฐธ์ฌ์์ด๊ฑฐ๋ ์ ์์=๋). ์ํ ํํฐ optional
+ // ๋์ ์ฝ์(์ฑํ
๋ฐฉ ์ฐธ์ฌ์์ด๊ฑฐ๋ ์ ์์=๋). ์ํ ํํฐ optional
@Query("""
SELECT a
FROM Appointment a
diff --git a/src/main/java/com/mrokga/carrot_server/chat/service/AppointmentService.java b/src/main/java/com/mrokga/carrot_server/chat/service/AppointmentService.java
index c48a2f0..5f140c4 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/service/AppointmentService.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/service/AppointmentService.java
@@ -18,6 +18,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+// ์ฑํ
๋ฐฉ ๋ด์์ ์ด๋ค์ง๋ ๊ธฐ๋ฅ
+// ์ฑํ
๋ฐฉ ์กฐํ ๋ฐ ์ด๋์์ ๋ณธ์ธ ์ธ์ฆ์ ํตํด ์ฑํ
๋ฐฉ ์ฐธ์ฌ์ ์ฌ๋ถ๋ฅผ ๊ฐ๋ ค๋จ๊ธฐ์
+// ์ฌ๊ธฐ์๋ ๋ฐ๋ก ์ฑํ
์ฐธ์ฌ์ ์ฌ๋ถ๋ฅผ ๊ฐ๋ฆฌ์ง ์์. (์ฆ ์ฝ์ CRUD ๊ถํ ์ฌ๋ถ ๊ฐ๋ฆฌ์ง ์์. ์๋ฏธ ์กฐํ ๋ฐ ์ด๋ํ์ผ๋ฉด ์ฝ์์ ๋ํ ๊ถํ๋ ๋น์ฐํ ๋ถ์ฌ)
@Service
@RequiredArgsConstructor
public class AppointmentService {
@@ -41,6 +44,7 @@ private AppointmentResponseDto toDto(Appointment appointment) {
.build();
}
+ // ์ฝ์ ์์ฑ
@Transactional
public AppointmentResponseDto create(Integer roomId, AppointmentRequestDto dto){
ChatRoom room = chatRoomRepository.findById(roomId)
@@ -49,7 +53,7 @@ public AppointmentResponseDto create(Integer roomId, AppointmentRequestDto dto){
User proposer = userRepository.findById(dto.getProposerId())
.orElseThrow(() -> new EntityNotFoundException("AppointmentService.create(): ์ ์ ์์"));
- // โ
์ค๋ณต ์ฝ์ ๋ฐฉ์ง: ํด๋น ์ฑํ
๋ฐฉ์ PENDING/ACCEPTED ์ํ ์ฝ์์ด ์์ผ๋ฉด ์์ฑ ๋ถ๊ฐ
+ // ์ค๋ณต ์ฝ์ ๋ฐฉ์ง: ํด๋น ์ฑํ
๋ฐฉ์ PENDING/ACCEPTED ์ํ ์ฝ์์ด ์์ผ๋ฉด ์์ฑ ๋ถ๊ฐ
appointmentRepository.findByChatRoom_IdAndStatusIn(
roomId, java.util.List.of(AppointmentStatus.PENDING, AppointmentStatus.ACCEPTED)
).ifPresent(a -> {
@@ -66,7 +70,7 @@ public AppointmentResponseDto create(Integer roomId, AppointmentRequestDto dto){
Appointment saved = appointmentRepository.save(appointment);
- // โ
์์คํ
๋ฉ์์ง
+ // ์์คํ
๋ฉ์์ง ์์ฑ ํ ์ ์ก
String content = String.format("%s๋์ด %s %s์ ๋ง๋์๊ณ ์ฝ์์ ์ ์ํ์ต๋๋ค.",
proposer.getNickname(),
dto.getMeetingTime().toLocalDate(),
@@ -76,6 +80,7 @@ public AppointmentResponseDto create(Integer roomId, AppointmentRequestDto dto){
return toDto(saved);
}
+ // ์ฝ์ ์๋ฝ
@Transactional
public AppointmentResponseDto acceptAppointment(Integer appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
@@ -98,13 +103,14 @@ public AppointmentResponseDto acceptAppointment(Integer appointmentId) {
productService.changeStatus(dto);
- // โ
์์คํ
๋ฉ์์ง
+ // ์์คํ
๋ฉ์์ง ์์ฑ ํ ์ ์ก
String content = "์ฝ์์ด ์๋ฝ๋์์ต๋๋ค. ์ํ ์ํ๊ฐ ์์ฝ์ค์ผ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค.";
chatMessageService.sendSystemMessage(room, content);
return toDto(appointment);
}
+ // ์ฝ์ ๊ฑฐ์
@Transactional
public AppointmentResponseDto rejectAppointment(Integer appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
@@ -112,13 +118,14 @@ public AppointmentResponseDto rejectAppointment(Integer appointmentId) {
appointment.setStatus(AppointmentStatus.REJECTED);
- // โ
์์คํ
๋ฉ์์ง
+ // ์์คํ
๋ฉ์์ง ์์ฑ ํ ์ ์ก
String content = "์ฝ์์ด ๊ฑฐ์ ๋์์ต๋๋ค.";
chatMessageService.sendSystemMessage(appointment.getChatRoom(), content);
return toDto(appointment);
}
+ // ์ฝ์ ์ทจ์
@Transactional
public AppointmentResponseDto cancelAppointment(Integer appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
@@ -129,7 +136,7 @@ public AppointmentResponseDto cancelAppointment(Integer appointmentId) {
ChatRoom room = appointment.getChatRoom();
Product product = room.getProduct();
- // โ
changeStatus ํธ์ถ (Transaction๊น์ง ์ ๋ฆฌ)
+ // changeStatus ํธ์ถ (Transaction๊น์ง ์ ๋ฆฌ)
ChangeStatusRequestDto dto = ChangeStatusRequestDto.builder()
.productId(product.getId())
.sellerId(room.getSeller().getId())
@@ -140,13 +147,14 @@ public AppointmentResponseDto cancelAppointment(Integer appointmentId) {
productService.changeStatus(dto);
- // โ
์์คํ
๋ฉ์์ง
+ // ์์คํ
๋ฉ์์ง ์์ฑ ํ ์ ์ก
String content = "์ฝ์์ด ์ทจ์๋์์ต๋๋ค. ์ํ ์ํ๊ฐ ํ๋งค์ค์ผ๋ก ๋์๊ฐ๋๋ค.";
chatMessageService.sendSystemMessage(room, content);
return toDto(appointment);
}
+ // ์ฝ์ ์กฐํ
@Transactional(readOnly = true)
public AppointmentResponseDto getAppointmentByChatRoomId(Integer roomId) {
Appointment appointment = appointmentRepository.findByChatRoom_Id(roomId)
diff --git a/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageReadService.java b/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageReadService.java
index 271878d..2fcc937 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageReadService.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageReadService.java
@@ -18,9 +18,13 @@ public class ChatMessageReadService {
private final ChatRoomRepository chatRoomRepository;
private final ChatMessageRepository chatMessageRepository;
+ // ์ฝ์ ์ฒ๋ฆฌ
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void markAsRead(Integer roomId, Integer messageId, Integer userId) {
- // ๊ถํ/๊ฒ์ฆ
+ /* ๊ถํ ํ์ธ
+ 1. ๋ค๋ฅธ ๋ฐฉ ๋ฉ์์ง ์ฝ์ ์ฒ๋ฆฌ ๋ถ๊ฐ
+ 2. ํด๋น ์ฑํ
๋ฐฉ ์ฐธ์ฌ์ ์ฌ๋ถ ํ์ธ
+ */
ChatRoom room = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new IllegalArgumentException("์ฑํ
๋ฐฉ ์์"));
diff --git a/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageService.java b/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageService.java
index 9587e95..6c2d9cd 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageService.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/service/ChatMessageService.java
@@ -25,7 +25,7 @@ public class ChatMessageService {
private final ChatRoomRepository chatRoomRepository;
private final ChatMessageRepository chatMessageRepository;
private final ChatMessageReadService chatMessageReadService;
- private final SimpMessageSendingOperations messagingTemplate; // โ
์ค์๊ฐ ์ ์ก ๊ธฐ๋ฅ ์ถ๊ฐ
+ private final SimpMessageSendingOperations messagingTemplate; // ์ค์๊ฐ ์ ์ก ๊ธฐ๋ฅ ์ถ๊ฐ
// ๋ฉ์ธ์ง ๊ฐ์ฒด DTO ํํ๋ก ๋ณํ ๋ฉ์๋
private MessageResponseDto toResponse(ChatMessage message){
@@ -37,7 +37,11 @@ private MessageResponseDto toResponse(ChatMessage message){
dto.setMessage(message.getMessage());
dto.setCreatedAt(message.getCreatedAt());
- // ๋ถ๋ชจ(๋ต์ฅ ๋์) ์์ผ๋ฉด ์์ฝ ์ฑ์ฐ๊ธฐ
+ /* ๋ถ๋ชจ(๋ต์ฅ ๋์) ์์ผ๋ฉด ์์ฝ ์ฑ์ฐ๊ธฐ
+ ์๋ฅผ ๋ค์ด ์๋๋ฐฉ ๋ฉ์ธ์ง์ ๋ํ ๋ต์ฅ ๋ฉ์ธ์ง์ผ ๊ฒฝ์ฐ,
+ ์๋๋ฐฉ ๋ฉ์ธ์ง(๋ถ๋ชจ ๋ฉ์ธ์ง) ์์ฝ์นธ์ด ๋ต์ฅ ๋ฉ์์ง(์์ ๋ฉ์ธ์ง) ์์ ์๋
+ UI๋ฅผ ์ํ ๋ถ๋ชจ ๋ฉ์ธ์ง ์์ฝ ์์ฑ.
+ */
ChatMessage p = message.getParentMessage();
if (p != null) {
dto.setReplyToMessageId(p.getId());
@@ -52,19 +56,19 @@ private MessageResponseDto toResponse(ChatMessage message){
return dto;
}
- // ๋ต์ฅ ๋ฉ์ธ์ง, ๋ถ๋ชจ ๋ฉ์ธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ 50์
+ // ๋ต์ฅ ๋ฉ์ธ์ง, ๋ถ๋ชจ ๋ฉ์ธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์์ฝ 50์
private String abbreviate(String s, int max) {
if (s == null) return null;
return s.length() <= max ? s : s.substring(0, max) + "โฆ";
}
- // โ
๊ธฐ์กด sendMessage: REST API์ฉ (๋ฉ์์ง ์ ์ฅ๋ง ์ํ)
+ // ๊ธฐ์กด sendMessage: REST API์ฉ (๋ฉ์์ง ์ ์ฅ๋ง ์ํ)
@Transactional
public MessageResponseDto sendMessage(MessageRequestDto dto, Integer senderId){
return saveMessage(dto, senderId);
}
- // โ
์๋ก์ด sendMessageAndBroadcast: WebSocket ์ ์ฉ (์ ์ฅ + ์ค์๊ฐ ์ ์ก)
+ // ์๋ก์ด sendMessageAndBroadcast: WebSocket ์ ์ฉ (์ ์ฅ + ์ค์๊ฐ ์ ์ก)
@Transactional
public void sendMessageAndBroadcast(MessageRequestDto dto, Integer senderId){
MessageResponseDto response = saveMessage(dto, senderId);
@@ -109,7 +113,6 @@ public MessageResponseDto saveMessage(MessageRequestDto dto, Integer senderId){
if (!parent.getChatRoom().getId().equals(room.getId())) {
throw new IllegalArgumentException("๋ค๋ฅธ ์ฑํ
๋ฐฉ์ ๋ฉ์์ง์๋ ๋ต์ฅํ ์ ์์ต๋๋ค.");
}
- // (์ ํ) parent๊ฐ ์ญ์ ๋์์ผ๋ฉด ๊ธ์งํ ์ง ํ์ฉํ ์ง ์ ์ฑ
๊ฒฐ์
}
ChatMessage message = chatMessageRepository.save(
@@ -139,21 +142,29 @@ public List getMessages(Integer roomId, Integer requesterId)
throw new AccessDeniedException("์ฑํ
๋ฐฉ ๋ฉ์ธ์ง ์กฐํ ๊ถํ์ด ์์ต๋๋ค.");
}
- // ๋ด ์ปคํธ๋ผ์ธ ๊ฐ์ ธ์ค๊ธฐ (์์ผ๋ฉด 0)
+ /* ๋ด ์ญ์ ์ง์ ๊ฐ์ ธ์ค๊ธฐ (์์ผ๋ฉด 0)
+ => ์ฆ, ์ด์ ์ ์ฑํ
๋ฐฉ์ ์ญ์ ํ ์ ์ด ์๋ค๋ฉด ์ญ์ ์ง์ ์ดํ์ ๋ฉ์ธ์ง ๋ถํฐ ๋ณด์ฌ์ค.
+ ์ญ์ ํ ์ ์์ผ๋ฉด ์ปคํธ๋ผ์ธ์ 0์ผ๋ก ์ ์ฒด ๋ฉ์ธ์ง๋ฅผ ๋ค ๋ณด์ฌ์ค.
+ */
Integer cutoff = chatRoomRepository.getDeleteCutoffForUser(roomId, requesterId);
int cutoffId = cutoff == null ? 0 : cutoff;
- // โ
์ปคํธ๋ผ์ธ ์ดํ ๋ฉ์์ง๋ง ์กฐํ
+ // ์ญ์ ์ง์ ์ดํ ๋ฉ์์ง๋ง ์กฐํ
List messages = chatMessageRepository.findAfterCutoff(roomId, cutoffId);
- // ===== ์ฌ๊ธฐ์ ์ฝ์ ์ฒ๋ฆฌ =====
+ /* ===== ์ฌ๊ธฐ์ ์ฝ์ ์ฒ๋ฆฌ =====
+ ์กฐํํ์ผ๋ฉด ๋น์ฐํ ์ฝ์ ์ฒ๋ฆฌ
+ */
if (!messages.isEmpty()) {
int lastMessageId = messages.get(messages.size() - 1).getId();
- // ์ด๊ฑฐ markAsRead ์๋น์ค ํธ์ถ
+ // markAsRead ์๋น์ค ํธ์ถ
chatMessageReadService.markAsRead(room.getId(), lastMessageId, requesterId);
}
- // ====== ๋ง์ง๋ง ๋ฉ์์ง '์ฝ์' ํ์ ======
+ /* ====== '๋ง์ง๋ง' ๋ฉ์ธ์ง ์ฝ์ ํ์ ======
+ UI์ ๋ง์ง๋ง ๋ฉ์ธ์ง ์ฝ์ ํ์ ๋์ธ ๋, ๋ด ๋ฉ์ธ์ง๋ฅผ ์๋๊ฐ ์ฝ์์ ๋๋ง ํ์.
+ ๋ด๊ฐ ์๋ ๋ฉ์ธ์ง๋ฅผ ์ฝ์์ ๋ ๊ตณ์ด ์ฝ์ ํ์๋ฅผ ๋์ธ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ.
+ */
Integer opponentReadId = Objects.equals(requesterId, buyerId)
? room.getSellerLastReadMessageId() : room.getBuyerLastReadMessageId();
@@ -161,7 +172,7 @@ public List getMessages(Integer roomId, Integer requesterId)
Integer lastMsgId = (lastMsg != null) ? lastMsg.getId() : null;
boolean lastIsMine = (lastMsg != null) && Objects.equals(lastMsg.getUser().getId(), requesterId);
- // ๋ง์ง๋ง ๋ฉ์์ง๊ฐ ๋ด๊ฐ ๋ณด๋ธ ๊ฑฐ๋ผ๋ฉด, ์๋ ํฌ์ธํฐ๊ฐ ๊ทธ ID ์ด์์ธ์ง๋ก ํ์
+ // ๋ง์ง๋ง ๋ฉ์์ง๊ฐ ๋ด๊ฐ ๋ณด๋ธ ๊ฑฐ๋ผ๋ฉด, ์๋ ๋ฉ์ธ์ง ์ฝ์ ํฌ์ธํฐ๊ฐ ๊ทธ ID ์ด์์ธ์ง๋ก ํ์
boolean lastMsgReadByOpponent = lastIsMine && opponentReadId != null
&& lastMsgId != null && opponentReadId >= lastMsgId;
@@ -172,7 +183,7 @@ public List getMessages(Integer roomId, Integer requesterId)
return messages.stream().map(m -> {
MessageResponseDto dto = toResponse(m);
- // ํ๋ก ํธ ๋ฒ๋ธ ์ ๋ ฌ/์ ๊ตฌ๋ถ์ ์ ์ฉ
+ // ํ๋ก ํธ ์ฑํ
๋ฒ๋ธ ์ ๋ ฌ/์ ๊ตฌ๋ถ์ ์ ์ฉ
boolean mine = Objects.equals(m.getUser().getId(), requesterId);
dto.setMine(mine);
@@ -185,10 +196,11 @@ public List getMessages(Integer roomId, Integer requesterId)
}
+ //์์คํ
๋ฉ์ธ์ง ์ ์ก
@Transactional
public ChatMessage sendSystemMessage(ChatRoom room, String content) {
- // โ
๋ฌธ์์ด ๋ฆฌํฐ๋ด "System" ์ฌ์ฉ
+ // ๋ฌธ์์ด ๋ฆฌํฐ๋ด "System" ์ฌ์ฉ
User systemUser = userRepository.findByNickname("SYSTEM");
if (systemUser == null) {
throw new RuntimeException("์์คํ
๊ณ์ ์ ์ฐพ์ ์ ์์ต๋๋ค.");
diff --git a/src/main/java/com/mrokga/carrot_server/chat/service/ChatRoomService.java b/src/main/java/com/mrokga/carrot_server/chat/service/ChatRoomService.java
index 97dffa3..6b3c3d1 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/service/ChatRoomService.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/service/ChatRoomService.java
@@ -39,6 +39,7 @@ private ChatRoomResponseDto toResponse(ChatRoom room) {
return dto;
}
+ // ์ฑํ
๋ฐฉ ์์ฑ
@Transactional
public ChatRoomResponseDto createOrGetRoom(Integer me, ChatRoomRequestDto dto){
Product product = productRepository.findById(dto.getProductId())
@@ -48,7 +49,7 @@ public ChatRoomResponseDto createOrGetRoom(Integer me, ChatRoomRequestDto dto){
final User seller;
final User buyer;
- // ํ๋งค์ ์ ํก
+ // 1. ํ๋งค์ ์ ํก
if(Objects.equals(me, ownerId)){
if(dto.getBuyerId() == null){
throw new IllegalArgumentException("๊ตฌ๋งค์ ID๊ฐ ํ์ํฉ๋๋ค.(ํ๋งค์ ์ ํก)");
@@ -58,7 +59,7 @@ public ChatRoomResponseDto createOrGetRoom(Integer me, ChatRoomRequestDto dto){
buyer = userRepository.findById(dto.getBuyerId())
.orElseThrow(() -> new IllegalArgumentException("๊ตฌ๋งค์ ์์"));
}
- // ๊ตฌ๋งค์ ์ ํก
+ // 2. ๊ตฌ๋งค์ ์ ํก
else{
buyer = userRepository.findById(me)
.orElseThrow(() -> new IllegalArgumentException("๊ตฌ๋งค์ ์์"));
@@ -101,13 +102,17 @@ public ChatRoomResponseDto createOrGetRoom(Integer me, ChatRoomRequestDto dto){
}
}
+ // ์ฑํ
๋ฐฉ ๋ฆฌ์คํธ ์กฐํ
@Transactional(readOnly = true)
public List getRoomByUser(Integer userId){
List rooms = chatRoomRepository.findByBuyer_IdOrSeller_Id(userId, userId);
return rooms.stream().map(room -> {
ChatRoomResponseDto dto = toResponse(room); // ๊ธฐ์กด ํ๋ ์ธํ
- // ๋ฐฉ์ ๋ง์ง๋ง ๋ณด์ด๋ ๋ฉ์์ง (์ปคํธ๋ผ์ธ ์ดํ)
+ /*
+ ํด๋น ์ฑํ
๋ฐฉ์ ๋ง์ง๋ง ๋ฉ์์ง (์ปคํธ๋ผ์ธ ์ดํ)
+ => UI์ ๊ฐ ์ฑํ
๋ฐฉ๋ง๋ค ๋ง์ง๋ง ๋ฉ์ธ์ง ์์ฝ ํ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ก ๋ณด์ฌ์ฃผ๊ธฐ ์ํด
+ */
chatRoomRepository.findLastVisibleMessageId(room.getId(), userId)
.ifPresent(lastId -> {
chatMessageRepository.findById(lastId).ifPresent(last -> {
@@ -116,13 +121,17 @@ public List getRoomByUser(Integer userId){
dto.setLastMessageAt(last.getCreatedAt());
dto.setLastMessagePreview(
last.getMessageType() == MessageType.TEXT
- ? abbreviate(last.getMessage(), 50)
+ ? abbreviate(last.getMessage())
: "[์ด๋ฏธ์ง]"
);
});
});
- // ๋ด๊ฐ ๋ณด๋ธ ๋ง์ง๋ง ๋ฉ์์ง id (์ปคํธ๋ผ์ธ ์ดํ ๊ธฐ์ค)
+ /*
+ ๋ด๊ฐ ๋ณด๋ธ ๋ง์ง๋ง ๋ฉ์์ง id (๋ด ์ปคํธ๋ผ์ธ ์ดํ ๊ธฐ์ค)
+ ์ฑํ
๋ฐฉ์ ์ญ์ ํ์ง ์์์ผ๋ฉด ์ปคํธ๋ผ์ธ 0, ์ญ์ ํ ์ ์๋ค๋ฉด ์ญ์ ์ง์ ๋ฉ์ธ์ง ๋ฐํ
+ (๋ด๊ฐ ๋ง์ง๋ง์ผ๋ก ๋ณด๋ด๊ณ ๋ด๊ฐ ์ฑํ
๋ฐฉ์ ์ญ์ ํ ๊ฒฝ์ฐ ๋ฑ ๋ค์ํ ๊ฒฝ์ฐ ๋๋นํด์)
+ */
Integer cutoff = chatRoomRepository.getDeleteCutoffForUser(room.getId(), userId);
int cutoffId = cutoff == null ? 0 : cutoff;
Integer lastMyMessageId = chatMessageRepository.findAfterCutoff(room.getId(), cutoffId).stream()
@@ -136,6 +145,7 @@ public List getRoomByUser(Integer userId){
? room.getSellerLastReadMessageId()
: room.getBuyerLastReadMessageId();
+ // ๋ง์ง๋ง ๋ฉ์ธ์ง ์ฝ์ ์ฌ๋ถ ํ์
boolean seen = (lastMyMessageId != null) && (opponentRead != null) && (opponentRead >= lastMyMessageId);
dto.setLastMessageSeen(seen);
@@ -145,23 +155,30 @@ public List getRoomByUser(Integer userId){
/**
* ์ฑํ
๋ฐฉ ์ํํธ ์ญ์ (๋ด ์ชฝ์์๋ง ์ญ์ )
- * - DB์์ ์ค์ ๋ก ์ง์ฐ์ง ์๊ณ ๋ด ์ปคํธ๋ผ์ธ์ ๋ง์ง๋ง ๋ฉ์์ง id๋ก ์ค์
+ * - DB์์ ์ค์ ๋ก ์ง์ฐ์ง ์๊ณ ๋ด๊ฐ ์ญ์ ํ ์ง์ (์ปคํธ๋ผ์ธ)์ ๋ง์ง๋ง ๋ฉ์์ง id๋ก ์ค์
* - ์ดํ ์ด ๋ฐฉ์ ๋ด ๋ชฉ๋ก์์ ์ ๋ณด์ด๊ณ , ๋์ค์ ์ ๋ฉ์์ง๊ฐ ์ค๋ฉด ๋ค์ ๋ฑ์ฅ
*/
@Transactional
public void deleteRoom(Integer roomId, Integer userId) {
+ //์ฑํ
๋ฐฉ ์กฐํ
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new IllegalArgumentException("์ฑํ
๋ฐฉ ์์"));
Integer buyerId = chatRoom.getBuyer().getId();
Integer sellerId = chatRoom.getSeller().getId();
+ // ๊ถํ ํ์ธ
if (!Objects.equals(userId, buyerId) && !Objects.equals(userId, sellerId)) {
throw new AccessDeniedException("์ฑํ
๋ฐฉ ์ญ์ ๊ถํ์ด ์์ต๋๋ค.");
}
+ // ๋ง์ง๋ง ๋ฉ์ธ์ง ์กฐํ
int lastMsgId = chatMessageRepository.findTopIdByRoomId(roomId).orElse(0);
+ /* ํด๋น ์ง์ ๊น์ง๋ ์ฝ์๋ค๋ ๊ฒ์ ์ค์ ํ, ํด๋น ์ง์ ์ ์ญ์ ์ง์ ์ผ๋ก ์ค์
+ 1. ๋ด๊ฐ buyer์ผ ๊ฒฝ์ฐ
+ 2. ๋ด๊ฐ seller์ผ ๊ฒฝ์ฐ
+ */
if (Objects.equals(userId, buyerId)) {
chatRoomRepository.markBuyerDeleted(roomId, buyerId, lastMsgId);
chatRoomRepository.bumpBuyerLastRead(roomId, lastMsgId);
@@ -171,8 +188,9 @@ public void deleteRoom(Integer roomId, Integer userId) {
}
}
- private String abbreviate(String s, int max) {
+ // ๋ฉ์ธ์ง ์์ฝ (50์ ์ด์์ผ ๊ฒฝ์ฐ ์ดํ ๋ด์ฉ ...์ผ๋ก)
+ private String abbreviate(String s) {
if (s == null) return null;
- return s.length() <= max ? s : s.substring(0, max) + "โฆ";
+ return s.length() <= 50 ? s : s.substring(0, 50) + "โฆ";
}
}
diff --git a/src/main/java/com/mrokga/carrot_server/chat/service/QuickReplyService.java b/src/main/java/com/mrokga/carrot_server/chat/service/QuickReplyService.java
index 935c6d5..32122cf 100644
--- a/src/main/java/com/mrokga/carrot_server/chat/service/QuickReplyService.java
+++ b/src/main/java/com/mrokga/carrot_server/chat/service/QuickReplyService.java
@@ -22,6 +22,7 @@ public class QuickReplyService {
private final ChatMessageRepository chatMessageRepository;
private final QuickReplyRepository quickReplyRepository;
+ // ์์ฃผ ์ฐ๋ ๋ฌธ๊ตฌ ์ถ๊ฐ
@Transactional
public QuickReplyAddResponseDto addQuikReply(Integer messageId){
Integer userId = QuickReplyUtils.currentUserIdOrThrow();
@@ -73,6 +74,7 @@ public QuickReplyAddResponseDto addQuikReply(Integer messageId){
}
}
+ // ์์ฃผ ์ฐ๋ ๋ฌธ๊ตฌ ์กฐํ
@Transactional(readOnly = true)
public List listMine() {
Integer userId = QuickReplyUtils.currentUserIdOrThrow();
@@ -82,6 +84,7 @@ public List listMine() {
.toList();
}
+ // ์์ฃผ ์ฐ๋ ๋ฌธ๊ตฌ ์ญ์
@Transactional
public void deleteMine(Integer id) {
Integer userId = QuickReplyUtils.currentUserIdOrThrow();
diff --git a/src/main/java/com/mrokga/carrot_server/community/controller/PostController.java b/src/main/java/com/mrokga/carrot_server/community/controller/PostController.java
index 20a948b..9ba6268 100644
--- a/src/main/java/com/mrokga/carrot_server/community/controller/PostController.java
+++ b/src/main/java/com/mrokga/carrot_server/community/controller/PostController.java
@@ -45,7 +45,7 @@ private Integer getCurrentUserId() {
return Integer.valueOf(auth.getName());
}
- // โ
2-Step ๋ฐฉ์ (JSON-only)
+ // 2-Step ๋ฐฉ์ (JSON-only)
@PostMapping
@Operation(summary = "๊ฒ์๊ธ ์์ฑ", description = "์ฌ์ฉ์๊ฐ ์๋ก์ด ๊ฒ์๊ธ์ ์์ฑํฉ๋๋ค.")
public ResponseEntity> createPost(@RequestBody CreatePostRequestDto dto){
@@ -54,7 +54,7 @@ public ResponseEntity> createPost(@RequestBody CreatePostReque
return ResponseEntity.ok(ApiResponseDto.success(HttpStatus.OK.value(), "success",null));
}
- // โ
1-Step ๋ฐฉ์ (๋ฉํฐํํธ)
+ // 1-Step ๋ฐฉ์ (๋ฉํฐํํธ)
@PostMapping(value = "/multipart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(
summary = "๊ฒ์๊ธ ์์ฑ(๋ฉํฐํํธ: JSON + ์ด๋ฏธ์ง ํ์ผ)",
diff --git a/src/main/java/com/mrokga/carrot_server/community/service/PostService.java b/src/main/java/com/mrokga/carrot_server/community/service/PostService.java
index 2992328..0e8b941 100644
--- a/src/main/java/com/mrokga/carrot_server/community/service/PostService.java
+++ b/src/main/java/com/mrokga/carrot_server/community/service/PostService.java
@@ -61,11 +61,11 @@ public void createPost(CreatePostRequestDto dto){
.dealPlaceLng(dto.getDealPlaceLng())
.build();
- // 2. ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด PostImage ์ํฐํฐ๋ก ๋ณํ ํ ๋งคํ
+ // 2. ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด PostImage(Post ํฌํจํ ์ํฐํฐ) ์ํฐํฐ๋ก ๋ณํ ํ ๋งคํ
if (dto.getImages() != null && !dto.getImages().isEmpty()) {
List postImages = dto.getImages().stream()
.map(imgDto -> PostImage.builder()
- .post(post)
+ .post(post) // 1๋ฒ์์ ์์ฑํ ๊ฒ์๊ธ ์ํฐํฐ
.imageUrl(imgDto.getImageUrl())
.sortOrder(imgDto.getSortOrder() != null ? imgDto.getSortOrder() : 0)
.isThumbnail(imgDto.getIsThumbnail() != null && imgDto.getIsThumbnail())
@@ -97,14 +97,14 @@ public void editPost(EditPostRequestDto dto, Integer me){
post.setContent(dto.getContent());
- // โ
์ฅ์ ์
๋ฐ์ดํธ: null ์ด๋ฉด โ๋ณ๊ฒฝ ์ํจโ, ๋น๋ฌธ์๋ฉด โ์ญ์ โ
+ // ์ฅ์ ์
๋ฐ์ดํธ: null ์ด๋ฉด โ๋ณ๊ฒฝ ์ํจโ, ๋น๋ฌธ์๋ฉด โ์ญ์ โ
if (dto.getDealPlace() != null || dto.getDealPlaceLat() != null || dto.getDealPlaceLng() != null) {
post.setDealPlace(dto.getDealPlace());
post.setDealPlaceLat(dto.getDealPlaceLat());
post.setDealPlaceLng(dto.getDealPlaceLng());
}
- // โ
์ด๋ฏธ์ง ๊ต์ฒด ๋ก์ง
+ // ์ด๋ฏธ์ง ๊ต์ฒด ๋ก์ง
if (dto.getImages() != null) {
// ๊ธฐ์กด ์ด๋ฏธ์ง๋ค
List oldImages = post.getImages();
@@ -114,7 +114,7 @@ public void editPost(EditPostRequestDto dto, Integer me){
.map(img -> img.getImageUrl())
.toList();
- // ์ญ์ ๋ ์ด๋ฏธ์ง ์ถ์ถ = old(DB) list ์๋ ์๋๋ฐ new list ์๋ ์์
+ // ์ญ์ ๋ ์ด๋ฏธ์ง ์ถ์ถ = old list(DB) ์๋ ์๋๋ฐ new list ์๋ ์๋ ์ด๋ฏธ์ง๋ค
List toDelete = oldImages.stream()
.filter(img -> !newUrls.contains(img.getImageUrl()))
.toList();
@@ -125,7 +125,7 @@ public void editPost(EditPostRequestDto dto, Integer me){
post.getImages().remove(img);
});
- // ์๋ก ์ถ๊ฐ๋ ์ด๋ฏธ์ง = new list ์๋ ์๋๋ฐ old(DB) list ์๋ ์์
+ // ์๋ก ์ถ๊ฐ๋ ์ด๋ฏธ์ง = new list ์๋ ์๋๋ฐ old list(DB) ์๋ ์๋ ์ด๋ฏธ์ง๋ค
dto.getImages().forEach(imgDto -> {
boolean exists = oldImages.stream()
.anyMatch(img -> img.getImageUrl().equals(imgDto.getImageUrl()));
@@ -151,7 +151,7 @@ public void deletePost(Integer postId, Integer me){
throw new SecurityException("PostService.deletePost(): ์ญ์ ๊ถํ ์์");
}
- // โ
S3์์ ์ด๋ฏธ์ง ์ญ์
+ // S3์์ ์ด๋ฏธ์ง ์ญ์
post.getImages().forEach(img -> awsS3Service.deleteFileByUrl(img.getImageUrl()));
// ํด๋น ๊ฒ์๊ธ์ ๋๊ธ ์ข์์ ์ญ์
@@ -170,7 +170,11 @@ public void deletePost(Integer postId, Integer me){
postRepository.delete(post);
}
- // ๊ฒ์๊ธ ๋ชฉ๋ก ์กฐํ(ํ์ด์ง)
+ // ๊ฒ์๊ธ ์ง์ญ & ์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํ(ํ์ด์ง)
+ /**
+ * ์ง์ญ์ ํ์, ์นดํ
๊ณ ๋ฆฌ๋ nullable
+ * ํ๋์ ๋ฉ์๋๋ก ์ง์ญ๋ง ํํฐ / ์ง์ญ+์นดํ
๊ณ ๋ฆฌ ํํฐ
+ */
@Transactional(readOnly = true)
public Page getPostList(Integer regionId, Integer categoryId, String keyword, Pageable pageable){
Region region = regionRepository.findById(regionId)
@@ -216,11 +220,13 @@ public PostDetailResponseDto getPostDetail(Integer postId, Integer me){
List comments = commentRepository.findByPostIdOrderByCreatedAtAsc(postId);
- // ๋ด๊ฐ ์ข์์ ๋๋ฅธ ๋๊ธ ID๋ค์ ํ ๋ฒ์ ์กฐํ
+ // ๋ด๊ฐ ์ข์์ ๋๋ฅธ ๋๊ธ ID ํ ๋ฒ์ ์กฐํ
List commentIds = comments.stream().map(Comment::getId).toList();
List likedIds = commentLikeRepository.findLikedCommentIds(me, commentIds);
Set likedIdSet = likedIds.stream().collect(Collectors.toSet());
+ // ๊ฒ์๊ธ์ ๋ฌ๋ฆฐ ๋๊ธ๋ค ์กฐํ
+ // ์ด๋ ๋ด๊ฐ ์ข์์ ๋๋ฅธ ๋๊ธ ID ํ ๋ฒ์ ์กฐํํ ๊ฒ๋ค๋ก ๋ด ์ข์์ ์ฌ๋ถ ํ๋จ
List commentDtos = comments.stream()
.map(c -> CommentResponseDto.builder()
.id(c.getId())
@@ -228,11 +234,12 @@ public PostDetailResponseDto getPostDetail(Integer postId, Integer me){
.nickname(c.getUser().getNickname())
.content(c.getContent())
.likeCount(c.getLikeCount())
- .likedByMe(likedIdSet.contains(c.getId()))
+ .likedByMe(likedIdSet.contains(c.getId())) // ๋๊ธ ์ข์์ ์ฌ๋ถ
.createdAt(c.getCreatedAt())
.build())
.toList();
+ // ๊ฒ์๊ธ ์ข์์ ์ฌ๋ถ
boolean likedByMe = postLikeRepository.findByUserIdAndPostId(me, postId) != null;
return PostDetailResponseDto.builder()
@@ -282,6 +289,7 @@ public void togglePostLike(Integer postId, Integer me){
}
+ // ๊ฒ์๊ธ ์ง์ญ๋ณ ์กฐํ
@Transactional(readOnly = true)
public Page getPostsByRegion(Integer regionId, Pageable pageable) {
Region region = regionRepository.findById(regionId)
diff --git a/src/main/java/com/mrokga/carrot_server/config/SecurityConfig.java b/src/main/java/com/mrokga/carrot_server/config/SecurityConfig.java
index e08c250..fb2f8b6 100644
--- a/src/main/java/com/mrokga/carrot_server/config/SecurityConfig.java
+++ b/src/main/java/com/mrokga/carrot_server/config/SecurityConfig.java
@@ -28,7 +28,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers(
"/actuator/health", "/actuator/info",
"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/api/theTest", "/__test-error",
- "/api/**"
+ "/api/**",
+ "/payment/kakao/success",
+ "/payment/kakao/cancel",
+ "/payment/kakao/fail"
).permitAll()
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // ์ถ๊ฐ
.anyRequest().authenticated()
diff --git a/src/main/java/com/mrokga/carrot_server/group/controller/GroupController.java b/src/main/java/com/mrokga/carrot_server/group/controller/GroupController.java
index 8f78fb8..218793b 100644
--- a/src/main/java/com/mrokga/carrot_server/group/controller/GroupController.java
+++ b/src/main/java/com/mrokga/carrot_server/group/controller/GroupController.java
@@ -201,14 +201,16 @@ public ResponseEntity> leave(@PathVariable Integer id) {
@Operation(summary = "๊ฐ์
์์ฒญ ๋ชฉ๋ก", description = "OWNER/MANAGER๋ง ์กฐํ ๊ฐ๋ฅ. status=PENDING/APPROVED/REJECTED")
@GetMapping("/{id}/requests")
- public Page requests(@PathVariable Integer id,
- @RequestParam(defaultValue = "PENDING") String status,
- @ParameterObject @PageableDefault(size = 20) Pageable pg) {
+ public Page requests(@PathVariable Integer id,
+ @RequestParam(defaultValue = "PENDING") String status,
+ @ParameterObject @PageableDefault(size = 20) Pageable pg) {
+
var actor = membershipRepository.findByGroupIdAndUserId(id, me())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN));
if (actor.getRole() == GroupMembership.Role.MEMBER)
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
- return joinRequestRepository.findByGroupIdAndStatus(id, GroupJoinRequest.Status.valueOf(status), pg);
+
+ return groupService.listJoinRequests(id, GroupJoinRequest.Status.valueOf(status), pg);
}
@Operation(summary = "๊ฐ์
์น์ธ", description = "OWNER/MANAGER ๊ถํ ํ์")
diff --git a/src/main/java/com/mrokga/carrot_server/group/dto/GroupJoinRequestResponse.java b/src/main/java/com/mrokga/carrot_server/group/dto/GroupJoinRequestResponse.java
new file mode 100644
index 0000000..4209e75
--- /dev/null
+++ b/src/main/java/com/mrokga/carrot_server/group/dto/GroupJoinRequestResponse.java
@@ -0,0 +1,20 @@
+package com.mrokga.carrot_server.group.dto;
+
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class GroupJoinRequestResponse {
+ private Integer id;
+ private Integer groupId;
+ private Integer userId;
+ private String userNickname;
+ private String status; // PENDING/APPROVED/REJECTED
+ private String message;
+ private LocalDateTime createdAt;
+}
\ No newline at end of file
diff --git a/src/main/java/com/mrokga/carrot_server/group/repository/GroupJoinRequestRepository.java b/src/main/java/com/mrokga/carrot_server/group/repository/GroupJoinRequestRepository.java
index 4cbe5a7..40cbf9b 100644
--- a/src/main/java/com/mrokga/carrot_server/group/repository/GroupJoinRequestRepository.java
+++ b/src/main/java/com/mrokga/carrot_server/group/repository/GroupJoinRequestRepository.java
@@ -2,8 +2,11 @@
import com.mrokga.carrot_server.group.entity.GroupJoinRequest;
import org.springframework.data.domain.*;
+import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GroupJoinRequestRepository extends JpaRepository {
- Page findByGroupIdAndStatus(Integer groupId, GroupJoinRequest.Status status, Pageable pageable);
+ @EntityGraph(attributePaths = {"user", "group"})
+ Page findByGroupIdAndStatus(Integer groupId,
+ GroupJoinRequest.Status status, Pageable pageable);
}
diff --git a/src/main/java/com/mrokga/carrot_server/group/service/GroupService.java b/src/main/java/com/mrokga/carrot_server/group/service/GroupService.java
index 9a4c27e..52f36d4 100644
--- a/src/main/java/com/mrokga/carrot_server/group/service/GroupService.java
+++ b/src/main/java/com/mrokga/carrot_server/group/service/GroupService.java
@@ -248,6 +248,23 @@ public GroupEventResponse toEventResponse(GroupEvent ev, Integer meId) {
.build();
}
+ @Transactional(readOnly = true)
+ public Page listJoinRequests(Integer groupId,
+ GroupJoinRequest.Status status, Pageable pg) {
+
+ return joinRequestRepository
+ .findByGroupIdAndStatus(groupId, status, pg)
+ .map(req -> GroupJoinRequestResponse.builder()
+ .id(req.getId())
+ .groupId(req.getGroup().getId())
+ .userId(req.getUser().getId())
+ .userNickname(req.getUser().getNickname())
+ .status(req.getStatus().name())
+ .message(req.getMessage())
+ .createdAt(req.getCreatedAt())
+ .build());
+ }
+
}
diff --git a/src/main/java/com/mrokga/carrot_server/payment/controller/PaymentController.java b/src/main/java/com/mrokga/carrot_server/payment/controller/PaymentController.java
index 403eb5a..a3782f9 100644
--- a/src/main/java/com/mrokga/carrot_server/payment/controller/PaymentController.java
+++ b/src/main/java/com/mrokga/carrot_server/payment/controller/PaymentController.java
@@ -8,10 +8,11 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.view.RedirectView;
@RestController
@RequiredArgsConstructor
-@RequestMapping("/api/payment")
+@RequestMapping("/payment")
@Tag(name = "Payment API", description = "๊ฒฐ์ ๊ด๋ จ API")
@Slf4j
public class PaymentController {
@@ -26,10 +27,12 @@ public KakaoReadyResponse kakaoReady(@PathVariable Integer transactionId) {
// ๊ฒฐ์ ์ฑ๊ณต
@GetMapping("/kakao/success")
- public KakaoApproveResponse kakaoSuccess(@RequestParam("transactionId") Integer transactionId,
- @RequestParam("tid") String tid,
- @RequestParam("pg_token") String pgToken) {
- return paymentService.kakaoApprovePayment(transactionId, tid, pgToken);
+ public RedirectView kakaoSuccess(@RequestParam("transactionId") Integer transactionId,
+ @RequestParam("pg_token") String pgToken) {
+
+ paymentService.kakaoApprovePayment(transactionId, pgToken);
+
+ return new RedirectView("http://localhost:3000/payment/success?transactionId=" + transactionId);
}
// ๊ฒฐ์ ์ทจ์
diff --git a/src/main/java/com/mrokga/carrot_server/payment/service/PaymentService.java b/src/main/java/com/mrokga/carrot_server/payment/service/PaymentService.java
index 320e6f2..cadfcd6 100644
--- a/src/main/java/com/mrokga/carrot_server/payment/service/PaymentService.java
+++ b/src/main/java/com/mrokga/carrot_server/payment/service/PaymentService.java
@@ -9,6 +9,7 @@
import com.mrokga.carrot_server.payment.repository.PaymentRepository;
import com.mrokga.carrot_server.transaction.entity.Transaction;
import com.mrokga.carrot_server.transaction.repository.TransactionRepository;
+import com.mrokga.carrot_server.transaction.service.TransactionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -33,6 +34,7 @@ public class PaymentService {
private final PaymentRepository paymentRepository;
private final TransactionRepository transactionRepository;
+ private final TransactionService transactionService;
@Value("${kakaopay.host}")
private String kakaoHost;
@@ -48,6 +50,11 @@ public class PaymentService {
private final RestTemplate restTemplate = new RestTemplate();
+ /**
+ * ์นด์นด์คํ์ด API ํต์ ์ ์ํ ๊ณตํต ํค๋ ์์ฑ method
+ * Authorization header์ Content-Type ์ค์
+ * @return HttpHeaders ๊ฐ์ฒด
+ */
private HttpHeaders buildKakaoHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoAdminKey);
@@ -55,21 +62,30 @@ private HttpHeaders buildKakaoHeaders() {
return headers;
}
- // โ
์นด์นด์คํ์ด ๊ฒฐ์ ์ค๋น
+ /**
+ * ์นด์นด์คํ์ด ๊ฒฐ์ ์ค๋น๋ฅผ ์์ฒญํ๊ณ , DB์ ๊ฒฐ์ ์ ๋ณด๋ฅผ ์ ์ฅ
+ * @param transactionId ๊ฑฐ๋ ID
+ * @return ์นด์นด์คํ์ด ๊ฒฐ์ ์ค๋น ์๋ต DTO
+ */
public KakaoReadyResponse kakaoReadyPayment(Integer transactionId) {
+ // 1. ํธ๋์ญ์
์กฐํ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฆ
Transaction transaction = transactionRepository.findById(transactionId)
.orElseThrow(() -> new IllegalArgumentException("Transaction not found"));
+ // 2. ์ด๋ฏธ ๊ฒฐ์ ๊ฐ ์งํ ์ค์ธ์ง ํ์ธ (์ค๋ณต ์์ฒญ ๋ฐฉ์ง)
if (paymentRepository.findByTransaction(transaction).isPresent()) {
throw new IllegalStateException("์ด๋ฏธ ๊ฒฐ์ ๊ฐ ์งํ ์ค์ธ ๊ฑฐ๋์
๋๋ค.");
}
+ // 3. ์นด์นด์คํ์ด ๊ฒฐ์ ์ค๋น API URL
String url = kakaoHost + "/v1/payment/ready";
+ // 4. ์์ฒญ ํค๋ ์ค์
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoAdminKey);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ // 5. ์์ฒญ ํ๋ผ๋ฏธํฐ ์ค์
MultiValueMap params = new LinkedMultiValueMap<>();
params.add("cid", kakaoCid);
params.add("partner_order_id", String.valueOf(transaction.getId()));
@@ -82,17 +98,20 @@ public KakaoReadyResponse kakaoReadyPayment(Integer transactionId) {
params.add("cancel_url", kakaoBaseUrl + "/cancel");
params.add("fail_url", kakaoBaseUrl + "/fail");
+ // 6. API ์์ฒญ
HttpEntity> request = new HttpEntity<>(params, headers);
ResponseEntity