본인 티어표 복사 복구와 팔로우 피드 추가
This commit is contained in:
@@ -426,6 +426,18 @@ async function ensureSchema() {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
`)
|
||||
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS user_follows (
|
||||
follower_id VARCHAR(64) NOT NULL,
|
||||
following_id VARCHAR(64) NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
PRIMARY KEY (follower_id, following_id),
|
||||
INDEX idx_user_follows_following (following_id, created_at),
|
||||
CONSTRAINT fk_user_follows_follower FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_follows_following FOREIGN KEY (following_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
`)
|
||||
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS image_assets (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
@@ -2182,6 +2194,198 @@ async function listUserTierLists(userId) {
|
||||
return applyFavoriteMetaToTierLists(tierLists, favoriteStats)
|
||||
}
|
||||
|
||||
async function findUserProfileById(userId, currentUserId = '') {
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u.nickname,
|
||||
u.avatar_src,
|
||||
u.created_at,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM tierlists t
|
||||
WHERE t.author_id = u.id AND t.is_public = 1
|
||||
) AS public_tierlist_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM user_follows uf
|
||||
WHERE uf.following_id = u.id
|
||||
) AS follower_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM user_follows uf
|
||||
WHERE uf.follower_id = u.id
|
||||
) AS following_count,
|
||||
${
|
||||
currentUserId
|
||||
? `EXISTS(
|
||||
SELECT 1
|
||||
FROM user_follows uf
|
||||
WHERE uf.follower_id = ? AND uf.following_id = u.id
|
||||
)`
|
||||
: '0'
|
||||
} AS is_following
|
||||
FROM users u
|
||||
WHERE u.id = ?
|
||||
LIMIT 1
|
||||
`,
|
||||
currentUserId ? [currentUserId, userId] : [userId]
|
||||
)
|
||||
const row = rows[0]
|
||||
if (!row) return null
|
||||
return {
|
||||
id: row.id,
|
||||
nickname: row.nickname || '',
|
||||
accountName: getUserAccountName(row),
|
||||
avatarSrc: row.avatar_src || '',
|
||||
createdAt: Number(row.created_at || 0),
|
||||
publicTierListCount: Number(row.public_tierlist_count || 0),
|
||||
followerCount: Number(row.follower_count || 0),
|
||||
followingCount: Number(row.following_count || 0),
|
||||
isFollowing: !!row.is_following,
|
||||
isSelf: !!currentUserId && currentUserId === row.id,
|
||||
}
|
||||
}
|
||||
|
||||
async function followUser({ followerId, followingId }) {
|
||||
await query('INSERT IGNORE INTO user_follows (follower_id, following_id, created_at) VALUES (?, ?, ?)', [
|
||||
followerId,
|
||||
followingId,
|
||||
now(),
|
||||
])
|
||||
}
|
||||
|
||||
async function unfollowUser({ followerId, followingId }) {
|
||||
await query('DELETE FROM user_follows WHERE follower_id = ? AND following_id = ?', [followerId, followingId])
|
||||
}
|
||||
|
||||
async function listPublicTierListsByAuthor(authorId, currentUserId = '', queryText = '') {
|
||||
const params = [authorId]
|
||||
let whereClause = 'WHERE t.author_id = ? AND t.is_public = 1'
|
||||
if ((queryText || '').trim()) {
|
||||
const search = `%${queryText.trim()}%`
|
||||
whereClause += ' AND (t.title LIKE ? OR tp.name LIKE ?)'
|
||||
params.push(search, search)
|
||||
}
|
||||
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.id,
|
||||
t.topic_id,
|
||||
tp.name AS topic_name,
|
||||
t.title,
|
||||
t.thumbnail_src,
|
||||
t.is_public,
|
||||
t.is_featured,
|
||||
t.featured_at,
|
||||
t.featured_by,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
t.author_id,
|
||||
u.nickname,
|
||||
u.email,
|
||||
u.avatar_src
|
||||
FROM tierlists t
|
||||
INNER JOIN users u ON u.id = t.author_id
|
||||
INNER JOIN topics tp ON tp.id = t.topic_id
|
||||
${whereClause}
|
||||
ORDER BY t.is_featured DESC, t.featured_at DESC, t.updated_at DESC
|
||||
LIMIT 200
|
||||
`,
|
||||
params
|
||||
)
|
||||
|
||||
const tierLists = rows.map((row) => ({
|
||||
id: row.id,
|
||||
topicId: row.topic_id,
|
||||
topicName: row.topic_name || '',
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
isPublic: !!row.is_public,
|
||||
isFeatured: !!row.is_featured,
|
||||
featuredAt: Number(row.featured_at || 0),
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
authorId: row.author_id,
|
||||
authorName: getUserDisplayName(row),
|
||||
authorAccountName: getUserAccountName(row),
|
||||
authorAvatarSrc: row.avatar_src || '',
|
||||
}))
|
||||
|
||||
const favoriteStats = await getFavoriteStatsForTierListIds(
|
||||
tierLists.map((tierList) => tierList.id),
|
||||
currentUserId
|
||||
)
|
||||
return applyFavoriteMetaToTierLists(tierLists, favoriteStats)
|
||||
}
|
||||
|
||||
async function listFollowingTierLists(userId, queryText = '') {
|
||||
const params = [userId]
|
||||
let whereClause = 'WHERE uf.follower_id = ? AND t.is_public = 1'
|
||||
if ((queryText || '').trim()) {
|
||||
const search = `%${queryText.trim()}%`
|
||||
whereClause += ' AND (t.title LIKE ? OR tp.name LIKE ? OR u.nickname LIKE ? OR u.email LIKE ?)'
|
||||
params.push(search, search, search, search)
|
||||
}
|
||||
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT
|
||||
t.id,
|
||||
t.topic_id,
|
||||
tp.name AS topic_name,
|
||||
t.title,
|
||||
t.thumbnail_src,
|
||||
t.is_public,
|
||||
t.is_featured,
|
||||
t.featured_at,
|
||||
t.featured_by,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
t.author_id,
|
||||
uf.created_at AS followed_at,
|
||||
u.nickname,
|
||||
u.email,
|
||||
u.avatar_src
|
||||
FROM user_follows uf
|
||||
INNER JOIN tierlists t ON t.author_id = uf.following_id
|
||||
INNER JOIN users u ON u.id = t.author_id
|
||||
INNER JOIN topics tp ON tp.id = t.topic_id
|
||||
${whereClause}
|
||||
ORDER BY t.updated_at DESC, uf.created_at DESC
|
||||
LIMIT 200
|
||||
`,
|
||||
params
|
||||
)
|
||||
|
||||
const tierLists = rows.map((row) => ({
|
||||
id: row.id,
|
||||
topicId: row.topic_id,
|
||||
topicName: row.topic_name || '',
|
||||
title: row.title,
|
||||
thumbnailSrc: row.thumbnail_src || '',
|
||||
isPublic: !!row.is_public,
|
||||
isFeatured: !!row.is_featured,
|
||||
featuredAt: Number(row.featured_at || 0),
|
||||
createdAt: Number(row.created_at),
|
||||
updatedAt: Number(row.updated_at),
|
||||
authorId: row.author_id,
|
||||
authorName: getUserDisplayName(row),
|
||||
authorAccountName: getUserAccountName(row),
|
||||
authorAvatarSrc: row.avatar_src || '',
|
||||
isFavorited: false,
|
||||
}))
|
||||
|
||||
const favoriteStats = await getFavoriteStatsForTierListIds(
|
||||
tierLists.map((tierList) => tierList.id),
|
||||
userId
|
||||
)
|
||||
return applyFavoriteMetaToTierLists(tierLists, favoriteStats)
|
||||
}
|
||||
|
||||
function uniqueTierListItems(poolItems) {
|
||||
const map = new Map()
|
||||
;(poolItems || []).forEach((item) => {
|
||||
@@ -2726,6 +2930,7 @@ module.exports = {
|
||||
findUserByEmail,
|
||||
findUserByNickname,
|
||||
findUserById,
|
||||
findUserProfileById,
|
||||
createUser,
|
||||
updateUserPassword,
|
||||
verifyUserEmail,
|
||||
@@ -2779,6 +2984,8 @@ module.exports = {
|
||||
listCustomItems,
|
||||
findUnusedCustomItems,
|
||||
listPublicTierLists,
|
||||
listPublicTierListsByAuthor,
|
||||
listFollowingTierLists,
|
||||
listFavoriteTierLists,
|
||||
listUserTierLists,
|
||||
listAdminTierLists,
|
||||
@@ -2788,6 +2995,8 @@ module.exports = {
|
||||
updateTierListFeaturedStatus,
|
||||
favoriteTopic,
|
||||
unfavoriteTopic,
|
||||
followUser,
|
||||
unfollowUser,
|
||||
favoriteTierList,
|
||||
unfavoriteTierList,
|
||||
deleteTierList,
|
||||
|
||||
Reference in New Issue
Block a user