From 85863b1b36d59382c72c82a8d053cdd56beade9d Mon Sep 17 00:00:00 2001 From: zenn Date: Thu, 2 Apr 2026 21:50:36 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=B4=EB=A6=AC=EC=8A=A4:=20v1.4.32=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20=EC=9D=B4=EB=A6=84=EC=B8=B5=20topic/templa?= =?UTF-8?q?te=20=EC=A0=95=EB=A6=AC=20=EB=A7=88=EA=B0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/index.js | 2 +- .../cleanup-unreferenced-legacy-uploads.js | 2 +- .../migrate-legacy-uploads-to-assets.js | 2 +- backend/src/db.js | 70 +++++------ backend/src/routes/admin.js | 14 +-- docs/history.md | 4 + docs/todo.md | 2 + docs/update.md | 5 + ...sSection.vue => AdminTemplatesSection.vue} | 14 +-- ...dGames.js => useAdminFeaturedTemplates.js} | 2 +- ...eManager.js => useAdminTemplateManager.js} | 2 +- frontend/src/router/index.js | 4 +- frontend/src/views/AdminView.vue | 116 +++++++++--------- .../{GameHubView.vue => TopicHubView.vue} | 0 14 files changed, 125 insertions(+), 114 deletions(-) rename frontend/src/components/admin/{AdminGamesSection.vue => AdminTemplatesSection.vue} (96%) rename frontend/src/composables/{useAdminFeaturedGames.js => useAdminFeaturedTemplates.js} (98%) rename frontend/src/composables/{useAdminGameManager.js => useAdminTemplateManager.js} (99%) rename frontend/src/views/{GameHubView.vue => TopicHubView.vue} (100%) diff --git a/backend/index.js b/backend/index.js index 27c39e0..208cc99 100644 --- a/backend/index.js +++ b/backend/index.js @@ -24,7 +24,7 @@ const allowedOrigins = (process.env.CORS_ORIGINS || '') const FileStore = FileStoreFactory(session) -;['uploads/avatars', 'uploads/games', 'uploads/custom', 'uploads/tierlists', '.sessions'].forEach((relativePath) => { +;['uploads/avatars', 'uploads/topics', 'uploads/custom', 'uploads/tierlists', '.sessions'].forEach((relativePath) => { fs.mkdirSync(path.join(__dirname, relativePath), { recursive: true }) }) diff --git a/backend/scripts/cleanup-unreferenced-legacy-uploads.js b/backend/scripts/cleanup-unreferenced-legacy-uploads.js index 020dcb2..fa2207b 100644 --- a/backend/scripts/cleanup-unreferenced-legacy-uploads.js +++ b/backend/scripts/cleanup-unreferenced-legacy-uploads.js @@ -7,7 +7,7 @@ const { } = require('../src/db') const BACKEND_ROOT = path.join(__dirname, '..') -const TARGET_DIRS = ['avatars', 'custom', 'games', 'tierlists'] +const TARGET_DIRS = ['avatars', 'custom', 'topics', 'tierlists'] async function main() { await ensureData() diff --git a/backend/scripts/migrate-legacy-uploads-to-assets.js b/backend/scripts/migrate-legacy-uploads-to-assets.js index e274b4f..295bee5 100644 --- a/backend/scripts/migrate-legacy-uploads-to-assets.js +++ b/backend/scripts/migrate-legacy-uploads-to-assets.js @@ -35,7 +35,7 @@ function getOptimizationConfig(roles) { if (roleSet.has('avatar')) { return { directory: 'avatars', width: 512, height: 512, fit: 'cover', quality: 82 } } - if (roleSet.has('game-thumbnail') || roleSet.has('tierlist-thumbnail') || roleSet.has('template-thumbnail')) { + if (roleSet.has('topic-thumbnail') || roleSet.has('tierlist-thumbnail') || roleSet.has('template-thumbnail')) { return { directory: 'legacy-thumbnails', width: 1280, height: 1280, fit: 'inside', quality: 84 } } return { directory: 'legacy-items', width: 512, height: 512, fit: 'inside', quality: 84 } diff --git a/backend/src/db.js b/backend/src/db.js index 590e60c..4fe05c7 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -67,7 +67,7 @@ function mapUserRow(row) { } } -function mapGameRow(row) { +function mapTopicRow(row) { if (!row) return null return { id: row.id, @@ -81,7 +81,7 @@ function mapGameRow(row) { } } -function mapGameItemRow(row) { +function mapTopicItemRow(row) { if (!row) return null return { id: row.id, @@ -292,8 +292,8 @@ async function ensureSchema() { ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `) - const gameIsPublicColumns = await query("SHOW COLUMNS FROM topics LIKE 'is_public'") - if (!gameIsPublicColumns.length) { + const topicIsPublicColumns = await query("SHOW COLUMNS FROM topics LIKE 'is_public'") + if (!topicIsPublicColumns.length) { await query('ALTER TABLE topics ADD COLUMN is_public TINYINT(1) NOT NULL DEFAULT 1 AFTER thumbnail_src') await query('UPDATE topics SET is_public = 1 WHERE is_public IS NULL') } @@ -316,8 +316,8 @@ async function ensureSchema() { ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `) - const gameItemDisplayOrderColumns = await query("SHOW COLUMNS FROM topic_items LIKE 'display_order'") - if (!gameItemDisplayOrderColumns.length) { + const topicItemDisplayOrderColumns = await query("SHOW COLUMNS FROM topic_items LIKE 'display_order'") + if (!topicItemDisplayOrderColumns.length) { await query('ALTER TABLE topic_items ADD COLUMN display_order INT NULL DEFAULT NULL AFTER label') } @@ -705,7 +705,7 @@ async function listTopics(currentUserId = '', options = {}) { `, [FREEFORM_TOPIC_ID] ) - const topics = rows.map(mapGameRow) + const topics = rows.map(mapTopicRow) if (!currentUserId) return topics.map((topic) => ({ ...topic, isFavorited: false })) const favoriteRows = await query('SELECT topic_id FROM favorite_topics WHERE user_id = ?', [currentUserId]) @@ -718,7 +718,7 @@ async function listTopics(currentUserId = '', options = {}) { async function findTopicById(id) { const rows = await query('SELECT id, name, thumbnail_src, is_public, display_rank, created_at FROM topics WHERE id = ? LIMIT 1', [id]) - return mapGameRow(rows[0]) + return mapTopicRow(rows[0]) } async function listTopicItems(topicId) { @@ -735,12 +735,12 @@ async function listTopicItems(topicId) { `, [topicId] ) - return rows.map(mapGameItemRow) + return rows.map(mapTopicItemRow) } async function findTopicItemById(itemId) { const rows = await query('SELECT id, topic_id, src, label, display_order, created_at FROM topic_items WHERE id = ? LIMIT 1', [itemId]) - return mapGameItemRow(rows[0]) + return mapTopicItemRow(rows[0]) } async function getTopicDetail(topicId) { @@ -868,7 +868,7 @@ async function listUnusedImageAssets({ limit = 100, minAgeHours = 24 } = {}) { const referencedSrcs = new Set() - const [userRows, gameRows, gameItemRows, customItemRows, tierListRows, templateRequestRows] = await Promise.all([ + const [userRows, topicRows, topicItemRows, customItemRows, tierListRows, templateRequestRows] = await Promise.all([ query("SELECT avatar_src FROM users WHERE avatar_src <> ''"), query("SELECT thumbnail_src FROM topics WHERE thumbnail_src <> ''"), query("SELECT src FROM topic_items WHERE src <> ''"), @@ -878,8 +878,8 @@ async function listUnusedImageAssets({ limit = 100, minAgeHours = 24 } = {}) { ]) for (const row of userRows) if (row.avatar_src) referencedSrcs.add(row.avatar_src) - for (const row of gameRows) if (row.thumbnail_src) referencedSrcs.add(row.thumbnail_src) - for (const row of gameItemRows) if (row.src) referencedSrcs.add(row.src) + for (const row of topicRows) if (row.thumbnail_src) referencedSrcs.add(row.thumbnail_src) + for (const row of topicItemRows) if (row.src) referencedSrcs.add(row.src) for (const row of customItemRows) if (row.src) referencedSrcs.add(row.src) for (const row of tierListRows) { @@ -921,7 +921,7 @@ async function listReferencedUploadUsage() { usageMap.get(src).add(role) } - const [userRows, gameRows, gameItemRows, customItemRows, tierListRows, templateRequestRows] = await Promise.all([ + const [userRows, topicRows, topicItemRows, customItemRows, tierListRows, templateRequestRows] = await Promise.all([ query("SELECT avatar_src FROM users WHERE avatar_src <> ''"), query("SELECT thumbnail_src FROM topics WHERE thumbnail_src <> ''"), query("SELECT src FROM topic_items WHERE src <> ''"), @@ -931,8 +931,8 @@ async function listReferencedUploadUsage() { ]) for (const row of userRows) addUsage(row.avatar_src, 'avatar') - for (const row of gameRows) addUsage(row.thumbnail_src, 'topic-thumbnail') - for (const row of gameItemRows) addUsage(row.src, 'topic-item') + for (const row of topicRows) addUsage(row.thumbnail_src, 'topic-thumbnail') + for (const row of topicItemRows) addUsage(row.src, 'topic-item') for (const row of customItemRows) addUsage(row.src, 'custom-item') for (const row of tierListRows) { @@ -964,14 +964,14 @@ function replaceItemSrc(items, fromSrc, toSrc) { async function replaceUploadSourceReferences({ fromSrc, toSrc }) { if (!fromSrc || !toSrc || fromSrc === toSrc) return { updatedRows: 0 } - const [userResult, gameResult, gameItemResult, customItemResult] = await Promise.all([ + const [userResult, topicResult, topicItemResult, customItemResult] = await Promise.all([ query('UPDATE users SET avatar_src = ? WHERE avatar_src = ?', [toSrc, fromSrc]), query('UPDATE topics SET thumbnail_src = ? WHERE thumbnail_src = ?', [toSrc, fromSrc]), query('UPDATE topic_items SET src = ? WHERE src = ?', [toSrc, fromSrc]), query('UPDATE custom_items SET src = ? WHERE src = ?', [toSrc, fromSrc]), ]) - let updatedRows = Number(userResult.affectedRows || 0) + Number(gameResult.affectedRows || 0) + Number(gameItemResult.affectedRows || 0) + Number(customItemResult.affectedRows || 0) + let updatedRows = Number(userResult.affectedRows || 0) + Number(topicResult.affectedRows || 0) + Number(topicItemResult.affectedRows || 0) + Number(customItemResult.affectedRows || 0) const tierListRows = await query('SELECT id, thumbnail_src, pool_json FROM tierlists') for (const row of tierListRows) { @@ -1120,16 +1120,16 @@ function stripMissingItems(items, missingItemIds, missingSrcs) { async function cleanupMissingUploadReferences() { const stats = { clearedAvatars: 0, - clearedGameThumbnails: 0, + clearedTopicThumbnails: 0, clearedTierListThumbnails: 0, clearedTemplateRequestThumbnails: 0, - deletedGameItems: 0, + deletedTopicItems: 0, updatedTierLists: 0, updatedTemplateRequests: 0, deletedCustomItems: 0, } - const [userRows, gameRows, gameItemRows, customItemRows, tierListRows, templateRequestRows] = await Promise.all([ + const [userRows, topicRows, topicItemRows, customItemRows, tierListRows, templateRequestRows] = await Promise.all([ query("SELECT id, avatar_src FROM users WHERE avatar_src <> ''"), query("SELECT id, thumbnail_src FROM topics WHERE thumbnail_src <> ''"), query("SELECT id, src FROM topic_items WHERE src <> ''"), @@ -1144,16 +1144,16 @@ async function cleanupMissingUploadReferences() { stats.clearedAvatars += 1 } - for (const row of gameRows) { + for (const row of topicRows) { if (await fileExistsForUploadSrc(row.thumbnail_src)) continue await query('UPDATE topics SET thumbnail_src = ? WHERE id = ?', ['', row.id]) - stats.clearedGameThumbnails += 1 + stats.clearedTopicThumbnails += 1 } - for (const row of gameItemRows) { + for (const row of topicItemRows) { if (await fileExistsForUploadSrc(row.src)) continue await deleteTopicItem(row.id) - stats.deletedGameItems += 1 + stats.deletedTopicItems += 1 } const missingCustomItemIds = new Set() @@ -1313,13 +1313,13 @@ async function createTopicItem({ id, topicId, src, label }) { createdAt, ]) const rows = await query('SELECT id, topic_id, src, label, display_order, created_at FROM topic_items WHERE id = ? LIMIT 1', [id]) - return mapGameItemRow(rows[0]) + return mapTopicItemRow(rows[0]) } async function updateTopicItemLabel(itemId, label) { await query('UPDATE topic_items SET label = ? WHERE id = ?', [label, itemId]) const rows = await query('SELECT id, topic_id, src, label, display_order, created_at FROM topic_items WHERE id = ? LIMIT 1', [itemId]) - return mapGameItemRow(rows[0]) + return mapTopicItemRow(rows[0]) } async function updateTopicItemDisplayOrder(topicId, itemIds) { @@ -1521,7 +1521,7 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod const hasQuery = !!searchText const search = `%${searchText}%` - const [customRows, gameItemRows, assetRows, usageMeta] = await Promise.all([ + const [customRows, topicItemRows, assetRows, usageMeta] = await Promise.all([ query( ` SELECT @@ -1569,7 +1569,7 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod ]) const templateLinkedBySrc = new Map() - gameItemRows.forEach((row) => { + topicItemRows.forEach((row) => { if (!row?.src) return if (!templateLinkedBySrc.has(row.src)) templateLinkedBySrc.set(row.src, new Map()) templateLinkedBySrc.get(row.src).set(row.topic_id, { @@ -1596,7 +1596,7 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod } }) - const templateSrcSet = new Set(gameItemRows.map((row) => row.src).filter(Boolean)) + const templateSrcSet = new Set(topicItemRows.map((row) => row.src).filter(Boolean)) const customSrcSet = new Set(customRows.map((row) => row.src).filter(Boolean)) const assetLibraryItems = assetRows .filter((row) => row?.src && !templateSrcSet.has(row.src) && !customSrcSet.has(row.src)) @@ -1619,7 +1619,7 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod isAssetLibraryItem: true, })) - const templateItems = gameItemRows.map((row) => ({ + const templateItems = topicItemRows.map((row) => ({ id: row.id, ownerId: '', src: row.src, @@ -1995,12 +1995,12 @@ async function listAdminTierLists({ queryText = '', topicId = '', page = 1, limi const normalizedPage = Math.max(Number(page) || 1, 1) const hasQuery = !!(queryText || '').trim() const resolvedTopicId = (topicId || '').trim() - const hasGameId = !!resolvedTopicId + const hasTopicId = !!resolvedTopicId const search = `%${(queryText || '').trim()}%` const whereParts = [] const params = [] - if (hasGameId) { + if (hasTopicId) { whereParts.push('t.topic_id = ?') params.push(resolvedTopicId) } @@ -2080,12 +2080,12 @@ async function listAdminTierLists({ queryText = '', topicId = '', page = 1, limi async function summarizeAdminTierLists({ queryText = '', topicId = '' } = {}) { const hasQuery = !!(queryText || '').trim() const resolvedTopicId = (topicId || '').trim() - const hasGameId = !!resolvedTopicId + const hasTopicId = !!resolvedTopicId const search = `%${(queryText || '').trim()}%` const whereParts = [] const params = [] - if (hasGameId) { + if (hasTopicId) { whereParts.push('t.topic_id = ?') params.push(resolvedTopicId) } diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 0f7a82a..9e03dc7 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -128,7 +128,7 @@ router.post('/templates', requireAdmin, async (req, res) => { if (exists) return res.status(409).json({ error: 'topic_id_taken' }) const template = await createTopic({ id: parsed.data.id, name: parsed.data.name, isPublic: parsed.data.isPublic }) if (parsed.data.thumbnailSrc) { - const copiedThumb = await copyUploadIntoGameAsset(parsed.data.thumbnailSrc) + const copiedThumb = await copyUploadIntoTopicAsset(parsed.data.thumbnailSrc) await updateTopicThumbnail(template.id, copiedThumb) } const savedTemplate = await findTopicById(template.id) @@ -469,7 +469,7 @@ async function promoteLibraryItemToTemplateItem({ item, templateId }) { }) } -async function copyUploadIntoGameAsset(src) { +async function copyUploadIntoTopicAsset(src) { if (typeof src !== 'string') return '' const raw = src.trim() if (!raw) return '' @@ -507,7 +507,7 @@ async function promoteTierListItemsToTemplate({ tierList, templateId, itemIds = const createdItems = [] for (const item of itemsToCopy) { - const copiedSrc = await copyUploadIntoGameAsset(item.src) + const copiedSrc = await copyUploadIntoTopicAsset(item.src) createdItems.push( await createTopicItem({ id: nanoid(), @@ -531,7 +531,7 @@ async function promoteSnapshotItemsToTemplate({ items, templateId }) { const createdItems = [] for (const item of items || []) { - const copiedSrc = await copyUploadIntoGameAsset(item.src) + const copiedSrc = await copyUploadIntoTopicAsset(item.src) if (!copiedSrc || existingSrcs.has(copiedSrc)) continue createdItems.push( await createTopicItem({ @@ -576,13 +576,13 @@ function pickTemplateRequestItems(templateRequest, itemIds = [], itemLabels = {} async function createTemplateFromTierList({ tierList, templateId, templateName }) { await createTopic({ id: templateId, name: templateName, isPublic: false }) if (tierList.thumbnailSrc) { - const copiedThumb = await copyUploadIntoGameAsset(tierList.thumbnailSrc) + const copiedThumb = await copyUploadIntoTopicAsset(tierList.thumbnailSrc) await updateTopicThumbnail(templateId, copiedThumb) } const createdItems = [] for (const item of uniqueTierListPoolItems(tierList)) { - const copiedSrc = await copyUploadIntoGameAsset(item.src) + const copiedSrc = await copyUploadIntoTopicAsset(item.src) createdItems.push( await createTopicItem({ id: nanoid(), @@ -600,7 +600,7 @@ async function createTemplateFromRequest({ templateRequest, templateId, template await createTopic({ id: templateId, name: templateName, isPublic: false }) if (templateRequest.thumbnailSrc) { - const copiedThumb = await copyUploadIntoGameAsset(templateRequest.thumbnailSrc) + const copiedThumb = await copyUploadIntoTopicAsset(templateRequest.thumbnailSrc) await updateTopicThumbnail(templateId, copiedThumb) } diff --git a/docs/history.md b/docs/history.md index 8e7be19..e688957 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-02 v1.4.32 +- 서비스 공개 전 마감 단계에서는 사용자 노출 텍스트만이 아니라 파일명·composable 이름·관리자 CSS 클래스·백엔드 헬퍼 함수명까지 같이 정리해 두는 편이 이후 유지보수 비용을 확실히 낮춘다고 판단했다. +- 이 시점부터는 `game`이 데이터 호환층도 아닌 단순 내부 이름으로 남아 있는 것조차 혼란을 만들 수 있으므로, 실제 기능을 바꾸지 않는 선에서 이름층까지 끝까지 정리해 코드 검색 결과 자체를 깨끗하게 만드는 방향으로 마감했다. + ## 2026-04-02 v1.4.31 - 서비스가 아직 외부 공개 전이고 예전 북마크/예전 데이터베이스를 이어갈 필요가 없다는 전제가 확인되었으므로, 남겨둔 호환층을 유지하는 것보다 지금 마감 시점에 완전히 제거해 구조를 단순화하는 편이 맞다고 판단했다. - 이 단계에서는 “기존 것도 읽어준다”보다 “현재 구조만 남긴다”가 더 중요한 목표가 되었으므로, redirect·legacy migration·`origin: 'game'` 허용까지 함께 정리해 실제 코드 검색에서 `game` 흔적을 0건으로 맞추는 방향으로 마감했다. diff --git a/docs/todo.md b/docs/todo.md index d52b0cc..35c59aa 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,8 @@ # 할 일 및 이슈 ## 단기 확인 +- `v1.4.32`에서 파일명·composable·관리자 클래스명·백엔드 헬퍼 함수명까지 `topic/template` 기준으로 끝까지 정리했으므로, 다음 실제 QA는 기능 동작 확인에 집중하고 이름층 회귀는 별도 체크만 하면 된다. +- 현재 `backend/src`, `frontend/src`, `backend/scripts`, `backend/index.js` 기준 `game/Game` 검색은 0건이므로, 이후 남는 확인 작업은 서비스 동작과 배포 환경 쪽에만 집중한다. - `v1.4.31`에서 `/games` redirect와 legacy DB 마이그레이션까지 제거했으므로, 실제 QA에서는 오직 현재 주소(`/topics`, `/admin/templates`)와 새 DB 기준 흐름만 집중적으로 확인하면 된다. - 현재 `backend/src`, `frontend/src` 기준 `game` 검색은 0건이므로, 이후 남는 확인 작업은 기능 QA와 운영 환경 배포 점검 쪽에만 집중한다. - `v1.4.30`에서 빈 로컬 MariaDB 재초기화 검증까지 통과했으므로, 다음 실제 QA에서는 “기존 데이터가 있는 환경”에서 `ensureData()`가 저장 티어표와 템플릿 요청 스냅샷의 legacy origin을 정상 정규화하는지만 추가 확인하면 된다. diff --git a/docs/update.md b/docs/update.md index 2dd3a8d..de820ee 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-04-02 v1.4.32 +- 파일명과 내부 심볼 이름까지 `topic/template` 기준으로 마감했다. `GameHubView`는 `TopicHubView`, `AdminGamesSection`은 `AdminTemplatesSection`, `useAdminGameManager`와 `useAdminFeaturedGames`는 각각 `useAdminTemplateManager`, `useAdminFeaturedTemplates`로 정리했다. +- 관리자 화면 내부 상태와 스타일 클래스도 `adminTemplatePicker`, `templateManagerGrid`, `templateSettingsCard` 기준으로 바꿔, 사용자에게는 안 보이지만 코드 검색에서 남던 `Game` 흔적을 더 걷어냈다. +- 백엔드도 `copyUploadIntoTopicAsset`, `mapTopicRow`, `mapTopicItemRow`처럼 내부 함수명을 맞추고, 업로드 디렉터리/정리 스크립트도 `topics` 기준으로 통일해 `backend/src`, `frontend/src`, `backend/scripts`, `backend/index.js` 범위의 `game/Game` 검색 결과를 0건으로 정리했다. + ## 2026-04-02 v1.4.31 - 서비스가 아직 공개 전이고 예전 링크/예전 DB를 이어갈 필요가 없다는 전제에 맞춰, `/games` redirect와 관리자 `/admin/games` redirect, DB 레거시 마이그레이션 코드, legacy origin 정규화 코드를 실제로 제거했다. - 티어표 저장/request schema도 이제 `origin: 'template' | 'custom'`만 받도록 정리했고, 관리자 최근 최적화 작업 분류 fallback에 남아 있던 `games` 처리도 걷어냈다. diff --git a/frontend/src/components/admin/AdminGamesSection.vue b/frontend/src/components/admin/AdminTemplatesSection.vue similarity index 96% rename from frontend/src/components/admin/AdminGamesSection.vue rename to frontend/src/components/admin/AdminTemplatesSection.vue index 19fe363..fe5970b 100644 --- a/frontend/src/components/admin/AdminGamesSection.vue +++ b/frontend/src/components/admin/AdminTemplatesSection.vue @@ -47,7 +47,7 @@ const props = defineProps({ selectedTemplateId: { type: String, default: '' }, }) -function setGameItemListElement(el) { +function setTemplateItemListElement(el) { props.templateItemListRef(el) } @@ -109,8 +109,8 @@ function setThumbFileElement(el) {
-
-
+
+
-
+
템플릿 설정
-
{{ props.selectedTemplate.template.name }} · {{ props.selectedTemplate.template.id }}
+
{{ props.selectedTemplate.template.name }} · {{ props.selectedTemplate.template.id }}
-
+
@@ -215,7 +215,7 @@ function setThumbFileElement(el) {
아직 등록된 기본 아이템이 없어요.
-
+
diff --git a/frontend/src/composables/useAdminFeaturedGames.js b/frontend/src/composables/useAdminFeaturedTemplates.js similarity index 98% rename from frontend/src/composables/useAdminFeaturedGames.js rename to frontend/src/composables/useAdminFeaturedTemplates.js index 7c6ff6d..e86f384 100644 --- a/frontend/src/composables/useAdminFeaturedGames.js +++ b/frontend/src/composables/useAdminFeaturedTemplates.js @@ -1,7 +1,7 @@ import { nextTick } from 'vue' import Sortable from 'sortablejs' -export function useAdminFeaturedGames({ +export function useAdminFeaturedTemplates({ api, featuredListEl, featuredSortable, diff --git a/frontend/src/composables/useAdminGameManager.js b/frontend/src/composables/useAdminTemplateManager.js similarity index 99% rename from frontend/src/composables/useAdminGameManager.js rename to frontend/src/composables/useAdminTemplateManager.js index 8d90104..8c218b4 100644 --- a/frontend/src/composables/useAdminGameManager.js +++ b/frontend/src/composables/useAdminTemplateManager.js @@ -1,7 +1,7 @@ import { nextTick } from 'vue' import Sortable from 'sortablejs' -export function useAdminGameManager({ +export function useAdminTemplateManager({ api, toApiUrl, selectedTemplateId, diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 995ad19..1d65db1 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,7 +1,7 @@ import { createRouter as _createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' -import GameHubView from '../views/GameHubView.vue' +import TopicHubView from '../views/TopicHubView.vue' import TierEditorView from '../views/TierEditorView.vue' import LoginView from '../views/LoginView.vue' import MyTierListsView from '../views/MyTierListsView.vue' @@ -16,7 +16,7 @@ export function createRouter() { history: createWebHistory(), routes: [ { path: '/', name: 'home', component: HomeView }, - { path: '/topics/:topicId', name: 'topicHub', component: GameHubView }, + { path: '/topics/:topicId', name: 'topicHub', component: TopicHubView }, { path: '/editor/:topicId/new', name: 'newEditor', component: TierEditorView }, { path: '/editor/:topicId/:tierListId', name: 'editEditor', component: TierEditorView }, { path: '/login', name: 'login', component: LoginView }, diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index b28b87d..e4058bd 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -8,13 +8,13 @@ import lockResetIcon from '../assets/icons/lock_reset.svg' import deleteIcon from '../assets/icons/delete.svg' import SvgIcon from '../components/SvgIcon.vue' import AdminFeaturedSection from '../components/admin/AdminFeaturedSection.vue' -import AdminGamesSection from '../components/admin/AdminGamesSection.vue' +import AdminTemplatesSection from '../components/admin/AdminTemplatesSection.vue' import AdminItemsSection from '../components/admin/AdminItemsSection.vue' import AdminTierlistsSection from '../components/admin/AdminTierlistsSection.vue' import AdminUsersSection from '../components/admin/AdminUsersSection.vue' import { useAdminCustomItems } from '../composables/useAdminCustomItems' -import { useAdminFeaturedGames } from '../composables/useAdminFeaturedGames' -import { useAdminGameManager } from '../composables/useAdminGameManager' +import { useAdminFeaturedTemplates } from '../composables/useAdminFeaturedTemplates' +import { useAdminTemplateManager } from '../composables/useAdminTemplateManager' import { useAdminTemplateRequests } from '../composables/useAdminTemplateRequests' import { useAdminUsers } from '../composables/useAdminUsers' import { useAuthStore } from '../stores/auth' @@ -50,7 +50,7 @@ const customItemModalTargetTemplateId = ref('') const adminTierLists = ref([]) const adminTierListQuery = ref('') -const adminTierListGameId = ref('') +const adminTierListTopicId = ref('') const adminTierListPage = ref(1) const adminTierListLimit = ref(50) const adminTierListTotal = ref(0) @@ -143,7 +143,7 @@ function setThumbFileInputRef(el) { thumbFileInput.value = el } -function scheduleGameItemSortableSync() { +function scheduleTemplateItemSortableSync() { if (templateItemSortableSyncTimer) { clearTimeout(templateItemSortableSyncTimer) templateItemSortableSyncTimer = null @@ -156,10 +156,10 @@ function scheduleGameItemSortableSync() { }, 0) } -function setGameItemListRef(el) { +function setTemplateItemListRef(el) { templateItemListEl.value = el if (!el) return - scheduleGameItemSortableSync() + scheduleTemplateItemSortableSync() } function normalizeAdminSrc(src) { @@ -437,7 +437,7 @@ watch( const nextMode = route.query.mode === 'all' ? 'all' : 'requests' if (tierlistsMode.value !== nextMode) tierlistsMode.value = nextMode const nextTierListTopicId = typeof route.query.topicId === 'string' ? route.query.topicId : '' - if (adminTierListGameId.value !== nextTierListTopicId) adminTierListGameId.value = nextTierListTopicId + if (adminTierListTopicId.value !== nextTierListTopicId) adminTierListTopicId.value = nextTierListTopicId } }, { immediate: true } @@ -465,13 +465,13 @@ watch( if (route.name !== 'adminTierlists') return syncAdminRouteQuery({ mode: mode === 'all' ? 'all' : undefined, - topicId: mode === 'all' && adminTierListGameId.value ? adminTierListGameId.value : undefined, + topicId: mode === 'all' && adminTierListTopicId.value ? adminTierListTopicId.value : undefined, }) } ) watch( - () => adminTierListGameId.value, + () => adminTierListTopicId.value, (topicId) => { if (route.name !== 'adminTierlists' || tierlistsMode.value !== 'all') return syncAdminRouteQuery({ topicId: topicId || undefined }) @@ -527,7 +527,7 @@ watch( () => [selectedTemplate.value?.template?.id || '', selectedTemplate.value?.items?.length || 0, !!templateItemListEl.value], ([templateId, itemCount, hasListEl]) => { if (!templateId || !itemCount || !hasListEl) return - scheduleGameItemSortableSync() + scheduleTemplateItemSortableSync() } ) @@ -715,10 +715,10 @@ async function cleanupMissingImageReferences() { success.value = `누락 참조를 정리했어요. ` + `아바타 ${result.clearedAvatars || 0}건, ` + - `템플릿 썸네일 ${result.clearedGameThumbnails || 0}건, ` + + `템플릿 썸네일 ${result.clearedTopicThumbnails || 0}건, ` + `티어표 썸네일 ${result.clearedTierListThumbnails || 0}건, ` + `요청 썸네일 ${result.clearedTemplateRequestThumbnails || 0}건, ` + - `템플릿 아이템 ${result.deletedGameItems || 0}건, ` + + `템플릿 아이템 ${result.deletedTopicItems || 0}건, ` + `커스텀 아이템 ${result.deletedCustomItems || 0}건` } catch (e) { error.value = '누락 이미지 참조 정리에 실패했어요.' @@ -822,7 +822,7 @@ async function refreshAdminTierLists() { try { const data = await api.listAdminTierLists({ q: adminTierListQuery.value, - topicId: adminTierListGameId.value, + topicId: adminTierListTopicId.value, page: adminTierListPage.value, limit: adminTierListLimit.value, }) @@ -839,7 +839,7 @@ async function refreshAdminTierLists() { async function refreshAdminTierListStats() { if (!auth.user?.isAdmin) return try { - const data = await api.getAdminTierListStats({ q: adminTierListQuery.value, topicId: adminTierListGameId.value }) + const data = await api.getAdminTierListStats({ q: adminTierListQuery.value, topicId: adminTierListTopicId.value }) adminTierListStats.value = { total: data.total || 0, publicCount: data.publicCount || 0, @@ -919,7 +919,7 @@ const { removeFeaturedTemplate, moveFeaturedTemplate, saveFeaturedOrder, -} = useAdminFeaturedGames({ +} = useAdminFeaturedTemplates({ api, featuredListEl, featuredSortable, @@ -943,7 +943,7 @@ const { clearItemFiles, uploadItem, saveTemplateItemOrder, -} = useAdminGameManager({ +} = useAdminTemplateManager({ api, toApiUrl, selectedTemplateId, @@ -1306,8 +1306,8 @@ function submitAdminTierListSearch() { refreshAdminTierLists() } -function setAdminTierListGameId(topicId) { - adminTierListGameId.value = topicId || '' +function setAdminTierListTopicId(topicId) { + adminTierListTopicId.value = topicId || '' adminTierListPage.value = 1 refreshAdminTierLists() } @@ -1327,7 +1327,7 @@ function closeTemplatePickerModal() { async function chooseTemplateFromPicker(templateId) { if (!templateId) return if (templatePickerMode.value === 'tierlists-filter') { - setAdminTierListGameId(templateId) + setAdminTierListTopicId(templateId) closeTemplatePickerModal() return } @@ -1700,7 +1700,7 @@ function userAvatarFallback(user) { :add-featured-template="addFeaturedTemplate" /> - 오래된순
-
+
검색 결과가 없어요.
@@ -2305,11 +2305,11 @@ function userAvatarFallback(user) {
-
+
필터된 주제
-
{{ templates.find((template) => template.id === adminTierListGameId)?.name || adminTierListGameId }}
-
{{ adminTierListGameId }}
- +
{{ templates.find((template) => template.id === adminTierListTopicId)?.name || adminTierListTopicId }}
+
{{ adminTierListTopicId }}
+