Keep prod users local and fix post-restore id sequences.

Prod restore skips users/session/audit, syncs serial sequences after load, and user create re-aligns users_id_seq so new accounts are not misreported as duplicates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-08-05 19:26:52 +03:00
co-authored by Cursor
parent 8a68e98258
commit cfee69c9a6
7 changed files with 83 additions and 9 deletions
+27
View File
@@ -38,6 +38,17 @@ async function clearUserSessions(userId) {
await pool.query(`DELETE FROM session WHERE (sess->>'userId')::int = $1`, [userId]);
}
/** Keep users_id_seq ahead of existing rows (restore inserts explicit ids). */
async function syncUsersIdSequence() {
await pool.query(
`SELECT setval(
pg_get_serial_sequence('users', 'id'),
GREATEST(1, COALESCE((SELECT MAX(id) FROM users), 1)),
true
)`
);
}
router.use(requirePermission('users'));
router.get('/', async (_req, res) => {
@@ -76,6 +87,16 @@ router.post('/', async (req, res) => {
const passwordHash = await bcrypt.hash(password, 10);
const storedPermissions = role === 'admin' ? ALL_PERMISSIONS : permissions;
const { rows: taken } = await pool.query(
`SELECT username FROM users WHERE LOWER(username) = LOWER($1) LIMIT 1`,
[username]
);
if (taken[0]) {
return res.status(409).json({ error: 'Username already exists' });
}
await syncUsersIdSequence();
const { rows } = await pool.query(
`INSERT INTO users (username, password_hash, role, permissions, is_active)
VALUES ($1, $2, $3, $4::text[], true)
@@ -95,6 +116,12 @@ router.post('/', async (req, res) => {
res.status(201).json({ user: mapUser(rows[0]) });
} catch (err) {
if (err.code === '23505') {
if (err.constraint === 'users_pkey') {
console.error('Users create PK conflict (sequence lag):', err.detail || err.message);
return res.status(409).json({
error: 'Could not allocate user id — retry create (sequence was out of sync)',
});
}
return res.status(409).json({ error: 'Username already exists' });
}
console.error('Users create error:', err.message);