diff --git a/docs/history.md b/docs/history.md index 3c5ff83..7441a36 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,8 @@ # 의사결정 이력 +## 2026-04-02 v1.4.11 +- 백엔드 `/api/games` 경로를 바로 바꾸기보다, 프런트 API 객체에서 먼저 `topic/template` 의미 이름을 제공하고 호출부를 옮기는 편이 위험이 훨씬 낮다고 판단했다. + ## 2026-04-02 v1.4.10 - 사용자 주소는 이미 `/topics`로 옮기기 시작했으므로, 라우트 이름과 기본 파라미터도 `topicHub / topicId` 기준으로 맞추고 기존 `gameId`는 호환 fallback으로만 남기는 편이 더 자연스럽다고 판단했다. diff --git a/docs/todo.md b/docs/todo.md index 86a995f..4df14bc 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,8 @@ # 할 일 및 이슈 ## 단기 확인 +- 프런트 API 호출부는 `topic/template` 의미 이름으로 옮겼으므로, 다음 단계에서는 `api.js` 안의 레거시 alias를 얼마나 더 유지할지와 백엔드 API 경로를 실제로 바꿀지 범위를 정한다. +- 관리자 템플릿/주제 화면과 홈·에디터·즐겨찾기에서 새 API 이름층으로 바뀐 뒤에도 저장, 즐겨찾기, 템플릿 생성, 아이템 정렬 흐름이 자연스러운지 한 번 더 QA한다. - `topicHub / topicId`를 기본 라우트 기준으로 세웠으므로, 기존 `/games/...` 북마크와 새 `/topics/...` 주소 양쪽에서 주제 상세와 에디터 진입이 모두 자연스럽게 이어지는지 한 번 더 QA한다. - 다음 단계에서는 `api.getGame`, `listGames`, `favoriteGame`처럼 남아 있는 프런트 API 이름을 어느 수준까지 `topic/template` 의미로 감쌀지 정리한다. - 경로 헬퍼를 사용자 주요 화면에 연결했으므로, 로그인 리다이렉트·공유 프리뷰·복사본 이동·주제 복귀 흐름이 실제 브라우저에서 모두 같은 주소 체계로 자연스럽게 이어지는지 한 번 더 QA한다. diff --git a/docs/update.md b/docs/update.md index 749324a..f9da59b 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,9 @@ # 업데이트 로그 +## 2026-04-02 v1.4.11 +- 프런트 API 이름층을 한 단계 더 정리해 `listTopics / getTopic / favoriteTopic`, `updateAdminTemplate*`, `searchPublicTierListsByTopic` 같은 의미 기반 이름을 추가하고 실제 호출부도 이 기준으로 옮겼다. +- 백엔드 경로와 응답 구조는 그대로 유지한 채 프런트에서 읽는 이름만 먼저 바꿔, 다음 단계의 API/모델 리네이밍 부담을 더 줄였다. + ## 2026-04-02 v1.4.10 - 주제 상세 라우트 이름을 `topicHub`로, 기본 경로 파라미터를 `topicId`로 바꾸고 기존 `gameId` 주소는 alias로 유지했다. - 앱 셸, 주제 상세, 티어표 편집기는 이제 내부에서 `topicId`를 우선 읽고, 레거시 주소로 들어온 경우에만 `gameId` fallback을 쓰도록 정리했다. diff --git a/frontend/src/composables/useAdminCustomItems.js b/frontend/src/composables/useAdminCustomItems.js index dab3682..84ee719 100644 --- a/frontend/src/composables/useAdminCustomItems.js +++ b/frontend/src/composables/useAdminCustomItems.js @@ -167,7 +167,7 @@ export function useAdminCustomItems({ try { item.isPromoting = true - await api.promoteAdminCustomItem(item.id, { gameId: customItemModalTargetTemplateId.value }) + await api.promoteAdminTemplateItem(item.id, { gameId: customItemModalTargetTemplateId.value }) const targetTemplateName = templates.value.find((template) => template.id === customItemModalTargetTemplateId.value)?.name || customItemModalTargetTemplateId.value if (selectedTemplateId.value === customItemModalTargetTemplateId.value) await loadTemplate() diff --git a/frontend/src/composables/useAdminFeaturedGames.js b/frontend/src/composables/useAdminFeaturedGames.js index a470afc..c3a3e31 100644 --- a/frontend/src/composables/useAdminFeaturedGames.js +++ b/frontend/src/composables/useAdminFeaturedGames.js @@ -70,7 +70,7 @@ export function useAdminFeaturedGames({ async function saveFeaturedOrder() { resetMessages() try { - const data = await api.updateAdminGameDisplayOrder({ gameIds: featuredTemplateIds.value }) + const data = await api.updateAdminTemplateDisplayOrder({ gameIds: featuredTemplateIds.value }) templates.value = data.games || [] featuredTemplateIds.value = templates.value .filter((template) => template.displayRank != null) diff --git a/frontend/src/composables/useAdminGameManager.js b/frontend/src/composables/useAdminGameManager.js index d693d06..e24daac 100644 --- a/frontend/src/composables/useAdminGameManager.js +++ b/frontend/src/composables/useAdminGameManager.js @@ -131,7 +131,7 @@ export function useAdminGameManager({ try { isGameLoading.value = true - const data = await api.getGame(selectedTemplateId.value) + const data = await api.getTopic(selectedTemplateId.value) selectedTemplate.value = { ...data, items: (data.items || []).map((item) => ({ @@ -336,7 +336,7 @@ export function useAdminGameManager({ if (!selectedTemplateId.value || !selectedTemplate.value?.items?.length) return try { - const data = await api.updateAdminGameItemDisplayOrder(selectedTemplateId.value, { + const data = await api.updateAdminTemplateItemDisplayOrder(selectedTemplateId.value, { itemIds: selectedTemplate.value.items.map((item) => item.id), }) selectedTemplate.value = { diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index e83caca..023e93c 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -30,17 +30,17 @@ export const api = { login: ({ email, password }) => request('/api/auth/login', { method: 'POST', body: { email, password } }), logout: () => request('/api/auth/logout', { method: 'POST' }), - listGames: () => request('/api/games'), - getGame: (gameId) => request(`/api/games/${encodeURIComponent(gameId)}`), - favoriteGame: (gameId) => request(`/api/games/${encodeURIComponent(gameId)}/favorite`, { method: 'POST' }), - unfavoriteGame: (gameId) => request(`/api/games/${encodeURIComponent(gameId)}/favorite`, { method: 'DELETE' }), - updateAdminGameDisplayOrder: (payload) => request('/api/admin/games/display-order', { method: 'PATCH', body: payload }), - updateAdminGameItemDisplayOrder: (gameId, payload) => - request(`/api/admin/games/${encodeURIComponent(gameId)}/items/display-order`, { method: 'PATCH', body: payload }), - updateAdminGame: (gameId, payload) => - request(`/api/admin/games/${encodeURIComponent(gameId)}`, { method: 'PATCH', body: payload }), - updateAdminGameItem: (gameId, itemId, payload) => - request(`/api/admin/games/${encodeURIComponent(gameId)}/items/${encodeURIComponent(itemId)}`, { method: 'PATCH', body: payload }), + listTopics: () => request('/api/games'), + getTopic: (topicId) => request(`/api/games/${encodeURIComponent(topicId)}`), + favoriteTopic: (topicId) => request(`/api/games/${encodeURIComponent(topicId)}/favorite`, { method: 'POST' }), + unfavoriteTopic: (topicId) => request(`/api/games/${encodeURIComponent(topicId)}/favorite`, { method: 'DELETE' }), + updateAdminTemplateDisplayOrder: (payload) => request('/api/admin/games/display-order', { method: 'PATCH', body: payload }), + updateAdminTemplateItemDisplayOrder: (templateId, payload) => + request(`/api/admin/games/${encodeURIComponent(templateId)}/items/display-order`, { method: 'PATCH', body: payload }), + updateAdminTemplate: (templateId, payload) => + request(`/api/admin/games/${encodeURIComponent(templateId)}`, { method: 'PATCH', body: payload }), + updateAdminTemplateItem: (templateId, itemId, payload) => + request(`/api/admin/games/${encodeURIComponent(templateId)}/items/${encodeURIComponent(itemId)}`, { method: 'PATCH', body: payload }), listAdminCustomItems: ({ q = '', page = 1, limit = 50, filter = 'all' } = {}) => request( `/api/admin/custom-items?q=${encodeURIComponent(q)}&page=${encodeURIComponent(page)}&limit=${encodeURIComponent(limit)}&filter=${encodeURIComponent(filter)}` @@ -66,13 +66,13 @@ export const api = { cleanupAdminMissingImageReferences: () => request('/api/admin/image-assets/missing/cleanup', { method: 'POST', body: {} }), listAdminUnusedImageAssets: ({ limit = 100, minAgeHours = 24 } = {}) => request(`/api/admin/image-assets/orphans?limit=${encodeURIComponent(limit)}&minAgeHours=${encodeURIComponent(minAgeHours)}`), cleanupAdminUnusedImageAssets: (payload) => request('/api/admin/image-assets/cleanup', { method: 'POST', body: payload || {} }), - promoteAdminCustomItem: (itemId, payload) => + promoteAdminTemplateItem: (itemId, payload) => request(`/api/admin/custom-items/${encodeURIComponent(itemId)}/promote`, { method: 'POST', body: payload }), updateAdminCustomItemLabel: (itemId, payload) => request(`/api/admin/custom-items/${encodeURIComponent(itemId)}/label`, { method: 'PATCH', body: payload }), promoteAdminTierListItems: (tierListId, payload) => request(`/api/admin/tierlists/${encodeURIComponent(tierListId)}/promote-items`, { method: 'POST', body: payload }), - createAdminGameTemplateFromTierList: (tierListId, payload) => + createAdminTemplateFromTierList: (tierListId, payload) => request(`/api/admin/tierlists/${encodeURIComponent(tierListId)}/create-game-template`, { method: 'POST', body: payload }), startAdminTemplateRequestReview: (requestId) => request(`/api/admin/template-requests/${encodeURIComponent(requestId)}/review`, { method: 'POST', body: {} }), @@ -111,10 +111,10 @@ export const api = { }, deleteAdminUser: (userId) => request(`/api/admin/users/${encodeURIComponent(userId)}`, { method: 'DELETE' }), - listPublicTierLists: (gameId) => - request(`/api/tierlists/public?gameId=${encodeURIComponent(gameId || '')}`), - searchPublicTierLists: (gameId, q = '') => - request(`/api/tierlists/public?gameId=${encodeURIComponent(gameId || '')}&q=${encodeURIComponent(q || '')}`), + listPublicTierListsByTopic: (topicId) => + request(`/api/tierlists/public?gameId=${encodeURIComponent(topicId || '')}`), + searchPublicTierListsByTopic: (topicId, q = '') => + request(`/api/tierlists/public?gameId=${encodeURIComponent(topicId || '')}&q=${encodeURIComponent(q || '')}`), searchAllPublicTierLists: (q = '') => request(`/api/tierlists/public?q=${encodeURIComponent(q || '')}`), listMyTierLists: () => request('/api/tierlists/me'), listMyFavoriteTierLists: ({ q = '', sort = 'favorited' } = {}) => @@ -146,4 +146,24 @@ export const api = { deleteAdminCustomItem: (itemId) => request(`/api/admin/custom-items/${encodeURIComponent(itemId)}`, { method: 'DELETE' }), deleteAdminUnusedCustomItems: ({ q = '' } = {}) => request(`/api/admin/custom-items?q=${encodeURIComponent(q)}`, { method: 'DELETE' }), + + listGames: () => request('/api/games'), + getGame: (gameId) => request(`/api/games/${encodeURIComponent(gameId)}`), + favoriteGame: (gameId) => request(`/api/games/${encodeURIComponent(gameId)}/favorite`, { method: 'POST' }), + unfavoriteGame: (gameId) => request(`/api/games/${encodeURIComponent(gameId)}/favorite`, { method: 'DELETE' }), + updateAdminGameDisplayOrder: (payload) => request('/api/admin/games/display-order', { method: 'PATCH', body: payload }), + updateAdminGameItemDisplayOrder: (gameId, payload) => + request(`/api/admin/games/${encodeURIComponent(gameId)}/items/display-order`, { method: 'PATCH', body: payload }), + updateAdminGame: (gameId, payload) => + request(`/api/admin/games/${encodeURIComponent(gameId)}`, { method: 'PATCH', body: payload }), + updateAdminGameItem: (gameId, itemId, payload) => + request(`/api/admin/games/${encodeURIComponent(gameId)}/items/${encodeURIComponent(itemId)}`, { method: 'PATCH', body: payload }), + promoteAdminCustomItem: (itemId, payload) => + request(`/api/admin/custom-items/${encodeURIComponent(itemId)}/promote`, { method: 'POST', body: payload }), + createAdminGameTemplateFromTierList: (tierListId, payload) => + request(`/api/admin/tierlists/${encodeURIComponent(tierListId)}/create-game-template`, { method: 'POST', body: payload }), + listPublicTierLists: (gameId) => + request(`/api/tierlists/public?gameId=${encodeURIComponent(gameId || '')}`), + searchPublicTierLists: (gameId, q = '') => + request(`/api/tierlists/public?gameId=${encodeURIComponent(gameId || '')}&q=${encodeURIComponent(q || '')}`), } diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index c157daf..0aeeefd 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -787,7 +787,7 @@ async function selectAdminTemplate(templateId) { async function refreshTemplates() { try { - const data = await api.listGames() + const data = await api.listTopics() templates.value = data.games || [] featuredTemplateIds.value = templates.value .filter((template) => template.displayRank != null) @@ -1170,7 +1170,7 @@ async function saveTemplateVisibility() { if (!selectedTemplate.value?.game?.id) return try { gameVisibilitySaving.value = true - const data = await api.updateAdminGame(selectedTemplate.value.game.id, { + const data = await api.updateAdminTemplate(selectedTemplate.value.game.id, { isPublic: !!selectedTemplate.value.game.isPublic, }) selectedTemplate.value = { @@ -1244,7 +1244,7 @@ async function saveTemplateItemLabel(item) { try { item.isSavingLabel = true - const data = await api.updateAdminGameItem(selectedTemplateId.value, item.id, { label: nextLabel }) + const data = await api.updateAdminTemplateItem(selectedTemplateId.value, item.id, { label: nextLabel }) item.label = data.item.label item.draftLabel = data.item.label success.value = '기본 아이템 이름을 수정했어요.' @@ -1589,7 +1589,7 @@ async function confirmTierListImport() { return } - const data = await api.createAdminGameTemplateFromTierList(tierList.id, { + const data = await api.createAdminTemplateFromTierList(tierList.id, { gameId: nextGameId, name: nextGameName, itemIds, diff --git a/frontend/src/views/GameHubView.vue b/frontend/src/views/GameHubView.vue index 6878f5b..2e3b60c 100644 --- a/frontend/src/views/GameHubView.vue +++ b/frontend/src/views/GameHubView.vue @@ -54,8 +54,8 @@ async function loadTierLists() { isTopicLoading.value = true try { const [gameRes, listRes] = await Promise.all([ - api.getGame(topicId.value), - api.searchPublicTierLists(topicId.value, query.value), + api.getTopic(topicId.value), + api.searchPublicTierListsByTopic(topicId.value, query.value), ]) topicName.value = gameRes.game?.name || '' brokenThumbnailIds.value = {} diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index d555a3d..7bfa606 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -36,7 +36,7 @@ const templates = computed(() => { async function loadTemplates() { try { - const data = await api.listGames() + const data = await api.listTopics() templateRecords.value = data.games || [] } catch (e) { error.value = '백엔드에 연결할 수 없어요. backend 서버가 실행 중인지 확인해주세요.' @@ -60,7 +60,7 @@ async function toggleFavorite(template, event) { try { loadingFavoriteId.value = template.id - const res = template.isFavorited ? await api.unfavoriteGame(template.id) : await api.favoriteGame(template.id) + const res = template.isFavorited ? await api.unfavoriteTopic(template.id) : await api.favoriteTopic(template.id) templateRecords.value = templateRecords.value.map((entry) => (entry.id === template.id ? { ...entry, ...res.game } : entry)) } catch (e) { error.value = '즐겨찾기 변경에 실패했어요.' diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue index d461e63..134f62a 100644 --- a/frontend/src/views/TierEditorView.vue +++ b/frontend/src/views/TierEditorView.vue @@ -898,7 +898,7 @@ onMounted(() => { } try { - const gameRes = await api.getGame(templateId.value) + const gameRes = await api.getTopic(templateId.value) templateName.value = gameRes.game?.name || templateId.value const base = (gameRes.items || []).map((img) => ({ id: img.id,