From 3598b347141c4f08efe08272ef05d9a02e4ab043 Mon Sep 17 00:00:00 2001 From: guswlwlgus <20220784@duksung.ac.kr> Date: Wed, 18 Feb 2026 18:10:29 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[hotfix]:=20=EA=B2=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=EB=82=B1=EB=A7=90=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B8=B0=EB=B3=B8=20=EB=82=B1=EB=A7=90=EB=A7=8C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20accountType=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/words/controllers/words.controller.js | 2 ++ src/words/services/words.service.js | 33 ++++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/words/controllers/words.controller.js b/src/words/controllers/words.controller.js index d7f3a6c..b369392 100644 --- a/src/words/controllers/words.controller.js +++ b/src/words/controllers/words.controller.js @@ -11,6 +11,7 @@ export class WordsController { async getWords(req, res, next) { try { const userId = req.user?.userId; + const accountType = req.user?.accountType; const queryDto = new GetWordsQueryDto({ categoryId: req.query.categoryId, @@ -19,6 +20,7 @@ export class WordsController { const result = await wordsService.getWords( userId, + accountType, queryDto.categoryId, queryDto.onlyFavorite ); diff --git a/src/words/services/words.service.js b/src/words/services/words.service.js index 9ce6e87..e141f82 100644 --- a/src/words/services/words.service.js +++ b/src/words/services/words.service.js @@ -9,13 +9,13 @@ export class WordsService { /** * 내부용: 전체 낱말 카드 목록 생성 (categoryId 없이) */ - async _getAllWordCards(userId, onlyFavorite = false) { + async _getAllWordCards(userId, accountType, onlyFavorite = false) { const wordCards = []; let categoryNameToUserCategoryIdMap = new Map(); let categoryNameToCategoryIdMap = new Map(); let hasUserCategories = false; - if (userId) { + if (userId && accountType !== 'GUEST') { const userCategories = await wordsRepository.findAllUserCategories(userId); if (userCategories.length > 0) { hasUserCategories = true; @@ -34,7 +34,7 @@ export class WordsService { let userWords = []; const userWordMap = new Map(); const deletedWordIds = new Set(); - if (userId) { + if (userId && accountType !== 'GUEST') { userWords = await wordsRepository.findUserWords(userId, null, onlyFavorite, true); userWords.forEach(uw => { if (uw.wordId) userWordMap.set(uw.wordId, uw); @@ -45,8 +45,8 @@ export class WordsService { } // 소셜 로그인(유저가 존재) 시에는 기본 Word를 반환하지 않고 UserWord만 반환 - // 게스트(유저 없음)일 때만 기본 Word 반환 - if (!onlyFavorite && !userId) { + // 게스트(유저 없음 또는 accountType이 GUEST)일 때 기본 Word 반환 + if (!onlyFavorite && (!userId || accountType === 'GUEST')) { const words = await wordsRepository.findWords(null, userId); words.forEach((word, index) => { const wordCategoryName = word.category?.categoryName; @@ -101,20 +101,20 @@ export class WordsService { /** * 다음 displayOrder 계산 (기본 Word + UserWord 모두 고려) * @param {string} userId + * @param {string} accountType * @param {string} categoryId * @returns {Promise} */ - async getNextDisplayOrder(userId, categoryId) { + async getNextDisplayOrder(userId, accountType, categoryId) { // 1. UserWord 조회 (삭제된 것 제외) - const userWords = await wordsRepository.findUserWords(userId, categoryId, false, false); - - // 2. UserWord가 있으면 최대값 + 1 반환 - if (userWords.length > 0) { - const maxOrder = Math.max(...userWords.map(w => w.displayOrder)); - return maxOrder + 1; + if (accountType !== 'GUEST') { + const userWords = await wordsRepository.findUserWords(userId, categoryId, false, false); + if (userWords.length > 0) { + const maxOrder = Math.max(...userWords.map(w => w.displayOrder)); + return maxOrder + 1; + } } - - // 3. UserWord가 없으면 기본 Word 개수 반환 + // 게스트이거나 UserWord가 없으면 기본 Word 개수 반환 const words = await wordsRepository.findWords(categoryId, userId); return words.length; } @@ -124,13 +124,14 @@ export class WordsService { * 기본 낱말(Word) + 개인 낱말(UserWord) 통합 반환 * * @param {string} userId + * @param {string} accountType * @param {string|null} categoryId - Category.id 또는 UserCategory.id * @param {boolean} onlyFavorite * @returns {Promise} { category, words } */ - async getWords(userId, categoryId = null, onlyFavorite = false) { + async getWords(userId, accountType, categoryId = null, onlyFavorite = false) { // 1. 전체 낱말 목록 생성 (categoryId 없이) - const allWords = await this._getAllWordCards(userId, onlyFavorite); + const allWords = await this._getAllWordCards(userId, accountType, onlyFavorite); // 2. categoryId가 없으면 전체 반환 if (!categoryId) { From ae9a358e8e2c6e7c38533a31847922f12b500d53 Mon Sep 17 00:00:00 2001 From: guswlwlgus <20220784@duksung.ac.kr> Date: Wed, 18 Feb 2026 20:03:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[fix]:=20=EB=82=B1=EB=A7=90=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=B0=98=EB=B3=B5=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=20=ED=86=B5=EC=9D=BC=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/category/category.service.js | 6 ++---- src/words/routes/words.route.js | 7 ++++--- src/words/services/words.service.js | 15 ++++++--------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/category/category.service.js b/src/category/category.service.js index 46ec638..8a44899 100644 --- a/src/category/category.service.js +++ b/src/category/category.service.js @@ -75,12 +75,10 @@ export const getCategoryListService = async ({ userId }) => { }); categories = await listUserCategories({ userId }); - } - // 기본 UserCategory 생성 후, 각 카테고리별 Word를 UserWord로 복사 + // 기본 UserCategory 생성 직후에만 복사 const wordsRepo = (await import("../words/repositories/words.repository.js")).default; for (const userCategory of categories.filter(c => c.isDefault)) { - // 기본 카테고리 이름으로 Category 찾기 const category = await prisma.category.findFirst({ where: { categoryName: userCategory.categoryName, @@ -88,10 +86,10 @@ export const getCategoryListService = async ({ userId }) => { } }); if (category) { - // 해당 Category의 Word를 UserWord로 복사 await wordsRepo.createSnapshotFromWords(userId, userCategory.id); } } + } return Promise.all( categories.map(async (c) => { diff --git a/src/words/routes/words.route.js b/src/words/routes/words.route.js index 6ad547b..765440e 100644 --- a/src/words/routes/words.route.js +++ b/src/words/routes/words.route.js @@ -16,9 +16,10 @@ const router = express.Router(); * get: * summary: 낱말 카드 조회 * description: | - * 낱말 카드 목록을 조회합니다. - * 토큰이 있으면 사용자별 낱말(즐겨찾기/개인화 포함), 없으면 기본 낱말만 반환합니다. - * categoryId가 있으면 data.category(카테고리 이름)도 함께 반환됩니다. +* 낱말 카드 목록을 조회합니다. +* 토큰이 있으면 게스트/소셜 계정 모두 사용자별 낱말(즐겨찾기/개인화 포함)을 조회합니다. +* 토큰이 없으면 기본 낱말만 반환합니다. +* categoryId가 있으면 data.category(카테고리 이름)도 함께 반환됩니다. * tags: [Words] * parameters: * - in: query diff --git a/src/words/services/words.service.js b/src/words/services/words.service.js index e141f82..0d9870a 100644 --- a/src/words/services/words.service.js +++ b/src/words/services/words.service.js @@ -34,7 +34,7 @@ export class WordsService { let userWords = []; const userWordMap = new Map(); const deletedWordIds = new Set(); - if (userId && accountType !== 'GUEST') { + if (userId) { userWords = await wordsRepository.findUserWords(userId, null, onlyFavorite, true); userWords.forEach(uw => { if (uw.wordId) userWordMap.set(uw.wordId, uw); @@ -44,17 +44,14 @@ export class WordsService { }); } - // 소셜 로그인(유저가 존재) 시에는 기본 Word를 반환하지 않고 UserWord만 반환 - // 게스트(유저 없음 또는 accountType이 GUEST)일 때 기본 Word 반환 - if (!onlyFavorite && (!userId || accountType === 'GUEST')) { - const words = await wordsRepository.findWords(null, userId); + // 소셜/게스트 로그인(유저가 존재) 시에는 UserWord 기반, 비회원(토큰 없음)만 기본 Word 반환 + if (!onlyFavorite && !userId && !accountType) { + const words = await wordsRepository.findWords(); words.forEach((word, index) => { - const wordCategoryName = word.category?.categoryName; - let mappedCategoryId = categoryNameToCategoryIdMap.get(wordCategoryName) || word.categoryId; wordCards.push(new WordCardResponseDto({ cardId: word.id, - categoryId: mappedCategoryId, - categoryName: wordCategoryName, + categoryId: word.categoryId, + categoryName: word.category?.categoryName, partOfSpeech: word.partOfSpeech, word: word.word, imageUrl: word.imageUrl,