Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a8d4ddabd | |||
| 75a3822502 |
@@ -7,7 +7,7 @@ const FileStoreFactory = require('session-file-store')
|
||||
|
||||
const { ensureData } = require('./src/db')
|
||||
const authRoutes = require('./src/routes/auth')
|
||||
const topicsRoutes = require('./src/routes/games')
|
||||
const topicsRoutes = require('./src/routes/topics')
|
||||
const tierListsRoutes = require('./src/routes/tierlists')
|
||||
const adminRoutes = require('./src/routes/admin')
|
||||
|
||||
|
||||
@@ -262,9 +262,7 @@ router.post('/template-request', requireAuth, async (req, res) => {
|
||||
type: payload.type,
|
||||
requesterId: req.session.userId,
|
||||
sourceTierListId: sourceTierList?.id || '',
|
||||
sourceGameId: topicId,
|
||||
sourceTopicId: topicId,
|
||||
targetGameId: payload.type === 'update' ? topicId : '',
|
||||
targetTopicId: payload.type === 'update' ? topicId : '',
|
||||
title: payload.requestTitle,
|
||||
description: payload.requestDescription,
|
||||
@@ -298,7 +296,6 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
const updated = await saveTierList({
|
||||
id: existing.id,
|
||||
authorId: existing.authorId,
|
||||
gameId: existing.topicId || existing.gameId,
|
||||
topicId: existing.topicId || existing.gameId,
|
||||
title: payload.title,
|
||||
thumbnailSrc: payload.thumbnailSrc || '',
|
||||
@@ -318,7 +315,6 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
const created = await saveTierList({
|
||||
id: nanoid(),
|
||||
authorId: req.session.userId,
|
||||
gameId: topicId,
|
||||
topicId,
|
||||
title: payload.title,
|
||||
thumbnailSrc: payload.thumbnailSrc || '',
|
||||
|
||||
@@ -9,8 +9,8 @@ router.get('/', async (req, res) => {
|
||||
res.json({ games: topics, topics })
|
||||
})
|
||||
|
||||
router.post('/:gameId/favorite', requireAuth, async (req, res) => {
|
||||
const topic = await findTopicById(req.params.gameId)
|
||||
router.post('/:topicId/favorite', requireAuth, async (req, res) => {
|
||||
const topic = await findTopicById(req.params.topicId)
|
||||
if (!topic || topic.id === 'freeform') return res.status(404).json({ error: 'not_found' })
|
||||
await favoriteTopic({ userId: req.session.userId, topicId: topic.id })
|
||||
const topics = await listTopics(req.session.userId)
|
||||
@@ -18,8 +18,8 @@ router.post('/:gameId/favorite', requireAuth, async (req, res) => {
|
||||
res.json({ game: updated, topic: updated })
|
||||
})
|
||||
|
||||
router.delete('/:gameId/favorite', requireAuth, async (req, res) => {
|
||||
const topic = await findTopicById(req.params.gameId)
|
||||
router.delete('/:topicId/favorite', requireAuth, async (req, res) => {
|
||||
const topic = await findTopicById(req.params.topicId)
|
||||
if (!topic || topic.id === 'freeform') return res.status(404).json({ error: 'not_found' })
|
||||
await unfavoriteTopic({ userId: req.session.userId, topicId: topic.id })
|
||||
const topics = await listTopics(req.session.userId)
|
||||
@@ -27,8 +27,8 @@ router.delete('/:gameId/favorite', requireAuth, async (req, res) => {
|
||||
res.json({ game: updated, topic: updated })
|
||||
})
|
||||
|
||||
router.get('/:gameId', async (req, res) => {
|
||||
const detail = await getTopicDetail(req.params.gameId)
|
||||
router.get('/:topicId', async (req, res) => {
|
||||
const detail = await getTopicDetail(req.params.topicId)
|
||||
if (!detail) return res.status(404).json({ error: 'not_found' })
|
||||
if (!detail.topic.isPublic && !req.session?.isAdmin) return res.status(404).json({ error: 'not_found' })
|
||||
res.json({ game: detail.topic, topic: detail.topic, items: detail.items })
|
||||
@@ -1,5 +1,13 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-04-02 v1.4.23
|
||||
- 프런트가 이미 `topic/template` 메서드만 실제로 쓰고 있다면, `api.js` 안에 남은 레거시 `game` 별칭까지 계속 유지하는 건 오히려 정리 상태를 흐리므로 이 단계에서 정리하는 편이 맞다고 판단했다.
|
||||
- 티어표 저장과 템플릿 요청처럼 핵심 생성 흐름은 백엔드 내부 payload도 먼저 `topicId` 기준으로 맞춰 두는 편이, 이후 응답 호환 키를 걷어낼 때 충격을 더 줄인다고 정리했다.
|
||||
|
||||
## 2026-04-02 v1.4.22
|
||||
- 내부 함수명과 export를 정리한 뒤에도 라우트 파일명이 계속 `games.js`로 남아 있으면 마지막까지 개념 충돌을 남기게 되므로, 공개 주제 라우트 파일명도 실제 의미에 맞게 `topics.js`로 옮기는 편이 맞다고 판단했다.
|
||||
- `/api/games` 호환 경로는 유지하더라도, 서버 내부 구현만큼은 `topic` 기준 param 이름과 파일 이름으로 정리해 두는 편이 이후 레거시 제거를 훨씬 더 쉽게 만든다고 정리했다.
|
||||
|
||||
## 2026-04-02 v1.4.21
|
||||
- 백엔드에서 `topic/template` 응답을 내보내더라도 프런트가 계속 `game` 키만 읽으면 호환 레이어가 끝나지 않으므로, 이번 단계부터는 실제 사용자 화면과 관리자 저장 흐름도 새 키를 우선 읽게 맞추는 편이 맞다고 판단했다.
|
||||
- 이 구간은 외부 API를 끊는 작업이 아니라 “프런트가 새 의미를 먼저 받아들이는 단계”이므로, 기존 `game` 키는 fallback으로만 남겨 두고 단계적으로 걷어내는 편이 가장 안전하다고 정리했다.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# 할 일 및 이슈
|
||||
|
||||
## 단기 확인
|
||||
- `v1.4.23`에서 프런트 `api.js`의 레거시 `game` 별칭 메서드와 티어표 저장/요청 내부 payload를 더 걷어냈으므로, 실제 브라우저에서 저장/복사/템플릿 요청/관리자 요청 카드 표시가 그대로 정상인지 한 번 더 QA한다.
|
||||
- 다음 단계에서는 응답의 `game`, `gameId`, `gameName`, `sourceGameId`, `targetGameId` 호환 키를 실제로 제거할지, 아니면 `v1.4` 마감 후 안정화 기간을 두고 걷어낼지 최종 결정한다.
|
||||
- `v1.4.22`에서 공개 주제 라우트 파일을 `topics.js`로 옮겼으므로, 실제 서버 재기동 후 `/api/topics`와 `/api/games` 호환 경로가 모두 정상 응답하는지 한 번 더 QA한다.
|
||||
- 다음 단계에서는 응답의 `game`, `gameId`, `gameName` 호환 키를 실제로 어느 범위까지 제거할지, 그리고 관리자/티어표 저장 payload에서 남은 `gameId` 입력 호환을 어디까지 유지할지 최종 결정한다.
|
||||
- `v1.4.21`에서 홈/주제 상세/에디터/나의 티어표/즐겨찾기/검색 결과/관리자 템플릿 생성이 `topic/template` 응답 키를 우선 읽도록 바뀌었으므로, 실제 브라우저에서 즐겨찾기 토글과 에디터 이동, 관리자 신규 템플릿 생성이 모두 정상인지 한 번 더 QA한다.
|
||||
- 다음 단계에서는 실제 응답의 `game`, `gameId`, `gameName` 호환 키를 어디까지 남길지, 그리고 `/api/games` 호환 경로와 `games.js` 파일명을 언제 걷어낼지 최종 범위를 정한다.
|
||||
- `v1.4.20`에서 백엔드 `db` export와 공개/관리자 라우트 내부 이름을 `topic/template` 기준으로 정리했으므로, 실제 브라우저와 관리자 화면에서 주제 목록/즐겨찾기/템플릿 생성/요청 반영 흐름이 모두 정상인지 한 번 더 QA한다.
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-04-02 v1.4.23
|
||||
- 프런트 `api.js`에서 더 이상 쓰지 않는 `listGames / getGame / favoriteGame / updateAdminGame* / listPublicTierLists` 같은 레거시 별칭 메서드를 정리해, 공개/관리자 호출부가 실제로 쓰는 `topic/template` API만 남기도록 정리했다.
|
||||
- 관리자 템플릿 요청 상태와 전체 티어표 관리 카드도 `sourceTopicId / targetTopicId / topicName`을 우선 읽도록 더 당겨, 화면에서 `game` 키를 보는 범위를 줄였다.
|
||||
- 티어표 저장/템플릿 요청 백엔드는 이제 내부적으로 `sourceTopicId / targetTopicId / topicId`만 넘기도록 정리하고, 기존 `sourceGameId / gameId`는 저장 경로에서 한 단계 더 덜어냈다.
|
||||
|
||||
## 2026-04-02 v1.4.22
|
||||
- 백엔드 공개 주제 라우트 파일을 [topics.js](/Users/bicute/Desktop/zenn.dev/tier-cursor/backend/src/routes/topics.js)로 옮기고, 진입점도 이 이름으로 읽히게 정리했다. 이제 서버 코드에서 `games.js` 파일명이 남아 있던 마지막 큰 표면도 실제 의미에 더 가깝게 맞춰졌다.
|
||||
- 공개 주제 라우트의 path 파라미터도 `:topicId` 기준으로 읽히게 바꿔, 내부 구현에서 더 이상 `req.params.gameId`를 기본 전제로 보지 않도록 정리했다.
|
||||
|
||||
## 2026-04-02 v1.4.21
|
||||
- 프런트의 실제 소비 지점도 `topic/template` 응답 키를 우선 읽도록 옮겼다. 홈의 즐겨찾기 토글, 주제 상세 헤더, 티어표 편집기 템플릿 로딩, 나의 티어표/즐겨찾기/검색 결과의 에디터 이동이 이제 `topic`, `topicId`, `template`를 먼저 사용한다.
|
||||
- 관리자 템플릿 공개 상태 저장과 신규 템플릿 생성 흐름도 `data.template`를 우선 읽고, 기존 `data.game`은 fallback으로만 남겨 프런트와 백엔드의 의미 이름이 한 단계 더 가까워지게 맞췄다.
|
||||
|
||||
@@ -148,7 +148,7 @@ const props = defineProps({
|
||||
<div class="tierAdminCard__title">{{ tierList.title }}</div>
|
||||
<div v-if="tierList.description" class="tierAdminCard__desc">{{ tierList.description }}</div>
|
||||
<div class="tierAdminCard__meta">
|
||||
{{ tierList.gameName || tierList.gameId }} · {{ props.tierListAuthorDisplayName(tierList) }}
|
||||
{{ tierList.topicName || tierList.gameName || tierList.topicId || tierList.gameId }} · {{ props.tierListAuthorDisplayName(tierList) }}
|
||||
</div>
|
||||
<div class="tierAdminCard__meta">{{ props.fmt(tierList.updatedAt) }}</div>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ const props = defineProps({
|
||||
</div>
|
||||
<div class="tierAdminSection__actions">
|
||||
<button class="btn btn--ghost btn--small" @click="props.openTierListImportModal(tierList, tierList.extraItems)">추가 아이템 전체 가져오기</button>
|
||||
<button v-if="tierList.gameId === 'freeform'" class="btn btn--primary btn--small" @click="props.openTierListImportModal(tierList, tierList.extraItems)">
|
||||
<button v-if="(tierList.topicId || tierList.gameId) === 'freeform'" class="btn btn--primary btn--small" @click="props.openTierListImportModal(tierList, tierList.extraItems)">
|
||||
새 템플릿으로 가져오기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -21,14 +21,14 @@ export function useAdminTemplateRequests({
|
||||
type: request.type,
|
||||
status: request.status,
|
||||
thumbnailSrc: request.thumbnailSrc || '',
|
||||
draftGameId: request.draftGameId || '',
|
||||
draftGameName: request.draftGameName || '',
|
||||
draftGameIsPublic: !!request.draftGameIsPublic,
|
||||
draftGameId: request.draftTopicId || request.draftGameId || '',
|
||||
draftGameName: request.draftTopicName || request.draftGameName || '',
|
||||
draftGameIsPublic: !!(request.draftTopicIsPublic ?? request.draftGameIsPublic),
|
||||
sourceTierListId: request.sourceTierListId || '',
|
||||
sourceGameId: request.sourceGameId || '',
|
||||
sourceGameId: request.sourceTopicId || request.sourceGameId || '',
|
||||
sourceTierListTitle: request.sourceTierListTitle || '',
|
||||
targetGameId: request.targetGameId || '',
|
||||
targetGameName: request.targetGameName || '',
|
||||
targetGameId: request.targetTopicId || request.targetGameId || '',
|
||||
targetGameName: request.targetTopicName || request.targetGameName || '',
|
||||
requesterName: request.requesterName || '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,24 +175,4 @@ 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?topicId=${encodeURIComponent(gameId || '')}`),
|
||||
searchPublicTierLists: (gameId, q = '') =>
|
||||
request(`/api/tierlists/public?topicId=${encodeURIComponent(gameId || '')}&q=${encodeURIComponent(q || '')}`),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user