릴리스: v1.4.25 topic 응답/요청 키 정리
This commit is contained in:
@@ -87,7 +87,6 @@ function mapGameItemRow(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
topicId: row.topic_id,
|
||||
gameId: row.topic_id,
|
||||
src: row.src,
|
||||
label: row.label,
|
||||
displayOrder: row.display_order == null ? null : Number(row.display_order),
|
||||
@@ -137,8 +136,6 @@ function mapTierListRow(row) {
|
||||
authorAvatarSrc: row.avatar_src || '',
|
||||
topicId: row.topic_id,
|
||||
topicName: row.topic_name || '',
|
||||
gameId: row.topic_id,
|
||||
gameName: row.topic_name || '',
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
description: row.description || '',
|
||||
@@ -167,15 +164,11 @@ function mapTemplateRequestRow(row) {
|
||||
sourceTierListId: row.source_tierlist_id || '',
|
||||
sourceTopicId: row.source_topic_id,
|
||||
sourceTopicName: row.source_topic_name || '',
|
||||
sourceGameId: row.source_topic_id,
|
||||
sourceGameName: row.source_topic_name || '',
|
||||
sourceTierListTitle: row.title_snapshot || '',
|
||||
sourceDescription: row.description_snapshot || '',
|
||||
thumbnailSrc: row.thumbnail_src_snapshot || '',
|
||||
targetTopicId: row.target_topic_id || '',
|
||||
targetTopicName: row.target_topic_name || '',
|
||||
targetGameId: row.target_topic_id || '',
|
||||
targetGameName: row.target_topic_name || '',
|
||||
status: row.status,
|
||||
items: parseJson(row.items_json, []),
|
||||
snapshotGroups: parseJson(row.groups_json, []),
|
||||
@@ -1347,15 +1340,14 @@ async function clearImageOptimizationJobs({ month } = {}) {
|
||||
const result = await query('DELETE FROM image_optimization_jobs')
|
||||
return Number(result.affectedRows || 0)
|
||||
}
|
||||
async function createTopicItem({ id, topicId, gameId = topicId, src, label }) {
|
||||
async function createTopicItem({ id, topicId, src, label }) {
|
||||
const createdAt = now()
|
||||
const resolvedTopicId = topicId || gameId
|
||||
const minOrderRows = await query('SELECT MIN(display_order) AS min_display_order FROM topic_items WHERE topic_id = ?', [resolvedTopicId])
|
||||
const minOrderRows = await query('SELECT MIN(display_order) AS min_display_order FROM topic_items WHERE topic_id = ?', [topicId])
|
||||
const nextDisplayOrder =
|
||||
minOrderRows[0]?.min_display_order == null ? 0 : Number(minOrderRows[0].min_display_order) - 1
|
||||
await query('INSERT INTO topic_items (id, topic_id, src, label, display_order, created_at) VALUES (?, ?, ?, ?, ?, ?)', [
|
||||
id,
|
||||
resolvedTopicId,
|
||||
topicId,
|
||||
src,
|
||||
label,
|
||||
nextDisplayOrder,
|
||||
@@ -1456,8 +1448,8 @@ async function updateTopicDisplayOrder(topicIds) {
|
||||
await query('UPDATE topics SET display_rank = NULL WHERE id <> ?', [FREEFORM_TOPIC_ID])
|
||||
|
||||
await Promise.all(
|
||||
normalizedIds.map((gameId, index) =>
|
||||
query('UPDATE topics SET display_rank = ? WHERE id = ? AND id <> ?', [index + 1, gameId, FREEFORM_TOPIC_ID])
|
||||
normalizedIds.map((topicId, index) =>
|
||||
query('UPDATE topics SET display_rank = ? WHERE id = ? AND id <> ?', [index + 1, topicId, FREEFORM_TOPIC_ID])
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1679,8 +1671,8 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
|
||||
sourceType: 'template',
|
||||
sourceLabel: '관리자 템플릿',
|
||||
canDelete: true,
|
||||
sourceGameId: '',
|
||||
sourceGameName: '',
|
||||
sourceTopicId: '',
|
||||
sourceTopicName: '',
|
||||
isAssetLibraryItem: true,
|
||||
}))
|
||||
|
||||
@@ -1697,8 +1689,8 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
|
||||
sourceType: 'template',
|
||||
sourceLabel: '관리자 템플릿',
|
||||
canDelete: true,
|
||||
sourceGameId: row.topic_id,
|
||||
sourceGameName: row.topic_name || row.topic_id,
|
||||
sourceTopicId: row.topic_id,
|
||||
sourceTopicName: row.topic_name || row.topic_id,
|
||||
}))
|
||||
|
||||
const baseItems = [...customItems, ...templateItems, ...assetLibraryItems]
|
||||
@@ -1743,8 +1735,8 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, filterMod
|
||||
sourceType: entry.sourceType,
|
||||
ownerName: entry.ownerName,
|
||||
createdAt: entry.createdAt,
|
||||
sourceGameId: entry.sourceGameId || '',
|
||||
sourceGameName: entry.sourceGameName || '',
|
||||
sourceTopicId: entry.sourceTopicId || '',
|
||||
sourceTopicName: entry.sourceTopicName || '',
|
||||
usageCount: entry.usageCount || 0,
|
||||
linkedGames: entry.linkedGames || [],
|
||||
isAssetLibraryItem: !!entry.isAssetLibraryItem,
|
||||
@@ -1902,7 +1894,6 @@ async function listPublicTierLists(topicId, currentUserId = '', queryText = '')
|
||||
const tierLists = rows.map((row) => ({
|
||||
id: row.id,
|
||||
topicId: row.topic_id,
|
||||
gameId: row.topic_id,
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
createdAt: Number(row.created_at),
|
||||
@@ -2011,7 +2002,6 @@ async function listUserTierLists(userId) {
|
||||
const tierLists = rows.map((row) => ({
|
||||
id: row.id,
|
||||
topicId: row.topic_id,
|
||||
gameId: row.topic_id,
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
createdAt: Number(row.created_at),
|
||||
@@ -2057,11 +2047,11 @@ function getAutoThumbnailSrc(groups = [], pool = []) {
|
||||
return fallbackItem?.src || ''
|
||||
}
|
||||
|
||||
async function listAdminTierLists({ queryText = '', gameId = '', topicId = '', page = 1, limit = 50, currentUserId = '' } = {}) {
|
||||
async function listAdminTierLists({ queryText = '', topicId = '', page = 1, limit = 50, currentUserId = '' } = {}) {
|
||||
const normalizedLimit = Math.min(Math.max(Number(limit) || 50, 1), 200)
|
||||
const normalizedPage = Math.max(Number(page) || 1, 1)
|
||||
const hasQuery = !!(queryText || '').trim()
|
||||
const resolvedTopicId = (topicId || gameId || '').trim()
|
||||
const resolvedTopicId = (topicId || '').trim()
|
||||
const hasGameId = !!resolvedTopicId
|
||||
const search = `%${(queryText || '').trim()}%`
|
||||
const whereParts = []
|
||||
@@ -2144,9 +2134,9 @@ async function listAdminTierLists({ queryText = '', gameId = '', topicId = '', p
|
||||
}
|
||||
}
|
||||
|
||||
async function summarizeAdminTierLists({ queryText = '', gameId = '', topicId = '' } = {}) {
|
||||
async function summarizeAdminTierLists({ queryText = '', topicId = '' } = {}) {
|
||||
const hasQuery = !!(queryText || '').trim()
|
||||
const resolvedTopicId = (topicId || gameId || '').trim()
|
||||
const resolvedTopicId = (topicId || '').trim()
|
||||
const hasGameId = !!resolvedTopicId
|
||||
const search = `%${(queryText || '').trim()}%`
|
||||
const whereParts = []
|
||||
@@ -2245,10 +2235,8 @@ async function createTemplateRequest({
|
||||
type,
|
||||
requesterId,
|
||||
sourceTierListId = '',
|
||||
sourceGameId,
|
||||
targetGameId = '',
|
||||
sourceTopicId = sourceGameId,
|
||||
targetTopicId = targetGameId,
|
||||
sourceTopicId,
|
||||
targetTopicId = '',
|
||||
title,
|
||||
description = '',
|
||||
thumbnailSrc = '',
|
||||
@@ -2401,8 +2389,8 @@ async function updateTemplateRequestStatus({ id, status }) {
|
||||
return findTemplateRequestById(id)
|
||||
}
|
||||
|
||||
async function updateTemplateRequestTargetGame({ id, targetGameId }) {
|
||||
await query('UPDATE template_requests SET target_topic_id = ?, updated_at = ? WHERE id = ?', [targetGameId || '', now(), id])
|
||||
async function updateTemplateRequestTargetTopic({ id, targetTopicId }) {
|
||||
await query('UPDATE template_requests SET target_topic_id = ?, updated_at = ? WHERE id = ?', [targetTopicId || '', now(), id])
|
||||
return findTemplateRequestById(id)
|
||||
}
|
||||
|
||||
@@ -2452,8 +2440,7 @@ async function deleteCustomItems(ids) {
|
||||
async function saveTierList({
|
||||
id,
|
||||
authorId,
|
||||
gameId,
|
||||
topicId = gameId,
|
||||
topicId,
|
||||
title,
|
||||
thumbnailSrc = '',
|
||||
description,
|
||||
@@ -2504,8 +2491,7 @@ async function duplicateTierListForUser({ tierList, targetUserId }) {
|
||||
return saveTierList({
|
||||
id: duplicateId,
|
||||
authorId: targetUserId,
|
||||
gameId: tierList.topicId || tierList.gameId,
|
||||
topicId: tierList.topicId || tierList.gameId,
|
||||
topicId: tierList.topicId,
|
||||
title: copyTitle,
|
||||
thumbnailSrc: tierList.thumbnailSrc || '',
|
||||
description: tierList.description || '',
|
||||
@@ -2528,14 +2514,12 @@ async function unfavoriteTierList({ userId, tierListId }) {
|
||||
await query('DELETE FROM favorite_tierlists WHERE user_id = ? AND tierlist_id = ?', [userId, tierListId])
|
||||
}
|
||||
|
||||
async function favoriteTopic({ userId, topicId, gameId = topicId }) {
|
||||
const resolvedTopicId = topicId || gameId
|
||||
await query('INSERT IGNORE INTO favorite_topics (user_id, topic_id, created_at) VALUES (?, ?, ?)', [userId, resolvedTopicId, now()])
|
||||
async function favoriteTopic({ userId, topicId }) {
|
||||
await query('INSERT IGNORE INTO favorite_topics (user_id, topic_id, created_at) VALUES (?, ?, ?)', [userId, topicId, now()])
|
||||
}
|
||||
|
||||
async function unfavoriteTopic({ userId, topicId, gameId = topicId }) {
|
||||
const resolvedTopicId = topicId || gameId
|
||||
await query('DELETE FROM favorite_topics WHERE user_id = ? AND topic_id = ?', [userId, resolvedTopicId])
|
||||
async function unfavoriteTopic({ userId, topicId }) {
|
||||
await query('DELETE FROM favorite_topics WHERE user_id = ? AND topic_id = ?', [userId, topicId])
|
||||
}
|
||||
|
||||
const favoriteGame = favoriteTopic
|
||||
@@ -2629,5 +2613,6 @@ module.exports = {
|
||||
findTemplateRequestById,
|
||||
listAdminTemplateRequests,
|
||||
updateTemplateRequestStatus,
|
||||
updateTemplateRequestTargetGame,
|
||||
updateTemplateRequestTargetTopic,
|
||||
updateTemplateRequestTargetGame: updateTemplateRequestTargetTopic,
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ const {
|
||||
listAdminTemplateRequests,
|
||||
findTemplateRequestById,
|
||||
updateTemplateRequestStatus,
|
||||
updateTemplateRequestTargetGame,
|
||||
updateTemplateRequestTargetTopic,
|
||||
adminUpdateUser,
|
||||
adminUpdateUserPassword,
|
||||
adminDeleteUser,
|
||||
@@ -152,16 +152,16 @@ router.patch(['/games/:gameId', '/templates/:templateId'], requireAdmin, async (
|
||||
|
||||
router.patch(['/games/display-order', '/templates/display-order'], requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameIds: z.array(z.string().min(1)).max(50),
|
||||
topicIds: z.array(z.string().min(1)).max(50),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const templates = await listTopics('', { includePrivate: true })
|
||||
const validGameIds = new Set(templates.map((template) => template.id))
|
||||
const filteredIds = parsed.data.gameIds.filter((gameId) => validGameIds.has(gameId))
|
||||
const updatedGames = await updateTopicDisplayOrder(filteredIds)
|
||||
res.json({ games: updatedGames, templates: updatedGames })
|
||||
const validTopicIds = new Set(templates.map((template) => template.id))
|
||||
const filteredIds = parsed.data.topicIds.filter((topicId) => validTopicIds.has(topicId))
|
||||
const updatedTemplates = await updateTopicDisplayOrder(filteredIds)
|
||||
res.json({ templates: updatedTemplates })
|
||||
})
|
||||
|
||||
router.patch(['/games/:gameId/items/display-order', '/templates/:templateId/items/display-order'], requireAdmin, async (req, res) => {
|
||||
@@ -244,7 +244,7 @@ router.get(['/games/:gameId/items/:itemId/usage', '/templates/:templateId/items/
|
||||
const template = await findTopicById(getTemplateIdParam(req))
|
||||
if (!template) return res.status(404).json({ error: 'not_found' })
|
||||
const item = await findTopicItemById(req.params.itemId)
|
||||
if (!item || item.gameId !== template.id) return res.status(404).json({ error: 'not_found' })
|
||||
if (!item || item.topicId !== template.id) return res.status(404).json({ error: 'not_found' })
|
||||
const usage = await countTierListsUsingTopicItem(req.params.itemId)
|
||||
res.json({ usage })
|
||||
})
|
||||
@@ -258,7 +258,7 @@ router.patch(['/games/:gameId/items/:itemId', '/templates/:templateId/items/:ite
|
||||
if (!template) return res.status(404).json({ error: 'not_found' })
|
||||
|
||||
const updated = await updateTopicItemLabel(req.params.itemId, parsed.data.label)
|
||||
if (!updated || updated.gameId !== template.id) return res.status(404).json({ error: 'not_found' })
|
||||
if (!updated || updated.topicId !== template.id) return res.status(404).json({ error: 'not_found' })
|
||||
res.json({ item: updated })
|
||||
})
|
||||
|
||||
@@ -319,7 +319,6 @@ router.get('/tierlists', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
q: z.string().trim().max(120).optional().default(''),
|
||||
topicId: z.string().trim().max(120).optional().default(''),
|
||||
gameId: z.string().trim().max(120).optional().default(''),
|
||||
page: z.coerce.number().int().min(1).optional().default(1),
|
||||
limit: z.coerce.number().int().min(1).max(200).optional().default(50),
|
||||
})
|
||||
@@ -328,8 +327,7 @@ router.get('/tierlists', requireAdmin, async (req, res) => {
|
||||
|
||||
const result = await listAdminTierLists({
|
||||
queryText: parsed.data.q,
|
||||
topicId: parsed.data.topicId || parsed.data.gameId,
|
||||
gameId: parsed.data.gameId,
|
||||
topicId: parsed.data.topicId,
|
||||
page: parsed.data.page,
|
||||
limit: parsed.data.limit,
|
||||
currentUserId: req.session?.userId || '',
|
||||
@@ -341,15 +339,13 @@ router.get('/tierlists/stats', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
q: z.string().trim().max(120).optional().default(''),
|
||||
topicId: z.string().trim().max(120).optional().default(''),
|
||||
gameId: z.string().trim().max(120).optional().default(''),
|
||||
})
|
||||
const parsed = schema.safeParse(req.query)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const result = await summarizeAdminTierLists({
|
||||
queryText: parsed.data.q,
|
||||
topicId: parsed.data.topicId || parsed.data.gameId,
|
||||
gameId: parsed.data.gameId,
|
||||
topicId: parsed.data.topicId,
|
||||
})
|
||||
res.json(result)
|
||||
})
|
||||
@@ -646,13 +642,13 @@ router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
|
||||
|
||||
router.post('/custom-items/:itemId/promote', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameId: z.string().min(1),
|
||||
topicId: z.string().min(1),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const template = await findTopicById(parsed.data.gameId)
|
||||
if (!template) return res.status(404).json({ error: 'game_not_found' })
|
||||
const template = await findTopicById(parsed.data.topicId)
|
||||
if (!template) return res.status(404).json({ error: 'topic_not_found' })
|
||||
|
||||
const customItem = await findCustomItemById(req.params.itemId)
|
||||
const templateItem = customItem ? null : await findTopicItemById(req.params.itemId)
|
||||
@@ -675,14 +671,14 @@ router.post('/custom-items/:itemId/promote', requireAdmin, async (req, res) => {
|
||||
|
||||
router.post('/tierlists/:tierListId/promote-items', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameId: z.string().min(1),
|
||||
topicId: z.string().min(1),
|
||||
itemIds: z.array(z.string().min(1)).optional().default([]),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const template = await findTopicById(parsed.data.gameId)
|
||||
if (!template) return res.status(404).json({ error: 'game_not_found' })
|
||||
const template = await findTopicById(parsed.data.topicId)
|
||||
if (!template) return res.status(404).json({ error: 'topic_not_found' })
|
||||
|
||||
const tierList = await findTierListById(req.params.tierListId)
|
||||
if (!tierList) return res.status(404).json({ error: 'not_found' })
|
||||
@@ -697,15 +693,15 @@ router.post('/tierlists/:tierListId/promote-items', requireAdmin, async (req, re
|
||||
|
||||
router.post('/tierlists/:tierListId/create-game-template', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameId: z.string().trim().min(1).max(120),
|
||||
topicId: z.string().trim().min(1).max(120),
|
||||
name: z.string().trim().min(1).max(120),
|
||||
itemIds: z.array(z.string().min(1)).optional().default([]),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const exists = await findTopicById(parsed.data.gameId)
|
||||
if (exists) return res.status(409).json({ error: 'game_id_taken' })
|
||||
const exists = await findTopicById(parsed.data.topicId)
|
||||
if (exists) return res.status(409).json({ error: 'topic_id_taken' })
|
||||
|
||||
const tierList = await findTierListById(req.params.tierListId)
|
||||
if (!tierList) return res.status(404).json({ error: 'not_found' })
|
||||
@@ -715,7 +711,7 @@ router.post('/tierlists/:tierListId/create-game-template', requireAdmin, async (
|
||||
...tierList,
|
||||
pool: parsed.data.itemIds.length ? (tierList.pool || []).filter((item) => parsed.data.itemIds.includes(item.id)) : tierList.pool,
|
||||
},
|
||||
templateId: parsed.data.gameId,
|
||||
templateId: parsed.data.topicId,
|
||||
templateName: parsed.data.name,
|
||||
})
|
||||
res.json(result)
|
||||
@@ -755,9 +751,9 @@ router.post('/template-requests/:requestId/approve', requireAdmin, async (req, r
|
||||
if (templateRequest.status !== 'pending') return res.status(409).json({ error: 'request_already_handled' })
|
||||
|
||||
if (templateRequest.type === 'update') {
|
||||
const targetGameId = templateRequest.targetGameId || templateRequest.sourceGameId
|
||||
const template = await findTopicById(targetGameId)
|
||||
if (!template) return res.status(404).json({ error: 'game_not_found' })
|
||||
const targetTopicId = templateRequest.targetTopicId || templateRequest.sourceTopicId
|
||||
const template = await findTopicById(targetTopicId)
|
||||
if (!template) return res.status(404).json({ error: 'topic_not_found' })
|
||||
|
||||
const items = await promoteSnapshotItemsToTemplate({
|
||||
items: templateRequest.items || [],
|
||||
@@ -768,18 +764,18 @@ router.post('/template-requests/:requestId/approve', requireAdmin, async (req, r
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
gameId: z.string().trim().min(1).max(120),
|
||||
topicId: z.string().trim().min(1).max(120),
|
||||
name: z.string().trim().min(1).max(120),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const exists = await findTopicById(parsed.data.gameId)
|
||||
if (exists) return res.status(409).json({ error: 'game_id_taken' })
|
||||
const exists = await findTopicById(parsed.data.topicId)
|
||||
if (exists) return res.status(409).json({ error: 'topic_id_taken' })
|
||||
|
||||
const result = await createTemplateFromRequest({
|
||||
templateRequest,
|
||||
templateId: parsed.data.gameId,
|
||||
templateId: parsed.data.topicId,
|
||||
templateName: parsed.data.name,
|
||||
})
|
||||
const request = await updateTemplateRequestStatus({ id: templateRequest.id, status: 'approved' })
|
||||
@@ -793,10 +789,10 @@ router.post('/template-requests/:requestId/review', requireAdmin, async (req, re
|
||||
return res.status(409).json({ error: 'request_already_handled' })
|
||||
}
|
||||
|
||||
if (templateRequest.type === 'create' && templateRequest.targetGameId && !templateRequest.targetGameName) {
|
||||
templateRequest = await updateTemplateRequestTargetGame({
|
||||
if (templateRequest.type === 'create' && templateRequest.targetTopicId && !templateRequest.targetTopicName) {
|
||||
templateRequest = await updateTemplateRequestTargetTopic({
|
||||
id: templateRequest.id,
|
||||
targetGameId: '',
|
||||
targetTopicId: '',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -810,7 +806,7 @@ router.post('/template-requests/:requestId/review', requireAdmin, async (req, re
|
||||
|
||||
router.post('/template-requests/:requestId/link-game', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameId: z.string().trim().min(1).max(120),
|
||||
topicId: z.string().trim().min(1).max(120),
|
||||
})
|
||||
const parsed = schema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
@@ -822,19 +818,19 @@ router.post('/template-requests/:requestId/link-game', requireAdmin, async (req,
|
||||
return res.status(409).json({ error: 'request_already_handled' })
|
||||
}
|
||||
|
||||
const template = await findTopicById(parsed.data.gameId)
|
||||
if (!template) return res.status(404).json({ error: 'game_not_found' })
|
||||
const template = await findTopicById(parsed.data.topicId)
|
||||
if (!template) return res.status(404).json({ error: 'topic_not_found' })
|
||||
|
||||
const request = await updateTemplateRequestTargetGame({
|
||||
const request = await updateTemplateRequestTargetTopic({
|
||||
id: templateRequest.id,
|
||||
targetGameId: template.id,
|
||||
targetTopicId: template.id,
|
||||
})
|
||||
res.json({ request })
|
||||
})
|
||||
|
||||
router.post('/template-requests/:requestId/promote-items', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
gameId: z.string().trim().min(1).max(120),
|
||||
topicId: z.string().trim().min(1).max(120),
|
||||
itemIds: z.array(z.string().min(1)).optional().default([]),
|
||||
itemSrcs: z.array(z.string().min(1)).optional().default([]),
|
||||
itemLabels: z.record(z.string(), z.string().min(1).max(60)).optional().default({}),
|
||||
@@ -848,8 +844,8 @@ router.post('/template-requests/:requestId/promote-items', requireAdmin, async (
|
||||
return res.status(409).json({ error: 'request_already_handled' })
|
||||
}
|
||||
|
||||
const template = await findTopicById(parsed.data.gameId)
|
||||
if (!template) return res.status(404).json({ error: 'game_not_found' })
|
||||
const template = await findTopicById(parsed.data.topicId)
|
||||
if (!template) return res.status(404).json({ error: 'topic_not_found' })
|
||||
|
||||
const promotableItems = pickTemplateRequestItems(templateRequest, parsed.data.itemIds, parsed.data.itemLabels, parsed.data.itemSrcs)
|
||||
if (!promotableItems.length) {
|
||||
@@ -865,7 +861,7 @@ router.post('/template-requests/:requestId/promote-items', requireAdmin, async (
|
||||
} catch (error) {
|
||||
console.error('[admin] template request promote-items failed', {
|
||||
requestId: templateRequest.id,
|
||||
gameId: template.id,
|
||||
topicId: template.id,
|
||||
itemCount: promotableItems.length,
|
||||
message: error?.message || 'unknown_error',
|
||||
code: error?.code || '',
|
||||
|
||||
@@ -61,7 +61,6 @@ const thumbnailUpload = createMemoryUpload(multer, { fileSize: 8 * 1024 * 1024 }
|
||||
const templateRequestSchema = z.object({
|
||||
type: z.enum(['create', 'update']),
|
||||
sourceTierListId: z.string().max(64).optional().default(''),
|
||||
gameId: z.string().min(1).max(120).optional(),
|
||||
topicId: z.string().min(1).max(120).optional(),
|
||||
requestTitle: z.string().trim().min(1).max(120),
|
||||
requestDescription: z.string().trim().min(1).max(1000),
|
||||
@@ -74,7 +73,7 @@ const templateRequestSchema = z.object({
|
||||
name: z.string().min(1).max(16),
|
||||
itemIds: z.array(z.string()).optional().default([]),
|
||||
}).passthrough().superRefine((value, ctx) => {
|
||||
if (!(value.topicId || value.gameId)) {
|
||||
if (!value.topicId) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'topicId_required', path: ['topicId'] })
|
||||
}
|
||||
})
|
||||
@@ -91,7 +90,6 @@ const templateRequestSchema = z.object({
|
||||
|
||||
const tierListUpsertSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
gameId: z.string().min(1).optional(),
|
||||
topicId: z.string().min(1).optional(),
|
||||
title: z.string().min(1).max(120),
|
||||
thumbnailSrc: z.string().max(255).optional().default(''),
|
||||
@@ -118,13 +116,13 @@ const tierListUpsertSchema = z.object({
|
||||
})
|
||||
),
|
||||
}).superRefine((value, ctx) => {
|
||||
if (!(value.topicId || value.gameId)) {
|
||||
if (!value.topicId) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'topicId_required', path: ['topicId'] })
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/public', async (req, res) => {
|
||||
const topicId = typeof req.query.topicId === 'string' ? req.query.topicId : req.query.gameId
|
||||
const topicId = typeof req.query.topicId === 'string' ? req.query.topicId : ''
|
||||
const queryText = typeof req.query.q === 'string' ? req.query.q : ''
|
||||
const lists = await listPublicTierLists(topicId, req.session?.userId || '', queryText)
|
||||
res.json({ tierLists: lists })
|
||||
@@ -236,7 +234,7 @@ router.post('/template-request', requireAuth, async (req, res) => {
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const payload = parsed.data
|
||||
const topicId = payload.topicId || payload.gameId
|
||||
const topicId = payload.topicId
|
||||
const normalizedBoardItems = payload.boardItems.map(normalizePoolItem)
|
||||
const customItems = normalizedBoardItems.filter((item) => item?.origin === 'custom')
|
||||
if (!customItems.length) return res.status(400).json({ error: 'custom_items_required' })
|
||||
@@ -285,7 +283,7 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
const parsed = tierListUpsertSchema.safeParse(req.body)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
const payload = parsed.data
|
||||
const topicId = payload.topicId || payload.gameId
|
||||
const topicId = payload.topicId
|
||||
const normalizedPool = payload.pool.map(normalizePoolItem)
|
||||
|
||||
let existing = null
|
||||
@@ -296,7 +294,7 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
const updated = await saveTierList({
|
||||
id: existing.id,
|
||||
authorId: existing.authorId,
|
||||
topicId: existing.topicId || existing.gameId,
|
||||
topicId: existing.topicId,
|
||||
title: payload.title,
|
||||
thumbnailSrc: payload.thumbnailSrc || '',
|
||||
description: payload.description || '',
|
||||
|
||||
Reference in New Issue
Block a user