diff --git "a/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\260\234\354\204\240.md" "b/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\260\234\354\204\240.md" index 4b256504..17616d92 100644 --- "a/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\260\234\354\204\240.md" +++ "b/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\260\234\354\204\240.md" @@ -11,13 +11,14 @@ assignees: ''
-## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) -- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) -- [ ] ๐Ÿ‘ค domain:user (์‚ฌ์šฉ์ž) -- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) -- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) -- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) -- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) +## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) +- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) +- [ ] ๐Ÿ‘ค domain:useraccount (์‚ฌ์šฉ์ž ๊ณ„์ •) +- [ ] ๐Ÿง  domain:personalization (๊ฐœ์ธํ™” ํ”„๋กœํ•„) +- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) +- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) +- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) +- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) - [ ] ๐ŸŽฏ domain:activity (์‚ฌ์šฉ์ž ํ™œ๋™) - [ ] ๐Ÿ” domain:auth (์ธ์ฆ/๋ณด์•ˆ) - [ ] ๐ŸŒ infra (์ธํ”„๋ผ/๋ฐฐํฌ) @@ -37,4 +38,4 @@ assignees: '' ## ๐Ÿ’ก ๊ฐœ์„  ์ด์œ  -
\ No newline at end of file +
diff --git "a/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\265\254\355\230\204.md" "b/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\265\254\355\230\204.md" index 86866d46..e38e1075 100644 --- "a/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\265\254\355\230\204.md" +++ "b/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245-\352\265\254\355\230\204.md" @@ -14,13 +14,14 @@ assignees: ''
-## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) -- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) -- [ ] ๐Ÿ‘ค domain:user (์‚ฌ์šฉ์ž) -- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) -- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) -- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) -- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) +## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) +- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) +- [ ] ๐Ÿ‘ค domain:useraccount (์‚ฌ์šฉ์ž ๊ณ„์ •) +- [ ] ๐Ÿง  domain:personalization (๊ฐœ์ธํ™” ํ”„๋กœํ•„) +- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) +- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) +- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) +- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) - [ ] ๐ŸŽฏ domain:activity (์‚ฌ์šฉ์ž ํ™œ๋™) - [ ] ๐Ÿ” domain:auth (์ธ์ฆ/๋ณด์•ˆ) - [ ] ๐ŸŒ infra (์ธํ”„๋ผ/๋ฐฐํฌ) @@ -35,4 +36,4 @@ assignees: '' ## ๐Ÿ’ก ์ฐธ๊ณ  ์‚ฌํ•ญ -
\ No newline at end of file +
diff --git "a/.github/ISSUE_TEMPLATE/\353\246\254\355\214\251\355\206\240\353\247\201.md" "b/.github/ISSUE_TEMPLATE/\353\246\254\355\214\251\355\206\240\353\247\201.md" index 9a378237..c4706b34 100644 --- "a/.github/ISSUE_TEMPLATE/\353\246\254\355\214\251\355\206\240\353\247\201.md" +++ "b/.github/ISSUE_TEMPLATE/\353\246\254\355\214\251\355\206\240\353\247\201.md" @@ -11,14 +11,17 @@ assignees: ''
-## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) -- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) -- [ ] ๐Ÿ‘ค domain:user (์‚ฌ์šฉ์ž) -- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) -- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) -- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) -- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) -- [ ] ๐ŸŒ infra (์ธํ”„๋ผ/๋ฐฐํฌ) +## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) +- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) +- [ ] ๐Ÿ‘ค domain:useraccount (์‚ฌ์šฉ์ž ๊ณ„์ •) +- [ ] ๐Ÿง  domain:personalization (๊ฐœ์ธํ™” ํ”„๋กœํ•„) +- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) +- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) +- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) +- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) +- [ ] ๐ŸŽฏ domain:activity (์‚ฌ์šฉ์ž ํ™œ๋™) +- [ ] ๐Ÿ” domain:auth (์ธ์ฆ/๋ณด์•ˆ) +- [ ] ๐ŸŒ infra (์ธํ”„๋ผ/๋ฐฐํฌ)
@@ -39,4 +42,4 @@ assignees: '' - [ ] ์ฝ”๋“œ ์ค‘๋ณต ์ œ๊ฑฐ - [ ] ๊ธฐํƒ€: -
\ No newline at end of file +
diff --git "a/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270-\354\210\230\354\240\225.md" "b/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270-\354\210\230\354\240\225.md" index ca9f6198..d9b08443 100644 --- "a/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270-\354\210\230\354\240\225.md" +++ "b/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270-\354\210\230\354\240\225.md" @@ -12,14 +12,17 @@ assignees: ''
-## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) -- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) -- [ ] ๐Ÿ‘ค domain:user (์‚ฌ์šฉ์ž) -- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) -- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) -- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) -- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) -- [ ] ๐ŸŒ infra (์ธํ”„๋ผ/๋ฐฐํฌ) +## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) +- [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) +- [ ] ๐Ÿ‘ค domain:useraccount (์‚ฌ์šฉ์ž ๊ณ„์ •) +- [ ] ๐Ÿง  domain:personalization (๊ฐœ์ธํ™” ํ”„๋กœํ•„) +- [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) +- [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) +- [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) +- [ ] ๐Ÿ“Š domain:recommendation (์ถ”์ฒœ) +- [ ] ๐ŸŽฏ domain:activity (์‚ฌ์šฉ์ž ํ™œ๋™) +- [ ] ๐Ÿ” domain:auth (์ธ์ฆ/๋ณด์•ˆ) +- [ ] ๐ŸŒ infra (์ธํ”„๋ผ/๋ฐฐํฌ)
@@ -34,4 +37,4 @@ assignees: '' ## ๐ŸŽฏ ์˜ˆ์ƒ ์›์ธ -
\ No newline at end of file +
diff --git "a/.github/ISSUE_TEMPLATE/\355\205\214\354\212\244\355\212\270-\354\236\221\354\204\261.md" "b/.github/ISSUE_TEMPLATE/\355\205\214\354\212\244\355\212\270-\354\236\221\354\204\261.md" index 0a452618..54ef1375 100644 --- "a/.github/ISSUE_TEMPLATE/\355\205\214\354\212\244\355\212\270-\354\236\221\354\204\261.md" +++ "b/.github/ISSUE_TEMPLATE/\355\205\214\354\212\244\355\212\270-\354\236\221\354\204\261.md" @@ -16,7 +16,8 @@ assignees: '' ## ๐Ÿท๏ธ ๋„๋ฉ”์ธ (ํ•ด๋‹นํ•˜๋Š” ๊ฒƒ์— ์ฒดํฌ) - [ ] ๐Ÿ“ domain:post (๊ฒŒ์‹œ๊ธ€) -- [ ] ๐Ÿ‘ค domain:user (์‚ฌ์šฉ์ž) +- [ ] ๐Ÿ‘ค domain:useraccount (์‚ฌ์šฉ์ž ๊ณ„์ •) +- [ ] ๐Ÿง  domain:personalization (๊ฐœ์ธํ™” ํ”„๋กœํ•„) - [ ] ๐Ÿข domain:source (ํ…Œํฌ๋ธ”๋กœ๊ทธ ์ถœ์ฒ˜) - [ ] ๐Ÿ” domain:search (๊ฒ€์ƒ‰) - [ ] ๐Ÿ”” domain:notification (์•Œ๋ฆผ) diff --git a/docs/ddd-test-refactoring-roadmap.md b/docs/ddd-test-refactoring-roadmap.md index 53e79c53..503be85e 100644 --- a/docs/ddd-test-refactoring-roadmap.md +++ b/docs/ddd-test-refactoring-roadmap.md @@ -47,7 +47,7 @@ DDD ๋ชฉํ‘œ ์ง€๋„ ์ž‘์„ฑ - ์˜ˆ: `ScrabPost`, `scrap_posts`, `Bookmark` - ์˜ˆ: `searchWord`, `query`, `keyKeywords`, `PostKeyword` - ์˜ˆ: ๊ณ„์ • ํ”„๋กœํ•„๊ณผ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ -- Search, Recommendation, Personalization Profile(`UserProfileService`) ์ชฝ์€ ์—ฌ๋Ÿฌ ์ปจํ…์ŠคํŠธ์™€ ์™ธ๋ถ€ ์ธํ”„๋ผ๊ฐ€ ์–ฝํ˜€ ์žˆ์–ด ๋ฆฌํŒฉํ„ฐ๋ง ์œ„ํ—˜์ด ํฌ๋‹ค. +- Search, Recommendation, Personalization Profile(`PersonalizationProfileService`) ์ชฝ์€ ์—ฌ๋Ÿฌ ์ปจํ…์ŠคํŠธ์™€ ์™ธ๋ถ€ ์ธํ”„๋ผ๊ฐ€ ์–ฝํ˜€ ์žˆ์–ด ๋ฆฌํŒฉํ„ฐ๋ง ์œ„ํ—˜์ด ํฌ๋‹ค. ๋”ฐ๋ผ์„œ ์•ˆ์ „ํ•œ ์ „ํ™˜ ์ „๋žต์€ ๋‹ค์Œ์ด๋‹ค. @@ -265,14 +265,14 @@ PostKeyword | ํ‘œ์ค€ ์šฉ์–ด | ์ฝ”๋“œ์ƒ ํ‘œํ˜„ | ์˜๋ฏธ | |---|---|---| | ๊ณ„์ • ํ”„๋กœํ•„ | `User.nickName`, `description`, `profileImage` | ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์ด๋Š” ๊ธฐ๋ณธ ํ”„๋กœํ•„ | -| ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | `UserProfileDocument.profileText`, `profileVector` | ๊ฒ€์ƒ‰/์ถ”์ฒœ์— ์“ฐ์ด๋Š” ํ™œ๋™ ๊ธฐ๋ฐ˜ LLM/์ž„๋ฒ ๋”ฉ ํ”„๋กœํ•„ | +| ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | `PersonalizationProfileDocument.profileText`, `profileVector` | ๊ฒ€์ƒ‰/์ถ”์ฒœ์— ์“ฐ์ด๋Š” ํ™œ๋™ ๊ธฐ๋ฐ˜ LLM/์ž„๋ฒ ๋”ฉ ํ”„๋กœํ•„ | ๊ถŒ์žฅ ์ˆœ์„œ: ```text 1. ๋ฌธ์„œ/API ์„ค๋ช…์—์„œ `User Account`์™€ `Personalization Profile` ๊ฒฝ๊ณ„๋ฅผ ๊ณ ์ • -2. UserProfileService ํ…Œ์ŠคํŠธ ์ž‘์„ฑ -3. UserProfileDocument์˜ ์—ญํ• ์„ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•จ +2. PersonalizationProfileService ํ…Œ์ŠคํŠธ ์ž‘์„ฑ +3. PersonalizationProfileDocument์˜ ์—ญํ• ์„ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•จ 4. ํ•„์š”ํ•˜๋ฉด ํŒจํ‚ค์ง€/์ด๋ฒคํŠธ/ํฌํŠธ ๋ถ„๋ฆฌ๋ฅผ ํ›„์† ๋‹จ๊ณ„์—์„œ ์ง„ํ–‰ ``` @@ -458,14 +458,14 @@ InterestCommandServiceTest ```text Personalization Profile -- UserProfileDocument (๊ฐœ์ธํ™” ๊ฒ€์ƒ‰/์ถ”์ฒœ์šฉ read model projection) -- UserProfileService (Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค๋กœ ์œ„์น˜ ์žฌ์ •์˜) +- PersonalizationProfileDocument (๊ฐœ์ธํ™” ๊ฒ€์ƒ‰/์ถ”์ฒœ์šฉ read model projection) +- PersonalizationProfileService (Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค๋กœ ์œ„์น˜ ์žฌ์ •์˜) ``` ##### ๋จผ์ € ์ž‘์„ฑํ•  ํ…Œ์ŠคํŠธ ```text -UserProfileServiceTest +PersonalizationProfileServiceTest - ๊ด€์‹ฌ์‚ฌ, ์ฝ์€ ๊ฒŒ์‹œ๊ธ€, ๋ถ๋งˆํฌ, ๊ฒ€์ƒ‰ ๊ธฐ๋ก์„ ๋ชจ์•„ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค. - LLM ์‘๋‹ต์—์„œ ํ”„๋กœํ•„ ํ…์ŠคํŠธ์™€ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ๋ฅผ ํŒŒ์‹ฑํ•œ๋‹ค. - ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ fallback ์ •์ฑ…์„ ๋”ฐ๋ฅธ๋‹ค. @@ -479,7 +479,7 @@ UserProfileServiceTest ํ˜„์žฌ Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค ์˜์กด: ```text -UserProfileService +PersonalizationProfileService - User ๊ด€์‹ฌ์‚ฌ - ReadPost - Bookmark @@ -492,8 +492,8 @@ UserProfileService ์ •๋ฆฌ ๋ฐฉํ–ฅ: -- `UserProfileDocument`๋ฅผ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•œ๋‹ค. -- `UserProfileService`๋ฅผ User Account ์„œ๋น„์Šค๊ฐ€ ์•„๋‹Œ Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค๋กœ ์œ„์น˜๋ฅผ ์žฌ์ •์˜ํ•œ๋‹ค. +- `PersonalizationProfileDocument`๋ฅผ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•œ๋‹ค. +- `PersonalizationProfileService`๋ฅผ User Account ์„œ๋น„์Šค๊ฐ€ ์•„๋‹Œ Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค๋กœ ์œ„์น˜๋ฅผ ์žฌ์ •์˜ํ•œ๋‹ค. - ๊ด€์‹ฌ์‚ฌ ๋ณ€๊ฒฝ/์˜จ๋ณด๋”ฉ ์™„๋ฃŒ๋Š” ์žฅ๊ธฐ์ ์œผ๋กœ `UserInterestsChanged`, `OnboardingCompleted` ์ด๋ฒคํŠธ๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค. ๋ถ„๋ฆฌ ํ›„๋ณด (์ ์ง„์ ์œผ๋กœ ์ ์šฉ): @@ -514,7 +514,7 @@ PersonalizedProfileRepository ##### ์™œ ๋„ค ๋ฒˆ์งธ์ธ๊ฐ€ - ๋ณต์žก๋„๊ฐ€ ๋†’๋‹ค. -- Elasticsearch, Personalization Profile(`UserProfileDocument`), Activity, Post์— ๋ชจ๋‘ ์˜์กดํ•œ๋‹ค. +- Elasticsearch, Personalization Profile(`PersonalizationProfileDocument`), Activity, Post์— ๋ชจ๋‘ ์˜์กดํ•œ๋‹ค. - ํ…Œ์ŠคํŠธ ์—†์ด ๊ฑด๋“œ๋ฆฌ๋ฉด ์œ„ํ—˜ํ•˜๋‹ค. ##### ๋ชฉํ‘œ ๋ชจ๋ธ @@ -587,7 +587,7 @@ SearchServiceImplTest - Elasticsearch ํ˜ธ์ถœ์„ adapter๋กœ ๊ฐ์‹ธ๊ธฐ - `PostDocument`๋ฅผ ๊ฒ€์ƒ‰ read model๋กœ ๋ช…์‹œ -- `UserProfileDocument`๋ฅผ Personalization Profile read model๋กœ ๋ช…์‹œ +- `PersonalizationProfileDocument`๋ฅผ Personalization Profile read model๋กœ ๋ช…์‹œ - Activity์˜ ๋ถ๋งˆํฌ ์—ฌ๋ถ€ ์กฐํšŒ๋ฅผ query composition์œผ๋กœ ์œ ์ง€ํ•˜๋˜ ํฌํŠธ ๋„์ž… ๊ฒ€ํ†  --- @@ -651,7 +651,7 @@ src/test/java/com/techfork/domain user UserTest InterestCommandServiceTest - UserProfileServiceTest + PersonalizationProfileServiceTest recommendation MmrServiceTest @@ -686,7 +686,7 @@ src/test/java/com/techfork/domain ```text [ ] P0 ํ…Œ์ŠคํŠธ๊ฐ€ ๋ชจ๋‘ ์กด์žฌํ•˜๊ณ  ./gradlew test -PexcludeIntegration ํ†ต๊ณผ -[ ] UserProfileServiceTest๋กœ Personalization Profile ์ƒ์„ฑ ํ๋ฆ„ ๋ณดํ˜ธ +[ ] PersonalizationProfileServiceTest๋กœ Personalization Profile ์ƒ์„ฑ ํ๋ฆ„ ๋ณดํ˜ธ [ ] MmrServiceTest + LlmRecommendationServiceTest๋กœ ์ถ”์ฒœ ์ƒ์„ฑ ํ•ต์‹ฌ ํ๋ฆ„ ๋ณดํ˜ธ [ ] SearchServiceImplTest๋กœ ์ผ๋ฐ˜/๊ฐœ์ธํ™” ๊ฒ€์ƒ‰ ํšŒ๊ท€ ๋ณดํ˜ธ [ ] User Account aggregate ์ฑ…์ž„๊ณผ Personalization Profile ์ƒ์„ฑ ์ฑ…์ž„์ด @@ -737,7 +737,7 @@ TechnicalPostIndexed 4. Post ๋„๋ฉ”์ธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ 5. Post๋ฅผ โ€œ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€โ€ ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌ 6. User ๊ด€์‹ฌ์‚ฌ/์˜จ๋ณด๋”ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ -7. UserProfileService ํ…Œ์ŠคํŠธ ์ž‘์„ฑ +7. PersonalizationProfileService ํ…Œ์ŠคํŠธ ์ž‘์„ฑ 8. Personalization Profile ์ฑ…์ž„ ๋ถ„๋ฆฌ 9. Recommendation ํ…Œ์ŠคํŠธ ์ž‘์„ฑ 10. PersonalizedProfileGenerated ์ด๋ฒคํŠธ ๋„์ž… @@ -802,8 +802,8 @@ TechnicalPostIndexed - ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ ํŒŒ์‹ฑ ๋ฆฌํŒฉํ„ฐ๋ง: -- UserProfileDocument๋ฅผ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•จ -- UserProfileService ์ฑ…์ž„ ๋ถ„๋ฆฌ +- PersonalizationProfileDocument๋ฅผ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•จ +- PersonalizationProfileService ์ฑ…์ž„ ๋ถ„๋ฆฌ - PersonalizedProfileGenerated ์ด๋ฒคํŠธ ๋„์ž… ์ค€๋น„ ``` @@ -866,19 +866,19 @@ TechnicalPostIndexed ```text ๋ชฉํ‘œ: -- UserProfileService๋ฅผ Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค๋กœ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ•œ๋‹ค. +- PersonalizationProfileService๋ฅผ Personalization Profile ์ƒ์„ฑ ์„œ๋น„์Šค๋กœ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ•œ๋‹ค. ์„ ํ–‰ ํ…Œ์ŠคํŠธ: - ๊ด€์‹ฌ์‚ฌ, ์ฝ์€ ๊ฒŒ์‹œ๊ธ€, ๋ถ๋งˆํฌ, ๊ฒ€์ƒ‰ ๊ธฐ๋ก์„ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋กœ ์ˆ˜์ง‘ํ•œ๋‹ค. - LLM ์‘๋‹ต์—์„œ profileText์™€ keyKeywords๋ฅผ ํŒŒ์‹ฑํ•œ๋‹ค. - ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ fallback ์ •์ฑ…์„ ๋”ฐ๋ฅธ๋‹ค. -- profileText๋ฅผ ์ž„๋ฒ ๋”ฉํ•˜์—ฌ UserProfileDocument๋ฅผ ์ €์žฅํ•œ๋‹ค. +- profileText๋ฅผ ์ž„๋ฒ ๋”ฉํ•˜์—ฌ PersonalizationProfileDocument๋ฅผ ์ €์žฅํ•œ๋‹ค. - ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ ํ›„ ์ถ”์ฒœ ์ƒ์„ฑ์„ ํ˜ธ์ถœํ•œ๋‹ค. - ์ถ”์ฒœ ์ƒ์„ฑ ์‹คํŒจ๊ฐ€ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ €์žฅ์„ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋ฆฌํŒฉํ„ฐ๋ง: -- UserProfileDocument๋ฅผ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•จ -- UserProfileService ์ฑ…์ž„ ๋ถ„๋ฆฌ (PersonalizedProfileGenerated ์ด๋ฒคํŠธ ๋„์ž… ์ค€๋น„) +- PersonalizationProfileDocument๋ฅผ Personalization Profile projection์œผ๋กœ ๋ช…ํ™•ํžˆ ํ•จ +- PersonalizationProfileService ์ฑ…์ž„ ๋ถ„๋ฆฌ (PersonalizedProfileGenerated ์ด๋ฒคํŠธ ๋„์ž… ์ค€๋น„) ``` ### 5.8 ์ž‘์—… ๋‹จ์œ„ 8: 1์ฐจ ์ด๋ฒคํŠธ ๋„์ž… @@ -893,11 +893,11 @@ TechnicalPostIndexed ๋„์ž… ์ˆœ์„œ: 1. UserInterestsChanged - - InterestCommandService๊ฐ€ UserProfileService๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ์ด๋ฒคํŠธ ๋ฐœํ–‰ + - InterestCommandService๊ฐ€ PersonalizationProfileService๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ์ด๋ฒคํŠธ ๋ฐœํ–‰ - ๋ฆฌ์Šค๋„ˆ: @TransactionalEventListener(AFTER_COMMIT) + @Async 2. PersonalizedProfileGenerated - - UserProfileService๊ฐ€ LlmRecommendationService๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ์ด๋ฒคํŠธ ๋ฐœํ–‰ + - PersonalizationProfileService๊ฐ€ LlmRecommendationService๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ์ด๋ฒคํŠธ ๋ฐœํ–‰ - ๋ฆฌ์Šค๋„ˆ: ์ถ”์ฒœ ์ƒ์„ฑ ํŠธ๋ฆฌ๊ฑฐ 3. TechnicalPostIndexed diff --git a/docs/domain-strategy.md b/docs/domain-strategy.md index d7a9486a..6d41d7bd 100644 --- a/docs/domain-strategy.md +++ b/docs/domain-strategy.md @@ -52,11 +52,11 @@ TechFork์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์€ ๋‹ค์Œ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. - `SearchServiceImpl` - `LlmRecommendationService` - `MmrService` -- `UserProfileService` +- `PersonalizationProfileService` - `SummaryExtractionService` - `ContentChunkerService` - `PostEmbeddingProcessor` -- `PostDocument`, `UserProfileDocument` +- `PostDocument`, `PersonalizationProfileDocument` #### ์ง€์› ํ•˜์œ„ ๋„๋ฉ”์ธ Supporting Subdomains @@ -87,7 +87,7 @@ TechFork์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์€ ๋‹ค์Œ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. - ๋‹ค๋งŒ Auth / Security, Activity, Notification์ด ๊ธฐ๋Œ€๋Š” ์‚ฌ์šฉ์ž ์ •์ฒด์„ฑ ๊ฒฝ๊ณ„๋ฅผ ์ œ๊ณตํ•œ๋‹ค. - `Personalization Profile` ์ปจํ…์ŠคํŠธ๋Š” ํ•ต์‹ฌ ํ•˜์œ„ ๋„๋ฉ”์ธ์— ๊ฐ€๊น๋‹ค. - ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ, ํ”„๋กœํ•„ ๋ฒกํ„ฐ, ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ, ์žฌ์ƒ์„ฑ ์ •์ฑ…์€ ๊ฒ€์ƒ‰/์ถ”์ฒœ ํ’ˆ์งˆ์˜ ์ค‘์‹ฌ์ด๋‹ค. - - ํ˜„์žฌ ๊ตฌํ˜„์—์„œ๋Š” `UserProfileDocument`๊ฐ€ ๋…๋ฆฝ aggregate๋ณด๋‹ค read model/projection ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•˜๊ณ , ์ƒ์„ฑ ์ฑ…์ž„๋„ `domain/user` ์•„๋ž˜ ์„œ๋น„์Šค์— ํ•จ๊ป˜ ๋ฌถ์—ฌ ์žˆ๋‹ค. + - ํ˜„์žฌ ๊ตฌํ˜„์—์„œ๋Š” `PersonalizationProfileDocument`๊ฐ€ ๋…๋ฆฝ aggregate๋ณด๋‹ค read model/projection ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•˜๊ณ , ์ƒ์„ฑ ์ฑ…์ž„์€ `domain/personalization` ์„œ๋น„์Šค๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค. - `Post / Content` ์ปจํ…์ŠคํŠธ ์ „์ฒด๊ฐ€ ํ•ต์‹ฌ์€ ์•„๋‹ˆ๋‹ค. - ๋‹จ์ˆœ ๋ชฉ๋ก/์ƒ์„ธ ์กฐํšŒ๋Š” ์ง€์› ํ•˜์œ„ ๋„๋ฉ”์ธ์ด๋‹ค. - ์š”์•ฝ, ํ‚ค์›Œ๋“œ ์ถ”์ถœ, ์ฒญํฌ, ์ž„๋ฒ ๋”ฉ, ๊ฒ€์ƒ‰ ๋ฌธ์„œํ™”๋Š” ํ•ต์‹ฌ ํ•˜์œ„ ๋„๋ฉ”์ธ์— ๊ฐ€๊น๋‹ค. @@ -119,26 +119,26 @@ TechFork์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์€ ๋‹ค์Œ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ˜„์žฌ ๋ฌธ์„œ ๊ธฐ์ค€ ๊ฒฐ๋ก ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. - **์ „๋žต ๋ฌธ์„œ์™€ glossary์—์„œ๋Š” `User Account`์™€ `Personalization Profile`์„ ๋ณ„๋„ ์ปจํ…์ŠคํŠธ๋กœ ๋ณธ๋‹ค.** -- ๋‹ค๋งŒ **ํ˜„์žฌ ๊ตฌํ˜„ ํŒจํ‚ค์ง€๋Š” ์•„์ง `domain/user` ์•„๋ž˜์— ํ•จ๊ป˜ ์กด์žฌํ•œ๋‹ค.** +- ํ˜„์žฌ ๊ตฌํ˜„์€ `domain/useraccount`์™€ `domain/personalization`์œผ๋กœ ๋ฌผ๋ฆฌ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๋‹ค. ์˜๋ฏธ: 1. `User` aggregate๋Š” ๋‹น๋ถ„๊ฐ„ `User Account` ์ปจํ…์ŠคํŠธ์˜ ํ•ต์‹ฌ ๋ฃจํŠธ๋กœ ๋ณธ๋‹ค. -2. `UserProfileDocument`๋Š” `Personalization Profile` ์ปจํ…์ŠคํŠธ์˜ ํ•ต์‹ฌ projection/read model๋กœ ๋ณธ๋‹ค. +2. `PersonalizationProfileDocument`๋Š” `Personalization Profile` ์ปจํ…์ŠคํŠธ์˜ ํ•ต์‹ฌ projection/read model๋กœ ๋ณธ๋‹ค. 3. Search/Recommendation๊ณผ์˜ ๊ด€๊ณ„ ํ•ด์„์€ `User Account`์™€ `Personalization Profile`์„ ๋ถ„๋ฆฌํ•ด์„œ ๋ณธ๋‹ค. -4. ํŒจํ‚ค์ง€ ๋ถ„๋ฆฌ, ํฌํŠธ ๋ถ„๋ฆฌ, ์ด๋ฒคํŠธ ๋ถ„๋ฆฌ๋Š” ํ›„์† ๋ฆฌํŒฉํ„ฐ๋ง ๊ณผ์ œ๋กœ ๋‚จ๊ธด๋‹ค. +4. ํŒจํ‚ค์ง€ ๋ถ„๋ฆฌ๋Š” ์™„๋ฃŒ๋˜์—ˆ๊ณ , ํฌํŠธ ๋ถ„๋ฆฌ์™€ ์ด๋ฒคํŠธ ๋ถ„๋ฆฌ๋Š” ํ›„์† ๋ฆฌํŒฉํ„ฐ๋ง ๊ณผ์ œ๋กœ ๋‚จ๊ธด๋‹ค. -ํ˜„์žฌ ํŒจํ‚ค์ง€๋ฅผ ์œ ์ง€ํ•˜๋Š” ์ด์œ : +ํ˜„์žฌ ์ƒํƒœ ๋ฉ”๋ชจ: -1. `domain/user` ๋‚ด๋ถ€์—์„œ ๊ณ„์ •/์˜จ๋ณด๋”ฉ/๊ด€์‹ฌ์‚ฌ/๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ์ด ์•„์ง ํ•จ๊ป˜ ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค. -2. `UserProfileService`๋Š” Activity/Post/Recommendation์„ ์กฐํ•ฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•˜์ง€๋งŒ, ์ด๋ฅผ ๋’ท๋ฐ›์นจํ•˜๋Š” ๋…๋ฆฝ ํŒจํ‚ค์ง€/ํฌํŠธ/์ด๋ฒคํŠธ ๊ฒฝ๊ณ„๋Š” ์•„์ง ์—†๋‹ค. -3. `UserProfileDocument`๋Š” ๋…๋ฆฝ write aggregate๋ณด๋‹ค ๊ฒ€์ƒ‰ยท์ถ”์ฒœ์šฉ read model์— ๊ฐ€๊น๋‹ค. +1. ํŒจํ‚ค์ง€๋Š” ๋ถ„๋ฆฌ๋˜์—ˆ์ง€๋งŒ, `InterestCommandService` โ†’ `PersonalizationProfileService` ์ง์ ‘ ํ˜ธ์ถœ์€ ์•„์ง ์œ ์ง€๋œ๋‹ค. +2. `PersonalizationProfileService`๋Š” Activity/Post/Recommendation์„ ์กฐํ•ฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•˜์ง€๋งŒ, ์ด๋ฅผ ๋’ท๋ฐ›์นจํ•˜๋Š” ๋…๋ฆฝ ํฌํŠธ/์ด๋ฒคํŠธ ๊ฒฝ๊ณ„๋Š” ์•„์ง ์—†๋‹ค. +3. `PersonalizationProfileDocument`๋Š” ๋…๋ฆฝ write aggregate๋ณด๋‹ค ๊ฒ€์ƒ‰ยท์ถ”์ฒœ์šฉ read model์— ๊ฐ€๊น๋‹ค. ํ–ฅํ›„ ์•„๋ž˜ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋˜๋ฉด ์‹ค์ œ ํŒจํ‚ค์ง€/๊ตฌํ˜„๋„ ๋‘˜๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์„ ๋‹ค์‹œ ๊ฒ€ํ† ํ•œ๋‹ค. 1. `OnboardingCompleted`, `UserInterestsChanged`, `PersonalizedProfileGenerated` ๊ฐ™์€ ์ด๋ฒคํŠธ ํ๋ฆ„์ด ์ •์ฐฉ๋  ๋•Œ 2. Search/Recommendation์ด ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ์ „์šฉ ํฌํŠธ/Published Language๋กœ ์†Œ๋น„ํ•  ๋•Œ -3. `domain/user` ๋‚ด๋ถ€๊ฐ€ ๊ณ„์ •/์˜จ๋ณด๋”ฉ๊ณผ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ์œผ๋กœ ํŒจํ‚ค์ง€ ์ˆ˜์ค€์—์„œ ๋ถ„๋ฆฌ๋  ๋•Œ +3. ํŒจํ‚ค์ง€ ๋ถ„๋ฆฌ ์ดํ›„ `useraccount` โ†” `personalization` direct dependency๋ฅผ ํฌํŠธ/์ด๋ฒคํŠธ๋กœ ์น˜ํ™˜ํ•  ๋•Œ 4. ๊ฐœ์ธํ™” ํ”„๋กœํ•„์ด ๋…๋ฆฝ ์ˆ˜๋ช…์ฃผ๊ธฐ, ์žฌ์ƒ์„ฑ ์ •์ฑ…, ์‹คํŒจ ๋ณต๊ตฌ ์ •์ฑ…์„ ๊ฐ€์ง„ ๋ชจ๋ธ๋กœ ์ปค์งˆ ๋•Œ --- @@ -216,20 +216,20 @@ U/D ํ‘œ๊ธฐ: **U** = Upstream(์ƒ๋ฅ˜, ๊ณต๊ธ‰์ž), **D** = Downstream(ํ•˜๋ฅ˜, ์†Œ | Source / Ingestion โ†” Post / Content | ์ถœ์ฒ˜ ์‹๋ณ„/ํ‘œ์‹œ | `Post / Content` | `Source / Ingestion` | **์ง์ ‘ ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ** (์•ฝํ•œ SK์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ๊ตฌํ˜„ ๊ฒฐํ•ฉ) | **OHS/PL** | source reference / ์ถœ์ฒ˜ ์Šค๋ƒ…์ƒท ์†Œ๋น„ | `Post.techBlog`๊ฐ€ `TechBlog` ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ์ฐธ์กฐ | ์ง€๊ธˆ์€ direct entity reference๋ผ์„œ SK์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ๋ชฉํ‘œ ์ƒํƒœ๋Š” `sourceId`๋‚˜ ์ถœ์ฒ˜ ์Šค๋ƒ…์ƒท ์†Œ๋น„์— ๊ฐ€๊น๋‹ค. | | Activity โ†” User Account | ํ–‰๋™ ์ฃผ์ฒด ์‹๋ณ„ | `Activity` | `User Account` | **์ง์ ‘ ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ** (์˜๋„์ƒ ์ตœ์†Œ SK) | **SK** (์ตœ์†Œ Shared Identity) | ์‚ฌ์šฉ์ž ๊ท€์† ์ฐธ์กฐ | `ReadPost`, `Bookmark`, `SearchHistory`๊ฐ€ `User`๋ฅผ ์ง์ ‘ ์ฐธ์กฐ | Activity๋Š” ์‚ฌ์šฉ์ž ์ •์ฒด์„ฑ๋งŒ ์•Œ๋ฉด ๋œ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” `UserId` ์ค‘์‹ฌ ์ฐธ์กฐ๊ฐ€ ๋” ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. | | Activity โ†” Post / Content | ํ–‰๋™ ๋Œ€์ƒ ์‹๋ณ„ | `Activity` | `Post / Content` | **CF** | **CF** (Conformist) | ์ง์ ‘ aggregate ์ฐธ์กฐ | `ReadPost`, `Bookmark`๊ฐ€ `Post`๋ฅผ ์ง์ ‘ ์ฐธ์กฐ | Activity๋Š” ํ–‰๋™ ๋Œ€์ƒ์ธ ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€ ๋ชจ๋ธ์„ ๋”ฐ๋ฅธ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” `PostId` ์ค‘์‹ฌ ์ฐธ์กฐ ์ถ•์†Œ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. | -| User Account โ†” Personalization Profile | ํ”„๋กœํ•„ ์ƒ์„ฑ ํŠธ๋ฆฌ๊ฑฐ | `Personalization Profile` | `User Account` | **๋™๊ธฐ ์ง์ ‘ ํ˜ธ์ถœ** | **OHS/PL** (Open Host Service / Published Language) | ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ / Published Language | ํ˜„์žฌ๋Š” `UserCommandService`, `InterestCommandService`๊ฐ€ `UserProfileService`๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ | ๋ชฉํ‘œ ์ƒํƒœ๋Š” `OnboardingCompleted`, `UserInterestsChanged` ๊ฐ™์€ ์ด๋ฒคํŠธ handoff๋‹ค. | -| Personalization Profile โ†” Activity | ํ–‰๋™ ์š”์•ฝ ์ž…๋ ฅ | `Personalization Profile` | `Activity` | **CS** | **OHS/PL** | ์กฐํšŒ ํฌํŠธ / `UserActivitySummary` ๊ฐ™์€ Published Language | `UserProfileService`๊ฐ€ Activity repository๋ฅผ ์ง์ ‘ ์กฐํšŒ | ์ด seam์€ ํ˜„์žฌ๋Š” Customer-Supplier์— ๊ฐ€๊น์ง€๋งŒ, ๋ชฉํ‘œ ์ƒํƒœ๋Š” โ€œํ–‰๋™ ์š”์•ฝ ์–ธ์–ด๋ฅผ ์†Œ๋น„ํ•œ๋‹คโ€๋Š” OHS/PL์ด ๋” ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. | -| Personalization Profile โ†” Post / Content | ๊ฒŒ์‹œ๊ธ€ ๊ด€์‹ฌ ์‹ ํ˜ธ ์ž…๋ ฅ | `Personalization Profile` | `Post / Content` | **CS** | **OHS/PL** | ๊ฒŒ์‹œ๊ธ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ projection / ๊ฒฝ๋Ÿ‰ ํฌํŠธ | `UserProfileService`๊ฐ€ `PostKeyword`์™€ ๊ฒŒ์‹œ๊ธ€ ์ œ๋ชฉ์„ ์ง์ ‘ ์ฝ๋Š”๋‹ค | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ๊ฒŒ์‹œ๊ธ€ ์‹ ํ˜ธ๋ฅผ ์†Œ๋น„ํ•œ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” ๊ฒฝ๋Ÿ‰ projection/port๋กœ ์ •๋ฆฌ ๊ฐ€๋Šฅํ•˜๋‹ค. | +| User Account โ†” Personalization Profile | ํ”„๋กœํ•„ ์ƒ์„ฑ ํŠธ๋ฆฌ๊ฑฐ | `Personalization Profile` | `User Account` | **๋™๊ธฐ ์ง์ ‘ ํ˜ธ์ถœ** | **OHS/PL** (Open Host Service / Published Language) | ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ / Published Language | ํ˜„์žฌ๋Š” `UserCommandService`, `InterestCommandService`๊ฐ€ `PersonalizationProfileService`๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ | ๋ชฉํ‘œ ์ƒํƒœ๋Š” `OnboardingCompleted`, `UserInterestsChanged` ๊ฐ™์€ ์ด๋ฒคํŠธ handoff๋‹ค. | +| Personalization Profile โ†” Activity | ํ–‰๋™ ์š”์•ฝ ์ž…๋ ฅ | `Personalization Profile` | `Activity` | **CS** | **OHS/PL** | ์กฐํšŒ ํฌํŠธ / `UserActivitySummary` ๊ฐ™์€ Published Language | `PersonalizationProfileService`๊ฐ€ Activity repository๋ฅผ ์ง์ ‘ ์กฐํšŒ | ์ด seam์€ ํ˜„์žฌ๋Š” Customer-Supplier์— ๊ฐ€๊น์ง€๋งŒ, ๋ชฉํ‘œ ์ƒํƒœ๋Š” โ€œํ–‰๋™ ์š”์•ฝ ์–ธ์–ด๋ฅผ ์†Œ๋น„ํ•œ๋‹คโ€๋Š” OHS/PL์ด ๋” ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. | +| Personalization Profile โ†” Post / Content | ๊ฒŒ์‹œ๊ธ€ ๊ด€์‹ฌ ์‹ ํ˜ธ ์ž…๋ ฅ | `Personalization Profile` | `Post / Content` | **CS** | **OHS/PL** | ๊ฒŒ์‹œ๊ธ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ projection / ๊ฒฝ๋Ÿ‰ ํฌํŠธ | `PersonalizationProfileService`๊ฐ€ `PostKeyword`์™€ ๊ฒŒ์‹œ๊ธ€ ์ œ๋ชฉ์„ ์ง์ ‘ ์ฝ๋Š”๋‹ค | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ๊ฒŒ์‹œ๊ธ€ ์‹ ํ˜ธ๋ฅผ ์†Œ๋น„ํ•œ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” ๊ฒฝ๋Ÿ‰ projection/port๋กœ ์ •๋ฆฌ ๊ฐ€๋Šฅํ•˜๋‹ค. | | Personalization Profile โ†” Recommendation | ํ”„๋กœํ•„ ์ƒ์„ฑ ์™„๋ฃŒ handoff | `Recommendation` | `Personalization Profile` | **๊ฐ•ํ•œ CS + ๋™๊ธฐ ์ง์ ‘ ํ˜ธ์ถœ** | **OHS/PL** | ํ”„๋กœํ•„ ์ƒ์„ฑ ์™„๋ฃŒ ์ด๋ฒคํŠธ | ํ˜„์žฌ๋Š” ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ ์งํ›„ ์ถ”์ฒœ ์ƒ์„ฑ์„ ์ง์ ‘ ํ˜ธ์ถœ | ๋ชฉํ‘œ ์ƒํƒœ๋Š” `PersonalizedProfileGenerated` ์ด๋ฒคํŠธ๋กœ ์ถ”์ฒœ ์žฌ์ƒ์„ฑ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค. | | Search โ†” Post / Content | ๊ฒ€์ƒ‰์šฉ ๊ฒŒ์‹œ๊ธ€ projection | `Search` | `Post / Content` | **OHS/PL** | **OHS/PL** (Open Host Service / Published Language) | Read Model / Projection ์†Œ๋น„ | `PostDocument`, `PostRepository` ์ฐธ์กฐ | Post๊ฐ€ `PostDocument` projection์„ ์ œ๊ณตํ•˜๊ณ  Search๊ฐ€ ์ด๋ฅผ ์†Œ๋น„ํ•œ๋‹ค. | -| Search โ†” Personalization Profile | ๊ฐœ์ธํ™” ๋ฆฌ๋žญํ‚น ์ž…๋ ฅ | `Search` | `Personalization Profile` | **OHS/PL** | **OHS/PL** (Open Host Service / Published Language) | Read Model / Projection ์†Œ๋น„ | `UserProfileDocument` ์ฐธ์กฐ | Search๋Š” `UserProfileDocument`๋ฅผ ์†Œ๋น„ํ•ด ๊ฐœ์ธํ™” ๋ฆฌ๋žญํ‚น์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. | +| Search โ†” Personalization Profile | ๊ฐœ์ธํ™” ๋ฆฌ๋žญํ‚น ์ž…๋ ฅ | `Search` | `Personalization Profile` | **OHS/PL** | **OHS/PL** (Open Host Service / Published Language) | Read Model / Projection ์†Œ๋น„ | `PersonalizationProfileDocument` ์ฐธ์กฐ | Search๋Š” `PersonalizationProfileDocument`๋ฅผ ์†Œ๋น„ํ•ด ๊ฐœ์ธํ™” ๋ฆฌ๋žญํ‚น์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. | | Search โ†” Activity | ๋ถ๋งˆํฌ ์—ฌ๋ถ€ ์กฐํšŒ | `Search` | `Activity` | **CS** | **CS** (Customer-Supplier) | Query Composition | `BookmarkRepository`๋กœ ๋ถ๋งˆํฌ ์—ฌ๋ถ€ ์กฐํšŒ | ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์‘๋‹ต ์กฐ๋ฆฝ์„ ์œ„ํ•œ ์กฐํšŒ ์กฐํ•ฉ์ด๋‹ค. | | Recommendation โ†” User Account | ์ถ”์ฒœ ๋Œ€์ƒ ์‚ฌ์šฉ์ž ์‹๋ณ„ | `Recommendation` | `User Account` | **์ง์ ‘ ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ** (์˜๋„์ƒ ์ตœ์†Œ SK) | **SK** (์ตœ์†Œ Shared Identity) | ์ถ”์ฒœ ๋Œ€์ƒ ์‚ฌ์šฉ์ž ์‹๋ณ„ | `User` ์ง์ ‘ ์ฐธ์กฐ | ์ถ”์ฒœ ๋Œ€์ƒ ์‚ฌ์šฉ์ž์˜ ์ •์ฒด์„ฑ๊ณผ ์ƒํƒœ๋ฅผ ์•Œ์•„์•ผ ํ•œ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” ์ตœ์†Œ ์‚ฌ์šฉ์ž ์‹๋ณ„์ž/์ƒํƒœ ๊ณต์œ ๋กœ ์ถ•์†Œ ๊ฐ€๋Šฅํ•˜๋‹ค. | -| Recommendation โ†” Personalization Profile | ํ”„๋กœํ•„ ๋ฒกํ„ฐ/ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ ์ž…๋ ฅ | `Recommendation` | `Personalization Profile` | **OHS/PL** | **OHS/PL** (Open Host Service / Published Language) | Read Model ์†Œ๋น„ | `UserProfileDocument` ์ฐธ์กฐ | Recommendation์˜ ํ•ต์‹ฌ ์ž…๋ ฅ์€ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ๋ฒกํ„ฐ์™€ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ๋‹ค. | +| Recommendation โ†” Personalization Profile | ํ”„๋กœํ•„ ๋ฒกํ„ฐ/ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ ์ž…๋ ฅ | `Recommendation` | `Personalization Profile` | **OHS/PL** | **OHS/PL** (Open Host Service / Published Language) | Read Model ์†Œ๋น„ | `PersonalizationProfileDocument` ์ฐธ์กฐ | Recommendation์˜ ํ•ต์‹ฌ ์ž…๋ ฅ์€ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ๋ฒกํ„ฐ์™€ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ๋‹ค. | | Recommendation โ†” Activity | ์ฝ์€ ๊ฒŒ์‹œ๊ธ€ ์ œ์™ธ ์‹ ํ˜ธ | `Recommendation` | `Activity` | **CS** | **CS** (Customer-Supplier) | Query Composition / ์ •์ฑ… ํฌํŠธ | `ReadPostRepository`๋กœ ์ฝ์€ ๊ฒŒ์‹œ๊ธ€ ์ œ์™ธ | ์ถ”์ฒœ ์ •์ฑ…์— ํ•„์š”ํ•œ ์ œ์™ธ ์‹ ํ˜ธ๋ฅผ Activity๊ฐ€ ๊ณต๊ธ‰ํ•œ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” exclusion set ํฌํŠธ๋กœ ์ขํž ์ˆ˜ ์žˆ๋‹ค. | | Recommendation โ†” Post / Content | ์ถ”์ฒœ ํ›„๋ณด ํƒ์ƒ‰ | `Recommendation` | `Post / Content` | **OHS/PL** | **OHS/PL** | ์ถ”์ฒœ ํ›„๋ณด projection ์†Œ๋น„ | `PostDocument` ์ฐธ์กฐ | ์ถ”์ฒœ ํ›„๋ณด ํƒ์ƒ‰์€ Post projection ์†Œ๋น„๋กœ ์ถฉ๋ถ„ํ•œ ๊ฒƒ์ด ๋ชฉํ‘œ ์ƒํƒœ๋‹ค. | | Recommendation โ†” Post / Content | ์ถ”์ฒœ ์ €์žฅ ๋Œ€์ƒ ์‹๋ณ„ | `Recommendation` | `Post / Content` | **์ง์ ‘ ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ** | **SK** (์ตœ์†Œ Shared Identity) | `PostId`/์ฐธ์กฐ ์‹๋ณ„ | ํ˜„์žฌ๋Š” JPA `Post` reference๋ฅผ ์ง์ ‘ ์ €์žฅ | ํ›„๋ณด ํƒ์ƒ‰ seam๊ณผ ์ €์žฅ seam์„ ๋ถ„๋ฆฌํ•ด์„œ ๋ณธ๋‹ค. ์ €์žฅ ์ชฝ์€ ์ตœ์†Œ ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€ ์‹๋ณ„ ๊ณต์œ ๋กœ ์ถ•์†Œํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๋‹ค. | | Post / User / Personalization Profile / Recommendation / Search โ†” LLM/Embedding Provider | ๋ชจ๋ธ ์ œ๊ณต์ž ์—ฐ๋™ | `Post / User / Personalization Profile / Recommendation / Search` | LLM/Embedding Provider | **ACL** | **ACL** (Anti-Corruption Layer) | Port & Adapter | `LlmClient`, `EmbeddingClient` | ์™ธ๋ถ€ ๋ชจ๋ธ ์ œ๊ณต์ž๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ฐจ๋‹จํ•œ๋‹ค. ์ œ๊ณต์ž ๋ณ€๊ฒฝ ์‹œ ACL๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋œ๋‹ค. | -| Search / Recommendation / Personalization Profile โ†” Elasticsearch | ์ฝ๊ธฐ ๋ชจ๋ธ ์ €์žฅ/์กฐํšŒ | `Search / Recommendation / Personalization Profile` | Elasticsearch | **์ฝ๊ธฐ ๋ชจ๋ธ ์ธํ”„๋ผ + ACL ์„ฑ๊ฒฉ** | **ACL** (Anti-Corruption Layer) | Projection / Search Read Model | `PostDocument`, `UserProfileDocument` | ๊ฒ€์ƒ‰/์ถ”์ฒœ/๊ฐœ์ธํ™” ํ”„๋กœํ•„์šฉ ์ฝ๊ธฐ ๋ชจ๋ธ์ด๋‹ค. ES ์ธ๋ฑ์Šค ๊ตฌ์กฐ ๋ณ€๊ฒฝ์ด ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ์ „ํŒŒ๋˜์ง€ ์•Š๋„๋ก ์ฐจ๋‹จํ•œ๋‹ค. | +| Search / Recommendation / Personalization Profile โ†” Elasticsearch | ์ฝ๊ธฐ ๋ชจ๋ธ ์ €์žฅ/์กฐํšŒ | `Search / Recommendation / Personalization Profile` | Elasticsearch | **์ฝ๊ธฐ ๋ชจ๋ธ ์ธํ”„๋ผ + ACL ์„ฑ๊ฒฉ** | **ACL** (Anti-Corruption Layer) | Projection / Search Read Model | `PostDocument`, `PersonalizationProfileDocument` | ๊ฒ€์ƒ‰/์ถ”์ฒœ/๊ฐœ์ธํ™” ํ”„๋กœํ•„์šฉ ์ฝ๊ธฐ ๋ชจ๋ธ์ด๋‹ค. ES ์ธ๋ฑ์Šค ๊ตฌ์กฐ ๋ณ€๊ฒฝ์ด ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ์ „ํŒŒ๋˜์ง€ ์•Š๋„๋ก ์ฐจ๋‹จํ•œ๋‹ค. | | Source / Ops โ†” Discord Webhook | ์šด์˜ ์•Œ๋ฆผ ์ „์†ก | `Source / Ops` | Discord Webhook | **ACL** | **ACL** (Anti-Corruption Layer) | External Adapter | `WebhookNotificationService` | ์šด์˜ ์•Œ๋ฆผ์šฉ ์™ธ๋ถ€ ํ†ตํ•ฉ์ด๋‹ค. ๋„๋ฉ”์ธ ํ•ต์‹ฌ ๋ชจ๋ธ๊ณผ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค. | ### 3.3 ํ˜„์žฌ ํ†ตํ•ฉ ์Šคํƒ€์ผ ํ‰๊ฐ€ @@ -242,7 +242,7 @@ U/D ํ‘œ๊ธฐ: **U** = Upstream(์ƒ๋ฅ˜, ๊ณต๊ธ‰์ž), **D** = Downstream(ํ•˜๋ฅ˜, ์†Œ - ์ดˆ๊ธฐ ์„œ๋น„์Šค ๊ทœ๋ชจ์—์„œ๋Š” ๋น ๋ฅด๊ฒŒ ๊ธฐ๋Šฅ์„ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. - ๋ฆฌ์Šคํฌ - `Source โ†” Post`์ฒ˜๋Ÿผ ์–‘๋ฐฉํ–ฅ ๋„๋ฉ”์ธ ์˜์กด์ด ์ƒ๊ธด๋‹ค. - - `UserProfileService`๊ฐ€ Activity, Post, Recommendation์„ ๋ชจ๋‘ ์•Œ์•„ ๊ฐœ์ธํ™” ํ๋ฆ„์˜ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’๋‹ค. + - `PersonalizationProfileService`๊ฐ€ Activity, Post, Recommendation์„ ๋ชจ๋‘ ์•Œ์•„ ๊ฐœ์ธํ™” ํ๋ฆ„์˜ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’๋‹ค. - Search/Recommendation์ด ์—ฌ๋Ÿฌ ์ปจํ…์ŠคํŠธ์˜ repository/read model์„ ์ง์ ‘ ์กฐํ•ฉํ•œ๋‹ค. - ๊ฐœ์„  ์šฐ์„ ์ˆœ์œ„ 1. `PersonalizedProfileGenerated` ์ด๋ฒคํŠธ๋กœ **๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ**๊ณผ ์ถ”์ฒœ ์ƒ์„ฑ์„ ๋ถ„๋ฆฌํ•œ๋‹ค. @@ -268,7 +268,7 @@ U/D ํ‘œ๊ธฐ: **U** = Upstream(์ƒ๋ฅ˜, ๊ณต๊ธ‰์ž), **D** = Downstream(ํ•˜๋ฅ˜, ์†Œ [Personalization Profile] ์‚ฌ์šฉ์ž + ํ™œ๋™ ๋ฐ์ดํ„ฐ + ๊ฒŒ์‹œ๊ธ€ ์‹ ํ˜ธ - โ†’ ๊ฐœ์ธํ™” ํ”„๋กœํ•„(UserProfileDocument: profileText, profileVector, keyKeywords) + โ†’ ๊ฐœ์ธํ™” ํ”„๋กœํ•„(PersonalizationProfileDocument: profileText, profileVector, keyKeywords) [Activity] ์ฝ์€ ๊ฒŒ์‹œ๊ธ€(ReadPost) / ๊ฒ€์ƒ‰ ๊ธฐ๋ก(SearchHistory) / ๋ถ๋งˆํฌ(Bookmark) diff --git a/docs/test-gap-analysis.md b/docs/test-gap-analysis.md index 65a03250..d422b0dc 100644 --- a/docs/test-gap-analysis.md +++ b/docs/test-gap-analysis.md @@ -17,7 +17,7 @@ | Source / Ingestion | RSS reader, processor, writer, scheduler, crawling service ํ…Œ์ŠคํŠธ๊ฐ€ ์ž˜ ์žˆ์Œ | ํŒŒ์ดํ”„๋ผ์ธ ๋ณดํ˜ธ ์ˆ˜์ค€ ์ข‹์Œ | | Post / Content | ์กฐํšŒ API/repository๋Š” ๊ฐ•ํ•จ. ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ/์š”์•ฝ/์ž„๋ฒ ๋”ฉ ํ…Œ์ŠคํŠธ๋Š” ๋ถ€์กฑ | `Post = ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€` ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ํ…Œ์ŠคํŠธ ํ•„์š” | | User Account | ์˜จ๋ณด๋”ฉ/๊ด€์‹ฌ์‚ฌ/๊ณ„์ • ํ”„๋กœํ•„์€ ๊ฐ•ํ•จ | ์‚ฌ์šฉ์ž ๊ณ„์ • ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์™€ ์˜จ๋ณด๋”ฉ ํ๋ฆ„์€ ๋น„๊ต์  ์ž˜ ๋ณดํ˜ธ๋˜์–ด ์žˆ๋‹ค | -| Personalization Profile | ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ์•ˆ์ „๋ง์ด ์•ฝํ•จ | `UserProfileService`/`UserProfileDocument` ์ค‘์‹ฌ ๊ฐœ์ธํ™” ํ๋ฆ„ ๋ณดํ˜ธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค | +| Personalization Profile | ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ์•ˆ์ „๋ง์ด ์•ฝํ•จ | `PersonalizationProfileService`/`PersonalizationProfileDocument` ์ค‘์‹ฌ ๊ฐœ์ธํ™” ํ๋ฆ„ ๋ณดํ˜ธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค | | Recommendation | ์กฐํšŒ ์ชฝ์€ ์ผ๋ถ€ ์žˆ์Œ. ์ถ”์ฒœ ์ƒ์„ฑ/ํ›„๋ณด ํƒ์ƒ‰/MMR/์ด๋ ฅํ™” ํ…Œ์ŠคํŠธ๋Š” ๋ถ€์กฑ | DDD ์ „ํ™˜ ์ „ ๊ฐ€์žฅ ํฐ ๋ฆฌ์Šคํฌ ์ค‘ ํ•˜๋‚˜ | | Search | ์ผ๋ฐ˜ ์‹คํ–‰ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฑฐ์˜ ์—†์Œ. evaluation suite๋งŒ ์กด์žฌ | ์ผ๋ฐ˜ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ ๋ถ€์žฌ๊ฐ€ ํผ | | Auth / Security | ํ† ํฐ/ํ•„ํ„ฐ/์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ๋Š” ๋น„๊ต์  ๊ฐ•ํ•จ | OAuth handler/OIDC ์ผ๋ถ€ ๋ณด๊ฐ• ์—ฌ์ง€ | @@ -28,7 +28,7 @@ 1. **SearchServiceImpl ์ผ๋ฐ˜ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ ๋ถ€์žฌ** 2. **LlmRecommendationService / MmrService ํ…Œ์ŠคํŠธ ๋ถ€์žฌ** -3. **Personalization Profile(`UserProfileService`) ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ๋ถ€์žฌ** +3. **Personalization Profile(`PersonalizationProfileService`) ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ๋ถ€์žฌ** 4. **Post ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ถ€์žฌ** 5. **Post embedding pipeline ํ…Œ์ŠคํŠธ ๋ถ€์žฌ** @@ -76,7 +76,7 @@ | domain/post | 4 | 72 | Controller/repository/query service ์ค‘์‹ฌ | | domain/recommendation | 3 | 8 | ์กฐํšŒ/์ปจ๋ฒ„ํ„ฐ ์ค‘์‹ฌ, ์ƒ์„ฑ ๋กœ์ง ๋ถ€์กฑ | | domain/source | 10 | 38 | RSS/๋ฐฐ์น˜/์Šค์ผ€์ค„๋Ÿฌ/๋ฝ/์›นํ›… ์ปค๋ฒ„ ์ข‹์Œ | -| domain/user | 7 | 58 | User Account ์ค‘์‹ฌ. Personalization Profile ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ๋Š” ๋ณ„๋„ ์•ˆ์ „๋ง ๋ถ€์กฑ | +| domain/useraccount + domain/personalization | 8 | 61 | User Account service/controller/repository ์ปค๋ฒ„ + Personalization Profile ๊ธฐ๋ณธ unit ์•ˆ์ „๋ง ํ™•๋ณด | | global | 6 | 33 | Security, cache, util, integration base | | evaluation | 27 | 18 | ๊ฒ€์ƒ‰/์ถ”์ฒœ ํ’ˆ์งˆ ํ‰๊ฐ€ ๋ฐ fixture setup | @@ -258,35 +258,35 @@ ContentChunkerServiceTest | `InterestCommandServiceTest` | unit/mock | ๊ด€์‹ฌ์‚ฌ ์ €์žฅ, ๊ธฐ์กด ๊ด€์‹ฌ์‚ฌ clear, invalid keyword category | | `UserCommandServiceTest` | unit/mock | ์˜จ๋ณด๋”ฉ, ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ •, ํƒˆํ‡ด | | `UserQueryServiceTest` | unit/mock | ๊ณ„์ • ํ”„๋กœํ•„ ์กฐํšŒ | -| `evaluation/search/setup/UserProfileServiceTest` | evaluation-setup | ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ƒ์„ฑ์šฉ setup | +| `evaluation/search/setup/PersonalizationProfileServiceTest` | evaluation-setup | ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ์šฉ setup | #### ํ‰๊ฐ€ - **User Account ์ชฝ**์€ ์˜จ๋ณด๋”ฉ, ๊ด€์‹ฌ์‚ฌ, ๊ณ„์ • ํ”„๋กœํ•„, ํƒˆํ‡ด ํ๋ฆ„์ด ๋น„๊ต์  ์ž˜ ๋ณดํ˜ธ๋˜์–ด ์žˆ๋‹ค. -- ๋ฐ˜๋ฉด **Personalization Profile ์ชฝ**์€ ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ lane์— `UserProfileService` ์•ˆ์ „๋ง์ด ๊ฑฐ์˜ ์—†๋‹ค. -- ํ˜„์žฌ ์žˆ๋Š” `UserProfileServiceTest`๋Š” evaluation setup ์šฉ๋„๋ผ์„œ, ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ๋ฆฌํŒฉํ„ฐ๋ง ์•ˆ์ „๋ง์œผ๋กœ ๋ณด๊ธฐ ์–ด๋ ต๋‹ค. +- ๊ธฐ์กด์—๋Š” **Personalization Profile ์ชฝ** ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ lane์ด ๋น„์–ด ์žˆ์—ˆ์ง€๋งŒ, ์ด์ œ `PersonalizationProfileServiceTest` ๊ธฐ๋ณธ ์•ˆ์ „๋ง์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค. +- evaluation setup์šฉ `PersonalizationProfileServiceTest`๋Š” ์—ฌ์ „ํžˆ ๋ณ„๋„ ๋ชฉ์ ์ด๋ฏ€๋กœ, ์ผ๋ฐ˜ unit test lane๊ณผ ๊ตฌ๋ถ„ํ•ด์„œ ๋ณธ๋‹ค. #### ๋‚จ์€ ๊ฐญ | ์šฐ์„ ์ˆœ์œ„ | ๊ฐญ | ์ด์œ  | |---|---|---| -| P0 | `UserProfileServiceTest` ์ผ๋ฐ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ | Personalization Profile ์ƒ์„ฑ์€ ์ถ”์ฒœ/๊ฒ€์ƒ‰ ๊ฐœ์ธํ™”์˜ ํ•ต์‹ฌ์ด๋ฉฐ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’์Œ | +| ์™„๋ฃŒ | `PersonalizationProfileServiceTest` ์ผ๋ฐ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ | ํ™œ๋™ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘, ํŒŒ์‹ฑ, fallback, ์ €์žฅ, ์ถ”์ฒœ ์‹คํŒจ ๊ฒฉ๋ฆฌ ๊ธฐ๋ณธ ํ๋ฆ„ ๋ณดํ˜ธ ์™„๋ฃŒ | | P0 | LLM ์‘๋‹ต parsing ํ…Œ์ŠคํŠธ | `### PROFILE`, `### KEYWORDS` parsing ์‹คํŒจ ์‹œ ํ’ˆ์งˆ/์žฅ์•  ์˜ํ–ฅ | | P0 | ๊ด€์‹ฌ์‚ฌ ๋ณ€๊ฒฝ ํ›„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ ํŠธ๋ฆฌ๊ฑฐ ๊ฒ€์ฆ | `UserInterestsChanged` ์ด๋ฒคํŠธ ๋„์ž… ์ „ ํ˜„์žฌ ๋™์ž‘ ๋ณดํ˜ธ | | P1 | `UserTest` | User Account aggregate์˜ ์†Œ์…œ ์‚ฌ์šฉ์ž ์ƒ์„ฑ, ์˜จ๋ณด๋”ฉ ACTIVE, ํƒˆํ‡ด anonymization, reactivate ๊ทœ์น™ ๋ณดํ˜ธ | | P1 | `UserInterestCategory/UserInterestKeyword` ๋„๋ฉ”์ธ ํ…Œ์ŠคํŠธ | ๊ด€์‹ฌ ํ‚ค์›Œ๋“œ๊ฐ€ ์นดํ…Œ๊ณ ๋ฆฌ์— ์†ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™ ๋ช…์‹œ | -| P1 | `UserProfileSchedulerTest` | ๋งค์ผ 06:00 KST active user personalization profile regeneration ๋ณดํ˜ธ | +| P1 | `PersonalizationProfileSchedulerTest` | ๋งค์ผ 06:00 KST active user personalization profile regeneration ๋ณดํ˜ธ | | P2 | `InterestQueryServiceTest` | ๊ด€์‹ฌ์‚ฌ ์กฐํšŒ ๋ณ€ํ™˜ ๋กœ์ง ๋ณดํ˜ธ | #### ์ถ”์ฒœ ์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ ```text -UserProfileServiceTest +PersonalizationProfileServiceTest - ๊ด€์‹ฌ์‚ฌ, ์ฝ์€ ๊ฒŒ์‹œ๊ธ€, ๋ถ๋งˆํฌ, ๊ฒ€์ƒ‰ ๊ธฐ๋ก์„ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋กœ ์ˆ˜์ง‘ํ•œ๋‹ค. - ์ฝ์€ ์‹œ๊ฐ„์€ ์ฝ๊ธฐ ๋ชฐ์ž…๋„ ์ž์—ฐ์–ด๋กœ ๋ณ€ํ™˜๋œ๋‹ค. - LLM ์‘๋‹ต์—์„œ profileText์™€ keyKeywords๋ฅผ ํŒŒ์‹ฑํ•œ๋‹ค. - ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ fallback ์ •์ฑ…์„ ๋”ฐ๋ฅธ๋‹ค. -- profileText๋ฅผ ์ž„๋ฒ ๋”ฉํ•˜์—ฌ UserProfileDocument๋ฅผ ์ €์žฅํ•œ๋‹ค. +- profileText๋ฅผ ์ž„๋ฒ ๋”ฉํ•˜์—ฌ PersonalizationProfileDocument๋ฅผ ์ €์žฅํ•œ๋‹ค. - ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ ํ›„ ์ถ”์ฒœ ์ƒ์„ฑ์„ ํ˜ธ์ถœํ•œ๋‹ค. - ์ถ”์ฒœ ์ƒ์„ฑ ์‹คํŒจ๊ฐ€ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ €์žฅ์„ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š๋Š”์ง€ ์ •์ฑ…์„ ๊ฒ€์ฆํ•œ๋‹ค. ``` @@ -316,7 +316,7 @@ UserTest #### ํ‰๊ฐ€ ์กฐํšŒ ์ชฝ์€ ์ผ๋ถ€ ๋ณดํ˜ธ๋˜์–ด ์žˆ์ง€๋งŒ, ํ•ต์‹ฌ์ธ **์ถ”์ฒœ ์ƒ์„ฑ ๋กœ์ง์ด ๊ฑฐ์˜ ๋น„์–ด ์žˆ๋‹ค.** -`LlmRecommendationService`๋Š” Personalization Profile(`UserProfileDocument`), Elasticsearch, Post, Activity, RRF, MMR, time decay, history ์ €์žฅ์„ ๋ชจ๋‘ ๋‹ค๋ฃจ๋ฏ€๋กœ DDD ๋ฆฌํŒฉํ„ฐ๋ง ์ „ ๋ฐ˜๋“œ์‹œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. +`LlmRecommendationService`๋Š” Personalization Profile(`PersonalizationProfileDocument`), Elasticsearch, Post, Activity, RRF, MMR, time decay, history ์ €์žฅ์„ ๋ชจ๋‘ ๋‹ค๋ฃจ๋ฏ€๋กœ DDD ๋ฆฌํŒฉํ„ฐ๋ง ์ „ ๋ฐ˜๋“œ์‹œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. #### ๋‚จ์€ ๊ฐญ @@ -387,7 +387,7 @@ evaluation suite๋Š” ๋ฌด๊ฒ๊ณ  runtime/profile/fixture ์˜์กด์ด ๊ฐ•ํ•˜๋ฏ€๋กœ DDD |---|---|---| | P0 | `SearchServiceImplTest` ์ผ๋ฐ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ | ์ผ๋ฐ˜ ๊ฒ€์ƒ‰/๊ฐœ์ธํ™” ๊ฒ€์ƒ‰ ํ•ต์‹ฌ ํ๋ฆ„ ๋ณดํ˜ธ ํ•„์š” | | P0 | RRF ๊ฒฐํ•ฉ ํ…Œ์ŠคํŠธ | ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ˆœ์œ„ ํ’ˆ์งˆ๊ณผ ์ง์ ‘ ์—ฐ๊ฒฐ | -| P0 | ๊ฐœ์ธํ™” fallback/reranking ํ…Œ์ŠคํŠธ | Personalization Profile(`UserProfileDocument`) ๊ฒฝ๊ณ„ ์ •๋ฆฌ ์ „ ํ•„์š” | +| P0 | ๊ฐœ์ธํ™” fallback/reranking ํ…Œ์ŠคํŠธ | Personalization Profile(`PersonalizationProfileDocument`) ๊ฒฝ๊ณ„ ์ •๋ฆฌ ์ „ ํ•„์š” | | P1 | BM25 query builder ๊ตฌ์กฐ ํ…Œ์ŠคํŠธ | dis_max/exact/fuzzy/chunk ๊ตฌ์กฐ ํšŒ๊ท€ ๋ฐฉ์ง€ | | P1 | Semantic KNN field/boost ํ…Œ์ŠคํŠธ | title/summary/chunk embedding field ํšŒ๊ท€ ๋ฐฉ์ง€ | | P1 | metadata attachment ํ…Œ์ŠคํŠธ | viewCount, isBookmarked ์กฐํ•ฉ ๋ณดํ˜ธ | @@ -501,7 +501,7 @@ src/main/java/com/techfork/domain/notification/entity/NotificationToken.java | `PostKeyword` | ์ง์ ‘ ํ…Œ์ŠคํŠธ ์—†์Œ | `Post` ๋‚ด๋ถ€ ์—”ํ‹ฐํ‹ฐ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์ถฉ๋ถ„ | | `User` | `UserCommandServiceTest` ์ค‘์‹ฌ | User Account aggregate ๊ด€์ ์˜ ์ง์ ‘ `UserTest` ํ•„์š” | | `UserInterestCategory/Keyword` | repository/service ์ค‘์‹ฌ | User Account ๋„๋ฉ”์ธ ๊ทœ์น™ ํ…Œ์ŠคํŠธ ๋ณด๊ฐ• ํ•„์š” | -| `UserProfileDocument` | evaluation setup ์ค‘์‹ฌ | Personalization Profile projection ์ƒ์„ฑ/ํŒŒ์‹ฑ ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ํ•„์š” | +| `PersonalizationProfileDocument` | evaluation setup ์ค‘์‹ฌ | Personalization Profile projection ์ƒ์„ฑ/ํŒŒ์‹ฑ ์ผ๋ฐ˜ ํ…Œ์ŠคํŠธ ํ•„์š” | | `ReadPost` | service/repository ์ค‘์‹ฌ | record aggregate ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ์„ ํƒ | | `Bookmark` | ํ˜„์žฌ `ScrabPost` service/repository ์ค‘์‹ฌ | rename ํ›„ `Bookmark` ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ•„์š” | | `SearchHistory` | repository/service ์ค‘์‹ฌ | record aggregate ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ์„ ํƒ | @@ -518,7 +518,7 @@ src/main/java/com/techfork/domain/notification/entity/NotificationToken.java | ํ…Œ์ŠคํŠธ | ๋ชฉ์  | ์„ ํ–‰/์—ฐ๊ฒฐ ์ž‘์—… | |---|---|---| | `PostTest` | `Post = ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€` ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ณดํ˜ธ | Post ์šฉ์–ด ์ •๋ฆฌ, EDifficultyLevel ์ œ๊ฑฐ | -| `UserProfileServiceTest` | Personalization Profile ์ƒ์„ฑ ํ๋ฆ„ ๋ณดํ˜ธ | User Account / Personalization Profile ๊ฒฝ๊ณ„ ์ •๋ฆฌ | +| `PersonalizationProfileServiceTest` | Personalization Profile ์ƒ์„ฑ ํ๋ฆ„ ๋ณดํ˜ธ | User Account / Personalization Profile ๊ฒฝ๊ณ„ ์ •๋ฆฌ | | `MmrServiceTest` | ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ•ต์‹ฌ ๋ณดํ˜ธ | Recommendation DDD ์ „ํ™˜ | | `LlmRecommendationServiceTest` | ์ถ”์ฒœ ์ƒ์„ฑ/์ด๋ ฅํ™”/์ฝ์€ ๊ธ€ ์ œ์™ธ ๋ณดํ˜ธ | `PersonalizedProfileGenerated` ์ด๋ฒคํŠธ ๋„์ž… ์ „ | | `SearchServiceImplTest` | ๊ฒ€์ƒ‰ ์ผ๋ฐ˜ ํšŒ๊ท€ ์•ˆ์ „๋ง | Search read model/adapter ๋ถ„๋ฆฌ ์ „ | @@ -537,7 +537,7 @@ src/main/java/com/techfork/domain/notification/entity/NotificationToken.java | `RecommendationSchedulerTest` | ์ผ์ผ ์ถ”์ฒœ ์ƒ์„ฑ ์Šค์ผ€์ค„ ๋ณดํ˜ธ | | `RecommendedPostRepositoryTest` | rank order/delete/unique ๋ณดํ˜ธ | | `RecommendationHistoryTest` | ์ด๋ ฅํ™”/click ๊ธฐ๋ก ๋ณดํ˜ธ | -| `UserProfileSchedulerTest` | ์ผ์ผ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์žฌ์ƒ์„ฑ ๋ณดํ˜ธ | +| `PersonalizationProfileSchedulerTest` | ์ผ์ผ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์žฌ์ƒ์„ฑ ๋ณดํ˜ธ | | `SearchControllerIntegrationTest` | ๊ฒ€์ƒ‰ API contract ๋ณดํ˜ธ | | `PostKeywordRepositoryTest` | ํ‚ค์›Œ๋“œ ์กฐํšŒ ์กฐํ•ฉ ๋ณดํ˜ธ | @@ -570,7 +570,7 @@ src/main/java/com/techfork/domain/notification/entity/NotificationToken.java 2. ScrabPost โ†’ Bookmark rename ์ „ํ›„ ํ…Œ์ŠคํŠธ ์•ˆ์ •ํ™” 3. PostTest ์ž‘์„ฑ 4. Post embedding pipeline ํ…Œ์ŠคํŠธ ์ž‘์„ฑ -5. UserProfileServiceTest ์ž‘์„ฑ +5. PersonalizationProfileServiceTest ์ž‘์„ฑ 6. MmrServiceTest ์ž‘์„ฑ 7. LlmRecommendationServiceTest ์ž‘์„ฑ 8. SearchServiceImplTest ์ž‘์„ฑ @@ -591,7 +591,7 @@ src/main/java/com/techfork/domain/notification/entity/NotificationToken.java - EDifficultyLevel ์ œ๊ฑฐ ์ „ ์‚ฌ์šฉ์ฒ˜ ํ™•์ธ ์ž‘์—… 3: Personalization Profile ํ…Œ์ŠคํŠธ -- UserProfileServiceTest ์ถ”๊ฐ€ +- PersonalizationProfileServiceTest ์ถ”๊ฐ€ - LLM/Embedding/RecommendationService๋Š” mock ์ฒ˜๋ฆฌ ์ž‘์—… 4: ์ถ”์ฒœ ์ƒ์„ฑ ํ…Œ์ŠคํŠธ @@ -646,9 +646,9 @@ src/test/java/com/techfork/domain service UserCommandServiceTest InterestCommandServiceTest - UserProfileServiceTest + PersonalizationProfileServiceTest scheduler - UserProfileSchedulerTest + PersonalizationProfileSchedulerTest recommendation entity @@ -685,7 +685,7 @@ P0 ํ…Œ์ŠคํŠธ๊ฐ€ ๋ชจ๋‘ ์กด์žฌํ•œ๋‹ค. ./gradlew test -PexcludeIntegration ํ†ต๊ณผ. Activity/Bookmark rename ํ›„ ๊ธฐ์กด Activity ํ…Œ์ŠคํŠธ ํ†ต๊ณผ. PostTest๋กœ ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๊ธฐ๋ณธ ๊ทœ์น™ ๋ณดํ˜ธ. -UserProfileServiceTest๋กœ Personalization Profile ์ƒ์„ฑ ํ๋ฆ„ ๋ณดํ˜ธ. +PersonalizationProfileServiceTest๋กœ Personalization Profile ์ƒ์„ฑ ํ๋ฆ„ ๋ณดํ˜ธ. MmrServiceTest์™€ LlmRecommendationServiceTest๋กœ ์ถ”์ฒœ ์ƒ์„ฑ ํ•ต์‹ฌ ํ๋ฆ„ ๋ณดํ˜ธ. SearchServiceImplTest๋กœ ์ผ๋ฐ˜/๊ฐœ์ธํ™” ๊ฒ€์ƒ‰ ํšŒ๊ท€ ๋ณดํ˜ธ. ``` diff --git a/docs/ubiquitous-language/README.md b/docs/ubiquitous-language/README.md index 6f9a681a..944768e6 100644 --- a/docs/ubiquitous-language/README.md +++ b/docs/ubiquitous-language/README.md @@ -9,7 +9,7 @@ - **๊ธฐ์ค€ ๋‹จ์œ„๋Š” ํŒจํ‚ค์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋‹ค.** ๋‹ค๋งŒ ๊ฐ ๋ฌธ์„œ์— ํ˜„์žฌ owning package๋ฅผ ํ•จ๊ป˜ ์ ์–ด ์ฝ”๋“œ ํƒ์ƒ‰ ๊ฒฝ๋กœ๋ฅผ ๋ช…ํ™•ํžˆ ํ•œ๋‹ค. - **์œ ๋น„์ฟผํ„ฐ์Šค ์–ธ์–ด ๋ฌธ์„œ๋Š” ๋„๋ฉ”์ธ ์ „๋žต๊ณผ ์™•๋ณตํ•œ๋‹ค.** ์ƒˆ ์šฉ์–ด๊ฐ€ ๋‚˜์˜ค๋ฉด ๋จผ์ € ์ด ๋ฌธ์„œ๋ฅผ ๊ณ ์น˜๊ณ , ๊ฒฝ๊ณ„๊ฐ€ ๋ฐ”๋€Œ๋ฉด `domain-strategy.md`๋ฅผ ํ•จ๊ป˜ ์กฐ์ •ํ•œ๋‹ค. -- **์ „๋žต ๋ฌธ์„œ์—์„œ๋Š” `User Account`์™€ `Personalization Profile`์„ ๋ณ„๋„ ์ปจํ…์ŠคํŠธ๋กœ ๋ณธ๋‹ค.** ๋‹ค๋งŒ ํ˜„์žฌ ๊ตฌํ˜„ ํŒจํ‚ค์ง€๋Š” `domain/user` ์•„๋ž˜์— ํ•จ๊ป˜ ์กด์žฌํ•œ๋‹ค. +- **์ „๋žต ๋ฌธ์„œ์—์„œ๋Š” `User Account`์™€ `Personalization Profile`์„ ๋ณ„๋„ ์ปจํ…์ŠคํŠธ๋กœ ๋ณธ๋‹ค.** ํ˜„์žฌ ๊ตฌํ˜„๋„ `domain/useraccount`์™€ `domain/personalization`์œผ๋กœ ๋ฌผ๋ฆฌ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๋‹ค. - ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๋ช…(`ScrabPost`, `searchWord`, `markAsisClicked`)์€ ํ—ˆ์šฉํ•˜๋˜, ๋ฌธ์„œ/PR/API์—์„œ๋Š” ํ‘œ์ค€ ์šฉ์–ด๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•œ๋‹ค. - ๊ฐ ์ปจํ…์ŠคํŠธ ๋ฌธ์„œ๋Š” ๊ฐ€๋Šฅํ•˜๋ฉด ์•„๋ž˜ ๋‹ค์„ฏ ๋ธ”๋ก์„ ์œ ์ง€ํ•œ๋‹ค. 1. ํ‘œ์ค€ ์šฉ์–ด @@ -26,8 +26,8 @@ |---|---|---|---| | Source / Ingestion | [`source-ingestion.md`](./source-ingestion.md) | `src/main/java/com/techfork/domain/source` | RSS ์ˆ˜์ง‘, ์†Œ์Šค ๋ธ”๋กœ๊ทธ, ํŒŒ์ดํ”„๋ผ์ธ ์‹œ์ž‘์  | | Post / Content | [`post-content.md`](./post-content.md) | `src/main/java/com/techfork/domain/post` | ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€ ๋ณธ๋ฌธ, ์š”์•ฝ, ํ‚ค์›Œ๋“œ, ๊ฒ€์ƒ‰ projection | -| User Account | [`user-account.md`](./user-account.md) | `src/main/java/com/techfork/domain/user` | ๊ณ„์ •, ์˜จ๋ณด๋”ฉ, ๊ด€์‹ฌ์‚ฌ, ๊ณ„์ • ํ”„๋กœํ•„ | -| Personalization Profile | [`personalization-profile.md`](./personalization-profile.md) | `src/main/java/com/techfork/domain/user` | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ, ๋ฒกํ„ฐ, ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ, ์žฌ์ƒ์„ฑ | +| User Account | [`user-account.md`](./user-account.md) | `src/main/java/com/techfork/domain/useraccount` | ๊ณ„์ •, ์˜จ๋ณด๋”ฉ, ๊ด€์‹ฌ์‚ฌ, ๊ณ„์ • ํ”„๋กœํ•„ | +| Personalization Profile | [`personalization-profile.md`](./personalization-profile.md) | `src/main/java/com/techfork/domain/personalization` | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ, ๋ฒกํ„ฐ, ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ, ์žฌ์ƒ์„ฑ | | Activity | [`activity.md`](./activity.md) | `src/main/java/com/techfork/domain/activity` | ์ฝ๊ธฐ/๊ฒ€์ƒ‰/๋ถ๋งˆํฌ ํ–‰๋™ ๊ธฐ๋ก | | Search | [`search.md`](./search.md) | `src/main/java/com/techfork/domain/search` | query service / read model ์ค‘์‹ฌ ์ปจํ…์ŠคํŠธ | | Recommendation | [`recommendation.md`](./recommendation.md) | `src/main/java/com/techfork/domain/recommendation` | ์ถ”์ฒœ ํ›„๋ณด ํƒ์ƒ‰, ๋žญํ‚น, ํ˜„์žฌ ์ถ”์ฒœ ๋ชฉ๋ก | @@ -52,13 +52,13 @@ | ํ‘œ์ค€ ์šฉ์–ด | ํ˜„์žฌ ์ฝ”๋“œ์ƒ ํ‘œํ˜„ | ์˜๋ฏธ | |---|---|---| | ๊ณ„์ • ํ”„๋กœํ•„ | `User.nickName`, `User.description`, `User.profileImage` | ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์ด๋Š” ๊ธฐ๋ณธ ํ”„๋กœํ•„ ์ •๋ณด | -| ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | `UserProfileDocument.profileText`, `profileVector` | ๊ฒ€์ƒ‰ ๋ฆฌ๋žญํ‚น/์ถ”์ฒœ์šฉ ํ™œ๋™ ๊ธฐ๋ฐ˜ ํ”„๋กœํ•„ | +| ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | `PersonalizationProfileDocument.profileText`, `profileVector` | ๊ฒ€์ƒ‰ ๋ฆฌ๋žญํ‚น/์ถ”์ฒœ์šฉ ํ™œ๋™ ๊ธฐ๋ฐ˜ ํ”„๋กœํ•„ | ๊ทœ์น™: - ๋ฌธ์„œ/PR/API์—์„œ **โ€œํ”„๋กœํ•„โ€ ๋‹จ๋… ํ‘œํ˜„์€ ์ง€์–‘**ํ•œ๋‹ค. - UI/์„ค์ • ํ™”๋ฉด์€ `๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ •`, ์ถ”์ฒœ/๊ฒ€์ƒ‰ ์ค€๋น„ ์ƒํƒœ๋Š” `๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ/์žฌ์ƒ์„ฑ`์œผ๋กœ ์“ด๋‹ค. -- `UserProfileDocument`๋Š” ํ˜„์žฌ `domain/user` ํŒจํ‚ค์ง€์— ์žˆ์œผ๋‚˜, ๊ฐœ๋…์ƒ์œผ๋กœ๋Š” `Personalization Profile` language zone์˜ read model์ด๋‹ค. +- `PersonalizationProfileDocument`๋Š” `domain/personalization` ํŒจํ‚ค์ง€์˜ read model/projection์ด๋‹ค. --- @@ -82,7 +82,7 @@ 1. `domain-strategy.md`์˜ ๋ช…์นญ๊ณผ glossary ๋ฌธ์„œ๋ช…์„ ๋งž์ถ˜๋‹ค. (`Auth` โ†’ `Auth / Security`) 2. `docs/ubiquitous-language.md`๋Š” ํ˜ธํ™˜์šฉ ์ธ๋ฑ์Šค๋กœ ์ถ•์†Œํ•˜๊ณ , ์ƒ์„ธ ์šฉ์–ด๋Š” ์ด ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฌธ์„œ์— ๋ชจ์€๋‹ค. 3. ์ „๋žต ๋ฌธ์„œ์™€ glossary๋Š” `User Account` / `Personalization Profile`๋กœ ๋ถ„๋ฆฌ ์œ ์ง€ํ•œ๋‹ค. -4. ์ถ”ํ›„ ์‹ค์ œ ํŒจํ‚ค์ง€ ๋ถ„๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด์ง€๋ฉด ๊ทธ ์‹œ์ ์— `domain/user` ํ•˜์œ„ ๊ตฌ์กฐ์™€ ๋ฌธ์„œ ๊ตฌ์กฐ๋ฅผ ๋‹ค์‹œ ์ •๋ ฌํ•œ๋‹ค. +4. ํŒจํ‚ค์ง€ ๋ถ„๋ฆฌ ์ดํ›„์—๋„ glossary์™€ ์ „๋žต ๋ฌธ์„œ์˜ ํ˜„์žฌ ์ƒํƒœ ์„ค๋ช…์ด staleํ•˜์ง€ ์•Š๋„๋ก ํ•จ๊ป˜ ์œ ์ง€ํ•œ๋‹ค. ## 6. ๋‚ด๋ถ€ glossary๋ฅผ ์ฑ„์šฐ๋Š” ๊ธฐ์ค€ diff --git a/docs/ubiquitous-language/personalization-profile.md b/docs/ubiquitous-language/personalization-profile.md index 68d21312..f4b0c479 100644 --- a/docs/ubiquitous-language/personalization-profile.md +++ b/docs/ubiquitous-language/personalization-profile.md @@ -1,38 +1,38 @@ # Personalization Profile > ํ™œ๋™ ๋ฐ์ดํ„ฐ์™€ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•˜๊ณ , ๊ฒ€์ƒ‰/์ถ”์ฒœ ์ž…๋ ฅ ๋ชจ๋ธ์„ ์ œ๊ณตํ•˜๋Š” ๊ฐœ๋…์  ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. -> ํ˜„์žฌ ๊ตฌํ˜„ ํŒจํ‚ค์ง€๋Š” `domain/user`์— ํ•จ๊ป˜ ์กด์žฌํ•˜์ง€๋งŒ, ์ „๋žต ๋ฌธ์„œ์—์„œ๋Š” `User Account`์™€ ๋ถ„๋ฆฌํ•ด์„œ ๋ด…๋‹ˆ๋‹ค. +> ํ˜„์žฌ ๊ตฌํ˜„์€ `domain/personalization`์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฉฐ, `User Account`์™€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ๋„ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ## Owning packages -- `src/main/java/com/techfork/domain/user` -- ๊ด€๋ จ read model: `src/main/java/com/techfork/domain/user/document` +- `src/main/java/com/techfork/domain/personalization` +- ๊ด€๋ จ read model: `src/main/java/com/techfork/domain/personalization/document` ## ํ‘œ์ค€ ์šฉ์–ด | ์šฉ์–ด | ์ฝ”๋“œ์ƒ ํ‘œํ˜„ | ์ •์˜ | |---|---|---| -| ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | `UserProfileDocument` | ์‚ฌ์šฉ์ž ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ LLM์œผ๋กœ ์š”์•ฝํ•˜๊ณ  ์ž„๋ฒ ๋”ฉํ•œ ๊ฐœ์ธํ™”์šฉ ํ”„๋กœํ•„ ๋ฌธ์„œ | +| ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | `PersonalizationProfileDocument` | ์‚ฌ์šฉ์ž ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ LLM์œผ๋กœ ์š”์•ฝํ•˜๊ณ  ์ž„๋ฒ ๋”ฉํ•œ ๊ฐœ์ธํ™”์šฉ ํ”„๋กœํ•„ ๋ฌธ์„œ | | ํ”„๋กœํ•„ ํ…์ŠคํŠธ | `profileText` | ๊ฒ€์ƒ‰ ๋ฆฌ๋žญํ‚น๊ณผ ์ถ”์ฒœ์— ์‚ฌ์šฉํ•  ์‚ฌ์šฉ์ž ๊ด€์‹ฌ์‚ฌ ์„ค๋ช…๋ฌธ | | ํ”„๋กœํ•„ ๋ฒกํ„ฐ | `profileVector` | `profileText`๋ฅผ ์ž„๋ฒ ๋”ฉํ•œ ๋ฒกํ„ฐ | | ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ | `keyKeywords` | LLM์ด ์‚ฌ์šฉ์ž ํ™œ๋™์—์„œ ์ถ”์ถœํ•œ 3~5๊ฐœ ๋Œ€ํ‘œ ๊ด€์‹ฌ ํ‚ค์›Œ๋“œ | | ํ™œ๋™ ๋ฐ์ดํ„ฐ | `UserActivityData` | ๊ด€์‹ฌ์‚ฌ, ์ตœ๊ทผ ์ฝ์€ ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€, ๋ถ๋งˆํฌํ•œ ๊ธฐ์ˆ  ๊ฒŒ์‹œ๊ธ€, ๊ฒ€์ƒ‰ ๊ธฐ๋ก์„ ํ•ฉ์นœ ์‚ฌ์šฉ์ž ๋ถ„์„ ์ž…๋ ฅ | -| ํ”„๋กœํ•„ ์žฌ์ƒ์„ฑ | `generateUserProfile`, `generateUserProfileSync` | ํ™œ๋™ ๋ณ€ํ™” ํ›„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ๋‹ค์‹œ ๋งŒ๋“œ๋Š” ํ–‰์œ„ | +| ํ”„๋กœํ•„ ์žฌ์ƒ์„ฑ | `generatePersonalizationProfile`, `generatePersonalizationProfileSync` | ํ™œ๋™ ๋ณ€ํ™” ํ›„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ๋‹ค์‹œ ๋งŒ๋“œ๋Š” ํ–‰์œ„ | ## ๋‚ด๋ถ€ glossary | ๋‚ด๋ถ€ ์šฉ์–ด | ์ฝ”๋“œ์ƒ ํ‘œํ˜„ | ์„ค๋ช… | |---|---|---| -| ํ”„๋กœํ•„ ์ƒ์„ฑ ์„œ๋น„์Šค | `UserProfileService` | ํ™œ๋™/๊ด€์‹ฌ์‚ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ projection์„ ์ƒ์„ฑํ•˜๋Š” ์„œ๋น„์Šค | -| ํ”„๋กœํ•„ ์žฌ์ƒ์„ฑ ์Šค์ผ€์ค„๋Ÿฌ | `UserProfileScheduler` | ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ์ƒ์„ฑํ•˜๋Š” ์Šค์ผ€์ค„๋Ÿฌ | -| ํ”„๋กœํ•„ projection | `UserProfileDocument` | ๊ฒ€์ƒ‰/์ถ”์ฒœ์— ์ œ๊ณต๋˜๋Š” read model | +| ํ”„๋กœํ•„ ์ƒ์„ฑ ์„œ๋น„์Šค | `PersonalizationProfileService` | ํ™œ๋™/๊ด€์‹ฌ์‚ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ projection์„ ์ƒ์„ฑํ•˜๋Š” ์„œ๋น„์Šค | +| ํ”„๋กœํ•„ ์žฌ์ƒ์„ฑ ์Šค์ผ€์ค„๋Ÿฌ | `PersonalizationProfileScheduler` | ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ์ƒ์„ฑํ•˜๋Š” ์Šค์ผ€์ค„๋Ÿฌ | +| ํ”„๋กœํ•„ projection | `PersonalizationProfileDocument` | ๊ฒ€์ƒ‰/์ถ”์ฒœ์— ์ œ๊ณต๋˜๋Š” read model | | ๊ฐœ์ธํ™” ์ž…๋ ฅ ํ‚ค์›Œ๋“œ | `keyKeywords` | ์ถ”์ฒœ BM25์™€ ๊ฒ€์ƒ‰ ๊ฐœ์ธํ™”์— ํ™œ์šฉ๋˜๋Š” ํ‚ค์›Œ๋“œ | | ํ”„๋กœํ•„ ์ƒ์„ฑ ํŠธ๋ฆฌ๊ฑฐ | ๊ด€์‹ฌ์‚ฌ ๋ณ€๊ฒฝ / ํ™œ๋™ ๋ˆ„์  | ํ˜„์žฌ๋Š” ์„œ๋น„์Šค ์ง์ ‘ ํ˜ธ์ถœ ๊ธฐ๋ฐ˜, ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” ์ด๋ฒคํŠธ ๋ถ„๋ฆฌ ๋Œ€์ƒ | ## ํ˜ผ๋™ ๊ธˆ์ง€ - `๊ฐœ์ธํ™” ํ”„๋กœํ•„`์€ UI์šฉ ๋‚ด ํ”„๋กœํ•„์ด ์•„๋‹ˆ๋‹ค. -- `UserProfileDocument`๋Š” aggregate๋ผ๊ธฐ๋ณด๋‹ค projection/read model์ด๋‹ค. +- `PersonalizationProfileDocument`๋Š” aggregate๋ผ๊ธฐ๋ณด๋‹ค projection/read model์ด๋‹ค. - `ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ`๋Š” ์ถ”์ฒœ/๊ฒ€์ƒ‰์šฉ ํ”„๋กœํ•„ ํŒŒ์ƒ ํ‚ค์›Œ๋“œ์ด๊ณ , `๊ฒŒ์‹œ๊ธ€ ํ‚ค์›Œ๋“œ(PostKeyword)`๋‚˜ `๊ฒ€์ƒ‰์–ด(SearchQuery)`์™€ ๋‹ค๋ฅด๋‹ค. ## ๊ธˆ์ง€ ํ‘œํ˜„ / ๊ถŒ์žฅ ํ‘œํ˜„ @@ -40,12 +40,12 @@ | ๊ธˆ์ง€/๋น„๊ถŒ์žฅ ํ‘œํ˜„ | ๊ถŒ์žฅ ํ‘œํ˜„ | ์ด์œ  | |---|---|---| | ํ”„๋กœํ•„ | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | ๊ณ„์ • ํ”„๋กœํ•„๊ณผ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•œ๋‹ค | -| ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ฌธ์„œ | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ / `UserProfileDocument` | projection ์„ฑ๊ฒฉ์„ ๋ถ„๋ช…ํžˆ ํ•˜๊ธฐ ์œ„ํ•ด | +| ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ฌธ์„œ | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ / `PersonalizationProfileDocument` | projection ์„ฑ๊ฒฉ์„ ๋ถ„๋ช…ํžˆ ํ•˜๊ธฐ ์œ„ํ•ด | | ๊ฒ€์ƒ‰์šฉ ํ”„๋กœํ•„ | ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | Search ํ•œ์ • ๋ชจ๋ธ๋กœ ์˜คํ•ดํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด | | ๊ด€์‹ฌ์‚ฌ ํ”„๋กœํ•„ | ํ™œ๋™ ๋ฐ์ดํ„ฐ / ๊ฐœ์ธํ™” ํ”„๋กœํ•„ | ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์™€ ์ƒ์„ฑ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•œ๋‹ค | ## ์ฃผ์š” ๊ทผ๊ฑฐ ํŒŒ์ผ -- `src/main/java/com/techfork/domain/user/service/UserProfileService.java` -- `src/main/java/com/techfork/domain/user/document/UserProfileDocument.java` -- `src/main/java/com/techfork/domain/user/scheduler/UserProfileScheduler.java` +- `src/main/java/com/techfork/domain/personalization/service/PersonalizationProfileService.java` +- `src/main/java/com/techfork/domain/personalization/document/PersonalizationProfileDocument.java` +- `src/main/java/com/techfork/domain/personalization/scheduler/PersonalizationProfileScheduler.java` diff --git a/docs/ubiquitous-language/search.md b/docs/ubiquitous-language/search.md index b9d1759f..b6b77d4d 100644 --- a/docs/ubiquitous-language/search.md +++ b/docs/ubiquitous-language/search.md @@ -5,7 +5,7 @@ ## Owning packages - `src/main/java/com/techfork/domain/search` -- ๊ด€๋ จ read model: `src/main/java/com/techfork/domain/post/document`, `src/main/java/com/techfork/domain/user/document` +- ๊ด€๋ จ read model: `src/main/java/com/techfork/domain/post/document`, `src/main/java/com/techfork/domain/personalization/document` ## ํ‘œ์ค€ ์šฉ์–ด @@ -45,7 +45,7 @@ ## ํ˜ผ๋™ ๊ธˆ์ง€ - `๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ`๋Š” ์ €์žฅ๋˜๋Š” aggregate๊ฐ€ ์•„๋‹ˆ๋ผ ๊ณ„์‚ฐ๋œ ์‘๋‹ต์ด๋‹ค. -- Search๊ฐ€ `PostDocument`, `UserProfileDocument`๋ฅผ ์ฝ๋Š”๋‹ค๊ณ  ํ•ด์„œ Post/User aggregate๋ฅผ ์†Œ์œ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. +- Search๊ฐ€ `PostDocument`, `PersonalizationProfileDocument`๋ฅผ ์ฝ๋Š”๋‹ค๊ณ  ํ•ด์„œ Post/User aggregate๋ฅผ ์†Œ์œ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. - `๊ฒ€์ƒ‰์–ด(SearchQuery)`์™€ ์ถ”์ฒœ์˜ `keyKeywords`๋Š” ๋‘˜ ๋‹ค ๋ฌธ์ž์—ด์ด์ง€๋งŒ ์ƒ์„ฑ ์ฃผ์ฒด๊ฐ€ ๋‹ค๋ฅด๋‹ค. ## ๊ธˆ์ง€ ํ‘œํ˜„ / ๊ถŒ์žฅ ํ‘œํ˜„ diff --git a/docs/ubiquitous-language/user-account.md b/docs/ubiquitous-language/user-account.md index f160b0dd..b318d40b 100644 --- a/docs/ubiquitous-language/user-account.md +++ b/docs/ubiquitous-language/user-account.md @@ -1,11 +1,11 @@ # User Account > ์‚ฌ์šฉ์ž ๊ณ„์ •, ์˜จ๋ณด๋”ฉ, ๊ด€์‹ฌ์‚ฌ, ๊ณ„์ • ํ”„๋กœํ•„์„ ๋‹ค๋ฃจ๋Š” ๊ฐœ๋…์  ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. -> ํ˜„์žฌ ๊ตฌํ˜„ ํŒจํ‚ค์ง€๋Š” `domain/user`์— ํ•จ๊ป˜ ์กด์žฌํ•˜์ง€๋งŒ, ์ „๋žต ๋ฌธ์„œ์—์„œ๋Š” `Personalization Profile`๊ณผ ๋ถ„๋ฆฌํ•ด์„œ ๋ด…๋‹ˆ๋‹ค. +> ํ˜„์žฌ ๊ตฌํ˜„์€ `domain/useraccount`๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฉฐ, `Personalization Profile`๊ณผ ๋ฌผ๋ฆฌ์ ์œผ๋กœ๋„ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ## Owning packages -- `src/main/java/com/techfork/domain/user` +- `src/main/java/com/techfork/domain/useraccount` ## ํ‘œ์ค€ ์šฉ์–ด @@ -30,7 +30,7 @@ | ์‚ฌ์šฉ์ž ๋ฃจํŠธ | `User` | ๊ณ„์ •/์ƒํƒœ/๊ธฐ๋ณธ ํ”„๋กœํ•„/๊ด€์‹ฌ์‚ฌ๋ฅผ ์†Œ์œ ํ•˜๋Š” aggregate root | | ์˜จ๋ณด๋”ฉ ์™„๋ฃŒ ์ปค๋งจ๋“œ | `UserCommandService.completeOnboarding` | ๊ณ„์ • ์ •๋ณด ์ €์žฅ + ๊ด€์‹ฌ์‚ฌ ์ €์žฅ + ํ™œ์„ฑํ™” ํ๋ฆ„ | | ๊ด€์‹ฌ์‚ฌ ๊ต์ฒด ์ปค๋งจ๋“œ | `InterestCommandService.updateUserInterests` | ๊ด€์‹ฌ์‚ฌ ์ „์ฒด๋ฅผ ๊ฐˆ์•„๋ผ์šฐ๋Š” ํ๋ฆ„ | -| ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • | `UserCommandService.updateUserProfile` | ๋‹‰๋„ค์ž„/์ž๊ธฐ์†Œ๊ฐœ ์ˆ˜์ • ํ๋ฆ„ | +| ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • | `UserCommandService.updateAccountProfile` | ๋‹‰๋„ค์ž„/์ž๊ธฐ์†Œ๊ฐœ ์ˆ˜์ • ํ๋ฆ„ | | ํƒˆํ‡ด ์ฒ˜๋ฆฌ | `User.withdraw()` | ๊ฐœ์ธ์ •๋ณด ์ต๋ช…ํ™”์™€ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋„๋ฉ”์ธ ๋™์ž‘ | ## ํ˜ผ๋™ ๊ธˆ์ง€ @@ -50,11 +50,11 @@ ## ์ฃผ์š” ๊ทผ๊ฑฐ ํŒŒ์ผ -- `src/main/java/com/techfork/domain/user/entity/User.java` -- `src/main/java/com/techfork/domain/user/enums/UserStatus.java` -- `src/main/java/com/techfork/domain/user/enums/EInterestCategory.java` -- `src/main/java/com/techfork/domain/user/enums/EInterestKeyword.java` -- `src/main/java/com/techfork/domain/user/service/UserCommandService.java` -- `src/main/java/com/techfork/domain/user/service/InterestCommandService.java` -- `src/main/java/com/techfork/domain/user/controller/OnboardingController.java` -- `src/main/java/com/techfork/domain/user/controller/UserController.java` +- `src/main/java/com/techfork/domain/useraccount/entity/User.java` +- `src/main/java/com/techfork/domain/useraccount/enums/UserStatus.java` +- `src/main/java/com/techfork/domain/useraccount/enums/EInterestCategory.java` +- `src/main/java/com/techfork/domain/useraccount/enums/EInterestKeyword.java` +- `src/main/java/com/techfork/domain/useraccount/service/UserCommandService.java` +- `src/main/java/com/techfork/domain/useraccount/service/InterestCommandService.java` +- `src/main/java/com/techfork/domain/useraccount/controller/OnboardingController.java` +- `src/main/java/com/techfork/domain/useraccount/controller/UserController.java` diff --git a/docs/ubiquitous-language/user-profile.md b/docs/ubiquitous-language/user-profile.md index d7c877ee..dcc65f24 100644 --- a/docs/ubiquitous-language/user-profile.md +++ b/docs/ubiquitous-language/user-profile.md @@ -6,4 +6,5 @@ - [User Account](./user-account.md) - [Personalization Profile](./personalization-profile.md) -ํ˜„์žฌ ๊ตฌํ˜„ ํŒจํ‚ค์ง€๋Š” ์—ฌ์ „ํžˆ `src/main/java/com/techfork/domain/user`๋กœ ํ•จ๊ป˜ ๋ฌถ์—ฌ ์žˆ์Šต๋‹ˆ๋‹ค. +ํ˜„์žฌ ๊ตฌํ˜„์€ `src/main/java/com/techfork/domain/useraccount`์™€ +`src/main/java/com/techfork/domain/personalization`๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/src/main/java/com/techfork/domain/activity/entity/Bookmark.java b/src/main/java/com/techfork/domain/activity/entity/Bookmark.java index 6033bdda..a97c4828 100644 --- a/src/main/java/com/techfork/domain/activity/entity/Bookmark.java +++ b/src/main/java/com/techfork/domain/activity/entity/Bookmark.java @@ -1,52 +1,52 @@ -package com.techfork.domain.activity.entity; - -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.time.LocalDateTime; - -@Entity -@Table( - name = "bookmarks", - uniqueConstraints = { - @UniqueConstraint(name = "uk_bookmarks_user_post", columnNames = {"user_id", "post_id"}) - } -) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Bookmark extends BaseEntity { - - @Column(name = "bookmarked_at") - private LocalDateTime bookmarkedAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id", nullable = false) - private Post post; - - @PersistenceCreator - @Builder - Bookmark(User user, Post post, LocalDateTime bookmarkedAt) { - this.user = user; - this.post = post; - this.bookmarkedAt = bookmarkedAt; - } - - public static Bookmark create(User user, Post post, LocalDateTime bookmarkedAt) { - return Bookmark.builder() - .user(user) - .post(post) - .bookmarkedAt(bookmarkedAt) - .build(); - } -} +package com.techfork.domain.activity.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.time.LocalDateTime; + +@Entity +@Table( + name = "bookmarks", + uniqueConstraints = { + @UniqueConstraint(name = "uk_bookmarks_user_post", columnNames = {"user_id", "post_id"}) + } +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Bookmark extends BaseEntity { + + @Column(name = "bookmarked_at") + private LocalDateTime bookmarkedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @PersistenceCreator + @Builder + Bookmark(User user, Post post, LocalDateTime bookmarkedAt) { + this.user = user; + this.post = post; + this.bookmarkedAt = bookmarkedAt; + } + + public static Bookmark create(User user, Post post, LocalDateTime bookmarkedAt) { + return Bookmark.builder() + .user(user) + .post(post) + .bookmarkedAt(bookmarkedAt) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/activity/entity/ReadPost.java b/src/main/java/com/techfork/domain/activity/entity/ReadPost.java index 6e7f4495..6e627a62 100644 --- a/src/main/java/com/techfork/domain/activity/entity/ReadPost.java +++ b/src/main/java/com/techfork/domain/activity/entity/ReadPost.java @@ -1,51 +1,51 @@ -package com.techfork.domain.activity.entity; - -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "read_posts") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ReadPost extends BaseEntity { - - @Column(nullable = false) - private LocalDateTime readAt; - - private Integer readDurationSeconds; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id", nullable = false) - private Post post; - - @PersistenceCreator - @Builder - ReadPost(User user, Post post, LocalDateTime readAt, Integer readDurationSeconds) { - this.user = user; - this.post = post; - this.readAt = readAt; - this.readDurationSeconds = readDurationSeconds; - } - - public static ReadPost create(User user, Post post, LocalDateTime readAt, Integer readDurationSeconds) { - return ReadPost.builder() - .user(user) - .post(post) - .readAt(readAt) - .readDurationSeconds(readDurationSeconds) - .build(); - } -} +package com.techfork.domain.activity.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "read_posts") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReadPost extends BaseEntity { + + @Column(nullable = false) + private LocalDateTime readAt; + + private Integer readDurationSeconds; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @PersistenceCreator + @Builder + ReadPost(User user, Post post, LocalDateTime readAt, Integer readDurationSeconds) { + this.user = user; + this.post = post; + this.readAt = readAt; + this.readDurationSeconds = readDurationSeconds; + } + + public static ReadPost create(User user, Post post, LocalDateTime readAt, Integer readDurationSeconds) { + return ReadPost.builder() + .user(user) + .post(post) + .readAt(readAt) + .readDurationSeconds(readDurationSeconds) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/activity/entity/SearchHistory.java b/src/main/java/com/techfork/domain/activity/entity/SearchHistory.java index 1ef61a9b..04b816b6 100644 --- a/src/main/java/com/techfork/domain/activity/entity/SearchHistory.java +++ b/src/main/java/com/techfork/domain/activity/entity/SearchHistory.java @@ -1,31 +1,31 @@ -package com.techfork.domain.activity.entity; - -import com.techfork.domain.user.entity.User; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "search_histories") -@Getter +package com.techfork.domain.activity.entity; + +import com.techfork.domain.useraccount.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "search_histories") +@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class SearchHistory extends BaseEntity { @Column(nullable = false, length = 200) private String query; - - private LocalDateTime searchedAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - + + private LocalDateTime searchedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + @PersistenceCreator @Builder SearchHistory(User user, String query, LocalDateTime searchedAt) { diff --git a/src/main/java/com/techfork/domain/activity/repository/BookmarkRepository.java b/src/main/java/com/techfork/domain/activity/repository/BookmarkRepository.java index 5bdaca4c..33b6bbfd 100644 --- a/src/main/java/com/techfork/domain/activity/repository/BookmarkRepository.java +++ b/src/main/java/com/techfork/domain/activity/repository/BookmarkRepository.java @@ -1,49 +1,49 @@ -package com.techfork.domain.activity.repository; - -import com.techfork.domain.activity.dto.BookmarkDto; -import com.techfork.domain.activity.entity.Bookmark; -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; -import java.util.Optional; - -public interface BookmarkRepository extends JpaRepository { - - @Query(""" - SELECT new com.techfork.domain.activity.dto.BookmarkDto( - b.id, p.id, p.title, p.shortSummary, p.url, t.companyName, t.logoUrl, - p.publishedAt, p.thumbnailUrl, p.viewCount, null, true - ) - FROM Bookmark b - JOIN b.post p - JOIN p.techBlog t - WHERE b.user = :user - AND (:lastBookmarkId IS NULL OR b.id < :lastBookmarkId) - ORDER BY b.id DESC - """) - List findBookmarksWithCursor( - @Param("user") User user, - @Param("lastBookmarkId") Long lastBookmarkId, - Pageable pageable - ); - - boolean existsByUserAndPost(User user, Post post); - - Optional findByUserAndPost(User user, Post post); - - @Query("SELECT b FROM Bookmark b JOIN FETCH b.post WHERE b.user.id = :userId ORDER BY b.bookmarkedAt DESC") - List findRecentBookmarksByUserId(@Param("userId") Long userId, Pageable pageable); - - @Query(""" - SELECT b.post.id - FROM Bookmark b - WHERE b.user.id = :userId - AND b.post.id IN :postIds - """) - List findBookmarkedPostIds(@Param("userId") Long userId, @Param("postIds") List postIds); -} +package com.techfork.domain.activity.repository; + +import com.techfork.domain.activity.dto.BookmarkDto; +import com.techfork.domain.activity.entity.Bookmark; +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.useraccount.entity.User; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface BookmarkRepository extends JpaRepository { + + @Query(""" + SELECT new com.techfork.domain.activity.dto.BookmarkDto( + b.id, p.id, p.title, p.shortSummary, p.url, t.companyName, t.logoUrl, + p.publishedAt, p.thumbnailUrl, p.viewCount, null, true + ) + FROM Bookmark b + JOIN b.post p + JOIN p.techBlog t + WHERE b.user = :user + AND (:lastBookmarkId IS NULL OR b.id < :lastBookmarkId) + ORDER BY b.id DESC + """) + List findBookmarksWithCursor( + @Param("user") User user, + @Param("lastBookmarkId") Long lastBookmarkId, + Pageable pageable + ); + + boolean existsByUserAndPost(User user, Post post); + + Optional findByUserAndPost(User user, Post post); + + @Query("SELECT b FROM Bookmark b JOIN FETCH b.post WHERE b.user.id = :userId ORDER BY b.bookmarkedAt DESC") + List findRecentBookmarksByUserId(@Param("userId") Long userId, Pageable pageable); + + @Query(""" + SELECT b.post.id + FROM Bookmark b + WHERE b.user.id = :userId + AND b.post.id IN :postIds + """) + List findBookmarkedPostIds(@Param("userId") Long userId, @Param("postIds") List postIds); +} diff --git a/src/main/java/com/techfork/domain/activity/repository/ReadPostRepository.java b/src/main/java/com/techfork/domain/activity/repository/ReadPostRepository.java index 4370dead..d8b2610d 100644 --- a/src/main/java/com/techfork/domain/activity/repository/ReadPostRepository.java +++ b/src/main/java/com/techfork/domain/activity/repository/ReadPostRepository.java @@ -1,50 +1,50 @@ -package com.techfork.domain.activity.repository; - -import com.techfork.domain.activity.dto.ReadPostDto; -import com.techfork.domain.activity.entity.ReadPost; -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; - -public interface ReadPostRepository extends JpaRepository { - - boolean existsByUserAndPost(User user, Post post); - - @Query(""" - SELECT rp FROM ReadPost rp - JOIN FETCH rp.post - WHERE rp.user.id = :userId - AND (rp.readDurationSeconds IS NULL OR rp.readDurationSeconds > 10) - ORDER BY rp.readAt DESC - """) - List findRecentReadPostsByUserIdWithMinDuration(@Param("userId") Long userId, Pageable pageable); - - @Query(""" - SELECT new com.techfork.domain.activity.dto.ReadPostDto( - rp.id, p.id, p.title, p.shortSummary, p.url, t.companyName, t.logoUrl, - p.publishedAt, p.thumbnailUrl, p.viewCount, null, null, rp.readAt - ) - FROM ReadPost rp - JOIN rp.post p - JOIN p.techBlog t - WHERE rp.user.id = :userId - AND rp.id IN ( - SELECT MAX(rp2.id) - FROM ReadPost rp2 - WHERE rp2.user.id = :userId - GROUP BY rp2.post.id - ) - AND (:lastReadPostId IS NULL OR rp.id < :lastReadPostId) - ORDER BY rp.id DESC - """) - List findReadPostsWithCursor( - @Param("userId") Long userId, - @Param("lastReadPostId") Long lastReadPostId, - Pageable pageable - ); -} +package com.techfork.domain.activity.repository; + +import com.techfork.domain.activity.dto.ReadPostDto; +import com.techfork.domain.activity.entity.ReadPost; +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.useraccount.entity.User; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ReadPostRepository extends JpaRepository { + + boolean existsByUserAndPost(User user, Post post); + + @Query(""" + SELECT rp FROM ReadPost rp + JOIN FETCH rp.post + WHERE rp.user.id = :userId + AND (rp.readDurationSeconds IS NULL OR rp.readDurationSeconds > 10) + ORDER BY rp.readAt DESC + """) + List findRecentReadPostsByUserIdWithMinDuration(@Param("userId") Long userId, Pageable pageable); + + @Query(""" + SELECT new com.techfork.domain.activity.dto.ReadPostDto( + rp.id, p.id, p.title, p.shortSummary, p.url, t.companyName, t.logoUrl, + p.publishedAt, p.thumbnailUrl, p.viewCount, null, null, rp.readAt + ) + FROM ReadPost rp + JOIN rp.post p + JOIN p.techBlog t + WHERE rp.user.id = :userId + AND rp.id IN ( + SELECT MAX(rp2.id) + FROM ReadPost rp2 + WHERE rp2.user.id = :userId + GROUP BY rp2.post.id + ) + AND (:lastReadPostId IS NULL OR rp.id < :lastReadPostId) + ORDER BY rp.id DESC + """) + List findReadPostsWithCursor( + @Param("userId") Long userId, + @Param("lastReadPostId") Long lastReadPostId, + Pageable pageable + ); +} diff --git a/src/main/java/com/techfork/domain/activity/service/ActivityCommandService.java b/src/main/java/com/techfork/domain/activity/service/ActivityCommandService.java index f926fe21..28c9dd2f 100644 --- a/src/main/java/com/techfork/domain/activity/service/ActivityCommandService.java +++ b/src/main/java/com/techfork/domain/activity/service/ActivityCommandService.java @@ -1,70 +1,70 @@ -package com.techfork.domain.activity.service; - -import com.techfork.domain.activity.dto.BookmarkRequest; -import com.techfork.domain.activity.dto.ReadPostRequest; -import com.techfork.domain.activity.dto.SearchHistoryRequest; -import com.techfork.domain.activity.entity.ReadPost; -import com.techfork.domain.activity.entity.Bookmark; -import com.techfork.domain.activity.entity.SearchHistory; -import com.techfork.domain.activity.exception.ActivityErrorCode; -import com.techfork.domain.activity.repository.ReadPostRepository; -import com.techfork.domain.activity.repository.BookmarkRepository; -import com.techfork.domain.activity.repository.SearchHistoryRepository; -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.post.exception.PostErrorCode; -import com.techfork.domain.post.repository.PostRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.global.exception.GeneralException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -@Slf4j -@Service -@RequiredArgsConstructor -public class ActivityCommandService { - - private final ReadPostRepository readPostRepository; - private final PostRepository postRepository; - private final UserRepository userRepository; - private final SearchHistoryRepository searchHistoryRepository; - private final BookmarkRepository bookmarkRepository; - - @Transactional - public void saveReadPost(Long userId, ReadPostRequest request) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - Post post = postRepository.findById(request.postId()) - .orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND)); - - boolean isFirstRead = !readPostRepository.existsByUserAndPost(user, post); - if (isFirstRead) { - post.incrementViewCount(); - } - - ReadPost readPost = ReadPost.create( - user, - post, - request.readAt(), - request.readDurationSeconds() - ); - - readPostRepository.save(readPost); - log.info("Saved read post for user {} and post {} (viewCount incremented: {})", - userId, request.postId(), isFirstRead); - } - - @Transactional - public void saveSearchHistory(Long userId, SearchHistoryRequest request) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - +package com.techfork.domain.activity.service; + +import com.techfork.domain.activity.dto.BookmarkRequest; +import com.techfork.domain.activity.dto.ReadPostRequest; +import com.techfork.domain.activity.dto.SearchHistoryRequest; +import com.techfork.domain.activity.entity.ReadPost; +import com.techfork.domain.activity.entity.Bookmark; +import com.techfork.domain.activity.entity.SearchHistory; +import com.techfork.domain.activity.exception.ActivityErrorCode; +import com.techfork.domain.activity.repository.ReadPostRepository; +import com.techfork.domain.activity.repository.BookmarkRepository; +import com.techfork.domain.activity.repository.SearchHistoryRepository; +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.post.exception.PostErrorCode; +import com.techfork.domain.post.repository.PostRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.global.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ActivityCommandService { + + private final ReadPostRepository readPostRepository; + private final PostRepository postRepository; + private final UserRepository userRepository; + private final SearchHistoryRepository searchHistoryRepository; + private final BookmarkRepository bookmarkRepository; + + @Transactional + public void saveReadPost(Long userId, ReadPostRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + Post post = postRepository.findById(request.postId()) + .orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND)); + + boolean isFirstRead = !readPostRepository.existsByUserAndPost(user, post); + if (isFirstRead) { + post.incrementViewCount(); + } + + ReadPost readPost = ReadPost.create( + user, + post, + request.readAt(), + request.readDurationSeconds() + ); + + readPostRepository.save(readPost); + log.info("Saved read post for user {} and post {} (viewCount incremented: {})", + userId, request.postId(), isFirstRead); + } + + @Transactional + public void saveSearchHistory(Long userId, SearchHistoryRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + SearchHistory searchHistory = SearchHistory.create( user, request.query(), @@ -74,38 +74,38 @@ public void saveSearchHistory(Long userId, SearchHistoryRequest request) { searchHistoryRepository.save(searchHistory); log.info("Saved search history for user {} with query: {}", userId, request.query()); } - - @Transactional - public void addBookmark(Long userId, BookmarkRequest request) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - Post post = postRepository.findById(request.postId()) - .orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND)); - - if (bookmarkRepository.existsByUserAndPost(user, post)) { - throw new GeneralException(ActivityErrorCode.BOOKMARK_ALREADY_EXISTS); - } - - Bookmark bookmark = Bookmark.create(user, post, LocalDateTime.now()); - bookmarkRepository.save(bookmark); - - log.info("Saved bookmark for user {} and post {}", userId, request.postId()); - } - - @Transactional - public void deleteBookmark(Long userId, BookmarkRequest request) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - Post post = postRepository.findById(request.postId()) - .orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND)); - - Bookmark bookmark = bookmarkRepository.findByUserAndPost(user, post) - .orElseThrow(() -> new GeneralException(ActivityErrorCode.BOOKMARK_NOT_FOUND)); - - bookmarkRepository.delete(bookmark); - log.info("Deleted bookmark for user {} and post {}", userId, request.postId()); - } - -} + + @Transactional + public void addBookmark(Long userId, BookmarkRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + Post post = postRepository.findById(request.postId()) + .orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND)); + + if (bookmarkRepository.existsByUserAndPost(user, post)) { + throw new GeneralException(ActivityErrorCode.BOOKMARK_ALREADY_EXISTS); + } + + Bookmark bookmark = Bookmark.create(user, post, LocalDateTime.now()); + bookmarkRepository.save(bookmark); + + log.info("Saved bookmark for user {} and post {}", userId, request.postId()); + } + + @Transactional + public void deleteBookmark(Long userId, BookmarkRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + Post post = postRepository.findById(request.postId()) + .orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND)); + + Bookmark bookmark = bookmarkRepository.findByUserAndPost(user, post) + .orElseThrow(() -> new GeneralException(ActivityErrorCode.BOOKMARK_NOT_FOUND)); + + bookmarkRepository.delete(bookmark); + log.info("Deleted bookmark for user {} and post {}", userId, request.postId()); + } + +} diff --git a/src/main/java/com/techfork/domain/activity/service/ActivityQueryService.java b/src/main/java/com/techfork/domain/activity/service/ActivityQueryService.java index bea27641..2372f4ac 100644 --- a/src/main/java/com/techfork/domain/activity/service/ActivityQueryService.java +++ b/src/main/java/com/techfork/domain/activity/service/ActivityQueryService.java @@ -1,158 +1,158 @@ -package com.techfork.domain.activity.service; - -import com.techfork.domain.activity.converter.ActivityConverter; -import com.techfork.domain.activity.dto.BookmarkDto; -import com.techfork.domain.activity.dto.BookmarkListResponse; -import com.techfork.domain.activity.dto.ReadPostDto; -import com.techfork.domain.activity.dto.ReadPostListResponse; -import com.techfork.domain.activity.repository.ReadPostRepository; -import com.techfork.domain.activity.repository.BookmarkRepository; -import com.techfork.domain.post.entity.PostKeyword; -import com.techfork.domain.post.repository.PostKeywordRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.global.exception.GeneralException; -import com.techfork.global.util.CloudflareThirdPartyThumbnailOptimizer; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class ActivityQueryService { - - private final UserRepository userRepository; - private final BookmarkRepository bookmarkRepository; - private final PostKeywordRepository postKeywordRepository; - private final ReadPostRepository readPostRepository; - private final ActivityConverter activityConverter; - private final CloudflareThirdPartyThumbnailOptimizer thumbnailOptimizer; - - public BookmarkListResponse getBookmarks(Long userId, Long lastBookmarkId, int size) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - PageRequest pageRequest = PageRequest.of(0, size + 1); - List bookmarks = bookmarkRepository.findBookmarksWithCursor(user, lastBookmarkId, pageRequest); - List bookmarksWithKeywords = attachKeywordsToPostInfoList(bookmarks); - - return activityConverter.toBookmarkListResponse(bookmarksWithKeywords, size); - } - - public ReadPostListResponse getReadPosts(Long userId, Long lastReadPostId, int size) { - PageRequest pageRequest = PageRequest.of(0, size + 1); - List readPosts = readPostRepository.findReadPostsWithCursor(userId, lastReadPostId, pageRequest); - List readPostsWithKeywords = attachKeywordsToReadPosts(readPosts); - List readPostsWithBookmarks = attachBookmarksToReadPosts(readPostsWithKeywords, userId); - - return activityConverter.toReadPostListResponse(readPostsWithBookmarks, size); - } - - private List attachKeywordsToPostInfoList(List bookmarks) { - if (bookmarks.isEmpty()) { - return bookmarks; - } - - List postIds = bookmarks.stream() - .map(BookmarkDto::postId) - .toList(); - - Map> keywordMap = postKeywordRepository.findByPostIdIn(postIds) - .stream() - .collect(Collectors.groupingBy( - pk -> pk.getPost().getId(), - Collectors.mapping(PostKeyword::getKeyword, Collectors.toList()) - )); - - return bookmarks.stream() - .map(post -> BookmarkDto.builder() - .bookmarkId(post.bookmarkId()) - .postId(post.postId()) - .title(post.title()) - .shortSummary(post.shortSummary()) - .url(post.url()) - .companyName(post.companyName()) - .logoUrl(post.logoUrl()) - .publishedAt(post.publishedAt()) - .thumbnailUrl(thumbnailOptimizer.optimize(post.thumbnailUrl())) - .viewCount(post.viewCount()) - .keywords(keywordMap.getOrDefault(post.postId(), List.of())) - .isBookmarked(post.isBookmarked()) - .build()) - .toList(); - } - - private List attachKeywordsToReadPosts(List readPosts) { - if (readPosts.isEmpty()) { - return readPosts; - } - - List postIds = readPosts.stream() - .map(ReadPostDto::postId) - .toList(); - - Map> keywordMap = postKeywordRepository.findByPostIdIn(postIds) - .stream() - .collect(Collectors.groupingBy( - pk -> pk.getPost().getId(), - Collectors.mapping(PostKeyword::getKeyword, Collectors.toList()) - )); - - return readPosts.stream() - .map(readPost -> ReadPostDto.builder() - .readPostId(readPost.readPostId()) - .postId(readPost.postId()) - .title(readPost.title()) - .shortSummary(readPost.shortSummary()) - .url(readPost.url()) - .companyName(readPost.companyName()) - .logoUrl(readPost.logoUrl()) - .publishedAt(readPost.publishedAt()) - .thumbnailUrl(thumbnailOptimizer.optimize(readPost.thumbnailUrl())) - .viewCount(readPost.viewCount()) - .keywords(keywordMap.getOrDefault(readPost.postId(), List.of())) - .isBookmarked(null) - .readAt(readPost.readAt()) - .build()) - .toList(); - } - - private List attachBookmarksToReadPosts(List readPosts, Long userId) { - if (readPosts.isEmpty()) { - return readPosts; - } - - List postIds = readPosts.stream() - .map(ReadPostDto::postId) - .toList(); - - List bookmarkedPostIds = bookmarkRepository.findBookmarkedPostIds(userId, postIds); - - return readPosts.stream() - .map(readPost -> ReadPostDto.builder() - .readPostId(readPost.readPostId()) - .postId(readPost.postId()) - .title(readPost.title()) - .shortSummary(readPost.shortSummary()) - .url(readPost.url()) - .companyName(readPost.companyName()) - .logoUrl(readPost.logoUrl()) - .publishedAt(readPost.publishedAt()) - .thumbnailUrl(thumbnailOptimizer.optimize(readPost.thumbnailUrl())) - .viewCount(readPost.viewCount()) - .keywords(readPost.keywords()) - .isBookmarked(bookmarkedPostIds.contains(readPost.postId())) - .readAt(readPost.readAt()) - .build()) - .toList(); - } -} +package com.techfork.domain.activity.service; + +import com.techfork.domain.activity.converter.ActivityConverter; +import com.techfork.domain.activity.dto.BookmarkDto; +import com.techfork.domain.activity.dto.BookmarkListResponse; +import com.techfork.domain.activity.dto.ReadPostDto; +import com.techfork.domain.activity.dto.ReadPostListResponse; +import com.techfork.domain.activity.repository.ReadPostRepository; +import com.techfork.domain.activity.repository.BookmarkRepository; +import com.techfork.domain.post.entity.PostKeyword; +import com.techfork.domain.post.repository.PostKeywordRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.global.exception.GeneralException; +import com.techfork.global.util.CloudflareThirdPartyThumbnailOptimizer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ActivityQueryService { + + private final UserRepository userRepository; + private final BookmarkRepository bookmarkRepository; + private final PostKeywordRepository postKeywordRepository; + private final ReadPostRepository readPostRepository; + private final ActivityConverter activityConverter; + private final CloudflareThirdPartyThumbnailOptimizer thumbnailOptimizer; + + public BookmarkListResponse getBookmarks(Long userId, Long lastBookmarkId, int size) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + PageRequest pageRequest = PageRequest.of(0, size + 1); + List bookmarks = bookmarkRepository.findBookmarksWithCursor(user, lastBookmarkId, pageRequest); + List bookmarksWithKeywords = attachKeywordsToPostInfoList(bookmarks); + + return activityConverter.toBookmarkListResponse(bookmarksWithKeywords, size); + } + + public ReadPostListResponse getReadPosts(Long userId, Long lastReadPostId, int size) { + PageRequest pageRequest = PageRequest.of(0, size + 1); + List readPosts = readPostRepository.findReadPostsWithCursor(userId, lastReadPostId, pageRequest); + List readPostsWithKeywords = attachKeywordsToReadPosts(readPosts); + List readPostsWithBookmarks = attachBookmarksToReadPosts(readPostsWithKeywords, userId); + + return activityConverter.toReadPostListResponse(readPostsWithBookmarks, size); + } + + private List attachKeywordsToPostInfoList(List bookmarks) { + if (bookmarks.isEmpty()) { + return bookmarks; + } + + List postIds = bookmarks.stream() + .map(BookmarkDto::postId) + .toList(); + + Map> keywordMap = postKeywordRepository.findByPostIdIn(postIds) + .stream() + .collect(Collectors.groupingBy( + pk -> pk.getPost().getId(), + Collectors.mapping(PostKeyword::getKeyword, Collectors.toList()) + )); + + return bookmarks.stream() + .map(post -> BookmarkDto.builder() + .bookmarkId(post.bookmarkId()) + .postId(post.postId()) + .title(post.title()) + .shortSummary(post.shortSummary()) + .url(post.url()) + .companyName(post.companyName()) + .logoUrl(post.logoUrl()) + .publishedAt(post.publishedAt()) + .thumbnailUrl(thumbnailOptimizer.optimize(post.thumbnailUrl())) + .viewCount(post.viewCount()) + .keywords(keywordMap.getOrDefault(post.postId(), List.of())) + .isBookmarked(post.isBookmarked()) + .build()) + .toList(); + } + + private List attachKeywordsToReadPosts(List readPosts) { + if (readPosts.isEmpty()) { + return readPosts; + } + + List postIds = readPosts.stream() + .map(ReadPostDto::postId) + .toList(); + + Map> keywordMap = postKeywordRepository.findByPostIdIn(postIds) + .stream() + .collect(Collectors.groupingBy( + pk -> pk.getPost().getId(), + Collectors.mapping(PostKeyword::getKeyword, Collectors.toList()) + )); + + return readPosts.stream() + .map(readPost -> ReadPostDto.builder() + .readPostId(readPost.readPostId()) + .postId(readPost.postId()) + .title(readPost.title()) + .shortSummary(readPost.shortSummary()) + .url(readPost.url()) + .companyName(readPost.companyName()) + .logoUrl(readPost.logoUrl()) + .publishedAt(readPost.publishedAt()) + .thumbnailUrl(thumbnailOptimizer.optimize(readPost.thumbnailUrl())) + .viewCount(readPost.viewCount()) + .keywords(keywordMap.getOrDefault(readPost.postId(), List.of())) + .isBookmarked(null) + .readAt(readPost.readAt()) + .build()) + .toList(); + } + + private List attachBookmarksToReadPosts(List readPosts, Long userId) { + if (readPosts.isEmpty()) { + return readPosts; + } + + List postIds = readPosts.stream() + .map(ReadPostDto::postId) + .toList(); + + List bookmarkedPostIds = bookmarkRepository.findBookmarkedPostIds(userId, postIds); + + return readPosts.stream() + .map(readPost -> ReadPostDto.builder() + .readPostId(readPost.readPostId()) + .postId(readPost.postId()) + .title(readPost.title()) + .shortSummary(readPost.shortSummary()) + .url(readPost.url()) + .companyName(readPost.companyName()) + .logoUrl(readPost.logoUrl()) + .publishedAt(readPost.publishedAt()) + .thumbnailUrl(thumbnailOptimizer.optimize(readPost.thumbnailUrl())) + .viewCount(readPost.viewCount()) + .keywords(readPost.keywords()) + .isBookmarked(bookmarkedPostIds.contains(readPost.postId())) + .readAt(readPost.readAt()) + .build()) + .toList(); + } +} diff --git a/src/main/java/com/techfork/domain/auth/converter/AuthConverter.java b/src/main/java/com/techfork/domain/auth/converter/AuthConverter.java index b1c4f84e..9128836b 100644 --- a/src/main/java/com/techfork/domain/auth/converter/AuthConverter.java +++ b/src/main/java/com/techfork/domain/auth/converter/AuthConverter.java @@ -2,7 +2,7 @@ import com.techfork.domain.auth.dto.DeveloperTokenResponse; import com.techfork.domain.auth.dto.KakaoLoginResponse; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/com/techfork/domain/auth/service/AuthService.java b/src/main/java/com/techfork/domain/auth/service/AuthService.java index a4cb36e6..1c3c3ec0 100644 --- a/src/main/java/com/techfork/domain/auth/service/AuthService.java +++ b/src/main/java/com/techfork/domain/auth/service/AuthService.java @@ -6,10 +6,10 @@ import com.techfork.domain.auth.dto.TokenRefreshResponse; import com.techfork.domain.auth.dto.kakao.KakaoUserInfoResponse; import com.techfork.domain.auth.exception.AuthErrorCode; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import com.techfork.global.security.auth.service.RefreshTokenService; import com.techfork.global.security.auth.service.UserAuthCacheService; diff --git a/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java b/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java index 9e40d540..7b70033f 100644 --- a/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java +++ b/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java @@ -1,24 +1,24 @@ -package com.techfork.domain.notification.entity; - -import com.techfork.domain.user.entity.User; -import com.techfork.global.common.BaseTimeEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class NotificationToken extends BaseTimeEntity { - - @Column(nullable = false, length = 500) - private String token; - - @Column(nullable = false) - private Boolean isActive = true; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; -} +package com.techfork.domain.notification.entity; + +import com.techfork.domain.useraccount.entity.User; +import com.techfork.global.common.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class NotificationToken extends BaseTimeEntity { + + @Column(nullable = false, length = 500) + private String token; + + @Column(nullable = false) + private Boolean isActive = true; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; +} diff --git a/src/main/java/com/techfork/domain/user/document/UserProfileDocument.java b/src/main/java/com/techfork/domain/personalization/document/PersonalizationProfileDocument.java similarity index 84% rename from src/main/java/com/techfork/domain/user/document/UserProfileDocument.java rename to src/main/java/com/techfork/domain/personalization/document/PersonalizationProfileDocument.java index b23c7743..866ba496 100644 --- a/src/main/java/com/techfork/domain/user/document/UserProfileDocument.java +++ b/src/main/java/com/techfork/domain/personalization/document/PersonalizationProfileDocument.java @@ -1,69 +1,69 @@ -package com.techfork.domain.user.document; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Transient; -import org.springframework.data.elasticsearch.annotations.Document; -import org.springframework.data.elasticsearch.annotations.Field; -import org.springframework.data.elasticsearch.annotations.FieldType; - -import java.time.LocalDateTime; -import java.util.List; - -@Document(indexName = "user_profiles") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@JsonIgnoreProperties(ignoreUnknown = true) -public class UserProfileDocument { - - @Id - private String id; // userId๋ฅผ ๋ฌธ์ž์—ด๋กœ - - @Field(type = FieldType.Long) - private Long userId; - - @Field(type = FieldType.Text) - private String profileText; - - @Field(type = FieldType.Dense_Vector, dims = 3072) // OpenAI text-embedding-3-large dimension - private float[] profileVector; - - @Field(type = FieldType.Keyword) - private List interests; - - @Field(type = FieldType.Keyword) - private List keyKeywords; - - @Field(type = FieldType.Date) - @Transient - private LocalDateTime generatedAt; - - @Builder - private UserProfileDocument(Long userId, String profileText, float[] profileVector, - List interests, List keyKeywords, LocalDateTime generatedAt) { - this.id = String.valueOf(userId); - this.userId = userId; - this.profileText = profileText; - this.profileVector = profileVector; - this.interests = interests; - this.keyKeywords = keyKeywords; - this.generatedAt = generatedAt; - } - - public static UserProfileDocument create(Long userId, String profileText, float[] profileVector, - List interests, List keyKeywords) { - return UserProfileDocument.builder() - .userId(userId) - .profileText(profileText) - .profileVector(profileVector) - .interests(interests) - .keyKeywords(keyKeywords) - .generatedAt(LocalDateTime.now()) - .build(); - } -} +package com.techfork.domain.personalization.document; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.time.LocalDateTime; +import java.util.List; + +@Document(indexName = "user_profiles") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PersonalizationProfileDocument { + + @Id + private String id; // userId๋ฅผ ๋ฌธ์ž์—ด๋กœ + + @Field(type = FieldType.Long) + private Long userId; + + @Field(type = FieldType.Text) + private String profileText; + + @Field(type = FieldType.Dense_Vector, dims = 3072) // OpenAI text-embedding-3-large dimension + private float[] profileVector; + + @Field(type = FieldType.Keyword) + private List interests; + + @Field(type = FieldType.Keyword) + private List keyKeywords; + + @Field(type = FieldType.Date) + @Transient + private LocalDateTime generatedAt; + + @Builder + private PersonalizationProfileDocument(Long userId, String profileText, float[] profileVector, + List interests, List keyKeywords, LocalDateTime generatedAt) { + this.id = String.valueOf(userId); + this.userId = userId; + this.profileText = profileText; + this.profileVector = profileVector; + this.interests = interests; + this.keyKeywords = keyKeywords; + this.generatedAt = generatedAt; + } + + public static PersonalizationProfileDocument create(Long userId, String profileText, float[] profileVector, + List interests, List keyKeywords) { + return PersonalizationProfileDocument.builder() + .userId(userId) + .profileText(profileText) + .profileVector(profileVector) + .interests(interests) + .keyKeywords(keyKeywords) + .generatedAt(LocalDateTime.now()) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/personalization/repository/PersonalizationProfileDocumentRepository.java b/src/main/java/com/techfork/domain/personalization/repository/PersonalizationProfileDocumentRepository.java new file mode 100644 index 00000000..05fea5a2 --- /dev/null +++ b/src/main/java/com/techfork/domain/personalization/repository/PersonalizationProfileDocumentRepository.java @@ -0,0 +1,9 @@ +package com.techfork.domain.personalization.repository; + +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import java.util.Optional; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +public interface PersonalizationProfileDocumentRepository extends ElasticsearchRepository { + Optional findByUserId(Long id); +} \ No newline at end of file diff --git a/src/main/java/com/techfork/domain/user/scheduler/UserProfileScheduler.java b/src/main/java/com/techfork/domain/personalization/scheduler/PersonalizationProfileScheduler.java similarity index 52% rename from src/main/java/com/techfork/domain/user/scheduler/UserProfileScheduler.java rename to src/main/java/com/techfork/domain/personalization/scheduler/PersonalizationProfileScheduler.java index f2464334..d75486c3 100644 --- a/src/main/java/com/techfork/domain/user/scheduler/UserProfileScheduler.java +++ b/src/main/java/com/techfork/domain/personalization/scheduler/PersonalizationProfileScheduler.java @@ -1,45 +1,45 @@ -package com.techfork.domain.user.scheduler; - -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.domain.user.service.UserProfileService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.util.List; - -@Slf4j -@Component -@RequiredArgsConstructor -public class UserProfileScheduler { - - private final UserRepository userRepository; - private final UserProfileService userProfileService; - - /** - * ๋งค์ผ ์˜ค์ „ 6์‹œ(KST)์— ์ตœ๊ทผ 24์‹œ๊ฐ„ ๋‚ด ํ™œ์„ฑ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„์„ ์žฌ์ƒ์„ฑ - * - ํฌ๋กค๋ง(5์‹œ) ํ›„ 1์‹œ๊ฐ„ ๋’ค ์‹คํ–‰ - */ - @Scheduled(cron = "0 0 6 * * *", zone = "Asia/Seoul") - public void regenerateActiveUserProfiles() { - log.info("Starting daily user profile regeneration for active users"); - - LocalDateTime since = LocalDateTime.now().minusHours(24); - List activeUsers = userRepository.findActiveUsersSince(since); - - log.info("Found {} active users in the last 24 hours", activeUsers.size()); - - activeUsers.forEach(user -> { - try { - userProfileService.generateUserProfile(user.getId()); - } catch (Exception e) { - log.error("Failed to generate profile for user: {}", user.getId(), e); - } - }); - - log.info("Completed daily user profile regeneration for {} active users", activeUsers.size()); - } -} +package com.techfork.domain.personalization.scheduler; + +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.domain.personalization.service.PersonalizationProfileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PersonalizationProfileScheduler { + + private final UserRepository userRepository; + private final PersonalizationProfileService personalizationProfileService; + + /** + * ๋งค์ผ ์˜ค์ „ 6์‹œ(KST)์— ์ตœ๊ทผ 24์‹œ๊ฐ„ ๋‚ด ํ™œ์„ฑ ์‚ฌ์šฉ์ž์˜ ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ์žฌ์ƒ์„ฑ + * - ํฌ๋กค๋ง(5์‹œ) ํ›„ 1์‹œ๊ฐ„ ๋’ค ์‹คํ–‰ + */ + @Scheduled(cron = "0 0 6 * * *", zone = "Asia/Seoul") + public void regenerateActiveUserProfiles() { + log.info("Starting daily personalization profile regeneration for active users"); + + LocalDateTime since = LocalDateTime.now().minusHours(24); + List activeUsers = userRepository.findActiveUsersSince(since); + + log.info("Found {} active users in the last 24 hours", activeUsers.size()); + + activeUsers.forEach(user -> { + try { + personalizationProfileService.generatePersonalizationProfile(user.getId()); + } catch (Exception e) { + log.error("Failed to generate personalization profile for user: {}", user.getId(), e); + } + }); + + log.info("Completed daily personalization profile regeneration for {} active users", activeUsers.size()); + } +} diff --git a/src/main/java/com/techfork/domain/user/service/UserProfileService.java b/src/main/java/com/techfork/domain/personalization/service/PersonalizationProfileService.java similarity index 87% rename from src/main/java/com/techfork/domain/user/service/UserProfileService.java rename to src/main/java/com/techfork/domain/personalization/service/PersonalizationProfileService.java index c7f2002a..d3c26088 100644 --- a/src/main/java/com/techfork/domain/user/service/UserProfileService.java +++ b/src/main/java/com/techfork/domain/personalization/service/PersonalizationProfileService.java @@ -1,304 +1,304 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.activity.entity.ReadPost; -import com.techfork.domain.activity.entity.Bookmark; -import com.techfork.domain.activity.entity.SearchHistory; -import com.techfork.domain.activity.repository.ReadPostRepository; -import com.techfork.domain.activity.repository.BookmarkRepository; -import com.techfork.domain.activity.repository.SearchHistoryRepository; -import com.techfork.domain.post.entity.PostKeyword; -import com.techfork.domain.recommendation.service.RecommendationService; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserInterestCategoryRepository; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.global.exception.GeneralException; -import com.techfork.global.llm.EmbeddingClient; -import com.techfork.global.llm.LlmClient; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -@Service -@RequiredArgsConstructor -public class UserProfileService { - - private final UserInterestCategoryRepository userInterestCategoryRepository; - private final ReadPostRepository readPostRepository; - private final BookmarkRepository bookmarkRepository; - private final SearchHistoryRepository searchHistoryRepository; - private final UserProfileDocumentRepository userProfileDocumentRepository; - private final UserRepository userRepository; - private final RecommendationService recommendationService; - private final LlmClient llmClient; - private final EmbeddingClient embeddingClient; - - @Async - @Transactional - public void generateUserProfile(Long userId) { - generateUserProfileSync(userId); - } - - /** - * ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ƒ์„ฑ (๋™๊ธฐ ๋ฒ„์ „) - * ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์ด๋‚˜ ๋™๊ธฐ ์‹คํ–‰์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉ - */ - @Transactional - public void generateUserProfileSync(Long userId) { - try { - UserActivityData activityData = collectUserActivityData(userId); - String llmResponse = generateProfileTextWithLLM(activityData); - - ProfileAndKeywords parsed = parseProfileAndKeywords(llmResponse); - float[] profileVector = generateEmbeddingVector(parsed.profileText); - - UserProfileDocument profileDocument = UserProfileDocument.create( - userId, - parsed.profileText, - profileVector, - activityData.interests, - parsed.keyKeywords - ); - - userProfileDocumentRepository.save(profileDocument); - - log.info("User profile generated successfully for userId: {}", userId); - - generateRecommendationsAfterProfile(userId); - - } catch (Exception e) { - log.error("Failed to generate user profile for userId: {}", userId, e); - throw e; - } - } - - /** - * ํ”„๋กœํ•„ ์ƒ์„ฑ ์™„๋ฃŒ ํ›„ ์ถ”์ฒœ ์ƒ์„ฑ - * ์˜จ๋ณด๋”ฉ ๋˜๋Š” ๊ด€์‹ฌ์‚ฌ ๋ณ€๊ฒฝ ์‹œ ์ƒˆ๋กœ์šด ํ”„๋กœํ•„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ถ”์ฒœ์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. - */ - private void generateRecommendationsAfterProfile(Long userId) { - try { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - int recommendationCount = recommendationService.generateRecommendationsForUser(user); - - log.info("Recommendations generated after profile creation for userId: {} - {} recommendations created", - userId, recommendationCount); - - } catch (Exception e) { - log.error("Failed to generate recommendations after profile creation for userId: {}", userId, e); - } - } - - private UserActivityData collectUserActivityData(Long userId) { - List categories = userInterestCategoryRepository.findByUserIdWithKeywords(userId); - List interests = categories.stream() - .flatMap(c -> c.getKeywords().stream()) - .map(k -> k.getKeyword().getDisplayName()) - .toList(); - - List readPosts = readPostRepository.findRecentReadPostsByUserIdWithMinDuration(userId, PageRequest.of(0, 20)); - List readPostData = readPosts.stream() - .map(rp -> new PostData( - rp.getPost().getTitle(), - rp.getPost().getKeywords().stream() - .map(PostKeyword::getKeyword) - .toList(), - convertReadingDurationToNaturalLanguage(rp.getReadDurationSeconds()) - )) - .toList(); - - List bookmarks = bookmarkRepository.findRecentBookmarksByUserId(userId, PageRequest.of(0, 20)); - List bookmarkedPostData = bookmarks.stream() - .map(sp -> new PostData( - sp.getPost().getTitle(), - sp.getPost().getKeywords().stream() - .map(PostKeyword::getKeyword) - .toList(), - null - )) - .toList(); - +package com.techfork.domain.personalization.service; + +import com.techfork.domain.activity.entity.ReadPost; +import com.techfork.domain.activity.entity.Bookmark; +import com.techfork.domain.activity.entity.SearchHistory; +import com.techfork.domain.activity.repository.ReadPostRepository; +import com.techfork.domain.activity.repository.BookmarkRepository; +import com.techfork.domain.activity.repository.SearchHistoryRepository; +import com.techfork.domain.post.entity.PostKeyword; +import com.techfork.domain.recommendation.service.RecommendationService; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserInterestCategoryRepository; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.global.exception.GeneralException; +import com.techfork.global.llm.EmbeddingClient; +import com.techfork.global.llm.LlmClient; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PersonalizationProfileService { + + private final UserInterestCategoryRepository userInterestCategoryRepository; + private final ReadPostRepository readPostRepository; + private final BookmarkRepository bookmarkRepository; + private final SearchHistoryRepository searchHistoryRepository; + private final PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; + private final UserRepository userRepository; + private final RecommendationService recommendationService; + private final LlmClient llmClient; + private final EmbeddingClient embeddingClient; + + @Async + @Transactional + public void generatePersonalizationProfile(Long userId) { + generatePersonalizationProfileSync(userId); + } + + /** + * ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ (๋™๊ธฐ ๋ฒ„์ „) + * ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์ด๋‚˜ ๋™๊ธฐ ์‹คํ–‰์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉ + */ + @Transactional + public void generatePersonalizationProfileSync(Long userId) { + try { + UserActivityData activityData = collectUserActivityData(userId); + String llmResponse = generateProfileTextWithLLM(activityData); + + ProfileAndKeywords parsed = parseProfileAndKeywords(llmResponse); + float[] profileVector = generateEmbeddingVector(parsed.profileText); + + PersonalizationProfileDocument profileDocument = PersonalizationProfileDocument.create( + userId, + parsed.profileText, + profileVector, + activityData.interests, + parsed.keyKeywords + ); + + personalizationProfileDocumentRepository.save(profileDocument); + + log.info("Personalization profile generated successfully for userId: {}", userId); + + generateRecommendationsAfterProfile(userId); + + } catch (Exception e) { + log.error("Failed to generate personalization profile for userId: {}", userId, e); + throw e; + } + } + + /** + * ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ƒ์„ฑ ์™„๋ฃŒ ํ›„ ์ถ”์ฒœ ์ƒ์„ฑ + * ์˜จ๋ณด๋”ฉ ๋˜๋Š” ๊ด€์‹ฌ์‚ฌ ๋ณ€๊ฒฝ ์‹œ ์ƒˆ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ถ”์ฒœ์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. + */ + private void generateRecommendationsAfterProfile(Long userId) { + try { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + int recommendationCount = recommendationService.generateRecommendationsForUser(user); + + log.info("Recommendations generated after personalization profile creation for userId: {} - {} recommendations created", + userId, recommendationCount); + + } catch (Exception e) { + log.error("Failed to generate recommendations after personalization profile creation for userId: {}", userId, e); + } + } + + private UserActivityData collectUserActivityData(Long userId) { + List categories = userInterestCategoryRepository.findByUserIdWithKeywords(userId); + List interests = categories.stream() + .flatMap(c -> c.getKeywords().stream()) + .map(k -> k.getKeyword().getDisplayName()) + .toList(); + + List readPosts = readPostRepository.findRecentReadPostsByUserIdWithMinDuration(userId, PageRequest.of(0, 20)); + List readPostData = readPosts.stream() + .map(rp -> new PostData( + rp.getPost().getTitle(), + rp.getPost().getKeywords().stream() + .map(PostKeyword::getKeyword) + .toList(), + convertReadingDurationToNaturalLanguage(rp.getReadDurationSeconds()) + )) + .toList(); + + List bookmarks = bookmarkRepository.findRecentBookmarksByUserId(userId, PageRequest.of(0, 20)); + List bookmarkedPostData = bookmarks.stream() + .map(sp -> new PostData( + sp.getPost().getTitle(), + sp.getPost().getKeywords().stream() + .map(PostKeyword::getKeyword) + .toList(), + null + )) + .toList(); + List searchHistories = searchHistoryRepository.findRecentSearchHistoriesByUserId(userId, PageRequest.of(0, 30)); List searchQueries = searchHistories.stream() .map(SearchHistory::getQuery) .toList(); return new UserActivityData(interests, readPostData, bookmarkedPostData, searchQueries); - } - - private String generateProfileTextWithLLM(UserActivityData data) { - String systemPrompt = "๋‹น์‹ ์€ ํ…Œํฌ ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ์˜ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ถ„์„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ฒ€์ƒ‰ ๊ณ ๋„ํ™”์™€ ํฌ์ŠคํŠธ ์ถ”์ฒœ์— ์ตœ์ ํ™”๋œ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค."; - String userPrompt = buildProfileGenerationPrompt(data); - return llmClient.call(systemPrompt, userPrompt); - } - - private String buildProfileGenerationPrompt(UserActivityData data) { - return String.format(""" - ์•„๋ž˜ ์‚ฌ์šฉ์ž์˜ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ฒ€์ƒ‰ ๋ฆฌ๋žญํ‚น๊ณผ ํฌ์ŠคํŠธ ์ถ”์ฒœ์— ์ตœ์ ํ™”๋œ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. - - ## ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ - - ### ๊ด€์‹ฌ ๊ธฐ์ˆ  ์Šคํƒ ๋ฐ ๋ถ„์•ผ - %s - - ### ์ตœ๊ทผ ์ฝ์€ ํฌ์ŠคํŠธ - %s - - ### ์Šคํฌ๋žฉํ•œ ํฌ์ŠคํŠธ - %s - - ### ๊ฒ€์ƒ‰ ๊ธฐ๋ก - %s - - ## ์š”๊ตฌ์‚ฌํ•ญ - - ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•ด์ฃผ์„ธ์š”: - - ### PROFILE - ์‚ฌ์šฉ์ž์˜ ๊ธฐ์ˆ ์  ๊ด€์‹ฌ์‚ฌ, ํ•™์Šต ํŒจํ„ด, ์„ ํ˜ธ๋„๋ฅผ ์˜๋ฏธ ๋ฐ€๋„ ๋†’๊ณ  ํ’๋ถ€ํ•˜๊ฒŒ ํ‘œํ˜„ํ•œ ํ…์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” (200-300์ž ์ •๋„). - - ๋‹ค์Œ ๋‚ด์šฉ์„ ๋ชจ๋‘ ํฌํ•จํ•˜๋˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ: - 1. ์ฃผ์š” ๊ด€์‹ฌ ๊ธฐ์ˆ  ์Šคํƒ๊ณผ ๊ฐœ๋ฐœ ๋ถ„์•ผ (๋ฐฑ์—”๋“œ/ํ”„๋ก ํŠธ์—”๋“œ/์ธํ”„๋ผ/AI ๋“ฑ) - 2. ์„ ํ˜ธํ•˜๋Š” ์ฃผ์ œ์™€ ํ•™์Šต ๋ฐฉํ–ฅ (์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„, ์„ฑ๋Šฅ ์ตœ์ ํ™”, ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…, ์‹ ๊ธฐ์ˆ  ํƒ๊ตฌ ๋“ฑ) - 3. ์ฝ์€ ํฌ์ŠคํŠธ์™€ ๊ฒ€์ƒ‰ ๊ธฐ๋ก์—์„œ ๋“œ๋Ÿฌ๋‚˜๋Š” ๊ตฌ์ฒด์ ์ธ ๊ด€์‹ฌ์‚ฌ - 4. ํ˜„์žฌ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ๋ฌธ์ œ๋‚˜ ํ•™์Šต ์ค‘์ธ ์˜์—ญ - 5. ์ฝ˜ํ…์ธ  ์„ ํ˜ธ ํŒจํ„ด (์‹ฌํ™” ๊ธฐ์ˆ , ์‹ค์ „ ๊ฒฝํ—˜, ํŠœํ† ๋ฆฌ์–ผ ๋“ฑ) - - ์ฃผ์˜์‚ฌํ•ญ: - - ๋งˆํฌ๋‹ค์šด ์—†์ด ์ˆœ์ˆ˜ ํ…์ŠคํŠธ๋กœ๋งŒ ์ž‘์„ฑ (๋ณผ๋“œ, ์ดํƒค๋ฆญ, ๋ฆฌ์ŠคํŠธ, ๋ฒˆํ˜ธ ๊ธˆ์ง€) - - ๊ตฌ์ฒด์ ์ธ ๊ธฐ์ˆ  ์šฉ์–ด๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜์—ฌ ์ž„๋ฒ ๋”ฉ ํ’ˆ์งˆ ํ–ฅ์ƒ - - "๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค", "์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค" ๊ฐ™์€ ๋ฉ”ํƒ€ ํ‘œํ˜„ ๋Œ€์‹  ์ง์ ‘์ ์ธ ๊ธฐ์ˆ  ์šฉ์–ด ๋‚˜์—ด - - ### KEYWORDS - ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๊ฐ€์žฅ ์ž˜ ๋Œ€ํ‘œํ•˜๋Š” ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ 3-5๊ฐœ๋ฅผ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋‚˜์—ดํ•˜์„ธ์š”. - - ๊ตฌ์ฒด์ ์ด๊ณ  ๊ฒ€์ƒ‰ ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•œ ํ‚ค์›Œ๋“œ๋งŒ ์„ ํƒ - - BM25 ๊ฒ€์ƒ‰์— ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ๊ฒ€์ƒ‰์–ด๋กœ ์ž์ฃผ ์“ฐ์ผ ๋งŒํ•œ ์šฉ์–ด ์„ ํƒ - - ์˜ˆ: Kubernetes, React hooks, ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜, ์„ฑ๋Šฅ ์ตœ์ ํ™”, MSA - - ์˜๋ฌธ๊ณผ ํ•œ๊ธ€ ํ˜ผ์šฉ ๊ฐ€๋Šฅ - - ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ ๊ด€์‹ฌ ๊ธฐ์ˆ  ์Šคํƒ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ผ๋ฐ˜์ ์ธ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. - """, + } + + private String generateProfileTextWithLLM(UserActivityData data) { + String systemPrompt = "๋‹น์‹ ์€ ํ…Œํฌ ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ์˜ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ถ„์„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ฒ€์ƒ‰ ๊ณ ๋„ํ™”์™€ ํฌ์ŠคํŠธ ์ถ”์ฒœ์— ์ตœ์ ํ™”๋œ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค."; + String userPrompt = buildProfileGenerationPrompt(data); + return llmClient.call(systemPrompt, userPrompt); + } + + private String buildProfileGenerationPrompt(UserActivityData data) { + return String.format(""" + ์•„๋ž˜ ์‚ฌ์šฉ์ž์˜ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ฒ€์ƒ‰ ๋ฆฌ๋žญํ‚น๊ณผ ํฌ์ŠคํŠธ ์ถ”์ฒœ์— ์ตœ์ ํ™”๋œ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. + + ## ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ + + ### ๊ด€์‹ฌ ๊ธฐ์ˆ  ์Šคํƒ ๋ฐ ๋ถ„์•ผ + %s + + ### ์ตœ๊ทผ ์ฝ์€ ํฌ์ŠคํŠธ + %s + + ### ์Šคํฌ๋žฉํ•œ ํฌ์ŠคํŠธ + %s + + ### ๊ฒ€์ƒ‰ ๊ธฐ๋ก + %s + + ## ์š”๊ตฌ์‚ฌํ•ญ + + ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์‘๋‹ตํ•ด์ฃผ์„ธ์š”: + + ### PROFILE + ์‚ฌ์šฉ์ž์˜ ๊ธฐ์ˆ ์  ๊ด€์‹ฌ์‚ฌ, ํ•™์Šต ํŒจํ„ด, ์„ ํ˜ธ๋„๋ฅผ ์˜๋ฏธ ๋ฐ€๋„ ๋†’๊ณ  ํ’๋ถ€ํ•˜๊ฒŒ ํ‘œํ˜„ํ•œ ํ…์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” (200-300์ž ์ •๋„). + + ๋‹ค์Œ ๋‚ด์šฉ์„ ๋ชจ๋‘ ํฌํ•จํ•˜๋˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ: + 1. ์ฃผ์š” ๊ด€์‹ฌ ๊ธฐ์ˆ  ์Šคํƒ๊ณผ ๊ฐœ๋ฐœ ๋ถ„์•ผ (๋ฐฑ์—”๋“œ/ํ”„๋ก ํŠธ์—”๋“œ/์ธํ”„๋ผ/AI ๋“ฑ) + 2. ์„ ํ˜ธํ•˜๋Š” ์ฃผ์ œ์™€ ํ•™์Šต ๋ฐฉํ–ฅ (์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„, ์„ฑ๋Šฅ ์ตœ์ ํ™”, ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…, ์‹ ๊ธฐ์ˆ  ํƒ๊ตฌ ๋“ฑ) + 3. ์ฝ์€ ํฌ์ŠคํŠธ์™€ ๊ฒ€์ƒ‰ ๊ธฐ๋ก์—์„œ ๋“œ๋Ÿฌ๋‚˜๋Š” ๊ตฌ์ฒด์ ์ธ ๊ด€์‹ฌ์‚ฌ + 4. ํ˜„์žฌ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ๋ฌธ์ œ๋‚˜ ํ•™์Šต ์ค‘์ธ ์˜์—ญ + 5. ์ฝ˜ํ…์ธ  ์„ ํ˜ธ ํŒจํ„ด (์‹ฌํ™” ๊ธฐ์ˆ , ์‹ค์ „ ๊ฒฝํ—˜, ํŠœํ† ๋ฆฌ์–ผ ๋“ฑ) + + ์ฃผ์˜์‚ฌํ•ญ: + - ๋งˆํฌ๋‹ค์šด ์—†์ด ์ˆœ์ˆ˜ ํ…์ŠคํŠธ๋กœ๋งŒ ์ž‘์„ฑ (๋ณผ๋“œ, ์ดํƒค๋ฆญ, ๋ฆฌ์ŠคํŠธ, ๋ฒˆํ˜ธ ๊ธˆ์ง€) + - ๊ตฌ์ฒด์ ์ธ ๊ธฐ์ˆ  ์šฉ์–ด๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜์—ฌ ์ž„๋ฒ ๋”ฉ ํ’ˆ์งˆ ํ–ฅ์ƒ + - "๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค", "์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค" ๊ฐ™์€ ๋ฉ”ํƒ€ ํ‘œํ˜„ ๋Œ€์‹  ์ง์ ‘์ ์ธ ๊ธฐ์ˆ  ์šฉ์–ด ๋‚˜์—ด + + ### KEYWORDS + ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๊ฐ€์žฅ ์ž˜ ๋Œ€ํ‘œํ•˜๋Š” ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ 3-5๊ฐœ๋ฅผ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋‚˜์—ดํ•˜์„ธ์š”. + - ๊ตฌ์ฒด์ ์ด๊ณ  ๊ฒ€์ƒ‰ ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•œ ํ‚ค์›Œ๋“œ๋งŒ ์„ ํƒ + - BM25 ๊ฒ€์ƒ‰์— ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ๊ฒ€์ƒ‰์–ด๋กœ ์ž์ฃผ ์“ฐ์ผ ๋งŒํ•œ ์šฉ์–ด ์„ ํƒ + - ์˜ˆ: Kubernetes, React hooks, ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜, ์„ฑ๋Šฅ ์ตœ์ ํ™”, MSA + - ์˜๋ฌธ๊ณผ ํ•œ๊ธ€ ํ˜ผ์šฉ ๊ฐ€๋Šฅ + + ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ ๊ด€์‹ฌ ๊ธฐ์ˆ  ์Šคํƒ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ผ๋ฐ˜์ ์ธ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. + """, formatList(data.interests), formatPostDataList(data.readPostData), formatPostDataList(data.bookmarkedPostData), formatList(data.searchQueries) ); - } - - private String formatList(List items) { - if (items == null || items.isEmpty()) { - return "- (๋ฐ์ดํ„ฐ ์—†์Œ)"; - } - return items.stream() - .map(item -> "- " + item) - .collect(Collectors.joining("\n")); - } - - private String formatPostDataList(List postDataList) { - if (postDataList == null || postDataList.isEmpty()) { - return "- (๋ฐ์ดํ„ฐ ์—†์Œ)"; - } - return postDataList.stream() - .map(postData -> { - String keywordsStr = postData.keywords.isEmpty() - ? "" - : " [ํ‚ค์›Œ๋“œ: " + String.join(", ", postData.keywords) + "]"; - String engagementStr = postData.readingEngagement != null - ? " (" + postData.readingEngagement + ")" - : ""; - return "- " + postData.title + keywordsStr + engagementStr; - }) - .collect(Collectors.joining("\n")); - } - - private float[] generateEmbeddingVector(String profileText) { - List embedding = embeddingClient.embed(profileText); - - float[] vector = new float[embedding.size()]; - for (int i = 0; i < embedding.size(); i++) { - vector[i] = embedding.get(i); - } - return vector; - } - - private String convertReadingDurationToNaturalLanguage(Integer durationSeconds) { - if (durationSeconds == null) { - return "์ฝ์Œ"; - } - - if (durationSeconds <= 30) { - return "๊ฐ€๋ณ๊ฒŒ ํ›‘์–ด๋ด„"; - } else if (durationSeconds <= 90) { - return "๋น ๋ฅด๊ฒŒ ์ฝ์Œ"; - } else if (durationSeconds <= 300) { - return "์ฝ์Œ"; - } else if (durationSeconds <= 600) { - return "์ •๋…ํ•จ"; - } else { - return "๊นŠ๊ฒŒ ์ฝ์Œ"; - } - } - - private ProfileAndKeywords parseProfileAndKeywords(String llmResponse) { - String profileText = ""; - List keyKeywords = List.of(); - - try { - // PROFILE ์„น์…˜ ์ถ”์ถœ - int profileStart = llmResponse.indexOf("### PROFILE"); - int keywordsStart = llmResponse.indexOf("### KEYWORDS"); - - if (profileStart != -1 && keywordsStart != -1) { - profileText = llmResponse.substring(profileStart + "### PROFILE".length(), keywordsStart) - .trim(); - - String keywordsSection = llmResponse.substring(keywordsStart + "### KEYWORDS".length()) - .trim(); - - // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ํ‚ค์›Œ๋“œ ํŒŒ์‹ฑ - keyKeywords = Arrays.stream(keywordsSection.split(",")) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .limit(5) // ์ตœ๋Œ€ 5๊ฐœ - .toList(); - } else { - // ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ํ”„๋กœํ•„๋กœ ์‚ฌ์šฉ - log.warn("Failed to parse LLM response sections, using full text as profile"); - profileText = llmResponse; - } - } catch (Exception e) { - log.error("Error parsing LLM response", e); - profileText = llmResponse; - } - - return new ProfileAndKeywords(profileText, keyKeywords); - } - - private record ProfileAndKeywords(String profileText, List keyKeywords) {} - + } + + private String formatList(List items) { + if (items == null || items.isEmpty()) { + return "- (๋ฐ์ดํ„ฐ ์—†์Œ)"; + } + return items.stream() + .map(item -> "- " + item) + .collect(Collectors.joining("\n")); + } + + private String formatPostDataList(List postDataList) { + if (postDataList == null || postDataList.isEmpty()) { + return "- (๋ฐ์ดํ„ฐ ์—†์Œ)"; + } + return postDataList.stream() + .map(postData -> { + String keywordsStr = postData.keywords.isEmpty() + ? "" + : " [ํ‚ค์›Œ๋“œ: " + String.join(", ", postData.keywords) + "]"; + String engagementStr = postData.readingEngagement != null + ? " (" + postData.readingEngagement + ")" + : ""; + return "- " + postData.title + keywordsStr + engagementStr; + }) + .collect(Collectors.joining("\n")); + } + + private float[] generateEmbeddingVector(String profileText) { + List embedding = embeddingClient.embed(profileText); + + float[] vector = new float[embedding.size()]; + for (int i = 0; i < embedding.size(); i++) { + vector[i] = embedding.get(i); + } + return vector; + } + + private String convertReadingDurationToNaturalLanguage(Integer durationSeconds) { + if (durationSeconds == null) { + return "์ฝ์Œ"; + } + + if (durationSeconds <= 30) { + return "๊ฐ€๋ณ๊ฒŒ ํ›‘์–ด๋ด„"; + } else if (durationSeconds <= 90) { + return "๋น ๋ฅด๊ฒŒ ์ฝ์Œ"; + } else if (durationSeconds <= 300) { + return "์ฝ์Œ"; + } else if (durationSeconds <= 600) { + return "์ •๋…ํ•จ"; + } else { + return "๊นŠ๊ฒŒ ์ฝ์Œ"; + } + } + + private ProfileAndKeywords parseProfileAndKeywords(String llmResponse) { + String profileText = ""; + List keyKeywords = List.of(); + + try { + // PROFILE ์„น์…˜ ์ถ”์ถœ + int profileStart = llmResponse.indexOf("### PROFILE"); + int keywordsStart = llmResponse.indexOf("### KEYWORDS"); + + if (profileStart != -1 && keywordsStart != -1) { + profileText = llmResponse.substring(profileStart + "### PROFILE".length(), keywordsStart) + .trim(); + + String keywordsSection = llmResponse.substring(keywordsStart + "### KEYWORDS".length()) + .trim(); + + // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ํ‚ค์›Œ๋“œ ํŒŒ์‹ฑ + keyKeywords = Arrays.stream(keywordsSection.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .limit(5) // ์ตœ๋Œ€ 5๊ฐœ + .toList(); + } else { + // ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ํ”„๋กœํ•„๋กœ ์‚ฌ์šฉ + log.warn("Failed to parse LLM response sections, using full text as profile"); + profileText = llmResponse; + } + } catch (Exception e) { + log.error("Error parsing LLM response", e); + profileText = llmResponse; + } + + return new ProfileAndKeywords(profileText, keyKeywords); + } + + private record ProfileAndKeywords(String profileText, List keyKeywords) {} + private record UserActivityData( List interests, List readPostData, List bookmarkedPostData, List searchQueries ) {} - - private record PostData( - String title, - List keywords, - String readingEngagement - ) {} -} + + private record PostData( + String title, + List keywords, + String readingEngagement + ) {} +} diff --git a/src/main/java/com/techfork/domain/recommendation/entity/RecommendationHistory.java b/src/main/java/com/techfork/domain/recommendation/entity/RecommendationHistory.java index 7b2e1206..6cff4381 100644 --- a/src/main/java/com/techfork/domain/recommendation/entity/RecommendationHistory.java +++ b/src/main/java/com/techfork/domain/recommendation/entity/RecommendationHistory.java @@ -1,92 +1,92 @@ -package com.techfork.domain.recommendation.entity; - -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.time.LocalDateTime; - -/** - * ์ถ”์ฒœ ์ด๋ ฅ ์—”ํ‹ฐํ‹ฐ - * ๊ณผ๊ฑฐ ์ถ”์ฒœ ๊ธฐ๋ก์„ ๋ณด๊ด€ํ•˜์—ฌ ์ถ”์ฒœ ํ’ˆ์งˆ ๋ถ„์„ ๋ฐ ๊ฐœ์„ ์— ํ™œ์šฉ - */ -@Entity -@Table( - name = "recommendation_history", - indexes = { - @Index(name = "idx_user_recommended_at", columnList = "user_id, recommended_at DESC"), - @Index(name = "idx_recommended_at", columnList = "recommended_at DESC") - } -) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class RecommendationHistory extends BaseEntity { - - @Column(nullable = false) - private Double similarityScore; - - @Column(nullable = false) - private Double mmrScore; - - @Column(nullable = false) - private Integer rankOrder; - - @Column(nullable = false) - private LocalDateTime recommendedAt; - - @Column(nullable = false) - private Boolean isClicked = false; - - private LocalDateTime clickedAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id", nullable = false) - private Post post; - - @PersistenceCreator - @Builder - RecommendationHistory(User user, Post post, Double similarityScore, - Double mmrScore, Integer rankOrder, LocalDateTime recommendedAt, - Boolean isClicked, LocalDateTime clickedAt) { - this.user = user; - this.post = post; - this.similarityScore = similarityScore; - this.mmrScore = mmrScore; - this.rankOrder = rankOrder; - this.recommendedAt = recommendedAt; - this.isClicked = isClicked != null ? isClicked : false; - this.clickedAt = clickedAt; - } - - /** - * RecommendedPost๋กœ๋ถ€ํ„ฐ ์ด๋ ฅ ์ƒ์„ฑ - */ - public static RecommendationHistory fromRecommendedPost(RecommendedPost recommendedPost) { - return RecommendationHistory.builder() - .user(recommendedPost.getUser()) - .post(recommendedPost.getPost()) - .similarityScore(recommendedPost.getSimilarityScore()) - .mmrScore(recommendedPost.getMmrScore()) - .rankOrder(recommendedPost.getRankOrder()) - .recommendedAt(recommendedPost.getRecommendedAt()) - .build(); - } - - /** - * ํด๋ฆญ ๊ธฐ๋ก - */ - public void markAsisClicked() { - this.isClicked = true; - this.clickedAt = LocalDateTime.now(); - } -} +package com.techfork.domain.recommendation.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.time.LocalDateTime; + +/** + * ์ถ”์ฒœ ์ด๋ ฅ ์—”ํ‹ฐํ‹ฐ + * ๊ณผ๊ฑฐ ์ถ”์ฒœ ๊ธฐ๋ก์„ ๋ณด๊ด€ํ•˜์—ฌ ์ถ”์ฒœ ํ’ˆ์งˆ ๋ถ„์„ ๋ฐ ๊ฐœ์„ ์— ํ™œ์šฉ + */ +@Entity +@Table( + name = "recommendation_history", + indexes = { + @Index(name = "idx_user_recommended_at", columnList = "user_id, recommended_at DESC"), + @Index(name = "idx_recommended_at", columnList = "recommended_at DESC") + } +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RecommendationHistory extends BaseEntity { + + @Column(nullable = false) + private Double similarityScore; + + @Column(nullable = false) + private Double mmrScore; + + @Column(nullable = false) + private Integer rankOrder; + + @Column(nullable = false) + private LocalDateTime recommendedAt; + + @Column(nullable = false) + private Boolean isClicked = false; + + private LocalDateTime clickedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @PersistenceCreator + @Builder + RecommendationHistory(User user, Post post, Double similarityScore, + Double mmrScore, Integer rankOrder, LocalDateTime recommendedAt, + Boolean isClicked, LocalDateTime clickedAt) { + this.user = user; + this.post = post; + this.similarityScore = similarityScore; + this.mmrScore = mmrScore; + this.rankOrder = rankOrder; + this.recommendedAt = recommendedAt; + this.isClicked = isClicked != null ? isClicked : false; + this.clickedAt = clickedAt; + } + + /** + * RecommendedPost๋กœ๋ถ€ํ„ฐ ์ด๋ ฅ ์ƒ์„ฑ + */ + public static RecommendationHistory fromRecommendedPost(RecommendedPost recommendedPost) { + return RecommendationHistory.builder() + .user(recommendedPost.getUser()) + .post(recommendedPost.getPost()) + .similarityScore(recommendedPost.getSimilarityScore()) + .mmrScore(recommendedPost.getMmrScore()) + .rankOrder(recommendedPost.getRankOrder()) + .recommendedAt(recommendedPost.getRecommendedAt()) + .build(); + } + + /** + * ํด๋ฆญ ๊ธฐ๋ก + */ + public void markAsisClicked() { + this.isClicked = true; + this.clickedAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java b/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java index 8af52b54..039a12a1 100644 --- a/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java +++ b/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java @@ -1,72 +1,72 @@ -package com.techfork.domain.recommendation.entity; - -import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.time.LocalDateTime; - -@Entity -@Table( - name = "recommended_posts", - uniqueConstraints = { - @UniqueConstraint(columnNames = {"user_id", "post_id"}) - }, - indexes = { - @Index(name = "idx_user_recommended_at", columnList = "user_id, recommended_at DESC") - } -) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class RecommendedPost extends BaseEntity { - - @Column(nullable = false) - private Double similarityScore; - - @Column(nullable = false) - private Double mmrScore; - - @Column(nullable = false) - private Integer rankOrder; - - @Column(nullable = false) - private LocalDateTime recommendedAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id", nullable = false) - private Post post; - - @PersistenceCreator - @Builder - RecommendedPost(User user, Post post, Double similarityScore, - Double mmrScore, Integer rankOrder, LocalDateTime recommendedAt) { - this.user = user; - this.post = post; - this.similarityScore = similarityScore; - this.mmrScore = mmrScore; - this.rankOrder = rankOrder; - this.recommendedAt = recommendedAt; - } - - public static RecommendedPost create(User user, Post post, Double similarityScore, - Double mmrScore, Integer rankOrder) { - return RecommendedPost.builder() - .user(user) - .post(post) - .similarityScore(similarityScore) - .mmrScore(mmrScore) - .rankOrder(rankOrder) - .recommendedAt(LocalDateTime.now()) - .build(); - } -} +package com.techfork.domain.recommendation.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.time.LocalDateTime; + +@Entity +@Table( + name = "recommended_posts", + uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "post_id"}) + }, + indexes = { + @Index(name = "idx_user_recommended_at", columnList = "user_id, recommended_at DESC") + } +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RecommendedPost extends BaseEntity { + + @Column(nullable = false) + private Double similarityScore; + + @Column(nullable = false) + private Double mmrScore; + + @Column(nullable = false) + private Integer rankOrder; + + @Column(nullable = false) + private LocalDateTime recommendedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @PersistenceCreator + @Builder + RecommendedPost(User user, Post post, Double similarityScore, + Double mmrScore, Integer rankOrder, LocalDateTime recommendedAt) { + this.user = user; + this.post = post; + this.similarityScore = similarityScore; + this.mmrScore = mmrScore; + this.rankOrder = rankOrder; + this.recommendedAt = recommendedAt; + } + + public static RecommendedPost create(User user, Post post, Double similarityScore, + Double mmrScore, Integer rankOrder) { + return RecommendedPost.builder() + .user(user) + .post(post) + .similarityScore(similarityScore) + .mmrScore(mmrScore) + .rankOrder(rankOrder) + .recommendedAt(LocalDateTime.now()) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/recommendation/repository/RecommendedPostRepository.java b/src/main/java/com/techfork/domain/recommendation/repository/RecommendedPostRepository.java index b1332413..92ca652f 100644 --- a/src/main/java/com/techfork/domain/recommendation/repository/RecommendedPostRepository.java +++ b/src/main/java/com/techfork/domain/recommendation/repository/RecommendedPostRepository.java @@ -1,26 +1,26 @@ -package com.techfork.domain.recommendation.repository; - -import com.techfork.domain.recommendation.entity.RecommendedPost; -import com.techfork.domain.user.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; - -public interface RecommendedPostRepository extends JpaRepository { - - @Query(""" - SELECT rp FROM RecommendedPost rp - JOIN FETCH rp.post p - JOIN FETCH p.techBlog - WHERE rp.user = :user - ORDER BY rp.rankOrder ASC - """) - List findByUserOrderByRankAsc(@Param("user") User user); - - @Modifying - @Query("DELETE FROM RecommendedPost rp WHERE rp.user = :user") - void deleteByUser(@Param("user") User user); -} +package com.techfork.domain.recommendation.repository; + +import com.techfork.domain.recommendation.entity.RecommendedPost; +import com.techfork.domain.useraccount.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface RecommendedPostRepository extends JpaRepository { + + @Query(""" + SELECT rp FROM RecommendedPost rp + JOIN FETCH rp.post p + JOIN FETCH p.techBlog + WHERE rp.user = :user + ORDER BY rp.rankOrder ASC + """) + List findByUserOrderByRankAsc(@Param("user") User user); + + @Modifying + @Query("DELETE FROM RecommendedPost rp WHERE rp.user = :user") + void deleteByUser(@Param("user") User user); +} diff --git a/src/main/java/com/techfork/domain/recommendation/scheduler/RecommendationScheduler.java b/src/main/java/com/techfork/domain/recommendation/scheduler/RecommendationScheduler.java index 301d1108..738c9575 100644 --- a/src/main/java/com/techfork/domain/recommendation/scheduler/RecommendationScheduler.java +++ b/src/main/java/com/techfork/domain/recommendation/scheduler/RecommendationScheduler.java @@ -1,61 +1,61 @@ -package com.techfork.domain.recommendation.scheduler; - -import com.techfork.domain.recommendation.config.RecommendationProperties; -import com.techfork.domain.recommendation.service.RecommendationService; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.util.List; - -@Slf4j -@Component -@RequiredArgsConstructor -public class RecommendationScheduler { - - private final UserRepository userRepository; - private final RecommendationService recommendationService; - private final RecommendationProperties properties; - - /** - * ๋งค์ผ ์˜ค์ „ 7์‹œ(KST)์— ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๋Œ€์ƒ ์ถ”์ฒœ ์ƒ์„ฑ - * - ํฌ๋กค๋ง(5์‹œ) โ†’ ํ”„๋กœํ•„ ์ƒ์„ฑ(6์‹œ) โ†’ ์ถ”์ฒœ ์ƒ์„ฑ(7์‹œ) ์ˆœ์„œ - * - ์ตœ๊ทผ N์‹œ๊ฐ„ ์ด๋‚ด ํ™œ์„ฑ ์‚ฌ์šฉ์ž๋งŒ ๋Œ€์ƒ - * - ํ–ฅํ›„ ์ถ”์ฒœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์˜ˆ์ • - */ - @Scheduled(cron = "0 0 7 * * *", zone = "Asia/Seoul") - public void generateDailyRecommendations() { - log.info("ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๋Œ€์ƒ์œผ๋กœ ๊ฒŒ์‹œ๊ธ€ ์ถ”์ฒœ ์‹œ์ž‘"); - - LocalDateTime since = LocalDateTime.now().minusHours(properties.getActiveUserHours()); - List activeUsers = userRepository.findActiveUsersSince(since); - - log.info("{} ๋ช…์˜ ํ™œ์„ฑ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ({} ์‹œ๊ฐ„ ์ด๋‚ด)", activeUsers.size(), properties.getActiveUserHours()); - - int totalRecommendations = 0; - int successCount = 0; - int failCount = 0; - - for (User user : activeUsers) { - try { - int count = recommendationService.generateRecommendationsForUser(user); - totalRecommendations += count; - successCount++; - - if (count > 0) { - log.debug("์‚ฌ์šฉ์ž {}์—๊ฒŒ {} ๊ฐœ ์ถ”์ฒœ ์ƒ์„ฑ ์™„๋ฃŒ", user.getId(), count); - } - } catch (Exception e) { - failCount++; - log.error("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ์ƒ์„ฑ ์‹คํŒจ", user.getId(), e); - } - } - - log.info("์ผ์ผ ์ถ”์ฒœ ์ƒ์„ฑ ์™„๋ฃŒ: ์ „์ฒด ์‚ฌ์šฉ์ž={}, ์„ฑ๊ณต={}, ์‹คํŒจ={}, ์ด ์ถ”์ฒœ ๊ฐœ์ˆ˜={}", - activeUsers.size(), successCount, failCount, totalRecommendations); - } -} +package com.techfork.domain.recommendation.scheduler; + +import com.techfork.domain.recommendation.config.RecommendationProperties; +import com.techfork.domain.recommendation.service.RecommendationService; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RecommendationScheduler { + + private final UserRepository userRepository; + private final RecommendationService recommendationService; + private final RecommendationProperties properties; + + /** + * ๋งค์ผ ์˜ค์ „ 7์‹œ(KST)์— ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๋Œ€์ƒ ์ถ”์ฒœ ์ƒ์„ฑ + * - ํฌ๋กค๋ง(5์‹œ) โ†’ ํ”„๋กœํ•„ ์ƒ์„ฑ(6์‹œ) โ†’ ์ถ”์ฒœ ์ƒ์„ฑ(7์‹œ) ์ˆœ์„œ + * - ์ตœ๊ทผ N์‹œ๊ฐ„ ์ด๋‚ด ํ™œ์„ฑ ์‚ฌ์šฉ์ž๋งŒ ๋Œ€์ƒ + * - ํ–ฅํ›„ ์ถ”์ฒœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์˜ˆ์ • + */ + @Scheduled(cron = "0 0 7 * * *", zone = "Asia/Seoul") + public void generateDailyRecommendations() { + log.info("ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๋Œ€์ƒ์œผ๋กœ ๊ฒŒ์‹œ๊ธ€ ์ถ”์ฒœ ์‹œ์ž‘"); + + LocalDateTime since = LocalDateTime.now().minusHours(properties.getActiveUserHours()); + List activeUsers = userRepository.findActiveUsersSince(since); + + log.info("{} ๋ช…์˜ ํ™œ์„ฑ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ({} ์‹œ๊ฐ„ ์ด๋‚ด)", activeUsers.size(), properties.getActiveUserHours()); + + int totalRecommendations = 0; + int successCount = 0; + int failCount = 0; + + for (User user : activeUsers) { + try { + int count = recommendationService.generateRecommendationsForUser(user); + totalRecommendations += count; + successCount++; + + if (count > 0) { + log.debug("์‚ฌ์šฉ์ž {}์—๊ฒŒ {} ๊ฐœ ์ถ”์ฒœ ์ƒ์„ฑ ์™„๋ฃŒ", user.getId(), count); + } + } catch (Exception e) { + failCount++; + log.error("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ์ƒ์„ฑ ์‹คํŒจ", user.getId(), e); + } + } + + log.info("์ผ์ผ ์ถ”์ฒœ ์ƒ์„ฑ ์™„๋ฃŒ: ์ „์ฒด ์‚ฌ์šฉ์ž={}, ์„ฑ๊ณต={}, ์‹คํŒจ={}, ์ด ์ถ”์ฒœ ๊ฐœ์ˆ˜={}", + activeUsers.size(), successCount, failCount, totalRecommendations); + } +} diff --git a/src/main/java/com/techfork/domain/recommendation/service/LlmRecommendationService.java b/src/main/java/com/techfork/domain/recommendation/service/LlmRecommendationService.java index 278f412b..b7b3b8ea 100644 --- a/src/main/java/com/techfork/domain/recommendation/service/LlmRecommendationService.java +++ b/src/main/java/com/techfork/domain/recommendation/service/LlmRecommendationService.java @@ -17,9 +17,9 @@ import com.techfork.domain.recommendation.repository.RecommendationHistoryRepository; import com.techfork.domain.recommendation.service.MmrService.MmrCandidate; import com.techfork.domain.recommendation.service.MmrService.MmrResult; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; import com.techfork.global.util.RrfScorer; import com.techfork.global.util.TimeDecayStrategy; import com.techfork.global.util.VectorUtil; @@ -48,7 +48,7 @@ public class LlmRecommendationService implements RecommendationService { private final ElasticsearchClient elasticsearchClient; - private final UserProfileDocumentRepository userProfileDocumentRepository; + private final PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; private final RecommendedPostRepository recommendedPostRepository; private final RecommendationHistoryRepository recommendationHistoryRepository; private final ReadPostRepository readPostRepository; @@ -69,18 +69,19 @@ public class LlmRecommendationService implements RecommendationService { public int generateRecommendationsForUser(User user) { log.info("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ์ƒ์„ฑ ์‹œ์ž‘", user.getId()); - Optional profileOpt = userProfileDocumentRepository.findByUserId(user.getId()); - if (profileOpt.isEmpty() || profileOpt.get().getProfileVector() == null) { - log.warn("์‚ฌ์šฉ์ž {}์˜ ํ”„๋กœํ•„ ๋˜๋Š” ๋ฒกํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ. ์ถ”์ฒœ ์ƒ์„ฑ ์Šคํ‚ต.", user.getId()); + Optional personalizationProfileOpt = + personalizationProfileDocumentRepository.findByUserId(user.getId()); + if (personalizationProfileOpt.isEmpty() || personalizationProfileOpt.get().getProfileVector() == null) { + log.warn("์‚ฌ์šฉ์ž {}์˜ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ๋˜๋Š” ๋ฒกํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ. ์ถ”์ฒœ ์ƒ์„ฑ ์Šคํ‚ต.", user.getId()); return 0; } - UserProfileDocument profile = profileOpt.get(); - float[] userProfileVector = profile.getProfileVector(); + PersonalizationProfileDocument personalizationProfile = personalizationProfileOpt.get(); + float[] personalizationProfileVector = personalizationProfile.getProfileVector(); try { // 2. k-NN ๊ฒ€์ƒ‰์œผ๋กœ ์ดˆ๊ธฐ ํ›„๋ณด๊ตฐ ๊ฐ€์ ธ์˜ค๊ธฐ - List candidates = searchCandidates(userProfileVector, user); + List candidates = searchCandidates(personalizationProfileVector, user); if (candidates.isEmpty()) { log.info("์‚ฌ์šฉ์ž {}์˜ ์ถ”์ฒœ ํ›„๋ณด๊ตฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ", user.getId()); @@ -118,14 +119,17 @@ public int generateRecommendationsForUser(User user) { } } - private List searchCandidates(float[] userProfileVector, User user) throws IOException { + private List searchCandidates(float[] personalizationProfileVector, User user) throws IOException { Set readPostIds = readPostRepository.findRecentReadPostsByUserIdWithMinDuration(user.getId(), PageRequest.of(0, 1000)) .stream() .map(readPost -> readPost.getPost().getId()) .collect(Collectors.toSet()); - Optional profileOpt = userProfileDocumentRepository.findByUserId(user.getId()); - List keyKeywords = profileOpt.map(UserProfileDocument::getKeyKeywords).orElse(List.of()); + Optional personalizationProfileOpt = + personalizationProfileDocumentRepository.findByUserId(user.getId()); + List keyKeywords = personalizationProfileOpt + .map(PersonalizationProfileDocument::getKeyKeywords) + .orElse(List.of()); RecommendationProperties.EmbeddingWeights weights = properties.getEmbeddingWeights(); Query filterQuery = vectorQueryBuilder.createExcludeFilter(readPostIds); @@ -133,7 +137,7 @@ private List searchCandidates(float[] userProfileVector, User user // 1. kNN ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ค€๋น„ List knnSearches = vectorQueryBuilder.createKnnSearches( TITLE_EMBEDDING_FIELD, SUMMARY_EMBEDDING_FIELD, CONTENT_CHUNKS_EMBEDDING_FIELD, - userProfileVector, weights.getTitle(), weights.getSummary(), weights.getContent(), + personalizationProfileVector, weights.getTitle(), weights.getSummary(), weights.getContent(), properties.getKnnSearchSize(), properties.getNumCandidates(), filterQuery ); diff --git a/src/main/java/com/techfork/domain/recommendation/service/RecommendationCommandService.java b/src/main/java/com/techfork/domain/recommendation/service/RecommendationCommandService.java index 9a34b967..77652430 100644 --- a/src/main/java/com/techfork/domain/recommendation/service/RecommendationCommandService.java +++ b/src/main/java/com/techfork/domain/recommendation/service/RecommendationCommandService.java @@ -1,24 +1,24 @@ -package com.techfork.domain.recommendation.service; - -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@Transactional -@RequiredArgsConstructor -public class RecommendationCommandService { - - private final RecommendationService recommendationService; - private final UserRepository userRepository; - - public void regenerateRecommendations(Long userId) { - User user = userRepository.getReferenceById(userId); - int generatedCount = recommendationService.generateRecommendationsForUser(user); - log.info("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ์ฆ‰์‹œ ์žฌ์ƒ์„ฑ ์™„๋ฃŒ: {} ๊ฐœ", userId, generatedCount); - } -} +package com.techfork.domain.recommendation.service; + +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class RecommendationCommandService { + + private final RecommendationService recommendationService; + private final UserRepository userRepository; + + public void regenerateRecommendations(Long userId) { + User user = userRepository.getReferenceById(userId); + int generatedCount = recommendationService.generateRecommendationsForUser(user); + log.info("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ์ฆ‰์‹œ ์žฌ์ƒ์„ฑ ์™„๋ฃŒ: {} ๊ฐœ", userId, generatedCount); + } +} diff --git a/src/main/java/com/techfork/domain/recommendation/service/RecommendationQueryService.java b/src/main/java/com/techfork/domain/recommendation/service/RecommendationQueryService.java index e583a1c4..1e36db57 100644 --- a/src/main/java/com/techfork/domain/recommendation/service/RecommendationQueryService.java +++ b/src/main/java/com/techfork/domain/recommendation/service/RecommendationQueryService.java @@ -1,59 +1,59 @@ -package com.techfork.domain.recommendation.service; - -import com.techfork.domain.activity.repository.BookmarkRepository; -import com.techfork.domain.recommendation.converter.RecommendationConverter; -import com.techfork.domain.recommendation.dto.RecommendationListResponse; -import com.techfork.domain.recommendation.dto.RecommendedPostDto; -import com.techfork.domain.recommendation.entity.RecommendedPost; -import com.techfork.domain.recommendation.repository.RecommendedPostRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class RecommendationQueryService { - - private final RecommendedPostRepository recommendedPostRepository; - private final UserRepository userRepository; - private final RecommendationConverter recommendationConverter; - private final BookmarkRepository bookmarkRepository; - - public RecommendationListResponse getRecommendations(Long userId) { - User user = userRepository.getReferenceById(userId); - List recommendedPosts = recommendedPostRepository.findByUserOrderByRankAsc(user); - log.info("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ๋ชฉ๋ก ์กฐํšŒ: {} ๊ฐœ", userId, recommendedPosts.size()); - - RecommendationListResponse response = recommendationConverter.toRecommendationListResponse(recommendedPosts); - response = attachBookmarkStatus(response, userId); - - return response; - } - - private RecommendationListResponse attachBookmarkStatus(RecommendationListResponse response, Long userId) { - if (response.recommendations().isEmpty()) { - return response; - } - - List postIds = response.recommendations().stream() - .map(RecommendedPostDto::postId) - .toList(); - List bookmarkedPostIds = bookmarkRepository.findBookmarkedPostIds(userId, postIds); - - List updatedRecommendations = response.recommendations().stream() - .map(dto -> dto.withBookmarkStatus(bookmarkedPostIds.contains(dto.postId()))) - .toList(); - - return RecommendationListResponse.builder() - .recommendations(updatedRecommendations) - .totalCount(response.totalCount()) - .build(); - } -} +package com.techfork.domain.recommendation.service; + +import com.techfork.domain.activity.repository.BookmarkRepository; +import com.techfork.domain.recommendation.converter.RecommendationConverter; +import com.techfork.domain.recommendation.dto.RecommendationListResponse; +import com.techfork.domain.recommendation.dto.RecommendedPostDto; +import com.techfork.domain.recommendation.entity.RecommendedPost; +import com.techfork.domain.recommendation.repository.RecommendedPostRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class RecommendationQueryService { + + private final RecommendedPostRepository recommendedPostRepository; + private final UserRepository userRepository; + private final RecommendationConverter recommendationConverter; + private final BookmarkRepository bookmarkRepository; + + public RecommendationListResponse getRecommendations(Long userId) { + User user = userRepository.getReferenceById(userId); + List recommendedPosts = recommendedPostRepository.findByUserOrderByRankAsc(user); + log.info("์‚ฌ์šฉ์ž {} ์ถ”์ฒœ ๋ชฉ๋ก ์กฐํšŒ: {} ๊ฐœ", userId, recommendedPosts.size()); + + RecommendationListResponse response = recommendationConverter.toRecommendationListResponse(recommendedPosts); + response = attachBookmarkStatus(response, userId); + + return response; + } + + private RecommendationListResponse attachBookmarkStatus(RecommendationListResponse response, Long userId) { + if (response.recommendations().isEmpty()) { + return response; + } + + List postIds = response.recommendations().stream() + .map(RecommendedPostDto::postId) + .toList(); + List bookmarkedPostIds = bookmarkRepository.findBookmarkedPostIds(userId, postIds); + + List updatedRecommendations = response.recommendations().stream() + .map(dto -> dto.withBookmarkStatus(bookmarkedPostIds.contains(dto.postId()))) + .toList(); + + return RecommendationListResponse.builder() + .recommendations(updatedRecommendations) + .totalCount(response.totalCount()) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/recommendation/service/RecommendationService.java b/src/main/java/com/techfork/domain/recommendation/service/RecommendationService.java index a6b2cfac..ebb5fb40 100644 --- a/src/main/java/com/techfork/domain/recommendation/service/RecommendationService.java +++ b/src/main/java/com/techfork/domain/recommendation/service/RecommendationService.java @@ -1,18 +1,18 @@ -package com.techfork.domain.recommendation.service; - -import com.techfork.domain.user.entity.User; - -/** - * ์ถ”์ฒœ ์ „๋žต ์ธํ„ฐํŽ˜์ด์Šค - * ๋‹ค์–‘ํ•œ ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”์ƒํ™” - */ -public interface RecommendationService { - - /** - * ์‚ฌ์šฉ์ž๋ณ„ ๊ฐœ์ธํ™” ์ถ”์ฒœ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ - * - * @param user ์ถ”์ฒœ ๋Œ€์ƒ ์‚ฌ์šฉ์ž - * @return ์ƒ์„ฑ๋œ ์ถ”์ฒœ ๊ฐœ์ˆ˜ - */ - int generateRecommendationsForUser(User user); -} +package com.techfork.domain.recommendation.service; + +import com.techfork.domain.useraccount.entity.User; + +/** + * ์ถ”์ฒœ ์ „๋žต ์ธํ„ฐํŽ˜์ด์Šค + * ๋‹ค์–‘ํ•œ ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”์ƒํ™” + */ +public interface RecommendationService { + + /** + * ์‚ฌ์šฉ์ž๋ณ„ ๊ฐœ์ธํ™” ์ถ”์ฒœ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ + * + * @param user ์ถ”์ฒœ ๋Œ€์ƒ ์‚ฌ์šฉ์ž + * @return ์ƒ์„ฑ๋œ ์ถ”์ฒœ ๊ฐœ์ˆ˜ + */ + int generateRecommendationsForUser(User user); +} diff --git a/src/main/java/com/techfork/domain/search/service/SearchServiceImpl.java b/src/main/java/com/techfork/domain/search/service/SearchServiceImpl.java index e471b3b8..5f528519 100644 --- a/src/main/java/com/techfork/domain/search/service/SearchServiceImpl.java +++ b/src/main/java/com/techfork/domain/search/service/SearchServiceImpl.java @@ -13,8 +13,8 @@ import com.techfork.domain.search.config.GeneralSearchProperties; import com.techfork.domain.search.dto.SearchResult; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; import com.techfork.global.llm.EmbeddingClient; import com.techfork.global.util.CloudflareThirdPartyThumbnailOptimizer; import com.techfork.global.util.RrfScorer; @@ -52,7 +52,7 @@ public class SearchServiceImpl implements SearchService { private final ElasticsearchClient elasticsearchClient; private final EmbeddingClient embeddingClient; private final GeneralSearchProperties generalSearchProperties; - private final UserProfileDocumentRepository userProfileDocumentRepository; + private final PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; private final PostRepository postRepository; private final BookmarkRepository bookmarkRepository; private final Executor searchAsyncExecutor; @@ -90,8 +90,10 @@ public List searchPersonalized(String query, Long userId) { log.debug("Personalized search started for userId: {} with query: '{}'", userId, query); long startTime = System.currentTimeMillis(); - Optional userProfileOpt = userProfileDocumentRepository.findByUserId(userId); - boolean hasProfile = userProfileOpt.isPresent() && userProfileOpt.get().getProfileVector() != null; + Optional personalizationProfileOpt = + personalizationProfileDocumentRepository.findByUserId(userId); + boolean hasProfile = personalizationProfileOpt.isPresent() + && personalizationProfileOpt.get().getProfileVector() != null; int candidateSize = hasProfile ? generalSearchProperties.getRRF_WINDOW_SIZE() @@ -107,8 +109,8 @@ public List searchPersonalized(String query, Long userId) { log.info("Personalized Search [FALLBACK]. UserID={}, Query='{}', Results={}, Time={}ms (Reason: No Profile)", userId, query, finalResults.size(), duration); } else { - float[] userProfileVector = userProfileOpt.get().getProfileVector(); - finalResults = personalReranking(initialResults, userProfileVector); + float[] personalizationProfileVector = personalizationProfileOpt.get().getProfileVector(); + finalResults = personalReranking(initialResults, personalizationProfileVector); long duration = System.currentTimeMillis() - startTime; log.info("Personalized Search [RERANKED]. UserID={}, Query='{}', Results={}, Time={}ms", userId, query, finalResults.size(), duration); diff --git a/src/main/java/com/techfork/domain/user/repository/UserProfileDocumentRepository.java b/src/main/java/com/techfork/domain/user/repository/UserProfileDocumentRepository.java deleted file mode 100644 index 69e54fc5..00000000 --- a/src/main/java/com/techfork/domain/user/repository/UserProfileDocumentRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.techfork.domain.user.repository; - -import com.techfork.domain.user.document.UserProfileDocument; -import java.util.Optional; -import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; - -public interface UserProfileDocumentRepository extends ElasticsearchRepository { - Optional findByUserId(Long id); -} \ No newline at end of file diff --git a/src/main/java/com/techfork/domain/user/controller/OnboardingController.java b/src/main/java/com/techfork/domain/useraccount/controller/OnboardingController.java similarity index 85% rename from src/main/java/com/techfork/domain/user/controller/OnboardingController.java rename to src/main/java/com/techfork/domain/useraccount/controller/OnboardingController.java index 38bcd2e7..a201d547 100644 --- a/src/main/java/com/techfork/domain/user/controller/OnboardingController.java +++ b/src/main/java/com/techfork/domain/useraccount/controller/OnboardingController.java @@ -1,51 +1,51 @@ -package com.techfork.domain.user.controller; - -import com.techfork.domain.user.dto.InterestListResponse; -import com.techfork.domain.user.dto.OnboardingRequest; -import com.techfork.domain.user.service.InterestQueryService; -import com.techfork.domain.user.service.UserCommandService; -import com.techfork.global.common.code.SuccessCode; -import com.techfork.global.response.BaseResponse; -import com.techfork.global.security.oauth.UserPrincipal; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "Onboarding", description = "์˜จ๋ณด๋”ฉ API") -@Slf4j -@RestController -@RequestMapping("/api/v1/onboarding") -@RequiredArgsConstructor -public class OnboardingController { - - private final InterestQueryService interestQueryService; - private final UserCommandService userCommandService; - - @Operation( - summary = "๊ด€์‹ฌ์‚ฌ ๋ชฉ๋ก ์กฐํšŒ", - description = "์˜จ๋ณด๋”ฉ ์‹œ ์„ ํƒ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ด€์‹ฌ์‚ฌ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ํ‚ค์›Œ๋“œ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค." - ) - @GetMapping("/interests") - public ResponseEntity> getInterests() { - InterestListResponse response = interestQueryService.getAllInterests(); - return BaseResponse.of(SuccessCode.OK, response); - } - - @Operation( - summary = "๋‚ด ์ •๋ณด ๋ฐ ๊ด€์‹ฌ์‚ฌ ์ €์žฅ", - description = "์˜จ๋ณด๋”ฉ ์‹œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด์™€ ์„ ํƒํ•œ ๊ด€์‹ฌ์‚ฌ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ์„ธ๋ถ€ ํ‚ค์›Œ๋“œ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ) - @PostMapping("/complete") - public ResponseEntity> completeOnboarding( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @Valid @RequestBody OnboardingRequest request - ) { - userCommandService.completeOnboarding(userPrincipal.getId(), request); - return BaseResponse.of(SuccessCode.CREATED); - } -} +package com.techfork.domain.useraccount.controller; + +import com.techfork.domain.useraccount.dto.InterestListResponse; +import com.techfork.domain.useraccount.dto.OnboardingRequest; +import com.techfork.domain.useraccount.service.InterestQueryService; +import com.techfork.domain.useraccount.service.UserCommandService; +import com.techfork.global.common.code.SuccessCode; +import com.techfork.global.response.BaseResponse; +import com.techfork.global.security.oauth.UserPrincipal; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Onboarding", description = "์˜จ๋ณด๋”ฉ API") +@Slf4j +@RestController +@RequestMapping("/api/v1/onboarding") +@RequiredArgsConstructor +public class OnboardingController { + + private final InterestQueryService interestQueryService; + private final UserCommandService userCommandService; + + @Operation( + summary = "๊ด€์‹ฌ์‚ฌ ๋ชฉ๋ก ์กฐํšŒ", + description = "์˜จ๋ณด๋”ฉ ์‹œ ์„ ํƒ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ด€์‹ฌ์‚ฌ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ํ‚ค์›Œ๋“œ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค." + ) + @GetMapping("/interests") + public ResponseEntity> getInterests() { + InterestListResponse response = interestQueryService.getAllInterests(); + return BaseResponse.of(SuccessCode.OK, response); + } + + @Operation( + summary = "๋‚ด ์ •๋ณด ๋ฐ ๊ด€์‹ฌ์‚ฌ ์ €์žฅ", + description = "์˜จ๋ณด๋”ฉ ์‹œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด์™€ ์„ ํƒํ•œ ๊ด€์‹ฌ์‚ฌ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ์„ธ๋ถ€ ํ‚ค์›Œ๋“œ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + ) + @PostMapping("/complete") + public ResponseEntity> completeOnboarding( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @Valid @RequestBody OnboardingRequest request + ) { + userCommandService.completeOnboarding(userPrincipal.getId(), request); + return BaseResponse.of(SuccessCode.CREATED); + } +} diff --git a/src/main/java/com/techfork/domain/user/controller/UserController.java b/src/main/java/com/techfork/domain/useraccount/controller/UserController.java similarity index 62% rename from src/main/java/com/techfork/domain/user/controller/UserController.java rename to src/main/java/com/techfork/domain/useraccount/controller/UserController.java index 530c194d..83e27649 100644 --- a/src/main/java/com/techfork/domain/user/controller/UserController.java +++ b/src/main/java/com/techfork/domain/useraccount/controller/UserController.java @@ -1,96 +1,96 @@ -package com.techfork.domain.user.controller; - -import com.techfork.domain.user.dto.SaveInterestRequest; -import com.techfork.domain.user.dto.UpdateUserProfileRequest; -import com.techfork.domain.user.dto.UserInterestResponse; -import com.techfork.domain.user.dto.UserProfileResponse; -import com.techfork.domain.user.service.InterestCommandService; -import com.techfork.domain.user.service.InterestQueryService; -import com.techfork.domain.user.service.UserCommandService; -import com.techfork.domain.user.service.UserQueryService; -import com.techfork.global.common.code.SuccessCode; -import com.techfork.global.response.BaseResponse; -import com.techfork.global.security.oauth.UserPrincipal; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "User", description = "์‚ฌ์šฉ์ž API") -@Slf4j -@RestController -@RequestMapping("/api/v1/users") -@RequiredArgsConstructor -public class UserController { - - private final InterestCommandService interestCommandService; - private final InterestQueryService interestQueryService; - private final UserCommandService userCommandService; - private final UserQueryService userQueryService; - - @Operation( - summary = "๋‚ด ๊ด€์‹ฌ์‚ฌ ์ˆ˜์ •", - description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ๊ด€์‹ฌ์‚ฌ๋Š” ๋ชจ๋‘ ์‚ญ์ œ๋˜๊ณ  ์ƒˆ๋กœ์šด ๊ด€์‹ฌ์‚ฌ๋กœ ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค." - ) - @PutMapping("/me/interests") - public ResponseEntity> updateMyInterests( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @Valid @RequestBody SaveInterestRequest request - ) { - interestCommandService.updateUserInterests(userPrincipal.getId(), request); - return BaseResponse.of(SuccessCode.OK); - } - - @Operation( - summary = "๋‚ด ๊ด€์‹ฌ์‚ฌ ์กฐํšŒ", - description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ด€์‹ฌ์‚ฌ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค." - ) - @GetMapping("/me/interests") - public ResponseEntity> getMyInterests( - @AuthenticationPrincipal UserPrincipal userPrincipal - ) { - UserInterestResponse response = interestQueryService.getUserInterests(userPrincipal.getId()); - return BaseResponse.of(SuccessCode.OK, response); - } - - @Operation( - summary = "๋‚ด ํ”„๋กœํ•„ ์ˆ˜์ •", - description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ๋‹‰๋„ค์ž„๊ณผ ์ž๊ธฐ์†Œ๊ฐœ๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." - ) - @PatchMapping("/me/profile") - public ResponseEntity> updateMyProfile( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @RequestBody UpdateUserProfileRequest request - ) { - userCommandService.updateUserProfile(userPrincipal.getId(), request); - return BaseResponse.of(SuccessCode.OK); - } - - @Operation( - summary = "๋‚ด ํ”„๋กœํ•„ ์กฐํšŒ", - description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ๋‹‰๋„ค์ž„, ์ด๋ฉ”์ผ, ์ž๊ธฐ์†Œ๊ฐœ)" - ) - @GetMapping("/me/profile") - public ResponseEntity> getMyProfile( - @AuthenticationPrincipal UserPrincipal userPrincipal - ) { - UserProfileResponse response = userQueryService.getUserProfile(userPrincipal.getId()); - return BaseResponse.of(SuccessCode.OK, response); - } - - @Operation( - summary = "ํšŒ์› ํƒˆํ‡ด", - description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ •์„ ํƒˆํ‡ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ธ์ •๋ณด๋Š” ์ฆ‰์‹œ ์ต๋ช…ํ™”๋˜๋ฉฐ, ํ™œ๋™ ๊ธฐ๋ก์€ ํ†ต๊ณ„ ๋ชฉ์ ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ํƒˆํ‡ด ํ›„ ๋™์ผํ•œ ์†Œ์…œ ๊ณ„์ •์œผ๋กœ ์žฌ๊ฐ€์ž… ์‹œ ์ƒˆ๋กœ์šด ํšŒ์›์œผ๋กœ ์˜จ๋ณด๋”ฉ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค." - ) - @PatchMapping("/me/withdrawal") - public ResponseEntity> withdrawUser( - @AuthenticationPrincipal UserPrincipal userPrincipal - ) { - userCommandService.withdrawUser(userPrincipal.getId()); - return BaseResponse.of(SuccessCode.OK); - } -} +package com.techfork.domain.useraccount.controller; + +import com.techfork.domain.useraccount.dto.SaveInterestRequest; +import com.techfork.domain.useraccount.dto.UpdateAccountProfileRequest; +import com.techfork.domain.useraccount.dto.UserInterestResponse; +import com.techfork.domain.useraccount.dto.AccountProfileResponse; +import com.techfork.domain.useraccount.service.InterestCommandService; +import com.techfork.domain.useraccount.service.InterestQueryService; +import com.techfork.domain.useraccount.service.UserCommandService; +import com.techfork.domain.useraccount.service.UserQueryService; +import com.techfork.global.common.code.SuccessCode; +import com.techfork.global.response.BaseResponse; +import com.techfork.global.security.oauth.UserPrincipal; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "User", description = "์‚ฌ์šฉ์ž API") +@Slf4j +@RestController +@RequestMapping("/api/v1/users") +@RequiredArgsConstructor +public class UserController { + + private final InterestCommandService interestCommandService; + private final InterestQueryService interestQueryService; + private final UserCommandService userCommandService; + private final UserQueryService userQueryService; + + @Operation( + summary = "๋‚ด ๊ด€์‹ฌ์‚ฌ ์ˆ˜์ •", + description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ๊ด€์‹ฌ์‚ฌ๋Š” ๋ชจ๋‘ ์‚ญ์ œ๋˜๊ณ  ์ƒˆ๋กœ์šด ๊ด€์‹ฌ์‚ฌ๋กœ ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค." + ) + @PutMapping("/me/interests") + public ResponseEntity> updateMyInterests( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @Valid @RequestBody SaveInterestRequest request + ) { + interestCommandService.updateUserInterests(userPrincipal.getId(), request); + return BaseResponse.of(SuccessCode.OK); + } + + @Operation( + summary = "๋‚ด ๊ด€์‹ฌ์‚ฌ ์กฐํšŒ", + description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ด€์‹ฌ์‚ฌ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค." + ) + @GetMapping("/me/interests") + public ResponseEntity> getMyInterests( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + UserInterestResponse response = interestQueryService.getUserInterests(userPrincipal.getId()); + return BaseResponse.of(SuccessCode.OK, response); + } + + @Operation( + summary = "๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ •", + description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ๋‹‰๋„ค์ž„๊ณผ ์ž๊ธฐ์†Œ๊ฐœ๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + ) + @PatchMapping("/me/profile") + public ResponseEntity> updateMyAccountProfile( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody UpdateAccountProfileRequest request + ) { + userCommandService.updateAccountProfile(userPrincipal.getId(), request); + return BaseResponse.of(SuccessCode.OK); + } + + @Operation( + summary = "๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์กฐํšŒ", + description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ํ”„๋กœํ•„ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. (ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ๋‹‰๋„ค์ž„, ์ด๋ฉ”์ผ, ์ž๊ธฐ์†Œ๊ฐœ)" + ) + @GetMapping("/me/profile") + public ResponseEntity> getMyAccountProfile( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + AccountProfileResponse response = userQueryService.getAccountProfile(userPrincipal.getId()); + return BaseResponse.of(SuccessCode.OK, response); + } + + @Operation( + summary = "ํšŒ์› ํƒˆํ‡ด", + description = "ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ •์„ ํƒˆํ‡ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์ • ํ”„๋กœํ•„ ๊ฐœ์ธ์ •๋ณด๋Š” ์ฆ‰์‹œ ์ต๋ช…ํ™”๋˜๋ฉฐ, ํ™œ๋™ ๊ธฐ๋ก์€ ํ†ต๊ณ„ ๋ชฉ์ ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ํƒˆํ‡ด ํ›„ ๋™์ผํ•œ ์†Œ์…œ ๊ณ„์ •์œผ๋กœ ์žฌ๊ฐ€์ž… ์‹œ ์ƒˆ๋กœ์šด ํšŒ์›์œผ๋กœ ์˜จ๋ณด๋”ฉ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค." + ) + @PatchMapping("/me/withdrawal") + public ResponseEntity> withdrawUser( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + userCommandService.withdrawUser(userPrincipal.getId()); + return BaseResponse.of(SuccessCode.OK); + } +} diff --git a/src/main/java/com/techfork/domain/user/converter/InterestConverter.java b/src/main/java/com/techfork/domain/useraccount/converter/InterestConverter.java similarity index 80% rename from src/main/java/com/techfork/domain/user/converter/InterestConverter.java rename to src/main/java/com/techfork/domain/useraccount/converter/InterestConverter.java index 535e471a..9f346eb6 100644 --- a/src/main/java/com/techfork/domain/user/converter/InterestConverter.java +++ b/src/main/java/com/techfork/domain/useraccount/converter/InterestConverter.java @@ -1,47 +1,47 @@ -package com.techfork.domain.user.converter; - -import com.techfork.domain.user.dto.InterestListResponse; -import com.techfork.domain.user.dto.UserInterestDto; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.EInterestKeyword; -import org.springframework.stereotype.Component; - -import java.util.Arrays; -import java.util.List; - -@Component -public class InterestConverter { - - public List toInterestCategoryDtoList() { - return Arrays.stream(EInterestCategory.values()) - .map(category -> { - List keywords = EInterestKeyword.getKeywordsByCategory(category) - .stream() - .map(keyword -> new InterestListResponse.Keyword(keyword.name(), keyword.getDisplayName())) - .toList(); - - return InterestListResponse.Category.builder() - .category(category.name()) - .displayName(category.getDisplayName()) - .keywords(keywords) - .build(); - }) - .toList(); - } - - public List toUserInterestDtoList(List categories) { - return categories.stream() - .map(category -> { - List keywords = category.getKeywords().stream() - .map(keyword -> keyword.getKeyword().name()) - .toList(); - - return UserInterestDto.builder() - .category(category.getCategory().name()) - .keywords(keywords) - .build(); - }) - .toList(); - } -} +package com.techfork.domain.useraccount.converter; + +import com.techfork.domain.useraccount.dto.InterestListResponse; +import com.techfork.domain.useraccount.dto.UserInterestDto; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +@Component +public class InterestConverter { + + public List toInterestCategoryDtoList() { + return Arrays.stream(EInterestCategory.values()) + .map(category -> { + List keywords = EInterestKeyword.getKeywordsByCategory(category) + .stream() + .map(keyword -> new InterestListResponse.Keyword(keyword.name(), keyword.getDisplayName())) + .toList(); + + return InterestListResponse.Category.builder() + .category(category.name()) + .displayName(category.getDisplayName()) + .keywords(keywords) + .build(); + }) + .toList(); + } + + public List toUserInterestDtoList(List categories) { + return categories.stream() + .map(category -> { + List keywords = category.getKeywords().stream() + .map(keyword -> keyword.getKeyword().name()) + .toList(); + + return UserInterestDto.builder() + .category(category.getCategory().name()) + .keywords(keywords) + .build(); + }) + .toList(); + } +} diff --git a/src/main/java/com/techfork/domain/user/converter/UserConverter.java b/src/main/java/com/techfork/domain/useraccount/converter/UserConverter.java similarity index 52% rename from src/main/java/com/techfork/domain/user/converter/UserConverter.java rename to src/main/java/com/techfork/domain/useraccount/converter/UserConverter.java index 0e7c71f0..75d4f3ed 100644 --- a/src/main/java/com/techfork/domain/user/converter/UserConverter.java +++ b/src/main/java/com/techfork/domain/useraccount/converter/UserConverter.java @@ -1,14 +1,14 @@ -package com.techfork.domain.user.converter; +package com.techfork.domain.useraccount.converter; -import com.techfork.domain.user.dto.UserProfileResponse; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.dto.AccountProfileResponse; +import com.techfork.domain.useraccount.entity.User; import org.springframework.stereotype.Component; @Component public class UserConverter { - public UserProfileResponse toUserProfileResponse(User user) { - return UserProfileResponse.builder() + public AccountProfileResponse toAccountProfileResponse(User user) { + return AccountProfileResponse.builder() .profileImage(user.getProfileImage()) .nickName(user.getNickName()) .email(user.getEmail()) diff --git a/src/main/java/com/techfork/domain/user/dto/UserProfileResponse.java b/src/main/java/com/techfork/domain/useraccount/dto/AccountProfileResponse.java similarity index 62% rename from src/main/java/com/techfork/domain/user/dto/UserProfileResponse.java rename to src/main/java/com/techfork/domain/useraccount/dto/AccountProfileResponse.java index 71e35003..6fd4e595 100644 --- a/src/main/java/com/techfork/domain/user/dto/UserProfileResponse.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/AccountProfileResponse.java @@ -1,12 +1,12 @@ -package com.techfork.domain.user.dto; +package com.techfork.domain.useraccount.dto; import lombok.Builder; @Builder -public record UserProfileResponse( +public record AccountProfileResponse( String profileImage, String nickName, String email, String description ) { -} \ No newline at end of file +} diff --git a/src/main/java/com/techfork/domain/user/dto/InterestListResponse.java b/src/main/java/com/techfork/domain/useraccount/dto/InterestListResponse.java similarity index 86% rename from src/main/java/com/techfork/domain/user/dto/InterestListResponse.java rename to src/main/java/com/techfork/domain/useraccount/dto/InterestListResponse.java index 4e76c669..80dedb24 100644 --- a/src/main/java/com/techfork/domain/user/dto/InterestListResponse.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/InterestListResponse.java @@ -1,22 +1,22 @@ -package com.techfork.domain.user.dto; - -import lombok.Builder; - -import java.util.List; - -@Builder -public record InterestListResponse( - List categories -) { - @Builder - public record Category( - String category, - String displayName, - List keywords - ) {} - - public record Keyword( - String keyword, - String displayName - ) {} -} +package com.techfork.domain.useraccount.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record InterestListResponse( + List categories +) { + @Builder + public record Category( + String category, + String displayName, + List keywords + ) {} + + public record Keyword( + String keyword, + String displayName + ) {} +} diff --git a/src/main/java/com/techfork/domain/user/dto/OnboardingRequest.java b/src/main/java/com/techfork/domain/useraccount/dto/OnboardingRequest.java similarity index 94% rename from src/main/java/com/techfork/domain/user/dto/OnboardingRequest.java rename to src/main/java/com/techfork/domain/useraccount/dto/OnboardingRequest.java index 2fa753d2..24aadf55 100644 --- a/src/main/java/com/techfork/domain/user/dto/OnboardingRequest.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/OnboardingRequest.java @@ -1,4 +1,4 @@ -package com.techfork.domain.user.dto; +package com.techfork.domain.useraccount.dto; import jakarta.validation.constraints.*; diff --git a/src/main/java/com/techfork/domain/user/dto/SaveInterestRequest.java b/src/main/java/com/techfork/domain/useraccount/dto/SaveInterestRequest.java similarity index 87% rename from src/main/java/com/techfork/domain/user/dto/SaveInterestRequest.java rename to src/main/java/com/techfork/domain/useraccount/dto/SaveInterestRequest.java index ffac4538..6555a57a 100644 --- a/src/main/java/com/techfork/domain/user/dto/SaveInterestRequest.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/SaveInterestRequest.java @@ -1,13 +1,13 @@ -package com.techfork.domain.user.dto; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -import java.util.List; - -public record SaveInterestRequest( - @NotNull(message = "๊ด€์‹ฌ์‚ฌ ๋ชฉ๋ก์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") - @NotEmpty(message = "๊ด€์‹ฌ์‚ฌ๋ฅผ ์ตœ์†Œ 1๊ฐœ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.") - List interests -) { -} +package com.techfork.domain.useraccount.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +public record SaveInterestRequest( + @NotNull(message = "๊ด€์‹ฌ์‚ฌ ๋ชฉ๋ก์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.") + @NotEmpty(message = "๊ด€์‹ฌ์‚ฌ๋ฅผ ์ตœ์†Œ 1๊ฐœ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.") + List interests +) { +} diff --git a/src/main/java/com/techfork/domain/user/dto/UpdateUserProfileRequest.java b/src/main/java/com/techfork/domain/useraccount/dto/UpdateAccountProfileRequest.java similarity index 67% rename from src/main/java/com/techfork/domain/user/dto/UpdateUserProfileRequest.java rename to src/main/java/com/techfork/domain/useraccount/dto/UpdateAccountProfileRequest.java index e9fce44f..bbcf4095 100644 --- a/src/main/java/com/techfork/domain/user/dto/UpdateUserProfileRequest.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/UpdateAccountProfileRequest.java @@ -1,9 +1,9 @@ -package com.techfork.domain.user.dto; +package com.techfork.domain.useraccount.dto; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ˆ˜์ • ์š”์ฒญ") -public record UpdateUserProfileRequest( +@Schema(description = "๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์š”์ฒญ") +public record UpdateAccountProfileRequest( @Schema(description = "๋‹‰๋„ค์ž„ (์„ ํƒ์ )", example = "ํ…Œํฌ๋Ÿฌ๋ฒ„") String nickName, diff --git a/src/main/java/com/techfork/domain/user/dto/UserInterestDto.java b/src/main/java/com/techfork/domain/useraccount/dto/UserInterestDto.java similarity index 75% rename from src/main/java/com/techfork/domain/user/dto/UserInterestDto.java rename to src/main/java/com/techfork/domain/useraccount/dto/UserInterestDto.java index d72ea6f1..9ce4789c 100644 --- a/src/main/java/com/techfork/domain/user/dto/UserInterestDto.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/UserInterestDto.java @@ -1,12 +1,12 @@ -package com.techfork.domain.user.dto; - -import lombok.Builder; - -import java.util.List; - -@Builder -public record UserInterestDto( - String category, - List keywords -) { -} +package com.techfork.domain.useraccount.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record UserInterestDto( + String category, + List keywords +) { +} diff --git a/src/main/java/com/techfork/domain/user/dto/UserInterestResponse.java b/src/main/java/com/techfork/domain/useraccount/dto/UserInterestResponse.java similarity index 74% rename from src/main/java/com/techfork/domain/user/dto/UserInterestResponse.java rename to src/main/java/com/techfork/domain/useraccount/dto/UserInterestResponse.java index 42cc8a0c..c0d84082 100644 --- a/src/main/java/com/techfork/domain/user/dto/UserInterestResponse.java +++ b/src/main/java/com/techfork/domain/useraccount/dto/UserInterestResponse.java @@ -1,11 +1,11 @@ -package com.techfork.domain.user.dto; - -import lombok.Builder; - -import java.util.List; - -@Builder -public record UserInterestResponse( - List interests -) { -} +package com.techfork.domain.useraccount.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record UserInterestResponse( + List interests +) { +} diff --git a/src/main/java/com/techfork/domain/user/entity/User.java b/src/main/java/com/techfork/domain/useraccount/entity/User.java similarity index 91% rename from src/main/java/com/techfork/domain/user/entity/User.java rename to src/main/java/com/techfork/domain/useraccount/entity/User.java index 0551be8b..086526fe 100644 --- a/src/main/java/com/techfork/domain/user/entity/User.java +++ b/src/main/java/com/techfork/domain/useraccount/entity/User.java @@ -1,111 +1,111 @@ -package com.techfork.domain.user.entity; - -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.enums.UserStatus; -import com.techfork.global.common.BaseTimeEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.util.ArrayList; -import java.util.List; - -@Entity -@Table(name = "users", uniqueConstraints = { - @UniqueConstraint(columnNames = {"social_type", "social_id"}) -}) -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class User extends BaseTimeEntity { - - private String nickName; - - private String email; - - private String profileImage; - - private String description; - - @Enumerated(EnumType.STRING) - @Column(name = "social_type", nullable = false) - private SocialType socialType; - - @Column(name = "social_id", nullable = false) - private String socialId; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private Role role; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private UserStatus status; - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List interestCategories = new ArrayList<>(); - - @PersistenceCreator - @Builder - User(String nickName, String email, String profileImage, String description, SocialType socialType, String socialId, Role role, UserStatus status) { - this.nickName = nickName; - this.email = email; - this.profileImage = profileImage; - this.description = description; - this.socialType = socialType; - this.socialId = socialId; - this.role = role != null ? role : Role.USER; - this.status = status != null ? status : UserStatus.PENDING; - } - - public static User createSocialUser(SocialType socialType, String socialId, String email, String profileImage) { - return User.builder() - .socialType(socialType) - .socialId(socialId) - .email(email) - .profileImage(profileImage) - .role(Role.USER) - .build(); - } - - public void updateUser(String nickName, String email, String description) { - this.nickName = nickName; - this.email = email; - this.description = description; - this.status = UserStatus.ACTIVE; - } - - public void updateProfile(String nickName, String description) { - if (nickName != null) { - this.nickName = nickName; - } - if (description != null) { - this.description = description; - } - } - - public boolean isActive() { - return status == UserStatus.ACTIVE; - } - - public boolean isWithdrawn() { - return status == UserStatus.WITHDRAWN; - } - - public void withdraw() { - this.status = UserStatus.WITHDRAWN; - this.nickName = null; - this.email = null; - this.profileImage = null; - this.description = null; - } - - public void reactivate(String email, String profileImage) { - this.email = email; - this.profileImage = profileImage; - this.status = UserStatus.PENDING; - } -} +package com.techfork.domain.useraccount.entity; + +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.enums.UserStatus; +import com.techfork.global.common.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "users", uniqueConstraints = { + @UniqueConstraint(columnNames = {"social_type", "social_id"}) +}) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class User extends BaseTimeEntity { + + private String nickName; + + private String email; + + private String profileImage; + + private String description; + + @Enumerated(EnumType.STRING) + @Column(name = "social_type", nullable = false) + private SocialType socialType; + + @Column(name = "social_id", nullable = false) + private String socialId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private UserStatus status; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List interestCategories = new ArrayList<>(); + + @PersistenceCreator + @Builder + User(String nickName, String email, String profileImage, String description, SocialType socialType, String socialId, Role role, UserStatus status) { + this.nickName = nickName; + this.email = email; + this.profileImage = profileImage; + this.description = description; + this.socialType = socialType; + this.socialId = socialId; + this.role = role != null ? role : Role.USER; + this.status = status != null ? status : UserStatus.PENDING; + } + + public static User createSocialUser(SocialType socialType, String socialId, String email, String profileImage) { + return User.builder() + .socialType(socialType) + .socialId(socialId) + .email(email) + .profileImage(profileImage) + .role(Role.USER) + .build(); + } + + public void updateUser(String nickName, String email, String description) { + this.nickName = nickName; + this.email = email; + this.description = description; + this.status = UserStatus.ACTIVE; + } + + public void updateProfile(String nickName, String description) { + if (nickName != null) { + this.nickName = nickName; + } + if (description != null) { + this.description = description; + } + } + + public boolean isActive() { + return status == UserStatus.ACTIVE; + } + + public boolean isWithdrawn() { + return status == UserStatus.WITHDRAWN; + } + + public void withdraw() { + this.status = UserStatus.WITHDRAWN; + this.nickName = null; + this.email = null; + this.profileImage = null; + this.description = null; + } + + public void reactivate(String email, String profileImage) { + this.email = email; + this.profileImage = profileImage; + this.status = UserStatus.PENDING; + } +} diff --git a/src/main/java/com/techfork/domain/user/entity/UserInterestCategory.java b/src/main/java/com/techfork/domain/useraccount/entity/UserInterestCategory.java similarity index 90% rename from src/main/java/com/techfork/domain/user/entity/UserInterestCategory.java rename to src/main/java/com/techfork/domain/useraccount/entity/UserInterestCategory.java index aa34f2ae..bf7b08bc 100644 --- a/src/main/java/com/techfork/domain/user/entity/UserInterestCategory.java +++ b/src/main/java/com/techfork/domain/useraccount/entity/UserInterestCategory.java @@ -1,50 +1,50 @@ -package com.techfork.domain.user.entity; - -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -import java.util.ArrayList; -import java.util.List; - -@Entity -@Table(name = "user_interest_categories") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class UserInterestCategory extends BaseEntity { - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 50) - private EInterestCategory category; - - @OneToMany(mappedBy = "userInterestCategory", cascade = CascadeType.ALL, orphanRemoval = true) - private List keywords = new ArrayList<>(); - - @PersistenceCreator - @Builder - UserInterestCategory(User user, EInterestCategory category) { - this.user = user; - this.category = category; - } - - public static UserInterestCategory create(User user, EInterestCategory category) { - return UserInterestCategory.builder() - .user(user) - .category(category) - .build(); - } - - public void addKeyword(UserInterestKeyword keyword) { - this.keywords.add(keyword); - keyword.setUserInterestCategory(this); - } -} +package com.techfork.domain.useraccount.entity; + +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "user_interest_categories") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserInterestCategory extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 50) + private EInterestCategory category; + + @OneToMany(mappedBy = "userInterestCategory", cascade = CascadeType.ALL, orphanRemoval = true) + private List keywords = new ArrayList<>(); + + @PersistenceCreator + @Builder + UserInterestCategory(User user, EInterestCategory category) { + this.user = user; + this.category = category; + } + + public static UserInterestCategory create(User user, EInterestCategory category) { + return UserInterestCategory.builder() + .user(user) + .category(category) + .build(); + } + + public void addKeyword(UserInterestKeyword keyword) { + this.keywords.add(keyword); + keyword.setUserInterestCategory(this); + } +} diff --git a/src/main/java/com/techfork/domain/user/entity/UserInterestKeyword.java b/src/main/java/com/techfork/domain/useraccount/entity/UserInterestKeyword.java similarity index 90% rename from src/main/java/com/techfork/domain/user/entity/UserInterestKeyword.java rename to src/main/java/com/techfork/domain/useraccount/entity/UserInterestKeyword.java index fd6bc607..3274e31a 100644 --- a/src/main/java/com/techfork/domain/user/entity/UserInterestKeyword.java +++ b/src/main/java/com/techfork/domain/useraccount/entity/UserInterestKeyword.java @@ -1,43 +1,43 @@ -package com.techfork.domain.user.entity; - -import com.techfork.domain.user.enums.EInterestKeyword; -import com.techfork.global.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.PersistenceCreator; - -@Entity -@Table(name = "user_interest_keywords") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class UserInterestKeyword extends BaseEntity { - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_interest_category_id", nullable = false) - private UserInterestCategory userInterestCategory; - - @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 50) - private EInterestKeyword keyword; - - @PersistenceCreator - @Builder - UserInterestKeyword(UserInterestCategory userInterestCategory, EInterestKeyword keyword) { - this.userInterestCategory = userInterestCategory; - this.keyword = keyword; - } - - public static UserInterestKeyword create(UserInterestCategory userInterestCategory, EInterestKeyword keyword) { - return UserInterestKeyword.builder() - .userInterestCategory(userInterestCategory) - .keyword(keyword) - .build(); - } - - protected void setUserInterestCategory(UserInterestCategory userInterestCategory) { - this.userInterestCategory = userInterestCategory; - } -} +package com.techfork.domain.useraccount.entity; + +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.PersistenceCreator; + +@Entity +@Table(name = "user_interest_keywords") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserInterestKeyword extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_interest_category_id", nullable = false) + private UserInterestCategory userInterestCategory; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 50) + private EInterestKeyword keyword; + + @PersistenceCreator + @Builder + UserInterestKeyword(UserInterestCategory userInterestCategory, EInterestKeyword keyword) { + this.userInterestCategory = userInterestCategory; + this.keyword = keyword; + } + + public static UserInterestKeyword create(UserInterestCategory userInterestCategory, EInterestKeyword keyword) { + return UserInterestKeyword.builder() + .userInterestCategory(userInterestCategory) + .keyword(keyword) + .build(); + } + + protected void setUserInterestCategory(UserInterestCategory userInterestCategory) { + this.userInterestCategory = userInterestCategory; + } +} diff --git a/src/main/java/com/techfork/domain/user/enums/EInterestCategory.java b/src/main/java/com/techfork/domain/useraccount/enums/EInterestCategory.java similarity index 89% rename from src/main/java/com/techfork/domain/user/enums/EInterestCategory.java rename to src/main/java/com/techfork/domain/useraccount/enums/EInterestCategory.java index 23dea0a1..80d82454 100644 --- a/src/main/java/com/techfork/domain/user/enums/EInterestCategory.java +++ b/src/main/java/com/techfork/domain/useraccount/enums/EInterestCategory.java @@ -1,44 +1,44 @@ -package com.techfork.domain.user.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum EInterestCategory { - - IOS("iOS"), - ANDROID("Android"), - - FRONTEND("Frontend"), - - BACKEND("Backend"), - - DATA_ENGINEERING("Data Engineering"), - DATA_SCIENCE("Data Science"), - DATABASE("Database"), - - AI_ML("AI/ML"), - - DEVOPS("DevOps"), - CLOUD("Cloud"), - SYSTEMS_OS("Systems/OS"), - NETWORKING("Networking"), - - SECURITY("Security"), - - GAME_DEV("Game Dev"), - AR_VR_XR("AR/VR/XR"), - - EMBEDDED_IOT("Embedded/IoT"), - - BLOCKCHAIN_WEB3("Blockchain/Web3"), - - QA_TEST("QA/Test"), - - PRODUCT_UX("Product/UX"), - - ARCHITECTURE("Architecture"); - - private final String displayName; -} +package com.techfork.domain.useraccount.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EInterestCategory { + + IOS("iOS"), + ANDROID("Android"), + + FRONTEND("Frontend"), + + BACKEND("Backend"), + + DATA_ENGINEERING("Data Engineering"), + DATA_SCIENCE("Data Science"), + DATABASE("Database"), + + AI_ML("AI/ML"), + + DEVOPS("DevOps"), + CLOUD("Cloud"), + SYSTEMS_OS("Systems/OS"), + NETWORKING("Networking"), + + SECURITY("Security"), + + GAME_DEV("Game Dev"), + AR_VR_XR("AR/VR/XR"), + + EMBEDDED_IOT("Embedded/IoT"), + + BLOCKCHAIN_WEB3("Blockchain/Web3"), + + QA_TEST("QA/Test"), + + PRODUCT_UX("Product/UX"), + + ARCHITECTURE("Architecture"); + + private final String displayName; +} diff --git a/src/main/java/com/techfork/domain/user/enums/EInterestKeyword.java b/src/main/java/com/techfork/domain/useraccount/enums/EInterestKeyword.java similarity index 96% rename from src/main/java/com/techfork/domain/user/enums/EInterestKeyword.java rename to src/main/java/com/techfork/domain/useraccount/enums/EInterestKeyword.java index eaa8cdaf..65a92ccf 100644 --- a/src/main/java/com/techfork/domain/user/enums/EInterestKeyword.java +++ b/src/main/java/com/techfork/domain/useraccount/enums/EInterestKeyword.java @@ -1,152 +1,152 @@ -package com.techfork.domain.user.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -@Getter -@RequiredArgsConstructor -public enum EInterestKeyword { - - // iOS - SWIFT(EInterestCategory.IOS, "Swift"), - SWIFTUI(EInterestCategory.IOS, "SwiftUI"), - UIKIT(EInterestCategory.IOS, "UIKit"), - XCODE(EInterestCategory.IOS, "Xcode"), - - // Android - KOTLIN(EInterestCategory.ANDROID, "Kotlin"), - ANDROID_JAVA(EInterestCategory.ANDROID, "Java"), - JETPACK_COMPOSE(EInterestCategory.ANDROID, "Jetpack Compose"), - ANDROID_STUDIO(EInterestCategory.ANDROID, "Android Studio"), - - // Frontend - REACT(EInterestCategory.FRONTEND, "React"), - VUE_JS(EInterestCategory.FRONTEND, "Vue.js"), - ANGULAR(EInterestCategory.FRONTEND, "Angular"), - JAVASCRIPT(EInterestCategory.FRONTEND, "JavaScript"), - TYPESCRIPT(EInterestCategory.FRONTEND, "TypeScript"), - - // Backend - JAVA(EInterestCategory.BACKEND, "Java"), - SPRING(EInterestCategory.BACKEND, "Spring"), - NODE_JS(EInterestCategory.BACKEND, "Node.js"), - PYTHON(EInterestCategory.BACKEND, "Python"), - DJANGO(EInterestCategory.BACKEND, "Django"), - - // Data Engineering - APACHE_SPARK(EInterestCategory.DATA_ENGINEERING, "Apache Spark"), - APACHE_KAFKA(EInterestCategory.DATA_ENGINEERING, "Apache Kafka"), - AIRFLOW(EInterestCategory.DATA_ENGINEERING, "Airflow"), - ETL(EInterestCategory.DATA_ENGINEERING, "ETL"), - - // Data Science - DS_PYTHON(EInterestCategory.DATA_SCIENCE, "Python"), - PANDAS(EInterestCategory.DATA_SCIENCE, "Pandas"), - NUMPY(EInterestCategory.DATA_SCIENCE, "NumPy"), - JUPYTER(EInterestCategory.DATA_SCIENCE, "Jupyter"), - SQL(EInterestCategory.DATA_SCIENCE, "SQL"), - - // Database - MYSQL(EInterestCategory.DATABASE, "MySQL"), - POSTGRESQL(EInterestCategory.DATABASE, "PostgreSQL"), - MONGODB(EInterestCategory.DATABASE, "MongoDB"), - REDIS(EInterestCategory.DATABASE, "Redis"), - ORACLE(EInterestCategory.DATABASE, "Oracle"), - - // AI/ML - TENSORFLOW(EInterestCategory.AI_ML, "TensorFlow"), - PYTORCH(EInterestCategory.AI_ML, "PyTorch"), - MACHINE_LEARNING(EInterestCategory.AI_ML, "Machine Learning"), - DEEP_LEARNING(EInterestCategory.AI_ML, "Deep Learning"), - - // DevOps - DOCKER(EInterestCategory.DEVOPS, "Docker"), - KUBERNETES(EInterestCategory.DEVOPS, "Kubernetes"), - DEVOPS_AWS(EInterestCategory.DEVOPS, "AWS"), - CI_CD(EInterestCategory.DEVOPS, "CI/CD"), - JENKINS(EInterestCategory.DEVOPS, "Jenkins"), - - // Cloud - AWS(EInterestCategory.CLOUD, "AWS"), - AZURE(EInterestCategory.CLOUD, "Azure"), - GCP(EInterestCategory.CLOUD, "GCP"), - FIREBASE(EInterestCategory.CLOUD, "Firebase"), - - // Systems/OS - LINUX(EInterestCategory.SYSTEMS_OS, "Linux"), - UNIX(EInterestCategory.SYSTEMS_OS, "Unix"), - WINDOWS_SERVER(EInterestCategory.SYSTEMS_OS, "Windows Server"), - SYSTEM_PROGRAMMING(EInterestCategory.SYSTEMS_OS, "์‹œ์Šคํ…œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ"), - - // Networking - TCP_IP(EInterestCategory.NETWORKING, "TCP/IP"), - HTTP_HTTPS(EInterestCategory.NETWORKING, "HTTP/HTTPS"), - RESTFUL_API(EInterestCategory.NETWORKING, "RESTful API"), - WEBSOCKET(EInterestCategory.NETWORKING, "WebSocket"), - - // Security - NETWORK_SECURITY(EInterestCategory.SECURITY, "๋„คํŠธ์›Œํฌ ๋ณด์•ˆ"), - WEB_SECURITY(EInterestCategory.SECURITY, "์›น ๋ณด์•ˆ"), - ENCRYPTION(EInterestCategory.SECURITY, "์•”ํ˜ธํ™”"), - AUTHENTICATION(EInterestCategory.SECURITY, "์ธ์ฆ"), - - // Game Dev - UNITY(EInterestCategory.GAME_DEV, "Unity"), - UNREAL_ENGINE(EInterestCategory.GAME_DEV, "Unreal Engine"), - GAME_CSHARP(EInterestCategory.GAME_DEV, "C#"), - GAME_CPP(EInterestCategory.GAME_DEV, "C++"), - - // AR/VR/XR - ARKIT(EInterestCategory.AR_VR_XR, "ARKit"), - REALITYKIT(EInterestCategory.AR_VR_XR, "RealityKit"), - UNITY_AR(EInterestCategory.AR_VR_XR, "Unity AR"), - VR_DEVELOPMENT(EInterestCategory.AR_VR_XR, "VR Development"), - - // Embedded/IoT - C(EInterestCategory.EMBEDDED_IOT, "C"), - CPP(EInterestCategory.EMBEDDED_IOT, "C++"), - ARDUINO(EInterestCategory.EMBEDDED_IOT, "Arduino"), - RASPBERRY_PI(EInterestCategory.EMBEDDED_IOT, "Raspberry Pi"), - RTOS(EInterestCategory.EMBEDDED_IOT, "RTOS"), - - // Blockchain/Web3 - ETHEREUM(EInterestCategory.BLOCKCHAIN_WEB3, "์ด๋”๋ฆฌ์›€"), - SMART_CONTRACT(EInterestCategory.BLOCKCHAIN_WEB3, "์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ"), - SOLIDITY(EInterestCategory.BLOCKCHAIN_WEB3, "Solidity"), - WEB3(EInterestCategory.BLOCKCHAIN_WEB3, "Web3"), - BLOCKCHAIN_BASICS(EInterestCategory.BLOCKCHAIN_WEB3, "๋ธ”๋ก์ฒด์ธ ๊ธฐ์ดˆ"), - DAPP(EInterestCategory.BLOCKCHAIN_WEB3, "DApp"), - NFT(EInterestCategory.BLOCKCHAIN_WEB3, "NFT"), - CRYPTOCURRENCY(EInterestCategory.BLOCKCHAIN_WEB3, "์•”ํ˜ธํ™”ํ"), - - // QA/Test - JUNIT(EInterestCategory.QA_TEST, "JUnit"), - SELENIUM(EInterestCategory.QA_TEST, "Selenium"), - TEST_AUTOMATION(EInterestCategory.QA_TEST, "Test Automation"), - TDD(EInterestCategory.QA_TEST, "TDD"), - - // Product/UX - FIGMA(EInterestCategory.PRODUCT_UX, "Figma"), - SKETCH(EInterestCategory.PRODUCT_UX, "Sketch"), - ADOBE_XD(EInterestCategory.PRODUCT_UX, "Adobe XD"), - PROTOTYPING(EInterestCategory.PRODUCT_UX, "ํ”„๋กœํ† ํƒ€์ดํ•‘"), - - // Architecture - MICROSERVICES(EInterestCategory.ARCHITECTURE, "Microservices"), - DDD(EInterestCategory.ARCHITECTURE, "DDD"), - DESIGN_PATTERNS(EInterestCategory.ARCHITECTURE, "Design Patterns"), - CLEAN_ARCHITECTURE(EInterestCategory.ARCHITECTURE, "Clean Architecture"); - - private final EInterestCategory category; - private final String displayName; - - public static List getKeywordsByCategory(EInterestCategory category) { - return Arrays.stream(values()) - .filter(keyword -> keyword.category == category) - .collect(Collectors.toList()); - } -} +package com.techfork.domain.useraccount.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@RequiredArgsConstructor +public enum EInterestKeyword { + + // iOS + SWIFT(EInterestCategory.IOS, "Swift"), + SWIFTUI(EInterestCategory.IOS, "SwiftUI"), + UIKIT(EInterestCategory.IOS, "UIKit"), + XCODE(EInterestCategory.IOS, "Xcode"), + + // Android + KOTLIN(EInterestCategory.ANDROID, "Kotlin"), + ANDROID_JAVA(EInterestCategory.ANDROID, "Java"), + JETPACK_COMPOSE(EInterestCategory.ANDROID, "Jetpack Compose"), + ANDROID_STUDIO(EInterestCategory.ANDROID, "Android Studio"), + + // Frontend + REACT(EInterestCategory.FRONTEND, "React"), + VUE_JS(EInterestCategory.FRONTEND, "Vue.js"), + ANGULAR(EInterestCategory.FRONTEND, "Angular"), + JAVASCRIPT(EInterestCategory.FRONTEND, "JavaScript"), + TYPESCRIPT(EInterestCategory.FRONTEND, "TypeScript"), + + // Backend + JAVA(EInterestCategory.BACKEND, "Java"), + SPRING(EInterestCategory.BACKEND, "Spring"), + NODE_JS(EInterestCategory.BACKEND, "Node.js"), + PYTHON(EInterestCategory.BACKEND, "Python"), + DJANGO(EInterestCategory.BACKEND, "Django"), + + // Data Engineering + APACHE_SPARK(EInterestCategory.DATA_ENGINEERING, "Apache Spark"), + APACHE_KAFKA(EInterestCategory.DATA_ENGINEERING, "Apache Kafka"), + AIRFLOW(EInterestCategory.DATA_ENGINEERING, "Airflow"), + ETL(EInterestCategory.DATA_ENGINEERING, "ETL"), + + // Data Science + DS_PYTHON(EInterestCategory.DATA_SCIENCE, "Python"), + PANDAS(EInterestCategory.DATA_SCIENCE, "Pandas"), + NUMPY(EInterestCategory.DATA_SCIENCE, "NumPy"), + JUPYTER(EInterestCategory.DATA_SCIENCE, "Jupyter"), + SQL(EInterestCategory.DATA_SCIENCE, "SQL"), + + // Database + MYSQL(EInterestCategory.DATABASE, "MySQL"), + POSTGRESQL(EInterestCategory.DATABASE, "PostgreSQL"), + MONGODB(EInterestCategory.DATABASE, "MongoDB"), + REDIS(EInterestCategory.DATABASE, "Redis"), + ORACLE(EInterestCategory.DATABASE, "Oracle"), + + // AI/ML + TENSORFLOW(EInterestCategory.AI_ML, "TensorFlow"), + PYTORCH(EInterestCategory.AI_ML, "PyTorch"), + MACHINE_LEARNING(EInterestCategory.AI_ML, "Machine Learning"), + DEEP_LEARNING(EInterestCategory.AI_ML, "Deep Learning"), + + // DevOps + DOCKER(EInterestCategory.DEVOPS, "Docker"), + KUBERNETES(EInterestCategory.DEVOPS, "Kubernetes"), + DEVOPS_AWS(EInterestCategory.DEVOPS, "AWS"), + CI_CD(EInterestCategory.DEVOPS, "CI/CD"), + JENKINS(EInterestCategory.DEVOPS, "Jenkins"), + + // Cloud + AWS(EInterestCategory.CLOUD, "AWS"), + AZURE(EInterestCategory.CLOUD, "Azure"), + GCP(EInterestCategory.CLOUD, "GCP"), + FIREBASE(EInterestCategory.CLOUD, "Firebase"), + + // Systems/OS + LINUX(EInterestCategory.SYSTEMS_OS, "Linux"), + UNIX(EInterestCategory.SYSTEMS_OS, "Unix"), + WINDOWS_SERVER(EInterestCategory.SYSTEMS_OS, "Windows Server"), + SYSTEM_PROGRAMMING(EInterestCategory.SYSTEMS_OS, "์‹œ์Šคํ…œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ"), + + // Networking + TCP_IP(EInterestCategory.NETWORKING, "TCP/IP"), + HTTP_HTTPS(EInterestCategory.NETWORKING, "HTTP/HTTPS"), + RESTFUL_API(EInterestCategory.NETWORKING, "RESTful API"), + WEBSOCKET(EInterestCategory.NETWORKING, "WebSocket"), + + // Security + NETWORK_SECURITY(EInterestCategory.SECURITY, "๋„คํŠธ์›Œํฌ ๋ณด์•ˆ"), + WEB_SECURITY(EInterestCategory.SECURITY, "์›น ๋ณด์•ˆ"), + ENCRYPTION(EInterestCategory.SECURITY, "์•”ํ˜ธํ™”"), + AUTHENTICATION(EInterestCategory.SECURITY, "์ธ์ฆ"), + + // Game Dev + UNITY(EInterestCategory.GAME_DEV, "Unity"), + UNREAL_ENGINE(EInterestCategory.GAME_DEV, "Unreal Engine"), + GAME_CSHARP(EInterestCategory.GAME_DEV, "C#"), + GAME_CPP(EInterestCategory.GAME_DEV, "C++"), + + // AR/VR/XR + ARKIT(EInterestCategory.AR_VR_XR, "ARKit"), + REALITYKIT(EInterestCategory.AR_VR_XR, "RealityKit"), + UNITY_AR(EInterestCategory.AR_VR_XR, "Unity AR"), + VR_DEVELOPMENT(EInterestCategory.AR_VR_XR, "VR Development"), + + // Embedded/IoT + C(EInterestCategory.EMBEDDED_IOT, "C"), + CPP(EInterestCategory.EMBEDDED_IOT, "C++"), + ARDUINO(EInterestCategory.EMBEDDED_IOT, "Arduino"), + RASPBERRY_PI(EInterestCategory.EMBEDDED_IOT, "Raspberry Pi"), + RTOS(EInterestCategory.EMBEDDED_IOT, "RTOS"), + + // Blockchain/Web3 + ETHEREUM(EInterestCategory.BLOCKCHAIN_WEB3, "์ด๋”๋ฆฌ์›€"), + SMART_CONTRACT(EInterestCategory.BLOCKCHAIN_WEB3, "์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ"), + SOLIDITY(EInterestCategory.BLOCKCHAIN_WEB3, "Solidity"), + WEB3(EInterestCategory.BLOCKCHAIN_WEB3, "Web3"), + BLOCKCHAIN_BASICS(EInterestCategory.BLOCKCHAIN_WEB3, "๋ธ”๋ก์ฒด์ธ ๊ธฐ์ดˆ"), + DAPP(EInterestCategory.BLOCKCHAIN_WEB3, "DApp"), + NFT(EInterestCategory.BLOCKCHAIN_WEB3, "NFT"), + CRYPTOCURRENCY(EInterestCategory.BLOCKCHAIN_WEB3, "์•”ํ˜ธํ™”ํ"), + + // QA/Test + JUNIT(EInterestCategory.QA_TEST, "JUnit"), + SELENIUM(EInterestCategory.QA_TEST, "Selenium"), + TEST_AUTOMATION(EInterestCategory.QA_TEST, "Test Automation"), + TDD(EInterestCategory.QA_TEST, "TDD"), + + // Product/UX + FIGMA(EInterestCategory.PRODUCT_UX, "Figma"), + SKETCH(EInterestCategory.PRODUCT_UX, "Sketch"), + ADOBE_XD(EInterestCategory.PRODUCT_UX, "Adobe XD"), + PROTOTYPING(EInterestCategory.PRODUCT_UX, "ํ”„๋กœํ† ํƒ€์ดํ•‘"), + + // Architecture + MICROSERVICES(EInterestCategory.ARCHITECTURE, "Microservices"), + DDD(EInterestCategory.ARCHITECTURE, "DDD"), + DESIGN_PATTERNS(EInterestCategory.ARCHITECTURE, "Design Patterns"), + CLEAN_ARCHITECTURE(EInterestCategory.ARCHITECTURE, "Clean Architecture"); + + private final EInterestCategory category; + private final String displayName; + + public static List getKeywordsByCategory(EInterestCategory category) { + return Arrays.stream(values()) + .filter(keyword -> keyword.category == category) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/techfork/domain/user/enums/Role.java b/src/main/java/com/techfork/domain/useraccount/enums/Role.java similarity index 80% rename from src/main/java/com/techfork/domain/user/enums/Role.java rename to src/main/java/com/techfork/domain/useraccount/enums/Role.java index d81f2ec5..c9cc787a 100644 --- a/src/main/java/com/techfork/domain/user/enums/Role.java +++ b/src/main/java/com/techfork/domain/useraccount/enums/Role.java @@ -1,4 +1,4 @@ -package com.techfork.domain.user.enums; +package com.techfork.domain.useraccount.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/techfork/domain/user/enums/SocialType.java b/src/main/java/com/techfork/domain/useraccount/enums/SocialType.java similarity index 91% rename from src/main/java/com/techfork/domain/user/enums/SocialType.java rename to src/main/java/com/techfork/domain/useraccount/enums/SocialType.java index bb85309b..81e15d6e 100644 --- a/src/main/java/com/techfork/domain/user/enums/SocialType.java +++ b/src/main/java/com/techfork/domain/useraccount/enums/SocialType.java @@ -1,4 +1,4 @@ -package com.techfork.domain.user.enums; +package com.techfork.domain.useraccount.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/techfork/domain/user/enums/UserStatus.java b/src/main/java/com/techfork/domain/useraccount/enums/UserStatus.java similarity index 87% rename from src/main/java/com/techfork/domain/user/enums/UserStatus.java rename to src/main/java/com/techfork/domain/useraccount/enums/UserStatus.java index 9e8fa3f5..7fd30396 100644 --- a/src/main/java/com/techfork/domain/user/enums/UserStatus.java +++ b/src/main/java/com/techfork/domain/useraccount/enums/UserStatus.java @@ -1,4 +1,4 @@ -package com.techfork.domain.user.enums; +package com.techfork.domain.useraccount.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/techfork/domain/user/exception/UserErrorCode.java b/src/main/java/com/techfork/domain/useraccount/exception/UserErrorCode.java similarity index 92% rename from src/main/java/com/techfork/domain/user/exception/UserErrorCode.java rename to src/main/java/com/techfork/domain/useraccount/exception/UserErrorCode.java index 75c2e034..0f380d0f 100644 --- a/src/main/java/com/techfork/domain/user/exception/UserErrorCode.java +++ b/src/main/java/com/techfork/domain/useraccount/exception/UserErrorCode.java @@ -1,26 +1,26 @@ -package com.techfork.domain.user.exception; - -import com.techfork.global.common.code.BaseCode; -import com.techfork.global.response.ReasonDTO; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; - -@RequiredArgsConstructor -public enum UserErrorCode implements BaseCode { - USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404_1", "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), - INVALID_INTEREST_KEYWORD(HttpStatus.BAD_REQUEST, "USER400_1", "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€์‹ฌ์‚ฌ ํ‚ค์›Œ๋“œ์ž…๋‹ˆ๋‹ค."), - ALREADY_WITHDRAWN(HttpStatus.BAD_REQUEST, "USER400_2", "์ด๋ฏธ ํƒˆํ‡ดํ•œ ํšŒ์›์ž…๋‹ˆ๋‹ค."); - - private final HttpStatus httpStatus; - private final String code; - private final String message; - - @Override - public ReasonDTO getReason() { - return ReasonDTO.builder() - .httpStatus(httpStatus) - .code(code) - .message(message) - .build(); - } -} +package com.techfork.domain.useraccount.exception; + +import com.techfork.global.common.code.BaseCode; +import com.techfork.global.response.ReasonDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum UserErrorCode implements BaseCode { + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404_1", "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + INVALID_INTEREST_KEYWORD(HttpStatus.BAD_REQUEST, "USER400_1", "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€์‹ฌ์‚ฌ ํ‚ค์›Œ๋“œ์ž…๋‹ˆ๋‹ค."), + ALREADY_WITHDRAWN(HttpStatus.BAD_REQUEST, "USER400_2", "์ด๋ฏธ ํƒˆํ‡ดํ•œ ํšŒ์›์ž…๋‹ˆ๋‹ค."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ReasonDTO getReason() { + return ReasonDTO.builder() + .httpStatus(httpStatus) + .code(code) + .message(message) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/user/repository/UserInterestCategoryRepository.java b/src/main/java/com/techfork/domain/useraccount/repository/UserInterestCategoryRepository.java similarity index 81% rename from src/main/java/com/techfork/domain/user/repository/UserInterestCategoryRepository.java rename to src/main/java/com/techfork/domain/useraccount/repository/UserInterestCategoryRepository.java index 68b1a3cf..8edc4df2 100644 --- a/src/main/java/com/techfork/domain/user/repository/UserInterestCategoryRepository.java +++ b/src/main/java/com/techfork/domain/useraccount/repository/UserInterestCategoryRepository.java @@ -1,18 +1,18 @@ -package com.techfork.domain.user.repository; - -import com.techfork.domain.user.entity.UserInterestCategory; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; - -public interface UserInterestCategoryRepository extends JpaRepository { - @Query(""" - SELECT DISTINCT uic FROM UserInterestCategory uic - LEFT JOIN FETCH uic.keywords - WHERE uic.user.id = :userId - """) - List findByUserIdWithKeywords(@Param("userId") Long userId); - -} +package com.techfork.domain.useraccount.repository; + +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface UserInterestCategoryRepository extends JpaRepository { + @Query(""" + SELECT DISTINCT uic FROM UserInterestCategory uic + LEFT JOIN FETCH uic.keywords + WHERE uic.user.id = :userId + """) + List findByUserIdWithKeywords(@Param("userId") Long userId); + +} diff --git a/src/main/java/com/techfork/domain/user/repository/UserRepository.java b/src/main/java/com/techfork/domain/useraccount/repository/UserRepository.java similarity index 90% rename from src/main/java/com/techfork/domain/user/repository/UserRepository.java rename to src/main/java/com/techfork/domain/useraccount/repository/UserRepository.java index 58c82b6f..781fb6db 100644 --- a/src/main/java/com/techfork/domain/user/repository/UserRepository.java +++ b/src/main/java/com/techfork/domain/useraccount/repository/UserRepository.java @@ -1,52 +1,52 @@ -package com.techfork.domain.user.repository; - -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -public interface UserRepository extends JpaRepository { - - Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); - - @Query(""" - SELECT DISTINCT u FROM User u - LEFT JOIN FETCH u.interestCategories - WHERE u.id = :userId - """) - Optional findByIdWithInterestCategories(@Param("userId") Long userId); - - /** - * ์ตœ๊ทผ ํŠน์ • ์‹œ๊ฐ„ ์ดํ›„ ํ™œ๋™ํ•œ ์‚ฌ์šฉ์ž ์กฐํšŒ - * (์ฝ์€ ํฌ์ŠคํŠธ, ๋ถ๋งˆํฌ, ๊ฒ€์ƒ‰ ๊ธฐ๋ก ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์žˆ์œผ๋ฉด ํ™œ์„ฑ ์‚ฌ์šฉ์ž) - * ํƒˆํ‡ดํ•œ ์‚ฌ์šฉ์ž๋Š” ์ œ์™ธ - */ - @Query(""" - SELECT DISTINCT u FROM User u - WHERE u.status != 'WITHDRAWN' - AND (EXISTS ( - SELECT 1 FROM ReadPost rp WHERE rp.user = u AND rp.readAt >= :since - ) OR EXISTS ( - SELECT 1 FROM Bookmark b WHERE b.user = u AND b.bookmarkedAt >= :since - ) OR EXISTS ( - SELECT 1 FROM SearchHistory sh WHERE sh.user = u AND sh.searchedAt >= :since - )) - """) - List findActiveUsersSince(@Param("since") LocalDateTime since); - - /** - * ๊ด€์‹ฌ์‚ฌ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ž ์กฐํšŒ (Fetch Join) - * ์ฃผ์˜: keywords๋Š” Multiple Bag Fetch ๋ฌธ์ œ๋กœ ์ œ์™ธ (ํ•„์š”์‹œ ๋ณ„๋„ ์ฟผ๋ฆฌ) - */ - @Query(""" - SELECT DISTINCT u FROM User u - LEFT JOIN FETCH u.interestCategories - WHERE u.id IN :userIds - """) - List findAllWithInterestCategoriesByIds(@Param("userIds") List userIds); -} +package com.techfork.domain.useraccount.repository; + +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + + Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); + + @Query(""" + SELECT DISTINCT u FROM User u + LEFT JOIN FETCH u.interestCategories + WHERE u.id = :userId + """) + Optional findByIdWithInterestCategories(@Param("userId") Long userId); + + /** + * ์ตœ๊ทผ ํŠน์ • ์‹œ๊ฐ„ ์ดํ›„ ํ™œ๋™ํ•œ ์‚ฌ์šฉ์ž ์กฐํšŒ + * (์ฝ์€ ํฌ์ŠคํŠธ, ๋ถ๋งˆํฌ, ๊ฒ€์ƒ‰ ๊ธฐ๋ก ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์žˆ์œผ๋ฉด ํ™œ์„ฑ ์‚ฌ์šฉ์ž) + * ํƒˆํ‡ดํ•œ ์‚ฌ์šฉ์ž๋Š” ์ œ์™ธ + */ + @Query(""" + SELECT DISTINCT u FROM User u + WHERE u.status != 'WITHDRAWN' + AND (EXISTS ( + SELECT 1 FROM ReadPost rp WHERE rp.user = u AND rp.readAt >= :since + ) OR EXISTS ( + SELECT 1 FROM Bookmark b WHERE b.user = u AND b.bookmarkedAt >= :since + ) OR EXISTS ( + SELECT 1 FROM SearchHistory sh WHERE sh.user = u AND sh.searchedAt >= :since + )) + """) + List findActiveUsersSince(@Param("since") LocalDateTime since); + + /** + * ๊ด€์‹ฌ์‚ฌ ์นดํ…Œ๊ณ ๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ž ์กฐํšŒ (Fetch Join) + * ์ฃผ์˜: keywords๋Š” Multiple Bag Fetch ๋ฌธ์ œ๋กœ ์ œ์™ธ (ํ•„์š”์‹œ ๋ณ„๋„ ์ฟผ๋ฆฌ) + */ + @Query(""" + SELECT DISTINCT u FROM User u + LEFT JOIN FETCH u.interestCategories + WHERE u.id IN :userIds + """) + List findAllWithInterestCategoriesByIds(@Param("userIds") List userIds); +} diff --git a/src/main/java/com/techfork/domain/user/service/InterestCommandService.java b/src/main/java/com/techfork/domain/useraccount/service/InterestCommandService.java similarity index 74% rename from src/main/java/com/techfork/domain/user/service/InterestCommandService.java rename to src/main/java/com/techfork/domain/useraccount/service/InterestCommandService.java index 7896739b..fb18071a 100644 --- a/src/main/java/com/techfork/domain/user/service/InterestCommandService.java +++ b/src/main/java/com/techfork/domain/useraccount/service/InterestCommandService.java @@ -1,77 +1,78 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.user.dto.SaveInterestRequest; -import com.techfork.domain.user.dto.UserInterestDto; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.entity.UserInterestKeyword; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.EInterestKeyword; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.global.exception.GeneralException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional -public class InterestCommandService { - - private final UserRepository userRepository; - private final UserProfileService userProfileService; - - public void updateUserInterests(Long userId, SaveInterestRequest request) { - User user = userRepository.findByIdWithInterestCategories(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - saveUserInterests(user, request); - } - - void saveUserInterests(User user, SaveInterestRequest request) { - user.getInterestCategories().clear(); - List categories = createCategoriesFromRequest(user, request); - user.getInterestCategories().addAll(categories); - - log.info("Saved {} interest categories for user {}", categories.size(), user.getId()); - - userProfileService.generateUserProfile(user.getId()); - } - - private List createCategoriesFromRequest(User user, SaveInterestRequest request) { - return request.interests().stream() - .map(dto -> createCategoryWithKeywords(user, dto)) - .toList(); - } - - private UserInterestCategory createCategoryWithKeywords(User user, UserInterestDto dto) { - EInterestCategory category = EInterestCategory.valueOf(dto.category()); - UserInterestCategory userCategory = UserInterestCategory.create(user, category); - - if (dto.keywords() != null && !dto.keywords().isEmpty()) { - addKeywordsToCategory(userCategory, category, dto.keywords()); - } - - return userCategory; - } - - private void addKeywordsToCategory(UserInterestCategory userCategory, EInterestCategory category, List keywordNames) { - for (String keywordName : keywordNames) { - EInterestKeyword keyword = EInterestKeyword.valueOf(keywordName); - validateKeywordCategory(keyword, category); - UserInterestKeyword userInterestKeyword = UserInterestKeyword.create(userCategory, keyword); - userCategory.addKeyword(userInterestKeyword); - } - } - - private void validateKeywordCategory(EInterestKeyword keyword, EInterestCategory category) { - if (keyword.getCategory() != category) { - throw new GeneralException(UserErrorCode.INVALID_INTEREST_KEYWORD); - } - } -} +package com.techfork.domain.useraccount.service; + +import com.techfork.domain.useraccount.dto.SaveInterestRequest; +import com.techfork.domain.useraccount.dto.UserInterestDto; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.entity.UserInterestKeyword; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.domain.personalization.service.PersonalizationProfileService; +import com.techfork.global.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional +public class InterestCommandService { + + private final UserRepository userRepository; + private final PersonalizationProfileService personalizationProfileService; + + public void updateUserInterests(Long userId, SaveInterestRequest request) { + User user = userRepository.findByIdWithInterestCategories(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + saveUserInterests(user, request); + } + + void saveUserInterests(User user, SaveInterestRequest request) { + user.getInterestCategories().clear(); + List categories = createCategoriesFromRequest(user, request); + user.getInterestCategories().addAll(categories); + + log.info("Saved {} interest categories for user {}", categories.size(), user.getId()); + + personalizationProfileService.generatePersonalizationProfile(user.getId()); + } + + private List createCategoriesFromRequest(User user, SaveInterestRequest request) { + return request.interests().stream() + .map(dto -> createCategoryWithKeywords(user, dto)) + .toList(); + } + + private UserInterestCategory createCategoryWithKeywords(User user, UserInterestDto dto) { + EInterestCategory category = EInterestCategory.valueOf(dto.category()); + UserInterestCategory userCategory = UserInterestCategory.create(user, category); + + if (dto.keywords() != null && !dto.keywords().isEmpty()) { + addKeywordsToCategory(userCategory, category, dto.keywords()); + } + + return userCategory; + } + + private void addKeywordsToCategory(UserInterestCategory userCategory, EInterestCategory category, List keywordNames) { + for (String keywordName : keywordNames) { + EInterestKeyword keyword = EInterestKeyword.valueOf(keywordName); + validateKeywordCategory(keyword, category); + UserInterestKeyword userInterestKeyword = UserInterestKeyword.create(userCategory, keyword); + userCategory.addKeyword(userInterestKeyword); + } + } + + private void validateKeywordCategory(EInterestKeyword keyword, EInterestCategory category) { + if (keyword.getCategory() != category) { + throw new GeneralException(UserErrorCode.INVALID_INTEREST_KEYWORD); + } + } +} diff --git a/src/main/java/com/techfork/domain/user/service/InterestQueryService.java b/src/main/java/com/techfork/domain/useraccount/service/InterestQueryService.java similarity index 67% rename from src/main/java/com/techfork/domain/user/service/InterestQueryService.java rename to src/main/java/com/techfork/domain/useraccount/service/InterestQueryService.java index 84aa5404..8237df5f 100644 --- a/src/main/java/com/techfork/domain/user/service/InterestQueryService.java +++ b/src/main/java/com/techfork/domain/useraccount/service/InterestQueryService.java @@ -1,47 +1,47 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.user.converter.InterestConverter; -import com.techfork.domain.user.dto.InterestListResponse; -import com.techfork.domain.user.dto.UserInterestDto; -import com.techfork.domain.user.dto.UserInterestResponse; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserInterestCategoryRepository; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.global.exception.GeneralException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class InterestQueryService { - - private final UserRepository userRepository; - private final UserInterestCategoryRepository userInterestCategoryRepository; - private final InterestConverter interestConverter; - - public InterestListResponse getAllInterests() { - return InterestListResponse.builder() - .categories(interestConverter.toInterestCategoryDtoList()) - .build(); - } - - public UserInterestResponse getUserInterests(Long userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - - List categories = userInterestCategoryRepository.findByUserIdWithKeywords(user.getId()); - List userInterestDtos = interestConverter.toUserInterestDtoList(categories); - - return UserInterestResponse.builder() - .interests(userInterestDtos) - .build(); - } -} +package com.techfork.domain.useraccount.service; + +import com.techfork.domain.useraccount.converter.InterestConverter; +import com.techfork.domain.useraccount.dto.InterestListResponse; +import com.techfork.domain.useraccount.dto.UserInterestDto; +import com.techfork.domain.useraccount.dto.UserInterestResponse; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserInterestCategoryRepository; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.global.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class InterestQueryService { + + private final UserRepository userRepository; + private final UserInterestCategoryRepository userInterestCategoryRepository; + private final InterestConverter interestConverter; + + public InterestListResponse getAllInterests() { + return InterestListResponse.builder() + .categories(interestConverter.toInterestCategoryDtoList()) + .build(); + } + + public UserInterestResponse getUserInterests(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); + + List categories = userInterestCategoryRepository.findByUserIdWithKeywords(user.getId()); + List userInterestDtos = interestConverter.toUserInterestDtoList(categories); + + return UserInterestResponse.builder() + .interests(userInterestDtos) + .build(); + } +} diff --git a/src/main/java/com/techfork/domain/user/service/UserCommandService.java b/src/main/java/com/techfork/domain/useraccount/service/UserCommandService.java similarity index 76% rename from src/main/java/com/techfork/domain/user/service/UserCommandService.java rename to src/main/java/com/techfork/domain/useraccount/service/UserCommandService.java index 21efdabb..598cf857 100644 --- a/src/main/java/com/techfork/domain/user/service/UserCommandService.java +++ b/src/main/java/com/techfork/domain/useraccount/service/UserCommandService.java @@ -1,11 +1,11 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.user.dto.OnboardingRequest; -import com.techfork.domain.user.dto.SaveInterestRequest; -import com.techfork.domain.user.dto.UpdateUserProfileRequest; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +package com.techfork.domain.useraccount.service; + +import com.techfork.domain.useraccount.dto.OnboardingRequest; +import com.techfork.domain.useraccount.dto.SaveInterestRequest; +import com.techfork.domain.useraccount.dto.UpdateAccountProfileRequest; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import com.techfork.global.security.auth.service.UserAuthCacheService; import jakarta.validation.Valid; @@ -35,13 +35,13 @@ public void completeOnboarding(Long userId, @Valid OnboardingRequest request) { userAuthCacheService.evict(userId); } - public void updateUserProfile(Long userId, UpdateUserProfileRequest request) { + public void updateAccountProfile(Long userId, UpdateAccountProfileRequest request) { User user = userRepository.findById(userId) .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); user.updateProfile(request.nickName(), request.description()); - log.info("User profile updated for userId: {} - nickName: {}, description: {}", + log.info("Account profile updated for userId: {} - nickName: {}, description: {}", userId, request.nickName() != null ? "updated" : "unchanged", request.description() != null ? "updated" : "unchanged"); diff --git a/src/main/java/com/techfork/domain/user/service/UserQueryService.java b/src/main/java/com/techfork/domain/useraccount/service/UserQueryService.java similarity index 51% rename from src/main/java/com/techfork/domain/user/service/UserQueryService.java rename to src/main/java/com/techfork/domain/useraccount/service/UserQueryService.java index 1b2e4da1..5549a8a2 100644 --- a/src/main/java/com/techfork/domain/user/service/UserQueryService.java +++ b/src/main/java/com/techfork/domain/useraccount/service/UserQueryService.java @@ -1,10 +1,10 @@ -package com.techfork.domain.user.service; +package com.techfork.domain.useraccount.service; -import com.techfork.domain.user.converter.UserConverter; -import com.techfork.domain.user.dto.UserProfileResponse; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.converter.UserConverter; +import com.techfork.domain.useraccount.dto.AccountProfileResponse; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,12 +20,12 @@ public class UserQueryService { private final UserRepository userRepository; private final UserConverter userConverter; - public UserProfileResponse getUserProfile(Long userId) { + public AccountProfileResponse getAccountProfile(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND)); - log.info("User profile retrieved for userId: {}", userId); + log.info("Account profile retrieved for userId: {}", userId); - return userConverter.toUserProfileResponse(user); + return userConverter.toAccountProfileResponse(user); } } diff --git a/src/main/java/com/techfork/global/config/ElasticsearchCacheManager.java b/src/main/java/com/techfork/global/config/ElasticsearchCacheManager.java index 8e4d50f5..b76462ef 100644 --- a/src/main/java/com/techfork/global/config/ElasticsearchCacheManager.java +++ b/src/main/java/com/techfork/global/config/ElasticsearchCacheManager.java @@ -5,7 +5,7 @@ import co.elastic.clients.json.JsonData; import com.techfork.domain.post.document.PostDocument; import com.techfork.domain.recommendation.config.RecommendationProperties; -import com.techfork.domain.user.document.UserProfileDocument; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationArguments; @@ -103,7 +103,7 @@ private void warmupUserProfilesKnn() { .index(USER_PROFILES_INDEX) .size(1) .knn(List.of(createKnn("profileVector", dummyVector))), - UserProfileDocument.class + PersonalizationProfileDocument.class ); log.debug("[ES Warmup] user_profiles kNN warmup OK"); } catch (Exception e) { diff --git a/src/main/java/com/techfork/global/security/auth/service/UserAuthCacheService.java b/src/main/java/com/techfork/global/security/auth/service/UserAuthCacheService.java index ae418785..3bb171fe 100644 --- a/src/main/java/com/techfork/global/security/auth/service/UserAuthCacheService.java +++ b/src/main/java/com/techfork/global/security/auth/service/UserAuthCacheService.java @@ -1,8 +1,8 @@ package com.techfork.global.security.auth.service; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.UserStatus; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.UserStatus; import com.techfork.global.constant.RedisKey; import com.techfork.global.security.oauth.UserPrincipal; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/techfork/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/techfork/global/security/filter/JwtAuthenticationFilter.java index bbf09ddd..a0e3be92 100644 --- a/src/main/java/com/techfork/global/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/techfork/global/security/filter/JwtAuthenticationFilter.java @@ -1,9 +1,9 @@ package com.techfork.global.security.filter; import com.techfork.domain.auth.exception.AuthErrorCode; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.UserStatus; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.UserStatus; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.constant.Constants; import com.techfork.global.constant.MdcKey; import com.techfork.global.exception.GeneralException; diff --git a/src/main/java/com/techfork/global/security/handler/login/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/techfork/global/security/handler/login/OAuth2AuthenticationSuccessHandler.java index 1a586f22..a78331ef 100644 --- a/src/main/java/com/techfork/global/security/handler/login/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/com/techfork/global/security/handler/login/OAuth2AuthenticationSuccessHandler.java @@ -1,6 +1,6 @@ package com.techfork.global.security.handler.login; -import com.techfork.domain.user.enums.UserStatus; +import com.techfork.domain.useraccount.enums.UserStatus; import com.techfork.global.security.auth.service.RefreshTokenService; import com.techfork.global.security.jwt.JwtDTO; import com.techfork.global.security.jwt.JwtProperties; diff --git a/src/main/java/com/techfork/global/security/jwt/JwtUtil.java b/src/main/java/com/techfork/global/security/jwt/JwtUtil.java index bb72f8c0..b417a372 100644 --- a/src/main/java/com/techfork/global/security/jwt/JwtUtil.java +++ b/src/main/java/com/techfork/global/security/jwt/JwtUtil.java @@ -1,7 +1,7 @@ package com.techfork.global.security.jwt; import com.techfork.domain.auth.exception.AuthErrorCode; -import com.techfork.domain.user.enums.Role; +import com.techfork.domain.useraccount.enums.Role; import com.techfork.global.exception.GeneralException; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; diff --git a/src/main/java/com/techfork/global/security/oauth/CustomOidcUserService.java b/src/main/java/com/techfork/global/security/oauth/CustomOidcUserService.java index 88138d10..9a2a968e 100644 --- a/src/main/java/com/techfork/global/security/oauth/CustomOidcUserService.java +++ b/src/main/java/com/techfork/global/security/oauth/CustomOidcUserService.java @@ -1,8 +1,8 @@ package com.techfork.global.security.oauth; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; diff --git a/src/main/java/com/techfork/global/security/oauth/UserPrincipal.java b/src/main/java/com/techfork/global/security/oauth/UserPrincipal.java index f23dadf4..a9e2a284 100644 --- a/src/main/java/com/techfork/global/security/oauth/UserPrincipal.java +++ b/src/main/java/com/techfork/global/security/oauth/UserPrincipal.java @@ -1,8 +1,8 @@ package com.techfork.global.security.oauth; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.UserStatus; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.UserStatus; import lombok.Builder; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; diff --git a/src/test/java/com/techfork/domain/activity/controller/ActivityControllerIntegrationTest.java b/src/test/java/com/techfork/domain/activity/controller/ActivityControllerIntegrationTest.java index aa17b427..a020166c 100644 --- a/src/test/java/com/techfork/domain/activity/controller/ActivityControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/activity/controller/ActivityControllerIntegrationTest.java @@ -14,10 +14,10 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.jwt.JwtDTO; import com.techfork.global.security.jwt.JwtUtil; diff --git a/src/test/java/com/techfork/domain/activity/repository/BookmarkRepositoryTest.java b/src/test/java/com/techfork/domain/activity/repository/BookmarkRepositoryTest.java index 0748c1a4..61262803 100644 --- a/src/test/java/com/techfork/domain/activity/repository/BookmarkRepositoryTest.java +++ b/src/test/java/com/techfork/domain/activity/repository/BookmarkRepositoryTest.java @@ -6,9 +6,9 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/techfork/domain/activity/repository/ReadPostRepositoryTest.java b/src/test/java/com/techfork/domain/activity/repository/ReadPostRepositoryTest.java index 0396de0b..ae73f4a5 100644 --- a/src/test/java/com/techfork/domain/activity/repository/ReadPostRepositoryTest.java +++ b/src/test/java/com/techfork/domain/activity/repository/ReadPostRepositoryTest.java @@ -5,9 +5,9 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/techfork/domain/activity/repository/SearchHistoryRepositoryTest.java b/src/test/java/com/techfork/domain/activity/repository/SearchHistoryRepositoryTest.java index a01dcb6e..83550457 100644 --- a/src/test/java/com/techfork/domain/activity/repository/SearchHistoryRepositoryTest.java +++ b/src/test/java/com/techfork/domain/activity/repository/SearchHistoryRepositoryTest.java @@ -1,9 +1,9 @@ package com.techfork.domain.activity.repository; import com.techfork.domain.activity.entity.SearchHistory; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/techfork/domain/activity/service/ActivityCommandServiceTest.java b/src/test/java/com/techfork/domain/activity/service/ActivityCommandServiceTest.java index 68211fd2..474a266d 100644 --- a/src/test/java/com/techfork/domain/activity/service/ActivityCommandServiceTest.java +++ b/src/test/java/com/techfork/domain/activity/service/ActivityCommandServiceTest.java @@ -14,9 +14,9 @@ import com.techfork.domain.post.exception.PostErrorCode; import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/techfork/domain/activity/service/ActivityQueryServiceTest.java b/src/test/java/com/techfork/domain/activity/service/ActivityQueryServiceTest.java index 7e22b73b..026066d9 100644 --- a/src/test/java/com/techfork/domain/activity/service/ActivityQueryServiceTest.java +++ b/src/test/java/com/techfork/domain/activity/service/ActivityQueryServiceTest.java @@ -10,9 +10,9 @@ import com.techfork.domain.post.entity.Post; import com.techfork.domain.post.entity.PostKeyword; import com.techfork.domain.post.repository.PostKeywordRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import com.techfork.global.util.CloudflareThirdPartyThumbnailOptimizer; import org.junit.jupiter.api.AfterAll; diff --git a/src/test/java/com/techfork/domain/admin/controller/AdminControllerIntegrationTest.java b/src/test/java/com/techfork/domain/admin/controller/AdminControllerIntegrationTest.java index fc625a4f..15f0e38a 100644 --- a/src/test/java/com/techfork/domain/admin/controller/AdminControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/admin/controller/AdminControllerIntegrationTest.java @@ -2,10 +2,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.techfork.domain.auth.exception.AuthErrorCode; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.jwt.JwtDTO; import com.techfork.global.security.jwt.JwtUtil; diff --git a/src/test/java/com/techfork/domain/auth/controller/AuthControllerIntegrationTest.java b/src/test/java/com/techfork/domain/auth/controller/AuthControllerIntegrationTest.java index 0ca67214..a9264f2f 100644 --- a/src/test/java/com/techfork/domain/auth/controller/AuthControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/auth/controller/AuthControllerIntegrationTest.java @@ -5,11 +5,11 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.techfork.domain.auth.dto.KakaoLoginRequest; import com.techfork.domain.auth.exception.AuthErrorCode; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.enums.UserStatus; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.enums.UserStatus; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.auth.service.RefreshTokenService; import com.techfork.global.security.jwt.JwtDTO; diff --git a/src/test/java/com/techfork/domain/auth/service/AuthServiceTest.java b/src/test/java/com/techfork/domain/auth/service/AuthServiceTest.java index 2d92cb67..5fdef67f 100644 --- a/src/test/java/com/techfork/domain/auth/service/AuthServiceTest.java +++ b/src/test/java/com/techfork/domain/auth/service/AuthServiceTest.java @@ -6,11 +6,11 @@ import com.techfork.domain.auth.dto.TokenRefreshResponse; import com.techfork.domain.auth.dto.kakao.KakaoUserInfoResponse; import com.techfork.domain.auth.exception.AuthErrorCode; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.enums.UserStatus; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.enums.UserStatus; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import com.techfork.global.security.auth.service.RefreshTokenService; import com.techfork.global.security.auth.service.UserAuthCacheService; diff --git a/src/test/java/com/techfork/domain/personalization/service/PersonalizationProfileServiceTest.java b/src/test/java/com/techfork/domain/personalization/service/PersonalizationProfileServiceTest.java new file mode 100644 index 00000000..6086444d --- /dev/null +++ b/src/test/java/com/techfork/domain/personalization/service/PersonalizationProfileServiceTest.java @@ -0,0 +1,274 @@ +package com.techfork.domain.personalization.service; + +import com.techfork.domain.activity.entity.Bookmark; +import com.techfork.domain.activity.entity.ReadPost; +import com.techfork.domain.activity.entity.SearchHistory; +import com.techfork.domain.activity.repository.BookmarkRepository; +import com.techfork.domain.activity.repository.ReadPostRepository; +import com.techfork.domain.activity.repository.SearchHistoryRepository; +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.post.entity.PostKeyword; +import com.techfork.domain.recommendation.service.RecommendationService; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.entity.UserInterestKeyword; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserInterestCategoryRepository; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.global.llm.EmbeddingClient; +import com.techfork.global.llm.LlmClient; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class PersonalizationProfileServiceTest { + + @Mock + private UserInterestCategoryRepository userInterestCategoryRepository; + + @Mock + private ReadPostRepository readPostRepository; + + @Mock + private BookmarkRepository bookmarkRepository; + + @Mock + private SearchHistoryRepository searchHistoryRepository; + + @Mock + private PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private RecommendationService recommendationService; + + @Mock + private LlmClient llmClient; + + @Mock + private EmbeddingClient embeddingClient; + + @InjectMocks + private PersonalizationProfileService personalizationProfileService; + + @Test + @DisplayName("์‚ฌ์šฉ์ž ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•˜๊ณ  ์ €์žฅํ•œ๋‹ค") + void generatePersonalizationProfileSync_CollectsActivityDataParsesAndSavesProfile() { + Long userId = 1L; + User user = createUser(userId); + List readPosts = List.of( + readPost("30์ดˆ ํฌ์ŠคํŠธ", List.of("Java"), 20), + readPost("90์ดˆ ํฌ์ŠคํŠธ", List.of("Spring"), 60), + readPost("300์ดˆ ํฌ์ŠคํŠธ", List.of("JPA"), 200), + readPost("600์ดˆ ํฌ์ŠคํŠธ", List.of("Kafka"), 400), + readPost("601์ดˆ ํฌ์ŠคํŠธ", List.of("Docker"), 700), + readPost("null ํฌ์ŠคํŠธ", List.of("Elastic"), null) + ); + List bookmarks = List.of( + bookmark("๋ถ๋งˆํฌ ํฌ์ŠคํŠธ", List.of("Kubernetes", "Helm")) + ); + + given(userInterestCategoryRepository.findByUserIdWithKeywords(userId)) + .willReturn(List.of( + interestCategory(user, EInterestCategory.BACKEND, EInterestKeyword.JAVA, EInterestKeyword.SPRING), + interestCategory(user, EInterestCategory.DEVOPS, EInterestKeyword.DOCKER) + )); + given(readPostRepository.findRecentReadPostsByUserIdWithMinDuration(anyLong(), any())) + .willReturn(readPosts); + given(bookmarkRepository.findRecentBookmarksByUserId(anyLong(), any())) + .willReturn(bookmarks); + given(searchHistoryRepository.findRecentSearchHistoriesByUserId(anyLong(), any())) + .willReturn(List.of( + SearchHistory.create(user, "Spring Batch", LocalDateTime.now()), + SearchHistory.create(user, "Elasticsearch vector", LocalDateTime.now()) + )); + given(llmClient.call(anyString(), anyString())) + .willReturn(""" + ### PROFILE + Java์™€ Spring ๊ธฐ๋ฐ˜ ๋ฐฑ์—”๋“œ, Docker ์ค‘์‹ฌ ์šด์˜ ์ž๋™ํ™”, Elasticsearch ๊ฒ€์ƒ‰ ์ตœ์ ํ™”์— ์ง‘์ค‘ํ•˜๋Š” ์‚ฌ์šฉ์ž + + ### KEYWORDS + Java, Spring, Docker, Elasticsearch, Batch + """); + given(embeddingClient.embed(anyString())).willReturn(List.of(0.1f, 0.2f, 0.3f)); + given(personalizationProfileDocumentRepository.save(any(PersonalizationProfileDocument.class))) + .willAnswer(invocation -> invocation.getArgument(0)); + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(recommendationService.generateRecommendationsForUser(user)).willReturn(5); + + personalizationProfileService.generatePersonalizationProfileSync(userId); + + ArgumentCaptor promptCaptor = ArgumentCaptor.forClass(String.class); + verify(llmClient).call(anyString(), promptCaptor.capture()); + + String prompt = promptCaptor.getValue(); + assertThat(prompt) + .contains("Java") + .contains("Spring") + .contains("Docker") + .contains("30์ดˆ ํฌ์ŠคํŠธ") + .contains("90์ดˆ ํฌ์ŠคํŠธ") + .contains("300์ดˆ ํฌ์ŠคํŠธ") + .contains("600์ดˆ ํฌ์ŠคํŠธ") + .contains("601์ดˆ ํฌ์ŠคํŠธ") + .contains("null ํฌ์ŠคํŠธ") + .contains("๋ถ๋งˆํฌ ํฌ์ŠคํŠธ") + .contains("Spring Batch") + .contains("Elasticsearch vector") + .contains("๊ฐ€๋ณ๊ฒŒ ํ›‘์–ด๋ด„") + .contains("๋น ๋ฅด๊ฒŒ ์ฝ์Œ") + .contains("์ฝ์Œ") + .contains("์ •๋…ํ•จ") + .contains("๊นŠ๊ฒŒ ์ฝ์Œ"); + + ArgumentCaptor documentCaptor = ArgumentCaptor.forClass(PersonalizationProfileDocument.class); + verify(personalizationProfileDocumentRepository).save(documentCaptor.capture()); + + PersonalizationProfileDocument savedDocument = documentCaptor.getValue(); + assertThat(savedDocument.getUserId()).isEqualTo(userId); + assertThat(savedDocument.getProfileText()) + .isEqualTo("Java์™€ Spring ๊ธฐ๋ฐ˜ ๋ฐฑ์—”๋“œ, Docker ์ค‘์‹ฌ ์šด์˜ ์ž๋™ํ™”, Elasticsearch ๊ฒ€์ƒ‰ ์ตœ์ ํ™”์— ์ง‘์ค‘ํ•˜๋Š” ์‚ฌ์šฉ์ž"); + assertThat(savedDocument.getProfileVector()).containsExactly(0.1f, 0.2f, 0.3f); + assertThat(savedDocument.getInterests()).containsExactly("Java", "Spring", "Docker"); + assertThat(savedDocument.getKeyKeywords()) + .containsExactly("Java", "Spring", "Docker", "Elasticsearch", "Batch"); + + verify(userRepository).findById(userId); + verify(recommendationService).generateRecommendationsForUser(user); + } + + @Test + @DisplayName("LLM ์‘๋‹ต์„ ํŒŒ์‹ฑํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ํ”„๋กœํ•„๋กœ fallback ์ €์žฅํ•œ๋‹ค") + void generatePersonalizationProfileSync_FallsBackToFullTextWhenSectionsAreMissing() { + Long userId = 2L; + User user = createUser(userId); + String llmResponse = "์„น์…˜ ์—†์ด๋„ ์ „์ฒด ์‘๋‹ต์„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„๋กœ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค"; + + given(userInterestCategoryRepository.findByUserIdWithKeywords(userId)).willReturn(List.of()); + given(readPostRepository.findRecentReadPostsByUserIdWithMinDuration(anyLong(), any())).willReturn(List.of()); + given(bookmarkRepository.findRecentBookmarksByUserId(anyLong(), any())).willReturn(List.of()); + given(searchHistoryRepository.findRecentSearchHistoriesByUserId(anyLong(), any())).willReturn(List.of()); + given(llmClient.call(anyString(), anyString())).willReturn(llmResponse); + given(embeddingClient.embed(llmResponse)).willReturn(List.of(1.0f, 2.0f)); + given(personalizationProfileDocumentRepository.save(any(PersonalizationProfileDocument.class))) + .willAnswer(invocation -> invocation.getArgument(0)); + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + + personalizationProfileService.generatePersonalizationProfileSync(userId); + + ArgumentCaptor documentCaptor = ArgumentCaptor.forClass(PersonalizationProfileDocument.class); + verify(personalizationProfileDocumentRepository).save(documentCaptor.capture()); + + PersonalizationProfileDocument savedDocument = documentCaptor.getValue(); + assertThat(savedDocument.getProfileText()).isEqualTo(llmResponse); + assertThat(savedDocument.getKeyKeywords()).isEmpty(); + assertThat(savedDocument.getProfileVector()).containsExactly(1.0f, 2.0f); + } + + @Test + @DisplayName("์ถ”์ฒœ ์ƒ์„ฑ์ด ์‹คํŒจํ•ด๋„ ๊ฐœ์ธํ™” ํ”„๋กœํ•„ ์ €์žฅ์€ ์œ ์ง€๋œ๋‹ค") + void generatePersonalizationProfileSync_RecommendationFailureDoesNotBreakProfileSave() { + Long userId = 3L; + User user = createUser(userId); + + given(userInterestCategoryRepository.findByUserIdWithKeywords(userId)).willReturn(List.of()); + given(readPostRepository.findRecentReadPostsByUserIdWithMinDuration(anyLong(), any())).willReturn(List.of()); + given(bookmarkRepository.findRecentBookmarksByUserId(anyLong(), any())).willReturn(List.of()); + given(searchHistoryRepository.findRecentSearchHistoriesByUserId(anyLong(), any())).willReturn(List.of()); + given(llmClient.call(anyString(), anyString())) + .willReturn(""" + ### PROFILE + ์ถ”์ฒœ ์‹คํŒจ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ €์žฅ๋˜์–ด์•ผ ํ•˜๋Š” ํ”„๋กœํ•„ + + ### KEYWORDS + ํ…Œ์ŠคํŠธ, ํšŒ๊ท€ + """); + given(embeddingClient.embed(anyString())).willReturn(List.of(9.0f, 8.0f)); + given(personalizationProfileDocumentRepository.save(any(PersonalizationProfileDocument.class))) + .willAnswer(invocation -> invocation.getArgument(0)); + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(recommendationService.generateRecommendationsForUser(user)) + .willThrow(new RuntimeException("recommendation failure")); + + assertThatCode(() -> personalizationProfileService.generatePersonalizationProfileSync(userId)) + .doesNotThrowAnyException(); + + verify(personalizationProfileDocumentRepository).save(any(PersonalizationProfileDocument.class)); + verify(recommendationService).generateRecommendationsForUser(user); + } + + private User createUser(Long userId) { + User user = User.createSocialUser(SocialType.KAKAO, "social-" + userId, "user" + userId + "@example.com", null); + ReflectionTestUtils.setField(user, "id", userId); + return user; + } + + private UserInterestCategory interestCategory(User user, EInterestCategory category, EInterestKeyword... keywords) { + UserInterestCategory userInterestCategory = UserInterestCategory.create(user, category); + for (EInterestKeyword keyword : keywords) { + userInterestCategory.addKeyword(UserInterestKeyword.create(userInterestCategory, keyword)); + } + return userInterestCategory; + } + + private ReadPost readPost(String title, List keywords, Integer durationSeconds) { + ReadPost readPost = org.mockito.Mockito.mock(ReadPost.class); + Post post = mockPost(title, keywords); + + given(readPost.getPost()).willReturn(post); + given(readPost.getReadDurationSeconds()).willReturn(durationSeconds); + + return readPost; + } + + private Bookmark bookmark(String title, List keywords) { + Bookmark bookmark = org.mockito.Mockito.mock(Bookmark.class); + Post post = mockPost(title, keywords); + + given(bookmark.getPost()).willReturn(post); + + return bookmark; + } + + private Post mockPost(String title, List keywords) { + Post post = org.mockito.Mockito.mock(Post.class); + List postKeywords = keywords.stream() + .map(this::mockPostKeyword) + .toList(); + + given(post.getTitle()).willReturn(title); + given(post.getKeywords()).willReturn(postKeywords); + + return post; + } + + private PostKeyword mockPostKeyword(String keyword) { + PostKeyword postKeyword = org.mockito.Mockito.mock(PostKeyword.class); + given(postKeyword.getKeyword()).willReturn(keyword); + return postKeyword; + } +} diff --git a/src/test/java/com/techfork/domain/post/controller/PostControllerIntegrationTest.java b/src/test/java/com/techfork/domain/post/controller/PostControllerIntegrationTest.java index cf82d295..ea9a8c28 100644 --- a/src/test/java/com/techfork/domain/post/controller/PostControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/post/controller/PostControllerIntegrationTest.java @@ -8,10 +8,10 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.jwt.JwtUtil; import org.junit.jupiter.api.AfterEach; diff --git a/src/test/java/com/techfork/domain/post/controller/PostControllerV2IntegrationTest.java b/src/test/java/com/techfork/domain/post/controller/PostControllerV2IntegrationTest.java index be04f3ea..a9b760b0 100644 --- a/src/test/java/com/techfork/domain/post/controller/PostControllerV2IntegrationTest.java +++ b/src/test/java/com/techfork/domain/post/controller/PostControllerV2IntegrationTest.java @@ -6,10 +6,10 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.jwt.JwtUtil; import org.junit.jupiter.api.AfterEach; diff --git a/src/test/java/com/techfork/domain/recommendation/controller/RecommendationControllerIntegrationTest.java b/src/test/java/com/techfork/domain/recommendation/controller/RecommendationControllerIntegrationTest.java index dfa54750..981ebd55 100644 --- a/src/test/java/com/techfork/domain/recommendation/controller/RecommendationControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/recommendation/controller/RecommendationControllerIntegrationTest.java @@ -9,10 +9,10 @@ import com.techfork.domain.recommendation.repository.RecommendedPostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.jwt.JwtDTO; import com.techfork.global.security.jwt.JwtUtil; diff --git a/src/test/java/com/techfork/domain/recommendation/converter/RecommendationConverterTest.java b/src/test/java/com/techfork/domain/recommendation/converter/RecommendationConverterTest.java index 6208f93c..3231136c 100644 --- a/src/test/java/com/techfork/domain/recommendation/converter/RecommendationConverterTest.java +++ b/src/test/java/com/techfork/domain/recommendation/converter/RecommendationConverterTest.java @@ -4,8 +4,8 @@ import com.techfork.domain.recommendation.dto.RecommendedPostDto; import com.techfork.domain.recommendation.entity.RecommendedPost; import com.techfork.domain.source.entity.TechBlog; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; import com.techfork.global.util.CloudflareThirdPartyThumbnailOptimizer; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/techfork/domain/recommendation/service/RecommendationQueryServiceTest.java b/src/test/java/com/techfork/domain/recommendation/service/RecommendationQueryServiceTest.java index d837d483..a777a76e 100644 --- a/src/test/java/com/techfork/domain/recommendation/service/RecommendationQueryServiceTest.java +++ b/src/test/java/com/techfork/domain/recommendation/service/RecommendationQueryServiceTest.java @@ -8,9 +8,9 @@ import com.techfork.domain.recommendation.entity.RecommendedPost; import com.techfork.domain.recommendation.repository.RecommendedPostRepository; import com.techfork.domain.source.entity.TechBlog; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/techfork/domain/user/controller/OnboardingControllerIntegrationTest.java b/src/test/java/com/techfork/domain/useraccount/controller/OnboardingControllerIntegrationTest.java similarity index 96% rename from src/test/java/com/techfork/domain/user/controller/OnboardingControllerIntegrationTest.java rename to src/test/java/com/techfork/domain/useraccount/controller/OnboardingControllerIntegrationTest.java index 61ea6ec0..7f714d70 100644 --- a/src/test/java/com/techfork/domain/user/controller/OnboardingControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/useraccount/controller/OnboardingControllerIntegrationTest.java @@ -1,12 +1,12 @@ -package com.techfork.domain.user.controller; +package com.techfork.domain.useraccount.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.techfork.domain.user.dto.OnboardingRequest; -import com.techfork.domain.user.dto.UserInterestDto; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.dto.OnboardingRequest; +import com.techfork.domain.useraccount.dto.UserInterestDto; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.llm.EmbeddingClient; import com.techfork.global.llm.LlmClient; diff --git a/src/test/java/com/techfork/domain/user/controller/UserControllerIntegrationTest.java b/src/test/java/com/techfork/domain/useraccount/controller/UserControllerIntegrationTest.java similarity index 84% rename from src/test/java/com/techfork/domain/user/controller/UserControllerIntegrationTest.java rename to src/test/java/com/techfork/domain/useraccount/controller/UserControllerIntegrationTest.java index 5c2e3bb0..4fc268a9 100644 --- a/src/test/java/com/techfork/domain/user/controller/UserControllerIntegrationTest.java +++ b/src/test/java/com/techfork/domain/useraccount/controller/UserControllerIntegrationTest.java @@ -1,12 +1,12 @@ -package com.techfork.domain.user.controller; +package com.techfork.domain.useraccount.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.techfork.domain.user.dto.UpdateUserProfileRequest; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.enums.UserStatus; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.dto.UpdateAccountProfileRequest; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.enums.UserStatus; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.security.jwt.JwtDTO; import com.techfork.global.security.jwt.JwtUtil; @@ -65,7 +65,7 @@ void tearDown() { // ===== ํ”„๋กœํ•„ ์กฐํšŒ ํ…Œ์ŠคํŠธ ===== @Test - @DisplayName("๋‚ด ํ”„๋กœํ•„ ์กฐํšŒ ์„ฑ๊ณต") + @DisplayName("๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์กฐํšŒ ์„ฑ๊ณต") void getMyProfile_Success() throws Exception { // When & Then mockMvc.perform(get("/api/v1/users/me/profile") @@ -82,10 +82,10 @@ void getMyProfile_Success() throws Exception { // ===== ํ”„๋กœํ•„ ์ˆ˜์ • ํ…Œ์ŠคํŠธ ===== @Test - @DisplayName("๋‚ด ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๋งŒ ์ˆ˜์ •") + @DisplayName("๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๋งŒ ์ˆ˜์ •") void updateMyProfile_Success_OnlyNickName() throws Exception { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest("์ƒˆ๋กœ์šด๋‹‰๋„ค์ž„", null); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest("์ƒˆ๋กœ์šด๋‹‰๋„ค์ž„", null); // When & Then mockMvc.perform(patch("/api/v1/users/me/profile") @@ -103,10 +103,10 @@ void updateMyProfile_Success_OnlyNickName() throws Exception { } @Test - @DisplayName("๋‚ด ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ์ž๊ธฐ์†Œ๊ฐœ๋งŒ ์ˆ˜์ •") + @DisplayName("๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ์ž๊ธฐ์†Œ๊ฐœ๋งŒ ์ˆ˜์ •") void updateMyProfile_Success_OnlyDescription() throws Exception { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest(null, "ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์ „ํ–ฅํ–ˆ์Šต๋‹ˆ๋‹ค."); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest(null, "ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์ „ํ–ฅํ–ˆ์Šต๋‹ˆ๋‹ค."); // When & Then mockMvc.perform(patch("/api/v1/users/me/profile") @@ -124,10 +124,10 @@ void updateMyProfile_Success_OnlyDescription() throws Exception { } @Test - @DisplayName("๋‚ด ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๊ณผ ์ž๊ธฐ์†Œ๊ฐœ ๋ชจ๋‘ ์ˆ˜์ •") + @DisplayName("๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๊ณผ ์ž๊ธฐ์†Œ๊ฐœ ๋ชจ๋‘ ์ˆ˜์ •") void updateMyProfile_Success_BothFields() throws Exception { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest("ํ’€์Šคํƒ๊ฐœ๋ฐœ์ž", "๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ์—”๋“œ ๋ชจ๋‘ ํ•ฉ๋‹ˆ๋‹ค."); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest("ํ’€์Šคํƒ๊ฐœ๋ฐœ์ž", "๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ์—”๋“œ ๋ชจ๋‘ ํ•ฉ๋‹ˆ๋‹ค."); // When & Then mockMvc.perform(patch("/api/v1/users/me/profile") @@ -145,10 +145,10 @@ void updateMyProfile_Success_BothFields() throws Exception { } @Test - @DisplayName("๋‚ด ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋นˆ ์š”์ฒญ (์•„๋ฌด๊ฒƒ๋„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Œ)") + @DisplayName("๋‚ด ๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋นˆ ์š”์ฒญ (์•„๋ฌด๊ฒƒ๋„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Œ)") void updateMyProfile_Success_EmptyRequest() throws Exception { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest(null, null); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest(null, null); // When & Then mockMvc.perform(patch("/api/v1/users/me/profile") @@ -166,10 +166,10 @@ void updateMyProfile_Success_EmptyRequest() throws Exception { } @Test - @DisplayName("ํ”„๋กœํ•„ ์ˆ˜์ • ํ›„ ์กฐํšŒ - ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋ฐ˜์˜ ํ™•์ธ") + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ํ›„ ์กฐํšŒ - ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋ฐ˜์˜ ํ™•์ธ") void updateAndGetProfile_Success() throws Exception { // Given - UpdateUserProfileRequest updateRequest = new UpdateUserProfileRequest("๋ณ€๊ฒฝ๋œ๋‹‰๋„ค์ž„", "๋ณ€๊ฒฝ๋œ ์ž๊ธฐ์†Œ๊ฐœ"); + UpdateAccountProfileRequest updateRequest = new UpdateAccountProfileRequest("๋ณ€๊ฒฝ๋œ๋‹‰๋„ค์ž„", "๋ณ€๊ฒฝ๋œ ์ž๊ธฐ์†Œ๊ฐœ"); // When - ํ”„๋กœํ•„ ์ˆ˜์ • mockMvc.perform(patch("/api/v1/users/me/profile") diff --git a/src/test/java/com/techfork/domain/user/repository/UserInterestCategoryRepositoryTest.java b/src/test/java/com/techfork/domain/useraccount/repository/UserInterestCategoryRepositoryTest.java similarity index 95% rename from src/test/java/com/techfork/domain/user/repository/UserInterestCategoryRepositoryTest.java rename to src/test/java/com/techfork/domain/useraccount/repository/UserInterestCategoryRepositoryTest.java index f0d36570..6e3a5b72 100644 --- a/src/test/java/com/techfork/domain/user/repository/UserInterestCategoryRepositoryTest.java +++ b/src/test/java/com/techfork/domain/useraccount/repository/UserInterestCategoryRepositoryTest.java @@ -1,11 +1,11 @@ -package com.techfork.domain.user.repository; - -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.entity.UserInterestKeyword; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.EInterestKeyword; -import com.techfork.domain.user.enums.SocialType; +package com.techfork.domain.useraccount.repository; + +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.entity.UserInterestKeyword; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import com.techfork.domain.useraccount.enums.SocialType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/techfork/domain/user/repository/UserRepositoryTest.java b/src/test/java/com/techfork/domain/useraccount/repository/UserRepositoryTest.java similarity index 97% rename from src/test/java/com/techfork/domain/user/repository/UserRepositoryTest.java rename to src/test/java/com/techfork/domain/useraccount/repository/UserRepositoryTest.java index dbc95543..211cd111 100644 --- a/src/test/java/com/techfork/domain/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/techfork/domain/useraccount/repository/UserRepositoryTest.java @@ -1,4 +1,4 @@ -package com.techfork.domain.user.repository; +package com.techfork.domain.useraccount.repository; import com.techfork.domain.activity.entity.ReadPost; import com.techfork.domain.activity.entity.Bookmark; @@ -10,10 +10,10 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.SocialType; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.SocialType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/techfork/domain/user/service/InterestCommandServiceTest.java b/src/test/java/com/techfork/domain/useraccount/service/InterestCommandServiceTest.java similarity index 85% rename from src/test/java/com/techfork/domain/user/service/InterestCommandServiceTest.java rename to src/test/java/com/techfork/domain/useraccount/service/InterestCommandServiceTest.java index 264566a5..9399c716 100644 --- a/src/test/java/com/techfork/domain/user/service/InterestCommandServiceTest.java +++ b/src/test/java/com/techfork/domain/useraccount/service/InterestCommandServiceTest.java @@ -1,12 +1,13 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.user.dto.SaveInterestRequest; -import com.techfork.domain.user.dto.UserInterestDto; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +package com.techfork.domain.useraccount.service; + +import com.techfork.domain.useraccount.dto.SaveInterestRequest; +import com.techfork.domain.useraccount.dto.UserInterestDto; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.domain.personalization.service.PersonalizationProfileService; import com.techfork.global.exception.GeneralException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ class InterestCommandServiceTest { private UserRepository userRepository; @Mock - private UserProfileService userProfileService; + private PersonalizationProfileService personalizationProfileService; @InjectMocks private InterestCommandService interestCommandService; @@ -61,7 +62,7 @@ void saveUserInterests_Success() { assertThat(user.getInterestCategories()).hasSize(1); assertThat(user.getInterestCategories().get(0).getKeywords()).hasSize(2); - verify(userProfileService, times(1)).generateUserProfile(user.getId()); + verify(personalizationProfileService, times(1)).generatePersonalizationProfile(user.getId()); } @Test @@ -86,7 +87,7 @@ void saveUserInterests_CategoryOnly_Success() { assertThat(user.getInterestCategories()).hasSize(1); assertThat(user.getInterestCategories().get(0).getKeywords()).isEmpty(); - verify(userProfileService, times(1)).generateUserProfile(user.getId()); + verify(personalizationProfileService, times(1)).generatePersonalizationProfile(user.getId()); } @Test @@ -121,7 +122,7 @@ void saveUserInterests_MultipleCategories_Success() { assertThat(user.getInterestCategories().get(1).getKeywords()).hasSize(2); assertThat(user.getInterestCategories().get(2).getKeywords()).hasSize(3); - verify(userProfileService, times(1)).generateUserProfile(user.getId()); + verify(personalizationProfileService, times(1)).generatePersonalizationProfile(user.getId()); } @Test @@ -150,7 +151,7 @@ void saveUserInterests_ClearExistingInterests_Success() { // Then assertThat(user.getInterestCategories()).hasSize(1); - verify(userProfileService, times(1)).generateUserProfile(user.getId()); + verify(personalizationProfileService, times(1)).generatePersonalizationProfile(user.getId()); } @Test @@ -174,7 +175,7 @@ void saveUserInterests_InvalidKeywordCategory_ThrowsException() { .isInstanceOf(GeneralException.class) .hasFieldOrPropertyWithValue("code", UserErrorCode.INVALID_INTEREST_KEYWORD); - verify(userProfileService, never()).generateUserProfile(any()); + verify(personalizationProfileService, never()).generatePersonalizationProfile(any()); } @Test @@ -204,7 +205,7 @@ void updateUserInterests_Success() { assertThat(mockUser.getInterestCategories()).hasSize(1); verify(userRepository, times(1)).findByIdWithInterestCategories(userId); - verify(userProfileService, times(1)).generateUserProfile(userId); + verify(personalizationProfileService, times(1)).generatePersonalizationProfile(userId); } @Test @@ -229,6 +230,6 @@ void updateUserInterests_UserNotFound_ThrowsException() { .isInstanceOf(GeneralException.class) .hasFieldOrPropertyWithValue("code", UserErrorCode.USER_NOT_FOUND); - verify(userProfileService, never()).generateUserProfile(any()); + verify(personalizationProfileService, never()).generatePersonalizationProfile(any()); } } diff --git a/src/test/java/com/techfork/domain/user/service/UserCommandServiceTest.java b/src/test/java/com/techfork/domain/useraccount/service/UserCommandServiceTest.java similarity index 83% rename from src/test/java/com/techfork/domain/user/service/UserCommandServiceTest.java rename to src/test/java/com/techfork/domain/useraccount/service/UserCommandServiceTest.java index 8802ec86..16240912 100644 --- a/src/test/java/com/techfork/domain/user/service/UserCommandServiceTest.java +++ b/src/test/java/com/techfork/domain/useraccount/service/UserCommandServiceTest.java @@ -1,13 +1,13 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.user.dto.OnboardingRequest; -import com.techfork.domain.user.dto.SaveInterestRequest; -import com.techfork.domain.user.dto.UpdateUserProfileRequest; -import com.techfork.domain.user.dto.UserInterestDto; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +package com.techfork.domain.useraccount.service; + +import com.techfork.domain.useraccount.dto.OnboardingRequest; +import com.techfork.domain.useraccount.dto.SaveInterestRequest; +import com.techfork.domain.useraccount.dto.UpdateAccountProfileRequest; +import com.techfork.domain.useraccount.dto.UserInterestDto; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import com.techfork.global.security.auth.service.UserAuthCacheService; import org.junit.jupiter.api.BeforeEach; @@ -205,14 +205,14 @@ void setUp() { } @Test - @DisplayName("ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๋งŒ ์ˆ˜์ •") - void updateUserProfile_Success_OnlyNickName() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๋งŒ ์ˆ˜์ •") + void updateAccountProfile_Success_OnlyNickName() { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest("์ƒˆ๋กœ์šด๋‹‰๋„ค์ž„", null); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest("์ƒˆ๋กœ์šด๋‹‰๋„ค์ž„", null); given(userRepository.findById(userId)).willReturn(Optional.of(testUser)); // When - userCommandService.updateUserProfile(userId, request); + userCommandService.updateAccountProfile(userId, request); // Then assertThat(testUser.getNickName()).isEqualTo("์ƒˆ๋กœ์šด๋‹‰๋„ค์ž„"); @@ -222,14 +222,14 @@ void updateUserProfile_Success_OnlyNickName() { } @Test - @DisplayName("ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ์ž๊ธฐ์†Œ๊ฐœ๋งŒ ์ˆ˜์ •") - void updateUserProfile_Success_OnlyDescription() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ์ž๊ธฐ์†Œ๊ฐœ๋งŒ ์ˆ˜์ •") + void updateAccountProfile_Success_OnlyDescription() { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest(null, "์ƒˆ๋กœ์šด ์ž๊ธฐ์†Œ๊ฐœ"); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest(null, "์ƒˆ๋กœ์šด ์ž๊ธฐ์†Œ๊ฐœ"); given(userRepository.findById(userId)).willReturn(Optional.of(testUser)); // When - userCommandService.updateUserProfile(userId, request); + userCommandService.updateAccountProfile(userId, request); // Then assertThat(testUser.getNickName()).isEqualTo("ํ…Œ์ŠคํŠธ์œ ์ €"); // ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ @@ -239,14 +239,14 @@ void updateUserProfile_Success_OnlyDescription() { } @Test - @DisplayName("ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๊ณผ ์ž๊ธฐ์†Œ๊ฐœ ๋ชจ๋‘ ์ˆ˜์ •") - void updateUserProfile_Success_BothFields() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ๋‹‰๋„ค์ž„๊ณผ ์ž๊ธฐ์†Œ๊ฐœ ๋ชจ๋‘ ์ˆ˜์ •") + void updateAccountProfile_Success_BothFields() { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest("์ƒˆ๋‹‰๋„ค์ž„", "์ƒˆ ์ž๊ธฐ์†Œ๊ฐœ"); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest("์ƒˆ๋‹‰๋„ค์ž„", "์ƒˆ ์ž๊ธฐ์†Œ๊ฐœ"); given(userRepository.findById(userId)).willReturn(Optional.of(testUser)); // When - userCommandService.updateUserProfile(userId, request); + userCommandService.updateAccountProfile(userId, request); // Then assertThat(testUser.getNickName()).isEqualTo("์ƒˆ๋‹‰๋„ค์ž„"); @@ -256,14 +256,14 @@ void updateUserProfile_Success_BothFields() { } @Test - @DisplayName("ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ์•„๋ฌด๊ฒƒ๋„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Œ") - void updateUserProfile_Success_NoChanges() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต - ์•„๋ฌด๊ฒƒ๋„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Œ") + void updateAccountProfile_Success_NoChanges() { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest(null, null); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest(null, null); given(userRepository.findById(userId)).willReturn(Optional.of(testUser)); // When - userCommandService.updateUserProfile(userId, request); + userCommandService.updateAccountProfile(userId, request); // Then assertThat(testUser.getNickName()).isEqualTo("ํ…Œ์ŠคํŠธ์œ ์ €"); // ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ @@ -273,14 +273,14 @@ void updateUserProfile_Success_NoChanges() { } @Test - @DisplayName("ํ”„๋กœํ•„ ์ˆ˜์ • ์‹คํŒจ - ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ") - void updateUserProfile_Fail_UserNotFound() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์ˆ˜์ • ์‹คํŒจ - ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ") + void updateAccountProfile_Fail_UserNotFound() { // Given - UpdateUserProfileRequest request = new UpdateUserProfileRequest("์ƒˆ๋‹‰๋„ค์ž„", "์ƒˆ ์ž๊ธฐ์†Œ๊ฐœ"); + UpdateAccountProfileRequest request = new UpdateAccountProfileRequest("์ƒˆ๋‹‰๋„ค์ž„", "์ƒˆ ์ž๊ธฐ์†Œ๊ฐœ"); given(userRepository.findById(userId)).willReturn(Optional.empty()); // When & Then - assertThatThrownBy(() -> userCommandService.updateUserProfile(userId, request)) + assertThatThrownBy(() -> userCommandService.updateAccountProfile(userId, request)) .isInstanceOf(GeneralException.class) .extracting(ex -> ((GeneralException) ex).getCode()) .isEqualTo(UserErrorCode.USER_NOT_FOUND); @@ -302,7 +302,7 @@ void withdrawUser_Success() { userCommandService.withdrawUser(userId); // Then - assertThat(testUser.getStatus()).isEqualTo(com.techfork.domain.user.enums.UserStatus.WITHDRAWN); + assertThat(testUser.getStatus()).isEqualTo(com.techfork.domain.useraccount.enums.UserStatus.WITHDRAWN); assertThat(testUser.isWithdrawn()).isTrue(); // ๊ฐœ์ธ์ •๋ณด ์ต๋ช…ํ™” ํ™•์ธ diff --git a/src/test/java/com/techfork/domain/user/service/UserQueryServiceTest.java b/src/test/java/com/techfork/domain/useraccount/service/UserQueryServiceTest.java similarity index 69% rename from src/test/java/com/techfork/domain/user/service/UserQueryServiceTest.java rename to src/test/java/com/techfork/domain/useraccount/service/UserQueryServiceTest.java index 76bebea1..6ca35e73 100644 --- a/src/test/java/com/techfork/domain/user/service/UserQueryServiceTest.java +++ b/src/test/java/com/techfork/domain/useraccount/service/UserQueryServiceTest.java @@ -1,11 +1,11 @@ -package com.techfork.domain.user.service; - -import com.techfork.domain.user.converter.UserConverter; -import com.techfork.domain.user.dto.UserProfileResponse; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.exception.UserErrorCode; -import com.techfork.domain.user.repository.UserRepository; +package com.techfork.domain.useraccount.service; + +import com.techfork.domain.useraccount.converter.UserConverter; +import com.techfork.domain.useraccount.dto.AccountProfileResponse; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.exception.UserErrorCode; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.exception.GeneralException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -47,10 +47,10 @@ void setUp() { } @Test - @DisplayName("ํ”„๋กœํ•„ ์กฐํšŒ ์„ฑ๊ณต") - void getUserProfile_Success() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์กฐํšŒ ์„ฑ๊ณต") + void getAccountProfile_Success() { // Given - UserProfileResponse expectedResponse = UserProfileResponse.builder() + AccountProfileResponse expectedResponse = AccountProfileResponse.builder() .profileImage("profile.jpg") .nickName("ํ…Œ์ŠคํŠธ์œ ์ €") .email("test@example.com") @@ -58,10 +58,10 @@ void getUserProfile_Success() { .build(); given(userRepository.findById(userId)).willReturn(Optional.of(testUser)); - given(userConverter.toUserProfileResponse(testUser)).willReturn(expectedResponse); + given(userConverter.toAccountProfileResponse(testUser)).willReturn(expectedResponse); // When - UserProfileResponse result = userQueryService.getUserProfile(userId); + AccountProfileResponse result = userQueryService.getAccountProfile(userId); // Then assertThat(result).isNotNull(); @@ -71,17 +71,17 @@ void getUserProfile_Success() { assertThat(result.description()).isEqualTo("๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค."); verify(userRepository).findById(userId); - verify(userConverter).toUserProfileResponse(testUser); + verify(userConverter).toAccountProfileResponse(testUser); } @Test - @DisplayName("ํ”„๋กœํ•„ ์กฐํšŒ ์‹คํŒจ - ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ") - void getUserProfile_Fail_UserNotFound() { + @DisplayName("๊ณ„์ • ํ”„๋กœํ•„ ์กฐํšŒ ์‹คํŒจ - ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ") + void getAccountProfile_Fail_UserNotFound() { // Given given(userRepository.findById(userId)).willReturn(Optional.empty()); // When & Then - assertThatThrownBy(() -> userQueryService.getUserProfile(userId)) + assertThatThrownBy(() -> userQueryService.getAccountProfile(userId)) .isInstanceOf(GeneralException.class) .extracting(ex -> ((GeneralException) ex).getCode()) .isEqualTo(UserErrorCode.USER_NOT_FOUND); diff --git a/src/test/java/com/techfork/evaluation/recommendation/KValueComparisonTest.java b/src/test/java/com/techfork/evaluation/recommendation/KValueComparisonTest.java index 6e504d31..2b047ee4 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/KValueComparisonTest.java +++ b/src/test/java/com/techfork/evaluation/recommendation/KValueComparisonTest.java @@ -1,7 +1,7 @@ package com.techfork.evaluation.recommendation; import com.techfork.domain.recommendation.config.RecommendationProperties; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import lombok.Builder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; diff --git a/src/test/java/com/techfork/evaluation/recommendation/LambdaOptimizationTest.java b/src/test/java/com/techfork/evaluation/recommendation/LambdaOptimizationTest.java index c236ede7..1287ba1a 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/LambdaOptimizationTest.java +++ b/src/test/java/com/techfork/evaluation/recommendation/LambdaOptimizationTest.java @@ -1,7 +1,7 @@ package com.techfork.evaluation.recommendation; import com.techfork.domain.recommendation.config.RecommendationProperties; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; diff --git a/src/test/java/com/techfork/evaluation/recommendation/MmrCandidateSizeComparisonTest.java b/src/test/java/com/techfork/evaluation/recommendation/MmrCandidateSizeComparisonTest.java index c1565a41..0022ecf8 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/MmrCandidateSizeComparisonTest.java +++ b/src/test/java/com/techfork/evaluation/recommendation/MmrCandidateSizeComparisonTest.java @@ -1,7 +1,7 @@ package com.techfork.evaluation.recommendation; import com.techfork.domain.recommendation.config.RecommendationProperties; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import lombok.Builder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; diff --git a/src/test/java/com/techfork/evaluation/recommendation/RecommendationConfigComparisonTest.java b/src/test/java/com/techfork/evaluation/recommendation/RecommendationConfigComparisonTest.java index f6e3d38c..044313b0 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/RecommendationConfigComparisonTest.java +++ b/src/test/java/com/techfork/evaluation/recommendation/RecommendationConfigComparisonTest.java @@ -1,6 +1,6 @@ package com.techfork.evaluation.recommendation; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; diff --git a/src/test/java/com/techfork/evaluation/recommendation/RecommendationEvaluationService.java b/src/test/java/com/techfork/evaluation/recommendation/RecommendationEvaluationService.java index 11506b59..99fad501 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/RecommendationEvaluationService.java +++ b/src/test/java/com/techfork/evaluation/recommendation/RecommendationEvaluationService.java @@ -17,9 +17,9 @@ import com.techfork.domain.recommendation.service.MmrService.MmrCandidate; import com.techfork.domain.recommendation.service.MmrService.MmrResult; import com.techfork.global.util.RrfScorer; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; import com.techfork.global.elasticsearch.query.VectorQueryBuilder; import com.techfork.global.util.TimeDecayStrategy; import lombok.extern.slf4j.Slf4j; @@ -39,7 +39,7 @@ @Service public class RecommendationEvaluationService extends LlmRecommendationService { - private final UserProfileDocumentRepository userProfileDocumentRepository; + private final PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; private final VectorQueryBuilder vectorQueryBuilder; private final ElasticsearchClient elasticsearchClient; @@ -50,7 +50,7 @@ public class RecommendationEvaluationService extends LlmRecommendationService { public RecommendationEvaluationService( ElasticsearchClient elasticsearchClient, - UserProfileDocumentRepository userProfileDocumentRepository, + PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository, RecommendedPostRepository recommendedPostRepository, RecommendationHistoryRepository recommendationHistoryRepository, ReadPostRepository readPostRepository, @@ -60,12 +60,12 @@ public RecommendationEvaluationService( RecommendationProperties properties, VectorQueryBuilder vectorQueryBuilder ) { - super(elasticsearchClient, userProfileDocumentRepository, recommendedPostRepository, + super(elasticsearchClient, personalizationProfileDocumentRepository, recommendedPostRepository, recommendationHistoryRepository, readPostRepository, postRepository, mmrService, timeDecayStrategy, properties, vectorQueryBuilder, Executors.newSingleThreadExecutor()); this.elasticsearchClient = elasticsearchClient; - this.userProfileDocumentRepository = userProfileDocumentRepository; + this.personalizationProfileDocumentRepository = personalizationProfileDocumentRepository; this.vectorQueryBuilder = vectorQueryBuilder; } @@ -75,12 +75,12 @@ public RecommendationEvaluationService( public List generateRecommendationsForEvaluation(User user, Set trainPostIds, RecommendationProperties properties) { long totalStartTime = System.currentTimeMillis(); - Optional profileOpt = userProfileDocumentRepository.findByUserId(user.getId()); + Optional profileOpt = personalizationProfileDocumentRepository.findByUserId(user.getId()); if (profileOpt.isEmpty() || profileOpt.get().getProfileVector() == null) { return Collections.emptyList(); } - UserProfileDocument profile = profileOpt.get(); + PersonalizationProfileDocument profile = profileOpt.get(); float[] userProfileVector = profile.getProfileVector(); List keyKeywords = profile.getKeyKeywords(); @@ -117,12 +117,12 @@ public List generateRecommendationsForEvaluation(User user, Set trai * 1์ฐจ ํ›„๋ณด๊ตฐ๋งŒ ๋ฐ˜ํ™˜ (MMR bypass) - RRF ๊ฒฐ๊ณผ๋ฅผ similarityScore ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ๋ฐ˜ํ™˜ */ public List generateCandidatesOnly(User user, Set trainPostIds, RecommendationProperties properties) { - Optional profileOpt = userProfileDocumentRepository.findByUserId(user.getId()); + Optional profileOpt = personalizationProfileDocumentRepository.findByUserId(user.getId()); if (profileOpt.isEmpty() || profileOpt.get().getProfileVector() == null) { return Collections.emptyList(); } - UserProfileDocument profile = profileOpt.get(); + PersonalizationProfileDocument profile = profileOpt.get(); float[] userProfileVector = profile.getProfileVector(); List keyKeywords = profile.getKeyKeywords(); diff --git a/src/test/java/com/techfork/evaluation/recommendation/RecommendationTestBase.java b/src/test/java/com/techfork/evaluation/recommendation/RecommendationTestBase.java index af42acf4..9a852789 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/RecommendationTestBase.java +++ b/src/test/java/com/techfork/evaluation/recommendation/RecommendationTestBase.java @@ -5,7 +5,7 @@ import com.techfork.domain.post.repository.PostDocumentRepository; import com.techfork.domain.recommendation.config.RecommendationProperties; import com.techfork.evaluation.recommendation.util.EvaluationFixtureLoader; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.config.ElasticsearchCacheManager; import com.techfork.global.util.VectorUtil; @@ -51,7 +51,7 @@ public abstract class RecommendationTestBase extends IntegrationTestBase { @Autowired protected RecommendationEvaluationService evaluationService; @Autowired protected PostDocumentRepository postDocumentRepository; @Autowired protected ReadPostRepository readPostRepository; - @Autowired protected com.techfork.domain.user.repository.UserRepository userRepository; + @Autowired protected com.techfork.domain.useraccount.repository.UserRepository userRepository; @Autowired protected ElasticsearchClient elasticsearchClient; @Autowired protected ElasticsearchCacheManager elasticsearchCacheManager; diff --git a/src/test/java/com/techfork/evaluation/recommendation/TitleSummaryRatioOptimizationTest.java b/src/test/java/com/techfork/evaluation/recommendation/TitleSummaryRatioOptimizationTest.java index a6470a82..22a03e5b 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/TitleSummaryRatioOptimizationTest.java +++ b/src/test/java/com/techfork/evaluation/recommendation/TitleSummaryRatioOptimizationTest.java @@ -1,6 +1,6 @@ package com.techfork.evaluation.recommendation; -import com.techfork.domain.user.entity.User; +import com.techfork.domain.useraccount.entity.User; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; diff --git a/src/test/java/com/techfork/evaluation/recommendation/setup/UserDataSetupAndExporter.java b/src/test/java/com/techfork/evaluation/recommendation/setup/UserDataSetupAndExporter.java index 5957c1b9..926d1363 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/setup/UserDataSetupAndExporter.java +++ b/src/test/java/com/techfork/evaluation/recommendation/setup/UserDataSetupAndExporter.java @@ -7,11 +7,11 @@ import com.techfork.evaluation.recommendation.setup.components.TestDataGenerator; import com.techfork.evaluation.recommendation.setup.components.TestDataGenerator.UserCreationResult; import com.techfork.evaluation.recommendation.util.EvaluationFixtureLoader; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.*; @@ -44,7 +44,7 @@ public class UserDataSetupAndExporter extends IntegrationTestBase { private ReadPostRepository readPostRepository; @Autowired - private UserProfileDocumentRepository userProfileDocumentRepository; + private PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; @Autowired private TestDataGenerator testDataGenerator; @@ -141,7 +141,7 @@ void step2_CreateTestUsers() throws IOException { log.info("์ด ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž: {} ๋ช…", users.size()); long profileCount = users.stream() - .filter(u -> userProfileDocumentRepository.findByUserId(u.getId()).isPresent()) + .filter(u -> personalizationProfileDocumentRepository.findByUserId(u.getId()).isPresent()) .count(); log.info("UserProfile(์ž„๋ฒ ๋”ฉ) ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž: {} ๋ช…", profileCount); @@ -179,8 +179,8 @@ void step3_ExportUserData() throws IOException { List readPosts = exportReadPosts(users); log.info("โœ“ ์ฝ์€ ๊ธ€ ์ด๋ ฅ {} ๊ฐœ export ์™„๋ฃŒ", readPosts.size()); - List userProfiles = exportUserProfiles(users); - log.info("โœ“ UserProfileDocument {} ๊ฐœ export ์™„๋ฃŒ (์ž„๋ฒ ๋”ฉ ํฌํ•จ)", userProfiles.size()); + List userProfiles = exportUserProfiles(users); + log.info("โœ“ PersonalizationProfileDocument {} ๊ฐœ export ์™„๋ฃŒ (์ž„๋ฒ ๋”ฉ ํฌํ•จ)", userProfiles.size()); log.info("===== STEP 3 ์™„๋ฃŒ ====="); log.info("์ถœ๋ ฅ ์œ„์น˜: {}", fileExporter.getOutputDir()); @@ -225,24 +225,24 @@ private List exportReadPosts(List users) throws IOException { return allReadPosts; } - private List exportUserProfiles(List users) throws IOException { - List profiles = new ArrayList<>(); + private List exportUserProfiles(List users) throws IOException { + List profiles = new ArrayList<>(); int notFoundCount = 0; for (User user : users) { - Optional profileOpt = - userProfileDocumentRepository.findByUserId(user.getId()); + Optional profileOpt = + personalizationProfileDocumentRepository.findByUserId(user.getId()); if (profileOpt.isPresent()) { profiles.add(profileOpt.get()); } else { notFoundCount++; - log.warn("UserProfileDocument not found for userId: {}", user.getId()); + log.warn("PersonalizationProfileDocument not found for userId: {}", user.getId()); } } if (notFoundCount > 0) { - log.warn("์ด {} ๋ช…์˜ UserProfileDocument๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.", notFoundCount); + log.warn("์ด {} ๋ช…์˜ PersonalizationProfileDocument๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.", notFoundCount); } // DTO ๋ณ€ํ™˜ (profileVector๋Š” float[]์ด๋ฏ€๋กœ List๋กœ ๋ณ€ํ™˜) @@ -254,7 +254,7 @@ private List exportUserProfiles(List users) throws IO // ์ž„๋ฒ ๋”ฉ ์ฐจ์› ๊ฒ€์ฆ if (!profiles.isEmpty()) { - UserProfileDocument sample = profiles.get(0); + PersonalizationProfileDocument sample = profiles.get(0); log.info("์ž„๋ฒ ๋”ฉ ์ฐจ์› ๊ฒ€์ฆ:"); log.info(" - profileVector: {} ์ฐจ์›", sample.getProfileVector() != null ? sample.getProfileVector().length : "null"); @@ -311,7 +311,7 @@ private Map convertReadPostToDto(ReadPost readPost) { return dto; } - private Map convertUserProfileToDto(UserProfileDocument profile) { + private Map convertUserProfileToDto(PersonalizationProfileDocument profile) { Map dto = new HashMap<>(); dto.put("id", profile.getId()); dto.put("userId", profile.getUserId()); diff --git a/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthGenerator.java b/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthGenerator.java index fe2af6e4..d9ecbeb2 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthGenerator.java +++ b/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthGenerator.java @@ -3,7 +3,7 @@ import com.techfork.domain.post.document.PostDocument; import com.techfork.domain.post.entity.Post; import com.techfork.domain.post.repository.PostDocumentRepository; -import com.techfork.domain.user.document.UserProfileDocument; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; import com.techfork.global.llm.LlmClient; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,7 +37,7 @@ public class GroundTruthGenerator { */ public Map calculateGroundTruth( List posts, - UserProfileDocument userProfile) { + PersonalizationProfileDocument userProfile) { Map groundTruthScores = new HashMap<>(); int count = 0; @@ -62,7 +62,7 @@ public Map calculateGroundTruth( /** * LLM์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒŒ์‹œ๊ธ€์˜ ๊ด€๋ จ๋„ ์ ์ˆ˜ ํ‰๊ฐ€ (1~5์ ) */ - private int calculateRelevanceScoreWithLLM(Post post, UserProfileDocument userProfile) { + private int calculateRelevanceScoreWithLLM(Post post, PersonalizationProfileDocument userProfile) { // PostDocument์—์„œ ๋” ํ’๋ถ€ํ•œ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (์š”์•ฝ๋ฌธ, ๋ณธ๋ฌธ ์ฒญํฌ ๋“ฑ) Optional postDocOpt = postDocumentRepository.findByPostId(post.getId()); diff --git a/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthValidator.java b/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthValidator.java index e64ffba8..fa7682dc 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthValidator.java +++ b/src/test/java/com/techfork/evaluation/recommendation/setup/components/GroundTruthValidator.java @@ -1,6 +1,6 @@ package com.techfork.evaluation.recommendation.setup.components; -import com.techfork.domain.user.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestCategory; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/src/test/java/com/techfork/evaluation/recommendation/setup/components/PostMatcher.java b/src/test/java/com/techfork/evaluation/recommendation/setup/components/PostMatcher.java index 0976a7fc..ec79ace5 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/setup/components/PostMatcher.java +++ b/src/test/java/com/techfork/evaluation/recommendation/setup/components/PostMatcher.java @@ -2,7 +2,7 @@ import com.techfork.domain.post.entity.Post; import com.techfork.domain.post.repository.PostRepository; -import com.techfork.domain.user.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestCategory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/src/test/java/com/techfork/evaluation/recommendation/setup/components/TestDataGenerator.java b/src/test/java/com/techfork/evaluation/recommendation/setup/components/TestDataGenerator.java index 3d519b5a..624b1a63 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/setup/components/TestDataGenerator.java +++ b/src/test/java/com/techfork/evaluation/recommendation/setup/components/TestDataGenerator.java @@ -2,11 +2,11 @@ import com.techfork.domain.post.entity.Post; import com.techfork.domain.post.repository.PostRepository; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; -import com.techfork.domain.user.service.UserProfileService; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; +import com.techfork.domain.personalization.service.PersonalizationProfileService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Disabled; @@ -33,8 +33,8 @@ public record UserCreationResult( ) {} private final PostRepository postRepository; - private final UserProfileService userProfileService; - private final UserProfileDocumentRepository userProfileDocumentRepository; + private final PersonalizationProfileService personalizationProfileService; + private final PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; private final org.springframework.data.elasticsearch.core.ElasticsearchOperations elasticsearchOperations; // ๋ถ„๋ฆฌ๋œ ์ปดํฌ๋„ŒํŠธ @@ -123,14 +123,14 @@ public UserCreationResult createTestUserWithGroundTruth( // UserProfile ์ƒ์„ฑ (์ž„๋ฒ ๋”ฉ ํฌํ•จ) - ๋™๊ธฐ ๋ฒ„์ „ ์‚ฌ์šฉ // Ground Truth ์ ์ˆ˜ ๊ณ„์‚ฐ ์ „์— ํ”„๋กœํ•„ ๋ฒกํ„ฐ๊ฐ€ ํ•„์š”ํ•จ - UserProfileDocument userProfile = null; + PersonalizationProfileDocument userProfile = null; try { - userProfileService.generateUserProfileSync(user.getId()); + personalizationProfileService.generatePersonalizationProfileSync(user.getId()); // Elasticsearch Refresh: ์ €์žฅ์ด ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•ด์ง€๋„๋ก ๊ฐ•์ œ ๊ฐฑ์‹  - elasticsearchOperations.indexOps(UserProfileDocument.class).refresh(); + elasticsearchOperations.indexOps(PersonalizationProfileDocument.class).refresh(); - Optional userProfileOpt = userProfileDocumentRepository.findByUserId(user.getId()); + Optional userProfileOpt = personalizationProfileDocumentRepository.findByUserId(user.getId()); if (userProfileOpt.isPresent()) { userProfile = userProfileOpt.get(); log.info("์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ฐ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ ์™„๋ฃŒ: userId={}", user.getId()); diff --git a/src/test/java/com/techfork/evaluation/recommendation/setup/components/UserTestDataBuilder.java b/src/test/java/com/techfork/evaluation/recommendation/setup/components/UserTestDataBuilder.java index b2444835..72214f86 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/setup/components/UserTestDataBuilder.java +++ b/src/test/java/com/techfork/evaluation/recommendation/setup/components/UserTestDataBuilder.java @@ -7,14 +7,14 @@ import com.techfork.domain.activity.repository.BookmarkRepository; import com.techfork.domain.activity.repository.SearchHistoryRepository; import com.techfork.domain.post.entity.Post; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.entity.UserInterestKeyword; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.EInterestKeyword; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserInterestCategoryRepository; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.entity.UserInterestKeyword; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserInterestCategoryRepository; +import com.techfork.domain.useraccount.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/src/test/java/com/techfork/evaluation/recommendation/util/EvaluationFixtureLoader.java b/src/test/java/com/techfork/evaluation/recommendation/util/EvaluationFixtureLoader.java index 5d92befb..9d875848 100644 --- a/src/test/java/com/techfork/evaluation/recommendation/util/EvaluationFixtureLoader.java +++ b/src/test/java/com/techfork/evaluation/recommendation/util/EvaluationFixtureLoader.java @@ -12,16 +12,16 @@ import com.techfork.domain.post.repository.PostRepository; import com.techfork.domain.source.entity.TechBlog; import com.techfork.domain.source.repository.TechBlogRepository; -import com.techfork.domain.user.document.UserProfileDocument; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.entity.UserInterestKeyword; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.EInterestKeyword; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.personalization.document.PersonalizationProfileDocument; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.entity.UserInterestKeyword; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestKeyword; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; +import com.techfork.domain.useraccount.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; @@ -47,7 +47,7 @@ public class EvaluationFixtureLoader { private final PostRepository postRepository; private final ReadPostRepository readPostRepository; private final PostDocumentRepository postDocumentRepository; - private final UserProfileDocumentRepository userProfileDocumentRepository; + private final PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; private final TechBlogRepository techBlogRepository; private final ObjectMapper objectMapper = new ObjectMapper() @@ -73,7 +73,7 @@ public Map> loadAll() { log.info("โœ“ PostDocument {} ๊ฐœ ๋กœ๋“œ ์™„๋ฃŒ (์ž„๋ฒ ๋”ฉ ํฌํ•จ)", postDocCount); int userProfileCount = loadUserProfiles(userMap); - log.info("โœ“ UserProfileDocument {} ๊ฐœ ๋กœ๋“œ ์™„๋ฃŒ (์ž„๋ฒ ๋”ฉ ํฌํ•จ)", userProfileCount); + log.info("โœ“ PersonalizationProfileDocument {} ๊ฐœ ๋กœ๋“œ ์™„๋ฃŒ (์ž„๋ฒ ๋”ฉ ํฌํ•จ)", userProfileCount); Map> groundTruth = loadGroundTruth(userMap, postMap); log.info("โœ“ Ground Truth {} ๋ช… ์‚ฌ์šฉ์ž ๋กœ๋“œ ์™„๋ฃŒ", groundTruth.size()); @@ -355,7 +355,7 @@ private int loadUserProfiles(Map userMap) throws IOException { } } - UserProfileDocument profile = UserProfileDocument.builder() + PersonalizationProfileDocument profile = PersonalizationProfileDocument.builder() .userId(actualUserId) .profileText(profileText) .profileVector(profileVector) @@ -363,7 +363,7 @@ private int loadUserProfiles(Map userMap) throws IOException { .keyKeywords(keyKeywords) .build(); - userProfileDocumentRepository.save(profile); + personalizationProfileDocumentRepository.save(profile); count++; } diff --git a/src/test/java/com/techfork/evaluation/search/SearchEvaluationTestBase.java b/src/test/java/com/techfork/evaluation/search/SearchEvaluationTestBase.java index 6d670a68..a5223468 100644 --- a/src/test/java/com/techfork/evaluation/search/SearchEvaluationTestBase.java +++ b/src/test/java/com/techfork/evaluation/search/SearchEvaluationTestBase.java @@ -11,7 +11,7 @@ import com.techfork.domain.search.config.GeneralSearchProperties; import com.techfork.domain.search.service.SearchService; import com.techfork.domain.search.service.SearchServiceImpl; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; import com.techfork.evaluation.search.util.GroundTruthItem; import com.techfork.evaluation.search.util.SearchQualityService; import com.techfork.global.config.CloudflareThirdPartyThumbnailOptimizationProperties; @@ -48,7 +48,7 @@ public abstract class SearchEvaluationTestBase { @Autowired protected ElasticsearchClient elasticsearchClient; @Autowired protected EmbeddingClient embeddingClient; - @Autowired protected UserProfileDocumentRepository userProfileDocumentRepository; + @Autowired protected PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; @Autowired protected PostRepository postRepository; @Autowired protected BookmarkRepository bookmarkRepository; @Autowired @Qualifier("searchAsyncExecutor") protected Executor searchAsyncExecutor; @@ -109,7 +109,7 @@ protected Map runEvaluation( SearchService svc = new SearchServiceImpl( elasticsearchClient, embeddingClient, props, - userProfileDocumentRepository, postRepository, + personalizationProfileDocumentRepository, postRepository, bookmarkRepository, searchAsyncExecutor, thumbnailOptimizer); // index: [nDCG@4, nDCG@8, nDCG@20, Recall@4, Recall@8, Recall@20, latency] diff --git a/src/test/java/com/techfork/evaluation/search/setup/UserProfileServiceTest.java b/src/test/java/com/techfork/evaluation/search/setup/PersonalizationProfileServiceTest.java similarity index 76% rename from src/test/java/com/techfork/evaluation/search/setup/UserProfileServiceTest.java rename to src/test/java/com/techfork/evaluation/search/setup/PersonalizationProfileServiceTest.java index 3003509d..9ece5d1f 100644 --- a/src/test/java/com/techfork/evaluation/search/setup/UserProfileServiceTest.java +++ b/src/test/java/com/techfork/evaluation/search/setup/PersonalizationProfileServiceTest.java @@ -1,12 +1,12 @@ package com.techfork.evaluation.search.setup; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.entity.UserInterestCategory; -import com.techfork.domain.user.enums.EInterestCategory; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserInterestCategoryRepository; -import com.techfork.domain.user.repository.UserRepository; -import com.techfork.domain.user.service.UserProfileService; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.entity.UserInterestCategory; +import com.techfork.domain.useraccount.enums.EInterestCategory; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserInterestCategoryRepository; +import com.techfork.domain.useraccount.repository.UserRepository; +import com.techfork.domain.personalization.service.PersonalizationProfileService; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -20,12 +20,12 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static com.techfork.domain.user.enums.EInterestCategory.*; +import static com.techfork.domain.useraccount.enums.EInterestCategory.*; @Tag("evaluation-setup") @Disabled("๋ฐ์ดํ„ฐ ์…‹์—…์šฉ - CI ์ œ์™ธ") @SpringBootTest -class UserProfileServiceTest { +class PersonalizationProfileServiceTest { @Autowired private UserRepository userRepository; @@ -34,7 +34,7 @@ class UserProfileServiceTest { private UserInterestCategoryRepository userInterestCategoryRepository; @Autowired - private UserProfileService userProfileService; + private PersonalizationProfileService personalizationProfileService; @Test @DisplayName("10๋ช…์˜ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๊ฐ€์ง„ ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ”„๋กœํ•„ ๋ฒกํ„ฐ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.") @@ -68,7 +68,7 @@ void generateTestUserProfiles() { userInterestCategoryRepository.saveAll(interestCategories); System.out.println("Generating profile for user: " + user.getId()); - userProfileService.generateUserProfile(user.getId()); + personalizationProfileService.generatePersonalizationProfile(user.getId()); System.out.println("Profile generated for user: " + user.getId()); }); } diff --git a/src/test/java/com/techfork/evaluation/search/setup/SearchGroundTruthGenerator.java b/src/test/java/com/techfork/evaluation/search/setup/SearchGroundTruthGenerator.java index a8f517dd..8a85db64 100644 --- a/src/test/java/com/techfork/evaluation/search/setup/SearchGroundTruthGenerator.java +++ b/src/test/java/com/techfork/evaluation/search/setup/SearchGroundTruthGenerator.java @@ -8,7 +8,7 @@ import com.techfork.domain.search.dto.SearchResult; import com.techfork.domain.search.config.GeneralSearchProperties; import com.techfork.domain.search.service.SearchServiceImpl; -import com.techfork.domain.user.repository.UserProfileDocumentRepository; +import com.techfork.domain.personalization.repository.PersonalizationProfileDocumentRepository; import com.techfork.evaluation.recommendation.setup.components.FileExporter; import com.techfork.evaluation.search.util.GroundTruthItem; import com.techfork.global.config.CloudflareThirdPartyThumbnailOptimizationProperties; @@ -102,7 +102,7 @@ class SearchGroundTruthGenerator { private GeneralSearchProperties generalSearchProperties; @Autowired - private UserProfileDocumentRepository userProfileDocumentRepository; + private PersonalizationProfileDocumentRepository personalizationProfileDocumentRepository; @Autowired private PostRepository postRepository; @@ -145,7 +145,7 @@ void generateSearchGroundTruth() throws IOException { ); SearchServiceImpl searchService = new SearchServiceImpl( elasticsearchClient, embeddingClient, generalSearchProperties, - userProfileDocumentRepository, postRepository, bookmarkRepository, searchAsyncExecutor, thumbnailOptimizer); + personalizationProfileDocumentRepository, postRepository, bookmarkRepository, searchAsyncExecutor, thumbnailOptimizer); List groundTruthItems = scoreAllQueries(uniqueQueryMap, searchService); log.info("์ตœ์ข… ground-truth ํ•ญ๋ชฉ ์ˆ˜: {}", groundTruthItems.size()); diff --git a/src/test/java/com/techfork/global/security/SecurityIntegrationTest.java b/src/test/java/com/techfork/global/security/SecurityIntegrationTest.java index 4af1d876..4cd61cd9 100644 --- a/src/test/java/com/techfork/global/security/SecurityIntegrationTest.java +++ b/src/test/java/com/techfork/global/security/SecurityIntegrationTest.java @@ -1,9 +1,9 @@ package com.techfork.global.security; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.common.IntegrationTestBase; import com.techfork.global.llm.EmbeddingClient; import com.techfork.global.llm.LlmClient; diff --git a/src/test/java/com/techfork/global/security/auth/service/UserAuthCacheServiceTest.java b/src/test/java/com/techfork/global/security/auth/service/UserAuthCacheServiceTest.java index 391b56b4..566366b1 100644 --- a/src/test/java/com/techfork/global/security/auth/service/UserAuthCacheServiceTest.java +++ b/src/test/java/com/techfork/global/security/auth/service/UserAuthCacheServiceTest.java @@ -1,9 +1,9 @@ package com.techfork.global.security.auth.service; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.enums.UserStatus; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.enums.UserStatus; import com.techfork.global.security.oauth.UserPrincipal; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/techfork/global/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/techfork/global/security/filter/JwtAuthenticationFilterTest.java index f246d4fd..375148d2 100644 --- a/src/test/java/com/techfork/global/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/techfork/global/security/filter/JwtAuthenticationFilterTest.java @@ -1,10 +1,10 @@ package com.techfork.global.security.filter; -import com.techfork.domain.user.entity.User; -import com.techfork.domain.user.enums.Role; -import com.techfork.domain.user.enums.SocialType; -import com.techfork.domain.user.enums.UserStatus; -import com.techfork.domain.user.repository.UserRepository; +import com.techfork.domain.useraccount.entity.User; +import com.techfork.domain.useraccount.enums.Role; +import com.techfork.domain.useraccount.enums.SocialType; +import com.techfork.domain.useraccount.enums.UserStatus; +import com.techfork.domain.useraccount.repository.UserRepository; import com.techfork.global.security.auth.service.UserAuthCacheService; import com.techfork.global.security.jwt.JwtProperties; import com.techfork.global.security.jwt.JwtUtil;