관리자 인기 지표와 회원 핵심 지표를 보강한다

This commit is contained in:
2026-04-03 12:48:29 +09:00
parent f9767624d1
commit f1756a4ff1
11 changed files with 145 additions and 65 deletions

View File

@@ -64,6 +64,8 @@ function mapUserRow(row) {
avatarSrc: row.avatar_src || '',
createdAt: Number(row.created_at),
tierListCount: Number(row.tierlist_count || 0),
followerCount: Number(row.follower_count || 0),
receivedFavoriteCount: Number(row.received_favorite_count || 0),
recentActivityAt: Number(row.recent_activity_at || row.created_at || 0),
}
}
@@ -839,6 +841,14 @@ async function listUsers({ queryText = '', sort = 'recent', direction = 'desc' }
? isAsc
? 'tierlist_count ASC, recent_activity_at ASC, u.email ASC'
: 'tierlist_count DESC, recent_activity_at DESC, u.email ASC'
: sort === 'followers'
? isAsc
? 'follower_count ASC, recent_activity_at ASC, u.email ASC'
: 'follower_count DESC, recent_activity_at DESC, u.email ASC'
: sort === 'favorites'
? isAsc
? 'received_favorite_count ASC, recent_activity_at ASC, u.email ASC'
: 'received_favorite_count DESC, recent_activity_at DESC, u.email ASC'
: isAsc
? 'recent_activity_at ASC, u.created_at ASC, u.email ASC'
: 'recent_activity_at DESC, u.created_at ASC, u.email ASC'
@@ -853,13 +863,17 @@ async function listUsers({ queryText = '', sort = 'recent', direction = 'desc' }
u.is_admin,
u.avatar_src,
u.created_at,
COUNT(t.id) AS tierlist_count,
COUNT(DISTINCT t.id) AS tierlist_count,
COUNT(DISTINCT uf.follower_id) AS follower_count,
COUNT(DISTINCT ft.user_id, ft.tierlist_id) AS received_favorite_count,
GREATEST(
u.created_at,
COALESCE(MAX(t.updated_at), 0)
) AS recent_activity_at
FROM users u
LEFT JOIN tierlists t ON t.author_id = u.id
LEFT JOIN user_follows uf ON uf.following_id = u.id
LEFT JOIN favorite_tierlists ft ON ft.tierlist_id = t.id
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
GROUP BY u.id, u.email, u.nickname, u.email_verified, u.is_admin, u.avatar_src, u.created_at
ORDER BY ${orderBy}
@@ -2414,9 +2428,18 @@ function getAutoThumbnailSrc(groups = [], pool = []) {
return fallbackItem?.src || ''
}
async function listAdminTierLists({ queryText = '', topicId = '', page = 1, limit = 50, currentUserId = '' } = {}) {
async function listAdminTierLists({
queryText = '',
topicId = '',
page = 1,
limit = 50,
sort = 'recent',
minFavorites = 0,
currentUserId = '',
} = {}) {
const normalizedLimit = Math.min(Math.max(Number(limit) || 50, 1), 200)
const normalizedPage = Math.max(Number(page) || 1, 1)
const normalizedMinFavorites = Math.max(Number(minFavorites) || 0, 0)
const hasQuery = !!(queryText || '').trim()
const resolvedTopicId = (topicId || '').trim()
const hasTopicId = !!resolvedTopicId
@@ -2477,7 +2500,7 @@ async function listAdminTierLists({ queryText = '', topicId = '', page = 1, limi
params
)
const allItems = rows.map((row) => {
const baseItems = rows.map((row) => {
const tierList = mapTierListRow(row)
const poolItems = uniqueTierListItems(tierList.pool)
const extraItems = poolItems.filter((item) => item.origin === 'custom')
@@ -2488,23 +2511,47 @@ async function listAdminTierLists({ queryText = '', topicId = '', page = 1, limi
extraItems,
}
})
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),
baseItems.map((tierList) => tierList.id),
currentUserId
)
const filteredItems = applyFavoriteMetaToTierLists(baseItems, favoriteStats)
.filter((tierList) => Number(tierList.favoriteCount || 0) >= normalizedMinFavorites)
.sort((a, b) => {
if (sort === 'favorites') {
return (
Number(b.favoriteCount || 0) - Number(a.favoriteCount || 0) ||
Number(b.updatedAt || 0) - Number(a.updatedAt || 0) ||
Number(b.createdAt || 0) - Number(a.createdAt || 0)
)
}
if (sort === 'created') {
return (
Number(b.createdAt || 0) - Number(a.createdAt || 0) ||
Number(b.updatedAt || 0) - Number(a.updatedAt || 0) ||
String(a.title || '').localeCompare(String(b.title || ''))
)
}
return (
Number(b.updatedAt || 0) - Number(a.updatedAt || 0) ||
Number(b.createdAt || 0) - Number(a.createdAt || 0) ||
String(a.title || '').localeCompare(String(b.title || ''))
)
})
const total = filteredItems.length
const offset = (normalizedPage - 1) * normalizedLimit
const pagedTierLists = filteredItems.slice(offset, offset + normalizedLimit)
return {
tierLists: applyFavoriteMetaToTierLists(pagedTierLists, favoriteStats),
tierLists: pagedTierLists,
total,
page: normalizedPage,
limit: normalizedLimit,
}
}
async function summarizeAdminTierLists({ queryText = '', topicId = '' } = {}) {
async function summarizeAdminTierLists({ queryText = '', topicId = '', minFavorites = 0 } = {}) {
const normalizedMinFavorites = Math.max(Number(minFavorites) || 0, 0)
const hasQuery = !!(queryText || '').trim()
const resolvedTopicId = (topicId || '').trim()
const hasTopicId = !!resolvedTopicId
@@ -2532,6 +2579,7 @@ async function summarizeAdminTierLists({ queryText = '', topicId = '' } = {}) {
const rows = await query(
`
SELECT t.is_public, t.is_featured
, t.id
FROM tierlists t
INNER JOIN users u ON u.id = t.author_id
INNER JOIN topics tp ON tp.id = t.topic_id
@@ -2540,9 +2588,16 @@ async function summarizeAdminTierLists({ queryText = '', topicId = '' } = {}) {
params
)
const total = rows.length
const publicCount = rows.filter((row) => Number(row.is_public) === 1).length
const featuredCount = rows.filter((row) => Number(row.is_featured) === 1).length
const favoriteStats = normalizedMinFavorites > 0
? await getFavoriteStatsForTierListIds(rows.map((row) => row.id), '')
: { countMap: new Map(), favoritedSet: new Set() }
const scopedRows = normalizedMinFavorites > 0
? rows.filter((row) => Number(favoriteStats.countMap.get(row.id) || 0) >= normalizedMinFavorites)
: rows
const total = scopedRows.length
const publicCount = scopedRows.filter((row) => Number(row.is_public) === 1).length
const featuredCount = scopedRows.filter((row) => Number(row.is_featured) === 1).length
return {
total,
publicCount,