릴리스: v0.1.44 공개 목록 검색과 즐겨찾기 페이지 추가

This commit is contained in:
2026-03-27 10:35:16 +09:00
parent 61fe758b7c
commit 7de5e96c4c
12 changed files with 433 additions and 11 deletions

View File

@@ -666,13 +666,18 @@ function applyFavoriteMetaToTierLists(tierLists, favoriteStats) {
}))
}
async function listPublicTierLists(gameId, currentUserId = '') {
async function listPublicTierLists(gameId, currentUserId = '', queryText = '') {
const params = []
let whereClause = 'WHERE t.is_public = 1'
if (gameId) {
whereClause += ' AND t.game_id = ?'
params.push(gameId)
}
if ((queryText || '').trim()) {
const search = `%${queryText.trim()}%`
whereClause += ' AND (t.title LIKE ? OR u.nickname LIKE ? OR u.email LIKE ?)'
params.push(search, search, search)
}
const rows = await query(
`
@@ -716,6 +721,67 @@ async function listPublicTierLists(gameId, currentUserId = '') {
return applyFavoriteMetaToTierLists(tierLists, favoriteStats)
}
async function listFavoriteTierLists(userId, { queryText = '', sort = 'favorited' } = {}) {
const allowedSort = new Set(['favorited', 'updated', 'favorites'])
const normalizedSort = allowedSort.has(sort) ? sort : 'favorited'
const params = [userId]
let whereClause = 'WHERE f.user_id = ?'
if ((queryText || '').trim()) {
const search = `%${queryText.trim()}%`
whereClause += ' AND (t.title LIKE ? OR g.name LIKE ? OR u.nickname LIKE ? OR u.email LIKE ?)'
params.push(search, search, search, search)
}
const orderClause =
normalizedSort === 'updated'
? 'ORDER BY t.updated_at DESC, f.created_at DESC'
: normalizedSort === 'favorites'
? 'ORDER BY favorite_count DESC, t.updated_at DESC'
: 'ORDER BY f.created_at DESC, t.updated_at DESC'
const rows = await query(
`
SELECT
t.id,
t.author_id,
t.game_id,
g.name AS game_name,
t.title,
t.thumbnail_src,
t.description,
t.is_public,
t.groups_json,
t.pool_json,
t.created_at,
t.updated_at,
f.created_at AS favorited_at,
u.nickname,
u.email,
u.avatar_src,
(
SELECT COUNT(*)
FROM favorite_tierlists ff
WHERE ff.tierlist_id = t.id
) AS favorite_count
FROM favorite_tierlists f
INNER JOIN tierlists t ON t.id = f.tierlist_id
INNER JOIN users u ON u.id = t.author_id
INNER JOIN games g ON g.id = t.game_id
${whereClause}
${orderClause}
`,
params
)
return rows.map((row) => ({
...mapTierListRow(row),
favoritedAt: Number(row.favorited_at || 0),
favoriteCount: Number(row.favorite_count || 0),
isFavorited: true,
}))
}
async function listUserTierLists(userId) {
const rows = await query(
`
@@ -971,6 +1037,7 @@ module.exports = {
listCustomItems,
findUnusedCustomItems,
listPublicTierLists,
listFavoriteTierLists,
listUserTierLists,
listAdminTierLists,
findTierListById,

View File

@@ -6,6 +6,7 @@ const { nanoid } = require('nanoid')
const {
findTierListById,
listPublicTierLists,
listFavoriteTierLists,
listUserTierLists,
deleteTierList,
saveTierList,
@@ -89,7 +90,8 @@ const tierListUpsertSchema = z.object({
router.get('/public', async (req, res) => {
const gameId = req.query.gameId
const lists = await listPublicTierLists(gameId, req.session?.userId || '')
const queryText = typeof req.query.q === 'string' ? req.query.q : ''
const lists = await listPublicTierLists(gameId, req.session?.userId || '', queryText)
res.json({ tierLists: lists })
})
@@ -98,6 +100,13 @@ router.get('/me', requireAuth, async (req, res) => {
res.json({ tierLists: lists })
})
router.get('/favorites/me', requireAuth, async (req, res) => {
const queryText = typeof req.query.q === 'string' ? req.query.q : ''
const sort = typeof req.query.sort === 'string' ? req.query.sort : 'favorited'
const lists = await listFavoriteTierLists(req.session.userId, { queryText, sort })
res.json({ tierLists: lists })
})
router.get('/:id', async (req, res) => {
const t = await findTierListById(req.params.id, req.session?.userId || '')
if (!t) return res.status(404).json({ error: 'not_found' })