회원 접속일과 콘텐츠 활동일을 분리한다

This commit is contained in:
2026-04-03 13:20:46 +09:00
parent f1756a4ff1
commit 953837137a
10 changed files with 86 additions and 22 deletions

View File

@@ -66,6 +66,7 @@ function mapUserRow(row) {
tierListCount: Number(row.tierlist_count || 0),
followerCount: Number(row.follower_count || 0),
receivedFavoriteCount: Number(row.received_favorite_count || 0),
lastLoginAt: Number(row.last_login_at || 0),
recentActivityAt: Number(row.recent_activity_at || row.created_at || 0),
}
}
@@ -284,6 +285,7 @@ async function ensureSchema() {
email_verified TINYINT(1) NOT NULL DEFAULT 1,
is_admin TINYINT(1) NOT NULL DEFAULT 0,
avatar_src VARCHAR(255) NOT NULL DEFAULT '',
last_login_at BIGINT NOT NULL DEFAULT 0,
created_at BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
@@ -294,6 +296,12 @@ async function ensureSchema() {
await query('UPDATE users SET email_verified = 1 WHERE email_verified IS NULL')
}
const userLastLoginColumns = await query("SHOW COLUMNS FROM users LIKE 'last_login_at'")
if (!userLastLoginColumns.length) {
await query('ALTER TABLE users ADD COLUMN last_login_at BIGINT NOT NULL DEFAULT 0 AFTER avatar_src')
await query('UPDATE users SET last_login_at = created_at WHERE last_login_at = 0')
}
await query(`
CREATE TABLE IF NOT EXISTS email_verification_tokens (
id VARCHAR(64) PRIMARY KEY,
@@ -636,7 +644,7 @@ async function countUsers() {
async function findUserByEmail(email) {
const rows = await query(
'SELECT id, email, nickname, password_hash, email_verified, is_admin, avatar_src, created_at FROM users WHERE email = ? LIMIT 1',
'SELECT id, email, nickname, password_hash, email_verified, is_admin, avatar_src, last_login_at, created_at FROM users WHERE email = ? LIMIT 1',
[email]
)
const row = rows[0]
@@ -650,7 +658,7 @@ async function findUserByNickname(nickname, excludeUserId = '') {
const rows = excludeUserId
? await query(
`
SELECT id, email, nickname, password_hash, email_verified, is_admin, avatar_src, created_at
SELECT id, email, nickname, password_hash, email_verified, is_admin, avatar_src, last_login_at, created_at
FROM users
WHERE TRIM(LOWER(nickname)) = TRIM(LOWER(?)) AND id <> ?
LIMIT 1
@@ -659,7 +667,7 @@ async function findUserByNickname(nickname, excludeUserId = '') {
)
: await query(
`
SELECT id, email, nickname, password_hash, email_verified, is_admin, avatar_src, created_at
SELECT id, email, nickname, password_hash, email_verified, is_admin, avatar_src, last_login_at, created_at
FROM users
WHERE TRIM(LOWER(nickname)) = TRIM(LOWER(?))
LIMIT 1
@@ -673,7 +681,7 @@ async function findUserByNickname(nickname, excludeUserId = '') {
async function findUserById(id) {
const rows = await query(
'SELECT id, email, nickname, email_verified, is_admin, avatar_src, created_at FROM users WHERE id = ? LIMIT 1',
'SELECT id, email, nickname, email_verified, is_admin, avatar_src, last_login_at, created_at FROM users WHERE id = ? LIMIT 1',
[id]
)
return mapUserRow(rows[0])
@@ -683,14 +691,24 @@ async function createUser({ id, email, nickname, passwordHash, emailVerified = t
const createdAt = now()
await query(
`
INSERT INTO users (id, email, nickname, password_hash, email_verified, is_admin, avatar_src, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO users (id, email, nickname, password_hash, email_verified, is_admin, avatar_src, last_login_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
[id, email, nickname || '', passwordHash, emailVerified ? 1 : 0, isAdmin ? 1 : 0, '', createdAt]
[id, email, nickname || '', passwordHash, emailVerified ? 1 : 0, isAdmin ? 1 : 0, '', isAdmin ? createdAt : 0, createdAt]
)
return findUserById(id)
}
async function touchUserLastLoginAt(userId, timestamp = now()) {
const lastLoginAt = Number(timestamp) || now()
const staleBefore = lastLoginAt - 10 * 60 * 1000
await query(
'UPDATE users SET last_login_at = ? WHERE id = ? AND last_login_at < ?',
[lastLoginAt, userId, staleBefore]
)
return findUserById(userId)
}
async function updateUserPassword({ id, passwordHash }) {
await query('UPDATE users SET password_hash = ? WHERE id = ?', [passwordHash, id])
return findUserById(id)
@@ -816,7 +834,7 @@ async function updateUserProfile({ id, nickname, avatarSrc }) {
async function findPrimaryAdminUser() {
const rows = await query(
'SELECT id, email, nickname, email_verified, is_admin, avatar_src, created_at FROM users WHERE is_admin = 1 ORDER BY created_at ASC, email ASC LIMIT 1'
'SELECT id, email, nickname, email_verified, is_admin, avatar_src, last_login_at, created_at FROM users WHERE is_admin = 1 ORDER BY created_at ASC, email ASC LIMIT 1'
)
return mapUserRow(rows[0])
}
@@ -849,6 +867,10 @@ async function listUsers({ queryText = '', sort = 'recent', direction = 'desc' }
? isAsc
? 'received_favorite_count ASC, recent_activity_at ASC, u.email ASC'
: 'received_favorite_count DESC, recent_activity_at DESC, u.email ASC'
: sort === 'lastLogin'
? isAsc
? 'u.last_login_at ASC, recent_activity_at ASC, u.email ASC'
: 'u.last_login_at 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'
@@ -862,6 +884,7 @@ async function listUsers({ queryText = '', sort = 'recent', direction = 'desc' }
u.email_verified,
u.is_admin,
u.avatar_src,
u.last_login_at,
u.created_at,
COUNT(DISTINCT t.id) AS tierlist_count,
COUNT(DISTINCT uf.follower_id) AS follower_count,
@@ -875,7 +898,7 @@ async function listUsers({ queryText = '', sort = 'recent', direction = 'desc' }
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
GROUP BY u.id, u.email, u.nickname, u.email_verified, u.is_admin, u.avatar_src, u.last_login_at, u.created_at
ORDER BY ${orderBy}
`,
params
@@ -2987,6 +3010,7 @@ module.exports = {
findUserById,
findUserProfileById,
createUser,
touchUserLastLoginAt,
updateUserPassword,
verifyUserEmail,
createEmailVerificationToken,