v0.12.22 User Manager updates

This commit is contained in:
2026-03-24 15:19:32 -04:00
parent 65e7cc4007
commit 72094d7d15
8 changed files with 372 additions and 64 deletions

View File

@@ -152,29 +152,47 @@ router.post('/bulk', authMiddleware, teamManagerMiddleware, async (req, res) =>
const results = { created: [], skipped: [] };
const seenEmails = new Set();
const defaultPw = process.env.USER_PASS || 'user@1234';
const validRoles = ['member', 'manager', 'admin'];
try {
for (const u of users) {
const email = (u.email || '').trim().toLowerCase();
const name = (u.name || '').trim();
if (!name || !email) { results.skipped.push({ email: email || '(blank)', reason: 'Missing name or email' }); continue; }
if (!isValidEmail(email)) { results.skipped.push({ email, reason: 'Invalid email address' }); continue; }
if (seenEmails.has(email)) { results.skipped.push({ email, reason: 'Duplicate email in CSV' }); continue; }
const email = (u.email || '').trim().toLowerCase();
const firstName = (u.firstName || '').trim();
const lastName = (u.lastName || '').trim();
// Support legacy name field too
const name = (firstName && lastName) ? `${firstName} ${lastName}` : (u.name || '').trim();
if (!email) { results.skipped.push({ email: '(blank)', reason: 'Email required' }); continue; }
if (!isValidEmail(email)){ results.skipped.push({ email, reason: 'Invalid email address' }); continue; }
if (!name) { results.skipped.push({ email, reason: 'First and last name required' }); continue; }
if (seenEmails.has(email)){ results.skipped.push({ email, reason: 'Duplicate email in CSV' }); continue; }
seenEmails.add(email);
const exists = await queryOne(req.schema, "SELECT id FROM users WHERE email=$1 AND status != 'deleted'", [email]);
if (exists) { results.skipped.push({ email, reason: 'Email already exists' }); continue; }
try {
const resolvedName = await resolveUniqueName(req.schema, name);
const pw = (u.password || '').trim() || defaultPw;
const hash = bcrypt.hashSync(pw, 10);
const newRole = u.role === 'admin' ? 'admin' : 'member';
const pw = (u.password || '').trim() || defaultPw;
const hash = bcrypt.hashSync(pw, 10);
const newRole = validRoles.includes(u.role) ? u.role : 'member';
const fn = firstName || name.split(' ')[0] || '';
const ln = lastName || name.split(' ').slice(1).join(' ') || '';
const r = await queryResult(req.schema,
"INSERT INTO users (name,email,password,role,status,must_change_password) VALUES ($1,$2,$3,$4,'active',TRUE) RETURNING id",
[resolvedName, email, hash, newRole]
"INSERT INTO users (name,first_name,last_name,email,password,role,status,must_change_password) VALUES ($1,$2,$3,$4,$5,$6,'active',TRUE) RETURNING id",
[resolvedName, fn, ln, email, hash, newRole]
);
await addUserToPublicGroups(req.schema, r.rows[0].id);
const userId = r.rows[0].id;
await addUserToPublicGroups(req.schema, userId);
if (newRole === 'admin') {
const sgId = await getOrCreateSupportGroup(req.schema);
if (sgId) await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [sgId, r.rows[0].id]);
if (sgId) await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [sgId, userId]);
}
// Add to user group if specified (silent — user was just created, no socket needed)
if (u.userGroupId) {
const ug = await queryOne(req.schema, 'SELECT * FROM user_groups WHERE id=$1', [u.userGroupId]);
if (ug) {
await exec(req.schema, 'INSERT INTO user_group_members (user_group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [ug.id, userId]);
if (ug.dm_group_id) {
await exec(req.schema, 'INSERT INTO group_members (group_id,user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [ug.dm_group_id, userId]);
}
}
}
results.created.push(email);
} catch (e) { results.skipped.push({ email, reason: e.message }); }