릴리스: v0.1.43 토스트와 즐겨찾기 추가

This commit is contained in:
2026-03-27 10:23:29 +09:00
parent 3bd9751621
commit 61fe758b7c
17 changed files with 559 additions and 209 deletions

View File

@@ -212,6 +212,18 @@ async function ensureSchema() {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
await query(`
CREATE TABLE IF NOT EXISTS favorite_tierlists (
user_id VARCHAR(64) NOT NULL,
tierlist_id VARCHAR(64) NOT NULL,
created_at BIGINT NOT NULL,
PRIMARY KEY (user_id, tierlist_id),
INDEX idx_favorite_tierlists_tierlist_id (tierlist_id),
CONSTRAINT fk_favorite_tierlists_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_favorite_tierlists_tierlist FOREIGN KEY (tierlist_id) REFERENCES tierlists(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
const tierListThumbnailColumns = await query("SHOW COLUMNS FROM tierlists LIKE 'thumbnail_src'")
if (!tierListThumbnailColumns.length) {
await query("ALTER TABLE tierlists ADD COLUMN thumbnail_src VARCHAR(255) NOT NULL DEFAULT '' AFTER title")
@@ -610,7 +622,51 @@ async function findUnusedCustomItems({ queryText = '' } = {}) {
.filter((item) => item.usageCount === 0)
}
async function listPublicTierLists(gameId) {
async function getFavoriteStatsForTierListIds(tierListIds, userId = '') {
const ids = Array.from(new Set((tierListIds || []).filter(Boolean)))
const countMap = new Map()
const favoritedSet = new Set()
if (!ids.length) return { countMap, favoritedSet }
const placeholders = ids.map(() => '?').join(', ')
const countRows = await query(
`
SELECT tierlist_id, COUNT(*) AS favorite_count
FROM favorite_tierlists
WHERE tierlist_id IN (${placeholders})
GROUP BY tierlist_id
`,
ids
)
countRows.forEach((row) => {
countMap.set(row.tierlist_id, Number(row.favorite_count || 0))
})
if (userId) {
const favoriteRows = await query(
`
SELECT tierlist_id
FROM favorite_tierlists
WHERE user_id = ? AND tierlist_id IN (${placeholders})
`,
[userId, ...ids]
)
favoriteRows.forEach((row) => favoritedSet.add(row.tierlist_id))
}
return { countMap, favoritedSet }
}
function applyFavoriteMetaToTierLists(tierLists, favoriteStats) {
return tierLists.map((tierList) => ({
...tierList,
favoriteCount: favoriteStats.countMap.get(tierList.id) || 0,
isFavorited: favoriteStats.favoritedSet.has(tierList.id),
}))
}
async function listPublicTierLists(gameId, currentUserId = '') {
const params = []
let whereClause = 'WHERE t.is_public = 1'
if (gameId) {
@@ -640,7 +696,7 @@ async function listPublicTierLists(gameId) {
params
)
return rows.map((row) => ({
const tierLists = rows.map((row) => ({
id: row.id,
gameId: row.game_id,
title: row.title,
@@ -652,6 +708,12 @@ async function listPublicTierLists(gameId) {
authorAccountName: getUserAccountName(row),
authorAvatarSrc: row.avatar_src || '',
}))
const favoriteStats = await getFavoriteStatsForTierListIds(
tierLists.map((tierList) => tierList.id),
currentUserId
)
return applyFavoriteMetaToTierLists(tierLists, favoriteStats)
}
async function listUserTierLists(userId) {
@@ -676,7 +738,7 @@ async function listUserTierLists(userId) {
[userId]
)
return rows.map((row) => ({
const tierLists = rows.map((row) => ({
id: row.id,
gameId: row.game_id,
title: row.title,
@@ -688,6 +750,12 @@ async function listUserTierLists(userId) {
authorAccountName: getUserAccountName(row),
authorAvatarSrc: row.avatar_src || '',
}))
const favoriteStats = await getFavoriteStatsForTierListIds(
tierLists.map((tierList) => tierList.id),
userId
)
return applyFavoriteMetaToTierLists(tierLists, favoriteStats)
}
function uniqueTierListItems(poolItems) {
@@ -704,7 +772,7 @@ function uniqueTierListItems(poolItems) {
return Array.from(map.values())
}
async function listAdminTierLists({ queryText = '', page = 1, limit = 50 } = {}) {
async function listAdminTierLists({ queryText = '', 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()
@@ -762,15 +830,20 @@ async function listAdminTierLists({ queryText = '', page = 1, limit = 50 } = {})
const total = allItems.length
const offset = (normalizedPage - 1) * normalizedLimit
const pagedTierLists = allItems.slice(offset, offset + normalizedLimit)
const favoriteStats = await getFavoriteStatsForTierListIds(
pagedTierLists.map((tierList) => tierList.id),
currentUserId
)
return {
tierLists: allItems.slice(offset, offset + normalizedLimit),
tierLists: applyFavoriteMetaToTierLists(pagedTierLists, favoriteStats),
total,
page: normalizedPage,
limit: normalizedLimit,
}
}
async function findTierListById(id) {
async function findTierListById(id, currentUserId = '') {
const rows = await query(
`
SELECT
@@ -797,7 +870,10 @@ async function findTierListById(id) {
`,
[id]
)
return mapTierListRow(rows[0])
const tierList = mapTierListRow(rows[0])
if (!tierList) return null
const favoriteStats = await getFavoriteStatsForTierListIds([tierList.id], currentUserId)
return applyFavoriteMetaToTierLists([tierList], favoriteStats)[0]
}
async function deleteTierList(id) {
@@ -832,7 +908,7 @@ async function deleteCustomItems(ids) {
}
async function saveTierList({ id, authorId, gameId, title, thumbnailSrc = '', description, isPublic, groups, pool }) {
const existing = id ? await findTierListById(id) : null
const existing = id ? await findTierListById(id, authorId) : null
if (existing) {
await query(
@@ -843,7 +919,7 @@ async function saveTierList({ id, authorId, gameId, title, thumbnailSrc = '', de
`,
[title, thumbnailSrc, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), now(), existing.id]
)
return findTierListById(existing.id)
return findTierListById(existing.id, authorId)
}
const createdAt = now()
@@ -856,7 +932,15 @@ async function saveTierList({ id, authorId, gameId, title, thumbnailSrc = '', de
`,
[id, authorId, gameId, title, thumbnailSrc, description || '', isPublic ? 1 : 0, serializeJson(groups), serializeJson(pool), createdAt, createdAt]
)
return findTierListById(id)
return findTierListById(id, authorId)
}
async function favoriteTierList({ userId, tierListId }) {
await query('INSERT IGNORE INTO favorite_tierlists (user_id, tierlist_id, created_at) VALUES (?, ?, ?)', [userId, tierListId, now()])
}
async function unfavoriteTierList({ userId, tierListId }) {
await query('DELETE FROM favorite_tierlists WHERE user_id = ? AND tierlist_id = ?', [userId, tierListId])
}
module.exports = {
@@ -890,6 +974,8 @@ module.exports = {
listUserTierLists,
listAdminTierLists,
findTierListById,
favoriteTierList,
unfavoriteTierList,
deleteTierList,
findCustomItemsByIds,
deleteCustomItems,