v4.0.10: 관리자 계정 변경 안전장치와 접속시간 지표 추가

역할 변경은 명시적으로 버튼을 눌렀을 때만 가능하게 제한하고, 마지막 접속시간과 비밀번호 분실 안내를 추가해 운영 관리성을 높인다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-23 18:24:45 +09:00
parent bc30356c8b
commit 5a56ddcede
11 changed files with 112 additions and 22 deletions

View File

@@ -28,6 +28,7 @@ export function initDb(dbPath) {
avatar_url TEXT,
role TEXT NOT NULL DEFAULT 'user',
is_active INTEGER NOT NULL DEFAULT 1,
last_login_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
@@ -77,6 +78,9 @@ export function initDb(dbPath) {
if (!userCols.some((c) => c.name === 'skip_deck_public_prompt')) {
db.exec('ALTER TABLE users ADD COLUMN skip_deck_public_prompt INTEGER NOT NULL DEFAULT 0');
}
if (!userCols.some((c) => c.name === 'last_login_at')) {
db.exec('ALTER TABLE users ADD COLUMN last_login_at TEXT');
}
return db;
}

View File

@@ -69,7 +69,8 @@ export function createAdminRouter(db) {
`).get(like, like, like).c;
rows = db.prepare(`
SELECT id, email, username, display_name AS displayName, avatar_url AS avatarUrl,
role, is_active AS isActive, created_at AS createdAt, updated_at AS updatedAt
role, is_active AS isActive, last_login_at AS lastLoginAt,
created_at AS createdAt, updated_at AS updatedAt
FROM users
WHERE email LIKE ? OR username LIKE ? OR display_name LIKE ?
ORDER BY created_at DESC LIMIT ? OFFSET ?
@@ -78,7 +79,8 @@ export function createAdminRouter(db) {
total = db.prepare('SELECT COUNT(*) AS c FROM users').get().c;
rows = db.prepare(`
SELECT id, email, username, display_name AS displayName, avatar_url AS avatarUrl,
role, is_active AS isActive, created_at AS createdAt, updated_at AS updatedAt
role, is_active AS isActive, last_login_at AS lastLoginAt,
created_at AS createdAt, updated_at AS updatedAt
FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?
`).all(Number(limit), offset);
}
@@ -100,7 +102,8 @@ export function createAdminRouter(db) {
router.get('/users/:id', (req, res) => {
const user = db.prepare(`
SELECT id, email, username, display_name AS displayName, avatar_url AS avatarUrl,
role, is_active AS isActive, created_at AS createdAt, updated_at AS updatedAt
role, is_active AS isActive, last_login_at AS lastLoginAt,
created_at AS createdAt, updated_at AS updatedAt
FROM users WHERE id = ?
`).get(req.params.id);
@@ -188,7 +191,8 @@ export function createAdminRouter(db) {
const updated = db.prepare(`
SELECT id, email, username, display_name AS displayName, avatar_url AS avatarUrl,
role, is_active AS isActive, created_at AS createdAt, updated_at AS updatedAt
role, is_active AS isActive, last_login_at AS lastLoginAt,
created_at AS createdAt, updated_at AS updatedAt
FROM users WHERE id = ?
`).get(targetId);
updated.avatarUrl = getAvatarUrl(updated.id) || updated.avatarUrl;

View File

@@ -97,8 +97,9 @@ export function createAuthRouter(db) {
return res.status(401).json({ error: '이메일 또는 비밀번호가 올바르지 않습니다.' });
}
db.prepare('UPDATE users SET last_login_at = ? WHERE id = ?').run(nowIso(), user.id);
const row = db.prepare(`
SELECT id, email, username, avatar_url, role, updated_at AS updatedAt
SELECT id, email, username, avatar_url, role, updated_at AS updatedAt, last_login_at AS lastLoginAt
FROM users WHERE id = ?
`).get(user.id);