diff --git a/.DS_Store b/.DS_Store index 00c8cc12..eeea4158 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/discodeit/.gitignore b/discodeit/.gitignore index 4f59eb2d..e8ba384e 100644 --- a/discodeit/.gitignore +++ b/discodeit/.gitignore @@ -7,6 +7,7 @@ build/ *.iml *.ipr *.iws +/.claude # IDE - Eclipse .classpath @@ -43,3 +44,8 @@ application-local.properties # File-based repository data file-data-map/ + +# Newman test reports +newman/ +newman-report*.html +newman-report*.json diff --git a/discodeit/TROUBLESHOOTING.md b/discodeit/TROUBLESHOOTING.md new file mode 100644 index 00000000..6c050c32 --- /dev/null +++ b/discodeit/TROUBLESHOOTING.md @@ -0,0 +1,379 @@ +# ๐Ÿ”ง ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ + +๋””์Šค์ฝ”๋“œ์ž‡ API ํ…Œ์ŠคํŠธ ์ค‘ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. + +--- + +## ๐Ÿ”ด ๋กœ๊ทธ์ธ ์‹คํŒจ (404 Not Found) + +### ์ฆ์ƒ +``` +Login successful | AssertionError: expected response to have status code 200 but got 404 +``` + +๋˜๋Š” ์‘๋‹ต: +```json +{ + "message": "Invalid username or password", + "timestamp": "2026-02-09T..." +} +``` + +### ์›์ธ +**DB์— ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!** + +๋””์Šค์ฝ”๋“œ์ž‡์€ **JCF (Java Collection Framework) ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ Repository**๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: +- โš ๏ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ ์‹œ **๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ** +- โš ๏ธ ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์ €์žฅ๋˜๋ฏ€๋กœ ์˜๊ตฌ ์ €์žฅ๋˜์ง€ ์•Š์Œ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +#### โœ… ์˜ฌ๋ฐ”๋ฅธ ์ˆœ์„œ: + +``` +1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ + โ†“ +2. ์‚ฌ์šฉ์ž ๋“ฑ๋ก โ† ๋จผ์ € ์ด๊ฒƒ! + โ†“ +3. ๋กœ๊ทธ์ธ โ† ๊ทธ ๋‹ค์Œ ์ด๊ฒƒ! +``` + +#### Postman์—์„œ: + +**1๋‹จ๊ณ„: ์‚ฌ์šฉ์ž ๋“ฑ๋ก** +``` +Collection > 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ > ์‚ฌ์šฉ์ž ๋“ฑ๋ก +``` +```json +{ + "username": "testuser", + "email": "test@example.com", + "password": "password123" +} +``` +**Send** โ†’ 201 Created ํ™•์ธ โœ… + +**2๋‹จ๊ณ„: ๋กœ๊ทธ์ธ** +``` +Collection > 2. ๊ถŒํ•œ ๊ด€๋ฆฌ > ๋กœ๊ทธ์ธ +``` +```json +{ + "username": "testuser", + "password": "password123" +} +``` +**Send** โ†’ 200 OK ํ™•์ธ โœ… + +--- + +## ๐Ÿ”ด ์ฑ„๋„/๋ฉ”์‹œ์ง€ ์กฐํšŒ ์‹คํŒจ (404 Not Found) + +### ์ฆ์ƒ +```json +{ + "message": "Channel not found: {uuid}", + "timestamp": "..." +} +``` + +### ์›์ธ +- Environment ๋ณ€์ˆ˜ (`channelId`, `messageId` ๋“ฑ)๊ฐ€ ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ์ž˜๋ชป๋จ +- ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘์œผ๋กœ ๋ฐ์ดํ„ฐ ์†์‹ค + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**1. Environment ๋ณ€์ˆ˜ ํ™•์ธ** +- ์šฐ์ธก ์ƒ๋‹จ Environment ์•„์ด์ฝ˜ ํด๋ฆญ +- "Discodeit Local" ์„ ํƒ +- `userId`, `channelId`, `messageId` ๊ฐ’ ํ™•์ธ + +**2. ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹คํ–‰** +``` +์‚ฌ์šฉ์ž ๋“ฑ๋ก โ†’ ์ฑ„๋„ ์ƒ์„ฑ โ†’ ๋ฉ”์‹œ์ง€ ์ „์†ก +``` + +--- + +## ๐Ÿ”ด Required request body is missing + +### ์ฆ์ƒ +```json +{ + "message": "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: Required request body is missing: ...", + "timestamp": "..." +} +``` + +### ์›์ธ +Body๊ฐ€ ์ œ๋Œ€๋กœ ์„ค์ •๋˜์ง€ ์•Š์Œ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**Postman์—์„œ ํ™•์ธ:** + +1. **Body ํƒญ ํด๋ฆญ** +2. **raw** ์„ ํƒ โœ… +3. ์šฐ์ธก ๋“œ๋กญ๋‹ค์šด์—์„œ **JSON** ์„ ํƒ โœ… (Text๊ฐ€ ์•„๋‹˜!) +4. JSON ๋ฐ์ดํ„ฐ ์ž…๋ ฅ + +```json +{ + "username": "testuser", + ... +} +``` + +--- + +## ๐Ÿ”ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜์ง€ ์•Š์Œ + +### ์ฆ์ƒ +``` +Failed to connect to localhost port 8080 +``` + +### ์›์ธ +Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜์ง€ ์•Š์Œ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**ํ„ฐ๋ฏธ๋„์—์„œ:** +```bash +cd /Users/ijongho/IdeaProjects/9-sprint-mission/discodeit +./gradlew bootRun +``` + +**๋˜๋Š” IDE์—์„œ:** +- Spring Boot Application ์‹คํ–‰ ๋ฒ„ํŠผ ํด๋ฆญ + +**์‹คํ–‰ ํ™•์ธ:** +```bash +lsof -i :8080 +``` + +์ •์ƒ ์ถœ๋ ฅ: +``` +java 12345 user 50u IPv6 ... TCP *:http-alt (LISTEN) +``` + +--- + +## ๐Ÿ”ด Environment ๋ณ€์ˆ˜๊ฐ€ ์ž๋™ ์ €์žฅ๋˜์ง€ ์•Š์Œ + +### ์ฆ์ƒ +userId, channelId ๋“ฑ์ด Environment์— ์ €์žฅ๋˜์ง€ ์•Š์Œ + +### ์›์ธ +Tests ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ๋ฐœ์ƒ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**1. Console ํ™•์ธ** +- Postman ํ•˜๋‹จ์˜ **Console** ํƒญ ํ™•์ธ +- ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋‚˜ ๋กœ๊ทธ ํ™•์ธ + +**2. Tests ํƒญ ํ™•์ธ** +- ์š”์ฒญ์˜ **Tests** ํƒญ์— ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ +- ์˜ˆ์‹œ: +```javascript +if (pm.response.code === 201) { + var jsonData = pm.response.json(); + pm.environment.set("userId", jsonData.id); +} +``` + +**3. ์ˆ˜๋™ ์ €์žฅ** +- ์‘๋‹ต์—์„œ `id` ๋ณต์‚ฌ +- Environment ํŽธ์ง‘ โ†’ `userId`์— ๋ถ™์—ฌ๋„ฃ๊ธฐ + +--- + +## ๐Ÿ”ด JSON parse error + +### ์ฆ์ƒ +```json +{ + "message": "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: JSON parse error: ...", + "timestamp": "..." +} +``` + +### ์›์ธ +JSON ํ˜•์‹์ด ์ž˜๋ชป๋จ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**์˜ฌ๋ฐ”๋ฅธ JSON ํ˜•์‹:** +```json +{ + "username": "testuser", + "email": "test@example.com" +} +``` + +**์ž˜๋ชป๋œ ์˜ˆ:** +```json +{ + username: "testuser", // โŒ ํ‚ค์— ๋”ฐ์˜ดํ‘œ ์—†์Œ + "email": 'test@example.com' // โŒ ์ž‘์€๋”ฐ์˜ดํ‘œ +} +``` + +**ํŒ:** +- JSON Validator ์‚ฌ์šฉ: https://jsonlint.com/ +- Postman์ด ์ž๋™์œผ๋กœ ๊ฒ€์ฆํ•ด์คŒ (๋นจ๊ฐ„ ๋ฐ‘์ค„) + +--- + +## ๐Ÿ”ด Private channel cannot be updated + +### ์ฆ์ƒ +```json +{ + "message": "Private channel cannot be updated", + "timestamp": "..." +} +``` + +### ์›์ธ +๋น„๊ณต๊ฐœ ์ฑ„๋„์€ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋„๋ก ์„ค๊ณ„๋จ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**๋น„๊ณต๊ฐœ ์ฑ„๋„์€ ์ˆ˜์ • ๋ถˆ๊ฐ€** +- ๊ณต๊ฐœ ์ฑ„๋„๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ +- ๋น„๊ณต๊ฐœ ์ฑ„๋„์„ ์ˆ˜์ •ํ•˜๋ ค๋ฉด ์‚ญ์ œ ํ›„ ์žฌ์ƒ์„ฑ + +--- + +## ๐Ÿ”ด ์ด๋ฉ”์ผ/์‚ฌ์šฉ์ž๋ช… ์ค‘๋ณต + +### ์ฆ์ƒ +```json +{ + "message": "์ด ์‚ฌ์šฉ์ž ์ด๋ฆ„์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: testuser", + "timestamp": "..." +} +``` + +๋˜๋Š” +```json +{ + "message": "์ด ์ด๋ฉ”์ผ์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: test@example.com", + "timestamp": "..." +} +``` + +### ์›์ธ +๊ฐ™์€ ์‚ฌ์šฉ์ž๋ช… ๋˜๋Š” ์ด๋ฉ”์ผ๋กœ ์ค‘๋ณต ๋“ฑ๋ก ์‹œ๋„ + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +**๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋ช…/์ด๋ฉ”์ผ ์‚ฌ์šฉ:** +```json +{ + "username": "testuser2", // โ† ๋ณ€๊ฒฝ + "email": "test2@example.com", // โ† ๋ณ€๊ฒฝ + "password": "password123" +} +``` + +--- + +## ๐Ÿ“Š ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” + +### ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ ์‹œ + +**JCF Repository ํŠน์„ฑ:** +- โœ… ๋น ๋ฅธ ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ +- โŒ ์˜๊ตฌ ์ €์žฅ ์•ˆ๋จ +- โŒ ์žฌ์‹œ์ž‘ ์‹œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ + +**์žฌ์‹œ์ž‘ ํ›„ ํ•ด์•ผ ํ•  ์ผ:** +``` +1. ์‚ฌ์šฉ์ž ๋“ฑ๋ก +2. ์ฑ„๋„ ์ƒ์„ฑ +3. ๋ฉ”์‹œ์ง€ ์ „์†ก +``` + +--- + +## ๐Ÿ’ก ์œ ์šฉํ•œ ํŒ + +### 1. Collection Runner ์‚ฌ์šฉ + +์ „์ฒด API๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์ž๋™ ์‹คํ–‰: +1. Collection ์šฐํด๋ฆญ โ†’ **Run collection** +2. ์ˆœ์„œ ์„ค์ • +3. **Run** ํด๋ฆญ + +### 2. Pre-request Script + +์ž๋™์œผ๋กœ ํ˜„์žฌ ์‹œ๊ฐ„ ์„ค์ •: +```javascript +pm.environment.set("currentTime", new Date().toISOString()); +``` + +### 3. Console ํ™œ์šฉ + +- **View โ†’ Show Postman Console** (Cmd/Ctrl + Alt + C) +- ๋ชจ๋“  ์š”์ฒญ/์‘๋‹ต ๋กœ๊ทธ ํ™•์ธ +- ๋””๋ฒ„๊น…์— ๋งค์šฐ ์œ ์šฉ + +### 4. Environment ๋ฐฑ์—… + +- Environment Export โ†’ JSON ํŒŒ์ผ ์ €์žฅ +- ํ•„์š”์‹œ ๋‹ค์‹œ Import + +--- + +## ๐Ÿ“ž ์ถ”๊ฐ€ ๋„์›€ + +### ๋กœ๊ทธ ํ™•์ธ + +**Spring Boot ๋กœ๊ทธ:** +- ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ„ฐ๋ฏธ๋„/์ฝ˜์†” +- ์—๋Ÿฌ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ํ™•์ธ + +**Postman Console:** +- Postman ํ•˜๋‹จ Console ํƒญ +- ์š”์ฒญ/์‘๋‹ต ์ƒ์„ธ ์ •๋ณด + +### ๋„๊ตฌ + +**Bash ์Šคํฌ๋ฆฝํŠธ:** +```bash +/tmp/test_discodeit_api.sh +``` + +**Python ์Šคํฌ๋ฆฝํŠธ:** +```bash +pip install -r requirements.txt +python test_api_automation.py +``` + +**API ๋ฌธ์„œ:** +``` +API_DOCUMENTATION.md +POSTMAN_GUIDE.md +``` + +--- + +## โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ํ™•์ธ: + +- [ ] ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ ์ค‘์ธ๊ฐ€? (`lsof -i :8080`) +- [ ] ์˜ฌ๋ฐ”๋ฅธ Environment๊ฐ€ ์„ ํƒ๋˜์—ˆ๋Š”๊ฐ€? ("Discodeit Local") +- [ ] ์‚ฌ์šฉ์ž๋ฅผ ๋จผ์ € ๋“ฑ๋กํ–ˆ๋Š”๊ฐ€? +- [ ] Body ํƒญ์—์„œ raw + JSON์ด ์„ ํƒ๋˜์—ˆ๋Š”๊ฐ€? +- [ ] JSON ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅธ๊ฐ€? +- [ ] Environment ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์—ˆ๋Š”๊ฐ€? +- [ ] URL์ด ์˜ฌ๋ฐ”๋ฅธ๊ฐ€? (`/auth/login` ๋“ฑ) +- [ ] Method๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ๊ฐ€? (POST, GET ๋“ฑ) + +--- + +**Happy Testing! ๐ŸŽ‰** diff --git a/discodeit/collection_structure.txt b/discodeit/collection_structure.txt new file mode 100644 index 00000000..3260feea --- /dev/null +++ b/discodeit/collection_structure.txt @@ -0,0 +1,27 @@ +๋””์Šค์ฝ”๋“œ์ž‡ API v2 ๊ตฌ์กฐ (์ˆ˜์ •๋จ) + +0. ์ดˆ๊ธฐ ์„ค์ • (๋จผ์ € ์‹คํ–‰) +โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 1 ๋“ฑ๋ก (user1) +โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 2 ๋“ฑ๋ก (user2) +โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 3 ๋“ฑ๋ก (user3) +โ”œโ”€โ”€ ์ฑ„๋„ 1 ์ƒ์„ฑ (์ผ๋ฐ˜) +โ”œโ”€โ”€ ์ฑ„๋„ 2 ์ƒ์„ฑ (๊ณต์ง€) +โ””โ”€โ”€ ์ฑ„๋„ 3 ์ƒ์„ฑ (์งˆ๋ฌธ) + +1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ +โ”œโ”€โ”€ ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ โœ… Tests ์ถ”๊ฐ€ +โ”œโ”€โ”€ ์‚ฌ์šฉ์ž1 ์กฐํšŒ โœ… Tests ์ถ”๊ฐ€ +โ”œโ”€โ”€ ์‚ฌ์šฉ์ž1 ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ โœ… Tests ์ถ”๊ฐ€ +โ”œโ”€โ”€ ์ถ”๊ฐ€ ์‚ฌ์šฉ์ž ๋“ฑ๋ก (user4) โœ… ์ˆ˜์ • ํ…Œ์ŠคํŠธ์šฉ +โ”œโ”€โ”€ user4 ์ •๋ณด ์ˆ˜์ • โœ… user2 ๋Œ€์‹  user4 ์ˆ˜์ • +โ””โ”€โ”€ ์‚ฌ์šฉ์ž3 ์‚ญ์ œ (ํ…Œ์ŠคํŠธ์šฉ) โœ… Tests ์ถ”๊ฐ€ + +2. ๊ถŒํ•œ ๊ด€๋ฆฌ +โ”œโ”€โ”€ user1 ๋กœ๊ทธ์ธ โœ… Tests ๊ฐœ์„  +โ””โ”€โ”€ user2 ๋กœ๊ทธ์ธ โœ… Tests ๊ฐœ์„  + +ํ•ต์‹ฌ ์ˆ˜์ •: +- user2๋Š” ์‚ญ์ œ/์ˆ˜์ •ํ•˜์ง€ ์•Š์Œ (๋กœ๊ทธ์ธ ํ…Œ์ŠคํŠธ์šฉ) +- user4๋ฅผ ์ถ”๊ฐ€๋กœ ์ƒ์„ฑํ•ด์„œ ์ˆ˜์ • ํ…Œ์ŠคํŠธ +- ๋ชจ๋“  API์— Tests ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€ +- ์ˆœ์„œ ์žฌ์กฐ์ • diff --git a/discodeit/docs/API-TEST-GUIDE.md b/discodeit/docs/API-TEST-GUIDE.md new file mode 100644 index 00000000..278b6911 --- /dev/null +++ b/discodeit/docs/API-TEST-GUIDE.md @@ -0,0 +1,167 @@ +# ๐Ÿ“ฎ API ํ…Œ์ŠคํŠธ ๊ฐ€์ด๋“œ + +IntelliJ IDEA์˜ HTTP Client๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  API๋ฅผ ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +### 1. ํŒŒ์ผ ์—ด๊ธฐ + +IntelliJ์—์„œ ๋‹ค์Œ ํŒŒ์ผ ์ค‘ ํ•˜๋‚˜๋ฅผ ์—ฝ๋‹ˆ๋‹ค: + +- **`api-tests-with-env.http`** (๊ถŒ์žฅ) - ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ +- **`api-tests.http`** - ๊ธฐ๋ณธ ๋ฒ„์ „ + +### 2. ํ™˜๊ฒฝ ์„ ํƒ + +`api-tests-with-env.http` ์‚ฌ์šฉ ์‹œ: +- ํŒŒ์ผ ์šฐ์ธก ์ƒ๋‹จ์˜ ํ™˜๊ฒฝ ๋“œ๋กญ๋‹ค์šด์—์„œ **`dev`** ์„ ํƒ +- ์‹ค์ œ ์‚ฌ์šฉ์ž ID๊ฐ€ ์ž๋™์œผ๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค + +### 3. API ์‹คํ–‰ + +๊ฐ ์š”์ฒญ ์˜†์˜ **โ–ถ ๋ฒ„ํŠผ** ํด๋ฆญ ๋˜๋Š”: +- **Windows/Linux**: `Ctrl` + `Enter` +- **Mac**: `Cmd` + `Enter` + +### 4. ๊ฒฐ๊ณผ ํ™•์ธ + +- ํ•˜๋‹จ์— ์‘๋‹ต ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค +- JSON ํ˜•์‹์œผ๋กœ ์ž๋™ ํฌ๋งทํŒ…๋ฉ๋‹ˆ๋‹ค +- ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ƒํƒœ ์ฝ”๋“œ์™€ ๋ฉ”์‹œ์ง€ ํ™•์ธ ๊ฐ€๋Šฅ + +## ๐Ÿ“‹ ํ…Œ์ŠคํŠธ ํ•ญ๋ชฉ + +### โœ… ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ +- [x] ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ +- [x] ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ +- [x] ์‚ฌ์šฉ์ž ์ƒ์„ฑ (JSON) +- [x] ์‚ฌ์šฉ์ž ์ƒ์„ฑ (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ) +- [x] ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (JSON) +- [x] ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ) +- [x] ์‚ฌ์šฉ์ž ์‚ญ์ œ + +### โœ… ์‚ฌ์šฉ์ž ์ƒํƒœ ๊ด€๋ฆฌ +- [x] ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +- [x] ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž ์ƒํƒœ ์ผ๊ด„ ์—…๋ฐ์ดํŠธ + +### โœ… ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  +- [x] ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ +- [x] ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ + +### โœ… ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ +- [x] ์‚ฌ์šฉ์ž ์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ • โ†’ ์‚ญ์ œ ์ „์ฒด ํ”Œ๋กœ์šฐ +- [x] ์ž๋™ ๊ฒ€์ฆ (์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ, ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ) + +### โœ… ์—๋Ÿฌ ์ผ€์ด์Šค +- [x] ์ค‘๋ณต ์ด๋ฉ”์ผ ๊ฒ€์ฆ +- [x] ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์กฐํšŒ (404) + +## ๐Ÿ”ง ํ™˜๊ฒฝ ๋ณ€์ˆ˜ + +`http-client.env.json` ํŒŒ์ผ์— ์ •์˜๋œ ๋ณ€์ˆ˜๋“ค: + +```json +{ + "dev": { + "baseUrl": "http://localhost:8080", + "user1Id": "ํ•œ์„ฑ์žฌ ์‚ฌ์šฉ์ž ID", + "user1ProfileId": "ํ•œ์„ฑ์žฌ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID", + "user2Id": "์„œํ˜„ํ•˜ ์‚ฌ์šฉ์ž ID", + ... + } +} +``` + +### ์‚ฌ์šฉ ์˜ˆ์‹œ +```http +GET {{baseUrl}}/users/{{user1Id}} +``` + +## ๐Ÿ’ก ๋™์  ๋ณ€์ˆ˜ + +IntelliJ HTTP Client๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋™์  ๋ณ€์ˆ˜: + +| ๋ณ€์ˆ˜ | ์„ค๋ช… | ์˜ˆ์‹œ | +|------|------|------| +| `{{$uuid}}` | ๋žœ๋ค UUID | `123e4567-e89b-12d3-a456-426614174000` | +| `{{$timestamp}}` | Unix ํƒ€์ž„์Šคํƒฌํ”„ | `1707567890123` | +| `{{$isoTimestamp}}` | ISO 8601 ์‹œ๊ฐ„ | `2026-02-10T08:23:57Z` | +| `{{$randomInt}}` | ๋žœ๋ค ์ •์ˆ˜ (0-1000) | `742` | + +### ์‚ฌ์šฉ ์˜ˆ์‹œ +```http +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "user_{{$randomInt}}", + "email": "user_{{$timestamp}}@test.com", + "password": "password123" +} +``` + +## ๐Ÿงช ์ž๋™ ๊ฒ€์ฆ + +์‘๋‹ต ๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ ์˜ˆ์‹œ: + +```javascript +> {% + client.test("์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต", function() { + client.assert(response.status === 200, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์ด์–ด์•ผ ํ•จ"); + client.assert(response.body.username !== null, "์‚ฌ์šฉ์ž๋ช…์ด ์žˆ์–ด์•ผ ํ•จ"); + }); + client.log("โœ… ํ…Œ์ŠคํŠธ ํ†ต๊ณผ"); +%} +``` + +## ๐ŸŽฏ ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ ์‹คํ–‰ + +์ „์ฒด ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰: + +1. **์‚ฌ์šฉ์ž ์ƒ์„ฑ** โ†’ `newUserId` ๋ณ€์ˆ˜์— ์ €์žฅ +2. **์ƒ์„ฑํ•œ ์‚ฌ์šฉ์ž ์กฐํšŒ** โ†’ `newUserId` ์‚ฌ์šฉ +3. **์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ** โ†’ `newUserId` ์‚ฌ์šฉ +4. **์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •** โ†’ `newUserId` ์‚ฌ์šฉ +5. **์‚ฌ์šฉ์ž ์‚ญ์ œ** โ†’ `newUserId` ์‚ฌ์šฉ +6. **์‚ญ์ œ ํ™•์ธ (404)** โ†’ `newUserId` ์‚ฌ์šฉ + +๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๊ฒ€์ฆ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + +## ๐Ÿ“Š ์‹คํ–‰ ๊ฒฐ๊ณผ ์˜ˆ์‹œ + +``` +โœ… ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์™„๋ฃŒ - ID: 123e4567-e89b-12d3-a456-426614174000 +โœ… ์‚ฌ์šฉ์ž ์กฐํšŒ ์™„๋ฃŒ - ์ด๋ฆ„: ์‹œ๋‚˜๋ฆฌ์˜ค์œ ์ € +โœ… ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ +โœ… ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์™„๋ฃŒ +โœ… ์‚ฌ์šฉ์ž ์‚ญ์ œ ์™„๋ฃŒ +โœ… ์‚ญ์ œ ํ™•์ธ ์™„๋ฃŒ - 404 ์‘๋‹ต +``` + +## ๐Ÿ”„ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” + +ํ…Œ์ŠคํŠธ ํ›„ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•˜๋ฉด: + +1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ +2. ์ดˆ๊ธฐ 4๋ช…์˜ ์‚ฌ์šฉ์ž๋งŒ ๋‚จ์Œ (ํ•œ์„ฑ์žฌ, ์žฅํ˜„๋ฏผ, ์ „๋ช…ํ›ˆ, ์„œํ˜„ํ•˜) + +## ๐Ÿ†˜ ๋ฌธ์ œ ํ•ด๊ฒฐ + +### Q: "ํ™˜๊ฒฝ์„ ์„ ํƒํ•  ์ˆ˜ ์—†์–ด์š”" +**A**: `http-client.env.json` ํŒŒ์ผ์ด ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. + +### Q: "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๊ฐ€ ์‹คํŒจํ•ด์š”" +**A**: `src/main/resources/data/profiles/` ํด๋”์— ์ด๋ฏธ์ง€ ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. + +### Q: "์ค‘๋ณต ์ด๋ฉ”์ผ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์š”" +**A**: ๋™์  ๋ณ€์ˆ˜ `{{$randomInt}}`๋‚˜ `{{$timestamp}}`๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ณ ์œ ํ•œ ์ด๋ฉ”์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”. + +### Q: "404 ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์š”" +**A**: +- ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ (`http://localhost:8080`) +- `http-client.env.json`์˜ ID ๊ฐ’์ด ์ตœ์‹ ์ธ์ง€ ํ™•์ธ + +## ๐Ÿ“š ์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค + +- [IntelliJ HTTP Client ๊ณต์‹ ๋ฌธ์„œ](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html) +- [ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • ๊ฐ€์ด๋“œ](https://www.jetbrains.com/help/idea/exploring-http-syntax.html#environment-variables) diff --git a/discodeit/docs/Discodeit API.postman_collection.json b/discodeit/docs/Discodeit API.postman_collection.json new file mode 100644 index 00000000..253ad707 --- /dev/null +++ b/discodeit/docs/Discodeit API.postman_collection.json @@ -0,0 +1,1291 @@ +{ + "info": { + "name": "Discodeit API", + "description": "Discodeit API ์ž๋™ ํ…Œ์ŠคํŠธ ์ปฌ๋ ‰์…˜\n\n์›น API ์š”๊ตฌ์‚ฌํ•ญ ์ˆœ์„œ๋Œ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "discodeit-api" + }, + "item": [ + { + "name": "์‚ฌ์šฉ์ž ๊ด€๋ฆฌ", + "item": [ + { + "name": "์‚ฌ์šฉ์ž ๋“ฑ๋ก", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 201) {", + " const error = pm.response.json();", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200 ๋˜๋Š” 201) - ${error.message || ''}`);", + " }", + " ", + " const user = pm.response.json();", + " if (!user.id) {", + " throw new Error('์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ID๊ฐ€ ์—†์Œ');", + " }", + " ", + " if (!user.email) {", + " throw new Error('์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ์ด๋ฉ”์ผ์ด ์—†์Œ');", + " }", + " ", + " // ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ID ์ €์žฅ", + " pm.environment.set('createdUserId', user.id);", + " ", + " pm.test('์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ๋“ฑ๋ก ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// ๋งค๋ฒˆ ๋‹ค๋ฅธ ์ด๋ฉ”์ผ ์ƒ์„ฑ", + "pm.environment.set('randomEmail', 'newuser' + Date.now() + '@example.com');" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"์ƒˆ๋กœ์šด์‚ฌ์šฉ์ž\",\n \"email\": \"{{randomEmail}}\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users", + "host": ["{{baseUrl}}"], + "path": ["users"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const user = pm.response.json();", + " const expectedId = pm.environment.get('user2Id');", + " if (user.id !== expectedId) {", + " throw new Error(`์‚ฌ์šฉ์ž ID ๋ณ€๊ฒฝ๋จ: ${user.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " pm.test('์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"{{user2Name}}_์ˆ˜์ •๋จ\",\n \"email\": \"{{user2Email}}\",\n \"password\": \"newpassword123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user2Id}}", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user2Id}}"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 204) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200 ๋˜๋Š” 204)`);", + " }", + " ", + " pm.test('์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ์‚ญ์ œ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/{{createdUserId}}", + "host": ["{{baseUrl}}"], + "path": ["users", "{{createdUserId}}"] + } + }, + "response": [] + }, + { + "name": "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const users = pm.response.json();", + " if (!Array.isArray(users)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (users.length === 0) {", + " throw new Error('์‚ฌ์šฉ์ž ๋ชฉ๋ก์ด ๋น„์–ด์žˆ์Œ');", + " }", + " ", + " const requiredFields = ['id', 'username', 'email', 'online'];", + " const missingFields = requiredFields.filter(field => !users[0].hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users", + "host": ["{{baseUrl}}"], + "path": ["users"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (์ด์˜ํฌ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const status = pm.response.json();", + " if (!status.hasOwnProperty('lastAccessAt')) {", + " throw new Error('lastAccessAt ํ•„๋“œ๊ฐ€ ์‘๋‹ต์— ์—†์Œ');", + " }", + " ", + " pm.test('์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastAccessAt\": \"{{$isoTimestamp}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user2Id}}/status", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user2Id}}", "status"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๋ฐ•๋ฏผ์ˆ˜)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const status = pm.response.json();", + " if (!status.hasOwnProperty('lastAccessAt')) {", + " throw new Error('lastAccessAt ํ•„๋“œ๊ฐ€ ์‘๋‹ต์— ์—†์Œ');", + " }", + " ", + " pm.test('์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastAccessAt\": \"{{$isoTimestamp}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user3Id}}/status", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user3Id}}", "status"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (์ตœ์ง€์—ฐ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const status = pm.response.json();", + " if (!status.hasOwnProperty('lastAccessAt')) {", + " throw new Error('lastAccessAt ํ•„๋“œ๊ฐ€ ์‘๋‹ต์— ์—†์Œ');", + " }", + " ", + " pm.test('์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastAccessAt\": \"{{$isoTimestamp}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user4Id}}/status", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user4Id}}", "status"] + } + }, + "response": [] + } + ] + }, + { + "name": "๊ถŒํ•œ ๊ด€๋ฆฌ", + "item": [ + { + "name": "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ (๋ฐ•๋ฏผ์ˆ˜)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const user = pm.response.json();", + " if (typeof user !== 'object' || Array.isArray(user)) {", + " throw new Error('์‘๋‹ต์ด ๊ฐ์ฒด๊ฐ€ ์•„๋‹˜');", + " }", + " ", + " const requiredFields = ['id', 'username', 'email'];", + " const missingFields = requiredFields.filter(field => !user.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " // ๋ฐ•๋ฏผ์ˆ˜๋กœ ๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€ ํ™•์ธ", + " const expectedId = pm.environment.get('user3Id');", + " if (user.id !== expectedId) {", + " throw new Error(`์‚ฌ์šฉ์ž ID ๋ถˆ์ผ์น˜: ${user.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " pm.test('์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"{{user3Name}}\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": ["{{baseUrl}}"], + "path": ["auth", "login"] + } + }, + "response": [] + } + ] + }, + { + "name": "์ฑ„๋„ ๊ด€๋ฆฌ", + "item": [ + { + "name": "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 201) {", + " const error = pm.response.json();", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 201) - ${error.message || ''}`);", + " }", + " ", + " const channel = pm.response.json();", + " if (!channel.id) {", + " throw new Error('์ƒ์„ฑ๋œ ์ฑ„๋„์— ID๊ฐ€ ์—†์Œ');", + " }", + " ", + " const requiredFields = ['id', 'name'];", + " const missingFields = requiredFields.filter(field => !channel.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " // isPrivate ํ•„๋“œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ๊ฒ€์ฆ", + " if (channel.hasOwnProperty('isPrivate') && channel.isPrivate !== false) {", + " throw new Error('๊ณต๊ฐœ ์ฑ„๋„์ด ์•„๋‹˜');", + " }", + " ", + " // ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์ฑ„๋„ ID ์ €์žฅ", + " pm.environment.set('createdPublicChannelId', channel.id);", + " ", + " pm.test('๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"ํ…Œ์ŠคํŠธ ๊ณต๊ฐœ ์ฑ„๋„\",\n \"description\": \"Postman ํ…Œ์ŠคํŠธ์šฉ ๊ณต๊ฐœ ์ฑ„๋„\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/channels/public", + "host": ["{{baseUrl}}"], + "path": ["channels", "public"] + } + }, + "response": [] + }, + { + "name": "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 201) {", + " const error = pm.response.json();", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 201) - ${error.message || ''}`);", + " }", + " ", + " const channel = pm.response.json();", + " if (!channel.id) {", + " throw new Error('์ƒ์„ฑ๋œ ์ฑ„๋„์— ID๊ฐ€ ์—†์Œ');", + " }", + " ", + " const requiredFields = ['id', 'name'];", + " const missingFields = requiredFields.filter(field => !channel.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " // isPrivate ํ•„๋“œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ๊ฒ€์ฆ", + " if (channel.hasOwnProperty('isPrivate') && channel.isPrivate !== true) {", + " throw new Error('๋น„๊ณต๊ฐœ ์ฑ„๋„์ด ์•„๋‹˜');", + " }", + " ", + " // ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์ฑ„๋„ ID ์ €์žฅ", + " pm.environment.set('createdPrivateChannelId', channel.id);", + " ", + " pm.test('๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"ํ…Œ์ŠคํŠธ ๋น„๊ณต๊ฐœ ์ฑ„๋„\",\n \"description\": \"Postman ํ…Œ์ŠคํŠธ์šฉ ๋น„๊ณต๊ฐœ ์ฑ„๋„\",\n \"memberIds\": [\"{{user2Id}}\", \"{{user3Id}}\"]\n}" + }, + "url": { + "raw": "{{baseUrl}}/channels/private", + "host": ["{{baseUrl}}"], + "path": ["channels", "private"] + } + }, + "response": [] + }, + { + "name": "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const channel = pm.response.json();", + " const expectedId = pm.environment.get('createdPublicChannelId');", + " if (channel.id !== expectedId) {", + " throw new Error(`์ฑ„๋„ ID ๋ถˆ์ผ์น˜: ${channel.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " pm.test('๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"์ˆ˜์ •๋œ ๊ณต๊ฐœ ์ฑ„๋„\",\n \"description\": \"์ฑ„๋„ ์ •๋ณด๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/channels/{{createdPublicChannelId}}", + "host": ["{{baseUrl}}"], + "path": ["channels", "{{createdPublicChannelId}}"] + } + }, + "response": [] + }, + { + "name": "์ฑ„๋„ ์‚ญ์ œ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 204) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 204)`);", + " }", + " ", + " pm.test('์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์ฑ„๋„ ์‚ญ์ œ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/channels/{{createdPrivateChannelId}}", + "host": ["{{baseUrl}}"], + "path": ["channels", "{{createdPrivateChannelId}}"] + } + }, + "response": [] + }, + { + "name": "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const channels = pm.response.json();", + " if (!Array.isArray(channels)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (channels.length === 0) {", + " throw new Error('์ฑ„๋„ ๋ชฉ๋ก์ด ๋น„์–ด์žˆ์Œ');", + " }", + " ", + " const requiredFields = ['id', 'name'];", + " const missingFields = requiredFields.filter(field => !channels[0].hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/channels?userId={{user2Id}}", + "host": ["{{baseUrl}}"], + "path": ["channels"], + "query": [ + { + "key": "userId", + "value": "{{user2Id}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ", + "item": [ + { + "name": "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 201) {", + " const error = pm.response.json();", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 201) - ${error.message || ''}`);", + " }", + " ", + " const message = pm.response.json();", + " if (!message.id) {", + " throw new Error('์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€์— ID๊ฐ€ ์—†์Œ');", + " }", + " ", + " const requiredFields = ['id', 'content', 'channelId', 'authorId'];", + " const missingFields = requiredFields.filter(field => !message.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " // ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€ ID ์ €์žฅ", + " pm.environment.set('createdMessageId', message.id);", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"Postman ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€\",\n \"channelId\": \"{{createdPublicChannelId}}\",\n \"authorId\": \"{{user2Id}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/messages", + "host": ["{{baseUrl}}"], + "path": ["messages"] + } + }, + "response": [] + }, + { + "name": "๋ฉ”์‹œ์ง€ ์ˆ˜์ •", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const message = pm.response.json();", + " const expectedId = pm.environment.get('createdMessageId');", + " if (message.id !== expectedId) {", + " throw new Error(`๋ฉ”์‹œ์ง€ ID ๋ถˆ์ผ์น˜: ${message.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/messages/{{createdMessageId}}", + "host": ["{{baseUrl}}"], + "path": ["messages", "{{createdMessageId}}"] + } + }, + "response": [] + }, + { + "name": "๋ฉ”์‹œ์ง€ ์‚ญ์ œ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 204) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 204)`);", + " }", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/messages/{{createdMessageId}}", + "host": ["{{baseUrl}}"], + "path": ["messages", "{{createdMessageId}}"] + } + }, + "response": [] + }, + { + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const messages = pm.response.json();", + " if (!Array.isArray(messages)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " // ๋ฉ”์‹œ์ง€๊ฐ€ ์‚ญ์ œ๋˜์–ด ๋น„์–ด์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ฒฝ๊ณ ๋งŒ ์ถœ๋ ฅ", + " if (messages.length === 0) {", + " console.log('๊ฒฝ๊ณ : ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก์ด ๋น„์–ด์žˆ์Œ (์ด์ „ ํ…Œ์ŠคํŠธ์—์„œ ์‚ญ์ œ๋จ)');", + " } else {", + " // ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ํ•„๋“œ ๊ฒ€์ฆ", + " const requiredFields = ['id', 'content', 'channelId', 'authorId'];", + " const missingFields = requiredFields.filter(field => !messages[0].hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " }", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/messages?channelId={{createdPublicChannelId}}", + "host": ["{{baseUrl}}"], + "path": ["messages"], + "query": [ + { + "key": "channelId", + "value": "{{createdPublicChannelId}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ", + "item": [ + { + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 201) {", + " const error = pm.response.json();", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 201) - ${error.message || ''}`);", + " }", + " ", + " const readStatus = pm.response.json();", + " if (!readStatus.id) {", + " throw new Error('์ƒ์„ฑ๋œ ์ˆ˜์‹  ์ •๋ณด์— ID๊ฐ€ ์—†์Œ');", + " }", + " ", + " const requiredFields = ['id', 'userId', 'channelId'];", + " const missingFields = requiredFields.filter(field => !readStatus.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " // ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์ˆ˜์‹  ์ •๋ณด ID ์ €์žฅ", + " pm.environment.set('createdReadStatusId', readStatus.id);", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"userId\": \"{{user2Id}}\",\n \"channelId\": \"{{createdPublicChannelId}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/read-statuses", + "host": ["{{baseUrl}}"], + "path": ["read-statuses"] + } + }, + "response": [] + }, + { + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ •", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const readStatus = pm.response.json();", + " const expectedId = pm.environment.get('createdReadStatusId');", + " if (readStatus.id !== expectedId) {", + " throw new Error(`์ˆ˜์‹  ์ •๋ณด ID ๋ถˆ์ผ์น˜: ${readStatus.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastReadMessageId\": \"{{createdMessageId}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/read-statuses/{{createdReadStatusId}}", + "host": ["{{baseUrl}}"], + "path": ["read-statuses", "{{createdReadStatusId}}"] + } + }, + "response": [] + }, + { + "name": "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const readStatuses = pm.response.json();", + " if (!Array.isArray(readStatuses)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (readStatuses.length === 0) {", + " throw new Error('์ˆ˜์‹  ์ •๋ณด ๋ชฉ๋ก์ด ๋น„์–ด์žˆ์Œ');", + " }", + " ", + " const requiredFields = ['id', 'userId', 'channelId'];", + " const missingFields = requiredFields.filter(field => !readStatuses[0].hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/read-statuses?userId={{user2Id}}", + "host": ["{{baseUrl}}"], + "path": ["read-statuses"], + "query": [ + { + "key": "userId", + "value": "{{user2Id}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ", + "item": [ + { + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const content = pm.response.json();", + " const expectedId = pm.environment.get('user2ProfileId');", + " if (content.id !== expectedId) {", + " throw new Error(`ํŒŒ์ผ ID ๋ถˆ์ผ์น˜: ${content.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " const requiredFields = ['id', 'filename', 'contentType'];", + " const missingFields = requiredFields.filter(field => !content.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents/{{user2ProfileId}}", + "host": ["{{baseUrl}}"], + "path": ["binary-contents", "{{user2ProfileId}}"] + } + }, + "response": [] + }, + { + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ (2๋ช…)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const contents = pm.response.json();", + " if (!Array.isArray(contents)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (contents.length === 0) {", + " throw new Error('์กฐํšŒ๋œ ํŒŒ์ผ์ด ์—†์Œ');", + " }", + " ", + " pm.test('ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents?ids={{user2ProfileId}},{{user3ProfileId}}", + "host": ["{{baseUrl}}"], + "path": ["binary-contents"], + "query": [ + { + "key": "ids", + "value": "{{user2ProfileId}},{{user3ProfileId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ (์ „์ฒด)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const contents = pm.response.json();", + " if (!Array.isArray(contents)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (contents.length === 0) {", + " throw new Error('์กฐํšŒ๋œ ํŒŒ์ผ์ด ์—†์Œ');", + " }", + " ", + " pm.test('ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents?ids={{user2ProfileId}},{{user3ProfileId}},{{user4ProfileId}}", + "host": ["{{baseUrl}}"], + "path": ["binary-contents"], + "query": [ + { + "key": "ids", + "value": "{{user2ProfileId}},{{user3ProfileId}},{{user4ProfileId}}" + } + ] + } + }, + "response": [] + } + ] + } + ] +} diff --git a/discodeit/docs/Discodeit API.postman_test_run.json b/discodeit/docs/Discodeit API.postman_test_run.json new file mode 100644 index 00000000..beb22f0e --- /dev/null +++ b/discodeit/docs/Discodeit API.postman_test_run.json @@ -0,0 +1,735 @@ +{ + "id": "72aa7dc4-e6f3-482f-9d9c-1131467aaa6d", + "name": "Discodeit API", + "timestamp": "2026-02-11T13:40:36.311Z", + "collection_id": "52123612-cc7620c4-e129-4558-909c-60cb60c4a7d8", + "folder_id": 0, + "environment_id": "52123612-b20157c7-1753-4dd5-a79e-ca28791a47c2", + "totalPass": 23, + "delay": 0, + "persist": true, + "status": "finished", + "startedAt": "2026-02-11T13:40:35.589Z", + "totalFail": 0, + "results": [ + { + "id": "62f080e5-1d87-4729-b59c-d9c262cc57f6", + "name": "์‚ฌ์šฉ์ž ๋“ฑ๋ก", + "url": "http://localhost:8080/users", + "time": 81, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 81 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต": true + } + ] + }, + { + "id": "38f02c1b-1da3-4305-8a73-966ecd4f514a", + "name": "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", + "url": "http://localhost:8080/users/20000000-0000-0000-0000-000000000001", + "time": 5, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "e223f7ff-58d7-474b-916f-bb9e870340a0", + "name": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "url": "http://localhost:8080/users/199f91c7-2ba9-41d0-9cd9-6bb94a002f3b", + "time": 3, + "responseCode": { + "code": 204, + "name": "No Content" + }, + "tests": { + "์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "05657515-669f-416b-abb6-bcd7a6ffc9b1", + "name": "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ", + "url": "http://localhost:8080/users", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "3c0426e3-cad0-45a9-b870-d3542de16393", + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (์ด์˜ํฌ)", + "url": "http://localhost:8080/users/20000000-0000-0000-0000-000000000001/status", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "1fccb8ae-a199-42c2-84b1-2e6e050ac4d5", + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๋ฐ•๋ฏผ์ˆ˜)", + "url": "http://localhost:8080/users/30000000-0000-0000-0000-000000000001/status", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "e4500e3f-52b4-40ab-873a-7f0c488a79ba", + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (์ตœ์ง€์—ฐ)", + "url": "http://localhost:8080/users/40000000-0000-0000-0000-000000000001/status", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "13e2e344-786e-4ec1-bc5f-a73047a93695", + "name": "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ (๋ฐ•๋ฏผ์ˆ˜)", + "url": "http://localhost:8080/auth/login", + "time": 4, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "67a951f5-93ee-4ea9-967c-e8e29fb4b6ad", + "name": "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + "url": "http://localhost:8080/channels/public", + "time": 7, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 7 + ], + "allTests": [ + { + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "f8543bc4-6878-4e40-84b0-48293ece9e98", + "name": "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + "url": "http://localhost:8080/channels/private", + "time": 6, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "c448ff0b-1c2c-44d6-ac60-a1755ac7b888", + "name": "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", + "url": "http://localhost:8080/channels/f16f2249-9b95-4ade-8c9a-dcab427daf29", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "e7cdf82b-b3be-457d-93c9-8b4610e1e4db", + "name": "์ฑ„๋„ ์‚ญ์ œ", + "url": "http://localhost:8080/channels/3f35e0e6-5a0b-4095-aeaf-ac541b04c38b", + "time": 2, + "responseCode": { + "code": 204, + "name": "No Content" + }, + "tests": { + "์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 2 + ], + "allTests": [ + { + "์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "bd3213a7-5150-4bbe-8b10-177e2b9b15c1", + "name": "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", + "url": "http://localhost:8080/channels?userId=20000000-0000-0000-0000-000000000001", + "time": 4, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "468a25e1-0e84-435d-96da-779eb8e77281", + "name": "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ", + "url": "http://localhost:8080/messages", + "time": 5, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "1122053e-946c-44d9-a1bc-5c814f355f0b", + "name": "๋ฉ”์‹œ์ง€ ์ˆ˜์ •", + "url": "http://localhost:8080/messages/bcf42db0-a178-4c67-85b8-b00e6216ea2a", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "1cb94982-c954-4c27-bb93-441313ce2519", + "name": "๋ฉ”์‹œ์ง€ ์‚ญ์ œ", + "url": "http://localhost:8080/messages/bcf42db0-a178-4c67-85b8-b00e6216ea2a", + "time": 3, + "responseCode": { + "code": 204, + "name": "No Content" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "52706e31-cbee-4fdc-9638-44097655653d", + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", + "url": "http://localhost:8080/messages?channelId=f16f2249-9b95-4ade-8c9a-dcab427daf29", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "5b4ca621-295c-4261-a9df-9978c4f22062", + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ", + "url": "http://localhost:8080/read-statuses", + "time": 4, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "7ab4d359-041b-4b6e-b686-bebb06a082bd", + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ •", + "url": "http://localhost:8080/read-statuses/4936dd2f-9cca-460c-8bfd-1e0a6b3c84ab", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "182a1d87-c2cc-4d03-9ff1-2cc073749b91", + "name": "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ", + "url": "http://localhost:8080/read-statuses?userId=20000000-0000-0000-0000-000000000001", + "time": 2, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 2 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "2ea5804a-177f-4c81-861c-2074010d9e74", + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ", + "url": "http://localhost:8080/binary-contents/20000000-0000-0000-0000-000000000002", + "time": 7, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 7 + ], + "allTests": [ + { + "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "71b66c5b-c4c6-4fc0-9df8-15780abe0860", + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ (2๋ช…)", + "url": "http://localhost:8080/binary-contents?ids=20000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000002", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "365c8a34-d62f-413d-81e0-46e02a774df0", + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ (์ „์ฒด)", + "url": "http://localhost:8080/binary-contents?ids=20000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000002,40000000-0000-0000-0000-000000000002", + "time": 2, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 2 + ], + "allTests": [ + { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + } + ], + "count": 1, + "totalTime": 168, + "collection": { + "requests": [ + { + "id": "62f080e5-1d87-4729-b59c-d9c262cc57f6", + "method": "POST" + }, + { + "id": "38f02c1b-1da3-4305-8a73-966ecd4f514a", + "method": "PUT" + }, + { + "id": "e223f7ff-58d7-474b-916f-bb9e870340a0", + "method": "DELETE" + }, + { + "id": "05657515-669f-416b-abb6-bcd7a6ffc9b1", + "method": "GET" + }, + { + "id": "3c0426e3-cad0-45a9-b870-d3542de16393", + "method": "PUT" + }, + { + "id": "1fccb8ae-a199-42c2-84b1-2e6e050ac4d5", + "method": "PUT" + }, + { + "id": "e4500e3f-52b4-40ab-873a-7f0c488a79ba", + "method": "PUT" + }, + { + "id": "13e2e344-786e-4ec1-bc5f-a73047a93695", + "method": "POST" + }, + { + "id": "67a951f5-93ee-4ea9-967c-e8e29fb4b6ad", + "method": "POST" + }, + { + "id": "f8543bc4-6878-4e40-84b0-48293ece9e98", + "method": "POST" + }, + { + "id": "c448ff0b-1c2c-44d6-ac60-a1755ac7b888", + "method": "PUT" + }, + { + "id": "e7cdf82b-b3be-457d-93c9-8b4610e1e4db", + "method": "DELETE" + }, + { + "id": "bd3213a7-5150-4bbe-8b10-177e2b9b15c1", + "method": "GET" + }, + { + "id": "468a25e1-0e84-435d-96da-779eb8e77281", + "method": "POST" + }, + { + "id": "1122053e-946c-44d9-a1bc-5c814f355f0b", + "method": "PUT" + }, + { + "id": "1cb94982-c954-4c27-bb93-441313ce2519", + "method": "DELETE" + }, + { + "id": "52706e31-cbee-4fdc-9638-44097655653d", + "method": "GET" + }, + { + "id": "5b4ca621-295c-4261-a9df-9978c4f22062", + "method": "POST" + }, + { + "id": "7ab4d359-041b-4b6e-b686-bebb06a082bd", + "method": "PUT" + }, + { + "id": "182a1d87-c2cc-4d03-9ff1-2cc073749b91", + "method": "GET" + }, + { + "id": "2ea5804a-177f-4c81-861c-2074010d9e74", + "method": "GET" + }, + { + "id": "71b66c5b-c4c6-4fc0-9df8-15780abe0860", + "method": "GET" + }, + { + "id": "365c8a34-d62f-413d-81e0-46e02a774df0", + "method": "GET" + } + ] + } +} \ No newline at end of file diff --git a/discodeit/docs/Discodeit.postman_environment.json b/discodeit/docs/Discodeit.postman_environment.json new file mode 100644 index 00000000..9c5d1b84 --- /dev/null +++ b/discodeit/docs/Discodeit.postman_environment.json @@ -0,0 +1,111 @@ +{ + "id": "discodeit-environment", + "name": "Discodeit Environment", + "values": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "default", + "enabled": true + }, + { + "key": "user1Id", + "value": "10000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user1Name", + "value": "๊น€์ฒ ์ˆ˜", + "type": "default", + "enabled": true + }, + { + "key": "user1Email", + "value": "kimcs@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user1ProfileId", + "value": "10000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + }, + { + "key": "user2Id", + "value": "20000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user2Name", + "value": "์ด์˜ํฌ", + "type": "default", + "enabled": true + }, + { + "key": "user2Email", + "value": "leeyh@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user2ProfileId", + "value": "20000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + }, + { + "key": "user3Id", + "value": "30000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user3Name", + "value": "๋ฐ•๋ฏผ์ˆ˜", + "type": "default", + "enabled": true + }, + { + "key": "user3Email", + "value": "parkms@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user3ProfileId", + "value": "30000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + }, + { + "key": "user4Id", + "value": "40000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user4Name", + "value": "์ตœ์ง€์—ฐ", + "type": "default", + "enabled": true + }, + { + "key": "user4Email", + "value": "choijy@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user4ProfileId", + "value": "40000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-02-11T00:33:00.000Z", + "_postman_exported_using": "Postman/11.0.0" +} diff --git a/discodeit/docs/README4.md b/discodeit/docs/README4.md new file mode 100644 index 00000000..d2a3d46f --- /dev/null +++ b/discodeit/docs/README4.md @@ -0,0 +1,1545 @@ +# ๐Ÿ“š ๋””์Šค์ฝ”๋“œ์ž‡ ํ”„๋กœ์ ํŠธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ (์ดˆ๋ณด์ž์šฉ) + +> ์›น API ๊ธฐ๋ฐ˜ Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ๊ตฌ์กฐ์™€ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ + +--- + +## ๐Ÿ“– ๋ชฉ์ฐจ + +1. [ํ”„๋กœ์ ํŠธ ๊ฐœ์š”](#1-ํ”„๋กœ์ ํŠธ-๊ฐœ์š”) +2. [์ „์ฒด ์•„ํ‚คํ…์ฒ˜](#2-์ „์ฒด-์•„ํ‚คํ…์ฒ˜) +3. [๊ณ„์ธต๋ณ„ ์ƒ์„ธ ์„ค๋ช…](#3-๊ณ„์ธต๋ณ„-์ƒ์„ธ-์„ค๋ช…) +4. [์›น API ๋™์ž‘ ์›๋ฆฌ](#4-์›น-api-๋™์ž‘-์›๋ฆฌ) +5. [์ฃผ์š” ๊ธฐ๋Šฅ ๊ตฌํ˜„](#5-์ฃผ์š”-๊ธฐ๋Šฅ-๊ตฌํ˜„) +6. [๋ฐ์ดํ„ฐ ํ๋ฆ„ ์˜ˆ์‹œ](#6-๋ฐ์ดํ„ฐ-ํ๋ฆ„-์˜ˆ์‹œ) +7. [์ดˆ๋ณด์ž๋ฅผ ์œ„ํ•œ ํ•ต์‹ฌ ๊ฐœ๋…](#7-์ดˆ๋ณด์ž๋ฅผ-์œ„ํ•œ-ํ•ต์‹ฌ-๊ฐœ๋…) +8. [์‹ค์Šต ์˜ˆ์ œ](#8-์‹ค์Šต-์˜ˆ์ œ) + +--- + +## 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” + +### 1.1 ๋””์Šค์ฝ”๋“œ์ž‡์ด๋ž€? + +**๋””์Šค์ฝ”๋“œ์ž‡(DiscoDeIt)**์€ Discord์™€ ์œ ์‚ฌํ•œ ๋ฉ”์‹œ์ง• ํ”Œ๋žซํผ์„ ํ•™์Šต ๋ชฉ์ ์œผ๋กœ ๊ตฌํ˜„ํ•œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. + +**์ฃผ์š” ๊ธฐ๋Šฅ:** +- ๐Ÿ‘ค **์‚ฌ์šฉ์ž ๊ด€๋ฆฌ**: ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ํ”„๋กœํ•„ ๊ด€๋ฆฌ +- ๐Ÿ’ฌ **์ฑ„๋„ ๊ด€๋ฆฌ**: ๊ณต๊ฐœ/๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ +- ๐Ÿ“จ **๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ**: ๋ฉ”์‹œ์ง€ ์ „์†ก, ์ˆ˜์ •, ์‚ญ์ œ +- ๐Ÿ“Ž **ํŒŒ์ผ ๊ด€๋ฆฌ**: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ ์—…๋กœ๋“œ +- ๐Ÿ‘€ **์ฝ๊ธฐ ์ƒํƒœ**: ๋ฉ”์‹œ์ง€ ์ฝ์Œ/์•ˆ์ฝ์Œ ์ถ”์  +- ๐ŸŸข **์˜จ๋ผ์ธ ์ƒํƒœ**: ์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ/์˜คํ”„๋ผ์ธ ํ‘œ์‹œ + +### 1.2 ๊ธฐ์ˆ  ์Šคํƒ + +| ๋ถ„๋ฅ˜ | ๊ธฐ์ˆ  | +|------|------| +| **ํ”„๋ ˆ์ž„์›Œํฌ** | Spring Boot 3.x | +| **์–ธ์–ด** | Java 17+ | +| **๋นŒ๋“œ ๋„๊ตฌ** | Gradle | +| **๋ฐ์ดํ„ฐ ์ €์žฅ** | Java Collection Framework (HashMap) - ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ | +| **API ์Šคํƒ€์ผ** | REST API | +| **์ง๋ ฌํ™”** | Jackson (JSON) | + +### 1.3 ์™œ ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜์ธ๊ฐ€? + +**ํ•™์Šต ๋ชฉ์ :** +- JPA๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • ์—†์ด ๋น ๋ฅด๊ฒŒ ์‹œ์ž‘ ๊ฐ€๋Šฅ +- ๋ฐ์ดํ„ฐ ์ €์žฅ ๋กœ์ง์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ฉด์„œ Repository ํŒจํ„ด ์ดํ•ด +- ๋‚˜์ค‘์— JPA๋กœ ์‰ฝ๊ฒŒ ๊ต์ฒด ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ + +**์‹ค๋ฌด์—์„œ๋Š”:** +- ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” MySQL, PostgreSQL ๋“ฑ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ +- Spring Data JPA๋กœ Repository ์ž๋™ ์ƒ์„ฑ +- ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ์˜์†์„ฑ ๋ณด์žฅ + +--- + +## 2. ์ „์ฒด ์•„ํ‚คํ…์ฒ˜ + +### 2.1 ๊ณ„์ธตํ˜• ์•„ํ‚คํ…์ฒ˜ (Layered Architecture) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Client (Postman, ๋ธŒ๋ผ์šฐ์ €) โ”‚ +โ”‚ HTTP ์š”์ฒญ/์‘๋‹ต โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ JSON + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐ŸŽฏ Controller Layer (REST API) โ”‚ +โ”‚ - UserController, ChannelController, MessageController โ”‚ +โ”‚ - HTTP ์š”์ฒญ์„ ๋ฐ›์•„์„œ ์ ์ ˆํ•œ Service ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ โ”‚ +โ”‚ - Service ๊ฒฐ๊ณผ๋ฅผ HTTP ์‘๋‹ต์œผ๋กœ ๋ณ€ํ™˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ DTO (Request, Response) + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ’ผ Service Layer (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) โ”‚ +โ”‚ - BasicUserService, BasicChannelService... โ”‚ +โ”‚ - ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ (์ค‘๋ณต ์ฒดํฌ, ๊ถŒํ•œ ํ™•์ธ) โ”‚ +โ”‚ - ์—ฌ๋Ÿฌ Repository ์กฐํ•ฉํ•˜์—ฌ ๋ณต์žกํ•œ ๋กœ์ง ๊ตฌํ˜„ โ”‚ +โ”‚ - Entity โ†” DTO ๋ณ€ํ™˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ Entity + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ’พ Repository Layer (๋ฐ์ดํ„ฐ ์ ‘๊ทผ) โ”‚ +โ”‚ - JCFUserRepository, JCFChannelRepository... โ”‚ +โ”‚ - HashMap์„ ์‚ฌ์šฉํ•œ CRUD ์—ฐ์‚ฐ โ”‚ +โ”‚ - ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ๋ง โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ HashMap (๋ฉ”๋ชจ๋ฆฌ) + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ—‚๏ธ Entity Layer (๋„๋ฉ”์ธ ๋ชจ๋ธ) โ”‚ +โ”‚ - User, Channel, Message, ReadStatus... โ”‚ +โ”‚ - ๋น„์ฆˆ๋‹ˆ์Šค ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ •์˜ โ”‚ +โ”‚ - ์—”ํ‹ฐํ‹ฐ ๋‚ด๋ถ€ ๋กœ์ง (update, isOnline ๋“ฑ) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 2.2 ๊ฐ ๊ณ„์ธต์˜ ์—ญํ•  + +| ๊ณ„์ธต | ์—ญํ•  | ์˜ˆ์‹œ | +|------|------|------| +| **Controller** | HTTP ์š”์ฒญ ์ฒ˜๋ฆฌ ๋ฐ ์‘๋‹ต ๋ฐ˜ํ™˜ | `POST /users` โ†’ `createUser()` | +| **Service** | ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰ | ์ค‘๋ณต ์ฒดํฌ, ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜, ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ | +| **Repository** | ๋ฐ์ดํ„ฐ ์ €์žฅ/์กฐํšŒ | `save()`, `findById()`, `delete()` | +| **Entity** | ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜ | `User`, `Channel`, `Message` | + +### 2.3 ์˜์กด์„ฑ ๋ฐฉํ–ฅ + +``` +Controller โ†’ Service โ†’ Repository โ†’ Entity + +๊ฐ ๊ณ„์ธต์€ ๋ฐ”๋กœ ์•„๋ž˜ ๊ณ„์ธต๋งŒ ์˜์กด +Controller๋Š” Repository๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ +Service๋Š” ์—ฌ๋Ÿฌ Repository๋ฅผ ์กฐํ•ฉ ๊ฐ€๋Šฅ +``` + +--- + +## 3. ๊ณ„์ธต๋ณ„ ์ƒ์„ธ ์„ค๋ช… + +### 3.1 Entity Layer (๋„๋ฉ”์ธ ๋ชจ๋ธ) + +**์œ„์น˜:** `src/main/java/com/sprint/mission/discodeit/entity/` + +#### 3.1.1 BaseEntity + +๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์˜ ๊ณตํ†ต ์†์„ฑ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + +```java +public class BaseEntity { + private UUID id; // ๊ณ ์œ  ์‹๋ณ„์ž + private Instant createdAt; // ์ƒ์„ฑ ์‹œ๊ฐ„ + private Instant updatedAt; // ์ˆ˜์ • ์‹œ๊ฐ„ + + // ์ˆ˜์ • ์‹œ๊ฐ„ ์ž๋™ ๊ฐฑ์‹  + protected void updateTimeStamp() { + this.updatedAt = Instant.now(); + } +} +``` + +**์™œ BaseEntity๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?** +- ๋ชจ๋“  ํ…Œ์ด๋ธ”์— ๊ณตํ†ต์œผ๋กœ ํ•„์š”ํ•œ ํ•„๋“œ ์ค‘๋ณต ์ œ๊ฑฐ +- ์ƒ์„ฑ/์ˆ˜์ • ์‹œ๊ฐ„ ์ž๋™ ๊ด€๋ฆฌ +- ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ ํ–ฅ์ƒ + +#### 3.1.2 User (์‚ฌ์šฉ์ž) + +```java +@Getter +public class User extends BaseEntity { + private String username; // ์‚ฌ์šฉ์ž๋ช… (๋กœ๊ทธ์ธ ID) + private String email; // ์ด๋ฉ”์ผ + private String password; // ๋น„๋ฐ€๋ฒˆํ˜ธ (์‹ค๋ฌด: ์•”ํ˜ธํ™” ํ•„์š”) + private UUID profileId; // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (BinaryContent ์ฐธ์กฐ) + + public void update(String username, String email, + String password, UUID profileId) { + // null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ + if (username != null) this.username = username; + if (email != null) this.email = email; + // ... + updateTimeStamp(); // ์ˆ˜์ • ์‹œ๊ฐ„ ๊ฐฑ์‹  + } +} +``` + +**ํ•ต์‹ฌ ํฌ์ธํŠธ:** +- `@Getter`: Lombok์œผ๋กœ getter ๋ฉ”์„œ๋“œ ์ž๋™ ์ƒ์„ฑ +- `profileId`: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ID๋งŒ ์ฐธ์กฐ +- `update()`: ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ์ง€์› (null ๊ฐ’์€ ์œ ์ง€) + +#### 3.1.3 Channel (์ฑ„๋„) + +```java +@Getter +public class Channel extends BaseEntity { + private ChannelType type; // PUBLIC ๋˜๋Š” PRIVATE + private String name; // ์ฑ„๋„ ์ด๋ฆ„ + private String description; // ์ฑ„๋„ ์„ค๋ช… + + // PUBLIC ์ฑ„๋„ ์ƒ์„ฑ์ž + public Channel(String name, String description) { + super(); + this.type = ChannelType.PUBLIC; + this.name = name; + this.description = description; + } + + // PRIVATE ์ฑ„๋„ ์ƒ์„ฑ์ž + public Channel() { + super(); + this.type = ChannelType.PRIVATE; + } +} +``` + +**์ฑ„๋„ ํƒ€์ž… ์ฐจ์ด:** +- **PUBLIC**: ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅ, ์ด๋ฆ„๊ณผ ์„ค๋ช…์ด ์žˆ์Œ +- **PRIVATE**: ์ดˆ๋Œ€๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ, ์ด๋ฆ„ ์—†์Œ (1:1 ๋˜๋Š” ๊ทธ๋ฃน DM) + +#### 3.1.4 Message (๋ฉ”์‹œ์ง€) + +```java +@Getter +public class Message extends BaseEntity { + private String content; // ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ + private UUID channelId; // ์–ด๋А ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€์ธ์ง€ + private UUID authorId; // ๋ˆ„๊ฐ€ ์ž‘์„ฑํ–ˆ๋Š”์ง€ + private List attachmentIds; // ์ฒจ๋ถ€ํŒŒ์ผ ID ๋ชฉ๋ก + + public void update(String content) { + if (content != null) { + this.content = content; + updateTimeStamp(); + } + } +} +``` + +**๊ด€๊ณ„:** +- `channelId` โ†’ Channel ์ฐธ์กฐ +- `authorId` โ†’ User ์ฐธ์กฐ +- `attachmentIds` โ†’ BinaryContent ๋ชฉ๋ก ์ฐธ์กฐ + +#### 3.1.5 UserStatus (์‚ฌ์šฉ์ž ์ƒํƒœ) + +```java +@Getter +public class UserStatus extends BaseEntity { + private UUID userId; // ์‚ฌ์šฉ์ž ID + private Instant lastActiveAt; // ๋งˆ์ง€๋ง‰ ํ™œ๋™ ์‹œ๊ฐ„ + + // 5๋ถ„ ์ด๋‚ด ํ™œ๋™ํ–ˆ์œผ๋ฉด ์˜จ๋ผ์ธ + public boolean isOnline() { + return Duration.between(lastActiveAt, Instant.now()) + .toMinutes() < 5; + } +} +``` + +**์˜จ๋ผ์ธ ํŒ๋‹จ ๋กœ์ง:** +- ๋งˆ์ง€๋ง‰ ํ™œ๋™ ์‹œ๊ฐ„์ด ํ˜„์žฌ๋กœ๋ถ€ํ„ฐ 5๋ถ„ ์ด๋‚ด โ†’ ์˜จ๋ผ์ธ +- 5๋ถ„ ์ด์ƒ ๊ฒฝ๊ณผ โ†’ ์˜คํ”„๋ผ์ธ + +#### 3.1.6 ReadStatus (์ฝ๊ธฐ ์ƒํƒœ) + +```java +@Getter +public class ReadStatus extends BaseEntity { + private UUID userId; // ์‚ฌ์šฉ์ž ID + private UUID channelId; // ์ฑ„๋„ ID + private Instant lastReadAt; // ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝ์€ ์‹œ๊ฐ„ +} +``` + +**๋‘ ๊ฐ€์ง€ ์—ญํ• :** +1. **PRIVATE ์ฑ„๋„ ์ ‘๊ทผ ์ œ์–ด**: ReadStatus๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ์ฑ„๋„ ๋ณผ ์ˆ˜ ์žˆ์Œ +2. **๋ฉ”์‹œ์ง€ ์ฝ์Œ ์ถ”์ **: ์–ธ์ œ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์—ˆ๋Š”์ง€ ๊ธฐ๋ก + +--- + +### 3.2 Repository Layer (๋ฐ์ดํ„ฐ ์ ‘๊ทผ) + +**์œ„์น˜:** `src/main/java/com/sprint/mission/discodeit/repository/jcf/` + +#### 3.2.1 Repository ์ธํ„ฐํŽ˜์ด์Šค + +```java +public interface UserRepository { + User save(User user); // ์ €์žฅ + Optional findById(UUID id); // ID๋กœ ์กฐํšŒ + Optional findByUsername(String username); // username์œผ๋กœ ์กฐํšŒ + List findAll(); // ์ „์ฒด ์กฐํšŒ + void deleteById(UUID id); // ์‚ญ์ œ + boolean existsByUsername(String username); // ์ค‘๋ณต ์ฒดํฌ +} +``` + +**์™œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€?** +- JCF ๊ตฌํ˜„์ฒด๋ฅผ ๋‚˜์ค‘์— JPA ๊ตฌํ˜„์ฒด๋กœ ์‰ฝ๊ฒŒ ๊ต์ฒด ๊ฐ€๋Šฅ +- ํ…Œ์ŠคํŠธ ์‹œ Mock ๊ฐ์ฒด ์‚ฌ์šฉ ๊ฐ€๋Šฅ +- ์ฝ”๋“œ ์˜์กด์„ฑ์„ ์ธํ„ฐํŽ˜์ด์Šค์—๋งŒ ๋‘์–ด ๊ฒฐํ•ฉ๋„ ๋‚ฎ์ถค + +#### 3.2.2 JCF ๊ตฌํ˜„์ฒด + +```java +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFUserRepository implements UserRepository { + // HashMap์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ ์ €์žฅ + private final Map data = new HashMap<>(); + + @Override + public User save(User user) { + data.put(user.getId(), user); + return user; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(data.get(id)); + } + + @Override + public Optional findByUsername(String username) { + return data.values().stream() + .filter(user -> user.getUsername().equals(username)) + .findFirst(); + } + + // ... ๊ธฐํƒ€ ๋ฉ”์„œ๋“œ +} +``` + +**ํ•ต์‹ฌ ํฌ์ธํŠธ:** +- `@ConditionalOnProperty`: ์„ค์ •์— ๋”ฐ๋ผ ์ด ๊ตฌํ˜„์ฒด ํ™œ์„ฑํ™” +- `HashMap`: ID๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๋น ๋ฅธ ์กฐํšŒ +- `Stream API`: ๋ณต์žกํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ฒ˜๋ฆฌ (username, email ๋“ฑ) + +**์žฅ๋‹จ์ :** + +| ์žฅ์  | ๋‹จ์  | +|------|------| +| โœ… ์„ค์ • ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ | โŒ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ ์†์‹ค | +| โœ… ๋น ๋ฅธ CRUD ์—ฐ์‚ฐ (๋ฉ”๋ชจ๋ฆฌ) | โŒ ๋™์‹œ์„ฑ ๋ฌธ์ œ (HashMap์€ thread-safe ํ•˜์ง€ ์•Š์Œ) | +| โœ… ํ•™์Šต์šฉ์œผ๋กœ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›€ | โŒ ๋ฉ”๋ชจ๋ฆฌ ์šฉ๋Ÿ‰ ์ œํ•œ | + +--- + +### 3.3 Service Layer (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) + +**์œ„์น˜:** `src/main/java/com/sprint/mission/discodeit/service/basic/` + +#### 3.3.1 BasicUserService + +```java +@Service +@RequiredArgsConstructor +public class BasicUserService implements UserService { + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + private final UserStatusRepository userStatusRepository; + + @Override + public UserResponse create(UserCreateRequest request, + BinaryContentCreateRequest profileRequest) { + // 1. ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ + if (userRepository.existsByUsername(request.username())) { + throw new IllegalArgumentException( + "์ด ์‚ฌ์šฉ์ž ์ด๋ฆ„์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: " + request.username() + ); + } + + if (userRepository.existsByEmail(request.email())) { + throw new IllegalArgumentException( + "์ด ์ด๋ฉ”์ผ์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: " + request.email() + ); + } + + // 2. ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ €์žฅ (์žˆ๋Š” ๊ฒฝ์šฐ) + UUID profileId = null; + if (profileRequest != null) { + BinaryContent profile = new BinaryContent( + profileRequest.fileName(), + profileRequest.contentType(), + profileRequest.data() + ); + profileId = binaryContentRepository.save(profile).getId(); + } + + // 3. User ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ ๋ฐ ์ €์žฅ + User user = new User( + request.username(), + request.email(), + request.password(), + profileId + ); + User savedUser = userRepository.save(user); + + // 4. UserStatus ์ƒ์„ฑ (์ดˆ๊ธฐ๊ฐ’: ํ˜„์žฌ ์‹œ๊ฐ„) + UserStatus userStatus = new UserStatus( + savedUser.getId(), + Instant.now() + ); + userStatusRepository.save(userStatus); + + // 5. Entity๋ฅผ DTO๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ + return toUserResponse(savedUser, true); + } + + private UserResponse toUserResponse(User user, boolean isOnline) { + return new UserResponse( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getProfileId(), + isOnline, // ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ œ์™ธ! + user.getCreatedAt(), + user.getUpdatedAt() + ); + } +} +``` + +**Service Layer์˜ ํ•ต์‹ฌ ์—ญํ• :** + +1. **๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ** + - ์ค‘๋ณต ์ฒดํฌ (username, email) + - ๊ถŒํ•œ ํ™•์ธ + - ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + +2. **์—ฌ๋Ÿฌ Repository ์กฐํ•ฉ** + - User + BinaryContent + UserStatus๋ฅผ ํ•จ๊ป˜ ๊ด€๋ฆฌ + - ํŠธ๋žœ์žญ์…˜ ๋‹จ์œ„ ์ž‘์—… (์‹ค๋ฌด์—์„œ๋Š” `@Transactional`) + +3. **Entity โ†” DTO ๋ณ€ํ™˜** + - ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ™์€ ๋ฏผ๊ฐ ์ •๋ณด ์ œ์™ธ + - ์ถ”๊ฐ€ ์ •๋ณด ๊ณ„์‚ฐ (isOnline) + +#### 3.3.2 BasicChannelService + +```java +@Service +@RequiredArgsConstructor +public class BasicChannelService implements ChannelService { + private final ChannelRepository channelRepository; + private final ReadStatusRepository readStatusRepository; + private final MessageRepository messageRepository; + private final BinaryContentRepository binaryContentRepository; + + @Override + public ChannelResponse createPrivate(PrivateChannelCreateRequest request) { + // 1. PRIVATE ์ฑ„๋„ ์ƒ์„ฑ + Channel channel = new Channel(); // ์ด๋ฆ„ ์—†๋Š” PRIVATE ์ฑ„๋„ + Channel savedChannel = channelRepository.save(channel); + + // 2. ๊ฐ ์ฐธ์—ฌ์ž์—๊ฒŒ ReadStatus ์ƒ์„ฑ (์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌ) + for (UUID memberId : request.memberIds()) { + ReadStatus readStatus = new ReadStatus( + memberId, + savedChannel.getId(), + Instant.now() + ); + readStatusRepository.save(readStatus); + } + + return toChannelResponse(savedChannel); + } + + @Override + public void delete(UUID id) { + // ์—ฐ์‡„ ์‚ญ์ œ (Cascade Delete) + Channel channel = channelRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException( + "Channel not found: " + id + )); + + // 1. ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์กฐํšŒ + List messages = messageRepository.findAllByChannelId(id); + + // 2. ๊ฐ ๋ฉ”์‹œ์ง€์˜ ์ฒจ๋ถ€ํŒŒ์ผ ์‚ญ์ œ + for (Message message : messages) { + for (UUID attachmentId : message.getAttachmentIds()) { + binaryContentRepository.deleteById(attachmentId); + } + } + + // 3. ๋ฉ”์‹œ์ง€ ์ผ๊ด„ ์‚ญ์ œ + messageRepository.deleteAllByChannelId(id); + + // 4. ReadStatus ์ผ๊ด„ ์‚ญ์ œ + readStatusRepository.deleteAllByChannelId(id); + + // 5. ์ตœ์ข…์ ์œผ๋กœ ์ฑ„๋„ ์‚ญ์ œ + channelRepository.deleteById(id); + } +} +``` + +**PRIVATE ์ฑ„๋„์˜ ์ ‘๊ทผ ์ œ์–ด:** + +```java +@Override +public List findAllByUserId(UUID userId) { + List allChannels = channelRepository.findAll(); + + return allChannels.stream() + .filter(channel -> { + if (channel.getType() == ChannelType.PUBLIC) { + return true; // PUBLIC์€ ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅ + } + // PRIVATE์€ ReadStatus๊ฐ€ ์žˆ์–ด์•ผ ์ ‘๊ทผ ๊ฐ€๋Šฅ + return readStatusRepository + .findByUserIdAndChannelId(userId, channel.getId()) + .isPresent(); + }) + .map(this::toChannelResponse) + .toList(); +} +``` + +**์—ฐ์‡„ ์‚ญ์ œ ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ :** +1. ์ฒจ๋ถ€ํŒŒ์ผ ๋จผ์ € ์‚ญ์ œ (๋ฉ”์‹œ์ง€๊ฐ€ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ) +2. ๋ฉ”์‹œ์ง€ ์‚ญ์ œ (์ฑ„๋„์ด ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ) +3. ReadStatus ์‚ญ์ œ (์ฑ„๋„์„ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ) +4. ๋งˆ์ง€๋ง‰์— ์ฑ„๋„ ์‚ญ์ œ + +--- + +### 3.4 Controller Layer (REST API) + +**์œ„์น˜:** `src/main/java/com/sprint/mission/discodeit/controller/` + +#### 3.4.1 UserController + +```java +@RestController +@RequestMapping("/users") +@RequiredArgsConstructor +public class UserController { + private final UserService userService; + private final UserStatusService userStatusService; + + /** + * ์‚ฌ์šฉ์ž ๋“ฑ๋ก + * POST /users + * + * ์š”์ฒญ ๋ณธ๋ฌธ: + * { + * "username": "user1", + * "email": "user1@example.com", + * "password": "password123" + * } + * + * ์‘๋‹ต: 201 Created + UserResponse + */ + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity createUser( + @RequestBody UserCreateRequest userRequest + ) { + UserResponse user = userService.create(userRequest, null); + return ResponseEntity.status(HttpStatus.CREATED).body(user); + } + + /** + * ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ + * GET /users + * + * ์‘๋‹ต: 200 OK + List + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getAllUsers() { + List users = userService.findAll(); + return ResponseEntity.ok(users); + } + + /** + * ์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + * PUT /users/{id}/status + * + * ์š”์ฒญ ๋ณธ๋ฌธ: + * { + * "lastActiveAt": "2024-01-15T10:30:00Z" + * } + * + * ์‘๋‹ต: 200 OK + UserStatus + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}/status") + public ResponseEntity updateUserStatus( + @PathVariable UUID id, + @RequestBody UserStatusUpdateRequest request + ) { + UserStatus userStatus = userStatusService.updateByUserId(id, request); + return ResponseEntity.ok(userStatus); + } +} +``` + +**Spring MVC ์ฃผ์š” ์–ด๋…ธํ…Œ์ด์…˜:** + +| ์–ด๋…ธํ…Œ์ด์…˜ | ์—ญํ•  | ์˜ˆ์‹œ | +|-----------|------|------| +| `@RestController` | REST API ์ปจํŠธ๋กค๋Ÿฌ์ž„์„ ํ‘œ์‹œ | JSON ์‘๋‹ต ์ž๋™ ๋ณ€ํ™˜ | +| `@RequestMapping("/users")` | ๊ธฐ๋ณธ URL ๊ฒฝ๋กœ | ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— `/users` ์ ‘๋‘์‚ฌ | +| `@RequestBody` | HTTP ์š”์ฒญ ๋ณธ๋ฌธ์„ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ | JSON โ†’ UserCreateRequest | +| `@PathVariable` | URL ๊ฒฝ๋กœ์˜ ๋ณ€์ˆ˜ ์ถ”์ถœ | `/users/{id}` โ†’ `UUID id` | +| `@RequestParam` | ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”์ถœ | `?userId=xxx` โ†’ `UUID userId` | + +**HTTP ์ƒํƒœ ์ฝ”๋“œ:** + +| ์ƒํƒœ ์ฝ”๋“œ | ์˜๋ฏธ | ์‚ฌ์šฉ ์‹œ์  | +|----------|------|----------| +| **200 OK** | ์„ฑ๊ณต | ์กฐํšŒ, ์ˆ˜์ • ์„ฑ๊ณต | +| **201 Created** | ์ƒ์„ฑ ์„ฑ๊ณต | ์‚ฌ์šฉ์ž ๋“ฑ๋ก, ์ฑ„๋„ ์ƒ์„ฑ | +| **204 No Content** | ์„ฑ๊ณต (์‘๋‹ต ๋ณธ๋ฌธ ์—†์Œ) | ์‚ญ์ œ ์„ฑ๊ณต | +| **404 Not Found** | ๋ฆฌ์†Œ์Šค ์—†์Œ | ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID ์กฐํšŒ | +| **400 Bad Request** | ์ž˜๋ชป๋œ ์š”์ฒญ | ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ | + +#### 3.4.2 ChannelController + +```java +@RestController +@RequestMapping("/channels") +@RequiredArgsConstructor +public class ChannelController { + private final ChannelService channelService; + + /** + * ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + * POST /channels/public + */ + @RequestMapping(method = RequestMethod.POST, value = "/public") + public ResponseEntity createPublicChannel( + @RequestBody PublicChannelCreateRequest request + ) { + ChannelResponse channel = channelService.createPublic(request); + return ResponseEntity.status(HttpStatus.CREATED).body(channel); + } + + /** + * ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + * POST /channels/private + */ + @RequestMapping(method = RequestMethod.POST, value = "/private") + public ResponseEntity createPrivateChannel( + @RequestBody PrivateChannelCreateRequest request + ) { + ChannelResponse channel = channelService.createPrivate(request); + return ResponseEntity.status(HttpStatus.CREATED).body(channel); + } + + /** + * ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ฑ„๋„ ์กฐํšŒ + * GET /channels?userId={userId} + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getChannelsByUser( + @RequestParam UUID userId + ) { + List channels = channelService.findAllByUserId(userId); + return ResponseEntity.ok(channels); + } +} +``` + +--- + +### 3.5 DTO Layer (๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ์ฒด) + +**์œ„์น˜:** `src/main/java/com/sprint/mission/discodeit/dto/` + +#### 3.5.1 Request DTO + +```java +// ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์š”์ฒญ +public record UserCreateRequest( + String username, + String email, + String password +) {} + +// ๋กœ๊ทธ์ธ ์š”์ฒญ +public record LoginRequest( + String username, + String password +) {} + +// ๋ฉ”์‹œ์ง€ ์ „์†ก ์š”์ฒญ +public record MessageCreateRequest( + String content, + UUID channelId, + UUID authorId +) {} +``` + +**Java Record์˜ ์žฅ์ :** +- ๋ถˆ๋ณ€ ๊ฐ์ฒด (Immutable) +- getter ์ž๋™ ์ƒ์„ฑ (`request.username()`) +- `equals()`, `hashCode()`, `toString()` ์ž๋™ ์ƒ์„ฑ +- ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ + +#### 3.5.2 Response DTO + +```java +// ์‚ฌ์šฉ์ž ์‘๋‹ต +public record UserResponse( + UUID id, + String username, + String email, + UUID profileId, + boolean isOnline, // ๊ณ„์‚ฐ๋œ ๊ฐ’ + Instant createdAt, + Instant updatedAt + // password๋Š” ํฌํ•จํ•˜์ง€ ์•Š์Œ! +) {} + +// ์ฑ„๋„ ์‘๋‹ต +public record ChannelResponse( + UUID id, + ChannelType type, + String name, + String description, + List participantIds, + Instant lastMessageAt, + Instant createdAt, + Instant updatedAt +) {} +``` + +**Entity vs DTO ์ฐจ์ด:** + +| ๊ตฌ๋ถ„ | Entity | DTO | +|------|--------|-----| +| **๋ชฉ์ ** | ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ | ๋„คํŠธ์›Œํฌ ์ „์†ก | +| **๋ณ€๊ฒฝ** | ๊ฐ€๋ณ€ (Mutable) | ๋ถˆ๋ณ€ (Immutable) | +| **์ •๋ณด** | ๋ชจ๋“  ์ •๋ณด ํฌํ•จ | ํ•„์š”ํ•œ ์ •๋ณด๋งŒ | +| **๋ฏผ๊ฐ ์ •๋ณด** | ํฌํ•จ (password) | ์ œ์™ธ | +| **์ถ”๊ฐ€ ์ •๋ณด** | ์—†์Œ | ๊ณ„์‚ฐ๋œ ๊ฐ’ ํฌํ•จ (isOnline) | + +--- + +## 4. ์›น API ๋™์ž‘ ์›๋ฆฌ + +### 4.1 HTTP ์š”์ฒญ๋ถ€ํ„ฐ ์‘๋‹ต๊นŒ์ง€์˜ ํ๋ฆ„ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 1๏ธโƒฃ HTTP ์š”์ฒญ โ”‚ +โ”‚ POST http://localhost:8080/users โ”‚ +โ”‚ Content-Type: application/json โ”‚ +โ”‚ Body: {"username":"user1","email":"...","password":"..."} โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ†“ Jackson์ด JSON์„ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 2๏ธโƒฃ Spring MVC (DispatcherServlet) โ”‚ +โ”‚ - URL ๋งคํ•‘ ํ™•์ธ: /users โ†’ UserController โ”‚ +โ”‚ - HTTP ๋ฉ”์„œ๋“œ ํ™•์ธ: POST โ†’ createUser() โ”‚ +โ”‚ - @RequestBody๋กœ UserCreateRequest ๊ฐ์ฒด ์ƒ์„ฑ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 3๏ธโƒฃ UserController.createUser() โ”‚ +โ”‚ @RequestMapping(method = RequestMethod.POST) โ”‚ +โ”‚ public ResponseEntity createUser( โ”‚ +โ”‚ @RequestBody UserCreateRequest request โ”‚ +โ”‚ ) { โ”‚ +โ”‚ UserResponse user = userService.create(request, null);โ”‚ +โ”‚ return ResponseEntity.status(201).body(user); โ”‚ +โ”‚ } โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ†“ Service ํ˜ธ์ถœ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 4๏ธโƒฃ BasicUserService.create() โ”‚ +โ”‚ 1. ์ค‘๋ณต ์ฒดํฌ (userRepository.existsByUsername) โ”‚ +โ”‚ 2. ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ €์žฅ (binaryContentRepository.save) โ”‚ +โ”‚ 3. User ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ ๋ฐ ์ €์žฅ (userRepository.save) โ”‚ +โ”‚ 4. UserStatus ์ƒ์„ฑ ๋ฐ ์ €์žฅ (userStatusRepository.save) โ”‚ +โ”‚ 5. UserResponse DTO๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ†“ Repository ํ˜ธ์ถœ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 5๏ธโƒฃ JCFUserRepository.save() โ”‚ +โ”‚ private Map data = new HashMap<>(); โ”‚ +โ”‚ โ”‚ +โ”‚ public User save(User user) { โ”‚ +โ”‚ data.put(user.getId(), user); โ”‚ +โ”‚ return user; โ”‚ +โ”‚ } โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ†“ ๋ฐ์ดํ„ฐ ์ €์žฅ ์™„๋ฃŒ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 6๏ธโƒฃ UserResponse ๋ฐ˜ํ™˜ โ”‚ +โ”‚ Service โ†’ Controller๋กœ UserResponse ๋ฐ˜ํ™˜ โ”‚ +โ”‚ { โ”‚ +โ”‚ "id": "uuid-value", โ”‚ +โ”‚ "username": "user1", โ”‚ +โ”‚ "email": "user1@example.com", โ”‚ +โ”‚ "isOnline": true, โ”‚ +โ”‚ "createdAt": "2024-01-15T10:30:00Z" โ”‚ +โ”‚ } โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ†“ Jackson์ด Java ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ 7๏ธโƒฃ HTTP ์‘๋‹ต โ”‚ +โ”‚ HTTP/1.1 201 Created โ”‚ +โ”‚ Content-Type: application/json โ”‚ +โ”‚ Body: {"id":"...","username":"user1",...} โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 4.2 Spring Boot์˜ ์ž๋™ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ + +**Spring Boot๊ฐ€ ์ž๋™์œผ๋กœ ํ•ด์ฃผ๋Š” ์ผ:** + +1. **JSON โ†” Java ๊ฐ์ฒด ๋ณ€ํ™˜ (Jackson)** + - ์š”์ฒญ: JSON โ†’ `UserCreateRequest` + - ์‘๋‹ต: `UserResponse` โ†’ JSON + +2. **์˜์กด์„ฑ ์ฃผ์ž… (Dependency Injection)** + - `@RequiredArgsConstructor`๋กœ ์ƒ์„ฑ์ž ์ž๋™ ์ƒ์„ฑ + - Controller์— Service ์ž๋™ ์ฃผ์ž… + - Service์— Repository ์ž๋™ ์ฃผ์ž… + +3. **HTTP ์ƒํƒœ ์ฝ”๋“œ ์„ค์ •** + - `ResponseEntity.status(HttpStatus.CREATED)` โ†’ `201 Created` + - `ResponseEntity.ok()` โ†’ `200 OK` + +4. **์˜ˆ์™ธ ์ฒ˜๋ฆฌ (GlobalExceptionHandler)** + - `NoSuchElementException` โ†’ `404 Not Found` + - `IllegalArgumentException` โ†’ `400 Bad Request` + +--- + +## 5. ์ฃผ์š” ๊ธฐ๋Šฅ ๊ตฌํ˜„ + +### 5.1 ์‚ฌ์šฉ์ž ์ธ์ฆ (๋กœ๊ทธ์ธ) + +```java +@Service +@RequiredArgsConstructor +public class BasicAuthService implements AuthService { + private final UserRepository userRepository; + private final UserStatusRepository userStatusRepository; + + @Override + public UserResponse login(LoginRequest request) { + // 1. username์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฒ€์ƒ‰ + User user = userRepository.findByUsername(request.username()) + .orElseThrow(() -> new NoSuchElementException( + "User not found with username: " + request.username() + )); + + // 2. ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ (์‹ค๋ฌด์—์„œ๋Š” ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต) + if (!user.getPassword().equals(request.password())) { + throw new IllegalArgumentException("Invalid password"); + } + + // 3. ์˜จ๋ผ์ธ ์ƒํƒœ ์กฐํšŒ + boolean isOnline = userStatusRepository.findByUserId(user.getId()) + .map(UserStatus::isOnline) + .orElse(false); + + // 4. UserResponse ๋ฐ˜ํ™˜ + return new UserResponse( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getProfileId(), + isOnline, + user.getCreatedAt(), + user.getUpdatedAt() + ); + } +} +``` + +**์‹ค๋ฌด์™€์˜ ์ฐจ์ด:** +- **ํ˜„์žฌ**: ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต (`password.equals()`) +- **์‹ค๋ฌด**: BCrypt, Argon2 ๋“ฑ์œผ๋กœ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต +- **์‹ค๋ฌด**: JWT ํ† ํฐ ๋ฐœ๊ธ‰, ์„ธ์…˜ ๊ด€๋ฆฌ + +### 5.2 PRIVATE ์ฑ„๋„ ์ƒ์„ฑ + +```java +@Override +public ChannelResponse createPrivate(PrivateChannelCreateRequest request) { + // 1. PRIVATE ์ฑ„๋„ ์ƒ์„ฑ (์ด๋ฆ„ ์—†์Œ) + Channel channel = new Channel(); // type = PRIVATE + Channel savedChannel = channelRepository.save(channel); + + // 2. ๊ฐ ์ฐธ์—ฌ์ž์—๊ฒŒ ReadStatus ์ƒ์„ฑ (์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌ) + for (UUID memberId : request.memberIds()) { + ReadStatus readStatus = new ReadStatus( + memberId, + savedChannel.getId(), + Instant.now() + ); + readStatusRepository.save(readStatus); + } + + // 3. ChannelResponse ๋ฐ˜ํ™˜ + return new ChannelResponse( + savedChannel.getId(), + savedChannel.getType(), + null, // PRIVATE ์ฑ„๋„์€ ์ด๋ฆ„ ์—†์Œ + null, // ์„ค๋ช…๋„ ์—†์Œ + request.memberIds(), // ์ฐธ์—ฌ์ž ๋ชฉ๋ก + null, + savedChannel.getCreatedAt(), + savedChannel.getUpdatedAt() + ); +} +``` + +**ReadStatus์˜ ์—ญํ• :** +- PRIVATE ์ฑ„๋„ ์ƒ์„ฑ ์‹œ ๊ฐ ์ฐธ์—ฌ์ž์—๊ฒŒ ReadStatus ์ƒ์„ฑ +- ๋‚˜์ค‘์— ์ฑ„๋„ ์กฐํšŒ ์‹œ ReadStatus๋กœ ์ ‘๊ทผ ๊ถŒํ•œ ํ™•์ธ + +### 5.3 ๋ฉ”์‹œ์ง€ ์ „์†ก ๋ฐ ์ฒจ๋ถ€ํŒŒ์ผ + +```java +@Override +public MessageResponse create(MessageCreateRequest request, + List attachmentRequests) { + // 1. ์ฒจ๋ถ€ํŒŒ์ผ ์ €์žฅ + List attachmentIds = new ArrayList<>(); + if (attachmentRequests != null) { + for (BinaryContentCreateRequest attachmentRequest : attachmentRequests) { + BinaryContent attachment = new BinaryContent( + attachmentRequest.fileName(), + attachmentRequest.contentType(), + attachmentRequest.data() + ); + UUID attachmentId = binaryContentRepository.save(attachment).getId(); + attachmentIds.add(attachmentId); + } + } + + // 2. Message ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ ๋ฐ ์ €์žฅ + Message message = new Message( + request.content(), + request.channelId(), + request.authorId(), + attachmentIds + ); + Message savedMessage = messageRepository.save(message); + + // 3. MessageResponse ๋ฐ˜ํ™˜ + return toMessageResponse(savedMessage); +} +``` + +**์ฒจ๋ถ€ํŒŒ์ผ ์ฒ˜๋ฆฌ ์ˆœ์„œ:** +1. ์ฒจ๋ถ€ํŒŒ์ผ์„ ๋จผ์ € BinaryContent๋กœ ์ €์žฅ +2. ์ €์žฅ๋œ ํŒŒ์ผ์˜ ID ๋ชฉ๋ก ์ˆ˜์ง‘ +3. Message ์—”ํ‹ฐํ‹ฐ์— ID ๋ชฉ๋ก ์ €์žฅ + +--- + +## 6. ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์˜ˆ์‹œ + +### 6.1 ์‚ฌ์šฉ์ž ๋“ฑ๋ก ์ „์ฒด ํ๋ฆ„ + +``` +[Postman] +POST http://localhost:8080/users +Body: { + "username": "alice", + "email": "alice@example.com", + "password": "pass123" +} + +โ†“ HTTP ์š”์ฒญ + +[UserController] +createUser(UserCreateRequest) + โ†“ Service ํ˜ธ์ถœ + +[BasicUserService] +create(UserCreateRequest, null) + โ”œโ”€ userRepository.existsByUsername("alice") โ†’ false โœ… + โ”œโ”€ userRepository.existsByEmail("alice@example.com") โ†’ false โœ… + โ”œโ”€ new User("alice", "alice@example.com", "pass123", null) + โ”œโ”€ userRepository.save(user) + โ”‚ โ†“ + โ”‚ [JCFUserRepository] + โ”‚ data.put(user.getId(), user) โ†’ HashMap์— ์ €์žฅ + โ”‚ โ†‘ + โ”œโ”€ new UserStatus(userId, Instant.now()) + โ”œโ”€ userStatusRepository.save(userStatus) + โ”‚ โ†“ + โ”‚ [JCFUserStatusRepository] + โ”‚ data.put(userStatus.getId(), userStatus) + โ”‚ โ†‘ + โ””โ”€ toUserResponse(user, true) + โ†“ + +[UserController] +ResponseEntity.status(201).body(userResponse) + โ†“ HTTP ์‘๋‹ต + +[Postman] +HTTP/1.1 201 Created +{ + "id": "uuid-generated", + "username": "alice", + "email": "alice@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2024-01-15T10:30:00Z", + "updatedAt": "2024-01-15T10:30:00Z" +} +``` + +### 6.2 ์ฑ„๋„ ์‚ญ์ œ ์ „์ฒด ํ๋ฆ„ (์—ฐ์‡„ ์‚ญ์ œ) + +``` +[Postman] +DELETE http://localhost:8080/channels/{channelId} + +โ†“ + +[ChannelController] +deleteChannel(UUID id) + โ†“ + +[BasicChannelService] +delete(UUID id) + โ”œโ”€ channelRepository.findById(id) โ†’ Channel ํ™•์ธ + โ”‚ + โ”œโ”€ messageRepository.findAllByChannelId(id) โ†’ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ + โ”‚ [๋ฉ”์‹œ์ง€1, ๋ฉ”์‹œ์ง€2, ๋ฉ”์‹œ์ง€3] + โ”‚ + โ”œโ”€ for (Message message : messages) { + โ”‚ for (UUID attachmentId : message.getAttachmentIds()) { + โ”‚ โ”œโ”€ binaryContentRepository.deleteById(attachmentId) + โ”‚ โ”‚ โ†“ + โ”‚ โ”‚ [JCFBinaryContentRepository] + โ”‚ โ”‚ data.remove(attachmentId) โ†’ ์ฒจ๋ถ€ํŒŒ์ผ ์‚ญ์ œ + โ”‚ โ”‚ โ†‘ + โ”‚ } + โ”‚ } + โ”‚ + โ”œโ”€ messageRepository.deleteAllByChannelId(id) + โ”‚ โ†“ + โ”‚ [JCFMessageRepository] + โ”‚ data.values().removeIf(m -> m.getChannelId().equals(id)) + โ”‚ โ†‘ + โ”‚ + โ”œโ”€ readStatusRepository.deleteAllByChannelId(id) + โ”‚ โ†“ + โ”‚ [JCFReadStatusRepository] + โ”‚ data.values().removeIf(r -> r.getChannelId().equals(id)) + โ”‚ โ†‘ + โ”‚ + โ””โ”€ channelRepository.deleteById(id) + โ†“ + [JCFChannelRepository] + data.remove(id) โ†’ ์ฑ„๋„ ์‚ญ์ œ + โ†‘ + +[ChannelController] +ResponseEntity.noContent().build() + โ†“ + +[Postman] +HTTP/1.1 204 No Content +``` + +**์‚ญ์ œ ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ :** +- ๋ฉ”์‹œ์ง€๊ฐ€ ์ฒจ๋ถ€ํŒŒ์ผ์„ ์ฐธ์กฐํ•˜๋ฏ€๋กœ ์ฒจ๋ถ€ํŒŒ์ผ์„ ๋จผ์ € ์‚ญ์ œ +- ์ฑ„๋„์ด ๋ฉ”์‹œ์ง€์™€ ReadStatus๋ฅผ ์ฐธ์กฐํ•˜๋ฏ€๋กœ ์ด๋“ค์„ ๋จผ์ € ์‚ญ์ œ +- ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ์œ ์ง€ + +--- + +## 7. ์ดˆ๋ณด์ž๋ฅผ ์œ„ํ•œ ํ•ต์‹ฌ ๊ฐœ๋… + +### 7.1 ์™œ ๊ณ„์ธต์„ ๋‚˜๋ˆ„๋Š”๊ฐ€? + +**๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ (Separation of Concerns)** + +``` +๋งŒ์•ฝ ๊ณ„์ธต์„ ๋‚˜๋ˆ„์ง€ ์•Š๋Š”๋‹ค๋ฉด? + +UserController { + public UserResponse createUser(UserCreateRequest request) { + // HTTP ์ฒ˜๋ฆฌ + ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง + ๋ฐ์ดํ„ฐ ์ ‘๊ทผ์ด ์„ž์—ฌ์žˆ์Œ + if (data.containsKey(request.username())) { // ๋ฐ์ดํ„ฐ ์ ‘๊ทผ + throw new Exception("์ค‘๋ณต!"); // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง + } + User user = new User(...); + data.put(user.getId(), user); // ๋ฐ์ดํ„ฐ ์ ‘๊ทผ + return new UserResponse(...); // HTTP ์‘๋‹ต + } +} + +๋ฌธ์ œ์ : +โŒ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์›€ +โŒ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์›€ +โŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‹œ Controller๋„ ์ˆ˜์ •ํ•ด์•ผ ํ•จ +โŒ ์žฌ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅ (๋‹ค๋ฅธ Controller์—์„œ ๊ฐ™์€ ๋กœ์ง ํ•„์š” ์‹œ ๋ณต์‚ฌํ•ด์•ผ ํ•จ) +``` + +**๊ณ„์ธต์„ ๋‚˜๋ˆˆ ๊ฒฝ์šฐ:** + +``` +Controller: HTTP๋งŒ ์ฒ˜๋ฆฌ + โ†“ +Service: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ์ฒ˜๋ฆฌ + โ†“ +Repository: ๋ฐ์ดํ„ฐ ์ ‘๊ทผ๋งŒ ์ฒ˜๋ฆฌ + +์žฅ์ : +โœ… ๊ฐ ๊ณ„์ธต์ด ํ•˜๋‚˜์˜ ์ฑ…์ž„๋งŒ ๊ฐ€์ง (Single Responsibility Principle) +โœ… ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›€ (Mock ๊ฐ์ฒด ์‚ฌ์šฉ) +โœ… ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ (Service๋Š” ์—ฌ๋Ÿฌ Controller์—์„œ ์‚ฌ์šฉ) +โœ… ์œ ์ง€๋ณด์ˆ˜ ์‰ฌ์›€ (๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹น ๊ณ„์ธต๋งŒ ์ˆ˜์ •) +``` + +### 7.2 ์™œ DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€? + +**Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์•ˆ ๋˜๋Š” ์ด์œ :** + +```java +// โŒ ๋‚˜์œ ์˜ˆ: Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ +@RequestMapping(method = RequestMethod.GET, value = "/{id}") +public User getUser(@PathVariable UUID id) { + return userRepository.findById(id).get(); +} + +// ์‘๋‹ต: +{ + "id": "uuid", + "username": "alice", + "email": "alice@example.com", + "password": "pass123", // โŒ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋…ธ์ถœ! + "createdAt": "...", + "updatedAt": "..." + // isOnline ๊ฐ™์€ ๊ณ„์‚ฐ๋œ ๊ฐ’์€ ์—†์Œ +} +``` + +```java +// โœ… ์ข‹์€ ์˜ˆ: DTO ์‚ฌ์šฉ +@RequestMapping(method = RequestMethod.GET, value = "/{id}") +public UserResponse getUser(@PathVariable UUID id) { + return userService.find(id); +} + +// ์‘๋‹ต: +{ + "id": "uuid", + "username": "alice", + "email": "alice@example.com", + // password ์—†์Œ โœ… + "isOnline": true, // โœ… ๊ณ„์‚ฐ๋œ ๊ฐ’ ์ถ”๊ฐ€ + "createdAt": "...", + "updatedAt": "..." +} +``` + +**DTO์˜ ์žฅ์ :** +1. **๋ณด์•ˆ**: ๋ฏผ๊ฐ ์ •๋ณด ์ œ์™ธ (๋น„๋ฐ€๋ฒˆํ˜ธ) +2. **์œ ์—ฐ์„ฑ**: ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ์ „์†ก +3. **ํ™•์žฅ์„ฑ**: ๊ณ„์‚ฐ๋œ ๊ฐ’ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ +4. **์•ˆ์ •์„ฑ**: API ์‘๋‹ต ๊ตฌ์กฐ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌ + +### 7.3 ์™œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€? + +```java +// UserService ์ธํ„ฐํŽ˜์ด์Šค +public interface UserService { + UserResponse create(UserCreateRequest request, ...); + UserResponse find(UUID id); + // ... +} + +// ๊ตฌํ˜„์ฒด 1: ๊ธฐ๋ณธ ๊ตฌํ˜„ +@Service +public class BasicUserService implements UserService { + // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ตฌํ˜„ +} + +// ๋‚˜์ค‘์— ๊ตฌํ˜„์ฒด 2 ์ถ”๊ฐ€ ๊ฐ€๋Šฅ: ์บ์‹ฑ ์ถ”๊ฐ€ +@Service +@Primary +public class CachedUserService implements UserService { + private final BasicUserService basicUserService; + private final Cache cache; + + public UserResponse find(UUID id) { + if (cache.has(id)) { + return cache.get(id); + } + UserResponse user = basicUserService.find(id); + cache.put(id, user); + return user; + } +} +``` + +**์žฅ์ :** +- Controller๋Š” ์ธํ„ฐํŽ˜์ด์Šค์—๋งŒ ์˜์กด โ†’ ๊ตฌํ˜„์ฒด ๊ต์ฒด ๊ฐ€๋Šฅ +- ํ…Œ์ŠคํŠธ ์‹œ Mock ๊ตฌํ˜„์ฒด ์‚ฌ์šฉ ๊ฐ€๋Šฅ +- ์—ฌ๋Ÿฌ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๊ธฐ๋Šฅ ํ™•์žฅ ๊ฐ€๋Šฅ + +### 7.4 ์˜์กด์„ฑ ์ฃผ์ž… (Dependency Injection) + +**์ˆ˜๋™ ๋ฐฉ์‹ (DI ์—†์ด):** + +```java +public class UserController { + // โŒ ์ง์ ‘ ๊ฐ์ฒด ์ƒ์„ฑ + private UserService userService = new BasicUserService( + new JCFUserRepository(), + new JCFBinaryContentRepository(), + new JCFUserStatusRepository() + ); +} + +๋ฌธ์ œ์ : +โŒ ๊ฒฐํ•ฉ๋„ ๋†’์Œ (ํŠน์ • ๊ตฌํ˜„์ฒด์— ์˜์กด) +โŒ ํ…Œ์ŠคํŠธ ์–ด๋ ค์›€ +โŒ ์„ค์ • ๋ณ€๊ฒฝ ์‹œ ์ฝ”๋“œ ์ˆ˜์ • ํ•„์š” +``` + +**Spring์˜ DI:** + +```java +@RestController +@RequiredArgsConstructor // Lombok: final ํ•„๋“œ๋กœ ์ƒ์„ฑ์ž ์ž๋™ ์ƒ์„ฑ +public class UserController { + // โœ… Spring์ด ์ž๋™์œผ๋กœ ์ฃผ์ž… + private final UserService userService; +} + +์žฅ์ : +โœ… Spring์ด ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ฃผ์ž… ์ž๋™ ์ฒ˜๋ฆฌ +โœ… ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์œผ๋กœ ์„ ์–ธ โ†’ ๊ตฌํ˜„์ฒด ๊ต์ฒด ์‰ฌ์›€ +โœ… ํ…Œ์ŠคํŠธ ์‹œ Mock ์ฃผ์ž… ๊ฐ€๋Šฅ +``` + +### 7.5 ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ์˜ ํ•œ๊ณ„์™€ ํ•ด๊ฒฐ์ฑ… + +**ํ˜„์žฌ (JCF HashMap):** + +```java +private final Map data = new HashMap<>(); +``` + +**๋ฌธ์ œ์ :** + +1. **๋ฐ์ดํ„ฐ ์†์‹ค** + - ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ + - ํ•ด๊ฒฐ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (MySQL, PostgreSQL) + +2. **๋™์‹œ์„ฑ ๋ฌธ์ œ** + - HashMap์€ thread-safe ํ•˜์ง€ ์•Š์Œ + - ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋™์‹œ์— ์ ‘๊ทผํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์†์ƒ ๊ฐ€๋Šฅ + - ํ•ด๊ฒฐ: `ConcurrentHashMap` ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜ + +3. **๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ** + - ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด OutOfMemoryError + - ํ•ด๊ฒฐ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค + ํŽ˜์ด์ง• + +**์‹ค๋ฌด ํ•ด๊ฒฐ์ฑ… (JPA):** + +```java +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); + boolean existsByUsername(String username); + // Spring Data JPA๊ฐ€ ์ž๋™์œผ๋กœ ๊ตฌํ˜„ ์ƒ์„ฑ! +} + +// application.yml +spring: + datasource: + url: jdbc:mysql://localhost:3306/discodeit + username: root + password: password + jpa: + hibernate: + ddl-auto: update +``` + +--- + +## 8. ์‹ค์Šต ์˜ˆ์ œ + +### 8.1 API ํ…Œ์ŠคํŠธ (Postman) + +#### 1๋‹จ๊ณ„: ์‚ฌ์šฉ์ž ๋“ฑ๋ก + +``` +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "alice", + "email": "alice@example.com", + "password": "password123" +} + +์‘๋‹ต (201 Created): +{ + "id": "generated-uuid", + "username": "alice", + "email": "alice@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2024-01-15T10:30:00Z", + "updatedAt": "2024-01-15T10:30:00Z" +} +``` + +#### 2๋‹จ๊ณ„: ๋กœ๊ทธ์ธ + +``` +POST http://localhost:8080/auth/login +Content-Type: application/json + +{ + "username": "alice", + "password": "password123" +} + +์‘๋‹ต (200 OK): +{ + "id": "generated-uuid", + "username": "alice", + "email": "alice@example.com", + "isOnline": true, + ... +} +``` + +#### 3๋‹จ๊ณ„: ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + +``` +POST http://localhost:8080/channels/public +Content-Type: application/json + +{ + "name": "์ผ๋ฐ˜ ๋Œ€ํ™”", + "description": "์ž์œ ๋กญ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ๊ณต๊ฐ„" +} + +์‘๋‹ต (201 Created): +{ + "id": "channel-uuid", + "type": "PUBLIC", + "name": "์ผ๋ฐ˜ ๋Œ€ํ™”", + "description": "์ž์œ ๋กญ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ๊ณต๊ฐ„", + ... +} +``` + +#### 4๋‹จ๊ณ„: ๋ฉ”์‹œ์ง€ ์ „์†ก + +``` +POST http://localhost:8080/messages +Content-Type: application/json + +{ + "content": "์•ˆ๋…•ํ•˜์„ธ์š”!", + "channelId": "channel-uuid", + "authorId": "alice-uuid" +} + +์‘๋‹ต (201 Created): +{ + "id": "message-uuid", + "content": "์•ˆ๋…•ํ•˜์„ธ์š”!", + "channelId": "channel-uuid", + "authorId": "alice-uuid", + "attachmentIds": [], + ... +} +``` + +#### 5๋‹จ๊ณ„: ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์กฐํšŒ + +``` +GET http://localhost:8080/messages?channelId=channel-uuid + +์‘๋‹ต (200 OK): +[ + { + "id": "message-uuid", + "content": "์•ˆ๋…•ํ•˜์„ธ์š”!", + "channelId": "channel-uuid", + "authorId": "alice-uuid", + ... + } +] +``` + +### 8.2 ์ฝ”๋“œ ์ˆ˜์ • ์‹ค์Šต + +#### ์‹ค์Šต 1: ์ƒˆ๋กœ์šด API ์ถ”๊ฐ€ + +**๋ชฉํ‘œ: ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฒ€์ƒ‰ API ์ถ”๊ฐ€** + +```java +// 1. UserController์— ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ +@RequestMapping(method = RequestMethod.GET, value = "/search") +public ResponseEntity getUserByUsername( + @RequestParam String username +) { + UserResponse user = userService.findByUsername(username); + return ResponseEntity.ok(user); +} + +// 2. UserService ์ธํ„ฐํŽ˜์ด์Šค์— ๋ฉ”์„œ๋“œ ์„ ์–ธ +public interface UserService { + // ... ๊ธฐ์กด ๋ฉ”์„œ๋“œ๋“ค + UserResponse findByUsername(String username); +} + +// 3. BasicUserService์— ๋ฉ”์„œ๋“œ ๊ตฌํ˜„ +@Override +public UserResponse findByUsername(String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new NoSuchElementException( + "User not found with username: " + username + )); + + boolean isOnline = getOnlineStatus(user.getId()); + return toUserResponse(user, isOnline); +} + +// 4. ํ…Œ์ŠคํŠธ +GET http://localhost:8080/users/search?username=alice +``` + +#### ์‹ค์Šต 2: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜์ • + +**๋ชฉํ‘œ: ์˜จ๋ผ์ธ ๊ธฐ์ค€์„ 5๋ถ„์—์„œ 10๋ถ„์œผ๋กœ ๋ณ€๊ฒฝ** + +```java +// UserStatus.java +public boolean isOnline() { + return Duration.between(lastActiveAt, Instant.now()) + .toMinutes() < 10; // 5 โ†’ 10์œผ๋กœ ๋ณ€๊ฒฝ +} +``` + +#### ์‹ค์Šต 3: DTO ํ•„๋“œ ์ถ”๊ฐ€ + +**๋ชฉํ‘œ: UserResponse์— ์ƒ์„ฑ ํ›„ ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ์ถ”๊ฐ€** + +```java +// UserResponse.java +public record UserResponse( + UUID id, + String username, + String email, + UUID profileId, + boolean isOnline, + Instant createdAt, + Instant updatedAt, + long createdDaysAgo // ์ƒˆ ํ•„๋“œ ์ถ”๊ฐ€ +) {} + +// BasicUserService.java +private UserResponse toUserResponse(User user, boolean isOnline) { + long daysAgo = Duration.between(user.getCreatedAt(), Instant.now()) + .toDays(); + + return new UserResponse( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getProfileId(), + isOnline, + user.getCreatedAt(), + user.getUpdatedAt(), + daysAgo // ๊ณ„์‚ฐ๋œ ๊ฐ’ + ); +} +``` + +--- + +## ๐ŸŽ“ ํ•™์Šต ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### ์ดํ•ดํ–ˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์„ธ์š”: + +- [ ] **์•„ํ‚คํ…์ฒ˜**: ๊ณ„์ธตํ˜• ๊ตฌ์กฐ์˜ ๊ฐ ๊ณ„์ธต ์—ญํ• ์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‚˜์š”? +- [ ] **Entity**: BaseEntity๋ฅผ ์ƒ์†ํ•˜๋Š” ์ด์œ ๋ฅผ ์•„๋‚˜์š”? +- [ ] **Repository**: JCF ๊ตฌํ˜„์ฒด๊ฐ€ HashMap์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋ฅผ ์•„๋‚˜์š”? +- [ ] **Service**: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์™œ Service ๊ณ„์ธต์— ์žˆ๋Š”์ง€ ์•„๋‚˜์š”? +- [ ] **Controller**: `@RequestMapping`, `@RequestBody`, `@PathVariable`์˜ ์—ญํ• ์„ ์•„๋‚˜์š”? +- [ ] **DTO**: Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ  DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋ฅผ ์•„๋‚˜์š”? +- [ ] **DI**: Spring์ด ์–ด๋–ป๊ฒŒ ์˜์กด์„ฑ์„ ์ž๋™ ์ฃผ์ž…ํ•˜๋Š”์ง€ ์•„๋‚˜์š”? +- [ ] **๋ฐ์ดํ„ฐ ํ๋ฆ„**: HTTP ์š”์ฒญ๋ถ€ํ„ฐ ์‘๋‹ต๊นŒ์ง€์˜ ์ „์ฒด ํ๋ฆ„์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‚˜์š”? +- [ ] **์—ฐ์‡„ ์‚ญ์ œ**: ์ฑ„๋„ ์‚ญ์ œ ์‹œ ๋ฉ”์‹œ์ง€์™€ ์ฒจ๋ถ€ํŒŒ์ผ์„ ๋จผ์ € ์‚ญ์ œํ•˜๋Š” ์ด์œ ๋ฅผ ์•„๋‚˜์š”? +- [ ] **PRIVATE ์ฑ„๋„**: ReadStatus๊ฐ€ ์–ด๋–ป๊ฒŒ ์ ‘๊ทผ ์ œ์–ด์— ์‚ฌ์šฉ๋˜๋Š”์ง€ ์•„๋‚˜์š”? + +--- + +## ๐Ÿ“š ๋” ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด + +### ๋‹ค์Œ ๋‹จ๊ณ„: + +1. **JPA๋กœ ์ „ํ™˜ํ•˜๊ธฐ** + - Spring Data JPA ํ•™์Šต + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ (MySQL, PostgreSQL) + - ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ (`@Transactional`) + +2. **๋ณด์•ˆ ์ถ”๊ฐ€ํ•˜๊ธฐ** + - Spring Security ์ ์šฉ + - JWT ํ† ํฐ ์ธ์ฆ + - ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™” (BCrypt) + +3. **ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ** + - JUnit 5 + - Mockito (Service ํ…Œ์ŠคํŠธ) + - MockMvc (Controller ํ…Œ์ŠคํŠธ) + +4. **์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ ์ถ”๊ฐ€** + - WebSocket์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ + - Server-Sent Events (SSE) + +5. **๊ณ ๊ธ‰ ๊ธฐ๋Šฅ** + - ํŒŒ์ผ ์—…๋กœ๋“œ (Multipart) + - ํŽ˜์ด์ง• ๋ฐ ์ •๋ ฌ + - ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ (QueryDSL) + - ์บ์‹ฑ (Redis) + +--- + +## ๐ŸŽ‰ ๋งˆ๋ฌด๋ฆฌ + +์ด ํ”„๋กœ์ ํŠธ๋Š” **Spring Boot์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๋“ค์„ ์‹ค์ œ๋กœ ์ ์šฉํ•œ ๊น”๋”ํ•œ ์˜ˆ์ œ**์ž…๋‹ˆ๋‹ค. + +**ํ•ต์‹ฌ ํฌ์ธํŠธ:** +- โœ… ๊ณ„์ธตํ˜• ์•„ํ‚คํ…์ฒ˜๋กœ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ +- โœ… Repository ํŒจํ„ด์œผ๋กœ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ์ถ”์ƒํ™” +- โœ… DTO๋กœ API ์‘๋‹ต ์ œ์–ด +- โœ… Spring DI๋กœ ๊ฐ์ฒด ๊ด€๋ฆฌ ์ž๋™ํ™” + +**์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ดํ•ดํ–ˆ๋‹ค๋ฉด:** +- ์‹ค๋ฌด Spring Boot ํ”„๋กœ์ ํŠธ์˜ 70% ์ด์ƒ ์ดํ•ด ๊ฐ€๋Šฅ! +- JPA, Security, WebSocket ๋“ฑ ์ถ”๊ฐ€ ๊ธฐ์ˆ  ํ•™์Šต ์ค€๋น„ ์™„๋ฃŒ! + +**Happy Coding! ๐Ÿš€** diff --git a/discodeit/docs/api-tests-with-env.http b/discodeit/docs/api-tests-with-env.http new file mode 100644 index 00000000..a5ca640b --- /dev/null +++ b/discodeit/docs/api-tests-with-env.http @@ -0,0 +1,321 @@ +### Discodeit API ํ…Œ์ŠคํŠธ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ) +### IntelliJ์—์„œ ๊ฐ ์š”์ฒญ ์˜†์˜ 'โ–ถ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ Ctrl+Enter (Mac: Cmd+Enter)๋กœ ์‹คํ–‰ +### ํ™˜๊ฒฝ ์„ ํƒ: ์šฐ์ธก ์ƒ๋‹จ ๋“œ๋กญ๋‹ค์šด์—์„œ 'dev' ์„ ํƒ + +### ========================================== +### 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ API +### ========================================== + +### 1-1. ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ +GET {{baseUrl}}/users +Accept: application/json + +### 1-2. ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ (ํ•œ์„ฑ์žฌ) +GET {{baseUrl}}/users/{{user1Id}} +Accept: application/json + +### 1-3. ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ (์žฅํ˜„๋ฏผ) +GET {{baseUrl}}/users/{{user3Id}} +Accept: application/json + +### 1-4. ์‚ฌ์šฉ์ž ์ƒ์„ฑ (JSON - ํ”„๋กœํ•„ ์—†์ด) +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "์‹ ๊ทœ์œ ์ €{{$randomInt}}", + "email": "user{{$randomInt}}@example.com", + "password": "password123" +} + +### 1-5. ์‚ฌ์šฉ์ž ์ƒ์„ฑ (Multipart - ํ”„๋กœํ•„ ํฌํ•จ) +POST {{baseUrl}}/users/with-profile +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="username" + +ํ”„๋กœํ•„์œ ์ €{{$randomInt}} +--WebAppBoundary +Content-Disposition: form-data; name="email" + +profile{{$randomInt}}@example.com +--WebAppBoundary +Content-Disposition: form-data; name="password" + +password123 +--WebAppBoundary +Content-Disposition: form-data; name="profileImage"; filename="test.jpg" +Content-Type: image/jpeg + +< ./src/main/resources/data/profiles/hansj.jpg +--WebAppBoundary-- + +### 1-6. ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (JSON) +PUT {{baseUrl}}/users/{{user1Id}} +Content-Type: application/json + +{ + "username": "{{user1Name}}_์ˆ˜์ •๋จ", + "email": "hansj_modified@codeit.com", + "password": "newpassword123" +} + +### 1-7. ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (Multipart - ํ”„๋กœํ•„ ํฌํ•จ) +PUT {{baseUrl}}/users/{{user1Id}}/with-profile +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="username" + +{{user1Name}} +--WebAppBoundary +Content-Disposition: form-data; name="email" + +hansj@codeit.com +--WebAppBoundary +Content-Disposition: form-data; name="password" + +password123 +--WebAppBoundary +Content-Disposition: form-data; name="profileImage"; filename="updated.jpg" +Content-Type: image/jpeg + +< ./src/main/resources/data/profiles/janghm.jpg +--WebAppBoundary-- + +### ========================================== +### 2. ์‚ฌ์šฉ์ž ์ƒํƒœ ๊ด€๋ฆฌ API +### ========================================== + +### 2-1. ํ•œ์„ฑ์žฌ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +PUT {{baseUrl}}/users/{{user1Id}}/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$isoTimestamp}}" +} + +### 2-2. ๋ชจ๋“  ์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +PUT {{baseUrl}}/users/{{user1Id}}/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$isoTimestamp}}" +} + +### +PUT {{baseUrl}}/users/{{user2Id}}/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$isoTimestamp}}" +} + +### +PUT {{baseUrl}}/users/{{user3Id}}/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$isoTimestamp}}" +} + +### +PUT {{baseUrl}}/users/{{user4Id}}/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$isoTimestamp}}" +} + +### ========================================== +### 3. ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์กฐํšŒ API +### ========================================== + +### 3-1. ํ•œ์„ฑ์žฌ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์กฐํšŒ +GET {{baseUrl}}/binary-contents/{{user1ProfileId}} +Accept: application/json + +### 3-2. ์žฅํ˜„๋ฏผ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์กฐํšŒ +GET {{baseUrl}}/binary-contents/{{user3ProfileId}} +Accept: application/json + +### 3-3. ์—ฌ๋Ÿฌ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์กฐํšŒ (ํ•œ์„ฑ์žฌ + ์žฅํ˜„๋ฏผ) +GET {{baseUrl}}/binary-contents?ids={{user1ProfileId}},{{user3ProfileId}} +Accept: application/json + +### 3-4. ๋ชจ๋“  ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์กฐํšŒ +GET {{baseUrl}}/binary-contents?ids={{user1ProfileId}},{{user2ProfileId}},{{user3ProfileId}},{{user4ProfileId}} +Accept: application/json + +### ========================================== +### 4. ์ „์ฒด ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ +### ========================================== + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ƒˆ ์‚ฌ์šฉ์ž ๋“ฑ๋ก +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "์‹œ๋‚˜๋ฆฌ์˜ค์œ ์ €", + "email": "scenario_{{$randomInt}}@test.com", + "password": "test1234" +} + +> {% + client.global.set("newUserId", response.body.id); + client.log("โœ… ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์™„๋ฃŒ - ID: " + response.body.id); +%} + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ์ƒ์„ฑํ•œ ์‚ฌ์šฉ์ž ์กฐํšŒ +GET {{baseUrl}}/users/{{newUserId}} +Accept: application/json + +> {% + client.test("์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต", function() { + client.assert(response.status === 200, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์ด์–ด์•ผ ํ•จ"); + client.assert(response.body.id === client.global.get("newUserId"), "ID๊ฐ€ ์ผ์น˜ํ•ด์•ผ ํ•จ"); + }); + client.log("โœ… ์‚ฌ์šฉ์ž ์กฐํšŒ ์™„๋ฃŒ - ์ด๋ฆ„: " + response.body.username); +%} + +### ์‹œ๋‚˜๋ฆฌ์˜ค 3: ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +PUT {{baseUrl}}/users/{{newUserId}}/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$isoTimestamp}}" +} + +> {% + client.test("์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต", function() { + client.assert(response.status === 200, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์ด์–ด์•ผ ํ•จ"); + client.assert(response.body.online === true, "์˜จ๋ผ์ธ ์ƒํƒœ์—ฌ์•ผ ํ•จ"); + }); + client.log("โœ… ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ"); +%} + +### ์‹œ๋‚˜๋ฆฌ์˜ค 4: ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • +PUT {{baseUrl}}/users/{{newUserId}} +Content-Type: application/json + +{ + "username": "์‹œ๋‚˜๋ฆฌ์˜ค์œ ์ €_์ˆ˜์ •๋จ", + "email": "scenario_modified@test.com", + "password": "newpassword" +} + +> {% + client.test("์‚ฌ์šฉ์ž ์ˆ˜์ • ์„ฑ๊ณต", function() { + client.assert(response.status === 200, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์ด์–ด์•ผ ํ•จ"); + client.assert(response.body.username === "์‹œ๋‚˜๋ฆฌ์˜ค์œ ์ €_์ˆ˜์ •๋จ", "์ด๋ฆ„์ด ์ˆ˜์ •๋˜์–ด์•ผ ํ•จ"); + }); + client.log("โœ… ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์™„๋ฃŒ"); +%} + +### ์‹œ๋‚˜๋ฆฌ์˜ค 5: ์ƒ์„ฑํ•œ ์‚ฌ์šฉ์ž ์‚ญ์ œ +DELETE {{baseUrl}}/users/{{newUserId}} + +> {% + client.test("์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต", function() { + client.assert(response.status === 204, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 204์—ฌ์•ผ ํ•จ"); + }); + client.log("โœ… ์‚ฌ์šฉ์ž ์‚ญ์ œ ์™„๋ฃŒ"); +%} + +### ์‹œ๋‚˜๋ฆฌ์˜ค 6: ์‚ญ์ œ๋œ ์‚ฌ์šฉ์ž ์กฐํšŒ (404 ์‘๋‹ต ํ™•์ธ) +GET {{baseUrl}}/users/{{newUserId}} +Accept: application/json + +> {% + client.test("์‚ญ์ œ๋œ ์‚ฌ์šฉ์ž๋Š” 404", function() { + client.assert(response.status === 404, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 404์—ฌ์•ผ ํ•จ"); + }); + client.log("โœ… ์‚ญ์ œ ํ™•์ธ ์™„๋ฃŒ - 404 ์‘๋‹ต"); +%} + +### ========================================== +### 5. ์—๋Ÿฌ ์ผ€์ด์Šค ํ…Œ์ŠคํŠธ +### ========================================== + +### 5-1. ์ค‘๋ณต ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์ƒ์„ฑ (์‹คํŒจ) +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "์ค‘๋ณตํ…Œ์ŠคํŠธ", + "email": "hansj@codeit.com", + "password": "test1234" +} + +> {% + client.test("์ค‘๋ณต ์ด๋ฉ”์ผ์€ ์‹คํŒจํ•ด์•ผ ํ•จ", function() { + client.assert(response.status === 400 || response.status === 409, "์—๋Ÿฌ ์ƒํƒœ ์ฝ”๋“œ์—ฌ์•ผ ํ•จ"); + }); + client.log("โœ… ์ค‘๋ณต ๊ฒ€์ฆ ์™„๋ฃŒ - ์—๋Ÿฌ ๋ฐœ์ƒ"); +%} + +### 5-2. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์กฐํšŒ (404) +GET {{baseUrl}}/users/00000000-0000-0000-0000-000000000000 +Accept: application/json + +> {% + client.test("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž๋Š” 404", function() { + client.assert(response.status === 404, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 404์—ฌ์•ผ ํ•จ"); + }); + client.log("โœ… 404 ์‘๋‹ต ํ™•์ธ ์™„๋ฃŒ"); +%} + +### 5-3. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์กฐํšŒ (404) +GET {{baseUrl}}/binary-contents/00000000-0000-0000-0000-000000000000 +Accept: application/json + +> {% + client.test("์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ์€ 404", function() { + client.assert(response.status === 404, "์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 404์—ฌ์•ผ ํ•จ"); + }); + client.log("โœ… 404 ์‘๋‹ต ํ™•์ธ ์™„๋ฃŒ"); +%} + +### ========================================== +### 6. ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ - ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž ์—ฐ์† ์ƒ์„ฑ +### ========================================== + +### ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ 1 +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "์„ฑ๋Šฅํ…Œ์ŠคํŠธ1_{{$timestamp}}", + "email": "perf1_{{$timestamp}}@test.com", + "password": "test1234" +} + +### +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "์„ฑ๋Šฅํ…Œ์ŠคํŠธ2_{{$timestamp}}", + "email": "perf2_{{$timestamp}}@test.com", + "password": "test1234" +} + +### +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "username": "์„ฑ๋Šฅํ…Œ์ŠคํŠธ3_{{$timestamp}}", + "email": "perf3_{{$timestamp}}@test.com", + "password": "test1234" +} + +### ========================================== +### ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋™์  ๋ณ€์ˆ˜ +### ========================================== +# {{$uuid}} - ๋žœ๋ค UUID +# {{$timestamp}} - Unix ํƒ€์ž„์Šคํƒฌํ”„ (๋ฐ€๋ฆฌ์ดˆ) +# {{$isoTimestamp}} - ISO 8601 ํ˜•์‹์˜ ํ˜„์žฌ ์‹œ๊ฐ„ +# {{$randomInt}} - 0-1000 ์‚ฌ์ด์˜ ๋žœ๋ค ์ •์ˆ˜ +# {{$random.uuid}} - ๋˜ ๋‹ค๋ฅธ ํ˜•ํƒœ์˜ UUID diff --git a/discodeit/docs/api-tests.http b/discodeit/docs/api-tests.http new file mode 100644 index 00000000..ec33b8ec --- /dev/null +++ b/discodeit/docs/api-tests.http @@ -0,0 +1,202 @@ +### Discodeit API ํ…Œ์ŠคํŠธ ๋ชจ์Œ +### IntelliJ์—์„œ ๊ฐ ์š”์ฒญ ์˜†์˜ 'โ–ถ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ Ctrl+Enter (Mac: Cmd+Enter)๋กœ ์‹คํ–‰ + +### ========================================== +### 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ API +### ========================================== + +### 1-1. ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ +GET http://localhost:8080/users +Accept: application/json + +### 1-2. ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ (ID๋Š” ์‹ค์ œ ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝ) +GET http://localhost:8080/users/7559dc90-d2de-4de2-a2b3-48a621c61f41 +Accept: application/json + +### 1-3. ์‚ฌ์šฉ์ž ์ƒ์„ฑ (JSON - ํ”„๋กœํ•„ ์—†์ด) +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "ํ…Œ์ŠคํŠธ์œ ์ €1", + "email": "test1@example.com", + "password": "password123" +} + +### 1-4. ์‚ฌ์šฉ์ž ์ƒ์„ฑ (Multipart - ํ”„๋กœํ•„ ํฌํ•จ) +POST http://localhost:8080/users/with-profile +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="username" + +ํ…Œ์ŠคํŠธ์œ ์ €2 +--WebAppBoundary +Content-Disposition: form-data; name="email" + +test2@example.com +--WebAppBoundary +Content-Disposition: form-data; name="password" + +password123 +--WebAppBoundary +Content-Disposition: form-data; name="profileImage"; filename="test.jpg" +Content-Type: image/jpeg + +< ./src/main/resources/data/profiles/hansj.jpg +--WebAppBoundary-- + +### 1-5. ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (JSON - ํ”„๋กœํ•„ ์—†์ด) +PUT http://localhost:8080/users/7559dc90-d2de-4de2-a2b3-48a621c61f41 +Content-Type: application/json + +{ + "username": "ํ•œ์„ฑ์žฌ_์ˆ˜์ •", + "email": "hansj_updated@codeit.com", + "password": "newpassword123" +} + +### 1-6. ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (Multipart - ํ”„๋กœํ•„ ํฌํ•จ) +PUT http://localhost:8080/users/7559dc90-d2de-4de2-a2b3-48a621c61f41/with-profile +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="username" + +ํ•œ์„ฑ์žฌ_์ตœ์ข… +--WebAppBoundary +Content-Disposition: form-data; name="email" + +hansj@codeit.com +--WebAppBoundary +Content-Disposition: form-data; name="password" + +password123 +--WebAppBoundary +Content-Disposition: form-data; name="profileImage"; filename="hansj.jpg" +Content-Type: image/jpeg + +< ./src/main/resources/data/profiles/hansj.jpg +--WebAppBoundary-- + +### 1-7. ์‚ฌ์šฉ์ž ์‚ญ์ œ (ํ…Œ์ŠคํŠธ ์œ ์ € ID๋กœ ๋ณ€๊ฒฝ ํ•„์š”) +# DELETE http://localhost:8080/users/ํ…Œ์ŠคํŠธ์œ ์ €ID + +### ========================================== +### 2. ์‚ฌ์šฉ์ž ์ƒํƒœ ๊ด€๋ฆฌ API +### ========================================== + +### 2-1. ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +PUT http://localhost:8080/users/7559dc90-d2de-4de2-a2b3-48a621c61f41/status +Content-Type: application/json + +{ + "lastAccessAt": "{{$timestamp}}" +} + +### ========================================== +### 3. ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์กฐํšŒ API +### ========================================== + +### 3-1. ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ (ํ”„๋กœํ•„ ID๋กœ ๋ณ€๊ฒฝ) +GET http://localhost:8080/binary-contents/9f0ed3d9-5f59-4b3f-95c8-06dc2f9ab8b3 +Accept: application/json + +### 3-2. ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ (ํ•œ์„ฑ์žฌ, ์žฅํ˜„๋ฏผ ํ”„๋กœํ•„) +GET http://localhost:8080/binary-contents?ids=9f0ed3d9-5f59-4b3f-95c8-06dc2f9ab8b3,571694c6-e839-4fa0-81ee-a4c3a04290b4 +Accept: application/json + +### 3-3. ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ (๋ชจ๋“  ์‚ฌ์šฉ์ž ํ”„๋กœํ•„) +GET http://localhost:8080/binary-contents?ids=9f0ed3d9-5f59-4b3f-95c8-06dc2f9ab8b3,571694c6-e839-4fa0-81ee-a4c3a04290b4,5b6243b9-0ab5-4fc2-84e7-8497f030f2a0,a0a39374-e9d3-4c59-8840-2f2078f2641a +Accept: application/json + +### ========================================== +### 4. ์ „์ฒด ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ +### ========================================== + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ƒˆ ์‚ฌ์šฉ์ž ๋“ฑ๋ก ํ›„ ์กฐํšŒ +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "์‹œ๋‚˜๋ฆฌ์˜คํ…Œ์ŠคํŠธ", + "email": "scenario@test.com", + "password": "test1234" +} + +> {% + client.global.set("newUserId", response.body.id); + client.log("Created user ID: " + response.body.id); +%} + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋ฐฉ๊ธˆ ์ƒ์„ฑํ•œ ์‚ฌ์šฉ์ž ์กฐํšŒ +GET http://localhost:8080/users/{{newUserId}} +Accept: application/json + +### ์‹œ๋‚˜๋ฆฌ์˜ค 3: ๋ฐฉ๊ธˆ ์ƒ์„ฑํ•œ ์‚ฌ์šฉ์ž ์‚ญ์ œ +DELETE http://localhost:8080/users/{{newUserId}} + +### ========================================== +### 5. ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ (์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž ์ƒ์„ฑ) +### ========================================== + +### ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ 1: ์‚ฌ์šฉ์ž 1 +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "์„ฑ๋Šฅํ…Œ์ŠคํŠธ1", + "email": "perf1@test.com", + "password": "test1234" +} + +### ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ 2: ์‚ฌ์šฉ์ž 2 +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "์„ฑ๋Šฅํ…Œ์ŠคํŠธ2", + "email": "perf2@test.com", + "password": "test1234" +} + +### ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ 3: ์‚ฌ์šฉ์ž 3 +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "์„ฑ๋Šฅํ…Œ์ŠคํŠธ3", + "email": "perf3@test.com", + "password": "test1234" +} + +### ========================================== +### 6. ์—๋Ÿฌ ์ผ€์ด์Šค ํ…Œ์ŠคํŠธ +### ========================================== + +### 6-1. ์ค‘๋ณต ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์ƒ์„ฑ (์‹คํŒจํ•ด์•ผ ํ•จ) +POST http://localhost:8080/users +Content-Type: application/json + +{ + "username": "์ค‘๋ณตํ…Œ์ŠคํŠธ", + "email": "hansj@codeit.com", + "password": "test1234" +} + +### 6-2. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์กฐํšŒ (404 ์‘๋‹ต) +GET http://localhost:8080/users/00000000-0000-0000-0000-000000000000 +Accept: application/json + +### 6-3. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์กฐํšŒ (404 ์‘๋‹ต) +GET http://localhost:8080/binary-contents/00000000-0000-0000-0000-000000000000 +Accept: application/json + +### ========================================== +### ์ฐธ๊ณ : ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ +### ========================================== +# IntelliJ HTTP Client๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: +# - {{$timestamp}}: ํ˜„์žฌ ํƒ€์ž„์Šคํƒฌํ”„ (ISO 8601) +# - {{$randomInt}}: ๋žœ๋ค ์ •์ˆ˜ +# - {{$uuid}}: ๋žœ๋ค UUID +# - ์ปค์Šคํ…€ ๋ณ€์ˆ˜: client.global.set() ์‚ฌ์šฉ diff --git a/discodeit/docs/http-client.env.json b/discodeit/docs/http-client.env.json new file mode 100644 index 00000000..ee5720dd --- /dev/null +++ b/discodeit/docs/http-client.env.json @@ -0,0 +1,17 @@ +{ + "dev": { + "baseUrl": "http://localhost:8080", + "user1Id": "10000000-0000-0000-0000-000000000001", + "user1Name": "ํ•œ์„ฑ์žฌ", + "user1ProfileId": "10000000-0000-0000-0000-000000000002", + "user2Id": "20000000-0000-0000-0000-000000000001", + "user2Name": "์žฅํ˜„๋ฏผ", + "user2ProfileId": "20000000-0000-0000-0000-000000000002", + "user3Id": "30000000-0000-0000-0000-000000000001", + "user3Name": "์ „๋ช…ํ›ˆ", + "user3ProfileId": "30000000-0000-0000-0000-000000000002", + "user4Id": "40000000-0000-0000-0000-000000000001", + "user4Name": "์„œํ˜„ํ•˜", + "user4ProfileId": "40000000-0000-0000-0000-000000000002" + } +} diff --git "a/discodeit/docs/\352\271\200\354\262\240\354\210\230 \355\224\204\353\241\234\355\225\204.png" "b/discodeit/docs/\352\271\200\354\262\240\354\210\230 \355\224\204\353\241\234\355\225\204.png" new file mode 100644 index 00000000..5c496f25 Binary files /dev/null and "b/discodeit/docs/\352\271\200\354\262\240\354\210\230 \355\224\204\353\241\234\355\225\204.png" differ diff --git "a/discodeit/docs/\353\260\225\353\257\274\354\210\230 \355\224\204\353\241\234\355\225\204.png" "b/discodeit/docs/\353\260\225\353\257\274\354\210\230 \355\224\204\353\241\234\355\225\204.png" new file mode 100644 index 00000000..0dd39241 Binary files /dev/null and "b/discodeit/docs/\353\260\225\353\257\274\354\210\230 \355\224\204\353\241\234\355\225\204.png" differ diff --git "a/discodeit/docs/\354\212\244\355\201\254\353\246\260\354\203\267 2026-02-11 \354\230\244\355\233\204 10.49.45.png" "b/discodeit/docs/\354\212\244\355\201\254\353\246\260\354\203\267 2026-02-11 \354\230\244\355\233\204 10.49.45.png" new file mode 100644 index 00000000..d1a6a8d2 Binary files /dev/null and "b/discodeit/docs/\354\212\244\355\201\254\353\246\260\354\203\267 2026-02-11 \354\230\244\355\233\204 10.49.45.png" differ diff --git "a/discodeit/docs/\354\235\264\354\230\201\355\235\254 \355\224\204\353\241\234\355\225\204.png" "b/discodeit/docs/\354\235\264\354\230\201\355\235\254 \355\224\204\353\241\234\355\225\204.png" new file mode 100644 index 00000000..1e6a05d9 Binary files /dev/null and "b/discodeit/docs/\354\235\264\354\230\201\355\235\254 \355\224\204\353\241\234\355\225\204.png" differ diff --git "a/discodeit/docs/\354\265\234\354\247\200\354\227\260 \355\224\204\353\241\234\355\225\204.png" "b/discodeit/docs/\354\265\234\354\247\200\354\227\260 \355\224\204\353\241\234\355\225\204.png" new file mode 100644 index 00000000..f7993c7c Binary files /dev/null and "b/discodeit/docs/\354\265\234\354\247\200\354\227\260 \355\224\204\353\241\234\355\225\204.png" differ diff --git a/discodeit/requirements.txt b/discodeit/requirements.txt new file mode 100644 index 00000000..2c24336e --- /dev/null +++ b/discodeit/requirements.txt @@ -0,0 +1 @@ +requests==2.31.0 diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/Discodeit API.postman_test_run.json b/discodeit/src/main/java/com/sprint/mission/discodeit/Discodeit API.postman_test_run.json new file mode 100644 index 00000000..beb22f0e --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/Discodeit API.postman_test_run.json @@ -0,0 +1,735 @@ +{ + "id": "72aa7dc4-e6f3-482f-9d9c-1131467aaa6d", + "name": "Discodeit API", + "timestamp": "2026-02-11T13:40:36.311Z", + "collection_id": "52123612-cc7620c4-e129-4558-909c-60cb60c4a7d8", + "folder_id": 0, + "environment_id": "52123612-b20157c7-1753-4dd5-a79e-ca28791a47c2", + "totalPass": 23, + "delay": 0, + "persist": true, + "status": "finished", + "startedAt": "2026-02-11T13:40:35.589Z", + "totalFail": 0, + "results": [ + { + "id": "62f080e5-1d87-4729-b59c-d9c262cc57f6", + "name": "์‚ฌ์šฉ์ž ๋“ฑ๋ก", + "url": "http://localhost:8080/users", + "time": 81, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 81 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต": true + } + ] + }, + { + "id": "38f02c1b-1da3-4305-8a73-966ecd4f514a", + "name": "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", + "url": "http://localhost:8080/users/20000000-0000-0000-0000-000000000001", + "time": 5, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "e223f7ff-58d7-474b-916f-bb9e870340a0", + "name": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "url": "http://localhost:8080/users/199f91c7-2ba9-41d0-9cd9-6bb94a002f3b", + "time": 3, + "responseCode": { + "code": 204, + "name": "No Content" + }, + "tests": { + "์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "05657515-669f-416b-abb6-bcd7a6ffc9b1", + "name": "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ", + "url": "http://localhost:8080/users", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "3c0426e3-cad0-45a9-b870-d3542de16393", + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (์ด์˜ํฌ)", + "url": "http://localhost:8080/users/20000000-0000-0000-0000-000000000001/status", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "1fccb8ae-a199-42c2-84b1-2e6e050ac4d5", + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๋ฐ•๋ฏผ์ˆ˜)", + "url": "http://localhost:8080/users/30000000-0000-0000-0000-000000000001/status", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "e4500e3f-52b4-40ab-873a-7f0c488a79ba", + "name": "์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (์ตœ์ง€์—ฐ)", + "url": "http://localhost:8080/users/40000000-0000-0000-0000-000000000001/status", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "13e2e344-786e-4ec1-bc5f-a73047a93695", + "name": "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ (๋ฐ•๋ฏผ์ˆ˜)", + "url": "http://localhost:8080/auth/login", + "time": 4, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "67a951f5-93ee-4ea9-967c-e8e29fb4b6ad", + "name": "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + "url": "http://localhost:8080/channels/public", + "time": 7, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 7 + ], + "allTests": [ + { + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "f8543bc4-6878-4e40-84b0-48293ece9e98", + "name": "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + "url": "http://localhost:8080/channels/private", + "time": 6, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "c448ff0b-1c2c-44d6-ac60-a1755ac7b888", + "name": "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", + "url": "http://localhost:8080/channels/f16f2249-9b95-4ade-8c9a-dcab427daf29", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "e7cdf82b-b3be-457d-93c9-8b4610e1e4db", + "name": "์ฑ„๋„ ์‚ญ์ œ", + "url": "http://localhost:8080/channels/3f35e0e6-5a0b-4095-aeaf-ac541b04c38b", + "time": 2, + "responseCode": { + "code": 204, + "name": "No Content" + }, + "tests": { + "์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 2 + ], + "allTests": [ + { + "์ฑ„๋„ ์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "bd3213a7-5150-4bbe-8b10-177e2b9b15c1", + "name": "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", + "url": "http://localhost:8080/channels?userId=20000000-0000-0000-0000-000000000001", + "time": 4, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "468a25e1-0e84-435d-96da-779eb8e77281", + "name": "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ", + "url": "http://localhost:8080/messages", + "time": 5, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "1122053e-946c-44d9-a1bc-5c814f355f0b", + "name": "๋ฉ”์‹œ์ง€ ์ˆ˜์ •", + "url": "http://localhost:8080/messages/bcf42db0-a178-4c67-85b8-b00e6216ea2a", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "1cb94982-c954-4c27-bb93-441313ce2519", + "name": "๋ฉ”์‹œ์ง€ ์‚ญ์ œ", + "url": "http://localhost:8080/messages/bcf42db0-a178-4c67-85b8-b00e6216ea2a", + "time": 3, + "responseCode": { + "code": 204, + "name": "No Content" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "52706e31-cbee-4fdc-9638-44097655653d", + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", + "url": "http://localhost:8080/messages?channelId=f16f2249-9b95-4ade-8c9a-dcab427daf29", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "5b4ca621-295c-4261-a9df-9978c4f22062", + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ", + "url": "http://localhost:8080/read-statuses", + "time": 4, + "responseCode": { + "code": 201, + "name": "Created" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "7ab4d359-041b-4b6e-b686-bebb06a082bd", + "name": "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ •", + "url": "http://localhost:8080/read-statuses/4936dd2f-9cca-460c-8bfd-1e0a6b3c84ab", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต": true + } + ] + }, + { + "id": "182a1d87-c2cc-4d03-9ff1-2cc073749b91", + "name": "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ", + "url": "http://localhost:8080/read-statuses?userId=20000000-0000-0000-0000-000000000001", + "time": 2, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 2 + ], + "allTests": [ + { + "๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "2ea5804a-177f-4c81-861c-2074010d9e74", + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ", + "url": "http://localhost:8080/binary-contents/20000000-0000-0000-0000-000000000002", + "time": 7, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 7 + ], + "allTests": [ + { + "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "71b66c5b-c4c6-4fc0-9df8-15780abe0860", + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ (2๋ช…)", + "url": "http://localhost:8080/binary-contents?ids=20000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000002", + "time": 3, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 3 + ], + "allTests": [ + { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "365c8a34-d62f-413d-81e0-46e02a774df0", + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ (์ „์ฒด)", + "url": "http://localhost:8080/binary-contents?ids=20000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000002,40000000-0000-0000-0000-000000000002", + "time": 2, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 2 + ], + "allTests": [ + { + "ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต": true + } + ] + } + ], + "count": 1, + "totalTime": 168, + "collection": { + "requests": [ + { + "id": "62f080e5-1d87-4729-b59c-d9c262cc57f6", + "method": "POST" + }, + { + "id": "38f02c1b-1da3-4305-8a73-966ecd4f514a", + "method": "PUT" + }, + { + "id": "e223f7ff-58d7-474b-916f-bb9e870340a0", + "method": "DELETE" + }, + { + "id": "05657515-669f-416b-abb6-bcd7a6ffc9b1", + "method": "GET" + }, + { + "id": "3c0426e3-cad0-45a9-b870-d3542de16393", + "method": "PUT" + }, + { + "id": "1fccb8ae-a199-42c2-84b1-2e6e050ac4d5", + "method": "PUT" + }, + { + "id": "e4500e3f-52b4-40ab-873a-7f0c488a79ba", + "method": "PUT" + }, + { + "id": "13e2e344-786e-4ec1-bc5f-a73047a93695", + "method": "POST" + }, + { + "id": "67a951f5-93ee-4ea9-967c-e8e29fb4b6ad", + "method": "POST" + }, + { + "id": "f8543bc4-6878-4e40-84b0-48293ece9e98", + "method": "POST" + }, + { + "id": "c448ff0b-1c2c-44d6-ac60-a1755ac7b888", + "method": "PUT" + }, + { + "id": "e7cdf82b-b3be-457d-93c9-8b4610e1e4db", + "method": "DELETE" + }, + { + "id": "bd3213a7-5150-4bbe-8b10-177e2b9b15c1", + "method": "GET" + }, + { + "id": "468a25e1-0e84-435d-96da-779eb8e77281", + "method": "POST" + }, + { + "id": "1122053e-946c-44d9-a1bc-5c814f355f0b", + "method": "PUT" + }, + { + "id": "1cb94982-c954-4c27-bb93-441313ce2519", + "method": "DELETE" + }, + { + "id": "52706e31-cbee-4fdc-9638-44097655653d", + "method": "GET" + }, + { + "id": "5b4ca621-295c-4261-a9df-9978c4f22062", + "method": "POST" + }, + { + "id": "7ab4d359-041b-4b6e-b686-bebb06a082bd", + "method": "PUT" + }, + { + "id": "182a1d87-c2cc-4d03-9ff1-2cc073749b91", + "method": "GET" + }, + { + "id": "2ea5804a-177f-4c81-861c-2074010d9e74", + "method": "GET" + }, + { + "id": "71b66c5b-c4c6-4fc0-9df8-15780abe0860", + "method": "GET" + }, + { + "id": "365c8a34-d62f-413d-81e0-46e02a774df0", + "method": "GET" + } + ] + } +} \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java b/discodeit/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java index 02ef38cb..1b0a91b3 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java @@ -3,219 +3,25 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import com.sprint.mission.discodeit.dto.request.*; -import com.sprint.mission.discodeit.dto.response.ChannelResponse; -import com.sprint.mission.discodeit.dto.response.MessageResponse; -import com.sprint.mission.discodeit.dto.response.UserResponse; -import com.sprint.mission.discodeit.entity.BinaryContent; -import com.sprint.mission.discodeit.entity.ReadStatus; -import com.sprint.mission.discodeit.entity.UserStatus; -import com.sprint.mission.discodeit.service.*; -import org.springframework.context.ConfigurableApplicationContext; - -import java.time.Instant; -import java.util.List; -import java.util.NoSuchElementException; - +/** + * Discodeit ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฉ”์ธ ํด๋ž˜์Šค + * + * Discord์™€ ์œ ์‚ฌํ•œ ๋ฉ”์‹œ์ง• ํ”Œ๋žซํผ์œผ๋กœ, ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: + * - ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ (ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ํ”„๋กœํ•„ ์ด๋ฏธ์ง€) + * - ์ฑ„๋„ ๊ด€๋ฆฌ (๊ณต๊ฐœ ์ฑ„๋„, ๋น„๊ณต๊ฐœ ์ฑ„๋„) + * - ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ (๋ฉ”์‹œ์ง€ ์ž‘์„ฑ, ์ฒจ๋ถ€ํŒŒ์ผ) + * - ์ฝ๊ธฐ ์ƒํƒœ ์ถ”์  + * - ์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ ๊ด€๋ฆฌ + */ @SpringBootApplication public class DiscodeitApplication { + /** + * ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘์  + * + * @param args ๋ช…๋ นํ–‰ ์ธ์ˆ˜ + */ public static void main(String[] args) { - ConfigurableApplicationContext context = SpringApplication.run(DiscodeitApplication.class, args); - - // Spring Context์—์„œ Bean ์กฐํšŒ - UserService userService = context.getBean(UserService.class); - ChannelService channelService = context.getBean(ChannelService.class); - MessageService messageService = context.getBean(MessageService.class); - AuthService authService = context.getBean(AuthService.class); - UserStatusService userStatusService = context.getBean(UserStatusService.class); - ReadStatusService readStatusService = context.getBean(ReadStatusService.class); - BinaryContentService binaryContentService = context.getBean(BinaryContentService.class); - - System.out.println("\n========== Discodeit ์„œ๋น„์Šค ์ „์ฒด ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ==========\n"); - - // ===== 1. User ์ƒ์„ฑ ํ…Œ์ŠคํŠธ (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ) ===== - System.out.println("--- 1. User ์ƒ์„ฑ ํ…Œ์ŠคํŠธ (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ) ---"); - BinaryContentCreateRequest profileImageRequest = new BinaryContentCreateRequest( - "profile.png", - "image/png", - new byte[]{1, 2, 3, 4, 5} - ); - UserCreateRequest userRequest = new UserCreateRequest("woody", "woody@codeit.com", "woody1234"); - UserResponse user = userService.create(userRequest, profileImageRequest); - System.out.println("User ์ƒ์„ฑ: " + user.id()); - System.out.println(" - username: " + user.username()); - System.out.println(" - email: " + user.email()); - System.out.println(" - online: " + user.online()); - - // ๋‘ ๋ฒˆ์งธ ์‚ฌ์šฉ์ž ์ƒ์„ฑ (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—†์ด) - UserCreateRequest user2Request = new UserCreateRequest("alice", "alice@codeit.com", "alice1234"); - UserResponse user2 = userService.create(user2Request, null); - System.out.println("User2 ์ƒ์„ฑ: " + user2.id()); - System.out.println(" - username: " + user2.username()); - - // ===== 2. AuthService ๋กœ๊ทธ์ธ ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 2. AuthService ๋กœ๊ทธ์ธ ํ…Œ์ŠคํŠธ ---"); - LoginRequest loginRequest = new LoginRequest("woody", "woody1234"); - UserResponse loggedInUser = authService.login(loginRequest); - System.out.println("๋กœ๊ทธ์ธ ์„ฑ๊ณต: " + loggedInUser.username()); - System.out.println(" - online: " + loggedInUser.online()); - - // ์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ…Œ์ŠคํŠธ - try { - LoginRequest wrongLogin = new LoginRequest("woody", "wrongpassword"); - authService.login(wrongLogin); - } catch (NoSuchElementException e) { - System.out.println("์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋กœ๊ทธ์ธ ์‹คํŒจ: " + e.getMessage()); - } - - // ===== 3. UserStatus ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 3. UserStatus ํ…Œ์ŠคํŠธ ---"); - UserStatus userStatus = userStatusService.findByUserId(user.id()); - System.out.println("UserStatus ์กฐํšŒ: " + userStatus.getId()); - System.out.println(" - userId: " + userStatus.getUserId()); - System.out.println(" - isOnline: " + userStatus.isOnline()); - - // UserStatus ์—…๋ฐ์ดํŠธ - UserStatusUpdateRequest statusUpdateRequest = new UserStatusUpdateRequest(Instant.now()); - userStatusService.updateByUserId(user.id(), statusUpdateRequest); - System.out.println("UserStatus ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ - lastActiveAt ๊ฐฑ์‹ ๋จ"); - - // ===== 4. PUBLIC Channel ์ƒ์„ฑ ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 4. PUBLIC Channel ์ƒ์„ฑ ํ…Œ์ŠคํŠธ ---"); - PublicChannelCreateRequest publicChannelRequest = new PublicChannelCreateRequest("๊ณต์ง€", "๊ณต์ง€ ์ฑ„๋„์ž…๋‹ˆ๋‹ค."); - ChannelResponse publicChannel = channelService.createPublic(publicChannelRequest); - System.out.println("PUBLIC Channel ์ƒ์„ฑ: " + publicChannel.id()); - System.out.println(" - name: " + publicChannel.name()); - System.out.println(" - type: " + publicChannel.type()); - - // PUBLIC Channel ์ˆ˜์ • ํ…Œ์ŠคํŠธ - ChannelUpdateRequest channelUpdateRequest = new ChannelUpdateRequest("๊ณต์ง€์‚ฌํ•ญ", "๊ณต์ง€์‚ฌํ•ญ ์ฑ„๋„์ž…๋‹ˆ๋‹ค."); - ChannelResponse updatedChannel = channelService.update(publicChannel.id(), channelUpdateRequest); - System.out.println("PUBLIC Channel ์ˆ˜์ •: " + updatedChannel.name()); - - // ===== 5. PRIVATE Channel ์ƒ์„ฑ ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 5. PRIVATE Channel ์ƒ์„ฑ ํ…Œ์ŠคํŠธ ---"); - PrivateChannelCreateRequest privateChannelRequest = new PrivateChannelCreateRequest( - List.of(user.id(), user2.id()) - ); - ChannelResponse privateChannel = channelService.createPrivate(privateChannelRequest); - System.out.println("PRIVATE Channel ์ƒ์„ฑ: " + privateChannel.id()); - System.out.println(" - type: " + privateChannel.type()); - System.out.println(" - participantIds: " + privateChannel.participantIds()); - - // PRIVATE Channel ์ˆ˜์ • ์‹œ๋„ (์‹คํŒจํ•ด์•ผ ํ•จ) - try { - channelService.update(privateChannel.id(), channelUpdateRequest); - System.out.println("PRIVATE Channel ์ˆ˜์ •: ์‹คํŒจ - ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!"); - } catch (IllegalArgumentException e) { - System.out.println("PRIVATE Channel ์ˆ˜์ • ์ฐจ๋‹จ๋จ: " + e.getMessage()); - } - - // ===== 6. ReadStatus ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 6. ReadStatus ํ…Œ์ŠคํŠธ ---"); - // PRIVATE ์ฑ„๋„ ์ƒ์„ฑ ์‹œ ์ž๋™์œผ๋กœ ReadStatus๊ฐ€ ์ƒ์„ฑ๋˜์–ด์•ผ ํ•จ - List readStatuses = readStatusService.findAllByUserId(user.id()); - System.out.println("User์˜ ReadStatus ์ˆ˜: " + readStatuses.size()); - for (ReadStatus rs : readStatuses) { - System.out.println(" - channelId: " + rs.getChannelId() + ", lastReadAt: " + rs.getLastReadAt()); - } - - // ReadStatus ์—…๋ฐ์ดํŠธ - if (!readStatuses.isEmpty()) { - ReadStatus firstReadStatus = readStatuses.get(0); - ReadStatusUpdateRequest rsUpdateRequest = new ReadStatusUpdateRequest(Instant.now()); - readStatusService.update(firstReadStatus.getId(), rsUpdateRequest); - System.out.println("ReadStatus ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ - lastReadAt ๊ฐฑ์‹ ๋จ"); - } - - // ===== 7. Message ์ƒ์„ฑ ํ…Œ์ŠคํŠธ (์ฒจ๋ถ€ํŒŒ์ผ ํฌํ•จ) ===== - System.out.println("\n--- 7. Message ์ƒ์„ฑ ํ…Œ์ŠคํŠธ (์ฒจ๋ถ€ํŒŒ์ผ ํฌํ•จ) ---"); - BinaryContentCreateRequest attachmentRequest = new BinaryContentCreateRequest( - "document.pdf", - "application/pdf", - new byte[]{10, 20, 30, 40, 50} - ); - MessageCreateRequest messageRequest = new MessageCreateRequest( - "์•ˆ๋…•ํ•˜์„ธ์š”! ์ฒจ๋ถ€ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.", - publicChannel.id(), - user.id() - ); - MessageResponse message = messageService.create(messageRequest, List.of(attachmentRequest)); - System.out.println("Message ์ƒ์„ฑ: " + message.id()); - System.out.println(" - content: " + message.content()); - System.out.println(" - channelId: " + message.channelId()); - System.out.println(" - authorId: " + message.authorId()); - System.out.println(" - attachmentIds: " + message.attachmentIds()); - - // ์ฒจ๋ถ€ํŒŒ์ผ ์—†๋Š” ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ - MessageCreateRequest message2Request = new MessageCreateRequest( - "๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.", - publicChannel.id(), - user.id() - ); - MessageResponse message2 = messageService.create(message2Request, null); - System.out.println("Message2 ์ƒ์„ฑ (์ฒจ๋ถ€ํŒŒ์ผ ์—†์Œ): " + message2.id()); - - // ===== 8. Message ์ˆ˜์ • ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 8. Message ์ˆ˜์ • ํ…Œ์ŠคํŠธ ---"); - MessageUpdateRequest messageUpdateRequest = new MessageUpdateRequest("์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค."); - MessageResponse updatedMessage = messageService.update(message2.id(), messageUpdateRequest); - System.out.println("Message ์ˆ˜์ •: " + updatedMessage.content()); - - // ===== 9. User ์ˆ˜์ • ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 9. User ์ˆ˜์ • ํ…Œ์ŠคํŠธ ---"); - BinaryContentCreateRequest newProfileRequest = new BinaryContentCreateRequest( - "new_profile.jpg", - "image/jpeg", - new byte[]{100, 101, 102} - ); - UserUpdateRequest userUpdateRequest = new UserUpdateRequest("woody_updated", "woody_new@codeit.com", "newpassword"); - UserResponse updatedUser = userService.update(user.id(), userUpdateRequest, newProfileRequest); - System.out.println("User ์ˆ˜์ •: " + updatedUser.username()); - System.out.println(" - email: " + updatedUser.email()); - - // ===== 10. BinaryContent ์กฐํšŒ ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 10. BinaryContent ์กฐํšŒ ํ…Œ์ŠคํŠธ ---"); - if (!message.attachmentIds().isEmpty()) { - BinaryContent attachment = binaryContentService.find(message.attachmentIds().get(0)); - System.out.println("์ฒจ๋ถ€ํŒŒ์ผ ์กฐํšŒ: " + attachment.getId()); - System.out.println(" - fileName: " + attachment.getFileName()); - System.out.println(" - contentType: " + attachment.getContentType()); - System.out.println(" - size: " + attachment.getData().length + " bytes"); - } - - // ===== 11. ์กฐํšŒ ํ…Œ์ŠคํŠธ ===== - System.out.println("\n--- 11. ์ „์ฒด ์กฐํšŒ ํ…Œ์ŠคํŠธ ---"); - List users = userService.findAll(); - System.out.println("์ „์ฒด User ์ˆ˜: " + users.size()); - - List channels = channelService.findAllByUserId(user.id()); - System.out.println("User๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” Channel ์ˆ˜: " + channels.size()); - - List messages = messageService.findAllByChannelId(publicChannel.id()); - System.out.println("Channel์˜ Message ์ˆ˜: " + messages.size()); - - // ===== 12. ์‚ญ์ œ ํ…Œ์ŠคํŠธ (Cascading) ===== - System.out.println("\n--- 12. ์‚ญ์ œ ํ…Œ์ŠคํŠธ (Cascading) ---"); - - // Message ์‚ญ์ œ (์ฒจ๋ถ€ํŒŒ์ผ๋„ ํ•จ๊ป˜ ์‚ญ์ œ) - System.out.println("Message ์‚ญ์ œ ์ „ - Message ์ˆ˜: " + messageService.findAllByChannelId(publicChannel.id()).size()); - messageService.delete(message.id()); - System.out.println("Message ์‚ญ์ œ ํ›„ - Message ์ˆ˜: " + messageService.findAllByChannelId(publicChannel.id()).size()); - - // Channel ์‚ญ์ œ (๊ด€๋ จ Message, ReadStatus ํ•จ๊ป˜ ์‚ญ์ œ) - System.out.println("\nPRIVATE Channel ์‚ญ์ œ ํ…Œ์ŠคํŠธ"); - channelService.delete(privateChannel.id()); - System.out.println("PRIVATE Channel ์‚ญ์ œ ์™„๋ฃŒ"); - - // User ์‚ญ์ œ ํ…Œ์ŠคํŠธ (๊ด€๋ จ UserStatus, BinaryContent ํ•จ๊ป˜ ์‚ญ์ œ) - System.out.println("\nUser2 ์‚ญ์ œ ํ…Œ์ŠคํŠธ"); - userService.delete(user2.id()); - System.out.println("User2 ์‚ญ์ œ ์™„๋ฃŒ"); - List remainingUsers = userService.findAll(); - System.out.println("๋‚จ์€ User ์ˆ˜: " + remainingUsers.size()); - - System.out.println("\n========== ์ „์ฒด ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ==========\n"); + SpringApplication.run(DiscodeitApplication.class, args); } } diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/config/DataInitializer.java b/discodeit/src/main/java/com/sprint/mission/discodeit/config/DataInitializer.java new file mode 100644 index 00000000..28cd6ce4 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/config/DataInitializer.java @@ -0,0 +1,224 @@ +package com.sprint.mission.discodeit.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.time.Instant; +import java.util.UUID; +import javax.imageio.ImageIO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Random; + +/** + * ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ํด๋ž˜์Šค + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DataInitializer implements CommandLineRunner { + + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + private final UserStatusRepository userStatusRepository; + private final ObjectMapper objectMapper; + + @Override + public void run(String... args) throws Exception { + log.info("=== ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ์ž‘ ==="); + + // ๊ฐ•์ œ ์ดˆ๊ธฐํ™” ๋ชจ๋“œ ํ™•์ธ (--force-init ์ธ์ž) + boolean forceInit = args.length > 0 && args[0].equals("--force-init"); + + List existingUsers = userRepository.findAll(); + + if (forceInit && !existingUsers.isEmpty()) { + log.info("๊ฐ•์ œ ์ดˆ๊ธฐํ™” ๋ชจ๋“œ: ๊ธฐ์กด ์‚ฌ์šฉ์ž {} ๋ช… ์‚ญ์ œ ํ›„ ์žฌ์ƒ์„ฑ", existingUsers.size()); + existingUsers.forEach(user -> { + userRepository.deleteById(user.getId()); + userStatusRepository.deleteByUserId(user.getId()); + if (user.getProfileId() != null) { + binaryContentRepository.deleteById(user.getProfileId()); + } + }); + } else if (!existingUsers.isEmpty()) { + log.info("๊ธฐ์กด ์‚ฌ์šฉ์ž {} ๋ช…์ด ์กด์žฌํ•˜์—ฌ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.", existingUsers.size()); + log.info("๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ --force-init ์˜ต์…˜๊ณผ ํ•จ๊ป˜ ์‹คํ–‰ํ•˜์„ธ์š”."); + return; + } + + loadInitialUsers(); + log.info("=== ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ ==="); + } + + /** + * users.json ํŒŒ์ผ์—์„œ ์ดˆ๊ธฐ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. + */ + private void loadInitialUsers() { + try { + // users.json ํŒŒ์ผ ์ฝ๊ธฐ + ClassPathResource resource = new ClassPathResource("data/users.json"); + List initialUsers = objectMapper.readValue( + resource.getInputStream(), + objectMapper.getTypeFactory().constructCollectionType(List.class, InitialUser.class) + ); + + // ๊ฐ ์‚ฌ์šฉ์ž ์ƒ์„ฑ + for (InitialUser initialUser : initialUsers) { + createUser(initialUser); + } + + log.info("์ด {}๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", initialUsers.size()); + + } catch (IOException e) { + log.error("์ดˆ๊ธฐ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ", e); + } + } + + /** + * ์‚ฌ์šฉ์ž์™€ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค (๊ณ ์ • UUID ์‚ฌ์šฉ). + */ + private void createUser(InitialUser initialUser) { + try { + // 1. ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (๊ณ ์ • UUID ์‚ฌ์šฉ) + UUID profileId = null; + if (initialUser.profileImage() != null && initialUser.profileId() != null) { + byte[] imageData = loadProfileImageData(initialUser.profileImage(), initialUser.username()); + if (imageData != null) { + String contentType = getContentType(initialUser.profileImage()); + BinaryContent profile = new BinaryContent( + initialUser.profileId(), + initialUser.profileImage(), + contentType, + imageData + ); + binaryContentRepository.save(profile); + profileId = initialUser.profileId(); + } + } + + // 2. ์‚ฌ์šฉ์ž ์ƒ์„ฑ (๊ณ ์ • UUID ์‚ฌ์šฉ) + User user = new User( + initialUser.id(), + initialUser.username(), + initialUser.email(), + initialUser.password(), + profileId + ); + userRepository.save(user); + + // 3. ์‚ฌ์šฉ์ž ์ƒํƒœ ์ƒ์„ฑ (์˜จ๋ผ์ธ ์ƒํƒœ๋กœ ์„ค์ •) + UserStatus userStatus = new UserStatus(initialUser.id(), Instant.now()); + userStatusRepository.save(userStatus); + + log.info("์‚ฌ์šฉ์ž ์ƒ์„ฑ ์™„๋ฃŒ: {} ({})", initialUser.username(), initialUser.email()); + + } catch (Exception e) { + log.error("์‚ฌ์šฉ์ž ์ƒ์„ฑ ์‹คํŒจ: {} - {}", initialUser.username(), e.getMessage()); + } + } + + /** + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. + * ํŒŒ์ผ์ด ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ๋žœ๋ค ์•„๋ฐ”ํƒ€ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + */ + private byte[] loadProfileImageData(String filename, String username) { + try { + ClassPathResource imageResource = new ClassPathResource("data/profiles/" + filename); + return Files.readAllBytes(imageResource.getFile().toPath()); + } catch (IOException e) { + log.warn("ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์—†์Œ ({}), {} ์•„๋ฐ”ํƒ€ ์ƒ์„ฑ", filename, username); + return generateRandomAvatar(username); + } + } + + /** + * ๋žœ๋ค ์•„๋ฐ”ํƒ€ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + */ + private byte[] generateRandomAvatar(String username) { + try { + Random random = new Random(username.hashCode()); + int size = 200; + BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = image.createGraphics(); + + // ์•ˆํ‹ฐ์•จ๋ฆฌ์–ด์‹ฑ ์ ์šฉ + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + // ์‚ฌ์šฉ์ž๋ณ„ ๊ณ ์ •๋œ ๋ฐฐ๊ฒฝ์ƒ‰ (ํŒŒ์Šคํ…”ํ†ค) + Color[] pastelColors = { + new Color(255, 179, 186), // ์—ฐํ•œ ๋ถ„ํ™ + new Color(255, 223, 186), // ์—ฐํ•œ ์ฃผํ™ฉ + new Color(255, 255, 186), // ์—ฐํ•œ ๋…ธ๋ž‘ + new Color(186, 255, 201), // ์—ฐํ•œ ์ดˆ๋ก + new Color(186, 225, 255), // ์—ฐํ•œ ํŒŒ๋ž‘ + new Color(220, 198, 224), // ์—ฐํ•œ ๋ณด๋ผ + }; + Color bgColor = pastelColors[Math.abs(username.hashCode()) % pastelColors.length]; + g2d.setColor(bgColor); + g2d.fillRect(0, 0, size, size); + + // ์ด๋‹ˆ์…œ ํ…์ŠคํŠธ (์‚ฌ์šฉ์ž ์ด๋ฆ„์˜ ์ฒซ ๊ธ€์ž) + g2d.setColor(Color.WHITE); + // ํ•œ๊ธ€ ํฐํŠธ ์ง€์› + g2d.setFont(new Font("๋ง‘์€ ๊ณ ๋”•", Font.BOLD, 80)); + FontMetrics fm = g2d.getFontMetrics(); + String initial = username.substring(0, 1); + int x = (size - fm.stringWidth(initial)) / 2; + int y = ((size - fm.getHeight()) / 2) + fm.getAscent(); + g2d.drawString(initial, x, y); + + g2d.dispose(); + + // PNG๋กœ ๋ณ€ํ™˜ + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "png", baos); + return baos.toByteArray(); + + } catch (Exception e) { + log.error("๋žœ๋ค ์•„๋ฐ”ํƒ€ ์ƒ์„ฑ ์‹คํŒจ: {}", username, e); + return new byte[0]; + } + } + + /** + * ํŒŒ์ผ ํ™•์žฅ์ž๋กœ Content-Type์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. + */ + private String getContentType(String filename) { + if (filename.endsWith(".png")) { + return "image/png"; + } else if (filename.endsWith(".gif")) { + return "image/gif"; + } + return "image/jpeg"; + } + + /** + * ์ดˆ๊ธฐ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ DTO + */ + private record InitialUser( + UUID id, + String username, + String email, + String password, + String profileImage, + UUID profileId + ) { + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java new file mode 100644 index 00000000..c9e3a837 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java @@ -0,0 +1,39 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.LoginRequest; +import com.sprint.mission.discodeit.dto.response.UserResponse; +import com.sprint.mission.discodeit.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * ์ธ์ฆ ๊ด€๋ จ REST API ์ปจํŠธ๋กค๋Ÿฌ + * + * ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + + /** + * ๋กœ๊ทธ์ธ + * POST /auth/login + */ + @RequestMapping( + method = RequestMethod.POST, + path = "login", + consumes = {MediaType.APPLICATION_JSON_VALUE} + ) + public ResponseEntity login(@RequestBody LoginRequest request) { + UserResponse user = authService.login(request); + return ResponseEntity.ok(user); + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java new file mode 100644 index 00000000..80a846f4 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java @@ -0,0 +1,48 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.service.BinaryContentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; + +/** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๊ด€๋ จ REST API ์ปจํŠธ๋กค๋Ÿฌ + * + * ํŒŒ์ผ(ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ ๋“ฑ) ์กฐํšŒ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestController +@RequestMapping("/binary-contents") +@RequiredArgsConstructor +public class BinaryContentController { + + private final BinaryContentService binaryContentService; + + /** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ + * GET /binary-contents/{id} + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}") + public ResponseEntity getBinaryContent(@PathVariable UUID id) { + BinaryContent binaryContent = binaryContentService.find(id); + return ResponseEntity.ok(binaryContent); + } + + /** + * ์—ฌ๋Ÿฌ ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์กฐํšŒ + * GET /binary-contents?ids={id1},{id2},... + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getBinaryContents(@RequestParam List ids) { + List binaryContents = binaryContentService.findAllByIdIn(ids); + return ResponseEntity.ok(binaryContents); + } + +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java new file mode 100644 index 00000000..88e874f8 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java @@ -0,0 +1,95 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.ChannelUpdateRequest; +import com.sprint.mission.discodeit.dto.request.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; +import com.sprint.mission.discodeit.dto.response.ChannelResponse; +import com.sprint.mission.discodeit.service.ChannelService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; + +/** + * ์ฑ„๋„ ๊ด€๋ จ REST API ์ปจํŠธ๋กค๋Ÿฌ + * + * ๊ณต๊ฐœ/๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestController +@RequestMapping("/channels") +@RequiredArgsConstructor +public class ChannelController { + + private final ChannelService channelService; + + /** + * ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + * POST /channels/public + */ + @RequestMapping(method = RequestMethod.POST, value = "/public") + public ResponseEntity createPublicChannel(@RequestBody PublicChannelCreateRequest request) { + ChannelResponse channel = channelService.createPublic(request); + return ResponseEntity.status(HttpStatus.CREATED).body(channel); + } + + /** + * ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + * POST /channels/private + */ + @RequestMapping(method = RequestMethod.POST, value = "/private") + public ResponseEntity createPrivateChannel(@RequestBody PrivateChannelCreateRequest request) { + ChannelResponse channel = channelService.createPrivate(request); + return ResponseEntity.status(HttpStatus.CREATED).body(channel); + } + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ฑ„๋„ ์กฐํšŒ + * GET /channels?userId={userId} + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getChannelsByUser(@RequestParam UUID userId) { + List channels = channelService.findAllByUserId(userId); + return ResponseEntity.ok(channels); + } + + /** + * ํŠน์ • ์ฑ„๋„ ์กฐํšŒ + * GET /channels/{id} + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}") + public ResponseEntity getChannel(@PathVariable UUID id) { + ChannelResponse channel = channelService.find(id); + return ResponseEntity.ok(channel); + } + + /** + * ๊ณต๊ฐœ ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • + * PUT /channels/{id} + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}") + public ResponseEntity updateChannel( + @PathVariable UUID id, + @RequestBody ChannelUpdateRequest request + ) { + ChannelResponse channel = channelService.update(id, request); + return ResponseEntity.ok(channel); + } + + /** + * ์ฑ„๋„ ์‚ญ์ œ + * DELETE /channels/{id} + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{id}") + public ResponseEntity deleteChannel(@PathVariable UUID id) { + channelService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java new file mode 100644 index 00000000..0715d71b --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java @@ -0,0 +1,88 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.dto.request.MessageCreateRequest; +import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest; +import com.sprint.mission.discodeit.dto.response.MessageResponse; +import com.sprint.mission.discodeit.service.MessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; + +/** + * ๋ฉ”์‹œ์ง€ ๊ด€๋ จ REST API ์ปจํŠธ๋กค๋Ÿฌ + * + * ๋ฉ”์‹œ์ง€ ์ „์†ก, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋ฐ ์ฒจ๋ถ€ํŒŒ์ผ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestController +@RequestMapping("/messages") +@RequiredArgsConstructor +public class MessageController { + + private final MessageService messageService; + + /** + * ๋ฉ”์‹œ์ง€ ์ „์†ก + * POST /messages + */ + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity createMessage( + @RequestBody MessageCreateRequest messageRequest + ) { + // ์ฒจ๋ถ€ํŒŒ์ผ์€ null๋กœ ์ „๋‹ฌ (๋ณ„๋„ ์—”๋“œํฌ์ธํŠธ๋กœ ์—…๋กœ๋“œ ๊ฐ€๋Šฅ) + MessageResponse message = messageService.create(messageRequest, null); + return ResponseEntity.status(HttpStatus.CREATED).body(message); + } + + /** + * ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ + * GET /messages?channelId={channelId} + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getMessagesByChannel(@RequestParam UUID channelId) { + List messages = messageService.findAllByChannelId(channelId); + return ResponseEntity.ok(messages); + } + + /** + * ํŠน์ • ๋ฉ”์‹œ์ง€ ์กฐํšŒ + * GET /messages/{id} + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}") + public ResponseEntity getMessage(@PathVariable UUID id) { + MessageResponse message = messageService.find(id); + return ResponseEntity.ok(message); + } + + /** + * ๋ฉ”์‹œ์ง€ ์ˆ˜์ • + * PUT /messages/{id} + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}") + public ResponseEntity updateMessage( + @PathVariable UUID id, + @RequestBody MessageUpdateRequest request + ) { + MessageResponse message = messageService.update(id, request); + return ResponseEntity.ok(message); + } + + /** + * ๋ฉ”์‹œ์ง€ ์‚ญ์ œ + * DELETE /messages/{id} + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{id}") + public ResponseEntity deleteMessage(@PathVariable UUID id) { + messageService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java new file mode 100644 index 00000000..13f82315 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java @@ -0,0 +1,64 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.ReadStatusCreateRequest; +import com.sprint.mission.discodeit.dto.request.ReadStatusUpdateRequest; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.service.ReadStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; + +/** + * ์ฝ๊ธฐ ์ƒํƒœ ๊ด€๋ จ REST API ์ปจํŠธ๋กค๋Ÿฌ + * + * ์‚ฌ์šฉ์ž์˜ ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestController +@RequestMapping("/read-statuses") +@RequiredArgsConstructor +public class ReadStatusController { + + private final ReadStatusService readStatusService; + + /** + * ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ + * POST /read-statuses + */ + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity createReadStatus(@RequestBody ReadStatusCreateRequest request) { + ReadStatus readStatus = readStatusService.create(request); + return ResponseEntity.status(HttpStatus.CREATED).body(readStatus); + } + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ + * GET /read-statuses?userId={userId} + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getReadStatusesByUser(@RequestParam UUID userId) { + List readStatuses = readStatusService.findAllByUserId(userId); + return ResponseEntity.ok(readStatuses); + } + + /** + * ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • + * PUT /read-statuses/{id} + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}") + public ResponseEntity updateReadStatus( + @PathVariable UUID id, + @RequestBody ReadStatusUpdateRequest request + ) { + ReadStatus readStatus = readStatusService.update(id, request); + return ResponseEntity.ok(readStatus); + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/controller/UserController.java b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/UserController.java new file mode 100644 index 00000000..c9b2ca57 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/controller/UserController.java @@ -0,0 +1,183 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.dto.request.UserCreateRequest; +import com.sprint.mission.discodeit.dto.request.UserStatusUpdateRequest; +import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; +import com.sprint.mission.discodeit.dto.response.UserDto; +import com.sprint.mission.discodeit.dto.response.UserResponse; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.service.UserService; +import com.sprint.mission.discodeit.service.UserStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.UUID; + +/** + * ์‚ฌ์šฉ์ž ๊ด€๋ จ REST API ์ปจํŠธ๋กค๋Ÿฌ + * + * ์‚ฌ์šฉ์ž ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋ฐ ์˜จ๋ผ์ธ ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestController +@RequestMapping("/users") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + private final UserStatusService userStatusService; + + /** + * ์‚ฌ์šฉ์ž ๋“ฑ๋ก (JSON) + * POST /users + * Content-Type: application/json + */ + @RequestMapping(method = RequestMethod.POST, consumes = "application/json") + public ResponseEntity createUser( + @RequestBody UserCreateRequest userRequest + ) { + // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋Š” null๋กœ ์ „๋‹ฌ + UserResponse user = userService.create(userRequest, null); + return ResponseEntity.status(HttpStatus.CREATED).body(user); + } + + /** + * ์‚ฌ์šฉ์ž ๋“ฑ๋ก (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ) + * POST /users/with-profile + * Content-Type: multipart/form-data + */ + @RequestMapping(method = RequestMethod.POST, value = "/with-profile", consumes = "multipart/form-data") + public ResponseEntity createUserWithProfile( + @RequestParam("username") String username, + @RequestParam("email") String email, + @RequestParam("password") String password, + @RequestParam(value = "profileImage", required = false) MultipartFile profileImage + ) { + try { + // ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์š”์ฒญ ๊ฐ์ฒด ์ƒ์„ฑ + UserCreateRequest userRequest = new UserCreateRequest(username, email, password); + + // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด BinaryContentCreateRequest๋กœ ๋ณ€ํ™˜ + BinaryContentCreateRequest profileRequest = null; + if (profileImage != null && !profileImage.isEmpty()) { + profileRequest = new BinaryContentCreateRequest( + profileImage.getOriginalFilename(), + profileImage.getContentType(), + profileImage.getBytes() + ); + } + + // ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserResponse user = userService.create(userRequest, profileRequest); + return ResponseEntity.status(HttpStatus.CREATED).body(user); + + } catch (Exception e) { + throw new RuntimeException("ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹คํŒจ: " + e.getMessage(), e); + } + } + + /** + * ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ + * GET /users + * ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ UserDto ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + */ + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity> getAllUsers() { + List users = userService.findAllAsDto(); + return ResponseEntity.ok(users); + } + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ + * GET /users/{id} + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}") + public ResponseEntity getUser(@PathVariable UUID id) { + UserResponse user = userService.find(id); + return ResponseEntity.ok(user); + } + + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (JSON) + * PUT /users/{id} + * Content-Type: application/json + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}", consumes = "application/json") + public ResponseEntity updateUser( + @PathVariable UUID id, + @RequestBody UserUpdateRequest userRequest + ) { + // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋Š” null๋กœ ์ „๋‹ฌ + UserResponse user = userService.update(id, userRequest, null); + return ResponseEntity.ok(user); + } + + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ) + * PUT /users/{id}/with-profile + * Content-Type: multipart/form-data + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}/with-profile", consumes = "multipart/form-data") + public ResponseEntity updateUserWithProfile( + @PathVariable UUID id, + @RequestParam("username") String username, + @RequestParam("email") String email, + @RequestParam("password") String password, + @RequestParam(value = "profileImage", required = false) MultipartFile profileImage + ) { + try { + // ์‚ฌ์šฉ์ž ์ˆ˜์ • ์š”์ฒญ ๊ฐ์ฒด ์ƒ์„ฑ + UserUpdateRequest userRequest = new UserUpdateRequest(username, email, password); + + // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด BinaryContentCreateRequest๋กœ ๋ณ€ํ™˜ + BinaryContentCreateRequest profileRequest = null; + if (profileImage != null && !profileImage.isEmpty()) { + profileRequest = new BinaryContentCreateRequest( + profileImage.getOriginalFilename(), + profileImage.getContentType(), + profileImage.getBytes() + ); + } + + // ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • + UserResponse user = userService.update(id, userRequest, profileRequest); + return ResponseEntity.ok(user); + + } catch (Exception e) { + throw new RuntimeException("ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹คํŒจ: " + e.getMessage(), e); + } + } + + /** + * ์‚ฌ์šฉ์ž ์‚ญ์ œ + * DELETE /users/{id} + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{id}") + public ResponseEntity deleteUser(@PathVariable UUID id) { + userService.delete(id); + return ResponseEntity.noContent().build(); + } + + /** + * ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + * PUT /users/{id}/status + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{id}/status") + public ResponseEntity updateUserStatus( + @PathVariable UUID id, + @RequestBody UserStatusUpdateRequest request + ) { + UserStatus userStatus = userStatusService.updateByUserId(id, request); + return ResponseEntity.ok(userStatus); + } + +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/docs/API_DOCUMENTATION.md b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/API_DOCUMENTATION.md new file mode 100644 index 00000000..a1dc3328 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/API_DOCUMENTATION.md @@ -0,0 +1,808 @@ +# ๋””์Šค์ฝ”๋“œ์ž‡ API ๋ฌธ์„œ + +๋””์Šค์ฝ”๋“œ์ž‡ ์›น API์˜ ์ „์ฒด ์—”๋“œํฌ์ธํŠธ์™€ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. + +**Base URL:** `http://localhost:8080` + +**Content-Type:** `application/json` + +--- + +## ๐Ÿ“‘ ๋ชฉ์ฐจ + +1. [์‚ฌ์šฉ์ž ๊ด€๋ฆฌ](#1-์‚ฌ์šฉ์ž-๊ด€๋ฆฌ) +2. [๊ถŒํ•œ ๊ด€๋ฆฌ](#2-๊ถŒํ•œ-๊ด€๋ฆฌ) +3. [์ฑ„๋„ ๊ด€๋ฆฌ](#3-์ฑ„๋„-๊ด€๋ฆฌ) +4. [๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ](#4-๋ฉ”์‹œ์ง€-๊ด€๋ฆฌ) +5. [๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ](#5-๋ฉ”์‹œ์ง€-์ˆ˜์‹ -์ •๋ณด-๊ด€๋ฆฌ) +6. [๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๊ด€๋ฆฌ](#6-๋ฐ”์ด๋„ˆ๋ฆฌ-ํŒŒ์ผ-๊ด€๋ฆฌ) + +--- + +## 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ + +### 1.1 ์‚ฌ์šฉ์ž ๋“ฑ๋ก + +์‚ฌ์šฉ์ž๋ฅผ ์ƒˆ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. + +```http +POST /users +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "username": "testuser", + "email": "test@example.com", + "password": "password123" +} +``` + +**Response (201 Created):** +```json +{ + "id": "uuid", + "username": "testuser", + "email": "test@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 1.2 ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ + +๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /users +``` + +**Response (200 OK):** +```json +[ + { + "id": "uuid", + "username": "testuser", + "email": "test@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" + } +] +``` + +--- + +### 1.3 ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ + +ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /users/{userId} +``` + +**Path Parameters:** +- `userId` (UUID, required): ์‚ฌ์šฉ์ž ID + +**Response (200 OK):** +```json +{ + "id": "uuid", + "username": "testuser", + "email": "test@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +**Error (404 Not Found):** +```json +{ + "message": "User not found: {userId}", + "timestamp": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 1.4 ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • + +์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. + +```http +PUT /users/{userId} +Content-Type: application/json +``` + +**Path Parameters:** +- `userId` (UUID, required): ์‚ฌ์šฉ์ž ID + +**Request Body:** +```json +{ + "username": "updated_user", + "email": "updated@example.com", + "password": "newpassword456" +} +``` + +**Response (200 OK):** +```json +{ + "id": "uuid", + "username": "updated_user", + "email": "updated@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:10:00Z" +} +``` + +--- + +### 1.5 ์‚ฌ์šฉ์ž ์‚ญ์ œ + +์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์—ฐ๊ด€๋œ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€์™€ ์ƒํƒœ ์ •๋ณด๋„ ํ•จ๊ป˜ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + +```http +DELETE /users/{userId} +``` + +**Path Parameters:** +- `userId` (UUID, required): ์‚ฌ์šฉ์ž ID + +**Response (204 No Content)** + +--- + +### 1.6 ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + +์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + +```http +PUT /users/{userId}/status +Content-Type: application/json +``` + +**Path Parameters:** +- `userId` (UUID, required): ์‚ฌ์šฉ์ž ID + +**Request Body:** +```json +{ + "lastActiveAt": "2026-02-09T08:00:00Z" +} +``` + +**Response (200 OK):** +```json +{ + "userId": "uuid", + "lastActiveAt": "2026-02-09T08:00:00Z", + "id": "uuid", + "createdAt": "2026-02-09T07:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z", + "online": true +} +``` + +--- + +## 2. ๊ถŒํ•œ ๊ด€๋ฆฌ + +### 2.1 ๋กœ๊ทธ์ธ + +์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธํ•ฉ๋‹ˆ๋‹ค. + +```http +POST /auth/login +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "username": "testuser", + "password": "password123" +} +``` + +**Response (200 OK):** +```json +{ + "id": "uuid", + "username": "testuser", + "email": "test@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +**Error (404 Not Found):** +```json +{ + "message": "Invalid username or password", + "timestamp": "2026-02-09T08:00:00Z" +} +``` + +--- + +## 3. ์ฑ„๋„ ๊ด€๋ฆฌ + +### 3.1 ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + +PUBLIC ํƒ€์ž…์˜ ์ฑ„๋„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +```http +POST /channels/public +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "name": "์ผ๋ฐ˜ ๋Œ€ํ™”", + "description": "์ž์œ ๋กญ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ์ฑ„๋„" +} +``` + +**Response (201 Created):** +```json +{ + "id": "uuid", + "type": "PUBLIC", + "name": "์ผ๋ฐ˜ ๋Œ€ํ™”", + "description": "์ž์œ ๋กญ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ์ฑ„๋„", + "participantIds": null, + "lastMessageAt": null, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 3.2 ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + +PRIVATE ํƒ€์ž…์˜ ์ฑ„๋„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ฐธ์—ฌ์ž ๋ชฉ๋ก์ด ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. + +```http +POST /channels/private +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "memberIds": [ + "user-uuid-1", + "user-uuid-2" + ] +} +``` + +**Response (201 Created):** +```json +{ + "id": "uuid", + "type": "PRIVATE", + "name": null, + "description": null, + "participantIds": [ + "user-uuid-1", + "user-uuid-2" + ], + "lastMessageAt": null, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 3.3 ์‚ฌ์šฉ์ž๋ณ„ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ + +ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ฑ„๋„์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. +- PUBLIC ์ฑ„๋„: ๋ชจ๋‘ ์กฐํšŒ ๊ฐ€๋Šฅ +- PRIVATE ์ฑ„๋„: ์ฐธ์—ฌ์ž๋งŒ ์กฐํšŒ ๊ฐ€๋Šฅ + +```http +GET /channels?userId={userId} +``` + +**Query Parameters:** +- `userId` (UUID, required): ์‚ฌ์šฉ์ž ID + +**Response (200 OK):** +```json +[ + { + "id": "uuid", + "type": "PUBLIC", + "name": "์ผ๋ฐ˜ ๋Œ€ํ™”", + "description": "์ž์œ ๋กญ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ์ฑ„๋„", + "participantIds": null, + "lastMessageAt": "2026-02-09T07:50:00Z", + "createdAt": "2026-02-09T07:00:00Z", + "updatedAt": "2026-02-09T07:00:00Z" + }, + { + "id": "uuid", + "type": "PRIVATE", + "name": null, + "description": null, + "participantIds": ["uuid1", "uuid2"], + "lastMessageAt": null, + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" + } +] +``` + +--- + +### 3.4 ํŠน์ • ์ฑ„๋„ ์กฐํšŒ + +ํŠน์ • ์ฑ„๋„์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /channels/{channelId} +``` + +**Path Parameters:** +- `channelId` (UUID, required): ์ฑ„๋„ ID + +**Response (200 OK):** +```json +{ + "id": "uuid", + "type": "PUBLIC", + "name": "์ผ๋ฐ˜ ๋Œ€ํ™”", + "description": "์ž์œ ๋กญ๊ฒŒ ๋Œ€ํ™”ํ•˜๋Š” ์ฑ„๋„", + "participantIds": null, + "lastMessageAt": "2026-02-09T07:50:00Z", + "createdAt": "2026-02-09T07:00:00Z", + "updatedAt": "2026-02-09T07:00:00Z" +} +``` + +--- + +### 3.5 ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • + +PUBLIC ์ฑ„๋„์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. PRIVATE ์ฑ„๋„์€ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +```http +PUT /channels/{channelId} +Content-Type: application/json +``` + +**Path Parameters:** +- `channelId` (UUID, required): ์ฑ„๋„ ID + +**Request Body:** +```json +{ + "name": "์ˆ˜์ •๋œ ์ฑ„๋„๋ช…", + "description": "์ˆ˜์ •๋œ ์„ค๋ช…" +} +``` + +**Response (200 OK):** +```json +{ + "id": "uuid", + "type": "PUBLIC", + "name": "์ˆ˜์ •๋œ ์ฑ„๋„๋ช…", + "description": "์ˆ˜์ •๋œ ์„ค๋ช…", + "participantIds": null, + "lastMessageAt": "2026-02-09T07:50:00Z", + "createdAt": "2026-02-09T07:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +**Error (400 Bad Request) - PRIVATE ์ฑ„๋„ ์ˆ˜์ • ์‹œ๋„:** +```json +{ + "message": "Private channel cannot be updated", + "timestamp": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 3.6 ์ฑ„๋„ ์‚ญ์ œ + +์ฑ„๋„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์—ฐ๊ด€๋œ ๋ฉ”์‹œ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ, ReadStatus๋„ ํ•จ๊ป˜ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + +```http +DELETE /channels/{channelId} +``` + +**Path Parameters:** +- `channelId` (UUID, required): ์ฑ„๋„ ID + +**Response (204 No Content)** + +--- + +## 4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ + +### 4.1 ๋ฉ”์‹œ์ง€ ์ „์†ก + +์ฑ„๋„์— ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. + +```http +POST /messages +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "content": "์•ˆ๋…•ํ•˜์„ธ์š”! ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.", + "channelId": "channel-uuid", + "authorId": "user-uuid" +} +``` + +**Response (201 Created):** +```json +{ + "id": "uuid", + "content": "์•ˆ๋…•ํ•˜์„ธ์š”! ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.", + "channelId": "channel-uuid", + "authorId": "user-uuid", + "attachmentIds": [], + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 4.2 ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์กฐํšŒ + +ํŠน์ • ์ฑ„๋„์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /messages?channelId={channelId} +``` + +**Query Parameters:** +- `channelId` (UUID, required): ์ฑ„๋„ ID + +**Response (200 OK):** +```json +[ + { + "id": "uuid", + "content": "์•ˆ๋…•ํ•˜์„ธ์š”! ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.", + "channelId": "channel-uuid", + "authorId": "user-uuid", + "attachmentIds": [], + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" + } +] +``` + +--- + +### 4.3 ํŠน์ • ๋ฉ”์‹œ์ง€ ์กฐํšŒ + +ํŠน์ • ๋ฉ”์‹œ์ง€์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /messages/{messageId} +``` + +**Path Parameters:** +- `messageId` (UUID, required): ๋ฉ”์‹œ์ง€ ID + +**Response (200 OK):** +```json +{ + "id": "uuid", + "content": "์•ˆ๋…•ํ•˜์„ธ์š”! ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.", + "channelId": "channel-uuid", + "authorId": "user-uuid", + "attachmentIds": [], + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 4.4 ๋ฉ”์‹œ์ง€ ์ˆ˜์ • + +๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. + +```http +PUT /messages/{messageId} +Content-Type: application/json +``` + +**Path Parameters:** +- `messageId` (UUID, required): ๋ฉ”์‹œ์ง€ ID + +**Request Body:** +```json +{ + "content": "์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ" +} +``` + +**Response (200 OK):** +```json +{ + "id": "uuid", + "content": "์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ", + "channelId": "channel-uuid", + "authorId": "user-uuid", + "attachmentIds": [], + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:10:00Z" +} +``` + +--- + +### 4.5 ๋ฉ”์‹œ์ง€ ์‚ญ์ œ + +๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์—ฐ๊ด€๋œ ์ฒจ๋ถ€ํŒŒ์ผ๋„ ํ•จ๊ป˜ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + +```http +DELETE /messages/{messageId} +``` + +**Path Parameters:** +- `messageId` (UUID, required): ๋ฉ”์‹œ์ง€ ID + +**Response (204 No Content)** + +--- + +## 5. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ + +### 5.1 ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ + +ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +```http +POST /read-status +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "userId": "user-uuid", + "channelId": "channel-uuid", + "lastReadAt": "2026-02-09T08:00:00Z" +} +``` + +**Response (201 Created):** +```json +{ + "userId": "user-uuid", + "channelId": "channel-uuid", + "lastReadAt": "2026-02-09T08:00:00Z", + "id": "uuid", + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 5.2 ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • + +๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + +```http +PUT /read-status/{readStatusId} +Content-Type: application/json +``` + +**Path Parameters:** +- `readStatusId` (UUID, required): ์ˆ˜์‹  ์ •๋ณด ID + +**Request Body:** +```json +{ + "lastReadAt": "2026-02-09T09:00:00Z" +} +``` + +**Response (200 OK):** +```json +{ + "userId": "user-uuid", + "channelId": "channel-uuid", + "lastReadAt": "2026-02-09T09:00:00Z", + "id": "uuid", + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T09:00:00Z" +} +``` + +--- + +### 5.3 ์‚ฌ์šฉ์ž๋ณ„ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ + +ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /read-status?userId={userId} +``` + +**Query Parameters:** +- `userId` (UUID, required): ์‚ฌ์šฉ์ž ID + +**Response (200 OK):** +```json +[ + { + "userId": "user-uuid", + "channelId": "channel-uuid-1", + "lastReadAt": "2026-02-09T08:00:00Z", + "id": "uuid-1", + "createdAt": "2026-02-09T07:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" + }, + { + "userId": "user-uuid", + "channelId": "channel-uuid-2", + "lastReadAt": "2026-02-09T07:30:00Z", + "id": "uuid-2", + "createdAt": "2026-02-09T07:00:00Z", + "updatedAt": "2026-02-09T07:30:00Z" + } +] +``` + +--- + +## 6. ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๊ด€๋ฆฌ + +### 6.1 ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ + +ํŠน์ • ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /binary-contents/{binaryContentId} +``` + +**Path Parameters:** +- `binaryContentId` (UUID, required): ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID + +**Response (200 OK):** +```json +{ + "id": "uuid", + "fileName": "test.png", + "contentType": "image/png", + "data": "base64-encoded-data", + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" +} +``` + +--- + +### 6.2 ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ + +์—ฌ๋Ÿฌ ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ์„ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +```http +GET /binary-contents?ids={id1},{id2},{id3} +``` + +**Query Parameters:** +- `ids` (comma-separated UUIDs, required): ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID ๋ชฉ๋ก + +**Response (200 OK):** +```json +[ + { + "id": "uuid-1", + "fileName": "file1.txt", + "contentType": "text/plain", + "data": "base64-encoded-data-1", + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" + }, + { + "id": "uuid-2", + "fileName": "file2.png", + "contentType": "image/png", + "data": "base64-encoded-data-2", + "createdAt": "2026-02-09T08:00:00Z", + "updatedAt": "2026-02-09T08:00:00Z" + } +] +``` + +--- + +## ๊ณตํ†ต ์—๋Ÿฌ ์‘๋‹ต + +### 400 Bad Request +์ž˜๋ชป๋œ ์š”์ฒญ ํ˜•์‹ + +```json +{ + "message": "Invalid request format", + "timestamp": "2026-02-09T08:00:00Z" +} +``` + +### 404 Not Found +๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ + +```json +{ + "message": "Resource not found: {resourceId}", + "timestamp": "2026-02-09T08:00:00Z" +} +``` + +### 500 Internal Server Error +์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜ + +```json +{ + "message": "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {error details}", + "timestamp": "2026-02-09T08:00:00Z" +} +``` + +--- + +## ํ…Œ์ŠคํŠธ ์ˆœ์„œ ๊ถŒ์žฅ์‚ฌํ•ญ + +### ๊ธฐ๋ณธ ํ”Œ๋กœ์šฐ +1. ์‚ฌ์šฉ์ž ๋“ฑ๋ก +2. ๋กœ๊ทธ์ธ +3. ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ +4. ๋ฉ”์‹œ์ง€ ์ „์†ก +5. ๋ฉ”์‹œ์ง€ ์กฐํšŒ +6. ๋ฉ”์‹œ์ง€ ์ˆ˜์ • +7. ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + +### ๋น„๊ณต๊ฐœ ์ฑ„๋„ ํ”Œ๋กœ์šฐ +1. ์‚ฌ์šฉ์ž 2๋ช… ๋“ฑ๋ก +2. ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ (๋‘ ์‚ฌ์šฉ์ž ์ดˆ๋Œ€) +3. ๊ฐ ์‚ฌ์šฉ์ž๋ณ„ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ +4. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ +5. ๋ฉ”์‹œ์ง€ ์ „์†ก ๋ฐ ์กฐํšŒ + +--- + +## ์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค + +- **Postman Collection**: `Discodeit_API_Collection.postman_collection.json` +- **Environment File**: `Discodeit_Environment.postman_environment.json` +- **Python ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ**: `test_api_automation.py` +- **Bash ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ**: `/tmp/test_discodeit_api.sh` + +--- + +**๋ฌธ์„œ ๋ฒ„์ „**: 1.0 +**์ตœ์ข… ์ˆ˜์ •์ผ**: 2026-02-09 diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/docs/POSTMAN_GUIDE.md b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/POSTMAN_GUIDE.md new file mode 100644 index 00000000..911c542f --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/POSTMAN_GUIDE.md @@ -0,0 +1,308 @@ +# ๐Ÿ“ฎ Postman ์‚ฌ์šฉ ๊ฐ€์ด๋“œ + +๋””์Šค์ฝ”๋“œ์ž‡ API๋ฅผ Postman์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ์™„๋ฒฝ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. + +--- + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +### 1๋‹จ๊ณ„: ํŒŒ์ผ Import + +#### โœ… Collection Import +1. Postman ์‹คํ–‰ +2. **Import** ๋ฒ„ํŠผ ํด๋ฆญ +3. `Discodeit_API_Collection.postman_collection.json` ํŒŒ์ผ ์„ ํƒ +4. **Import** ํด๋ฆญ + +#### โœ… Environment Import +1. Postman์—์„œ **Environments** ํƒญ ์„ ํƒ +2. **Import** ๋ฒ„ํŠผ ํด๋ฆญ +3. `Discodeit_Environment.postman_environment.json` ํŒŒ์ผ ์„ ํƒ +4. **Import** ํด๋ฆญ + +### 2๋‹จ๊ณ„: Environment ์„ ํƒ + +์šฐ์ธก ์ƒ๋‹จ์˜ Environment ๋“œ๋กญ๋‹ค์šด์—์„œ **"Discodeit Local"** ์„ ํƒ + +### 3๋‹จ๊ณ„: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ + +ํ„ฐ๋ฏธ๋„์—์„œ: +```bash +cd /Users/ijongho/IdeaProjects/9-sprint-mission/discodeit +./gradlew bootRun +``` + +๋˜๋Š” IDE์—์„œ Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ + +### 4๋‹จ๊ณ„: API ํ…Œ์ŠคํŠธ ์‹œ์ž‘! + +Collection์—์„œ ์›ํ•˜๋Š” API ์„ ํƒ โ†’ **Send** ๋ฒ„ํŠผ ํด๋ฆญ + +--- + +## ๐Ÿ“‚ Collection ๊ตฌ์กฐ + +``` +๋””์Šค์ฝ”๋“œ์ž‡ API +โ”œโ”€โ”€ 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž ๋“ฑ๋ก โ† ์—ฌ๊ธฐ์„œ ์‹œ์ž‘! +โ”‚ โ”œโ”€โ”€ ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž ์ˆ˜์ • +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž ์‚ญ์ œ +โ”‚ โ””โ”€โ”€ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +โ”œโ”€โ”€ 2. ๊ถŒํ•œ ๊ด€๋ฆฌ +โ”‚ โ””โ”€โ”€ ๋กœ๊ทธ์ธ +โ”œโ”€โ”€ 3. ์ฑ„๋„ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ +โ”‚ โ”œโ”€โ”€ ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž๋ณ„ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ํŠน์ • ์ฑ„๋„ ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„ ์ˆ˜์ • +โ”‚ โ””โ”€โ”€ ์ฑ„๋„ ์‚ญ์ œ +โ”œโ”€โ”€ 4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ๋ฉ”์‹œ์ง€ ์ „์†ก +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ํŠน์ • ๋ฉ”์‹œ์ง€ ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ๋ฉ”์‹œ์ง€ ์ˆ˜์ • +โ”‚ โ””โ”€โ”€ ๋ฉ”์‹œ์ง€ ์‚ญ์ œ +โ”œโ”€โ”€ 5. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด +โ”‚ โ”œโ”€โ”€ ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ +โ”‚ โ”œโ”€โ”€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • +โ”‚ โ””โ”€โ”€ ์‚ฌ์šฉ์ž๋ณ„ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ +โ””โ”€โ”€ 6. ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ + โ”œโ”€โ”€ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ + โ””โ”€โ”€ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ +``` + +--- + +## ๐ŸŽฏ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ๊ธฐ๋ณธ ํ”Œ๋กœ์šฐ (์ถ”์ฒœ) + +**๋ชฉํ‘œ**: ์‚ฌ์šฉ์ž ์ƒ์„ฑ โ†’ ์ฑ„๋„ ์ƒ์„ฑ โ†’ ๋ฉ”์‹œ์ง€ ์ „์†ก + +1. **์‚ฌ์šฉ์ž ๋“ฑ๋ก** + - `1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ > ์‚ฌ์šฉ์ž ๋“ฑ๋ก` ์‹คํ–‰ + - โœ… userId๊ฐ€ ์ž๋™์œผ๋กœ Environment์— ์ €์žฅ๋จ + +2. **๋กœ๊ทธ์ธ** + - `2. ๊ถŒํ•œ ๊ด€๋ฆฌ > ๋กœ๊ทธ์ธ` ์‹คํ–‰ + - โœ… ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ™•์ธ + +3. **๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ** + - `3. ์ฑ„๋„ ๊ด€๋ฆฌ > ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ` ์‹คํ–‰ + - โœ… channelId๊ฐ€ ์ž๋™์œผ๋กœ Environment์— ์ €์žฅ๋จ + +4. **๋ฉ”์‹œ์ง€ ์ „์†ก** + - `4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ > ๋ฉ”์‹œ์ง€ ์ „์†ก` ์‹คํ–‰ + - โœ… messageId๊ฐ€ ์ž๋™์œผ๋กœ Environment์— ์ €์žฅ๋จ + +5. **๋ฉ”์‹œ์ง€ ์กฐํšŒ** + - `4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ > ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์กฐํšŒ` ์‹คํ–‰ + - โœ… ๋ฐฉ๊ธˆ ์ „์†กํ•œ ๋ฉ”์‹œ์ง€ ํ™•์ธ + +6. **๋ฉ”์‹œ์ง€ ์ˆ˜์ •** + - `4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ > ๋ฉ”์‹œ์ง€ ์ˆ˜์ •` ์‹คํ–‰ + - โœ… ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ ๋ณ€๊ฒฝ ํ™•์ธ + +--- + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋น„๊ณต๊ฐœ ์ฑ„๋„ + +**๋ชฉํ‘œ**: ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ๋ฐ ์ฐธ์—ฌ์ž ๊ด€๋ฆฌ + +1. **์‚ฌ์šฉ์ž 2๋ช… ๋“ฑ๋ก** + - ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ์ž ๋“ฑ๋ก โ†’ userId1 ๋ณต์‚ฌ + - Body์˜ username, email ๋ณ€๊ฒฝ ํ›„ ๋‹ค์‹œ ์‹คํ–‰ โ†’ userId2 ๋ณต์‚ฌ + +2. **๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ** + - `3. ์ฑ„๋„ ๊ด€๋ฆฌ > ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ` + - Body ์ˆ˜์ •: + ```json + { + "memberIds": [ + "{{userId}}", // ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ์ž + "paste-userId2-here" // ๋‘ ๋ฒˆ์งธ ์‚ฌ์šฉ์ž + ] + } + ``` + +3. **์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ** + - Environment์˜ userId๋ฅผ ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ์ž๋กœ ์„ค์ • + - `3. ์ฑ„๋„ ๊ด€๋ฆฌ > ์‚ฌ์šฉ์ž๋ณ„ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ` + - โœ… ๋น„๊ณต๊ฐœ ์ฑ„๋„ ํฌํ•จ ํ™•์ธ + + - Environment์˜ userId๋ฅผ ๋‘ ๋ฒˆ์งธ ์‚ฌ์šฉ์ž๋กœ ์„ค์ • + - ๋‹ค์‹œ ์กฐํšŒ + - โœ… ๊ฐ™์€ ๋น„๊ณต๊ฐœ ์ฑ„๋„ ํ™•์ธ + +--- + +### ์‹œ๋‚˜๋ฆฌ์˜ค 3: CRUD ์ „์ฒด ํ…Œ์ŠคํŠธ + +**๋ชฉํ‘œ**: ์ƒ์„ฑ-์กฐํšŒ-์ˆ˜์ •-์‚ญ์ œ ์ „์ฒด ํ”Œ๋กœ์šฐ + +#### ์‚ฌ์šฉ์ž CRUD +``` +์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ • โ†’ ์‚ญ์ œ +``` + +#### ์ฑ„๋„ CRUD +``` +์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ • โ†’ ์‚ญ์ œ +``` + +#### ๋ฉ”์‹œ์ง€ CRUD +``` +์ „์†ก โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ • โ†’ ์‚ญ์ œ +``` + +--- + +## ๐Ÿ”ง Environment ๋ณ€์ˆ˜ + +| ๋ณ€์ˆ˜๋ช… | ์„ค๋ช… | ์ž๋™ ์ €์žฅ | +|--------|------|----------| +| `baseUrl` | API ๊ธฐ๋ณธ URL | โŒ ์ˆ˜๋™ | +| `userId` | ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ID | โœ… ์ž๋™ | +| `channelId` | ์ƒ์„ฑ๋œ ์ฑ„๋„ ID | โœ… ์ž๋™ | +| `messageId` | ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€ ID | โœ… ์ž๋™ | +| `readStatusId` | ์ƒ์„ฑ๋œ ์ˆ˜์‹ ์ •๋ณด ID | โœ… ์ž๋™ | +| `binaryContentId` | ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ID | โŒ ์ˆ˜๋™ | + +### ๋ณ€์ˆ˜ ํ™•์ธ ๋ฐฉ๋ฒ• + +1. ์šฐ์ธก ์ƒ๋‹จ Environment ์•„์ด์ฝ˜ ํด๋ฆญ +2. "Discodeit Local" ์„ ํƒ +3. **Current Value** ์ปฌ๋Ÿผ์—์„œ ์ €์žฅ๋œ ๊ฐ’ ํ™•์ธ + +### ๋ณ€์ˆ˜ ์ˆ˜๋™ ์„ค์ • + +1. Environment ํŽธ์ง‘ ํ™”๋ฉด์—์„œ **Current Value** ์ž…๋ ฅ +2. ๋˜๋Š” **Console** (ํ•˜๋‹จ)์—์„œ ์ž๋™ ์ €์žฅ ๋กœ๊ทธ ํ™•์ธ: + ``` + ์‚ฌ์šฉ์ž ID ์ €์žฅ: uuid-value + ์ฑ„๋„ ID ์ €์žฅ: uuid-value + ``` + +--- + +## ๐Ÿ’ก ์œ ์šฉํ•œ ํŒ + +### 1๏ธโƒฃ Tests ์Šคํฌ๋ฆฝํŠธ + +๊ฐ ์š”์ฒญ์—๋Š” ์ž๋™์œผ๋กœ ID๋ฅผ ์ €์žฅํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค: + +```javascript +// ์‚ฌ์šฉ์ž ๋“ฑ๋ก ํ›„ ์ž๋™ ์‹คํ–‰ +if (pm.response.code === 201) { + var jsonData = pm.response.json(); + pm.environment.set("userId", jsonData.id); + console.log("์‚ฌ์šฉ์ž ID ์ €์žฅ:", jsonData.id); +} +``` + +### 2๏ธโƒฃ ์‘๋‹ต ์ž๋™ ํฌ๋งทํŒ… + +Postman์ด ์ž๋™์œผ๋กœ JSON์„ ๋ณด๊ธฐ ์ข‹๊ฒŒ ํฌ๋งทํŒ…ํ•ฉ๋‹ˆ๋‹ค. +- **Pretty** ํƒญ: ์ฝ๊ธฐ ํŽธํ•œ ํ˜•์‹ +- **Raw** ํƒญ: ์›๋ณธ JSON +- **Preview** ํƒญ: HTML ๋ Œ๋”๋ง (ํ•ด๋‹น ์‹œ) + +### 3๏ธโƒฃ Collection Runner + +์ „์ฒด API๋ฅผ ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธ: + +1. Collection ์šฐํด๋ฆญ โ†’ **Run collection** +2. ์‹คํ–‰ํ•  ์š”์ฒญ ์„ ํƒ +3. **Run** ๋ฒ„ํŠผ ํด๋ฆญ +4. โœ… ์ „์ฒด ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ™•์ธ + +### 4๏ธโƒฃ ์š”์ฒญ ๋ณต์‚ฌ + +ํŠน์ • ์š”์ฒญ์„ curl๋กœ ๋ณ€ํ™˜: + +1. ์š”์ฒญ ์šฐํด๋ฆญ โ†’ **Export** โ†’ **Request** +2. ๋˜๋Š” **Code** ์•„์ด์ฝ˜ ํด๋ฆญ โ†’ curl ์„ ํƒ + +--- + +## ๐Ÿ› ๋ฌธ์ œ ํ•ด๊ฒฐ + +### ๋ฌธ์ œ 1: "baseUrl is not defined" + +**์›์ธ**: Environment๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์Œ + +**ํ•ด๊ฒฐ**: +1. ์šฐ์ธก ์ƒ๋‹จ์—์„œ "Discodeit Local" ์„ ํƒ +2. Environment ๋ณ€์ˆ˜ ํ™•์ธ + +### ๋ฌธ์ œ 2: "Failed to connect" + +**์›์ธ**: Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜์ง€ ์•Š์Œ + +**ํ•ด๊ฒฐ**: +```bash +# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ™•์ธ +lsof -i :8080 + +# ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด +./gradlew bootRun +``` + +### ๋ฌธ์ œ 3: "User not found" / "Channel not found" + +**์›์ธ**: Environment ๋ณ€์ˆ˜๊ฐ€ ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ UUID + +**ํ•ด๊ฒฐ**: +1. ์‚ฌ์šฉ์ž ๋“ฑ๋ก๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ +2. Environment์˜ `userId`, `channelId` ํ™•์ธ + +### ๋ฌธ์ œ 4: "Required request body is missing" + +**์›์ธ**: Body ํƒญ์—์„œ raw + JSON์ด ์„ ํƒ๋˜์ง€ ์•Š์Œ + +**ํ•ด๊ฒฐ**: +1. Body ํƒญ ํด๋ฆญ +2. **raw** ์„ ํƒ +3. ์šฐ์ธก ๋“œ๋กญ๋‹ค์šด์—์„œ **JSON** ์„ ํƒ + +--- + +## ๐Ÿ“Š ์ถ”๊ฐ€ ๋„๊ตฌ + +### Bash ์Šคํฌ๋ฆฝํŠธ +์ „์ฒด API๋ฅผ ํ„ฐ๋ฏธ๋„์—์„œ ํ…Œ์ŠคํŠธ: +```bash +/tmp/test_discodeit_api.sh +``` + +### Python ์Šคํฌ๋ฆฝํŠธ +ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ API ํ…Œ์ŠคํŠธ: +```bash +# ์˜์กด์„ฑ ์„ค์น˜ +pip install -r requirements.txt + +# ์‹คํ–‰ +python test_api_automation.py +``` + +### API ๋ฌธ์„œ +์ƒ์„ธํ•œ API ๋ฌธ์„œ: +``` +API_DOCUMENTATION.md +``` + +--- + +## ๐Ÿ“ž ๋„์›€์ด ํ•„์š”ํ•˜์‹ ๊ฐ€์š”? + +- **API ๋ฌธ์„œ**: `API_DOCUMENTATION.md` ์ฐธ์กฐ +- **์—๋Ÿฌ ๋กœ๊ทธ**: Postman Console (ํ•˜๋‹จ) ํ™•์ธ +- **์„œ๋ฒ„ ๋กœ๊ทธ**: Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ˜์†” ํ™•์ธ + +--- + +**Happy Testing! ๐ŸŽ‰** diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/docs/POSTMAN_SETUP_GUIDE_v2.md b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/POSTMAN_SETUP_GUIDE_v2.md new file mode 100644 index 00000000..c1ea16ab --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/POSTMAN_SETUP_GUIDE_v2.md @@ -0,0 +1,335 @@ +# ๐Ÿ“ฎ Postman ์™„๋ฒฝ ๊ฐ€์ด๋“œ v2 (๊ฐœ์„  ๋ฒ„์ „) + +## ๐ŸŽฏ ์ฃผ์š” ๊ฐœ์„ ์‚ฌํ•ญ + +### โœ… ํ•ด๊ฒฐ๋œ ๋ฌธ์ œ + +**์ด์ „ ๋ฒ„์ „์˜ ๋ฌธ์ œ:** +- โŒ ์‚ฌ์šฉ์ž ์‚ญ์ œ ํ›„ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ +- โŒ ์ฑ„๋„ ์‚ญ์ œ ํ›„ ๋ฉ”์‹œ์ง€ ํ…Œ์ŠคํŠธ ์‹คํŒจ +- โŒ ์‚ญ์ œ ํ…Œ์ŠคํŠธ๋กœ ์ธํ•œ ์—ฐ์‡„ ์‹คํŒจ + +**v2์˜ ํ•ด๊ฒฐ์ฑ…:** +- โœ… **์‚ฌ์šฉ์ž 3๋ช… ๋ฏธ๋ฆฌ ์ƒ์„ฑ** (user1, user2, user3) +- โœ… **์ฑ„๋„ 3๊ฐœ ๋ฏธ๋ฆฌ ์ƒ์„ฑ** (์ผ๋ฐ˜, ๊ณต์ง€, ์งˆ๋ฌธ) +- โœ… **์‚ญ์ œ์šฉ ๋ฆฌ์†Œ์Šค ๋ถ„๋ฆฌ** (user3, channel3 ์‚ญ์ œ ์ „์šฉ) +- โœ… **์‚ญ์ œ ํ›„์—๋„ ๊ณ„์† ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ** + +--- + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +### 1๋‹จ๊ณ„: Import + +#### Collection Import +1. Postman ์‹คํ–‰ +2. **Import** ๋ฒ„ํŠผ ํด๋ฆญ +3. `Discodeit_API_Collection_v2.postman_collection.json` ์„ ํƒ +4. **Import** ํด๋ฆญ + +#### Environment Import +1. **Environments** ํƒญ +2. **Import** ๋ฒ„ํŠผ ํด๋ฆญ +3. `Discodeit_Environment_v2.postman_environment.json` ์„ ํƒ +4. **Import** ํด๋ฆญ + +### 2๋‹จ๊ณ„: Environment ์„ ํƒ + +์šฐ์ธก ์ƒ๋‹จ์—์„œ **"Discodeit Local v2"** ์„ ํƒ + +### 3๋‹จ๊ณ„: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ + +ํ„ฐ๋ฏธ๋„์—์„œ: +```bash +cd /Users/ijongho/IdeaProjects/9-sprint-mission/discodeit +./gradlew bootRun +``` + +### 4๋‹จ๊ณ„: ์ดˆ๊ธฐ ์„ค์ • ์‹คํ–‰ โš ๏ธ ์ค‘์š”! + +**๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:** + +``` +Collection > 0. ์ดˆ๊ธฐ ์„ค์ • (๋จผ์ € ์‹คํ–‰) +``` + +**Collection Runner ์‚ฌ์šฉ (๊ถŒ์žฅ):** +1. "0. ์ดˆ๊ธฐ ์„ค์ •" ํด๋” ์šฐํด๋ฆญ +2. **Run folder** ์„ ํƒ +3. **Run** ๋ฒ„ํŠผ ํด๋ฆญ +4. 6๊ฐœ API๊ฐ€ ์ˆœ์ฐจ ์‹คํ–‰๋จ: + - ์‚ฌ์šฉ์ž 1, 2, 3 ๋“ฑ๋ก + - ์ฑ„๋„ 1, 2, 3 ์ƒ์„ฑ + +**๋˜๋Š” ์ˆ˜๋™์œผ๋กœ:** +1. ์‚ฌ์šฉ์ž 1 ๋“ฑ๋ก โ†’ Send +2. ์‚ฌ์šฉ์ž 2 ๋“ฑ๋ก โ†’ Send +3. ์‚ฌ์šฉ์ž 3 ๋“ฑ๋ก โ†’ Send +4. ์ฑ„๋„ 1 ์ƒ์„ฑ โ†’ Send +5. ์ฑ„๋„ 2 ์ƒ์„ฑ โ†’ Send +6. ์ฑ„๋„ 3 ์ƒ์„ฑ โ†’ Send + +--- + +## ๐Ÿ“‚ Collection ๊ตฌ์กฐ v2 + +``` +๋””์Šค์ฝ”๋“œ์ž‡ API v2 +โ”œโ”€โ”€ 0. ์ดˆ๊ธฐ ์„ค์ • (๋จผ์ € ์‹คํ–‰) โญ ๊ฐ€์žฅ ์ค‘์š”! +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 1 ๋“ฑ๋ก +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 2 ๋“ฑ๋ก +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 3 ๋“ฑ๋ก +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„ 1 ์ƒ์„ฑ (์ผ๋ฐ˜) +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„ 2 ์ƒ์„ฑ (๊ณต์ง€) +โ”‚ โ””โ”€โ”€ ์ฑ„๋„ 3 ์ƒ์„ฑ (์งˆ๋ฌธ) +โ”œโ”€โ”€ 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž1 ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž2 ์ •๋ณด ์ˆ˜์ • +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž3 ์‚ญ์ œ (ํ…Œ์ŠคํŠธ์šฉ) โ† ์‚ญ์ œํ•ด๋„ user1, user2 ๋‚จ์Œ +โ”‚ โ””โ”€โ”€ ์‚ฌ์šฉ์ž1 ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +โ”œโ”€โ”€ 2. ๊ถŒํ•œ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ user1 ๋กœ๊ทธ์ธ +โ”‚ โ””โ”€โ”€ user2 ๋กœ๊ทธ์ธ +โ”œโ”€โ”€ 3. ์ฑ„๋„ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ user1์˜ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1 ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„2 ์ •๋ณด ์ˆ˜์ • +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„3 ์‚ญ์ œ (ํ…Œ์ŠคํŠธ์šฉ) โ† ์‚ญ์ œํ•ด๋„ ์ฑ„๋„1, ์ฑ„๋„2 ๋‚จ์Œ +โ”‚ โ””โ”€โ”€ ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ (user1, user2) +โ”œโ”€โ”€ 4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€ ์ „์†ก (user1) +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€ ์ „์†ก (user2) +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ๋ฉ”์‹œ์ง€1 ์กฐํšŒ +โ”‚ โ”œโ”€โ”€ ๋ฉ”์‹œ์ง€1 ์ˆ˜์ • +โ”‚ โ””โ”€โ”€ ๋ฉ”์‹œ์ง€2 ์‚ญ์ œ (ํ…Œ์ŠคํŠธ์šฉ) โ† ์‚ญ์ œํ•ด๋„ ๋ฉ”์‹œ์ง€1 ๋‚จ์Œ +โ”œโ”€โ”€ 5. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1 ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ (user1) +โ”‚ โ”œโ”€โ”€ ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • +โ”‚ โ””โ”€โ”€ user1์˜ ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ +โ””โ”€โ”€ 6. ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ + โ”œโ”€โ”€ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ + โ””โ”€โ”€ ํŒŒ์ผ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ +``` + +--- + +## ๐ŸŽฏ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ „์ฒด ํ”Œ๋กœ์šฐ (์ถ”์ฒœ) + +**๋ชฉํ‘œ**: ์ดˆ๊ธฐ ์„ค์ •๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€ ์ „์†ก๊นŒ์ง€ ์ „์ฒด ํ…Œ์ŠคํŠธ + +``` +1. ์ดˆ๊ธฐ ์„ค์ • ์‹คํ–‰ (0๋ฒˆ ํด๋”) + โ†“ +2. user1 ๋กœ๊ทธ์ธ + โ†“ +3. ์ฑ„๋„1์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ + โ†“ +4. ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€ ์ „์†ก + โ†“ +5. ๋ฉ”์‹œ์ง€ ์ˆ˜์ • + โ†“ +6. ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ +``` + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ์‚ญ์ œ ํ…Œ์ŠคํŠธ + +**๋ชฉํ‘œ**: ์‚ญ์ œ ํ›„์—๋„ ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค๋กœ ๊ณ„์† ํ…Œ์ŠคํŠธ + +``` +1. ์ดˆ๊ธฐ ์„ค์ • ์‹คํ–‰ + โ†“ +2. ์‚ฌ์šฉ์ž3 ์‚ญ์ œ โ† user1, user2๋Š” ๋‚จ์•„์žˆ์Œ + โ†“ +3. user1์œผ๋กœ ๋กœ๊ทธ์ธ โ† ์ •์ƒ ์ž‘๋™ โœ… + โ†“ +4. ์ฑ„๋„3 ์‚ญ์ œ โ† ์ฑ„๋„1, ์ฑ„๋„2๋Š” ๋‚จ์•„์žˆ์Œ + โ†“ +5. ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€ ์ „์†ก โ† ์ •์ƒ ์ž‘๋™ โœ… +``` + +### ์‹œ๋‚˜๋ฆฌ์˜ค 3: ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž ํ…Œ์ŠคํŠธ + +**๋ชฉํ‘œ**: ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ํ™œ๋™ํ•˜๋Š” ์ƒํ™ฉ + +``` +1. ์ดˆ๊ธฐ ์„ค์ • ์‹คํ–‰ + โ†“ +2. user1 ๋กœ๊ทธ์ธ + โ†“ +3. user2 ๋กœ๊ทธ์ธ + โ†“ +4. user1์ด ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€ ์ „์†ก + โ†“ +5. user2๊ฐ€ ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€ ์ „์†ก + โ†“ +6. ์ฑ„๋„1์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ โ† 2๊ฐœ ๋ฉ”์‹œ์ง€ ํ™•์ธ +``` + +--- + +## ๐Ÿ”ง Environment ๋ณ€์ˆ˜ v2 + +| ๋ณ€์ˆ˜๋ช… | ์„ค๋ช… | ์ž๋™ ์ €์žฅ | +|--------|------|----------| +| `baseUrl` | API ๊ธฐ๋ณธ URL | โŒ ์ˆ˜๋™ | +| `userId1` | ์‚ฌ์šฉ์ž1 ID | โœ… ์ž๋™ | +| `userId2` | ์‚ฌ์šฉ์ž2 ID | โœ… ์ž๋™ | +| `userId3` | ์‚ฌ์šฉ์ž3 ID (์‚ญ์ œ์šฉ) | โœ… ์ž๋™ | +| `channelId1` | ์ฑ„๋„1 ID (์ผ๋ฐ˜) | โœ… ์ž๋™ | +| `channelId2` | ์ฑ„๋„2 ID (๊ณต์ง€) | โœ… ์ž๋™ | +| `channelId3` | ์ฑ„๋„3 ID (์‚ญ์ œ์šฉ) | โœ… ์ž๋™ | +| `privateChannelId` | ๋น„๊ณต๊ฐœ ์ฑ„๋„ ID | โœ… ์ž๋™ | +| `messageId1` | ๋ฉ”์‹œ์ง€1 ID | โœ… ์ž๋™ | +| `messageId2` | ๋ฉ”์‹œ์ง€2 ID (์‚ญ์ œ์šฉ) | โœ… ์ž๋™ | +| `readStatusId1` | ์ˆ˜์‹ ์ •๋ณด1 ID | โœ… ์ž๋™ | +| `binaryContentId` | ํŒŒ์ผ ID | โŒ ์ˆ˜๋™ | + +--- + +## ๐Ÿ’ก ์‚ญ์ œ ํ…Œ์ŠคํŠธ ์ „๋žต + +### โœ… ์•ˆ์ „ํ•œ ์‚ญ์ œ ํ…Œ์ŠคํŠธ + +**์‚ญ์ œ ์ „์šฉ ๋ฆฌ์†Œ์Šค:** +- `user3` โ†’ ์‚ญ์ œ ํ›„์—๋„ user1, user2 ์‚ฌ์šฉ ๊ฐ€๋Šฅ +- `channel3` โ†’ ์‚ญ์ œ ํ›„์—๋„ ์ฑ„๋„1, ์ฑ„๋„2 ์‚ฌ์šฉ ๊ฐ€๋Šฅ +- `message2` โ†’ ์‚ญ์ œ ํ›„์—๋„ ๋ฉ”์‹œ์ง€1 ์‚ฌ์šฉ ๊ฐ€๋Šฅ + +**์ผ๋ฐ˜ ๋ฆฌ์†Œ์Šค (์‚ญ์ œํ•˜์ง€ ์•Š์Œ):** +- `user1`, `user2` โ†’ ์ง€์†์ ์œผ๋กœ ์‚ฌ์šฉ +- `channel1`, `channel2` โ†’ ์ง€์†์ ์œผ๋กœ ์‚ฌ์šฉ +- `message1` โ†’ ์ง€์†์ ์œผ๋กœ ์‚ฌ์šฉ + +--- + +## ๐Ÿ“Š Collection Runner ํ™œ์šฉ + +### ์ „์ฒด ์ž๋™ ํ…Œ์ŠคํŠธ + +**1. ์ดˆ๊ธฐ ์„ค์ • ์ž๋™ ์‹คํ–‰:** +``` +"0. ์ดˆ๊ธฐ ์„ค์ •" ํด๋” ์šฐํด๋ฆญ โ†’ Run folder +``` + +**2. ์ „์ฒด Collection ์‹คํ–‰:** +``` +Collection ์šฐํด๋ฆญ โ†’ Run collection +โ†’ ์ˆœ์„œ ํ™•์ธ โ†’ Run +``` + +**3. ๊ฒฐ๊ณผ ํ™•์ธ:** +- โœ… Passed: ์„ฑ๊ณตํ•œ ํ…Œ์ŠคํŠธ +- โŒ Failed: ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ +- ์ƒ์„ธ ๋กœ๊ทธ ํ™•์ธ ๊ฐ€๋Šฅ + +--- + +## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ + +### 1. ์ดˆ๊ธฐ ์„ค์ • ํ•„์ˆ˜ + +**๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ „์— "0. ์ดˆ๊ธฐ ์„ค์ •" ์‹คํ–‰ ํ•„์ˆ˜!** + +์‹คํ–‰ํ•˜์ง€ ์•Š์œผ๋ฉด: +- โŒ ๋กœ๊ทธ์ธ ์‹คํŒจ (404 Not Found) +- โŒ ์ฑ„๋„ ์กฐํšŒ ์‹คํŒจ +- โŒ ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํŒจ + +### 2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ ์‹œ + +**๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”๋จ (๋ฉ”๋ชจ๋ฆฌ DB):** + +``` +์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ + โ†“ +๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ + โ†“ +"0. ์ดˆ๊ธฐ ์„ค์ •" ๋‹ค์‹œ ์‹คํ–‰ +``` + +### 3. ์‚ญ์ œ ํ…Œ์ŠคํŠธ ์ˆœ์„œ + +**๊ถŒ์žฅ ์ˆœ์„œ:** + +``` +โœ… ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ๋จผ์ € + โ†“ +โœ… ์‚ญ์ œ ํ…Œ์ŠคํŠธ ๋‚˜์ค‘์— +``` + +**๋น„๊ถŒ์žฅ:** +``` +โŒ ์‚ญ์ œ ํ…Œ์ŠคํŠธ ๋จผ์ € โ†’ ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ์‹คํŒจ +``` + +--- + +## ๐Ÿ”„ ๋ฐ์ดํ„ฐ ๋ฆฌ์…‹ + +### Collection Runner๋กœ ์ดˆ๊ธฐํ™” + +**์ „์ฒด ๋ฆฌ์…‹:** +``` +1. "0. ์ดˆ๊ธฐ ์„ค์ •" ํด๋” Run + โ†’ ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž/์ฑ„๋„ ์ƒ์„ฑ + +2. Environment ๋ณ€์ˆ˜ ์ž๋™ ์—…๋ฐ์ดํŠธ + โ†’ ์ƒˆ ID๋กœ ๊ต์ฒด๋จ +``` + +--- + +## ๐Ÿ’ป Postman ๋ณ€์ˆ˜ ํŒ + +### ๋™์  ๋ณ€์ˆ˜ ์‚ฌ์šฉ + +**ํ˜„์žฌ ์‹œ๊ฐ„ ์ž๋™ ์ƒ์„ฑ:** +```json +{ + "lastActiveAt": "{{$isoTimestamp}}" +} +``` + +**UUID ์ž๋™ ์ƒ์„ฑ:** +```json +{ + "id": "{{$guid}}" +} +``` + +**๋žœ๋ค ์ด๋ฉ”์ผ:** +```json +{ + "email": "user{{$randomInt}}@example.com" +} +``` + +--- + +## ๐ŸŽŠ v2์˜ ์žฅ์  + +### โœ… ๊ฐœ์„ ๋œ ํ…Œ์ŠคํŠธ ์•ˆ์ •์„ฑ + +| ํ•ญ๋ชฉ | v1 | v2 | +|------|----|----| +| ์‚ฌ์šฉ์ž ์‚ญ์ œ ํ›„ | โŒ ํ…Œ์ŠคํŠธ ์‹คํŒจ | โœ… ๊ณ„์† ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ | +| ์ฑ„๋„ ์‚ญ์ œ ํ›„ | โŒ ํ…Œ์ŠคํŠธ ์‹คํŒจ | โœ… ๊ณ„์† ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ | +| ์ดˆ๊ธฐ ์„ค์ • | โŒ ์ˆ˜๋™์œผ๋กœ ํ•˜๋‚˜์”ฉ | โœ… ํด๋” ํ•œ ๋ฒˆ ์‹คํ–‰ | +| ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž | โŒ 1๋ช…๋งŒ | โœ… 3๋ช… ๋™์‹œ ํ…Œ์ŠคํŠธ | +| ์—ฌ๋Ÿฌ ์ฑ„๋„ | โŒ 1๊ฐœ๋งŒ | โœ… 3๊ฐœ ๋™์‹œ ํ…Œ์ŠคํŠธ | + +--- + +## ๐Ÿ“š ์ถ”๊ฐ€ ๋ฌธ์„œ + +- **API_DOCUMENTATION.md**: ์ „์ฒด API ์ƒ์„ธ ๋ฌธ์„œ +- **TROUBLESHOOTING.md**: ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ +- **test_api_automation.py**: Python ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ + +--- + +**Happy Testing with v2! ๐ŸŽ‰** diff --git "a/discodeit/src/main/java/com/sprint/mission/discodeit/docs/README4(SB3\354\235\264\354\242\205\355\230\270).md" "b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/README4(SB3\354\235\264\354\242\205\355\230\270).md" new file mode 100644 index 00000000..09028a02 --- /dev/null +++ "b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/README4(SB3\354\235\264\354\242\205\355\230\270).md" @@ -0,0 +1,845 @@ +# Discodeit ํ”„๋กœ์ ํŠธ - ์ดˆ๋ณด์ž๋ฅผ ์œ„ํ•œ ์™„์ „ ๊ฐ€์ด๋“œ + +> Spring Boot 3 ๊ธฐ๋ฐ˜ Discord ํด๋ก  ํ”„๋กœ์ ํŠธ์˜ ๊ตฌ์กฐ์™€ ์ฝ”๋“œ๋ฅผ ์ดˆ๋ณด์ž ๊ด€์ ์—์„œ ์ƒ์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## ๋ชฉ์ฐจ + +1. [ํ”„๋กœ์ ํŠธ ๊ฐœ์š”](#1-ํ”„๋กœ์ ํŠธ-๊ฐœ์š”) +2. [ํŒจํ‚ค์ง€ ๊ตฌ์กฐ](#2-ํŒจํ‚ค์ง€-๊ตฌ์กฐ) +3. [๊ณ„์ธตํ˜• ์•„ํ‚คํ…์ฒ˜ ์ดํ•ดํ•˜๊ธฐ](#3-๊ณ„์ธตํ˜•-์•„ํ‚คํ…์ฒ˜-์ดํ•ดํ•˜๊ธฐ) +4. [Entity ๊ณ„์ธต - ๋ฐ์ดํ„ฐ์˜ ๋ผˆ๋Œ€](#4-entity-๊ณ„์ธต---๋ฐ์ดํ„ฐ์˜-๋ผˆ๋Œ€) +5. [DTO ๊ณ„์ธต - ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๊ฐ์ฒด](#5-dto-๊ณ„์ธต---๋ฐ์ดํ„ฐ-์ „๋‹ฌ-๊ฐ์ฒด) +6. [Repository ๊ณ„์ธต - ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ](#6-repository-๊ณ„์ธต---๋ฐ์ดํ„ฐ-์ €์žฅ์†Œ) +7. [Service ๊ณ„์ธต - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง](#7-service-๊ณ„์ธต---๋น„์ฆˆ๋‹ˆ์Šค-๋กœ์ง) +8. [์ „์ฒด ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์˜ˆ์‹œ](#8-์ „์ฒด-๋ฐ์ดํ„ฐ-ํ๋ฆ„-์˜ˆ์‹œ) +9. [์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ](#9-์ง์ ‘-๊ตฌํ˜„ํ•ด๋ณด๊ธฐ) +10. [์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜์™€ ํ•ด๊ฒฐ๋ฒ•](#10-์ž์ฃผ-ํ•˜๋Š”-์‹ค์ˆ˜์™€-ํ•ด๊ฒฐ๋ฒ•) + +--- + +## 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” + +### 1.1 ์ด ํ”„๋กœ์ ํŠธ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? + +**Discodeit**์€ Discord์™€ ์œ ์‚ฌํ•œ ์ฑ„ํŒ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐฑ์—”๋“œ์ž…๋‹ˆ๋‹ค. + +์ฃผ์š” ๊ธฐ๋Šฅ: +- **์‚ฌ์šฉ์ž ๊ด€๋ฆฌ**: ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ํ”„๋กœํ•„ ๊ด€๋ฆฌ +- **์ฑ„๋„ ๊ด€๋ฆฌ**: ๊ณต๊ฐœ/๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ +- **๋ฉ”์‹œ์ง€**: ์ฑ„๋„ ๋‚ด ๋ฉ”์‹œ์ง€ ์†ก์ˆ˜์‹  +- **ํŒŒ์ผ ์ฒจ๋ถ€**: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ๋ฉ”์‹œ์ง€ ์ฒจ๋ถ€ํŒŒ์ผ +- **์ƒํƒœ ๊ด€๋ฆฌ**: ์˜จ๋ผ์ธ/์˜คํ”„๋ผ์ธ ์ƒํƒœ, ๋ฉ”์‹œ์ง€ ์ฝ์Œ ์ƒํƒœ + +### 1.2 ์‚ฌ์šฉ ๊ธฐ์ˆ  + +| ๊ธฐ์ˆ  | ์šฉ๋„ | +|------|------| +| Java 17+ | ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด | +| Spring Boot 3 | ์›น ํ”„๋ ˆ์ž„์›Œํฌ | +| Lombok | ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ ์ž๋™ ์ƒ์„ฑ | +| UUID | ๊ณ ์œ  ์‹๋ณ„์ž ์ƒ์„ฑ | + +--- + +## 2. ํŒจํ‚ค์ง€ ๊ตฌ์กฐ + +``` +src/main/java/com/sprint/mission/discodeit/ +โ”‚ +โ”œโ”€โ”€ DiscodeitApplication.java # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘์  +โ”‚ +โ”œโ”€โ”€ entity/ # ๐Ÿ“ฆ ์—”ํ‹ฐํ‹ฐ (๋ฐ์ดํ„ฐ ๋ชจ๋ธ) +โ”‚ โ”œโ”€โ”€ BaseEntity.java # ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์˜ ๋ถ€๋ชจ ํด๋ž˜์Šค +โ”‚ โ”œโ”€โ”€ User.java # ์‚ฌ์šฉ์ž +โ”‚ โ”œโ”€โ”€ Channel.java # ์ฑ„๋„ +โ”‚ โ”œโ”€โ”€ Message.java # ๋ฉ”์‹œ์ง€ +โ”‚ โ”œโ”€โ”€ BinaryContent.java # ํŒŒ์ผ (์ด๋ฏธ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ) +โ”‚ โ”œโ”€โ”€ UserStatus.java # ์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ ์ƒํƒœ +โ”‚ โ”œโ”€โ”€ ReadStatus.java # ๋ฉ”์‹œ์ง€ ์ฝ์Œ ์ƒํƒœ +โ”‚ โ””โ”€โ”€ ChannelType.java # ์ฑ„๋„ ํƒ€์ž… (PUBLIC/PRIVATE) +โ”‚ +โ”œโ”€โ”€ dto/ # ๐Ÿ“จ ๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ์ฒด +โ”‚ โ”œโ”€โ”€ request/ # ์š”์ฒญ์šฉ DTO +โ”‚ โ”‚ โ”œโ”€โ”€ UserCreateRequest.java +โ”‚ โ”‚ โ”œโ”€โ”€ UserUpdateRequest.java +โ”‚ โ”‚ โ””โ”€โ”€ ... +โ”‚ โ””โ”€โ”€ response/ # ์‘๋‹ต์šฉ DTO +โ”‚ โ”œโ”€โ”€ UserResponse.java +โ”‚ โ””โ”€โ”€ ... +โ”‚ +โ”œโ”€โ”€ repository/ # ๐Ÿ’พ ์ €์žฅ์†Œ (๋ฐ์ดํ„ฐ ์ ‘๊ทผ) +โ”‚ โ”œโ”€โ”€ UserRepository.java # ์ธํ„ฐํŽ˜์ด์Šค +โ”‚ โ”œโ”€โ”€ file/ # ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์ฒด +โ”‚ โ”‚ โ””โ”€โ”€ FileUserRepository.java +โ”‚ โ””โ”€โ”€ jcf/ # ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์ฒด +โ”‚ โ””โ”€โ”€ JCFUserRepository.java +โ”‚ +โ””โ”€โ”€ service/ # โš™๏ธ ์„œ๋น„์Šค (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) + โ”œโ”€โ”€ UserService.java # ์ธํ„ฐํŽ˜์ด์Šค + โ””โ”€โ”€ basic/ # ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด + โ””โ”€โ”€ BasicUserService.java +``` + +### ์™œ ์ด๋ ‡๊ฒŒ ๋‚˜๋ˆŒ๊นŒ์š”? + +๊ฐ ํŒจํ‚ค์ง€๋Š” **ํ•˜๋‚˜์˜ ์—ญํ• **๋งŒ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค: +- **entity**: "๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€" ์ •์˜ +- **dto**: "์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„์ง€" ์ •์˜ +- **repository**: "๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ €์žฅ/์กฐํšŒํ• ์ง€" ์ •์˜ +- **service**: "์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ• ์ง€" ์ •์˜ + +์ด๋ ‡๊ฒŒ ๋‚˜๋ˆ„๋ฉด **์ˆ˜์ •์ด ์‰ฝ๊ณ **, **ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฝ๊ณ **, **์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค**. + +--- + +## 3. ๊ณ„์ธตํ˜• ์•„ํ‚คํ…์ฒ˜ ์ดํ•ดํ•˜๊ธฐ + +### 3.1 ๊ณ„์ธต ๊ตฌ์กฐ ๋‹ค์ด์–ด๊ทธ๋žจ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ํด๋ผ์ด์–ธํŠธ (Frontend) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ HTTP ์š”์ฒญ/์‘๋‹ต +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Controller ๊ณ„์ธต (REST API) โ”‚ +โ”‚ - HTTP ์š”์ฒญ์„ ๋ฐ›์•„์„œ Service์— ์ „๋‹ฌ โ”‚ +โ”‚ - Service ๊ฒฐ๊ณผ๋ฅผ HTTP ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ DTO ์ „๋‹ฌ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Service ๊ณ„์ธต โ”‚ +โ”‚ - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ โ”‚ +โ”‚ - ์—ฌ๋Ÿฌ Repository๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์ž‘์—… ์ˆ˜ํ–‰ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ Entity ์ „๋‹ฌ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Repository ๊ณ„์ธต โ”‚ +โ”‚ - ๋ฐ์ดํ„ฐ ์ €์žฅ/์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ โ”‚ +โ”‚ - ํŒŒ์ผ ๋˜๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Entity ๊ณ„์ธต โ”‚ +โ”‚ - ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ ์ •์˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 3.2 ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋ฐฉํ–ฅ + +``` +์š”์ฒญ ์‹œ: ํด๋ผ์ด์–ธํŠธ โ†’ Controller โ†’ Service โ†’ Repository โ†’ ์ €์žฅ์†Œ +์‘๋‹ต ์‹œ: ์ €์žฅ์†Œ โ†’ Repository โ†’ Service โ†’ Controller โ†’ ํด๋ผ์ด์–ธํŠธ +``` + +**ํ•ต์‹ฌ ๊ทœ์น™**: +- ์ƒ์œ„ ๊ณ„์ธต์€ ํ•˜์œ„ ๊ณ„์ธต์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Œ +- ํ•˜์œ„ ๊ณ„์ธต์€ ์ƒ์œ„ ๊ณ„์ธต์„ ๋ชฐ๋ผ์•ผ ํ•จ (์˜์กด์„ฑ ๋ฐฉํ–ฅ ํ†ต์ผ) + +--- + +## 4. Entity ๊ณ„์ธต - ๋ฐ์ดํ„ฐ์˜ ๋ผˆ๋Œ€ + +### 4.1 BaseEntity - ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์˜ ๋ถ€๋ชจ + +๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ณตํ†ต์œผ๋กœ ๊ฐ€์ง€๋Š” ์†์„ฑ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + +```java +@Getter +public class BaseEntity implements Serializable { + + protected UUID id; // ๊ณ ์œ  ์‹๋ณ„์ž + protected Instant createdAt; // ์ƒ์„ฑ ์‹œ๊ฐ„ + protected Instant updatedAt; // ์ˆ˜์ • ์‹œ๊ฐ„ + + public BaseEntity() { + this.id = UUID.randomUUID(); // ์ž๋™์œผ๋กœ ๊ณ ์œ  ID ์ƒ์„ฑ + Instant now = Instant.now(); + this.createdAt = now; + this.updatedAt = now; + } + + // ์ˆ˜์ • ์‹œ ํ˜ธ์ถœํ•˜์—ฌ ์ˆ˜์ • ์‹œ๊ฐ„ ๊ฐฑ์‹  + protected void updateTimeStamp() { + this.updatedAt = Instant.now(); + } +} +``` + +**์™œ BaseEntity๋ฅผ ๋งŒ๋“ค๊นŒ์š”?** +- ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋Š” `id`, `createdAt`, `updatedAt`์ด ํ•„์š”ํ•จ +- ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ  ์ผ๊ด€์„ฑ ์œ ์ง€ +- ์ƒ์†์„ ํ†ตํ•ด ์ž๋™์œผ๋กœ ๊ณตํ†ต ๊ธฐ๋Šฅ ํš๋“ + +### 4.2 User ์—”ํ‹ฐํ‹ฐ + +```java +@Getter +public class User extends BaseEntity { + + private String username; // ์‚ฌ์šฉ์ž๋ช… (๋กœ๊ทธ์ธ ID) + private String email; // ์ด๋ฉ”์ผ + private String password; // ๋น„๋ฐ€๋ฒˆํ˜ธ + private UUID profileId; // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (์„ ํƒ) + + // ์ƒ์„ฑ์ž: ์ƒˆ ์‚ฌ์šฉ์ž ๋งŒ๋“ค ๋•Œ + public User(String username, String email, String password, UUID profileId) { + super(); // BaseEntity ์ƒ์„ฑ์ž ํ˜ธ์ถœ โ†’ id, createdAt, updatedAt ์ž๋™ ์„ค์ • + this.username = username; + this.email = email; + this.password = password; + this.profileId = profileId; + } + + // ์ˆ˜์ • ๋ฉ”์„œ๋“œ: null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ + public void update(String username, String email, String password, UUID profileId) { + if (username != null) this.username = username; + if (email != null) this.email = email; + if (password != null) this.password = password; + if (profileId != null) this.profileId = profileId; + updateTimeStamp(); // ์ˆ˜์ • ์‹œ๊ฐ„ ๊ฐฑ์‹  + } +} +``` + +**ํฌ์ธํŠธ ํ•ด์„ค**: +1. `extends BaseEntity`: ๊ณตํ†ต ์†์„ฑ ์ƒ์† +2. `super()`: ๋ถ€๋ชจ ์ƒ์„ฑ์ž ํ˜ธ์ถœ๋กœ id, ์‹œ๊ฐ„ ์ž๋™ ์ƒ์„ฑ +3. `update()`: null ์ฒดํฌ๋กœ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ +4. `updateTimeStamp()`: ์ˆ˜์ • ์‹œ ์ž๋™์œผ๋กœ ์‹œ๊ฐ„ ๊ฐฑ์‹  + +### 4.3 ์ „์ฒด ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„๋„ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ BaseEntity โ”‚ โ† ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์˜ ๋ถ€๋ชจ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ–ณ + โ”‚ ์ƒ์† + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ–ผ โ–ผ โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ User โ”‚ โ”‚Channelโ”‚ โ”‚ Message โ”‚ โ”‚ReadStatusโ”‚ โ”‚UserStatus โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ + โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ BinaryContent โ”‚ โ”‚ โ”‚ +โ”‚ (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ) โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ + โ”‚ โ”‚ + ์‚ฌ์šฉ์ž-์ฑ„๋„ ์‚ฌ์šฉ์ž + ์ฝ์Œ ์ƒํƒœ ์˜จ๋ผ์ธ ์ƒํƒœ +``` + +### 4.4 ๊ฐ ์—”ํ‹ฐํ‹ฐ ์š”์•ฝ + +| ์—”ํ‹ฐํ‹ฐ | ์—ญํ•  | ์ฃผ์š” ํ•„๋“œ | +|--------|------|----------| +| **User** | ์‚ฌ์šฉ์ž ์ •๋ณด | username, email, password, profileId | +| **Channel** | ์ฑ„ํŒ…๋ฐฉ | type(PUBLIC/PRIVATE), name, description | +| **Message** | ๋ฉ”์‹œ์ง€ | content, channelId, authorId, attachmentIds | +| **BinaryContent** | ํŒŒ์ผ ์ €์žฅ | fileName, contentType, data(byte[]) | +| **UserStatus** | ์˜จ๋ผ์ธ ์ƒํƒœ | userId, lastActiveAt | +| **ReadStatus** | ์ฝ์Œ ์ƒํƒœ | userId, channelId, lastReadAt | + +--- + +## 5. DTO ๊ณ„์ธต - ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๊ฐ์ฒด + +### 5.1 DTO๋ž€? + +**DTO (Data Transfer Object)**: ๊ณ„์ธต ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด + +**์™œ Entity๋ฅผ ์ง์ ‘ ์•ˆ ์“ฐ๊ณ  DTO๋ฅผ ์“ธ๊นŒ์š”?** + +1. **๋ณด์•ˆ**: ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด ์ˆจ๊ธฐ๊ธฐ +2. **์œ ์—ฐ์„ฑ**: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•„์š”ํ•œ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต +3. **๋ถ„๋ฆฌ**: Entity ๋ณ€๊ฒฝ์ด API์— ์˜ํ–ฅ ์ฃผ์ง€ ์•Š์Œ + +### 5.2 Request DTO - ์š”์ฒญ์šฉ + +ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +```java +// ํšŒ์›๊ฐ€์ž… ์š”์ฒญ DTO +public record UserCreateRequest( + String username, // ์‚ฌ์šฉ์ž๋ช… + String email, // ์ด๋ฉ”์ผ + String password // ๋น„๋ฐ€๋ฒˆํ˜ธ +) {} +``` + +**record๋ž€?** +- Java 16+์—์„œ ์ถ”๊ฐ€๋œ ๋ถˆ๋ณ€ ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค +- ์ž๋™์œผ๋กœ ์ƒ์„ฑ์ž, getter, equals, hashCode, toString ์ƒ์„ฑ +- DTO์— ์ ํ•ฉ! + +**์ „์ฒด Request DTO ๋ชฉ๋ก**: + +| DTO | ์šฉ๋„ | ํ•„๋“œ | +|-----|------|------| +| `UserCreateRequest` | ํšŒ์›๊ฐ€์ž… | username, email, password | +| `UserUpdateRequest` | ์ •๋ณด ์ˆ˜์ • | username, email, password (๋ชจ๋‘ ์„ ํƒ) | +| `LoginRequest` | ๋กœ๊ทธ์ธ | username, password | +| `PublicChannelCreateRequest` | ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ | name, description | +| `PrivateChannelCreateRequest` | ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ | memberIds (List) | +| `ChannelUpdateRequest` | ์ฑ„๋„ ์ˆ˜์ • | name, description | +| `MessageCreateRequest` | ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ | content, channelId, authorId | +| `MessageUpdateRequest` | ๋ฉ”์‹œ์ง€ ์ˆ˜์ • | content | +| `BinaryContentCreateRequest` | ํŒŒ์ผ ์—…๋กœ๋“œ | fileName, contentType, data | + +### 5.3 Response DTO - ์‘๋‹ต์šฉ + +์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +```java +// ์‚ฌ์šฉ์ž ์‘๋‹ต DTO +public record UserResponse( + UUID id, // ์‚ฌ์šฉ์ž ID + String username, // ์‚ฌ์šฉ์ž๋ช… + String email, // ์ด๋ฉ”์ผ + UUID profileId, // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID + boolean isOnline, // ์˜จ๋ผ์ธ ์—ฌ๋ถ€ โ˜… ์ถ”๊ฐ€ ์ •๋ณด! + Instant createdAt, // ์ƒ์„ฑ ์‹œ๊ฐ„ + Instant updatedAt // ์ˆ˜์ • ์‹œ๊ฐ„ +) {} +``` + +**Entity vs Response DTO ๋น„๊ต**: + +| ํ•ญ๋ชฉ | User Entity | UserResponse DTO | +|------|-------------|------------------| +| password | โœ… ์žˆ์Œ | โŒ ์—†์Œ (๋ณด์•ˆ) | +| isOnline | โŒ ์—†์Œ | โœ… ์žˆ์Œ (์ถ”๊ฐ€ ์ •๋ณด) | + +--- + +## 6. Repository ๊ณ„์ธต - ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ + +### 6.1 Repository ํŒจํ„ด์ด๋ž€? + +๋ฐ์ดํ„ฐ ์ €์žฅ/์กฐํšŒ ๋กœ์ง์„ **์ถ”์ƒํ™”**ํ•˜์—ฌ Service๊ฐ€ ์ €์žฅ ๋ฐฉ์‹์„ ๋ชฐ๋ผ๋„ ๋˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. + +``` +Service: "User ์ €์žฅํ•ด์ค˜" + โ†“ +Repository ์ธํ„ฐํŽ˜์ด์Šค: save(User user) + โ†“ +๊ตฌํ˜„์ฒด ์„ ํƒ: + โ”œโ”€ FileRepository โ†’ ํŒŒ์ผ์— ์ €์žฅ + โ””โ”€ JCFRepository โ†’ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ +``` + +### 6.2 Repository ์ธํ„ฐํŽ˜์ด์Šค + +```java +public interface UserRepository { + + // ๊ธฐ๋ณธ CRUD + User save(User user); // ์ €์žฅ/์ˆ˜์ • + Optional findById(UUID id); // ID๋กœ ์กฐํšŒ + List findAll(); // ์ „์ฒด ์กฐํšŒ + void deleteById(UUID id); // ์‚ญ์ œ + + // ์กฐํšŒ ๋ฉ”์„œ๋“œ + Optional findByUsername(String username); // ๋กœ๊ทธ์ธ์šฉ + Optional findByEmail(String email); // ์ด๋ฉ”์ผ ์กฐํšŒ + + // ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ + boolean existsById(UUID id); + boolean existsByUsername(String username); // ์ค‘๋ณต ์ฒดํฌ์šฉ + boolean existsByEmail(String email); // ์ค‘๋ณต ์ฒดํฌ์šฉ +} +``` + +**Optional์ด๋ž€?** +- ๊ฐ’์ด ์žˆ์„ ์ˆ˜๋„, ์—†์„ ์ˆ˜๋„ ์žˆ๋Š” ์ปจํ…Œ์ด๋„ˆ +- null ๋Œ€์‹  ์‚ฌ์šฉํ•˜์—ฌ NullPointerException ๋ฐฉ์ง€ + +```java +// Optional ์‚ฌ์šฉ ์˜ˆ์‹œ +Optional userOpt = userRepository.findById(id); + +// ๋ฐฉ๋ฒ• 1: ์žˆ์œผ๋ฉด ๊ฐ€์ ธ์˜ค๊ณ , ์—†์œผ๋ฉด ์˜ˆ์™ธ +User user = userOpt.orElseThrow(() -> + new NoSuchElementException("User not found")); + +// ๋ฐฉ๋ฒ• 2: ์žˆ์œผ๋ฉด ๊ฐ€์ ธ์˜ค๊ณ , ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ +User user = userOpt.orElse(defaultUser); + +// ๋ฐฉ๋ฒ• 3: ์žˆ์„ ๋•Œ๋งŒ ์ฒ˜๋ฆฌ +userOpt.ifPresent(user -> System.out.println(user.getUsername())); +``` + +### 6.3 ๊ตฌํ˜„์ฒด ์„ ํƒ ๋ฐฉ์‹ + +**application.properties์—์„œ ์„ค์ •**: + +```properties +# ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ (๊ธฐ๋ณธ๊ฐ’, ํ…Œ์ŠคํŠธ/๊ฐœ๋ฐœ์šฉ) +discodeit.repository.type=jcf + +# ํŒŒ์ผ ์ €์žฅ (๋ฐ์ดํ„ฐ ์˜๊ตฌ ๋ณด์กด) +discodeit.repository.type=file +discodeit.repository.file-directory=./data +``` + +**์กฐ๊ฑด๋ถ€ ๋นˆ ๋“ฑ๋ก**: + +```java +// ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์ฒด +@Repository +@ConditionalOnProperty(name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true) // ์„ค์ • ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ +public class JCFUserRepository implements UserRepository { ... } + +// ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์ฒด +@Repository +@ConditionalOnProperty(name = "discodeit.repository.type", + havingValue = "file") +public class FileUserRepository implements UserRepository { ... } +``` + +### 6.4 ์ „์ฒด Repository ๋ชฉ๋ก + +| Repository | ๋‹ด๋‹น Entity | ํŠน์ˆ˜ ๋ฉ”์„œ๋“œ | +|------------|-------------|-------------| +| `UserRepository` | User | findByUsername, findByEmail | +| `ChannelRepository` | Channel | - | +| `MessageRepository` | Message | findAllByChannelId, deleteAllByChannelId | +| `BinaryContentRepository` | BinaryContent | findAllByIdIn | +| `UserStatusRepository` | UserStatus | findByUserId, deleteByUserId | +| `ReadStatusRepository` | ReadStatus | findByUserIdAndChannelId | + +--- + +## 7. Service ๊ณ„์ธต - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง + +### 7.1 Service์˜ ์—ญํ•  + +- **๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ์ ์šฉ**: ์ค‘๋ณต ์ฒดํฌ, ์œ ํšจ์„ฑ ๊ฒ€์ฆ +- **์—ฌ๋Ÿฌ Repository ์กฐํ•ฉ**: ๋ณต์žกํ•œ ์ž‘์—… ์ฒ˜๋ฆฌ +- **DTO ๋ณ€ํ™˜**: Entity โ†” DTO ๋ณ€ํ™˜ + +### 7.2 BasicUserService ์ƒ์„ธ ๋ถ„์„ + +```java +@Service +@RequiredArgsConstructor // final ํ•„๋“œ ์ƒ์„ฑ์ž ์ž๋™ ์ƒ์„ฑ +public class BasicUserService implements UserService { + + // ์˜์กด์„ฑ ์ฃผ์ž… (์ƒ์„ฑ์ž ์ฃผ์ž…) + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + private final UserStatusRepository userStatusRepository; +``` + +**@RequiredArgsConstructor๋ž€?** +```java +// Lombok์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ: +public BasicUserService( + UserRepository userRepository, + BinaryContentRepository binaryContentRepository, + UserStatusRepository userStatusRepository +) { + this.userRepository = userRepository; + this.binaryContentRepository = binaryContentRepository; + this.userStatusRepository = userStatusRepository; +} +``` + +### 7.3 ํšŒ์›๊ฐ€์ž… ๋กœ์ง ๋ถ„์„ + +```java +@Override +public UserResponse create(UserCreateRequest request, + BinaryContentCreateRequest profileRequest) { + + // ========== 1๋‹จ๊ณ„: ์œ ํšจ์„ฑ ๊ฒ€์ฆ ========== + // ์‚ฌ์šฉ์ž๋ช… ์ค‘๋ณต ์ฒดํฌ + if (userRepository.existsByUsername(request.username())) { + throw new IllegalArgumentException( + "์ด ์‚ฌ์šฉ์ž ์ด๋ฆ„์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: " + request.username()); + } + + // ์ด๋ฉ”์ผ ์ค‘๋ณต ์ฒดํฌ + if (userRepository.existsByEmail(request.email())) { + throw new IllegalArgumentException( + "์ด ์ด๋ฉ”์ผ์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: " + request.email()); + } + + // ========== 2๋‹จ๊ณ„: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ (์„ ํƒ) ========== + UUID profileId = null; + if (profileRequest != null) { + BinaryContent profile = new BinaryContent( + profileRequest.fileName(), + profileRequest.contentType(), + profileRequest.data() + ); + profileId = binaryContentRepository.save(profile).getId(); + } + + // ========== 3๋‹จ๊ณ„: User ์ƒ์„ฑ ๋ฐ ์ €์žฅ ========== + User user = new User( + request.username(), + request.email(), + request.password(), + profileId + ); + User savedUser = userRepository.save(user); + + // ========== 4๋‹จ๊ณ„: UserStatus ์ž๋™ ์ƒ์„ฑ ========== + UserStatus userStatus = new UserStatus(savedUser.getId(), Instant.now()); + userStatusRepository.save(userStatus); + + // ========== 5๋‹จ๊ณ„: ์‘๋‹ต ๋ฐ˜ํ™˜ ========== + return toUserResponse(savedUser, true); // ๋ฐฉ๊ธˆ ๊ฐ€์ž… = ์˜จ๋ผ์ธ +} +``` + +**ํ๋ฆ„ ์š”์•ฝ**: +``` +์š”์ฒญ โ†’ ์ค‘๋ณต๊ฒ€์ฆ โ†’ ํ”„๋กœํ•„์ €์žฅ โ†’ User์ €์žฅ โ†’ UserStatus์ €์žฅ โ†’ ์‘๋‹ต +``` + +### 7.4 ์ˆ˜์ • ๋กœ์ง ๋ถ„์„ + +```java +@Override +public UserResponse update(UUID id, UserUpdateRequest request, + BinaryContentCreateRequest profileRequest) { + + // 1. ์‚ฌ์šฉ์ž ์ฐพ๊ธฐ + User user = userRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("User not found: " + id)); + + // 2. ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๊ต์ฒด (์„ ํƒ) + UUID newProfileId = user.getProfileId(); + if (profileRequest != null) { + // ๊ธฐ์กด ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์‚ญ์ œ + if (user.getProfileId() != null) { + binaryContentRepository.deleteById(user.getProfileId()); + } + // ์ƒˆ ์ด๋ฏธ์ง€ ์ €์žฅ + BinaryContent profile = new BinaryContent( + profileRequest.fileName(), + profileRequest.contentType(), + profileRequest.data() + ); + newProfileId = binaryContentRepository.save(profile).getId(); + } + + // 3. ์‚ฌ์šฉ์ž ์ •๋ณด ์—…๋ฐ์ดํŠธ + user.update(request.username(), request.email(), + request.password(), newProfileId); + + // 4. ์ €์žฅ ๋ฐ ๋ฐ˜ํ™˜ + User savedUser = userRepository.save(user); + return toUserResponse(savedUser, getOnlineStatus(savedUser.getId())); +} +``` + +### 7.5 ์‚ญ์ œ ๋กœ์ง ๋ถ„์„ + +```java +@Override +public void delete(UUID id) { + // 1. ์‚ฌ์šฉ์ž ์ฐพ๊ธฐ + User user = userRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("User not found: " + id)); + + // 2. ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ๋จผ์ € ์‚ญ์ œ (์ˆœ์„œ ์ค‘์š”!) + if (user.getProfileId() != null) { + binaryContentRepository.deleteById(user.getProfileId()); + } + userStatusRepository.deleteByUserId(id); + + // 3. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž ์‚ญ์ œ + userRepository.deleteById(id); +} +``` + +**์‚ญ์ œ ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ **: +- ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, UserStatus โ†’ User ์ˆœ์œผ๋กœ ์‚ญ์ œ +- ๋ฐ˜๋Œ€๋กœ ํ•˜๋ฉด User ์‚ญ์ œ ํ›„ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ ์•„(orphan)๊ฐ€ ๋จ + +### 7.6 ์ „์ฒด Service ๋ชฉ๋ก + +| Service | ์ฃผ์š” ๊ธฐ๋Šฅ | +|---------|----------| +| `UserService` | ํšŒ์›๊ฐ€์ž…, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ | +| `ChannelService` | ์ฑ„๋„ ์ƒ์„ฑ(PUBLIC/PRIVATE), ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ | +| `MessageService` | ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ | +| `AuthService` | ๋กœ๊ทธ์ธ | +| `BinaryContentService` | ํŒŒ์ผ ์ €์žฅ, ์กฐํšŒ, ์‚ญ์ œ | +| `UserStatusService` | ์˜จ๋ผ์ธ ์ƒํƒœ ๊ด€๋ฆฌ | +| `ReadStatusService` | ์ฝ์Œ ์ƒํƒœ ๊ด€๋ฆฌ | + +--- + +## 8. ์ „์ฒด ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์˜ˆ์‹œ + +### 8.1 ํšŒ์›๊ฐ€์ž… ์‹œ๋‚˜๋ฆฌ์˜ค + +``` +1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ POST ์š”์ฒญ + { + "username": "john", + "email": "john@example.com", + "password": "1234" + } + +2. Controller๊ฐ€ ์š”์ฒญ์„ ๋ฐ›์•„ UserCreateRequest๋กœ ๋ณ€ํ™˜ + +3. BasicUserService.create() ํ˜ธ์ถœ + โ”œโ”€ userRepository.existsByUsername("john") โ†’ false + โ”œโ”€ userRepository.existsByEmail("john@example.com") โ†’ false + โ”œโ”€ new User("john", "john@example.com", "1234", null) + โ”œโ”€ userRepository.save(user) โ†’ UUID: abc-123 + โ””โ”€ userStatusRepository.save(new UserStatus(abc-123, now)) + +4. UserResponse ์ƒ์„ฑ ๋ฐ ๋ฐ˜ํ™˜ + { + "id": "abc-123", + "username": "john", + "email": "john@example.com", + "profileId": null, + "isOnline": true, + "createdAt": "2025-01-29T...", + "updatedAt": "2025-01-29T..." + } +``` + +### 8.2 ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์‹œ๋‚˜๋ฆฌ์˜ค + +``` +1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ POST ์š”์ฒญ + { + "memberIds": ["user-1", "user-2", "user-3"] + } + +2. BasicChannelService.createPrivate() ํ˜ธ์ถœ + โ”œโ”€ new Channel(PRIVATE, null, null) // ์ด๋ฆ„, ์„ค๋ช… ์—†์Œ + โ”œโ”€ channelRepository.save(channel) โ†’ UUID: channel-123 + โ”‚ + โ””โ”€ memberIds ๊ฐ๊ฐ์— ๋Œ€ํ•ด ReadStatus ์ƒ์„ฑ: + โ”œโ”€ readStatusRepository.save(new ReadStatus(user-1, channel-123, now)) + โ”œโ”€ readStatusRepository.save(new ReadStatus(user-2, channel-123, now)) + โ””โ”€ readStatusRepository.save(new ReadStatus(user-3, channel-123, now)) + +3. ChannelResponse ์ƒ์„ฑ ๋ฐ ๋ฐ˜ํ™˜ + { + "id": "channel-123", + "type": "PRIVATE", + "name": null, + "description": null, + "participantIds": ["user-1", "user-2", "user-3"], + ... + } +``` + +--- + +## 9. ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ + +### 9.1 ์ƒˆ๋กœ์šด Entity ๋งŒ๋“ค๊ธฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +```java +// 1. BaseEntity ์ƒ์† +public class MyEntity extends BaseEntity { + + // 2. ํ•„๋“œ ์ •์˜ (private) + private String name; + private String description; + + // 3. ์ƒ์„ฑ์ž (super() ํ˜ธ์ถœ ํ•„์ˆ˜!) + public MyEntity(String name, String description) { + super(); // โ† ์ด๊ฑฐ ๋น ๋œจ๋ฆฌ๋ฉด id, createdAt, updatedAt์ด null! + this.name = name; + this.description = description; + } + + // 4. update ๋ฉ”์„œ๋“œ (๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ) + public void update(String name, String description) { + if (name != null) this.name = name; + if (description != null) this.description = description; + updateTimeStamp(); // โ† ์ˆ˜์ • ์‹œ๊ฐ„ ๊ฐฑ์‹  + } +} +``` + +### 9.2 ์ƒˆ๋กœ์šด Repository ๋งŒ๋“ค๊ธฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +```java +// 1. ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ +public interface MyEntityRepository { + MyEntity save(MyEntity entity); + Optional findById(UUID id); + List findAll(); + void deleteById(UUID id); + boolean existsById(UUID id); + // ํ•„์š”ํ•œ ์กฐํšŒ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€... +} + +// 2. JCF ๊ตฌํ˜„์ฒด (๋ฉ”๋ชจ๋ฆฌ) +@Repository +@ConditionalOnProperty(name = "discodeit.repository.type", + havingValue = "jcf", matchIfMissing = true) +public class JCFMyEntityRepository implements MyEntityRepository { + + private final Map store = new HashMap<>(); + + @Override + public MyEntity save(MyEntity entity) { + store.put(entity.getId(), entity); + return entity; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(store.get(id)); + } + // ... ๋‚˜๋จธ์ง€ ๊ตฌํ˜„ +} +``` + +### 9.3 ์ƒˆ๋กœ์šด Service ๋งŒ๋“ค๊ธฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +```java +// 1. ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ +public interface MyEntityService { + MyEntityResponse create(MyEntityCreateRequest request); + MyEntityResponse find(UUID id); + List findAll(); + MyEntityResponse update(UUID id, MyEntityUpdateRequest request); + void delete(UUID id); +} + +// 2. ๊ตฌํ˜„์ฒด +@Service +@RequiredArgsConstructor +public class BasicMyEntityService implements MyEntityService { + + private final MyEntityRepository myEntityRepository; + + @Override + public MyEntityResponse create(MyEntityCreateRequest request) { + // 1. ์œ ํšจ์„ฑ ๊ฒ€์ฆ + // 2. Entity ์ƒ์„ฑ + // 3. ์ €์žฅ + // 4. Response ๋ณ€ํ™˜ ํ›„ ๋ฐ˜ํ™˜ + } + // ... ๋‚˜๋จธ์ง€ ๊ตฌํ˜„ +} +``` + +--- + +## 10. ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜์™€ ํ•ด๊ฒฐ๋ฒ• + +### 10.1 super() ํ˜ธ์ถœ ๋ˆ„๋ฝ + +```java +// โŒ ์ž˜๋ชป๋œ ์ฝ”๋“œ +public User(String username, String email, String password) { + // super() ์—†์Œ! + this.username = username; + this.email = email; + this.password = password; +} +// ๊ฒฐ๊ณผ: id, createdAt, updatedAt์ด ๋ชจ๋‘ null + +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ +public User(String username, String email, String password) { + super(); // BaseEntity ์ƒ์„ฑ์ž ํ˜ธ์ถœ + this.username = username; + this.email = email; + this.password = password; +} +``` + +### 10.2 Optional ์ž˜๋ชป ์‚ฌ์šฉ + +```java +// โŒ ์ž˜๋ชป๋œ ์ฝ”๋“œ +User user = userRepository.findById(id).get(); +// ๊ฒฐ๊ณผ: ์—†์œผ๋ฉด NoSuchElementException (๋ฉ”์‹œ์ง€ ์—†์Œ) + +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ +User user = userRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("User not found: " + id)); +// ๊ฒฐ๊ณผ: ๋ช…ํ™•ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ +``` + +### 10.3 ์‚ญ์ œ ์ˆœ์„œ ์‹ค์ˆ˜ + +```java +// โŒ ์ž˜๋ชป๋œ ์ˆœ์„œ +userRepository.deleteById(id); // User ๋จผ์ € ์‚ญ์ œ +userStatusRepository.deleteByUserId(id); // ์ด๋ฏธ User๊ฐ€ ์—†์–ด์„œ ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ + +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ˆœ์„œ +userStatusRepository.deleteByUserId(id); // ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ๋จผ์ € +userRepository.deleteById(id); // ์ฃผ ์—”ํ‹ฐํ‹ฐ ๋‚˜์ค‘์— +``` + +### 10.4 null ์ฒดํฌ ๋ˆ„๋ฝ + +```java +// โŒ ์ž˜๋ชป๋œ ์ฝ”๋“œ +binaryContentRepository.deleteById(user.getProfileId()); +// ๊ฒฐ๊ณผ: profileId๊ฐ€ null์ด๋ฉด ์—๋Ÿฌ + +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ +if (user.getProfileId() != null) { + binaryContentRepository.deleteById(user.getProfileId()); +} +``` + +### 10.5 updateTimeStamp() ํ˜ธ์ถœ ๋ˆ„๋ฝ + +```java +// โŒ ์ž˜๋ชป๋œ ์ฝ”๋“œ +public void update(String name) { + if (name != null) this.name = name; + // updateTimeStamp() ์—†์Œ! +} +// ๊ฒฐ๊ณผ: updatedAt์ด ๊ฐฑ์‹ ๋˜์ง€ ์•Š์Œ + +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ +public void update(String name) { + if (name != null) this.name = name; + updateTimeStamp(); // ์ˆ˜์ • ์‹œ๊ฐ„ ๊ฐฑ์‹  +} +``` + +--- + +## ๋งˆ๋ฌด๋ฆฌ + +์ด ๊ฐ€์ด๋“œ๋ฅผ ํ†ตํ•ด Discodeit ํ”„๋กœ์ ํŠธ์˜ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜์…จ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. + +**ํ•™์Šต ์ˆœ์„œ ์ถ”์ฒœ**: +1. Entity ์ฝ”๋“œ ์ฝ๊ธฐ โ†’ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ดํ•ด +2. DTO ์ฝ”๋“œ ์ฝ๊ธฐ โ†’ ์ž…์ถœ๋ ฅ ํ˜•์‹ ์ดํ•ด +3. Repository ์ธํ„ฐํŽ˜์ด์Šค ์ฝ๊ธฐ โ†’ ์ €์žฅ ๋ฐฉ์‹ ์ดํ•ด +4. Service ์ฝ”๋“œ ์ฝ๊ธฐ โ†’ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ดํ•ด +5. ์ง์ ‘ ์ƒˆ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•ด๋ณด๊ธฐ! + +**์งˆ๋ฌธ์ด ์žˆ๋‹ค๋ฉด ์ฝ”๋“œ์˜ ์ฃผ์„์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. ๋ชจ๋“  ์ฃผ์š” ํด๋ž˜์Šค์™€ ๋ฉ”์„œ๋“œ์— ์ƒ์„ธํ•œ ํ•œ๊ตญ์–ด ์ฃผ์„์ด ์žˆ์Šต๋‹ˆ๋‹ค.** + +--- + +*์ž‘์„ฑ์ผ: 2025-01-29* +*์ž‘์„ฑ์ž: ์ด์ข…ํ˜ธ (with Claude)* diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/docs/README_v3.md b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/README_v3.md new file mode 100644 index 00000000..fe6fb99d --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/docs/README_v3.md @@ -0,0 +1,345 @@ +# ๐Ÿ“ฎ Postman Collection v3 - ์™„๋ฒฝ ๊ฐ€์ด๋“œ + +## ๐ŸŽฏ v3์˜ ํ•ต์‹ฌ ๊ฐœ์„ ์‚ฌํ•ญ + +### โœ… ํ•ด๊ฒฐ๋œ ๋ชจ๋“  ๋ฌธ์ œ + +| ๋ฌธ์ œ | v2 | v3 | +|------|----|----| +| "No tests found" ์—๋Ÿฌ | โŒ ์ผ๋ถ€ API | โœ… ๋ชจ๋“  API์— Tests ์ถ”๊ฐ€ | +| user2 ๋กœ๊ทธ์ธ 404 ์—๋Ÿฌ | โŒ user2๊ฐ€ ์ˆ˜์ •๋จ | โœ… user2๋Š” ์ ˆ๋Œ€ ์ˆ˜์ • ์•ˆํ•จ | +| ์ˆ˜์ • ํ…Œ์ŠคํŠธ | โŒ user2 ์ˆ˜์ • | โœ… user4 ์ถ”๊ฐ€ํ•ด์„œ ์ˆ˜์ • | +| ์‚ญ์ œ ํ›„ ํ…Œ์ŠคํŠธ ์‹คํŒจ | โŒ ๊ฐ„ํ˜น ๋ฐœ์ƒ | โœ… ์™„์ „ ํ•ด๊ฒฐ | + +--- + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ (5๋‹จ๊ณ„) + +### 1๋‹จ๊ณ„: Import + +**Collection Import:** +``` +Postman > Import > Discodeit_API_Collection_v3.postman_collection.json +``` + +**Environment Import:** +``` +Postman > Environments > Import > Discodeit_Environment_v3.postman_environment.json +``` + +### 2๋‹จ๊ณ„: Environment ์„ ํƒ + +์šฐ์ธก ์ƒ๋‹จ์—์„œ **"Discodeit Local v3"** ์„ ํƒ โœ… + +### 3๋‹จ๊ณ„: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํ™•์ธ + +```bash +lsof -i :8080 +``` + +์‹คํ–‰๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด: +```bash +cd /Users/ijongho/IdeaProjects/9-sprint-mission/discodeit +./gradlew bootRun +``` + +### 4๋‹จ๊ณ„: ์ดˆ๊ธฐ ์„ค์ • ์‹คํ–‰ โญ ์ค‘์š”! + +**Collection Runner ์‚ฌ์šฉ (๊ฐ•๋ ฅ ์ถ”์ฒœ):** +1. "0. ์ดˆ๊ธฐ ์„ค์ • (๋จผ์ € ์‹คํ–‰)" ํด๋” ์šฐํด๋ฆญ +2. **Run folder** ํด๋ฆญ +3. **Run** ๋ฒ„ํŠผ ํด๋ฆญ +4. 6๊ฐœ API๊ฐ€ ์ž๋™ ์‹คํ–‰๋จ: + - โœ… ์‚ฌ์šฉ์ž 1 ๋“ฑ๋ก + - โœ… ์‚ฌ์šฉ์ž 2 ๋“ฑ๋ก + - โœ… ์‚ฌ์šฉ์ž 3 ๋“ฑ๋ก + - โœ… ์ฑ„๋„ 1 ์ƒ์„ฑ + - โœ… ์ฑ„๋„ 2 ์ƒ์„ฑ + - โœ… ์ฑ„๋„ 3 ์ƒ์„ฑ + +### 5๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ ์‹œ์ž‘! + +์ด์ œ ๋ชจ๋“  API๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! + +--- + +## ๐Ÿ“‹ Collection ๊ตฌ์กฐ v3 + +``` +๋””์Šค์ฝ”๋“œ์ž‡ API v3 +โ”‚ +โ”œโ”€โ”€ 0. ์ดˆ๊ธฐ ์„ค์ • (๋จผ์ € ์‹คํ–‰) โญ ๊ฐ€์žฅ ๋จผ์ €! +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 1 ๋“ฑ๋ก โœ… Tests ํฌํ•จ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 2 ๋“ฑ๋ก โœ… Tests ํฌํ•จ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž 3 ๋“ฑ๋ก โœ… Tests ํฌํ•จ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„ 1 ์ƒ์„ฑ (์ผ๋ฐ˜) โœ… Tests ํฌํ•จ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„ 2 ์ƒ์„ฑ (๊ณต์ง€) โœ… Tests ํฌํ•จ +โ”‚ โ””โ”€โ”€ ์ฑ„๋„ 3 ์ƒ์„ฑ (์งˆ๋ฌธ) โœ… Tests ํฌํ•จ +โ”‚ +โ”œโ”€โ”€ 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ โœ… Tests: ์ตœ์†Œ 3๋ช… ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž1 ์กฐํšŒ โœ… Tests: username ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž4 ๋“ฑ๋ก โœ… Tests: ID ์ €์žฅ (์ˆ˜์ • ํ…Œ์ŠคํŠธ์šฉ) +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž4 ์ˆ˜์ • โœ… Tests: ์ˆ˜์ • ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ์‚ฌ์šฉ์ž3 ์‚ญ์ œ โœ… Tests: 204 ํ™•์ธ +โ”‚ โ””โ”€โ”€ ์‚ฌ์šฉ์ž1 ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ โœ… Tests: 200 ํ™•์ธ +โ”‚ +โ”œโ”€โ”€ 2. ๊ถŒํ•œ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ user1 ๋กœ๊ทธ์ธ โœ… Tests: 200, username ํ™•์ธ +โ”‚ โ””โ”€โ”€ user2 ๋กœ๊ทธ์ธ โœ… Tests: 200, username ํ™•์ธ +โ”‚ +โ”œโ”€โ”€ 3. ์ฑ„๋„ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ user1์˜ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ โœ… Tests: ์ตœ์†Œ 3๊ฐœ ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1 ์กฐํšŒ โœ… Tests: name ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„2 ์ˆ˜์ • โœ… Tests: ์ˆ˜์ • ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„3 ์‚ญ์ œ โœ… Tests: 204 ํ™•์ธ +โ”‚ โ””โ”€โ”€ ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ โœ… Tests: ID ์ €์žฅ, PRIVATE ํ™•์ธ +โ”‚ +โ”œโ”€โ”€ 4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€1 ์ „์†ก (user1) โœ… Tests: ID ์ €์žฅ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1์— ๋ฉ”์‹œ์ง€2 ์ „์†ก (user2) โœ… Tests: ID ์ €์žฅ +โ”‚ โ”œโ”€โ”€ ์ฑ„๋„1 ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ โœ… Tests: ์ตœ์†Œ 2๊ฐœ ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ๋ฉ”์‹œ์ง€1 ์กฐํšŒ โœ… Tests: content ํ™•์ธ +โ”‚ โ”œโ”€โ”€ ๋ฉ”์‹œ์ง€1 ์ˆ˜์ • โœ… Tests: ์ˆ˜์ • ํ™•์ธ +โ”‚ โ””โ”€โ”€ ๋ฉ”์‹œ์ง€2 ์‚ญ์ œ โœ… Tests: 204 ํ™•์ธ +โ”‚ +โ”œโ”€โ”€ 5. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด +โ”‚ โ”œโ”€โ”€ ์ˆ˜์‹ ์ •๋ณด ์ƒ์„ฑ โœ… Tests: ID ์ €์žฅ +โ”‚ โ”œโ”€โ”€ ์ˆ˜์‹ ์ •๋ณด ์ˆ˜์ • โœ… Tests: 200 ํ™•์ธ +โ”‚ โ””โ”€โ”€ ์ˆ˜์‹ ์ •๋ณด ์กฐํšŒ โœ… Tests: 1๊ฐœ ์ด์ƒ ํ™•์ธ +โ”‚ +โ””โ”€โ”€ 6. ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ + โ”œโ”€โ”€ ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ โœ… Tests ํฌํ•จ + โ””โ”€โ”€ ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ โœ… Tests ํฌํ•จ +``` + +--- + +## ๐ŸŽฏ ํ•ต์‹ฌ ๊ฐœ์„ ์‚ฌํ•ญ + +### 1. ๋ชจ๋“  API์— Tests ์Šคํฌ๋ฆฝํŠธ + +**์ด์ „ (v2):** +```javascript +// Tests ์—†์Œ โ†’ "No tests found" ์—๋Ÿฌ +``` + +**v3:** +```javascript +pm.test("โœ… ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต", function() { + pm.response.to.have.status(200); + var jsonData = pm.response.json(); + pm.expect(jsonData.length).to.be.at.least(3); + console.log("โœ… ์‚ฌ์šฉ์ž ์ˆ˜:", jsonData.length); +}); +``` + +### 2. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ๊ทœ์น™ + +| ์‚ฌ์šฉ์ž | ์šฉ๋„ | ์ˆ˜์ • | ์‚ญ์ œ | ๋กœ๊ทธ์ธ ํ…Œ์ŠคํŠธ | +|--------|------|------|------|--------------| +| user1 | ์ผ๋ฐ˜ ์‚ฌ์šฉ | โŒ | โŒ | โœ… | +| user2 | ๋กœ๊ทธ์ธ ์ „์šฉ | โŒ | โŒ | โœ… | +| user3 | ์‚ญ์ œ ํ…Œ์ŠคํŠธ | โŒ | โœ… | โŒ | +| user4 | ์ˆ˜์ • ํ…Œ์ŠคํŠธ | โœ… | โŒ | โŒ | + +**ํ•ต์‹ฌ:** user2๋Š” **์ ˆ๋Œ€ ์ˆ˜์ •/์‚ญ์ œํ•˜์ง€ ์•Š์Œ!** + +### 3. Tests ์Šคํฌ๋ฆฝํŠธ ์˜ˆ์‹œ + +**200 OK ์‘๋‹ต:** +```javascript +pm.test("โœ… user1 ๋กœ๊ทธ์ธ ์„ฑ๊ณต", function() { + pm.response.to.have.status(200); + var jsonData = pm.response.json(); + pm.expect(jsonData.username).to.eql("user1"); + console.log("โœ… ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž:", jsonData.username); +}); +``` + +**201 Created ์‘๋‹ต:** +```javascript +pm.test("โœ… ์‚ฌ์šฉ์ž ๋“ฑ๋ก ์„ฑ๊ณต", function() { + pm.response.to.have.status(201); + var jsonData = pm.response.json(); + pm.environment.set("userId1", jsonData.id); + console.log("โœ… userId1 ์ €์žฅ:", jsonData.id); +}); +``` + +**204 No Content ์‘๋‹ต:** +```javascript +pm.test("โœ… ์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต", function() { + pm.response.to.have.status(204); + console.log("โœ… user3 ์‚ญ์ œ ์™„๋ฃŒ"); +}); +``` + +**๋ฐฐ์—ด ์‘๋‹ต:** +```javascript +pm.test("โœ… ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต", function() { + pm.response.to.have.status(200); + var jsonData = pm.response.json(); + pm.expect(jsonData).to.be.an('array'); + pm.expect(jsonData.length).to.be.at.least(3); + console.log("โœ… ์‚ฌ์šฉ์ž ์ˆ˜:", jsonData.length); +}); +``` + +--- + +## ๐Ÿ“Š ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค + +### ์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ „์ฒด ํ”Œ๋กœ์šฐ (์ถ”์ฒœ) + +``` +1. ์ดˆ๊ธฐ ์„ค์ • ์‹คํ–‰ (0๋ฒˆ ํด๋”) + โ†’ ์‚ฌ์šฉ์ž 3๋ช…, ์ฑ„๋„ 3๊ฐœ ์ƒ์„ฑ + โ†“ +2. user1 ๋กœ๊ทธ์ธ + โ†’ โœ… 200 OK + โ†“ +3. user2 ๋กœ๊ทธ์ธ + โ†’ โœ… 200 OK (user2๋Š” ์ ˆ๋Œ€ ์ˆ˜์ • ์•ˆ๋จ!) + โ†“ +4. ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ + โ†’ โœ… ์ตœ์†Œ 3๋ช… ํ™•์ธ + โ†“ +5. user4 ๋“ฑ๋ก ๋ฐ ์ˆ˜์ • + โ†’ โœ… user4๋งŒ ์ˆ˜์ •๋จ + โ†“ +6. user3 ์‚ญ์ œ + โ†’ โœ… ์‚ญ์ œ ํ›„์—๋„ user1, user2 ์ •์ƒ ์ž‘๋™ + โ†“ +7. ์ฑ„๋„/๋ฉ”์‹œ์ง€ ํ…Œ์ŠคํŠธ + โ†’ โœ… ๋ชจ๋“  API ์ •์ƒ ์ž‘๋™ +``` + +### ์‹œ๋‚˜๋ฆฌ์˜ค 2: Collection Runner ์ „์ฒด ์‹คํ–‰ + +**ํ•œ ๋ฒˆ์— ๋ชจ๋“  API ํ…Œ์ŠคํŠธ:** +``` +Collection ์šฐํด๋ฆญ โ†’ Run collection + โ†“ +์ˆœ์„œ ํ™•์ธ + โ†“ +Run ๋ฒ„ํŠผ ํด๋ฆญ + โ†“ +๊ฒฐ๊ณผ ํ™•์ธ: X๊ฐœ Passed, 0๊ฐœ Failed +``` + +--- + +## ๐Ÿ”ง Environment ๋ณ€์ˆ˜ v3 + +| ๋ณ€์ˆ˜ | ์„ค๋ช… | ์ž๋™ ์ €์žฅ | ์šฉ๋„ | +|------|------|----------|------| +| baseUrl | API URL | โŒ ์ˆ˜๋™ | ๋ชจ๋“  API | +| userId1 | ์‚ฌ์šฉ์ž1 ID | โœ… ์ž๋™ | ์ผ๋ฐ˜ ์‚ฌ์šฉ | +| userId2 | ์‚ฌ์šฉ์ž2 ID | โœ… ์ž๋™ | ๋กœ๊ทธ์ธ ํ…Œ์ŠคํŠธ | +| userId3 | ์‚ฌ์šฉ์ž3 ID | โœ… ์ž๋™ | ์‚ญ์ œ ํ…Œ์ŠคํŠธ | +| userId4 | ์‚ฌ์šฉ์ž4 ID | โœ… ์ž๋™ | ์ˆ˜์ • ํ…Œ์ŠคํŠธ | +| channelId1 | ์ฑ„๋„1 ID | โœ… ์ž๋™ | ์ผ๋ฐ˜ ์‚ฌ์šฉ | +| channelId2 | ์ฑ„๋„2 ID | โœ… ์ž๋™ | ์ˆ˜์ • ํ…Œ์ŠคํŠธ | +| channelId3 | ์ฑ„๋„3 ID | โœ… ์ž๋™ | ์‚ญ์ œ ํ…Œ์ŠคํŠธ | +| privateChannelId | ๋น„๊ณต๊ฐœ ์ฑ„๋„ ID | โœ… ์ž๋™ | ๋น„๊ณต๊ฐœ ์ฑ„๋„ | +| messageId1 | ๋ฉ”์‹œ์ง€1 ID | โœ… ์ž๋™ | ์ผ๋ฐ˜ ์‚ฌ์šฉ | +| messageId2 | ๋ฉ”์‹œ์ง€2 ID | โœ… ์ž๋™ | ์‚ญ์ œ ํ…Œ์ŠคํŠธ | +| readStatusId1 | ์ˆ˜์‹ ์ •๋ณด ID | โœ… ์ž๋™ | ์ˆ˜์‹  ์ •๋ณด | +| binaryContentId | ํŒŒ์ผ ID | โŒ ์ˆ˜๋™ | ํŒŒ์ผ ํ…Œ์ŠคํŠธ | + +--- + +## โš ๏ธ ์ค‘์š” ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +ํ…Œ์ŠคํŠธ ์ „์— ๋ฐ˜๋“œ์‹œ ํ™•์ธ: + +- [ ] ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ ์ค‘์ธ๊ฐ€? (`lsof -i :8080`) +- [ ] Environment๊ฐ€ "Discodeit Local v3"๋กœ ์„ ํƒ๋˜์—ˆ๋Š”๊ฐ€? +- [ ] "0. ์ดˆ๊ธฐ ์„ค์ •" ํด๋”๋ฅผ ์‹คํ–‰ํ–ˆ๋Š”๊ฐ€? +- [ ] Console ์ฐฝ์ด ์—ด๋ ค์žˆ๋Š”๊ฐ€? (Cmd/Ctrl + Alt + C) + +--- + +## ๐Ÿ’ก Tests ๊ฒฐ๊ณผ ํ™•์ธ + +### Postman์—์„œ ํ™•์ธ + +**๊ฐ ์š”์ฒญ ์‹คํ–‰ ํ›„:** +``` +Tests ํƒญ โ†’ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ™•์ธ + โœ… Test Results (1/1) โ† ์„ฑ๊ณต + โŒ Test Results (0/1) โ† ์‹คํŒจ +``` + +**Console์—์„œ ํ™•์ธ:** +``` +โœ… ์‚ฌ์šฉ์ž1 ID ์ €์žฅ: uuid-value +โœ… ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž: user1 +โœ… ์‚ฌ์šฉ์ž ์ˆ˜: 4 +``` + +### Collection Runner ๊ฒฐ๊ณผ + +**์‹คํ–‰ ํ›„ Summary:** +``` +โœ… Passed: 30 +โŒ Failed: 0 +Total Tests: 30 +``` + +--- + +## ๐Ÿ”„ ๋ฐ์ดํ„ฐ ๋ฆฌ์…‹ + +### ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ ํ›„ + +``` +1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์žฌ์‹œ์ž‘ + โ†’ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ (๋ฉ”๋ชจ๋ฆฌ DB) + โ†“ +2. "0. ์ดˆ๊ธฐ ์„ค์ •" ๋‹ค์‹œ ์‹คํ–‰ + โ†’ ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž/์ฑ„๋„ ์ƒ์„ฑ + โ†“ +3. Environment ๋ณ€์ˆ˜ ์ž๋™ ์—…๋ฐ์ดํŠธ + โ†’ ์ƒˆ ID๋กœ ๊ต์ฒด + โ†“ +4. ํ…Œ์ŠคํŠธ ๊ณ„์† ๊ฐ€๋Šฅ +``` + +--- + +## ๐ŸŽŠ v3์˜ ์žฅ์  ์ด์ •๋ฆฌ + +### โœ… ์™„๋ฒฝํ•œ ์•ˆ์ •์„ฑ + +``` +โœ… ๋ชจ๋“  API์— Tests ์Šคํฌ๋ฆฝํŠธ +โœ… "No tests found" ์—๋Ÿฌ ์™„์ „ ์ œ๊ฑฐ +โœ… user2 ๋กœ๊ทธ์ธ 404 ์—๋Ÿฌ ์™„์ „ ํ•ด๊ฒฐ +โœ… ์‚ญ์ œ ํ…Œ์ŠคํŠธ ํ›„์—๋„ ๊ณ„์† ์‚ฌ์šฉ ๊ฐ€๋Šฅ +โœ… ๋ช…ํ™•ํ•œ ์‚ฌ์šฉ์ž ์—ญํ•  ๋ถ„๋ฆฌ +โœ… ์ž์„ธํ•œ Console ๋กœ๊ทธ +โœ… Collection Runner ์™„๋ฒฝ ์ง€์› +``` + +--- + +## ๐Ÿ“š ๊ด€๋ จ ๋ฌธ์„œ + +- **API_DOCUMENTATION.md**: ์ „์ฒด API ์ƒ์„ธ ๋ฌธ์„œ +- **TROUBLESHOOTING.md**: ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ +- **test_api_automation.py**: Python ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ + +--- + +## ๐ŸŽ‰ ์ด์ œ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค! + +**v3 Collection์„ Importํ•˜๊ณ  ๋ฐ”๋กœ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”!** + +1. โœ… Import 2๊ฐœ ํŒŒ์ผ +2. โœ… Environment ์„ ํƒ +3. โœ… "0. ์ดˆ๊ธฐ ์„ค์ •" Run +4. โœ… ๋ชจ๋“  API ์™„๋ฒฝ ์ž‘๋™! + +**๋ฌธ์ œ๊ฐ€ ์ „ํ˜€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!** ๐Ÿš€ diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java index 7b22c7c1..90f5d336 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java @@ -1,7 +1,16 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ ์š”์ฒญ DTO + * ์ด๋ฏธ์ง€, PDF ๋“ฑ์˜ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param fileName ํŒŒ์ผ๋ช… + * @param contentType MIME ํƒ€์ž… (์˜ˆ: "image/jpeg", "application/pdf") + * @param data ์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ + */ public record BinaryContentCreateRequest( String fileName, String contentType, byte[] data -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ChannelUpdateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ChannelUpdateRequest.java index 32b3cc0f..47646e6c 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ChannelUpdateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ChannelUpdateRequest.java @@ -1,6 +1,14 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ์š”์ฒญ DTO + * ์ฑ„๋„์˜ ์ด๋ฆ„๊ณผ ์„ค๋ช…์„ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param name ๋ณ€๊ฒฝํ•  ์ฑ„๋„ ์ด๋ฆ„ + * @param description ๋ณ€๊ฒฝํ•  ์ฑ„๋„ ์„ค๋ช… + */ public record ChannelUpdateRequest( String name, String description -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java index 89a1a711..96839024 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java @@ -1,6 +1,14 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ๋กœ๊ทธ์ธ ์š”์ฒญ DTO + * ์‚ฌ์šฉ์ž ์ธ์ฆ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. + * + * @param username ์‚ฌ์šฉ์ž๋ช… + * @param password ๋น„๋ฐ€๋ฒˆํ˜ธ + */ public record LoginRequest( String username, String password -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java index 2a51970c..7abf58a7 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java @@ -2,8 +2,17 @@ import java.util.UUID; +/** + * ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ ์š”์ฒญ DTO + * ์ฑ„๋„์— ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param content ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ + * @param channelId ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ฑ„๋„ ID + * @param authorId ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ์ž ID + */ public record MessageCreateRequest( String content, UUID channelId, UUID authorId -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java index edf205ea..f83e6b09 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java @@ -1,5 +1,12 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ๋ฉ”์‹œ์ง€ ์ˆ˜์ • ์š”์ฒญ DTO + * ๊ธฐ์กด ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param content ๋ณ€๊ฒฝํ•  ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ + */ public record MessageUpdateRequest( String content -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java index a7ee23a3..1474fbb3 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java @@ -3,6 +3,13 @@ import java.util.List; import java.util.UUID; +/** + * ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์š”์ฒญ DTO + * PRIVATE ํƒ€์ž…์˜ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param memberIds ์ฑ„๋„์— ์ดˆ๋Œ€ํ•  ์‚ฌ์šฉ์ž ID ๋ชฉ๋ก + */ public record PrivateChannelCreateRequest( - List participantIds -) {} + List memberIds +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java index c2ef88ad..56bace8b 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java @@ -1,6 +1,14 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์š”์ฒญ DTO + * PUBLIC ํƒ€์ž…์˜ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param name ์ฑ„๋„ ์ด๋ฆ„ + * @param description ์ฑ„๋„ ์„ค๋ช… + */ public record PublicChannelCreateRequest( String name, String description -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java index fcfc28a1..2219c25e 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java @@ -3,8 +3,17 @@ import java.time.Instant; import java.util.UUID; +/** + * ์ฝ๊ธฐ ์ƒํƒœ ์ƒ์„ฑ ์š”์ฒญ DTO + * ์‚ฌ์šฉ์ž์˜ ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @param channelId ์ฑ„๋„ ID + * @param lastReadAt ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝ์€ ์‹œ๊ฐ„ + */ public record ReadStatusCreateRequest( UUID userId, UUID channelId, Instant lastReadAt -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java index ddb0cbc2..89443a15 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java @@ -2,6 +2,13 @@ import java.time.Instant; +/** + * ์ฝ๊ธฐ ์ƒํƒœ ์ˆ˜์ • ์š”์ฒญ DTO + * ์‚ฌ์šฉ์ž์˜ ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„์„ ๊ฐฑ์‹ ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param lastReadAt ๊ฐฑ์‹ ํ•  ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„ + */ public record ReadStatusUpdateRequest( Instant lastReadAt -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java index dfbe1ca7..4ed0f5d6 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java @@ -1,7 +1,16 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์š”์ฒญ DTO + * ํšŒ์›๊ฐ€์ž… ์‹œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param username ์‚ฌ์šฉ์ž๋ช… + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @param password ๋น„๋ฐ€๋ฒˆํ˜ธ + */ public record UserCreateRequest( String username, String email, String password -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java index 560edbb5..ed616951 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java @@ -3,7 +3,15 @@ import java.time.Instant; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ƒ์„ฑ ์š”์ฒญ DTO + * ์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @param lastAccessAt ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ + */ public record UserStatusCreateRequest( UUID userId, - Instant lastActiveAt -) {} + Instant lastAccessAt +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java index 10e67d80..68993d06 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java @@ -2,6 +2,13 @@ import java.time.Instant; +/** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ˆ˜์ • ์š”์ฒญ DTO + * ์‚ฌ์šฉ์ž์˜ ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„์„ ๊ฐฑ์‹ ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param lastAccessAt ๊ฐฑ์‹ ํ•  ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ + */ public record UserStatusUpdateRequest( - Instant lastActiveAt -) {} + Instant lastAccessAt +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java index f3c91e34..eaf13bc7 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java @@ -1,7 +1,16 @@ package com.sprint.mission.discodeit.dto.request; +/** + * ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ์š”์ฒญ DTO + * ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param username ๋ณ€๊ฒฝํ•  ์‚ฌ์šฉ์ž๋ช… + * @param email ๋ณ€๊ฒฝํ•  ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @param password ๋ณ€๊ฒฝํ•  ๋น„๋ฐ€๋ฒˆํ˜ธ + */ public record UserUpdateRequest( String username, String email, String password -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/ChannelResponse.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/ChannelResponse.java index 8219ad15..0bb317dd 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/ChannelResponse.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/ChannelResponse.java @@ -5,6 +5,19 @@ import java.util.List; import java.util.UUID; +/** + * ์ฑ„๋„ ์‘๋‹ต DTO + * ์ฑ„๋„ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฑ„๋„ ID + * @param type ์ฑ„๋„ ํƒ€์ž… (PUBLIC/PRIVATE) + * @param name ์ฑ„๋„ ์ด๋ฆ„ + * @param description ์ฑ„๋„ ์„ค๋ช… + * @param participantIds ์ฐธ์—ฌ์ž ID ๋ชฉ๋ก + * @param lastMessageAt ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€ ์‹œ๊ฐ„ + * @param createdAt ์ƒ์„ฑ ์‹œ๊ฐ„ + * @param updatedAt ์ˆ˜์ • ์‹œ๊ฐ„ + */ public record ChannelResponse( UUID id, ChannelType type, @@ -14,4 +27,5 @@ public record ChannelResponse( Instant lastMessageAt, Instant createdAt, Instant updatedAt -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/MessageResponse.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/MessageResponse.java index d3ac77c4..fdefbbf9 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/MessageResponse.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/MessageResponse.java @@ -4,6 +4,18 @@ import java.util.List; import java.util.UUID; +/** + * ๋ฉ”์‹œ์ง€ ์‘๋‹ต DTO + * ๋ฉ”์‹œ์ง€ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฉ”์‹œ์ง€ ID + * @param content ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ + * @param channelId ์ฑ„๋„ ID + * @param authorId ์ž‘์„ฑ์ž ID + * @param attachmentIds ์ฒจ๋ถ€ํŒŒ์ผ ID ๋ชฉ๋ก + * @param createdAt ์ƒ์„ฑ ์‹œ๊ฐ„ + * @param updatedAt ์ˆ˜์ • ์‹œ๊ฐ„ + */ public record MessageResponse( UUID id, String content, @@ -12,4 +24,5 @@ public record MessageResponse( List attachmentIds, Instant createdAt, Instant updatedAt -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserDto.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserDto.java new file mode 100644 index 00000000..3ed607ac --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserDto.java @@ -0,0 +1,27 @@ +package com.sprint.mission.discodeit.dto.response; + +import java.time.Instant; +import java.util.UUID; + +/** + * ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์กฐํšŒ์šฉ DTO + * ์‚ฌ์šฉ์ž ์ •๋ณด์™€ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ID + * @param createdAt ์ƒ์„ฑ ์‹œ๊ฐ„ + * @param updatedAt ์ˆ˜์ • ์‹œ๊ฐ„ + * @param username ์‚ฌ์šฉ์ž๋ช… + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @param profileId ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID + * @param online ์˜จ๋ผ์ธ ์—ฌ๋ถ€ (Boolean ํƒ€์ž…) + */ +public record UserDto( + UUID id, + Instant createdAt, + Instant updatedAt, + String username, + String email, + UUID profileId, + Boolean online +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserResponse.java b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserResponse.java index 92eb1135..49ffb664 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserResponse.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/dto/response/UserResponse.java @@ -3,12 +3,25 @@ import java.time.Instant; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์‘๋‹ต DTO + * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ID + * @param username ์‚ฌ์šฉ์ž๋ช… + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @param profileId ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID + * @param isOnline ์˜จ๋ผ์ธ ์—ฌ๋ถ€ + * @param createdAt ์ƒ์„ฑ ์‹œ๊ฐ„ + * @param updatedAt ์ˆ˜์ • ์‹œ๊ฐ„ + */ public record UserResponse( UUID id, String username, String email, UUID profileId, - boolean online, + boolean isOnline, Instant createdAt, Instant updatedAt -) {} +) { +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java index da33545e..3e2c4396 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java @@ -6,14 +6,29 @@ import java.time.Instant; import java.util.UUID; +/** + * ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ์˜ ๊ณตํ†ต ์†์„ฑ์„ ์ •์˜ํ•˜๋Š” ๊ธฐ๋ณธ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ID, ์ƒ์„ฑ์ผ์‹œ, ์ˆ˜์ •์ผ์‹œ๋ฅผ ํฌํ•จํ•˜๋ฉฐ ์ง๋ ฌํ™”๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. + */ @Getter public class BaseEntity implements Serializable { + /** ์ง๋ ฌํ™” ๋ฒ„์ „ UID */ @Serial private static final long serialVersionUID = 1L; + + /** ์—”ํ‹ฐํ‹ฐ ๊ณ ์œ  ์‹๋ณ„์ž (UUID) */ protected UUID id; + + /** ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ ์ผ์‹œ */ protected Instant createdAt; + + /** ์—”ํ‹ฐํ‹ฐ ๋งˆ์ง€๋ง‰ ์ˆ˜์ • ์ผ์‹œ */ protected Instant updatedAt; + /** + * ๊ธฐ๋ณธ ์ƒ์„ฑ์ž + * UUID๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ƒ์„ฑ์ผ์‹œ์™€ ์ˆ˜์ •์ผ์‹œ๋ฅผ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. + */ public BaseEntity() { this.id = UUID.randomUUID(); Instant now = Instant.now(); @@ -21,7 +36,21 @@ public BaseEntity() { this.updatedAt = now; } + /** + * ๊ณ ์ • UUID๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ƒ์„ฑ์ž (ํ…Œ์ŠคํŠธ ๋ฐ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ์šฉ) + * + * @param id ๊ณ ์ • UUID + */ + protected BaseEntity(UUID id) { + this.id = id; + Instant now = Instant.now(); + this.createdAt = now; + this.updatedAt = now; + } + /** + * ์—”ํ‹ฐํ‹ฐ ์ˆ˜์ • ์‹œ ์ˆ˜์ •์ผ์‹œ๋ฅผ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + */ protected void updateTimeStamp() { this.updatedAt = Instant.now(); } diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java index 243c5a4f..e5bc521b 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java @@ -1,23 +1,45 @@ package com.sprint.mission.discodeit.entity; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import java.io.Serial; import java.io.Serializable; import java.time.Instant; import java.util.UUID; +/** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ์ด๋ฏธ์ง€, PDF ๋“ฑ์˜ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ฉฐ, ๋ถˆ๋ณ€(immutable) ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. + */ @Getter public class BinaryContent implements Serializable { + /** ์ง๋ ฌํ™” ๋ฒ„์ „ UID */ @Serial private static final long serialVersionUID = 1L; + /** ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๊ณ ์œ  ์‹๋ณ„์ž */ private final UUID id; + + /** ์ƒ์„ฑ ์ผ์‹œ */ private final Instant createdAt; - private final String fileName; // ํŒŒ์ผ๋ช… - private final String contentType; // MIME ํƒ€์ž… (์˜ˆ: image/png, application/pdf) - private final byte[] data; // ์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ + /** ํŒŒ์ผ๋ช… */ + @JsonProperty("filename") + private final String fileName; + + /** MIME ํƒ€์ž… (์˜ˆ: image/png, application/pdf) */ + private final String contentType; + + /** ์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ */ + private final byte[] data; + /** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ์ž + * + * @param fileName ํŒŒ์ผ๋ช… + * @param contentType MIME ํƒ€์ž… + * @param data ์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ + */ public BinaryContent(String fileName, String contentType, byte[] data) { this.id = UUID.randomUUID(); this.createdAt = Instant.now(); @@ -25,4 +47,20 @@ public BinaryContent(String fileName, String contentType, byte[] data) { this.contentType = contentType; this.data = data; } + + /** + * ๊ณ ์ • UUID๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ์ž (ํ…Œ์ŠคํŠธ ๋ฐ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ์šฉ) + * + * @param id ๊ณ ์ • UUID + * @param fileName ํŒŒ์ผ๋ช… + * @param contentType MIME ํƒ€์ž… + * @param data ์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ + */ + public BinaryContent(UUID id, String fileName, String contentType, byte[] data) { + this.id = id; + this.createdAt = Instant.now(); + this.fileName = fileName; + this.contentType = contentType; + this.data = data; + } } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Channel.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Channel.java index db476f11..fb35ad26 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Channel.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Channel.java @@ -1,12 +1,28 @@ package com.sprint.mission.discodeit.entity; import lombok.Getter; +/** + * ์ฑ„๋„ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ๋ฉ”์‹œ์ง€๊ฐ€ ๊ตํ™˜๋˜๋Š” ์ฑ„๋„์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ PUBLIC ๋˜๋Š” PRIVATE ํƒ€์ž…์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. + */ @Getter public class Channel extends BaseEntity { + /** ์ฑ„๋„ ํƒ€์ž… (PUBLIC/PRIVATE) */ private final ChannelType type; + + /** ์ฑ„๋„ ์ด๋ฆ„ */ private String name; + + /** ์ฑ„๋„ ์„ค๋ช… */ private String description; + /** + * ์ฑ„๋„ ์ƒ์„ฑ์ž + * + * @param type ์ฑ„๋„ ํƒ€์ž… (PUBLIC ๋˜๋Š” PRIVATE) + * @param name ์ฑ„๋„ ์ด๋ฆ„ + * @param description ์ฑ„๋„ ์„ค๋ช… + */ public Channel(ChannelType type, String name, String description) { super(); this.type = type; @@ -14,6 +30,13 @@ public Channel(ChannelType type, String name, String description) { this.description = description; } + /** + * ์ฑ„๋„ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ๋˜๋ฉฐ, ์ˆ˜์ •์ผ์‹œ๊ฐ€ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค. + * + * @param name ์ƒˆ๋กœ์šด ์ฑ„๋„ ์ด๋ฆ„ (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + * @param description ์ƒˆ๋กœ์šด ์ฑ„๋„ ์„ค๋ช… (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + */ public void update(String name, String description) { if (name != null) this.name = name; if (description != null) this.description = description; diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java index 738304e1..89d712bd 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java @@ -1,5 +1,14 @@ package com.sprint.mission.discodeit.entity; +/** + * ์ฑ„๋„ ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” ์—ด๊ฑฐํ˜• + * PUBLIC: ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ณต๊ฐœ ์ฑ„๋„ + * PRIVATE: ์ดˆ๋Œ€๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋น„๊ณต๊ฐœ ์ฑ„๋„ + */ public enum ChannelType { - PUBLIC, PRIVATE + /** ๊ณต๊ฐœ ์ฑ„๋„ */ + PUBLIC, + + /** ๋น„๊ณต๊ฐœ ์ฑ„๋„ */ + PRIVATE } diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Message.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Message.java index d7482c52..9bcb6eba 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Message.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/Message.java @@ -4,13 +4,32 @@ import java.util.List; import java.util.UUID; +/** + * ๋ฉ”์‹œ์ง€ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ์ฑ„๋„ ๋‚ด์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ ์ฒจ๋ถ€ํŒŒ์ผ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + */ @Getter public class Message extends BaseEntity { + /** ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ */ private String content; + + /** ๋ฉ”์‹œ์ง€๊ฐ€ ์†ํ•œ ์ฑ„๋„ ID */ private final UUID channelId; + + /** ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ์ž ID */ private final UUID authorId; - private final List attachmentIds; // ์ฒจ๋ถ€ํŒŒ์ผ ID ๋ชฉ๋ก (BinaryContent ์ฐธ์กฐ) + /** ์ฒจ๋ถ€ํŒŒ์ผ ID ๋ชฉ๋ก (BinaryContent ์ฐธ์กฐ) */ + private final List attachmentIds; + + /** + * ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ์ž + * + * @param content ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ + * @param channelId ๋ฉ”์‹œ์ง€๊ฐ€ ์†ํ•œ ์ฑ„๋„ ID + * @param authorId ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ์ž ID + * @param attachmentIds ์ฒจ๋ถ€ํŒŒ์ผ ID ๋ชฉ๋ก + */ public Message(String content, UUID channelId, UUID authorId, List attachmentIds) { super(); this.content = content; @@ -19,6 +38,12 @@ public Message(String content, UUID channelId, UUID authorId, List attachm this.attachmentIds = attachmentIds; } + /** + * ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ๋˜๋ฉฐ, ์ˆ˜์ •์ผ์‹œ๊ฐ€ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค. + * + * @param content ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + */ public void update(String content) { if (content != null) { this.content = content; diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java index 516a5a91..bd5005aa 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java @@ -5,15 +5,32 @@ import java.time.Instant; import java.util.UUID; +/** + * ์ฝ๊ธฐ ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝ์€ ์‹œ๊ฐ„์„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. + */ @Getter public class ReadStatus extends BaseEntity { + /** ์ง๋ ฌํ™” ๋ฒ„์ „ UID */ @Serial private static final long serialVersionUID = 1L; - private final UUID userId; // ์‚ฌ์šฉ์ž ID - private final UUID channelId; // ์ฑ„๋„ ID - private Instant lastReadAt; // ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์€ ์‹œ๊ฐ„ + /** ์‚ฌ์šฉ์ž ID */ + private final UUID userId; + /** ์ฑ„๋„ ID */ + private final UUID channelId; + + /** ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์€ ์‹œ๊ฐ„ */ + private Instant lastReadAt; + + /** + * ์ฝ๊ธฐ ์ƒํƒœ ์ƒ์„ฑ์ž + * + * @param userId ์‚ฌ์šฉ์ž ID + * @param channelId ์ฑ„๋„ ID + * @param lastReadAt ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝ์€ ์‹œ๊ฐ„ + */ public ReadStatus(UUID userId, UUID channelId, Instant lastReadAt) { super(); this.userId = userId; @@ -21,6 +38,12 @@ public ReadStatus(UUID userId, UUID channelId, Instant lastReadAt) { this.lastReadAt = lastReadAt; } + /** + * ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ๋˜๋ฉฐ, ์ˆ˜์ •์ผ์‹œ๊ฐ€ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค. + * + * @param lastReadAt ์ƒˆ๋กœ์šด ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„ (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + */ public void update(Instant lastReadAt) { if (lastReadAt != null) { this.lastReadAt = lastReadAt; diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/User.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/User.java index 7eac3c54..54cf2580 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/User.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/User.java @@ -3,13 +3,32 @@ import lombok.Getter; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ์‹œ์Šคํ…œ์˜ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ ์ธ์ฆ ๋ฐ ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + */ @Getter public class User extends BaseEntity { + /** ์‚ฌ์šฉ์ž๋ช… */ private String username; + + /** ์ด๋ฉ”์ผ ์ฃผ์†Œ */ private String email; + + /** ๋น„๋ฐ€๋ฒˆํ˜ธ */ private String password; - private UUID profileId; // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (BinaryContent ์ฐธ์กฐ, nullable) + /** ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (BinaryContent ์ฐธ์กฐ, null ๊ฐ€๋Šฅ) */ + private UUID profileId; + + /** + * ์‚ฌ์šฉ์ž ์ƒ์„ฑ์ž + * + * @param username ์‚ฌ์šฉ์ž๋ช… + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @param password ๋น„๋ฐ€๋ฒˆํ˜ธ + * @param profileId ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (null ๊ฐ€๋Šฅ) + */ public User(String username, String email, String password, UUID profileId) { super(); this.username = username; @@ -18,6 +37,32 @@ public User(String username, String email, String password, UUID profileId) { this.profileId = profileId; } + /** + * ๊ณ ์ • UUID๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž ์ƒ์„ฑ์ž (ํ…Œ์ŠคํŠธ ๋ฐ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ์šฉ) + * + * @param id ๊ณ ์ • UUID + * @param username ์‚ฌ์šฉ์ž๋ช… + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @param password ๋น„๋ฐ€๋ฒˆํ˜ธ + * @param profileId ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (null ๊ฐ€๋Šฅ) + */ + public User(UUID id, String username, String email, String password, UUID profileId) { + super(id); + this.username = username; + this.email = email; + this.password = password; + this.profileId = profileId; + } + + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ๋˜๋ฉฐ, ์ˆ˜์ •์ผ์‹œ๊ฐ€ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค. + * + * @param username ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ช… (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + * @param email ์ƒˆ๋กœ์šด ์ด๋ฉ”์ผ (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + * @param password ์ƒˆ๋กœ์šด ๋น„๋ฐ€๋ฒˆํ˜ธ (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + * @param profileId ์ƒˆ๋กœ์šด ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ID (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + */ public void update(String username, String email, String password, UUID profileId) { if (username != null) this.username = username; if (email != null) this.email = email; diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java index c6ea7138..b7a584d4 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java @@ -5,36 +5,58 @@ import java.time.Instant; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค + * ์‚ฌ์šฉ์ž์˜ ์ ‘์† ์ƒํƒœ ๋ฐ ๋งˆ์ง€๋ง‰ ํ™œ๋™ ์‹œ๊ฐ„์„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. + */ @Getter public class UserStatus extends BaseEntity { + /** ์ง๋ ฌํ™” ๋ฒ„์ „ UID */ @Serial private static final long serialVersionUID = 1L; - private final UUID userId; // ์‚ฌ์šฉ์ž ID - private Instant lastActiveAt; // ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ + /** ์‚ฌ์šฉ์ž ID */ + private final UUID userId; - public UserStatus(UUID userId, Instant lastActiveAt) { + /** ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ */ + private Instant lastAccessAt; + + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ƒ์„ฑ์ž + * + * @param userId ์‚ฌ์šฉ์ž ID + * @param lastAccessAt ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„ + */ + public UserStatus(UUID userId, Instant lastAccessAt) { super(); this.userId = userId; - this.lastActiveAt = lastActiveAt; + this.lastAccessAt = lastAccessAt; } - public void update(Instant lastActiveAt) { - if (lastActiveAt != null) { - this.lastActiveAt = lastActiveAt; + /** + * ๋งˆ์ง€๋ง‰ ํ™œ๋™ ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * null์ด ์•„๋‹Œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ๋˜๋ฉฐ, ์ˆ˜์ •์ผ์‹œ๊ฐ€ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค. + * + * @param lastAccessAt ์ƒˆ๋กœ์šด ๋งˆ์ง€๋ง‰ ํ™œ๋™ ์‹œ๊ฐ„ (null์ด๋ฉด ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ) + */ + public void update(Instant lastAccessAt) { + if (lastAccessAt != null) { + this.lastAccessAt = lastAccessAt; } updateTimeStamp(); } /** - * ํ˜„์žฌ ์˜จ๋ผ์ธ ์ƒํƒœ์ธ์ง€ ํ™•์ธ - * ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„์ด ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ๋ถ€ํ„ฐ 5๋ถ„ ์ด๋‚ด์ด๋ฉด ์˜จ๋ผ์ธ์œผ๋กœ ๊ฐ„์ฃผ + * ํ˜„์žฌ ์˜จ๋ผ์ธ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * ๋งˆ์ง€๋ง‰ ์ ‘์† ์‹œ๊ฐ„์ด ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ๋ถ€ํ„ฐ 5๋ถ„ ์ด๋‚ด์ด๋ฉด ์˜จ๋ผ์ธ์œผ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค. + * + * @return ์˜จ๋ผ์ธ ์ƒํƒœ ์—ฌ๋ถ€ (true: ์˜จ๋ผ์ธ, false: ์˜คํ”„๋ผ์ธ) */ public boolean isOnline() { - if (lastActiveAt == null) { + if (lastAccessAt == null) { return false; } Instant fiveMinutesAgo = Instant.now().minusSeconds(5 * 60); - return lastActiveAt.isAfter(fiveMinutesAgo); + return lastAccessAt.isAfter(fiveMinutesAgo); } } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java b/discodeit/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java new file mode 100644 index 00000000..16d828de --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java @@ -0,0 +1,18 @@ +package com.sprint.mission.discodeit.exception; + +import java.time.Instant; + +/** + * API ์—๋Ÿฌ ์‘๋‹ต DTO + * + * @param message ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ + * @param timestamp ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ๊ฐ + */ +public record ErrorResponse( + String message, + Instant timestamp +) { + public static ErrorResponse of(String message) { + return new ErrorResponse(message, Instant.now()); + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/discodeit/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..bdb1fb23 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java @@ -0,0 +1,51 @@ +package com.sprint.mission.discodeit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.NoSuchElementException; + +/** + * ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ + * + * ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ค‘์•™์—์„œ ์ฒ˜๋ฆฌํ•˜์—ฌ + * ์ผ๊ด€๋œ ์—๋Ÿฌ ์‘๋‹ต์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * NoSuchElementException ์ฒ˜๋ฆฌ + * - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์กฐํšŒ ์‹œ ๋ฐœ์ƒ + * - 404 NOT_FOUND ๋ฐ˜ํ™˜ + */ + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleNoSuchElement(NoSuchElementException e) { + ErrorResponse error = ErrorResponse.of(e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + + /** + * IllegalArgumentException ์ฒ˜๋ฆฌ + * - ์ž˜๋ชป๋œ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์œ„๋ฐ˜ ์‹œ ๋ฐœ์ƒ + * - 400 BAD_REQUEST ๋ฐ˜ํ™˜ + */ + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + ErrorResponse error = ErrorResponse.of(e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + /** + * ๊ธฐํƒ€ ๋ชจ๋“  ์˜ˆ์™ธ ์ฒ˜๋ฆฌ + * - ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์„œ๋ฒ„ ์—๋Ÿฌ + * - 500 INTERNAL_SERVER_ERROR ๋ฐ˜ํ™˜ + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + ErrorResponse error = ErrorResponse.of("์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } +} diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java index a7ea2812..75a90670 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java @@ -5,11 +5,54 @@ import java.util.Optional; import java.util.UUID; +/** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค + * ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ CRUD ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface BinaryContentRepository { + /** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param binaryContent ์ €์žฅํ•  ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  + * @return ์ €์žฅ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  + */ BinaryContent save(BinaryContent binaryContent); + + /** + * ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID + * @return ์กฐํšŒ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  (Optional) + */ Optional findById(UUID id); + + /** + * ๋ชจ๋“  ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก + */ List findAll(); + + /** + * ์—ฌ๋Ÿฌ ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param ids ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID ๋ชฉ๋ก + * @return ์กฐํšŒ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก + */ List findAllByIdIn(List ids); + + /** + * ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID + */ void deleteById(UUID id); + + /** + * ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsById(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java index db818c11..8ab301a1 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java @@ -6,10 +6,46 @@ import java.util.Optional; import java.util.UUID; +/** + * ์ฑ„๋„ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค + * ์ฑ„๋„ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ CRUD ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface ChannelRepository { + /** + * ์ฑ„๋„์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param channel ์ €์žฅํ•  ์ฑ„๋„ + * @return ์ €์žฅ๋œ ์ฑ„๋„ + */ Channel save(Channel channel); + + /** + * ID๋กœ ์ฑ„๋„์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฑ„๋„ ID + * @return ์กฐํšŒ๋œ ์ฑ„๋„ (Optional) + */ Optional findById(UUID id); + + /** + * ๋ชจ๋“  ์ฑ„๋„์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์ฑ„๋„ ๋ชฉ๋ก + */ List findAll(); + + /** + * ID๋กœ ์ฑ„๋„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์ฑ„๋„ ID + */ void deleteById(UUID id); + + /** + * ID๋กœ ์ฑ„๋„์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฑ„๋„ ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsById(UUID id); } diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java index a0fd4506..bddaa994 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java @@ -6,12 +6,61 @@ import java.util.Optional; import java.util.UUID; +/** + * ๋ฉ”์‹œ์ง€ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค + * ๋ฉ”์‹œ์ง€ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ CRUD ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface MessageRepository { + /** + * ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param message ์ €์žฅํ•  ๋ฉ”์‹œ์ง€ + * @return ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ + */ Message save(Message message); + + /** + * ID๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฉ”์‹œ์ง€ ID + * @return ์กฐํšŒ๋œ ๋ฉ”์‹œ์ง€ (Optional) + */ Optional findById(UUID id); + + /** + * ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก + */ List findAll(); - List findAllByChannelId(UUID channelId); // ์ถ”๊ฐ€: ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์กฐํšŒ + + /** + * ํŠน์ • ์ฑ„๋„์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param channelId ์ฑ„๋„ ID + * @return ํ•ด๋‹น ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก + */ + List findAllByChannelId(UUID channelId); + + /** + * ID๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ๋ฉ”์‹œ์ง€ ID + */ void deleteById(UUID id); - void deleteAllByChannelId(UUID channelId); // ์ถ”๊ฐ€: ์ฑ„๋„ ์‚ญ์ œ ์‹œ ๋ฉ”์‹œ์ง€ ์ผ๊ด„ ์‚ญ์ œ + + /** + * ํŠน์ • ์ฑ„๋„์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์ผ๊ด„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param channelId ์ฑ„๋„ ID + */ + void deleteAllByChannelId(UUID channelId); + + /** + * ID๋กœ ๋ฉ”์‹œ์ง€์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฉ”์‹œ์ง€ ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsById(UUID id); } diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java index e6834f0b..7743a168 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java @@ -5,14 +5,78 @@ import java.util.Optional; import java.util.UUID; +/** + * ์ฝ๊ธฐ ์ƒํƒœ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž์˜ ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ ์ƒํƒœ์— ๋Œ€ํ•œ CRUD ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface ReadStatusRepository { + /** + * ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param readStatus ์ €์žฅํ•  ์ฝ๊ธฐ ์ƒํƒœ + * @return ์ €์žฅ๋œ ์ฝ๊ธฐ ์ƒํƒœ + */ ReadStatus save(ReadStatus readStatus); + + /** + * ID๋กœ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฝ๊ธฐ ์ƒํƒœ ID + * @return ์กฐํšŒ๋œ ์ฝ๊ธฐ ์ƒํƒœ (Optional) + */ Optional findById(UUID id); + + /** + * ๋ชจ๋“  ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์ฝ๊ธฐ ์ƒํƒœ ๋ชฉ๋ก + */ List findAll(); + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ฝ๊ธฐ ์ƒํƒœ ๋ชฉ๋ก + */ List findAllByUserId(UUID userId); + + /** + * ํŠน์ • ์ฑ„๋„์˜ ๋ชจ๋“  ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param channelId ์ฑ„๋„ ID + * @return ํ•ด๋‹น ์ฑ„๋„์˜ ์ฝ๊ธฐ ์ƒํƒœ ๋ชฉ๋ก + */ List findAllByChannelId(UUID channelId); + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž์˜ ํŠน์ • ์ฑ„๋„์— ๋Œ€ํ•œ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @param channelId ์ฑ„๋„ ID + * @return ์กฐํšŒ๋œ ์ฝ๊ธฐ ์ƒํƒœ (Optional) + */ Optional findByUserIdAndChannelId(UUID userId, UUID channelId); + + /** + * ID๋กœ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์ฝ๊ธฐ ์ƒํƒœ ID + */ void deleteById(UUID id); + + /** + * ํŠน์ • ์ฑ„๋„์˜ ๋ชจ๋“  ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ผ๊ด„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param channelId ์ฑ„๋„ ID + */ void deleteAllByChannelId(UUID channelId); + + /** + * ID๋กœ ์ฝ๊ธฐ ์ƒํƒœ์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฝ๊ธฐ ์ƒํƒœ ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsById(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java index f27189aa..ed03d430 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java @@ -6,14 +6,78 @@ import java.util.Optional; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ CRUD ์ž‘์—… ๋ฐ ์ธ์ฆ ๊ด€๋ จ ์กฐํšŒ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface UserRepository { + /** + * ์‚ฌ์šฉ์ž๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param user ์ €์žฅํ•  ์‚ฌ์šฉ์ž + * @return ์ €์žฅ๋œ ์‚ฌ์šฉ์ž + */ User save(User user); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ID + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž (Optional) + */ Optional findById(UUID id); - Optional findByUsername(String username); // ์ถ”๊ฐ€: ๋กœ๊ทธ์ธ ์‹œ ์‚ฌ์šฉ - Optional findByEmail(String email); // ์ถ”๊ฐ€: ์ด๋ฉ”์ผ ์ค‘๋ณต ์ฒดํฌ + + /** + * ์‚ฌ์šฉ์ž๋ช…์œผ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค (๋กœ๊ทธ์ธ ์‹œ ์‚ฌ์šฉ). + * + * @param username ์‚ฌ์šฉ์ž๋ช… + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž (Optional) + */ + Optional findByUsername(String username); + + /** + * ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค (์ด๋ฉ”์ผ ์ค‘๋ณต ์ฒดํฌ ์‹œ ์‚ฌ์šฉ). + * + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž (Optional) + */ + Optional findByEmail(String email); + + /** + * ๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก + */ List findAll(); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ID + */ void deleteById(UUID id); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsById(UUID id); - boolean existsByUsername(String username); // ์ถ”๊ฐ€: username ์ค‘๋ณต ์ฒดํฌ - boolean existsByEmail(String email); // ์ถ”๊ฐ€: email ์ค‘๋ณต ์ฒดํฌ + + /** + * ์‚ฌ์šฉ์ž๋ช…์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค (username ์ค‘๋ณต ์ฒดํฌ). + * + * @param username ์‚ฌ์šฉ์ž๋ช… + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ + boolean existsByUsername(String username); + + /** + * ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค (email ์ค‘๋ณต ์ฒดํฌ). + * + * @param email ์ด๋ฉ”์ผ ์ฃผ์†Œ + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ + boolean existsByEmail(String email); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java index 0bf5175e..7cfce19f 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java @@ -5,13 +5,69 @@ import java.util.Optional; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ ๋ฐ ํ™œ๋™ ์ •๋ณด์— ๋Œ€ํ•œ CRUD ์ž‘์—…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface UserStatusRepository { + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param userStatus ์ €์žฅํ•  ์‚ฌ์šฉ์ž ์ƒํƒœ + * @return ์ €์žฅ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + */ UserStatus save(UserStatus userStatus); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ์ƒํƒœ ID + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ (Optional) + */ Optional findById(UUID id); + + /** + * ์‚ฌ์šฉ์ž ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ (Optional) + */ Optional findByUserId(UUID userId); + + /** + * ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ƒํƒœ ๋ชฉ๋ก + */ List findAll(); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ์ƒํƒœ ID + */ void deleteById(UUID id); + + /** + * ์‚ฌ์šฉ์ž ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + */ void deleteByUserId(UUID userId); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ์ƒํƒœ ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsById(UUID id); + + /** + * ์‚ฌ์šฉ์ž ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ์กด์žฌ ์—ฌ๋ถ€ + */ boolean existsByUserId(UUID userId); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java index 833a8f6c..8f08ea53 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java @@ -14,10 +14,16 @@ import java.util.Optional; import java.util.UUID; +/** + * ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * ์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") public class FileBinaryContentRepository implements BinaryContentRepository { + /** ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ */ private final Path DIRECTORY; + /** ์ €์žฅ ํŒŒ์ผ์˜ ํ™•์žฅ์ž (.ser) */ private final String EXTENSION = ".ser"; public FileBinaryContentRepository(@Value("${discodeit.repository.file-directory}") String fileDirectory) { diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java index b01afd30..71328bcd 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java @@ -14,10 +14,16 @@ import java.util.Optional; import java.util.UUID; +/** + * ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์ฑ„๋„ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * ์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") public class FileChannelRepository implements ChannelRepository { + /** ์ฑ„๋„ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ */ private final Path DIRECTORY; + /** ์ €์žฅ ํŒŒ์ผ์˜ ํ™•์žฅ์ž (.ser) */ private final String EXTENSION = ".ser"; public FileChannelRepository(@Value("${discodeit.repository.file-directory}") String fileDirectory) { diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java index d40311c5..b6823564 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java @@ -14,10 +14,16 @@ import java.util.Optional; import java.util.UUID; +/** + * ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ฉ”์‹œ์ง€ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * ์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") public class FileMessageRepository implements MessageRepository { + /** ๋ฉ”์‹œ์ง€ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ */ private final Path DIRECTORY; + /** ์ €์žฅ ํŒŒ์ผ์˜ ํ™•์žฅ์ž (.ser) */ private final String EXTENSION = ".ser"; public FileMessageRepository(@Value("${discodeit.repository.file-directory}") String fileDirectory) { diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java index 85ae76ad..3e875af4 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java @@ -14,10 +14,16 @@ import java.util.Optional; import java.util.UUID; +/** + * ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์ฝ์Œ ์ƒํƒœ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * ์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") public class FileReadStatusRepository implements ReadStatusRepository { + /** ์ฝ์Œ ์ƒํƒœ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ */ private final Path DIRECTORY; + /** ์ €์žฅ ํŒŒ์ผ์˜ ํ™•์žฅ์ž (.ser) */ private final String EXTENSION = ".ser"; public FileReadStatusRepository(@Value("${discodeit.repository.file-directory}") String fileDirectory) { diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java index a5473bd6..71477d33 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java @@ -14,10 +14,16 @@ import java.util.Optional; import java.util.UUID; +/** + * ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * ์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") public class FileUserRepository implements UserRepository { + /** ์‚ฌ์šฉ์ž ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ */ private final Path DIRECTORY; + /** ์ €์žฅ ํŒŒ์ผ์˜ ํ™•์žฅ์ž (.ser) */ private final String EXTENSION = ".ser"; public FileUserRepository(@Value("${discodeit.repository.file-directory}") String fileDirectory) { diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java index 4453cf47..9b8cc5e6 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java @@ -14,10 +14,16 @@ import java.util.Optional; import java.util.UUID; +/** + * ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž ์ƒํƒœ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * ์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file") public class FileUserStatusRepository implements UserStatusRepository { + /** ์‚ฌ์šฉ์ž ์ƒํƒœ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ */ private final Path DIRECTORY; + /** ์ €์žฅ ํŒŒ์ผ์˜ ํ™•์žฅ์ž (.ser) */ private final String EXTENSION = ".ser"; public FileUserStatusRepository(@Value("${discodeit.repository.file-directory}") String fileDirectory) { diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java index 9987baf4..5a5b8ecf 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java @@ -7,9 +7,14 @@ import java.util.*; +/** + * Java Collection Framework ๊ธฐ๋ฐ˜ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * HashMap์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) public class JCFBinaryContentRepository implements BinaryContentRepository { + /** ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๋Š” HashMap */ private final Map data = new HashMap<>(); @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java index 28b9a34e..76d96194 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java @@ -7,9 +7,14 @@ import java.util.*; +/** + * Java Collection Framework ๊ธฐ๋ฐ˜ ์ฑ„๋„ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * HashMap์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) public class JCFChannelRepository implements ChannelRepository { + /** ์ฑ„๋„์„ ์ €์žฅํ•˜๋Š” HashMap */ private final Map data = new HashMap<>(); @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java index 73203604..b62b0b33 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java @@ -7,9 +7,14 @@ import java.util.*; +/** + * Java Collection Framework ๊ธฐ๋ฐ˜ ๋ฉ”์‹œ์ง€ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * HashMap์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) public class JCFMessageRepository implements MessageRepository { + /** ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” HashMap */ private final Map data = new HashMap<>(); @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java index 9397c253..e5586e2f 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java @@ -7,9 +7,14 @@ import java.util.*; +/** + * Java Collection Framework ๊ธฐ๋ฐ˜ ์ฝ๊ธฐ ์ƒํƒœ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * HashMap์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) public class JCFReadStatusRepository implements ReadStatusRepository { + /** ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” HashMap */ private final Map data = new HashMap<>(); @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java index 9cb492ea..b5fee54e 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java @@ -7,9 +7,14 @@ import java.util.*; +/** + * Java Collection Framework ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * HashMap์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) public class JCFUserRepository implements UserRepository { + /** ์‚ฌ์šฉ์ž๋ฅผ ์ €์žฅํ•˜๋Š” HashMap */ private final Map data = new HashMap<>(); @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java index ca687b72..00b055f6 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java @@ -7,9 +7,14 @@ import java.util.*; +/** + * Java Collection Framework ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž ์ƒํƒœ ์ €์žฅ์†Œ ๊ตฌํ˜„์ฒด + * HashMap์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + */ @Repository @ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "jcf", matchIfMissing = true) public class JCFUserStatusRepository implements UserStatusRepository { + /** ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” HashMap */ private final Map data = new HashMap<>(); @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/AuthService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/AuthService.java index e6fcefd9..dc0b81e4 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/AuthService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/AuthService.java @@ -3,6 +3,16 @@ import com.sprint.mission.discodeit.dto.request.LoginRequest; import com.sprint.mission.discodeit.dto.response.UserResponse; +/** + * ์ธ์ฆ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๋ฐ ์ธ์ฆ ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface AuthService { + /** + * ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ๋กœ๊ทธ์ธ ์š”์ฒญ ์ •๋ณด (์‚ฌ์šฉ์ž๋ช…, ๋น„๋ฐ€๋ฒˆํ˜ธ) + * @return ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด + */ UserResponse login(LoginRequest request); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java index 463bbdf2..e4e1f0f1 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java @@ -5,9 +5,39 @@ import java.util.List; import java.util.UUID; +/** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface BinaryContentService { + /** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ ์š”์ฒญ + * @return ์ƒ์„ฑ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  + */ BinaryContent create(BinaryContentCreateRequest request); + + /** + * ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID + * @return ์กฐํšŒ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  + */ BinaryContent find(UUID id); + + /** + * ์—ฌ๋Ÿฌ ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param ids ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID ๋ชฉ๋ก + * @return ์กฐํšŒ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ๋ชฉ๋ก + */ List findAllByIdIn(List ids); + + /** + * ID๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ID + */ void delete(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java index fdcca611..7a66d045 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java @@ -7,11 +7,56 @@ import java.util.List; import java.util.UUID; +/** + * ์ฑ„๋„ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ๊ณต๊ฐœ/๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface ChannelService { + /** + * ๊ณต๊ฐœ ์ฑ„๋„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์š”์ฒญ + * @return ์ƒ์„ฑ๋œ ์ฑ„๋„ ์ •๋ณด + */ ChannelResponse createPublic(PublicChannelCreateRequest request); + + /** + * ๋น„๊ณต๊ฐœ ์ฑ„๋„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ์š”์ฒญ + * @return ์ƒ์„ฑ๋œ ์ฑ„๋„ ์ •๋ณด + */ ChannelResponse createPrivate(PrivateChannelCreateRequest request); + + /** + * ID๋กœ ์ฑ„๋„์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฑ„๋„ ID + * @return ์กฐํšŒ๋œ ์ฑ„๋„ ์ •๋ณด + */ ChannelResponse find(UUID id); + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์ฑ„๋„์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ฑ„๋„ ๋ชฉ๋ก + */ List findAllByUserId(UUID userId); + + /** + * ์ฑ„๋„ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฑ„๋„ ID + * @param request ์ฑ„๋„ ์—…๋ฐ์ดํŠธ ์š”์ฒญ + * @return ์—…๋ฐ์ดํŠธ๋œ ์ฑ„๋„ ์ •๋ณด + */ ChannelResponse update(UUID id, ChannelUpdateRequest request); + + /** + * ์ฑ„๋„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์ฑ„๋„ ID + */ void delete(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/MessageService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/MessageService.java index a7baa104..af7db695 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/MessageService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/MessageService.java @@ -7,10 +7,49 @@ import java.util.List; import java.util.UUID; +/** + * ๋ฉ”์‹œ์ง€ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋ฐ ์ฒจ๋ถ€ํŒŒ์ผ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface MessageService { + /** + * ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค (์ฒจ๋ถ€ํŒŒ์ผ ํฌํ•จ ๊ฐ€๋Šฅ). + * + * @param request ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ ์š”์ฒญ + * @param attachmentRequests ์ฒจ๋ถ€ํŒŒ์ผ ๋ชฉ๋ก + * @return ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€ ์ •๋ณด + */ MessageResponse create(MessageCreateRequest request, List attachmentRequests); + + /** + * ID๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฉ”์‹œ์ง€ ID + * @return ์กฐํšŒ๋œ ๋ฉ”์‹œ์ง€ ์ •๋ณด + */ MessageResponse find(UUID id); + + /** + * ํŠน์ • ์ฑ„๋„์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param channelId ์ฑ„๋„ ID + * @return ํ•ด๋‹น ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก + */ List findAllByChannelId(UUID channelId); + + /** + * ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ๋ฉ”์‹œ์ง€ ID + * @param request ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ ์š”์ฒญ + * @return ์—…๋ฐ์ดํŠธ๋œ ๋ฉ”์‹œ์ง€ ์ •๋ณด + */ MessageResponse update(UUID id, MessageUpdateRequest request); + + /** + * ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ๋ฉ”์‹œ์ง€ ID + */ void delete(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java index 87ef9a02..a40c4c1d 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java @@ -6,10 +6,48 @@ import java.util.List; import java.util.UUID; +/** + * ์ฝ๊ธฐ ์ƒํƒœ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž์˜ ์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface ReadStatusService { + /** + * ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ์ฝ๊ธฐ ์ƒํƒœ ์ƒ์„ฑ ์š”์ฒญ + * @return ์ƒ์„ฑ๋œ ์ฝ๊ธฐ ์ƒํƒœ + */ ReadStatus create(ReadStatusCreateRequest request); + + /** + * ID๋กœ ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฝ๊ธฐ ์ƒํƒœ ID + * @return ์กฐํšŒ๋œ ์ฝ๊ธฐ ์ƒํƒœ + */ ReadStatus find(UUID id); + + /** + * ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ฝ๊ธฐ ์ƒํƒœ ๋ชฉ๋ก + */ List findAllByUserId(UUID userId); + + /** + * ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ฝ๊ธฐ ์ƒํƒœ ID + * @param request ์ฝ๊ธฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์š”์ฒญ + * @return ์—…๋ฐ์ดํŠธ๋œ ์ฝ๊ธฐ ์ƒํƒœ + */ ReadStatus update(UUID id, ReadStatusUpdateRequest request); + + /** + * ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์ฝ๊ธฐ ์ƒํƒœ ID + */ void delete(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserService.java index a9646a38..4d401c8b 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserService.java @@ -3,14 +3,62 @@ import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; import com.sprint.mission.discodeit.dto.request.UserCreateRequest; import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; +import com.sprint.mission.discodeit.dto.response.UserDto; import com.sprint.mission.discodeit.dto.response.UserResponse; import java.util.List; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋ฐ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface UserService { + /** + * ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํฌํ•จ ๊ฐ€๋Šฅ). + * + * @param request ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์š”์ฒญ + * @param profileRequest ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์š”์ฒญ (null ๊ฐ€๋Šฅ) + * @return ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด + */ UserResponse create(UserCreateRequest request, BinaryContentCreateRequest profileRequest); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ID + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด + */ UserResponse find(UUID id); + + /** + * ๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก + */ List findAll(); + + /** + * ๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ UserDto ํ˜•์‹์œผ๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * API ์ŠคํŽ™์— ๋งž๋Š” ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก (UserDto ํ˜•์‹) + */ + List findAllAsDto(); + + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ). + * + * @param id ์‚ฌ์šฉ์ž ID + * @param request ์‚ฌ์šฉ์ž ์—…๋ฐ์ดํŠธ ์š”์ฒญ + * @param profileRequest ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋ฐ์ดํŠธ ์š”์ฒญ (null ๊ฐ€๋Šฅ) + * @return ์—…๋ฐ์ดํŠธ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด + */ UserResponse update(UUID id, UserUpdateRequest request, BinaryContentCreateRequest profileRequest); + + /** + * ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ID + */ void delete(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java index bc7f79b8..7fb58dab 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java @@ -6,12 +6,64 @@ import java.util.List; import java.util.UUID; +/** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค + * ์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ ๋ฐ ํ™œ๋™ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + */ public interface UserStatusService { + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ์‚ฌ์šฉ์ž ์ƒํƒœ ์ƒ์„ฑ ์š”์ฒญ + * @return ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + */ UserStatus create(UserStatusCreateRequest request); + + /** + * ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ์ƒํƒœ ID + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + */ UserStatus find(UUID id); + + /** + * ์‚ฌ์šฉ์ž ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + */ UserStatus findByUserId(UUID userId); + + /** + * ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @return ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ƒํƒœ ๋ชฉ๋ก + */ List findAll(); + + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ฌ์šฉ์ž ์ƒํƒœ ID + * @param request ์‚ฌ์šฉ์ž ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์š”์ฒญ + * @return ์—…๋ฐ์ดํŠธ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + */ UserStatus update(UUID id, UserStatusUpdateRequest request); + + /** + * ์‚ฌ์šฉ์ž ID๋กœ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @param request ์‚ฌ์šฉ์ž ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์š”์ฒญ + * @return ์—…๋ฐ์ดํŠธ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + */ UserStatus updateByUserId(UUID userId, UserStatusUpdateRequest request); + + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ์ƒํƒœ ID + */ void delete(UUID id); } \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java index 646adfb5..36f8c4e2 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java @@ -12,12 +12,30 @@ import java.util.NoSuchElementException; +/** + * AuthService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicAuthService implements AuthService { + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserRepository userRepository; + + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserStatusRepository userStatusRepository; + /** + * ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ๋กœ๊ทธ์ธ ์š”์ฒญ ์ •๋ณด (username, password) + * @return ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด + * @throws NoSuchElementException ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๊ฑฐ๋‚˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ + */ @Override public UserResponse login(LoginRequest request) { // username์œผ๋กœ User ์กฐํšŒ diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java index 890fa2b2..4f1483bf 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java @@ -11,9 +11,16 @@ import java.util.NoSuchElementException; import java.util.UUID; +/** + * BinaryContentService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ (ํŒŒ์ผ, ์ด๋ฏธ์ง€ ๋“ฑ)์˜ ์ €์žฅ ๋ฐ ์กฐํšŒ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicBinaryContentService implements BinaryContentService { + /** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final BinaryContentRepository binaryContentRepository; @Override diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java index 8a4689b7..00683df7 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java @@ -21,12 +21,31 @@ import java.util.NoSuchElementException; import java.util.UUID; +/** + * ChannelService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ์ฑ„๋„(PUBLIC, PRIVATE)์˜ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicChannelService implements ChannelService { + /** + * ์ฑ„๋„ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final ChannelRepository channelRepository; + + /** + * ์ฑ„๋„๋ณ„ ์‚ฌ์šฉ์ž ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final ReadStatusRepository readStatusRepository; + + /** + * ๋ฉ”์‹œ์ง€ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final MessageRepository messageRepository; + + /** + * ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ (์ฒจ๋ถ€ํŒŒ์ผ)๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final BinaryContentRepository binaryContentRepository; @Override @@ -36,6 +55,13 @@ public ChannelResponse createPublic(PublicChannelCreateRequest request) { return toChannelResponse(savedChannel); } + /** + * PRIVATE ์ฑ„๋„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * ์ฐธ์—ฌ์ž ๋ชฉ๋ก์— ์žˆ๋Š” ๊ฐ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ReadStatus๋„ ํ•จ๊ป˜ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * + * @param request PRIVATE ์ฑ„๋„ ์ƒ์„ฑ ์š”์ฒญ ์ •๋ณด (์ฐธ์—ฌ์ž ID ๋ชฉ๋ก ํฌํ•จ) + * @return ์ƒ์„ฑ๋œ ์ฑ„๋„ ์ •๋ณด + */ @Override public ChannelResponse createPrivate(PrivateChannelCreateRequest request) { // PRIVATE ์ฑ„๋„์€ name, description ์—†์Œ @@ -43,7 +69,7 @@ public ChannelResponse createPrivate(PrivateChannelCreateRequest request) { Channel savedChannel = channelRepository.save(channel); // ์ฐธ์—ฌ์ž๋ณ„ ReadStatus ์ƒ์„ฑ - for (UUID userId : request.participantIds()) { + for (UUID userId : request.memberIds()) { ReadStatus readStatus = new ReadStatus(userId, savedChannel.getId(), Instant.now()); readStatusRepository.save(readStatus); } @@ -58,6 +84,13 @@ public ChannelResponse find(UUID id) { return toChannelResponse(channel); } + /** + * ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์ฑ„๋„์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * PUBLIC ์ฑ„๋„์€ ๋ชจ๋‘ ์กฐํšŒ ๊ฐ€๋Šฅํ•˜๋ฉฐ, PRIVATE ์ฑ„๋„์€ ์ฐธ์—ฌ์ž๋งŒ ์กฐํšŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ฑ„๋„ ๋ชฉ๋ก + */ @Override public List findAllByUserId(UUID userId) { List allChannels = channelRepository.findAll(); @@ -91,6 +124,13 @@ public ChannelResponse update(UUID id, ChannelUpdateRequest request) { return toChannelResponse(savedChannel); } + /** + * ์ฑ„๋„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * ์—ฐ๊ด€๋œ ๋ฉ”์‹œ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ, ReadStatus๋„ ํ•จ๊ป˜ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์ฑ„๋„ ID + * @throws NoSuchElementException ์ฑ„๋„์„ ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ + */ @Override public void delete(UUID id) { Channel channel = channelRepository.findById(id) @@ -113,6 +153,13 @@ public void delete(UUID id) { channelRepository.deleteById(id); } + /** + * Channel ์—”ํ‹ฐํ‹ฐ๋ฅผ ChannelResponse DTO๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์‹œ๊ฐ„๊ณผ ์ฐธ์—ฌ์ž ๋ชฉ๋ก(PRIVATE ์ฑ„๋„์˜ ๊ฒฝ์šฐ)์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. + * + * @param channel ๋ณ€ํ™˜ํ•  Channel ์—”ํ‹ฐํ‹ฐ + * @return ChannelResponse DTO + */ private ChannelResponse toChannelResponse(Channel channel) { // ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์‹œ๊ฐ„ ์กฐํšŒ Instant lastMessageAt = messageRepository.findAllByChannelId(channel.getId()).stream() diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java index 552ae80a..efd4c835 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java @@ -11,23 +11,43 @@ import com.sprint.mission.discodeit.service.MessageService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; +/** + * MessageService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ๋ฉ”์‹œ์ง€์˜ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋ฐ ์ฒจ๋ถ€ํŒŒ์ผ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicMessageService implements MessageService { + /** + * ๋ฉ”์‹œ์ง€ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final MessageRepository messageRepository; + + /** + * ๋ฉ”์‹œ์ง€ ์ฒจ๋ถ€ํŒŒ์ผ์„ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final BinaryContentRepository binaryContentRepository; + /** + * ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * ์ฒจ๋ถ€ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ•จ๊ป˜ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ ์š”์ฒญ ์ •๋ณด + * @param attachmentRequests ์ฒจ๋ถ€ํŒŒ์ผ ๋ชฉ๋ก (์„ ํƒ ์‚ฌํ•ญ) + * @return ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€ ์ •๋ณด + */ @Override public MessageResponse create(MessageCreateRequest request, List attachmentRequests) { // ์ฒจ๋ถ€ํŒŒ์ผ ์ €์žฅ (์„ ํƒ์ ) List attachmentIds = new ArrayList<>(); - if (attachmentRequests != null && !attachmentRequests.isEmpty()) { + if (!CollectionUtils.isEmpty(attachmentRequests)) { for (BinaryContentCreateRequest attachmentRequest : attachmentRequests) { BinaryContent attachment = new BinaryContent( attachmentRequest.fileName(), @@ -73,13 +93,20 @@ public MessageResponse update(UUID id, MessageUpdateRequest request) { return toMessageResponse(savedMessage); } + /** + * ๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * ์—ฐ๊ด€๋œ ์ฒจ๋ถ€ํŒŒ์ผ๋„ ํ•จ๊ป˜ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ๋ฉ”์‹œ์ง€ ID + * @throws NoSuchElementException ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ + */ @Override public void delete(UUID id) { Message message = messageRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("Message not found: " + id)); // ์ฒจ๋ถ€ํŒŒ์ผ ์‚ญ์ œ - if (message.getAttachmentIds() != null) { + if (!CollectionUtils.isEmpty(message.getAttachmentIds())) { for (UUID attachmentId : message.getAttachmentIds()) { binaryContentRepository.deleteById(attachmentId); } @@ -89,6 +116,12 @@ public void delete(UUID id) { messageRepository.deleteById(id); } + /** + * Message ์—”ํ‹ฐํ‹ฐ๋ฅผ MessageResponse DTO๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * + * @param message ๋ณ€ํ™˜ํ•  Message ์—”ํ‹ฐํ‹ฐ + * @return MessageResponse DTO + */ private MessageResponse toMessageResponse(Message message) { return new MessageResponse( message.getId(), diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java index 414e0241..1d767309 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java @@ -14,13 +14,37 @@ import java.util.NoSuchElementException; import java.util.UUID; +/** + * ReadStatusService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ์‚ฌ์šฉ์ž์˜ ์ฑ„๋„๋ณ„ ์ฝ๊ธฐ ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicReadStatusService implements ReadStatusService { + /** + * ์ฝ๊ธฐ ์ƒํƒœ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final ReadStatusRepository readStatusRepository; + + /** + * ์‚ฌ์šฉ์ž ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ์„ ์œ„ํ•œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserRepository userRepository; + + /** + * ์ฑ„๋„ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ์„ ์œ„ํ•œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final ChannelRepository channelRepository; + /** + * ์ฝ๊ธฐ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ , ์ค‘๋ณต ์ƒ์„ฑ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ์ฝ๊ธฐ ์ƒํƒœ ์ƒ์„ฑ ์š”์ฒญ ์ •๋ณด + * @return ์ƒ์„ฑ๋œ ์ฝ๊ธฐ ์ƒํƒœ + * @throws NoSuchElementException ์‚ฌ์šฉ์ž ๋˜๋Š” ์ฑ„๋„์„ ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ + * @throws IllegalArgumentException ํ•ด๋‹น ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„์˜ ์ฝ๊ธฐ ์ƒํƒœ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•  ๊ฒฝ์šฐ + */ @Override public ReadStatus create(ReadStatusCreateRequest request) { // User ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java index 5bacfd19..98eba8c3 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java @@ -3,6 +3,7 @@ import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; import com.sprint.mission.discodeit.dto.request.UserCreateRequest; import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; +import com.sprint.mission.discodeit.dto.response.UserDto; import com.sprint.mission.discodeit.dto.response.UserResponse; import com.sprint.mission.discodeit.entity.BinaryContent; import com.sprint.mission.discodeit.entity.User; @@ -19,25 +20,51 @@ import java.util.NoSuchElementException; import java.util.UUID; +/** + * UserService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ์‚ฌ์šฉ์ž์˜ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ๋ฐ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicUserService implements UserService { + + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserRepository userRepository; + + /** + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final BinaryContentRepository binaryContentRepository; + + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserStatusRepository userStatusRepository; + /** + * ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ์ด๋ฉ”์ผ ์ค‘๋ณต์„ ํ™•์ธํ•˜๊ณ , ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•จ๊ป˜ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์š”์ฒญ ์ •๋ณด + * @param profileRequest ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ •๋ณด (์„ ํƒ ์‚ฌํ•ญ) + * @return ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด + * @throws IllegalArgumentException ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋˜๋Š” ์ด๋ฉ”์ผ์ด ์ด๋ฏธ ์กด์žฌํ•  ๊ฒฝ์šฐ + */ @Override public UserResponse create(UserCreateRequest request, BinaryContentCreateRequest profileRequest) { - // username ์ค‘๋ณต ์ฒดํฌ + // 1. ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ค‘๋ณต ์ฒดํฌ if (userRepository.existsByUsername(request.username())) { - throw new IllegalArgumentException("Username already exists: " + request.username()); + throw new IllegalArgumentException("์ด ์‚ฌ์šฉ์ž ์ด๋ฆ„์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: " + request.username()); } - // email ์ค‘๋ณต ์ฒดํฌ + + // 2. ์ด๋ฉ”์ผ ์ค‘๋ณต ์ฒดํฌ if (userRepository.existsByEmail(request.email())) { - throw new IllegalArgumentException("Email already exists: " + request.email()); + throw new IllegalArgumentException("์ด ์ด๋ฉ”์ผ์€ ์ด๋ฏธ ์กด์žฌํ•ด์š”!: " + request.email()); } - // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ €์žฅ (์„ ํƒ์ ) + // 3. ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ €์žฅ (์„ ํƒ ์‚ฌํ•ญ) UUID profileId = null; if (profileRequest != null) { BinaryContent profile = new BinaryContent( @@ -48,7 +75,7 @@ public UserResponse create(UserCreateRequest request, BinaryContentCreateRequest profileId = binaryContentRepository.save(profile).getId(); } - // User ์ƒ์„ฑ + // 4. User ๊ฐ์ฒด ์ƒ์„ฑ User user = new User( request.username(), request.email(), @@ -57,10 +84,11 @@ public UserResponse create(UserCreateRequest request, BinaryContentCreateRequest ); User savedUser = userRepository.save(user); - // UserStatus ์ƒ์„ฑ + // 5. UserStatus ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ €์žฅ UserStatus userStatus = new UserStatus(savedUser.getId(), Instant.now()); userStatusRepository.save(userStatus); + // 6. ์ตœ์ข… ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ return toUserResponse(savedUser, true); } @@ -68,6 +96,7 @@ public UserResponse create(UserCreateRequest request, BinaryContentCreateRequest public UserResponse find(UUID id) { User user = userRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("User not found: " + id)); + boolean isOnline = getOnlineStatus(user.getId()); return toUserResponse(user, isOnline); } @@ -79,19 +108,46 @@ public List findAll() { .toList(); } + @Override + public List findAllAsDto() { + return userRepository.findAll().stream() + .map(user -> { + boolean isOnline = getOnlineStatus(user.getId()); + return new UserDto( + user.getId(), + user.getCreatedAt(), + user.getUpdatedAt(), + user.getUsername(), + user.getEmail(), + user.getProfileId(), + isOnline + ); + }) + .toList(); + } + + /** + * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•˜๊ณ  ์ƒˆ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์ˆ˜์ •ํ•  ์‚ฌ์šฉ์ž ID + * @param request ์‚ฌ์šฉ์ž ์ˆ˜์ • ์š”์ฒญ ์ •๋ณด + * @param profileRequest ์ƒˆ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ •๋ณด (์„ ํƒ ์‚ฌํ•ญ) + * @return ์ˆ˜์ •๋œ ์‚ฌ์šฉ์ž ์ •๋ณด + * @throws NoSuchElementException ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ + */ @Override public UserResponse update(UUID id, UserUpdateRequest request, BinaryContentCreateRequest profileRequest) { + // 1. ์ˆ˜์ •ํ•  ์‚ฌ์šฉ์ž ๋จผ์ € ์ฐพ๊ธฐ User user = userRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("User not found: " + id)); - // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋Œ€์ฒด (์„ ํƒ์ ) + // 2. ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๊ต์ฒด (์„ ํƒ ์‚ฌํ•ญ) UUID newProfileId = user.getProfileId(); if (profileRequest != null) { - // ๊ธฐ์กด ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์‚ญ์ œ if (user.getProfileId() != null) { binaryContentRepository.deleteById(user.getProfileId()); } - // ์ƒˆ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ €์žฅ BinaryContent profile = new BinaryContent( profileRequest.fileName(), profileRequest.contentType(), @@ -100,34 +156,60 @@ public UserResponse update(UUID id, UserUpdateRequest request, BinaryContentCrea newProfileId = binaryContentRepository.save(profile).getId(); } + // 3. ์‚ฌ์šฉ์ž ์ •๋ณด ์—…๋ฐ์ดํŠธ user.update(request.username(), request.email(), request.password(), newProfileId); + + // 4. ๋ณ€๊ฒฝ๋œ ์ •๋ณด ์ €์žฅ User savedUser = userRepository.save(user); + // 5. ์ตœ์ข… ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ boolean isOnline = getOnlineStatus(savedUser.getId()); return toUserResponse(savedUser, isOnline); } + /** + * ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * ์—ฐ๊ด€๋œ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€์™€ ์ƒํƒœ ์ •๋ณด๋„ ํ•จ๊ป˜ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. + * + * @param id ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ID + * @throws NoSuchElementException ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ + */ @Override public void delete(UUID id) { + // 1. ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ๋จผ์ € ์ฐพ๊ธฐ User user = userRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("User not found: " + id)); - // ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์‚ญ์ œ + // 2. ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋“ค ๋จผ์ € ์‚ญ์ œํ•˜๊ธฐ if (user.getProfileId() != null) { binaryContentRepository.deleteById(user.getProfileId()); } - // UserStatus ์‚ญ์ œ userStatusRepository.deleteByUserId(id); - // User ์‚ญ์ œ + + // 3. ์ตœ์ข…์ ์œผ๋กœ ์‚ฌ์šฉ์ž ์‚ญ์ œ userRepository.deleteById(id); } + /** + * ์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. + * + * @param userId ์‚ฌ์šฉ์ž ID + * @return ์˜จ๋ผ์ธ ์—ฌ๋ถ€ (์ƒํƒœ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด false) + */ private boolean getOnlineStatus(UUID userId) { return userStatusRepository.findByUserId(userId) .map(UserStatus::isOnline) .orElse(false); } + /** + * User ์—”ํ‹ฐํ‹ฐ๋ฅผ UserResponse DTO๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + * + * @param user ๋ณ€ํ™˜ํ•  User ์—”ํ‹ฐํ‹ฐ + * @param isOnline ์˜จ๋ผ์ธ ์ƒํƒœ + * @return UserResponse DTO + */ private UserResponse toUserResponse(User user, boolean isOnline) { return new UserResponse( user.getId(), diff --git a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java index c58c3975..67fa49f0 100644 --- a/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java +++ b/discodeit/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java @@ -13,12 +13,32 @@ import java.util.NoSuchElementException; import java.util.UUID; +/** + * UserStatusService ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด. + * ์‚ฌ์šฉ์ž ์ƒํƒœ(์˜จ๋ผ์ธ/์˜คํ”„๋ผ์ธ) ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + */ @Service @RequiredArgsConstructor public class BasicUserStatusService implements UserStatusService { + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserStatusRepository userStatusRepository; + + /** + * ์‚ฌ์šฉ์ž ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ์„ ์œ„ํ•œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ + */ private final UserRepository userRepository; + /** + * ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * ์‚ฌ์šฉ์ž์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ , ์ค‘๋ณต ์ƒ์„ฑ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. + * + * @param request ์‚ฌ์šฉ์ž ์ƒํƒœ ์ƒ์„ฑ ์š”์ฒญ ์ •๋ณด + * @return ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ์ƒํƒœ + * @throws NoSuchElementException ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ + * @throws IllegalArgumentException ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ƒํƒœ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•  ๊ฒฝ์šฐ + */ @Override public UserStatus create(UserStatusCreateRequest request) { // User ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ @@ -30,7 +50,7 @@ public UserStatus create(UserStatusCreateRequest request) { throw new IllegalArgumentException("UserStatus already exists for user: " + request.userId()); } - UserStatus userStatus = new UserStatus(request.userId(), request.lastActiveAt()); + UserStatus userStatus = new UserStatus(request.userId(), request.lastAccessAt()); return userStatusRepository.save(userStatus); } @@ -55,7 +75,7 @@ public List findAll() { public UserStatus update(UUID id, UserStatusUpdateRequest request) { UserStatus userStatus = userStatusRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("UserStatus not found: " + id)); - userStatus.update(request.lastActiveAt()); + userStatus.update(request.lastAccessAt()); return userStatusRepository.save(userStatus); } @@ -63,7 +83,7 @@ public UserStatus update(UUID id, UserStatusUpdateRequest request) { public UserStatus updateByUserId(UUID userId, UserStatusUpdateRequest request) { UserStatus userStatus = userStatusRepository.findByUserId(userId) .orElseThrow(() -> new NoSuchElementException("UserStatus not found for user: " + userId)); - userStatus.update(request.lastActiveAt()); + userStatus.update(request.lastAccessAt()); return userStatusRepository.save(userStatus); } diff --git a/discodeit/src/main/java/com/sprint/mission/docs/Discodeit API.postman_collection.json b/discodeit/src/main/java/com/sprint/mission/docs/Discodeit API.postman_collection.json new file mode 100644 index 00000000..e3602f2d --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/docs/Discodeit API.postman_collection.json @@ -0,0 +1,755 @@ +{ + "info": { + "name": "Discodeit API", + "description": "Discodeit API ์ž๋™ ํ…Œ์ŠคํŠธ ์ปฌ๋ ‰์…˜\n\n๊ณ ์ • UUID๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์žฌ์‹œ์ž‘ ํ›„์—๋„ ์•ˆ์ •์ ์ธ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "discodeit-api" + }, + "item": [ + { + "name": "์‚ฌ์šฉ์ž ๊ด€๋ฆฌ", + "item": [ + { + "name": "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const users = pm.response.json();", + " if (!Array.isArray(users)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (users.length === 0) {", + " throw new Error('์‚ฌ์šฉ์ž ๋ชฉ๋ก์ด ๋น„์–ด์žˆ์Œ');", + " }", + " ", + " const requiredFields = ['id', 'username', 'email', 'online'];", + " const missingFields = requiredFields.filter(field => !users[0].hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users", + "host": ["{{baseUrl}}"], + "path": ["users"] + } + }, + "response": [] + }, + { + "name": "ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ (์ด์˜ํฌ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const user = pm.response.json();", + " if (typeof user !== 'object' || Array.isArray(user)) {", + " throw new Error('์‘๋‹ต์ด ๊ฐ์ฒด๊ฐ€ ์•„๋‹˜');", + " }", + " ", + " const expectedId = pm.environment.get('user2Id');", + " if (user.id !== expectedId) {", + " throw new Error(`์‚ฌ์šฉ์ž ID ๋ถˆ์ผ์น˜: ${user.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " const requiredFields = ['id', 'username', 'email'];", + " const missingFields = requiredFields.filter(field => !user.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/{{user2Id}}", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user2Id}}"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์ƒ์„ฑ (JSON)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 201) {", + " const error = pm.response.json();", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200 ๋˜๋Š” 201) - ${error.message || ''}`);", + " }", + " ", + " const user = pm.response.json();", + " if (!user.id) {", + " throw new Error('์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ID๊ฐ€ ์—†์Œ');", + " }", + " ", + " if (!user.email) {", + " throw new Error('์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ์ด๋ฉ”์ผ์ด ์—†์Œ');", + " }", + " ", + " // ๋‹ค์Œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ID ์ €์žฅ", + " pm.environment.set('createdUserId', user.id);", + " ", + " pm.test('์‚ฌ์šฉ์ž ์ƒ์„ฑ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ์ƒ์„ฑ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// ๋งค๋ฒˆ ๋‹ค๋ฅธ ์ด๋ฉ”์ผ ์ƒ์„ฑ", + "pm.environment.set('randomEmail', 'newuser' + Date.now() + '@example.com');" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"์ƒˆ๋กœ์šด์‚ฌ์šฉ์ž\",\n \"email\": \"{{randomEmail}}\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users", + "host": ["{{baseUrl}}"], + "path": ["users"] + } + }, + "response": [] + }, + { + "name": "์‚ฌ์šฉ์ž ์ˆ˜์ • (์ด์˜ํฌ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const user = pm.response.json();", + " const expectedId = pm.environment.get('user2Id');", + " if (user.id !== expectedId) {", + " throw new Error(`์‚ฌ์šฉ์ž ID ๋ณ€๊ฒฝ๋จ: ${user.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " pm.test('์‚ฌ์šฉ์ž ์ˆ˜์ • ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ์ˆ˜์ • ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"{{user2Name}}_์ˆ˜์ •๋จ\",\n \"email\": \"{{user2Email}}\",\n \"password\": \"newpassword123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user2Id}}", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user2Id}}"] + } + }, + "response": [] + }, + { + "name": "์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ์‚ญ์ œ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200 && pm.response.code !== 204) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200 ๋˜๋Š” 204)`);", + " }", + " ", + " pm.test('์‚ฌ์šฉ์ž ์‚ญ์ œ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์‚ฌ์šฉ์ž ์‚ญ์ œ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/{{createdUserId}}", + "host": ["{{baseUrl}}"], + "path": ["users", "{{createdUserId}}"] + } + }, + "response": [] + } + ] + }, + { + "name": "์‚ฌ์šฉ์ž ์ƒํƒœ ๊ด€๋ฆฌ", + "item": [ + { + "name": "์ด์˜ํฌ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const status = pm.response.json();", + " if (!status.hasOwnProperty('lastAccessAt')) {", + " throw new Error('lastAccessAt ํ•„๋“œ๊ฐ€ ์‘๋‹ต์— ์—†์Œ');", + " }", + " ", + " pm.test('์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastAccessAt\": \"{{$isoTimestamp}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user2Id}}/status", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user2Id}}", "status"] + } + }, + "response": [] + }, + { + "name": "๋ฐ•๋ฏผ์ˆ˜ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const status = pm.response.json();", + " if (!status.hasOwnProperty('lastAccessAt')) {", + " throw new Error('lastAccessAt ํ•„๋“œ๊ฐ€ ์‘๋‹ต์— ์—†์Œ');", + " }", + " ", + " pm.test('์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastAccessAt\": \"{{$isoTimestamp}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user3Id}}/status", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user3Id}}", "status"] + } + }, + "response": [] + }, + { + "name": "์ตœ์ง€์—ฐ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const status = pm.response.json();", + " if (!status.hasOwnProperty('lastAccessAt')) {", + " throw new Error('lastAccessAt ํ•„๋“œ๊ฐ€ ์‘๋‹ต์— ์—†์Œ');", + " }", + " ", + " pm.test('์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"lastAccessAt\": \"{{$isoTimestamp}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{user4Id}}/status", + "host": ["{{baseUrl}}"], + "path": ["users", "{{user4Id}}", "status"] + } + }, + "response": [] + } + ] + }, + { + "name": "๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ ", + "item": [ + { + "name": "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ (์ด์˜ํฌ ํ”„๋กœํ•„)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const content = pm.response.json();", + " const expectedId = pm.environment.get('user2ProfileId');", + " if (content.id !== expectedId) {", + " throw new Error(`ํŒŒ์ผ ID ๋ถˆ์ผ์น˜: ${content.id} (์˜ˆ์ƒ: ${expectedId})`);", + " }", + " ", + " const requiredFields = ['id', 'filename', 'contentType'];", + " const missingFields = requiredFields.filter(field => !content.hasOwnProperty(field));", + " if (missingFields.length > 0) {", + " throw new Error(`ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ: ${missingFields.join(', ')}`);", + " }", + " ", + " pm.test('ํŒŒ์ผ ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŒŒ์ผ ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents/{{user2ProfileId}}", + "host": ["{{baseUrl}}"], + "path": ["binary-contents", "{{user2ProfileId}}"] + } + }, + "response": [] + }, + { + "name": "ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ (2๋ช…)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const contents = pm.response.json();", + " if (!Array.isArray(contents)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (contents.length === 0) {", + " throw new Error('์กฐํšŒ๋œ ํŒŒ์ผ์ด ์—†์Œ');", + " }", + " ", + " pm.test('ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents?ids={{user2ProfileId}},{{user3ProfileId}}", + "host": ["{{baseUrl}}"], + "path": ["binary-contents"], + "query": [ + { + "key": "ids", + "value": "{{user2ProfileId}},{{user3ProfileId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ (์ „์ฒด)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 200) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 200)`);", + " }", + " ", + " const contents = pm.response.json();", + " if (!Array.isArray(contents)) {", + " throw new Error('์‘๋‹ต์ด ๋ฐฐ์—ด์ด ์•„๋‹˜');", + " }", + " ", + " if (contents.length === 0) {", + " throw new Error('์กฐํšŒ๋œ ํŒŒ์ผ์ด ์—†์Œ');", + " }", + " ", + " pm.test('ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents?ids={{user2ProfileId}},{{user3ProfileId}},{{user4ProfileId}}", + "host": ["{{baseUrl}}"], + "path": ["binary-contents"], + "query": [ + { + "key": "ids", + "value": "{{user2ProfileId}},{{user3ProfileId}},{{user4ProfileId}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "์—๋Ÿฌ ์ผ€์ด์Šค ํ…Œ์ŠคํŠธ", + "item": [ + { + "name": "์ค‘๋ณต ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์ƒ์„ฑ (400 ์—๋Ÿฌ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 400) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 400)`);", + " }", + " ", + " const error = pm.response.json();", + " if (!error.hasOwnProperty('message')) {", + " throw new Error('์—๋Ÿฌ ์‘๋‹ต์— message ํ•„๋“œ๊ฐ€ ์—†์Œ');", + " }", + " ", + " pm.test('์ค‘๋ณต ์ด๋ฉ”์ผ ์—๋Ÿฌ ๊ฒ€์ฆ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์ค‘๋ณต ์ด๋ฉ”์ผ ์—๋Ÿฌ ๊ฒ€์ฆ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"์ค‘๋ณตํ…Œ์ŠคํŠธ\",\n \"email\": \"{{user2Email}}\",\n \"password\": \"test1234\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users", + "host": ["{{baseUrl}}"], + "path": ["users"] + } + }, + "response": [] + }, + { + "name": "์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์กฐํšŒ (404 ์—๋Ÿฌ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 404) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 404)`);", + " }", + " ", + " const error = pm.response.json();", + " if (!error.hasOwnProperty('message')) {", + " throw new Error('์—๋Ÿฌ ์‘๋‹ต์— message ํ•„๋“œ๊ฐ€ ์—†์Œ');", + " }", + " ", + " pm.test('์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์—๋Ÿฌ ๊ฒ€์ฆ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์—๋Ÿฌ ๊ฒ€์ฆ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/00000000-0000-0000-0000-000000000000", + "host": ["{{baseUrl}}"], + "path": ["users", "00000000-0000-0000-0000-000000000000"] + } + }, + "response": [] + }, + { + "name": "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ์กฐํšŒ (404 ์—๋Ÿฌ)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " if (pm.response.code !== 404) {", + " throw new Error(`์ƒํƒœ ์ฝ”๋“œ ์˜ค๋ฅ˜: ${pm.response.code} (์˜ˆ์ƒ: 404)`);", + " }", + " ", + " const error = pm.response.json();", + " if (!error.hasOwnProperty('message')) {", + " throw new Error('์—๋Ÿฌ ์‘๋‹ต์— message ํ•„๋“œ๊ฐ€ ์—†์Œ');", + " }", + " ", + " pm.test('์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ์—๋Ÿฌ ๊ฒ€์ฆ ์„ฑ๊ณต', () => {", + " pm.expect(true).to.be.true;", + " });", + "} catch (error) {", + " pm.test(`์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ์—๋Ÿฌ ๊ฒ€์ฆ ์‹คํŒจ: ${error.message}`, () => {", + " pm.expect.fail(error.message);", + " });", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/binary-contents/00000000-0000-0000-0000-000000000000", + "host": ["{{baseUrl}}"], + "path": ["binary-contents", "00000000-0000-0000-0000-000000000000"] + } + }, + "response": [] + } + ] + } + ] +} diff --git a/discodeit/src/main/java/com/sprint/mission/docs/Discodeit API.postman_test_run.json b/discodeit/src/main/java/com/sprint/mission/docs/Discodeit API.postman_test_run.json new file mode 100644 index 00000000..93f1dc66 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/docs/Discodeit API.postman_test_run.json @@ -0,0 +1,723 @@ +{ + "id": "e1564ef8-2dee-477e-86d7-0bf9833a25c2", + "name": "Discodeit API", + "timestamp": "2026-02-11T04:05:07.759Z", + "collection_id": "52123612-55251926-59b1-421c-9a2a-ee03fab096df", + "folder_id": 0, + "environment_id": "52123612-29d21f93-3998-4caf-b280-05ecc6f4e0de", + "totalPass": 25, + "delay": 0, + "persist": true, + "status": "finished", + "startedAt": "2026-02-11T04:05:06.896Z", + "totalFail": 21, + "results": [ + { + "id": "b2d33473-fb38-4502-981e-ea96de84f3c6", + "name": "๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ", + "url": "http://localhost:8080/users", + "time": 16, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": true, + "์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์— ํ•„์ˆ˜ ํ•„๋“œ ํฌํ•จ": true + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": { + "pass": 1, + "fail": 0 + }, + "์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์— ํ•„์ˆ˜ ํ•„๋“œ ํฌํ•จ": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 16 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": true, + "์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์— ํ•„์ˆ˜ ํ•„๋“œ ํฌํ•จ": true + } + ] + }, + { + "id": "279082f2-cd70-4070-910f-7e7c8421382f", + "name": "ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ (๊น€์ฒ ์ˆ˜)", + "url": "http://localhost:8080/users/10000000-0000-0000-0000-000000000001", + "time": 4, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์ผ์น˜": false, + "ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 0, + "fail": 1 + }, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": { + "pass": 1, + "fail": 0 + }, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์ผ์น˜": { + "pass": 0, + "fail": 1 + }, + "ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์ผ์น˜": false, + "ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ": false + } + ] + }, + { + "id": "b14033a0-47cb-4ac9-84e3-1772c8141bfc", + "name": "ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ (์ด์˜ํฌ)", + "url": "http://localhost:8080/users/20000000-0000-0000-0000-000000000001", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์ผ์น˜": true + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": { + "pass": 1, + "fail": 0 + }, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์ผ์น˜": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์ผ์น˜": true + } + ] + }, + { + "id": "39a0f803-5c50-4e94-9baa-b2d6a71ab587", + "name": "์‚ฌ์šฉ์ž ์ƒ์„ฑ (JSON)", + "url": "http://localhost:8080/users", + "time": 6, + "responseCode": { + "code": 400, + "name": "Bad Request" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200 ๋˜๋Š” 201": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ID ์กด์žฌ": false, + "์ด๋ฉ”์ผ์ด ์š”์ฒญํ•œ ๊ฐ’๊ณผ ์ผ์น˜": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200 ๋˜๋Š” 201": { + "pass": 0, + "fail": 1 + }, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": { + "pass": 1, + "fail": 0 + }, + "์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ID ์กด์žฌ": { + "pass": 0, + "fail": 1 + }, + "์ด๋ฉ”์ผ์ด ์š”์ฒญํ•œ ๊ฐ’๊ณผ ์ผ์น˜": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200 ๋˜๋Š” 201": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์— ID ์กด์žฌ": false, + "์ด๋ฉ”์ผ์ด ์š”์ฒญํ•œ ๊ฐ’๊ณผ ์ผ์น˜": false + } + ] + }, + { + "id": "ac47daac-22dd-45db-8ab6-186339b60e6b", + "name": "์‚ฌ์šฉ์ž ์ˆ˜์ • (๊น€์ฒ ์ˆ˜)", + "url": "http://localhost:8080/users/10000000-0000-0000-0000-000000000001", + "time": 6, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์œ ์ง€๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 0, + "fail": 1 + }, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": { + "pass": 1, + "fail": 0 + }, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์œ ์ง€๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "์‚ฌ์šฉ์ž ID๊ฐ€ ์œ ์ง€๋จ": false + } + ] + }, + { + "id": "0becf4c2-9274-4084-ae0c-bf1997917042", + "name": "์‚ฌ์šฉ์ž ์‚ญ์ œ", + "url": "http://localhost:8080/users/10000000-0000-0000-0000-000000000001", + "time": 7, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200 ๋˜๋Š” 204": false, + "์‚ญ์ œ ์„ฑ๊ณต": true + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200 ๋˜๋Š” 204": { + "pass": 0, + "fail": 1 + }, + "์‚ญ์ œ ์„ฑ๊ณต": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 7 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200 ๋˜๋Š” 204": false, + "์‚ญ์ œ ์„ฑ๊ณต": true + } + ] + }, + { + "id": "2ca9cb56-2ff5-48af-893a-ddf4144afb58", + "name": "๊น€์ฒ ์ˆ˜ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "url": "http://localhost:8080/users/10000000-0000-0000-0000-000000000001/status", + "time": 5, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 0, + "fail": 1 + }, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": { + "pass": 1, + "fail": 0 + }, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + } + ] + }, + { + "id": "728da0e0-bd0b-4983-b147-e0f7d4f604b3", + "name": "์ด์˜ํฌ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "url": "http://localhost:8080/users/20000000-0000-0000-0000-000000000001/status", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + } + ] + }, + { + "id": "2bf9253e-56b4-41b9-9dc1-9f8a11a051f0", + "name": "๋ฐ•๋ฏผ์ˆ˜ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "url": "http://localhost:8080/users/30000000-0000-0000-0000-000000000001/status", + "time": 9, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 9 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + } + ] + }, + { + "id": "6027e5fd-58ad-48bc-a623-93033ac3bb33", + "name": "์ตœ์ง€์—ฐ ์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + "url": "http://localhost:8080/users/40000000-0000-0000-0000-000000000001/status", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "lastAccessAt ํ•„๋“œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ": false + } + ] + }, + { + "id": "31c2d1d4-b67d-46c2-bc2a-3aa50bd207e1", + "name": "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ (๊น€์ฒ ์ˆ˜ ํ”„๋กœํ•„)", + "url": "http://localhost:8080/binary-contents/10000000-0000-0000-0000-000000000002", + "time": 5, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "ํŒŒ์ผ ID๊ฐ€ ์ผ์น˜": false, + "ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 0, + "fail": 1 + }, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": { + "pass": 1, + "fail": 0 + }, + "ํŒŒ์ผ ID๊ฐ€ ์ผ์น˜": { + "pass": 0, + "fail": 1 + }, + "ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "์‘๋‹ต์€ JSON ๊ฐ์ฒด": true, + "ํŒŒ์ผ ID๊ฐ€ ์ผ์น˜": false, + "ํ•„์ˆ˜ ํ•„๋“œ ์กด์žฌ": false + } + ] + }, + { + "id": "311a6d31-1dd7-4324-83ff-fd062d0c4cd4", + "name": "ํŒŒ์ผ 1๊ฐœ ์กฐํšŒ (์ด์˜ํฌ ํ”„๋กœํ•„)", + "url": "http://localhost:8080/binary-contents/20000000-0000-0000-0000-000000000002", + "time": 5, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "ํŒŒ์ผ ID๊ฐ€ ์ผ์น˜": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 0, + "fail": 1 + }, + "ํŒŒ์ผ ID๊ฐ€ ์ผ์น˜": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": false, + "ํŒŒ์ผ ID๊ฐ€ ์ผ์น˜": false + } + ] + }, + { + "id": "bdf9f815-c91c-4824-944e-a8fef290fef2", + "name": "ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ (๊น€์ฒ ์ˆ˜ + ์ด์˜ํฌ)", + "url": "http://localhost:8080/binary-contents?ids=10000000-0000-0000-0000-000000000002,20000000-0000-0000-0000-000000000002", + "time": 8, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": true, + "2๊ฐœ์˜ ํŒŒ์ผ ์กฐํšŒ๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": { + "pass": 1, + "fail": 0 + }, + "2๊ฐœ์˜ ํŒŒ์ผ ์กฐํšŒ๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 8 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": true, + "2๊ฐœ์˜ ํŒŒ์ผ ์กฐํšŒ๋จ": false + } + ] + }, + { + "id": "c79d76fc-f58d-43b4-a08e-906af1259905", + "name": "ํŒŒ์ผ ์—ฌ๋Ÿฌ๊ฐœ ์กฐํšŒ (์ „์ฒด)", + "url": "http://localhost:8080/binary-contents?ids=10000000-0000-0000-0000-000000000002,20000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000002,40000000-0000-0000-0000-000000000002", + "time": 6, + "responseCode": { + "code": 200, + "name": "OK" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": true, + "4๊ฐœ์˜ ํŒŒ์ผ ์กฐํšŒ๋จ": false + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": { + "pass": 1, + "fail": 0 + }, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": { + "pass": 1, + "fail": 0 + }, + "4๊ฐœ์˜ ํŒŒ์ผ ์กฐํšŒ๋จ": { + "pass": 0, + "fail": 1 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 200": true, + "์‘๋‹ต์€ JSON ๋ฐฐ์—ด": true, + "4๊ฐœ์˜ ํŒŒ์ผ ์กฐํšŒ๋จ": false + } + ] + }, + { + "id": "b88c2929-0801-4dce-a8a6-ce327ad78f5f", + "name": "์ค‘๋ณต ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์ƒ์„ฑ (400 ์—๋Ÿฌ)", + "url": "http://localhost:8080/users", + "time": 5, + "responseCode": { + "code": 400, + "name": "Bad Request" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 400 (Bad Request)": true, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": true + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 400 (Bad Request)": { + "pass": 1, + "fail": 0 + }, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 5 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 400 (Bad Request)": true, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": true + } + ] + }, + { + "id": "672c6b2e-40e2-4bb1-9560-2590d19021bc", + "name": "์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์กฐํšŒ (404 ์—๋Ÿฌ)", + "url": "http://localhost:8080/users/00000000-0000-0000-0000-000000000000", + "time": 4, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 404 (Not Found)": true, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": true + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 404 (Not Found)": { + "pass": 1, + "fail": 0 + }, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 4 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 404 (Not Found)": true, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": true + } + ] + }, + { + "id": "245a3ab7-4bec-4eff-9464-d17e18227eef", + "name": "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ์กฐํšŒ (404 ์—๋Ÿฌ)", + "url": "http://localhost:8080/binary-contents/00000000-0000-0000-0000-000000000000", + "time": 6, + "responseCode": { + "code": 404, + "name": "Not Found" + }, + "tests": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 404 (Not Found)": true, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": true + }, + "testPassFailCounts": { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 404 (Not Found)": { + "pass": 1, + "fail": 0 + }, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": { + "pass": 1, + "fail": 0 + } + }, + "times": [ + 6 + ], + "allTests": [ + { + "์ƒํƒœ ์ฝ”๋“œ๋Š” 404 (Not Found)": true, + "์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํฌํ•จ": true + } + ] + } + ], + "count": 1, + "totalTime": 110, + "collection": { + "requests": [ + { + "id": "b2d33473-fb38-4502-981e-ea96de84f3c6", + "method": "GET" + }, + { + "id": "279082f2-cd70-4070-910f-7e7c8421382f", + "method": "GET" + }, + { + "id": "b14033a0-47cb-4ac9-84e3-1772c8141bfc", + "method": "GET" + }, + { + "id": "39a0f803-5c50-4e94-9baa-b2d6a71ab587", + "method": "POST" + }, + { + "id": "ac47daac-22dd-45db-8ab6-186339b60e6b", + "method": "PUT" + }, + { + "id": "0becf4c2-9274-4084-ae0c-bf1997917042", + "method": "DELETE" + }, + { + "id": "2ca9cb56-2ff5-48af-893a-ddf4144afb58", + "method": "PUT" + }, + { + "id": "728da0e0-bd0b-4983-b147-e0f7d4f604b3", + "method": "PUT" + }, + { + "id": "2bf9253e-56b4-41b9-9dc1-9f8a11a051f0", + "method": "PUT" + }, + { + "id": "6027e5fd-58ad-48bc-a623-93033ac3bb33", + "method": "PUT" + }, + { + "id": "31c2d1d4-b67d-46c2-bc2a-3aa50bd207e1", + "method": "GET" + }, + { + "id": "311a6d31-1dd7-4324-83ff-fd062d0c4cd4", + "method": "GET" + }, + { + "id": "bdf9f815-c91c-4824-944e-a8fef290fef2", + "method": "GET" + }, + { + "id": "c79d76fc-f58d-43b4-a08e-906af1259905", + "method": "GET" + }, + { + "id": "b88c2929-0801-4dce-a8a6-ce327ad78f5f", + "method": "POST" + }, + { + "id": "672c6b2e-40e2-4bb1-9560-2590d19021bc", + "method": "GET" + }, + { + "id": "245a3ab7-4bec-4eff-9464-d17e18227eef", + "method": "GET" + } + ] + } +} \ No newline at end of file diff --git a/discodeit/src/main/java/com/sprint/mission/docs/Discodeit.postman_environment.json b/discodeit/src/main/java/com/sprint/mission/docs/Discodeit.postman_environment.json new file mode 100644 index 00000000..9c5d1b84 --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/docs/Discodeit.postman_environment.json @@ -0,0 +1,111 @@ +{ + "id": "discodeit-environment", + "name": "Discodeit Environment", + "values": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "default", + "enabled": true + }, + { + "key": "user1Id", + "value": "10000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user1Name", + "value": "๊น€์ฒ ์ˆ˜", + "type": "default", + "enabled": true + }, + { + "key": "user1Email", + "value": "kimcs@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user1ProfileId", + "value": "10000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + }, + { + "key": "user2Id", + "value": "20000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user2Name", + "value": "์ด์˜ํฌ", + "type": "default", + "enabled": true + }, + { + "key": "user2Email", + "value": "leeyh@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user2ProfileId", + "value": "20000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + }, + { + "key": "user3Id", + "value": "30000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user3Name", + "value": "๋ฐ•๋ฏผ์ˆ˜", + "type": "default", + "enabled": true + }, + { + "key": "user3Email", + "value": "parkms@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user3ProfileId", + "value": "30000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + }, + { + "key": "user4Id", + "value": "40000000-0000-0000-0000-000000000001", + "type": "default", + "enabled": true + }, + { + "key": "user4Name", + "value": "์ตœ์ง€์—ฐ", + "type": "default", + "enabled": true + }, + { + "key": "user4Email", + "value": "choijy@codeit.com", + "type": "default", + "enabled": true + }, + { + "key": "user4ProfileId", + "value": "40000000-0000-0000-0000-000000000002", + "type": "default", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-02-11T00:33:00.000Z", + "_postman_exported_using": "Postman/11.0.0" +} diff --git a/discodeit/src/main/java/com/sprint/mission/docs/http-client.env.json b/discodeit/src/main/java/com/sprint/mission/docs/http-client.env.json new file mode 100644 index 00000000..c226451d --- /dev/null +++ b/discodeit/src/main/java/com/sprint/mission/docs/http-client.env.json @@ -0,0 +1,21 @@ +{ + "dev": { + "baseUrl": "http://localhost:8080", + "user1Id": "10000000-0000-0000-0000-000000000001", + "user1Name": "๊น€์ฒ ์ˆ˜", + "user1Email": "kimcs@codeit.com", + "user1ProfileId": "10000000-0000-0000-0000-000000000002", + "user2Id": "20000000-0000-0000-0000-000000000001", + "user2Name": "์ด์˜ํฌ", + "user2Email": "leeyh@codeit.com", + "user2ProfileId": "20000000-0000-0000-0000-000000000002", + "user3Id": "30000000-0000-0000-0000-000000000001", + "user3Name": "๋ฐ•๋ฏผ์ˆ˜", + "user3Email": "parkms@codeit.com", + "user3ProfileId": "30000000-0000-0000-0000-000000000002", + "user4Id": "40000000-0000-0000-0000-000000000001", + "user4Name": "์ตœ์ง€์—ฐ", + "user4Email": "choijy@codeit.com", + "user4ProfileId": "40000000-0000-0000-0000-000000000002" + } +} diff --git "a/discodeit/src/main/java/com/sprint/mission/docs/\354\212\244\355\201\254\353\246\260\354\203\267 2026-02-11 \354\230\244\354\240\204 10.58.33.png" "b/discodeit/src/main/java/com/sprint/mission/docs/\354\212\244\355\201\254\353\246\260\354\203\267 2026-02-11 \354\230\244\354\240\204 10.58.33.png" new file mode 100644 index 00000000..d07db6ab Binary files /dev/null and "b/discodeit/src/main/java/com/sprint/mission/docs/\354\212\244\355\201\254\353\246\260\354\203\267 2026-02-11 \354\230\244\354\240\204 10.58.33.png" differ diff --git a/discodeit/src/main/resources/data/profiles/choijy.jpg b/discodeit/src/main/resources/data/profiles/choijy.jpg new file mode 100644 index 00000000..6b42706b Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/choijy.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/choijy.png b/discodeit/src/main/resources/data/profiles/choijy.png new file mode 100644 index 00000000..f7993c7c Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/choijy.png differ diff --git a/discodeit/src/main/resources/data/profiles/hansj.jpg b/discodeit/src/main/resources/data/profiles/hansj.jpg new file mode 100644 index 00000000..9f504c4f Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/hansj.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/janghm.jpg b/discodeit/src/main/resources/data/profiles/janghm.jpg new file mode 100644 index 00000000..08b0c74d Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/janghm.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/jeonmh.jpg b/discodeit/src/main/resources/data/profiles/jeonmh.jpg new file mode 100644 index 00000000..d0c45b03 Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/jeonmh.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/kimcs.jpg b/discodeit/src/main/resources/data/profiles/kimcs.jpg new file mode 100644 index 00000000..9f504c4f Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/kimcs.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/kimcs.png b/discodeit/src/main/resources/data/profiles/kimcs.png new file mode 100644 index 00000000..5c496f25 Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/kimcs.png differ diff --git a/discodeit/src/main/resources/data/profiles/leeyh.jpg b/discodeit/src/main/resources/data/profiles/leeyh.jpg new file mode 100644 index 00000000..08b0c74d Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/leeyh.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/leeyh.png b/discodeit/src/main/resources/data/profiles/leeyh.png new file mode 100644 index 00000000..1e6a05d9 Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/leeyh.png differ diff --git a/discodeit/src/main/resources/data/profiles/parkms.jpg b/discodeit/src/main/resources/data/profiles/parkms.jpg new file mode 100644 index 00000000..d0c45b03 Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/parkms.jpg differ diff --git a/discodeit/src/main/resources/data/profiles/parkms.png b/discodeit/src/main/resources/data/profiles/parkms.png new file mode 100644 index 00000000..0dd39241 Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/parkms.png differ diff --git a/discodeit/src/main/resources/data/profiles/seohh.jpg b/discodeit/src/main/resources/data/profiles/seohh.jpg new file mode 100644 index 00000000..6b42706b Binary files /dev/null and b/discodeit/src/main/resources/data/profiles/seohh.jpg differ diff --git a/discodeit/src/main/resources/data/users.json b/discodeit/src/main/resources/data/users.json new file mode 100644 index 00000000..becfab9e --- /dev/null +++ b/discodeit/src/main/resources/data/users.json @@ -0,0 +1,34 @@ +[ + { + "id": "10000000-0000-0000-0000-000000000001", + "username": "๊น€์ฒ ์ˆ˜", + "email": "kimcs@codeit.com", + "password": "password123", + "profileImage": "kimcs.png", + "profileId": "10000000-0000-0000-0000-000000000002" + }, + { + "id": "20000000-0000-0000-0000-000000000001", + "username": "์ด์˜ํฌ", + "email": "leeyh@codeit.com", + "password": "password123", + "profileImage": "leeyh.png", + "profileId": "20000000-0000-0000-0000-000000000002" + }, + { + "id": "30000000-0000-0000-0000-000000000001", + "username": "๋ฐ•๋ฏผ์ˆ˜", + "email": "parkms@codeit.com", + "password": "password123", + "profileImage": "parkms.png", + "profileId": "30000000-0000-0000-0000-000000000002" + }, + { + "id": "40000000-0000-0000-0000-000000000001", + "username": "์ตœ์ง€์—ฐ", + "email": "choijy@codeit.com", + "password": "password123", + "profileImage": "choijy.png", + "profileId": "40000000-0000-0000-0000-000000000002" + } +] diff --git a/discodeit/src/main/resources/static/index.html b/discodeit/src/main/resources/static/index.html new file mode 100644 index 00000000..6eb6f3af --- /dev/null +++ b/discodeit/src/main/resources/static/index.html @@ -0,0 +1,13 @@ + + + + + + Discodeit + + + + + diff --git a/discodeit/src/main/resources/static/script.js b/discodeit/src/main/resources/static/script.js new file mode 100644 index 00000000..f1c0c9af --- /dev/null +++ b/discodeit/src/main/resources/static/script.js @@ -0,0 +1,107 @@ +// API endpoints +const ENDPOINTS = { + USERS: '/users' +}; + +// Initialize the application +document.addEventListener('DOMContentLoaded', () => { + fetchAndRenderUsers(); +}); + +// Fetch users from the API +async function fetchAndRenderUsers() { + try { + const response = await fetch(ENDPOINTS.USERS); + if (!response.ok) throw new Error('Failed to fetch users'); + const users = await response.json(); + renderUserList(users); + } catch (error) { + console.error('Error fetching users:', error); + } +} + +// Fetch user profile image +async function fetchUserProfile(profileId) { + try { + const response = await fetch(`/binary-contents/${profileId}`); + if (!response.ok) throw new Error('Failed to fetch profile'); + const profile = await response.json(); + + // Data is already base64 encoded string + return `data:${profile.contentType};base64,${profile.data}`; + } catch (error) { + console.error('Error fetching profile:', error); + return '/default-avatar.png'; // Fallback to default avatar + } +} + +// Render user list +async function renderUserList(users) { + const userListElement = document.getElementById('userList'); + userListElement.innerHTML = ''; // Clear existing content + + if (users.length === 0) { + userListElement.innerHTML = '

๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

'; + return; + } + + for (const user of users) { + const userElement = document.createElement('div'); + userElement.className = 'user-item'; + + // Get profile image URL + const profileUrl = user.profileId ? + await fetchUserProfile(user.profileId) : + '/default-avatar.png'; + + userElement.innerHTML = ` +
+ ${user.username} + +
+ ${user.online ? '์˜จ๋ผ์ธ' : '์˜คํ”„๋ผ์ธ'} +
+
+ + `; + + userListElement.appendChild(userElement); + } +} + +// Edit user +function editUser(userId) { + window.location.href = `/user-edit.html?id=${userId}`; +} + +// Delete user +async function deleteUser(userId, username) { + if (!confirm(`์ •๋ง๋กœ "${username}" ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?`)) { + return; + } + + try { + const response = await fetch(`/users/${userId}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error(`Failed to delete user: ${response.status}`); + } + + alert('โœ… ์‚ฌ์šฉ์ž๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); + + // Reload user list + fetchAndRenderUsers(); + + } catch (error) { + console.error('Error deleting user:', error); + alert('โŒ ์‚ฌ์šฉ์ž ์‚ญ์ œ ์‹คํŒจ: ' + error.message); + } +} diff --git a/discodeit/src/main/resources/static/styles.css b/discodeit/src/main/resources/static/styles.css new file mode 100644 index 00000000..57a53d2d --- /dev/null +++ b/discodeit/src/main/resources/static/styles.css @@ -0,0 +1,80 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; +} + +.container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +h1 { + text-align: center; + margin-bottom: 30px; + color: #333; +} + +.user-list { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.user-item { + display: flex; + align-items: center; + padding: 20px; + border-bottom: 1px solid #eee; +} + +.user-item:last-child { + border-bottom: none; +} + +.user-avatar { + width: 60px; + height: 60px; + border-radius: 50%; + margin-right: 20px; + object-fit: cover; +} + +.user-info { + flex-grow: 1; +} + +.user-name { + font-size: 18px; + font-weight: bold; + color: #333; + margin-bottom: 5px; +} + +.user-email { + font-size: 14px; + color: #666; +} + +.status-badge { + padding: 6px 12px; + border-radius: 20px; + font-size: 14px; + font-weight: bold; +} + +.online { + background-color: #4CAF50; + color: white; +} + +.offline { + background-color: #9e9e9e; + color: white; +} diff --git a/discodeit/src/main/resources/static/user-create.html b/discodeit/src/main/resources/static/user-create.html new file mode 100644 index 00000000..ed3ad0b8 --- /dev/null +++ b/discodeit/src/main/resources/static/user-create.html @@ -0,0 +1,409 @@ + + + + + + ์‚ฌ์šฉ์ž ๋“ฑ๋ก + + + +
+
+

๐Ÿ‘ค ์‚ฌ์šฉ์ž ๋“ฑ๋ก

+

์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค

+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +

์ตœ์†Œ 6์ž ์ด์ƒ ์ž…๋ ฅํ•˜์„ธ์š”

+
+ +
+ +
+ + +
+

์„ ํƒ๋œ ํŒŒ์ผ ์—†์Œ

+
+ ๋ฏธ๋ฆฌ๋ณด๊ธฐ +
+
+ +
+ + +
+
+
+
+ + + + diff --git a/discodeit/src/main/resources/static/user-edit.html b/discodeit/src/main/resources/static/user-edit.html new file mode 100644 index 00000000..13b913f7 --- /dev/null +++ b/discodeit/src/main/resources/static/user-edit.html @@ -0,0 +1,498 @@ + + + + + + ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • + + + +
+
+
+
+

์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

+
+ + +
+
+ + + + diff --git a/discodeit/src/main/resources/static/user-list.html b/discodeit/src/main/resources/static/user-list.html new file mode 100644 index 00000000..738218e2 --- /dev/null +++ b/discodeit/src/main/resources/static/user-list.html @@ -0,0 +1,264 @@ + + + + + + ์‚ฌ์šฉ์ž ๋ชฉ๋ก + + + +
+
+

์‚ฌ์šฉ์ž ๋ชฉ๋ก

+
+
+ ๋กœ๋”ฉ ์ค‘... +
+ +
+ +
+
+ + + diff --git a/discodeit/src/test/java/com/sprint/mission/discodeit/service/FunctionalTest.java b/discodeit/src/test/java/com/sprint/mission/discodeit/service/FunctionalTest.java new file mode 100644 index 00000000..b2b35d0f --- /dev/null +++ b/discodeit/src/test/java/com/sprint/mission/discodeit/service/FunctionalTest.java @@ -0,0 +1,729 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.request.*; +import com.sprint.mission.discodeit.dto.response.ChannelResponse; +import com.sprint.mission.discodeit.dto.response.MessageResponse; +import com.sprint.mission.discodeit.dto.response.UserResponse; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.jcf.*; +import com.sprint.mission.discodeit.service.basic.*; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.UUID; + +/** + * ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ - ์ฝ˜์†” ๋ฐ ํŒŒ์ผ ์ถœ๋ ฅ + * + * ๋ชจ๋“  ์š”๊ตฌ์‚ฌํ•ญ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ฝ˜์†”๊ณผ ํŒŒ์ผ์— ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. + */ +public class FunctionalTest { + + // Repositories + private final JCFUserRepository userRepository; + private final JCFUserStatusRepository userStatusRepository; + private final JCFBinaryContentRepository binaryContentRepository; + private final JCFChannelRepository channelRepository; + private final JCFMessageRepository messageRepository; + private final JCFReadStatusRepository readStatusRepository; + + // Services + private final UserService userService; + private final AuthService authService; + private final UserStatusService userStatusService; + private final ChannelService channelService; + private final MessageService messageService; + private final ReadStatusService readStatusService; + private final BinaryContentService binaryContentService; + + // Output + private final StringBuilder outputBuffer; + private int testCount = 0; + private int passCount = 0; + private int failCount = 0; + + public FunctionalTest() { + // Repository ์ดˆ๊ธฐํ™” + userRepository = new JCFUserRepository(); + userStatusRepository = new JCFUserStatusRepository(); + binaryContentRepository = new JCFBinaryContentRepository(); + channelRepository = new JCFChannelRepository(); + messageRepository = new JCFMessageRepository(); + readStatusRepository = new JCFReadStatusRepository(); + + // Service ์ดˆ๊ธฐํ™” + userService = new BasicUserService(userRepository, binaryContentRepository, userStatusRepository); + authService = new BasicAuthService(userRepository, userStatusRepository); + userStatusService = new BasicUserStatusService(userStatusRepository, userRepository); + channelService = new BasicChannelService(channelRepository, readStatusRepository, messageRepository, binaryContentRepository); + messageService = new BasicMessageService(messageRepository, binaryContentRepository); + readStatusService = new BasicReadStatusService(readStatusRepository, userRepository, channelRepository); + binaryContentService = new BasicBinaryContentService(binaryContentRepository); + + outputBuffer = new StringBuilder(); + } + + public static void main(String[] args) { + FunctionalTest test = new FunctionalTest(); + test.runAllTests(); + } + + private void runAllTests() { + printHeader("DISCODEIT ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ"); + printLine("ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์‹œ๊ฐ„: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + printSeparator(); + + // ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ + printSection("1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ"); + testUserCreate(); + testUserUpdate(); + testUserDelete(); + testUserFindAll(); + testUserStatusUpdate(); + + // ๊ถŒํ•œ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ + printSection("2. ๊ถŒํ•œ ๊ด€๋ฆฌ"); + testLogin(); + testLoginFail(); + + // ์ฑ„๋„ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ + printSection("3. ์ฑ„๋„ ๊ด€๋ฆฌ"); + testPublicChannelCreate(); + testPrivateChannelCreate(); + testPublicChannelUpdate(); + testPrivateChannelUpdateFail(); + testChannelDelete(); + testFindChannelsByUserId(); + + // ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ + printSection("4. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ"); + testMessageCreate(); + testMessageUpdate(); + testMessageDelete(); + testFindMessagesByChannelId(); + + // ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ + printSection("5. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ"); + testReadStatusCreate(); + testReadStatusUpdate(); + testFindReadStatusByUserId(); + + // ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ํ…Œ์ŠคํŠธ + printSection("6. ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ"); + testBinaryContentFindOne(); + testBinaryContentFindMultiple(); + + // ๊ฒฐ๊ณผ ์š”์•ฝ + printSummary(); + + // ํŒŒ์ผ๋กœ ์ €์žฅ + saveToFile(); + } + + // ==================== ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + private void testUserCreate() { + String testName = "์‚ฌ์šฉ์ž๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + UserCreateRequest request = new UserCreateRequest("ํ™๊ธธ๋™", "hong@example.com", "password123"); + UserResponse response = userService.create(request, null); + + printTestResult(testName, true); + printDetail("์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ID: " + response.id()); + printDetail("์‚ฌ์šฉ์ž๋ช…: " + response.username()); + printDetail("์ด๋ฉ”์ผ: " + response.email()); + printDetail("์˜จ๋ผ์ธ ์ƒํƒœ: " + response.isOnline()); + printDetail("์ƒ์„ฑ์ผ์‹œ: " + response.createdAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testUserUpdate() { + String testName = "์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ๋จผ์ € ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserCreateRequest createRequest = new UserCreateRequest("๊น€์ฒ ์ˆ˜", "kim@example.com", "pass123"); + UserResponse createdUser = userService.create(createRequest, null); + + // ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • + UserUpdateRequest updateRequest = new UserUpdateRequest("๊น€์˜ํฌ", "younghee@example.com", "newpass456"); + UserResponse updatedUser = userService.update(createdUser.id(), updateRequest, null); + + printTestResult(testName, true); + printDetail("์ˆ˜์ • ์ „ ์‚ฌ์šฉ์ž๋ช…: ๊น€์ฒ ์ˆ˜ โ†’ ์ˆ˜์ • ํ›„: " + updatedUser.username()); + printDetail("์ˆ˜์ • ์ „ ์ด๋ฉ”์ผ: kim@example.com โ†’ ์ˆ˜์ • ํ›„: " + updatedUser.email()); + printDetail("์ˆ˜์ •์ผ์‹œ: " + updatedUser.updatedAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testUserDelete() { + String testName = "์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserCreateRequest request = new UserCreateRequest("์‚ญ์ œ๋ ์‚ฌ์šฉ์ž", "delete@example.com", "pass"); + UserResponse createdUser = userService.create(request, null); + UUID userId = createdUser.id(); + + // ์‚ญ์ œ ์ „ ์‚ฌ์šฉ์ž ์ˆ˜ + int beforeCount = userService.findAll().size(); + + // ์‚ฌ์šฉ์ž ์‚ญ์ œ + userService.delete(userId); + + // ์‚ญ์ œ ํ›„ ์‚ฌ์šฉ์ž ์ˆ˜ + int afterCount = userService.findAll().size(); + + printTestResult(testName, true); + printDetail("์‚ญ์ œ๋œ ์‚ฌ์šฉ์ž ID: " + userId); + printDetail("์‚ญ์ œ ์ „ ์‚ฌ์šฉ์ž ์ˆ˜: " + beforeCount + " โ†’ ์‚ญ์ œ ํ›„: " + afterCount); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testUserFindAll() { + String testName = "๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์ถ”๊ฐ€ ์‚ฌ์šฉ์ž ์ƒ์„ฑ + userService.create(new UserCreateRequest("์‚ฌ์šฉ์žA", "userA@example.com", "pass"), null); + userService.create(new UserCreateRequest("์‚ฌ์šฉ์žB", "userB@example.com", "pass"), null); + + List allUsers = userService.findAll(); + + printTestResult(testName, true); + printDetail("์ „์ฒด ์‚ฌ์šฉ์ž ์ˆ˜: " + allUsers.size()); + for (UserResponse user : allUsers) { + printDetail(" - " + user.username() + " (" + user.email() + ") - ์˜จ๋ผ์ธ: " + user.isOnline()); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testUserStatusUpdate() { + String testName = "์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserCreateRequest request = new UserCreateRequest("์ƒํƒœํ…Œ์ŠคํŠธ", "status@example.com", "pass"); + UserResponse user = userService.create(request, null); + + // UserStatus ์กฐํšŒ + UserStatus userStatus = userStatusService.findByUserId(user.id()); + Instant beforeTime = userStatus.getLastActiveAt(); + + // ์ƒํƒœ ์—…๋ฐ์ดํŠธ + Thread.sleep(100); // ์‹œ๊ฐ„ ์ฐจ์ด๋ฅผ ์œ„ํ•ด + Instant newTime = Instant.now(); + UserStatus updatedStatus = userStatusService.update(userStatus.getId(), new UserStatusUpdateRequest(newTime)); + + printTestResult(testName, true); + printDetail("์‚ฌ์šฉ์ž ID: " + user.id()); + printDetail("์—…๋ฐ์ดํŠธ ์ „ ๋งˆ์ง€๋ง‰ ํ™œ๋™: " + beforeTime); + printDetail("์—…๋ฐ์ดํŠธ ํ›„ ๋งˆ์ง€๋ง‰ ํ™œ๋™: " + updatedStatus.getLastActiveAt()); + printDetail("์˜จ๋ผ์ธ ์ƒํƒœ: " + updatedStatus.isOnline()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + // ==================== ๊ถŒํ•œ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + private void testLogin() { + String testName = "์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ๋กœ๊ทธ์ธํ•  ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserCreateRequest createRequest = new UserCreateRequest("๋กœ๊ทธ์ธ์‚ฌ์šฉ์ž", "login@example.com", "mypassword"); + userService.create(createRequest, null); + + // ๋กœ๊ทธ์ธ ์‹œ๋„ + LoginRequest loginRequest = new LoginRequest("๋กœ๊ทธ์ธ์‚ฌ์šฉ์ž", "mypassword"); + UserResponse loggedInUser = authService.login(loginRequest); + + printTestResult(testName, true); + printDetail("๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž: " + loggedInUser.username()); + printDetail("์ด๋ฉ”์ผ: " + loggedInUser.email()); + printDetail("์‚ฌ์šฉ์ž ID: " + loggedInUser.id()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testLoginFail() { + String testName = "์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ ์‹œ ์‹คํŒจํ•œ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserCreateRequest createRequest = new UserCreateRequest("์‹คํŒจํ…Œ์ŠคํŠธ", "fail@example.com", "correctpass"); + userService.create(createRequest, null); + + // ์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ ์‹œ๋„ + LoginRequest loginRequest = new LoginRequest("์‹คํŒจํ…Œ์ŠคํŠธ", "wrongpassword"); + try { + authService.login(loginRequest); + printTestResult(testName, false); + printDetail("์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•˜๋Š”๋ฐ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ"); + } catch (Exception e) { + printTestResult(testName, true); + printDetail("์˜ˆ์ƒ๋Œ€๋กœ ๋กœ๊ทธ์ธ ์‹คํŒจ: " + e.getMessage()); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + // ==================== ์ฑ„๋„ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + private void testPublicChannelCreate() { + String testName = "๊ณต๊ฐœ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + PublicChannelCreateRequest request = new PublicChannelCreateRequest("์ผ๋ฐ˜", "์ผ๋ฐ˜ ๋Œ€ํ™” ์ฑ„๋„์ž…๋‹ˆ๋‹ค"); + ChannelResponse response = channelService.createPublic(request); + + printTestResult(testName, true); + printDetail("์ฑ„๋„ ID: " + response.id()); + printDetail("์ฑ„๋„ ํƒ€์ž…: " + response.type()); + printDetail("์ฑ„๋„๋ช…: " + response.name()); + printDetail("์„ค๋ช…: " + response.description()); + printDetail("์ƒ์„ฑ์ผ์‹œ: " + response.createdAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testPrivateChannelCreate() { + String testName = "๋น„๊ณต๊ฐœ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์ฐธ์—ฌ์ž ์ƒ์„ฑ + UserResponse user1 = userService.create(new UserCreateRequest("๋น„๊ณต๊ฐœ1", "private1@example.com", "pass"), null); + UserResponse user2 = userService.create(new UserCreateRequest("๋น„๊ณต๊ฐœ2", "private2@example.com", "pass"), null); + + PrivateChannelCreateRequest request = new PrivateChannelCreateRequest(List.of(user1.id(), user2.id())); + ChannelResponse response = channelService.createPrivate(request); + + printTestResult(testName, true); + printDetail("์ฑ„๋„ ID: " + response.id()); + printDetail("์ฑ„๋„ ํƒ€์ž…: " + response.type()); + printDetail("์ฐธ์—ฌ์ž ์ˆ˜: " + response.participantIds().size()); + printDetail("์ฐธ์—ฌ์ž ID ๋ชฉ๋ก: " + response.participantIds()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testPublicChannelUpdate() { + String testName = "๊ณต๊ฐœ ์ฑ„๋„์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์ฑ„๋„ ์ƒ์„ฑ + PublicChannelCreateRequest createRequest = new PublicChannelCreateRequest("์ˆ˜์ •์ „์ฑ„๋„", "์ˆ˜์ • ์ „ ์„ค๋ช…"); + ChannelResponse createdChannel = channelService.createPublic(createRequest); + + // ์ฑ„๋„ ์ˆ˜์ • + ChannelUpdateRequest updateRequest = new ChannelUpdateRequest("์ˆ˜์ •ํ›„์ฑ„๋„", "์ˆ˜์ • ํ›„ ์„ค๋ช…์ž…๋‹ˆ๋‹ค"); + ChannelResponse updatedChannel = channelService.update(createdChannel.id(), updateRequest); + + printTestResult(testName, true); + printDetail("์ˆ˜์ • ์ „ ์ฑ„๋„๋ช…: ์ˆ˜์ •์ „์ฑ„๋„ โ†’ ์ˆ˜์ • ํ›„: " + updatedChannel.name()); + printDetail("์ˆ˜์ • ์ „ ์„ค๋ช…: ์ˆ˜์ • ์ „ ์„ค๋ช… โ†’ ์ˆ˜์ • ํ›„: " + updatedChannel.description()); + printDetail("์ˆ˜์ •์ผ์‹œ: " + updatedChannel.updatedAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testPrivateChannelUpdateFail() { + String testName = "๋น„๊ณต๊ฐœ ์ฑ„๋„์€ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค"; + try { + // ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("์ˆ˜์ •๋ถˆ๊ฐ€", "noupdate@example.com", "pass"), null); + PrivateChannelCreateRequest createRequest = new PrivateChannelCreateRequest(List.of(user.id())); + ChannelResponse privateChannel = channelService.createPrivate(createRequest); + + // ์ˆ˜์ • ์‹œ๋„ + ChannelUpdateRequest updateRequest = new ChannelUpdateRequest("์ˆ˜์ •์‹œ๋„", "์ˆ˜์ • ์‹œ๋„ ์„ค๋ช…"); + try { + channelService.update(privateChannel.id(), updateRequest); + printTestResult(testName, false); + printDetail("์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผ ํ•˜๋Š”๋ฐ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ"); + } catch (IllegalArgumentException e) { + printTestResult(testName, true); + printDetail("์˜ˆ์ƒ๋Œ€๋กœ ์ˆ˜์ • ์‹คํŒจ: " + e.getMessage()); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testChannelDelete() { + String testName = "์ฑ„๋„์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์ฑ„๋„ ์ƒ์„ฑ + PublicChannelCreateRequest request = new PublicChannelCreateRequest("์‚ญ์ œ๋ ์ฑ„๋„", "๊ณง ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค"); + ChannelResponse createdChannel = channelService.createPublic(request); + UUID channelId = createdChannel.id(); + + // ์ฑ„๋„ ์‚ญ์ œ + channelService.delete(channelId); + + printTestResult(testName, true); + printDetail("์‚ญ์ œ๋œ ์ฑ„๋„ ID: " + channelId); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testFindChannelsByUserId() { + String testName = "ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ฑ„๋„ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž ์ƒ์„ฑ + UserResponse user1 = userService.create(new UserCreateRequest("์ฑ„๋„์กฐํšŒ1", "channel1@example.com", "pass"), null); + UserResponse user2 = userService.create(new UserCreateRequest("์ฑ„๋„์กฐํšŒ2", "channel2@example.com", "pass"), null); + + // ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + channelService.createPublic(new PublicChannelCreateRequest("๊ณต๊ฐœ์ฑ„๋„A", "๊ณต๊ฐœA")); + channelService.createPublic(new PublicChannelCreateRequest("๊ณต๊ฐœ์ฑ„๋„B", "๊ณต๊ฐœB")); + + // ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ (user1๋งŒ ์ฐธ์—ฌ) + channelService.createPrivate(new PrivateChannelCreateRequest(List.of(user1.id()))); + + // ๊ฐ ์‚ฌ์šฉ์ž๋ณ„ ์ฑ„๋„ ์กฐํšŒ + List user1Channels = channelService.findAllByUserId(user1.id()); + List user2Channels = channelService.findAllByUserId(user2.id()); + + printTestResult(testName, true); + printDetail("์‚ฌ์šฉ์ž1 (๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ฐธ์—ฌ) ์กฐํšŒ ๊ฐ€๋Šฅ ์ฑ„๋„ ์ˆ˜: " + user1Channels.size()); + for (ChannelResponse ch : user1Channels) { + printDetail(" - [" + ch.type() + "] " + (ch.name() != null ? ch.name() : "๋น„๊ณต๊ฐœ์ฑ„๋„")); + } + printDetail("์‚ฌ์šฉ์ž2 (๋น„๊ณต๊ฐœ ์ฑ„๋„ ๋ฏธ์ฐธ์—ฌ) ์กฐํšŒ ๊ฐ€๋Šฅ ์ฑ„๋„ ์ˆ˜: " + user2Channels.size()); + for (ChannelResponse ch : user2Channels) { + printDetail(" - [" + ch.type() + "] " + (ch.name() != null ? ch.name() : "๋น„๊ณต๊ฐœ์ฑ„๋„")); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + // ==================== ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + private void testMessageCreate() { + String testName = "๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("๋ฉ”์‹œ์ง€์ž‘์„ฑ์ž", "msg@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("๋ฉ”์‹œ์ง€์ฑ„๋„", "๋ฉ”์‹œ์ง€ ํ…Œ์ŠคํŠธ")); + + // ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ + MessageCreateRequest request = new MessageCreateRequest("์•ˆ๋…•ํ•˜์„ธ์š”! ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.", channel.id(), user.id()); + MessageResponse response = messageService.create(request, null); + + printTestResult(testName, true); + printDetail("๋ฉ”์‹œ์ง€ ID: " + response.id()); + printDetail("๋‚ด์šฉ: " + response.content()); + printDetail("์ž‘์„ฑ์ž ID: " + response.authorId()); + printDetail("์ฑ„๋„ ID: " + response.channelId()); + printDetail("์ž‘์„ฑ์ผ์‹œ: " + response.createdAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testMessageUpdate() { + String testName = "๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("๋ฉ”์‹œ์ง€์ˆ˜์ •์ž", "msgupdate@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("์ˆ˜์ •ํ…Œ์ŠคํŠธ์ฑ„๋„", "์ˆ˜์ • ํ…Œ์ŠคํŠธ")); + + // ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ + MessageCreateRequest createRequest = new MessageCreateRequest("์›๋ž˜ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ", channel.id(), user.id()); + MessageResponse createdMessage = messageService.create(createRequest, null); + + // ๋ฉ”์‹œ์ง€ ์ˆ˜์ • + MessageUpdateRequest updateRequest = new MessageUpdateRequest("์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค!"); + MessageResponse updatedMessage = messageService.update(createdMessage.id(), updateRequest); + + printTestResult(testName, true); + printDetail("์ˆ˜์ • ์ „: ์›๋ž˜ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ"); + printDetail("์ˆ˜์ • ํ›„: " + updatedMessage.content()); + printDetail("์ˆ˜์ •์ผ์‹œ: " + updatedMessage.updatedAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testMessageDelete() { + String testName = "๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("๋ฉ”์‹œ์ง€์‚ญ์ œ์ž", "msgdelete@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("์‚ญ์ œํ…Œ์ŠคํŠธ์ฑ„๋„", "์‚ญ์ œ ํ…Œ์ŠคํŠธ")); + + // ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ + MessageCreateRequest request = new MessageCreateRequest("์‚ญ์ œ๋  ๋ฉ”์‹œ์ง€", channel.id(), user.id()); + MessageResponse createdMessage = messageService.create(request, null); + UUID messageId = createdMessage.id(); + + // ์‚ญ์ œ ์ „ ๋ฉ”์‹œ์ง€ ์ˆ˜ + int beforeCount = messageService.findAllByChannelId(channel.id()).size(); + + // ๋ฉ”์‹œ์ง€ ์‚ญ์ œ + messageService.delete(messageId); + + // ์‚ญ์ œ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜ + int afterCount = messageService.findAllByChannelId(channel.id()).size(); + + printTestResult(testName, true); + printDetail("์‚ญ์ œ๋œ ๋ฉ”์‹œ์ง€ ID: " + messageId); + printDetail("์‚ญ์ œ ์ „ ๋ฉ”์‹œ์ง€ ์ˆ˜: " + beforeCount + " โ†’ ์‚ญ์ œ ํ›„: " + afterCount); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testFindMessagesByChannelId() { + String testName = "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("๋ฉ”์‹œ์ง€๋ชฉ๋ก", "msglist@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("๋ชฉ๋กํ…Œ์ŠคํŠธ์ฑ„๋„", "๋ชฉ๋ก ํ…Œ์ŠคํŠธ")); + + // ์—ฌ๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ + messageService.create(new MessageCreateRequest("์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€", channel.id(), user.id()), null); + messageService.create(new MessageCreateRequest("๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€", channel.id(), user.id()), null); + messageService.create(new MessageCreateRequest("์„ธ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€", channel.id(), user.id()), null); + + // ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ + List messages = messageService.findAllByChannelId(channel.id()); + + printTestResult(testName, true); + printDetail("์ฑ„๋„ ID: " + channel.id()); + printDetail("๋ฉ”์‹œ์ง€ ์ˆ˜: " + messages.size()); + for (MessageResponse msg : messages) { + printDetail(" - " + msg.content() + " (์ž‘์„ฑ: " + msg.createdAt() + ")"); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + // ==================== ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + private void testReadStatusCreate() { + String testName = "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("์ˆ˜์‹ ์ •๋ณด์ƒ์„ฑ", "readcreate@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("์ˆ˜์‹ ์ •๋ณด์ฑ„๋„", "์ˆ˜์‹  ์ •๋ณด ํ…Œ์ŠคํŠธ")); + + // ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ + Instant now = Instant.now(); + ReadStatusCreateRequest request = new ReadStatusCreateRequest(user.id(), channel.id(), now); + ReadStatus readStatus = readStatusService.create(request); + + printTestResult(testName, true); + printDetail("์ˆ˜์‹  ์ •๋ณด ID: " + readStatus.getId()); + printDetail("์‚ฌ์šฉ์ž ID: " + readStatus.getUserId()); + printDetail("์ฑ„๋„ ID: " + readStatus.getChannelId()); + printDetail("๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„: " + readStatus.getLastReadAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testReadStatusUpdate() { + String testName = "ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("์ˆ˜์‹ ์ •๋ณด์ˆ˜์ •", "readupdate@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("์ˆ˜์‹ ์ˆ˜์ •์ฑ„๋„", "์ˆ˜์‹  ์ˆ˜์ • ํ…Œ์ŠคํŠธ")); + + // ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ + Instant oldTime = Instant.now().minusSeconds(3600); + ReadStatusCreateRequest createRequest = new ReadStatusCreateRequest(user.id(), channel.id(), oldTime); + ReadStatus createdReadStatus = readStatusService.create(createRequest); + + // ์ˆ˜์‹  ์ •๋ณด ์ˆ˜์ • + Instant newTime = Instant.now(); + ReadStatusUpdateRequest updateRequest = new ReadStatusUpdateRequest(newTime); + ReadStatus updatedReadStatus = readStatusService.update(createdReadStatus.getId(), updateRequest); + + printTestResult(testName, true); + printDetail("์ˆ˜์ • ์ „ ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„: " + oldTime); + printDetail("์ˆ˜์ • ํ›„ ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„: " + updatedReadStatus.getLastReadAt()); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testFindReadStatusByUserId() { + String testName = "ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์‚ฌ์šฉ์ž์™€ ์ฑ„๋„ ์ƒ์„ฑ + UserResponse user = userService.create(new UserCreateRequest("์ˆ˜์‹ ์ •๋ณด์กฐํšŒ", "readfind@example.com", "pass"), null); + ChannelResponse channel1 = channelService.createPublic(new PublicChannelCreateRequest("์ˆ˜์‹ ์กฐํšŒ์ฑ„๋„1", "์ฑ„๋„1")); + ChannelResponse channel2 = channelService.createPublic(new PublicChannelCreateRequest("์ˆ˜์‹ ์กฐํšŒ์ฑ„๋„2", "์ฑ„๋„2")); + + // ์ˆ˜์‹  ์ •๋ณด ์ƒ์„ฑ + readStatusService.create(new ReadStatusCreateRequest(user.id(), channel1.id(), Instant.now())); + readStatusService.create(new ReadStatusCreateRequest(user.id(), channel2.id(), Instant.now())); + + // ์ˆ˜์‹  ์ •๋ณด ์กฐํšŒ + List readStatuses = readStatusService.findAllByUserId(user.id()); + + printTestResult(testName, true); + printDetail("์‚ฌ์šฉ์ž ID: " + user.id()); + printDetail("์ˆ˜์‹  ์ •๋ณด ์ˆ˜: " + readStatuses.size()); + for (ReadStatus rs : readStatuses) { + printDetail(" - ์ฑ„๋„ ID: " + rs.getChannelId() + ", ๋งˆ์ง€๋ง‰ ์ฝ์€ ์‹œ๊ฐ„: " + rs.getLastReadAt()); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + // ==================== ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ํ…Œ์ŠคํŠธ ==================== + + private void testBinaryContentFindOne() { + String testName = "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ์„ 1๊ฐœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ + byte[] data = "ํ…Œ์ŠคํŠธ ํŒŒ์ผ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.".getBytes(); + BinaryContentCreateRequest request = new BinaryContentCreateRequest("test.txt", "text/plain", data); + BinaryContent createdContent = binaryContentService.create(request); + + // ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์กฐํšŒ + BinaryContent foundContent = binaryContentService.find(createdContent.getId()); + + printTestResult(testName, true); + printDetail("ํŒŒ์ผ ID: " + foundContent.getId()); + printDetail("ํŒŒ์ผ๋ช…: " + foundContent.getFileName()); + printDetail("Content-Type: " + foundContent.getContentType()); + printDetail("๋ฐ์ดํ„ฐ ํฌ๊ธฐ: " + foundContent.getData().length + " bytes"); + printDetail("๋ฐ์ดํ„ฐ ๋‚ด์šฉ: " + new String(foundContent.getData())); + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + private void testBinaryContentFindMultiple() { + String testName = "๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ์„ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค"; + try { + // ์—ฌ๋Ÿฌ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ + BinaryContent content1 = binaryContentService.create( + new BinaryContentCreateRequest("file1.txt", "text/plain", "๋‚ด์šฉ1".getBytes())); + BinaryContent content2 = binaryContentService.create( + new BinaryContentCreateRequest("file2.png", "image/png", new byte[]{1, 2, 3, 4, 5})); + BinaryContent content3 = binaryContentService.create( + new BinaryContentCreateRequest("file3.pdf", "application/pdf", new byte[]{10, 20, 30})); + + // ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒ + List ids = List.of(content1.getId(), content2.getId(), content3.getId()); + List contents = binaryContentService.findAllByIdIn(ids); + + printTestResult(testName, true); + printDetail("์š”์ฒญํ•œ ํŒŒ์ผ ์ˆ˜: " + ids.size()); + printDetail("์กฐํšŒ๋œ ํŒŒ์ผ ์ˆ˜: " + contents.size()); + for (BinaryContent content : contents) { + printDetail(" - " + content.getFileName() + " (" + content.getContentType() + ", " + content.getData().length + " bytes)"); + } + } catch (Exception e) { + printTestResult(testName, false); + printDetail("์˜ค๋ฅ˜: " + e.getMessage()); + } + } + + // ==================== ์ถœ๋ ฅ ํ—ฌํผ ๋ฉ”์„œ๋“œ ==================== + + private void print(String message) { + System.out.println(message); + outputBuffer.append(message).append("\n"); + } + + private void printHeader(String title) { + String header = "\n" + "=".repeat(60) + "\n" + " " + title + "\n" + "=".repeat(60); + print(header); + } + + private void printSection(String section) { + print("\n" + "-".repeat(50)); + print("[ " + section + " ]"); + print("-".repeat(50)); + } + + private void printSeparator() { + print("-".repeat(60)); + } + + private void printLine(String line) { + print(line); + } + + private void printTestResult(String testName, boolean passed) { + testCount++; + if (passed) { + passCount++; + print("\n[PASS] " + testName); + } else { + failCount++; + print("\n[FAIL] " + testName); + } + } + + private void printDetail(String detail) { + print(" " + detail); + } + + private void printSummary() { + print("\n" + "=".repeat(60)); + print(" ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์š”์•ฝ"); + print("=".repeat(60)); + print("์ „์ฒด ํ…Œ์ŠคํŠธ: " + testCount); + print("์„ฑ๊ณต: " + passCount); + print("์‹คํŒจ: " + failCount); + print("์„ฑ๊ณต๋ฅ : " + String.format("%.1f", (passCount * 100.0 / testCount)) + "%"); + print("=".repeat(60)); + print("ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ์‹œ๊ฐ„: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + } + + private void saveToFile() { + String fileName = "test_result_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".txt"; + String filePath = "build/" + fileName; + + try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { + writer.print(outputBuffer.toString()); + print("\n๊ฒฐ๊ณผ๊ฐ€ ํŒŒ์ผ๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: " + filePath); + } catch (IOException e) { + print("\nํŒŒ์ผ ์ €์žฅ ์‹คํŒจ: " + e.getMessage()); + } + } +} diff --git a/discodeit/src/test/java/com/sprint/mission/discodeit/service/IntegrationServiceTest.java b/discodeit/src/test/java/com/sprint/mission/discodeit/service/IntegrationServiceTest.java new file mode 100644 index 00000000..bc48a76b --- /dev/null +++ b/discodeit/src/test/java/com/sprint/mission/discodeit/service/IntegrationServiceTest.java @@ -0,0 +1,563 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.request.*; +import com.sprint.mission.discodeit.dto.response.ChannelResponse; +import com.sprint.mission.discodeit.dto.response.MessageResponse; +import com.sprint.mission.discodeit.dto.response.UserResponse; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.jcf.*; +import com.sprint.mission.discodeit.service.basic.*; +import org.junit.jupiter.api.*; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * ์š”๊ตฌ์‚ฌํ•ญ ๊ธฐ๋ฐ˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ + * + * ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ, ๊ถŒํ•œ ๊ด€๋ฆฌ, ์ฑ„๋„ ๊ด€๋ฆฌ, ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ, + * ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ, ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class IntegrationServiceTest { + + // Repositories + private JCFUserRepository userRepository; + private JCFUserStatusRepository userStatusRepository; + private JCFBinaryContentRepository binaryContentRepository; + private JCFChannelRepository channelRepository; + private JCFMessageRepository messageRepository; + private JCFReadStatusRepository readStatusRepository; + + // Services + private UserService userService; + private AuthService authService; + private UserStatusService userStatusService; + private ChannelService channelService; + private MessageService messageService; + private ReadStatusService readStatusService; + private BinaryContentService binaryContentService; + + @BeforeEach + void setUp() { + // JCF Repository ์ดˆ๊ธฐํ™” + userRepository = new JCFUserRepository(); + userStatusRepository = new JCFUserStatusRepository(); + binaryContentRepository = new JCFBinaryContentRepository(); + channelRepository = new JCFChannelRepository(); + messageRepository = new JCFMessageRepository(); + readStatusRepository = new JCFReadStatusRepository(); + + // Service ์ดˆ๊ธฐํ™” + userService = new BasicUserService(userRepository, binaryContentRepository, userStatusRepository); + authService = new BasicAuthService(userRepository, userStatusRepository); + userStatusService = new BasicUserStatusService(userStatusRepository, userRepository); + channelService = new BasicChannelService(channelRepository, readStatusRepository, messageRepository, binaryContentRepository); + messageService = new BasicMessageService(messageRepository, binaryContentRepository); + readStatusService = new BasicReadStatusService(readStatusRepository, userRepository, channelRepository); + binaryContentService = new BasicBinaryContentService(binaryContentRepository); + } + + // ==================== ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(1) + @DisplayName("์‚ฌ์šฉ์ž๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค") + void createUser() { + // given + UserCreateRequest request = new UserCreateRequest("testuser", "test@example.com", "password123"); + + // when + UserResponse response = userService.create(request, null); + + // then + assertNotNull(response); + assertNotNull(response.id()); + assertEquals("testuser", response.username()); + assertEquals("test@example.com", response.email()); + assertTrue(response.isOnline()); + } + + @Test + @Order(2) + @DisplayName("์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค") + void updateUser() { + // given + UserCreateRequest createRequest = new UserCreateRequest("olduser", "old@example.com", "oldpass"); + UserResponse createdUser = userService.create(createRequest, null); + + UserUpdateRequest updateRequest = new UserUpdateRequest("newuser", "new@example.com", "newpass"); + + // when + UserResponse updatedUser = userService.update(createdUser.id(), updateRequest, null); + + // then + assertEquals(createdUser.id(), updatedUser.id()); + assertEquals("newuser", updatedUser.username()); + assertEquals("new@example.com", updatedUser.email()); + } + + @Test + @Order(3) + @DisplayName("์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค") + void deleteUser() { + // given + UserCreateRequest request = new UserCreateRequest("deleteuser", "delete@example.com", "password"); + UserResponse createdUser = userService.create(request, null); + + // when + userService.delete(createdUser.id()); + + // then + List allUsers = userService.findAll(); + assertTrue(allUsers.stream().noneMatch(u -> u.id().equals(createdUser.id()))); + } + + @Test + @Order(4) + @DisplayName("๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค") + void findAllUsers() { + // given + userService.create(new UserCreateRequest("user1", "user1@example.com", "pass1"), null); + userService.create(new UserCreateRequest("user2", "user2@example.com", "pass2"), null); + userService.create(new UserCreateRequest("user3", "user3@example.com", "pass3"), null); + + // when + List allUsers = userService.findAll(); + + // then + assertEquals(3, allUsers.size()); + } + + @Test + @Order(5) + @DisplayName("์‚ฌ์šฉ์ž์˜ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค") + void updateUserOnlineStatus() { + // given + UserCreateRequest createRequest = new UserCreateRequest("statususer", "status@example.com", "password"); + UserResponse createdUser = userService.create(createRequest, null); + + UserStatus userStatus = userStatusService.findByUserId(createdUser.id()); + Instant newAccessTime = Instant.now(); + + // when + UserStatus updatedStatus = userStatusService.update(userStatus.getId(), new UserStatusUpdateRequest(newAccessTime)); + + // then + assertNotNull(updatedStatus); + assertEquals(newAccessTime, updatedStatus.getLastActiveAt()); + } + + // ==================== ๊ถŒํ•œ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(6) + @DisplayName("์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋‹ค") + void loginUser() { + // given + UserCreateRequest createRequest = new UserCreateRequest("loginuser", "login@example.com", "mypassword"); + userService.create(createRequest, null); + + LoginRequest loginRequest = new LoginRequest("loginuser", "mypassword"); + + // when + UserResponse loggedInUser = authService.login(loginRequest); + + // then + assertNotNull(loggedInUser); + assertEquals("loginuser", loggedInUser.username()); + assertEquals("login@example.com", loggedInUser.email()); + } + + @Test + @Order(7) + @DisplayName("์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค") + void loginWithWrongPassword() { + // given + UserCreateRequest createRequest = new UserCreateRequest("wrongpassuser", "wrongpass@example.com", "correctpass"); + userService.create(createRequest, null); + + LoginRequest loginRequest = new LoginRequest("wrongpassuser", "wrongpassword"); + + // when & then + assertThrows(Exception.class, () -> authService.login(loginRequest)); + } + + // ==================== ์ฑ„๋„ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(8) + @DisplayName("๊ณต๊ฐœ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค") + void createPublicChannel() { + // given + PublicChannelCreateRequest request = new PublicChannelCreateRequest("general", "์ผ๋ฐ˜ ์ฑ„๋„์ž…๋‹ˆ๋‹ค"); + + // when + ChannelResponse response = channelService.createPublic(request); + + // then + assertNotNull(response); + assertNotNull(response.id()); + assertEquals(ChannelType.PUBLIC, response.type()); + assertEquals("general", response.name()); + assertEquals("์ผ๋ฐ˜ ์ฑ„๋„์ž…๋‹ˆ๋‹ค", response.description()); + } + + @Test + @Order(9) + @DisplayName("๋น„๊ณต๊ฐœ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค") + void createPrivateChannel() { + // given + UserResponse user1 = userService.create(new UserCreateRequest("privateuser1", "private1@example.com", "pass"), null); + UserResponse user2 = userService.create(new UserCreateRequest("privateuser2", "private2@example.com", "pass"), null); + + PrivateChannelCreateRequest request = new PrivateChannelCreateRequest(List.of(user1.id(), user2.id())); + + // when + ChannelResponse response = channelService.createPrivate(request); + + // then + assertNotNull(response); + assertEquals(ChannelType.PRIVATE, response.type()); + assertNull(response.name()); + assertNull(response.description()); + assertEquals(2, response.participantIds().size()); + assertTrue(response.participantIds().contains(user1.id())); + assertTrue(response.participantIds().contains(user2.id())); + } + + @Test + @Order(10) + @DisplayName("๊ณต๊ฐœ ์ฑ„๋„์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค") + void updatePublicChannel() { + // given + PublicChannelCreateRequest createRequest = new PublicChannelCreateRequest("oldchannel", "old description"); + ChannelResponse createdChannel = channelService.createPublic(createRequest); + + ChannelUpdateRequest updateRequest = new ChannelUpdateRequest("newchannel", "new description"); + + // when + ChannelResponse updatedChannel = channelService.update(createdChannel.id(), updateRequest); + + // then + assertEquals(createdChannel.id(), updatedChannel.id()); + assertEquals("newchannel", updatedChannel.name()); + assertEquals("new description", updatedChannel.description()); + } + + @Test + @Order(11) + @DisplayName("๋น„๊ณต๊ฐœ ์ฑ„๋„์€ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค") + void cannotUpdatePrivateChannel() { + // given + UserResponse user = userService.create(new UserCreateRequest("notupdateuser", "notupdate@example.com", "pass"), null); + PrivateChannelCreateRequest createRequest = new PrivateChannelCreateRequest(List.of(user.id())); + ChannelResponse privateChannel = channelService.createPrivate(createRequest); + + ChannelUpdateRequest updateRequest = new ChannelUpdateRequest("tryupdate", "try description"); + + // when & then + assertThrows(IllegalArgumentException.class, () -> channelService.update(privateChannel.id(), updateRequest)); + } + + @Test + @Order(12) + @DisplayName("์ฑ„๋„์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค") + void deleteChannel() { + // given + PublicChannelCreateRequest createRequest = new PublicChannelCreateRequest("deletechannel", "to be deleted"); + ChannelResponse createdChannel = channelService.createPublic(createRequest); + + // when + channelService.delete(createdChannel.id()); + + // then + UserResponse user = userService.create(new UserCreateRequest("deletechanneluser", "deletechannel@example.com", "pass"), null); + List channels = channelService.findAllByUserId(user.id()); + assertTrue(channels.stream().noneMatch(c -> c.id().equals(createdChannel.id()))); + } + + @Test + @Order(13) + @DisplayName("ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ฑ„๋„ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค") + void findAllChannelsByUserId() { + // given + UserResponse user1 = userService.create(new UserCreateRequest("channeluser1", "channeluser1@example.com", "pass"), null); + UserResponse user2 = userService.create(new UserCreateRequest("channeluser2", "channeluser2@example.com", "pass"), null); + + // ๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ + channelService.createPublic(new PublicChannelCreateRequest("public1", "๊ณต๊ฐœ์ฑ„๋„1")); + channelService.createPublic(new PublicChannelCreateRequest("public2", "๊ณต๊ฐœ์ฑ„๋„2")); + + // ๋น„๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ (user1๋งŒ ์ฐธ์—ฌ) + channelService.createPrivate(new PrivateChannelCreateRequest(List.of(user1.id()))); + + // when + List user1Channels = channelService.findAllByUserId(user1.id()); + List user2Channels = channelService.findAllByUserId(user2.id()); + + // then + assertEquals(3, user1Channels.size()); // ๊ณต๊ฐœ 2๊ฐœ + ๋น„๊ณต๊ฐœ 1๊ฐœ + assertEquals(2, user2Channels.size()); // ๊ณต๊ฐœ 2๊ฐœ๋งŒ + } + + // ==================== ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(14) + @DisplayName("๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค") + void createMessage() { + // given + UserResponse user = userService.create(new UserCreateRequest("msguser", "msguser@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("msgchannel", "๋ฉ”์‹œ์ง€ ์ฑ„๋„")); + + MessageCreateRequest request = new MessageCreateRequest("์•ˆ๋…•ํ•˜์„ธ์š”!", channel.id(), user.id()); + + // when + MessageResponse response = messageService.create(request, null); + + // then + assertNotNull(response); + assertNotNull(response.id()); + assertEquals("์•ˆ๋…•ํ•˜์„ธ์š”!", response.content()); + assertEquals(channel.id(), response.channelId()); + assertEquals(user.id(), response.authorId()); + } + + @Test + @Order(15) + @DisplayName("๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค") + void updateMessage() { + // given + UserResponse user = userService.create(new UserCreateRequest("msgupdateuser", "msgupdate@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("msgupdatechannel", "์ˆ˜์ • ํ…Œ์ŠคํŠธ")); + + MessageCreateRequest createRequest = new MessageCreateRequest("์›๋ž˜ ๋ฉ”์‹œ์ง€", channel.id(), user.id()); + MessageResponse createdMessage = messageService.create(createRequest, null); + + MessageUpdateRequest updateRequest = new MessageUpdateRequest("์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€"); + + // when + MessageResponse updatedMessage = messageService.update(createdMessage.id(), updateRequest); + + // then + assertEquals(createdMessage.id(), updatedMessage.id()); + assertEquals("์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€", updatedMessage.content()); + } + + @Test + @Order(16) + @DisplayName("๋ฉ”์‹œ์ง€๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค") + void deleteMessage() { + // given + UserResponse user = userService.create(new UserCreateRequest("msgdeleteuser", "msgdelete@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("msgdeletechannel", "์‚ญ์ œ ํ…Œ์ŠคํŠธ")); + + MessageCreateRequest createRequest = new MessageCreateRequest("์‚ญ์ œ๋  ๋ฉ”์‹œ์ง€", channel.id(), user.id()); + MessageResponse createdMessage = messageService.create(createRequest, null); + + // when + messageService.delete(createdMessage.id()); + + // then + List messages = messageService.findAllByChannelId(channel.id()); + assertTrue(messages.stream().noneMatch(m -> m.id().equals(createdMessage.id()))); + } + + @Test + @Order(17) + @DisplayName("ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค") + void findAllMessagesByChannelId() { + // given + UserResponse user = userService.create(new UserCreateRequest("msglistuser", "msglist@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("msglistchannel", "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก")); + + messageService.create(new MessageCreateRequest("๋ฉ”์‹œ์ง€1", channel.id(), user.id()), null); + messageService.create(new MessageCreateRequest("๋ฉ”์‹œ์ง€2", channel.id(), user.id()), null); + messageService.create(new MessageCreateRequest("๋ฉ”์‹œ์ง€3", channel.id(), user.id()), null); + + // when + List messages = messageService.findAllByChannelId(channel.id()); + + // then + assertEquals(3, messages.size()); + } + + // ==================== ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(18) + @DisplayName("ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค") + void createReadStatus() { + // given + UserResponse user = userService.create(new UserCreateRequest("readstatususer", "readstatus@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("readstatuschannel", "์ˆ˜์‹ ์ •๋ณด ํ…Œ์ŠคํŠธ")); + + ReadStatusCreateRequest request = new ReadStatusCreateRequest(user.id(), channel.id(), Instant.now()); + + // when + ReadStatus readStatus = readStatusService.create(request); + + // then + assertNotNull(readStatus); + assertNotNull(readStatus.getId()); + assertEquals(user.id(), readStatus.getUserId()); + assertEquals(channel.id(), readStatus.getChannelId()); + } + + @Test + @Order(19) + @DisplayName("ํŠน์ • ์ฑ„๋„์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค") + void updateReadStatus() { + // given + UserResponse user = userService.create(new UserCreateRequest("readupdateuser", "readupdate@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("readupdatechannel", "์ˆ˜์‹ ์ •๋ณด ์ˆ˜์ •")); + + ReadStatusCreateRequest createRequest = new ReadStatusCreateRequest(user.id(), channel.id(), Instant.now().minusSeconds(3600)); + ReadStatus createdReadStatus = readStatusService.create(createRequest); + + Instant newLastReadAt = Instant.now(); + ReadStatusUpdateRequest updateRequest = new ReadStatusUpdateRequest(newLastReadAt); + + // when + ReadStatus updatedReadStatus = readStatusService.update(createdReadStatus.getId(), updateRequest); + + // then + assertEquals(createdReadStatus.getId(), updatedReadStatus.getId()); + assertEquals(newLastReadAt, updatedReadStatus.getLastReadAt()); + } + + @Test + @Order(20) + @DisplayName("ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค") + void findAllReadStatusByUserId() { + // given + UserResponse user = userService.create(new UserCreateRequest("readfinduser", "readfind@example.com", "pass"), null); + ChannelResponse channel1 = channelService.createPublic(new PublicChannelCreateRequest("readfindchannel1", "์ฑ„๋„1")); + ChannelResponse channel2 = channelService.createPublic(new PublicChannelCreateRequest("readfindchannel2", "์ฑ„๋„2")); + + readStatusService.create(new ReadStatusCreateRequest(user.id(), channel1.id(), Instant.now())); + readStatusService.create(new ReadStatusCreateRequest(user.id(), channel2.id(), Instant.now())); + + // when + List readStatuses = readStatusService.findAllByUserId(user.id()); + + // then + assertEquals(2, readStatuses.size()); + assertTrue(readStatuses.stream().allMatch(rs -> rs.getUserId().equals(user.id()))); + } + + // ==================== ๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(21) + @DisplayName("๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ์„ 1๊ฐœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค") + void findOneBinaryContent() { + // given + BinaryContentCreateRequest request = new BinaryContentCreateRequest( + "test.png", + "image/png", + new byte[]{1, 2, 3, 4, 5} + ); + BinaryContent createdContent = binaryContentService.create(request); + + // when + BinaryContent foundContent = binaryContentService.find(createdContent.getId()); + + // then + assertNotNull(foundContent); + assertEquals(createdContent.getId(), foundContent.getId()); + assertEquals("test.png", foundContent.getFileName()); + assertEquals("image/png", foundContent.getContentType()); + assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, foundContent.getData()); + } + + @Test + @Order(22) + @DisplayName("๋ฐ”์ด๋„ˆ๋ฆฌ ํŒŒ์ผ์„ ์—ฌ๋Ÿฌ ๊ฐœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค") + void findMultipleBinaryContents() { + // given + BinaryContent content1 = binaryContentService.create(new BinaryContentCreateRequest("file1.txt", "text/plain", "๋‚ด์šฉ1".getBytes())); + BinaryContent content2 = binaryContentService.create(new BinaryContentCreateRequest("file2.txt", "text/plain", "๋‚ด์šฉ2".getBytes())); + BinaryContent content3 = binaryContentService.create(new BinaryContentCreateRequest("file3.txt", "text/plain", "๋‚ด์šฉ3".getBytes())); + + List ids = List.of(content1.getId(), content2.getId(), content3.getId()); + + // when + List contents = binaryContentService.findAllByIdIn(ids); + + // then + assertEquals(3, contents.size()); + } + + // ==================== ์ถ”๊ฐ€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ==================== + + @Test + @Order(23) + @DisplayName("ํ”„๋กœํ•„ ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ž๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค") + void createUserWithProfile() { + // given + UserCreateRequest userRequest = new UserCreateRequest("profileuser", "profile@example.com", "password"); + BinaryContentCreateRequest profileRequest = new BinaryContentCreateRequest( + "profile.jpg", + "image/jpeg", + new byte[]{10, 20, 30} + ); + + // when + UserResponse response = userService.create(userRequest, profileRequest); + + // then + assertNotNull(response); + assertNotNull(response.profileId()); + assertEquals("profileuser", response.username()); + } + + @Test + @Order(24) + @DisplayName("์ฒจ๋ถ€ํŒŒ์ผ๊ณผ ํ•จ๊ป˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค") + void createMessageWithAttachments() { + // given + UserResponse user = userService.create(new UserCreateRequest("attachuser", "attach@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("attachchannel", "์ฒจ๋ถ€ํŒŒ์ผ ํ…Œ์ŠคํŠธ")); + + MessageCreateRequest messageRequest = new MessageCreateRequest("์ฒจ๋ถ€ํŒŒ์ผ ์žˆ๋Š” ๋ฉ”์‹œ์ง€", channel.id(), user.id()); + List attachments = List.of( + new BinaryContentCreateRequest("attach1.pdf", "application/pdf", new byte[]{1, 2, 3}), + new BinaryContentCreateRequest("attach2.png", "image/png", new byte[]{4, 5, 6}) + ); + + // when + MessageResponse response = messageService.create(messageRequest, attachments); + + // then + assertNotNull(response); + assertNotNull(response.attachmentIds()); + assertEquals(2, response.attachmentIds().size()); + } + + @Test + @Order(25) + @DisplayName("์ฑ„๋„ ์‚ญ์ œ ์‹œ ๊ด€๋ จ ๋ฉ”์‹œ์ง€๋„ ํ•จ๊ป˜ ์‚ญ์ œ๋œ๋‹ค") + void deleteChannelWithMessages() { + // given + UserResponse user = userService.create(new UserCreateRequest("cascadeuser", "cascade@example.com", "pass"), null); + ChannelResponse channel = channelService.createPublic(new PublicChannelCreateRequest("cascadechannel", "cascade test")); + + messageService.create(new MessageCreateRequest("๋ฉ”์‹œ์ง€1", channel.id(), user.id()), null); + messageService.create(new MessageCreateRequest("๋ฉ”์‹œ์ง€2", channel.id(), user.id()), null); + + // when + channelService.delete(channel.id()); + + // then + List messages = messageService.findAllByChannelId(channel.id()); + assertTrue(messages.isEmpty()); + } +} diff --git a/discodeit/test_api_automation.py b/discodeit/test_api_automation.py new file mode 100644 index 00000000..02fc2f33 --- /dev/null +++ b/discodeit/test_api_automation.py @@ -0,0 +1,477 @@ +#!/usr/bin/env python3 +""" +๋””์Šค์ฝ”๋“œ์ž‡ API ์ž๋™ํ™” ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ + +์‚ฌ์šฉ๋ฒ•: + python test_api_automation.py + +์š”๊ตฌ์‚ฌํ•ญ: + pip install requests +""" + +import requests +import json +from datetime import datetime +from typing import Dict, Optional +import sys + + +class DiscodeitAPITester: + """๋””์Šค์ฝ”๋“œ์ž‡ API ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค""" + + def __init__(self, base_url: str = "http://localhost:8080"): + self.base_url = base_url + self.session = requests.Session() + self.session.headers.update({"Content-Type": "application/json"}) + + # ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ €์žฅ + self.user_id: Optional[str] = None + self.channel_id: Optional[str] = None + self.message_id: Optional[str] = None + self.read_status_id: Optional[str] = None + self.binary_content_id: Optional[str] = None + + # ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ + self.passed = 0 + self.failed = 0 + self.errors = [] + + def print_header(self, title: str): + """ํ—ค๋” ์ถœ๋ ฅ""" + print("\n" + "=" * 60) + print(f" {title}") + print("=" * 60) + + def print_test(self, name: str, passed: bool, detail: str = ""): + """ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์ถœ๋ ฅ""" + status = "โœ“ PASS" if passed else "โœ— FAIL" + color = "\033[92m" if passed else "\033[91m" + reset = "\033[0m" + + print(f"\n{color}[{status}]{reset} {name}") + if detail: + print(f" {detail}") + + if passed: + self.passed += 1 + else: + self.failed += 1 + self.errors.append(name) + + def test_user_create(self): + """์‚ฌ์šฉ์ž ๋“ฑ๋ก ํ…Œ์ŠคํŠธ""" + try: + response = self.session.post( + f"{self.base_url}/users", + json={ + "username": "apitest_user", + "email": "apitest@example.com", + "password": "testpass123" + } + ) + + if response.status_code == 201: + data = response.json() + self.user_id = data["id"] + self.print_test( + "์‚ฌ์šฉ์ž ๋“ฑ๋ก", + True, + f"์‚ฌ์šฉ์ž ID: {self.user_id}" + ) + else: + self.print_test( + "์‚ฌ์šฉ์ž ๋“ฑ๋ก", + False, + f"Status: {response.status_code}, {response.text}" + ) + except Exception as e: + self.print_test("์‚ฌ์šฉ์ž ๋“ฑ๋ก", False, str(e)) + + def test_login(self): + """๋กœ๊ทธ์ธ ํ…Œ์ŠคํŠธ""" + try: + response = self.session.post( + f"{self.base_url}/auth/login", + json={ + "username": "apitest_user", + "password": "testpass123" + } + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "๋กœ๊ทธ์ธ", + True, + f"๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž: {data['username']}" + ) + else: + self.print_test( + "๋กœ๊ทธ์ธ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("๋กœ๊ทธ์ธ", False, str(e)) + + def test_user_list(self): + """์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ ํ…Œ์ŠคํŠธ""" + try: + response = self.session.get(f"{self.base_url}/users") + + if response.status_code == 200: + data = response.json() + self.print_test( + "์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ", + True, + f"์‚ฌ์šฉ์ž ์ˆ˜: {len(data)}" + ) + else: + self.print_test( + "์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ", False, str(e)) + + def test_user_get(self): + """ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ ํ…Œ์ŠคํŠธ""" + if not self.user_id: + self.print_test("ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ", False, "์‚ฌ์šฉ์ž ID ์—†์Œ") + return + + try: + response = self.session.get(f"{self.base_url}/users/{self.user_id}") + + if response.status_code == 200: + data = response.json() + self.print_test( + "ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ", + True, + f"์‚ฌ์šฉ์ž๋ช…: {data['username']}" + ) + else: + self.print_test( + "ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ", False, str(e)) + + def test_user_update(self): + """์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ํ…Œ์ŠคํŠธ""" + if not self.user_id: + self.print_test("์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", False, "์‚ฌ์šฉ์ž ID ์—†์Œ") + return + + try: + response = self.session.put( + f"{self.base_url}/users/{self.user_id}", + json={ + "username": "updated_apitest", + "email": "updated_apitest@example.com", + "password": "newpass456" + } + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", + True, + f"์ˆ˜์ •๋œ ์‚ฌ์šฉ์ž๋ช…: {data['username']}" + ) + else: + self.print_test( + "์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •", False, str(e)) + + def test_channel_create_public(self): + """๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ ํ…Œ์ŠคํŠธ""" + try: + response = self.session.post( + f"{self.base_url}/channels/public", + json={ + "name": "์ž๋™ํ™” ํ…Œ์ŠคํŠธ ์ฑ„๋„", + "description": "Python ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ๋กœ ์ƒ์„ฑ๋œ ์ฑ„๋„" + } + ) + + if response.status_code == 201: + data = response.json() + self.channel_id = data["id"] + self.print_test( + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + True, + f"์ฑ„๋„ ID: {self.channel_id}" + ) + else: + self.print_test( + "๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("๊ณต๊ฐœ ์ฑ„๋„ ์ƒ์„ฑ", False, str(e)) + + def test_channel_list(self): + """์‚ฌ์šฉ์ž๋ณ„ ์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ ํ…Œ์ŠคํŠธ""" + if not self.user_id: + self.print_test("์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", False, "์‚ฌ์šฉ์ž ID ์—†์Œ") + return + + try: + response = self.session.get( + f"{self.base_url}/channels", + params={"userId": self.user_id} + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", + True, + f"์ฑ„๋„ ์ˆ˜: {len(data)}" + ) + else: + self.print_test( + "์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("์ฑ„๋„ ๋ชฉ๋ก ์กฐํšŒ", False, str(e)) + + def test_channel_update(self): + """์ฑ„๋„ ์ •๋ณด ์ˆ˜์ • ํ…Œ์ŠคํŠธ""" + if not self.channel_id: + self.print_test("์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", False, "์ฑ„๋„ ID ์—†์Œ") + return + + try: + response = self.session.put( + f"{self.base_url}/channels/{self.channel_id}", + json={ + "name": "์ˆ˜์ •๋œ ์ฑ„๋„๋ช…", + "description": "์ˆ˜์ •๋œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค" + } + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", + True, + f"์ˆ˜์ •๋œ ์ฑ„๋„๋ช…: {data['name']}" + ) + else: + self.print_test( + "์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("์ฑ„๋„ ์ •๋ณด ์ˆ˜์ •", False, str(e)) + + def test_message_create(self): + """๋ฉ”์‹œ์ง€ ์ „์†ก ํ…Œ์ŠคํŠธ""" + if not self.channel_id or not self.user_id: + self.print_test("๋ฉ”์‹œ์ง€ ์ „์†ก", False, "์ฑ„๋„ ID ๋˜๋Š” ์‚ฌ์šฉ์ž ID ์—†์Œ") + return + + try: + response = self.session.post( + f"{self.base_url}/messages", + json={ + "content": "์ž๋™ํ™” ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค", + "channelId": self.channel_id, + "authorId": self.user_id + } + ) + + if response.status_code == 201: + data = response.json() + self.message_id = data["id"] + self.print_test( + "๋ฉ”์‹œ์ง€ ์ „์†ก", + True, + f"๋ฉ”์‹œ์ง€ ID: {self.message_id}" + ) + else: + self.print_test( + "๋ฉ”์‹œ์ง€ ์ „์†ก", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("๋ฉ”์‹œ์ง€ ์ „์†ก", False, str(e)) + + def test_message_list(self): + """์ฑ„๋„๋ณ„ ๋ฉ”์‹œ์ง€ ์กฐํšŒ ํ…Œ์ŠคํŠธ""" + if not self.channel_id: + self.print_test("๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", False, "์ฑ„๋„ ID ์—†์Œ") + return + + try: + response = self.session.get( + f"{self.base_url}/messages", + params={"channelId": self.channel_id} + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", + True, + f"๋ฉ”์‹œ์ง€ ์ˆ˜: {len(data)}" + ) + else: + self.print_test( + "๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("๋ฉ”์‹œ์ง€ ๋ชฉ๋ก ์กฐํšŒ", False, str(e)) + + def test_message_update(self): + """๋ฉ”์‹œ์ง€ ์ˆ˜์ • ํ…Œ์ŠคํŠธ""" + if not self.message_id: + self.print_test("๋ฉ”์‹œ์ง€ ์ˆ˜์ •", False, "๋ฉ”์‹œ์ง€ ID ์—†์Œ") + return + + try: + response = self.session.put( + f"{self.base_url}/messages/{self.message_id}", + json={"content": "์ˆ˜์ •๋œ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ"} + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "๋ฉ”์‹œ์ง€ ์ˆ˜์ •", + True, + f"์ˆ˜์ •๋œ ๋‚ด์šฉ: {data['content']}" + ) + else: + self.print_test( + "๋ฉ”์‹œ์ง€ ์ˆ˜์ •", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("๋ฉ”์‹œ์ง€ ์ˆ˜์ •", False, str(e)) + + def test_user_status_update(self): + """์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ํ…Œ์ŠคํŠธ""" + if not self.user_id: + self.print_test("์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", False, "์‚ฌ์šฉ์ž ID ์—†์Œ") + return + + try: + response = self.session.put( + f"{self.base_url}/users/{self.user_id}/status", + json={"lastActiveAt": datetime.utcnow().isoformat() + "Z"} + ) + + if response.status_code == 200: + data = response.json() + self.print_test( + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + True, + f"์˜จ๋ผ์ธ: {data.get('online', 'N/A')}" + ) + else: + self.print_test( + "์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", + False, + f"Status: {response.status_code}" + ) + except Exception as e: + self.print_test("์˜จ๋ผ์ธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ", False, str(e)) + + def print_summary(self): + """ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์š”์•ฝ ์ถœ๋ ฅ""" + self.print_header("ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์š”์•ฝ") + + total = self.passed + self.failed + success_rate = (self.passed / total * 100) if total > 0 else 0 + + print(f"\n์ด ํ…Œ์ŠคํŠธ: {total}") + print(f"์„ฑ๊ณต: \033[92m{self.passed}\033[0m") + print(f"์‹คํŒจ: \033[91m{self.failed}\033[0m") + print(f"์„ฑ๊ณต๋ฅ : {success_rate:.1f}%") + + if self.errors: + print("\n์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ:") + for error in self.errors: + print(f" - {error}") + + print("\n์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค:") + print(f" - ์‚ฌ์šฉ์ž ID: {self.user_id}") + print(f" - ์ฑ„๋„ ID: {self.channel_id}") + print(f" - ๋ฉ”์‹œ์ง€ ID: {self.message_id}") + + print("=" * 60) + + return self.failed == 0 + + def run_all_tests(self): + """๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰""" + self.print_header("๋””์Šค์ฝ”๋“œ์ž‡ API ์ž๋™ํ™” ํ…Œ์ŠคํŠธ ์‹œ์ž‘") + print(f"Base URL: {self.base_url}") + print(f"์‹œ์ž‘ ์‹œ๊ฐ„: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + # 1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ + self.print_header("1. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ") + self.test_user_create() + self.test_login() + self.test_user_list() + self.test_user_get() + self.test_user_update() + + # 2. ์ฑ„๋„ ๊ด€๋ฆฌ + self.print_header("2. ์ฑ„๋„ ๊ด€๋ฆฌ") + self.test_channel_create_public() + self.test_channel_list() + self.test_channel_update() + + # 3. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ + self.print_header("3. ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ") + self.test_message_create() + self.test_message_list() + self.test_message_update() + + # 4. ์˜จ๋ผ์ธ ์ƒํƒœ + self.print_header("4. ์˜จ๋ผ์ธ ์ƒํƒœ") + self.test_user_status_update() + + # ๊ฒฐ๊ณผ ์š”์•ฝ + success = self.print_summary() + + return 0 if success else 1 + + +def main(): + """๋ฉ”์ธ ํ•จ์ˆ˜""" + tester = DiscodeitAPITester() + + try: + exit_code = tester.run_all_tests() + sys.exit(exit_code) + except KeyboardInterrupt: + print("\n\nํ…Œ์ŠคํŠธ๊ฐ€ ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.") + sys.exit(1) + except Exception as e: + print(f"\n\n์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main()