144 lines
3.5 KiB
JavaScript
144 lines
3.5 KiB
JavaScript
import { db } from '../db/index.js';
|
|
|
|
const selectUsersWithGroups = db.prepare(`
|
|
SELECT
|
|
u.id,
|
|
u.username,
|
|
u.email,
|
|
u.is_active,
|
|
u.avatar_color,
|
|
u.last_login,
|
|
u.created_at,
|
|
u.updated_at,
|
|
GROUP_CONCAT(g.id || '::' || g.name, '|||') AS group_pairs
|
|
FROM users u
|
|
LEFT JOIN user_group_memberships m ON m.user_id = u.id
|
|
LEFT JOIN user_groups g ON g.id = m.group_id
|
|
GROUP BY u.id
|
|
ORDER BY u.username COLLATE NOCASE
|
|
`);
|
|
|
|
const selectUserWithGroupsById = db.prepare(`
|
|
SELECT
|
|
u.id,
|
|
u.username,
|
|
u.email,
|
|
u.is_active,
|
|
u.avatar_color,
|
|
u.last_login,
|
|
u.created_at,
|
|
u.updated_at,
|
|
GROUP_CONCAT(g.id || '::' || g.name, '|||') AS group_pairs
|
|
FROM users u
|
|
LEFT JOIN user_group_memberships m ON m.user_id = u.id
|
|
LEFT JOIN user_groups g ON g.id = m.group_id
|
|
WHERE u.id = ?
|
|
GROUP BY u.id
|
|
`);
|
|
|
|
const deleteMembershipsByUser = db.prepare(`
|
|
DELETE FROM user_group_memberships
|
|
WHERE user_id = ?
|
|
`);
|
|
|
|
const insertMembershipForUser = db.prepare(`
|
|
INSERT OR IGNORE INTO user_group_memberships (user_id, group_id)
|
|
VALUES (?, ?)
|
|
`);
|
|
|
|
const selectGroupById = db.prepare('SELECT id, name FROM user_groups WHERE id = ?');
|
|
|
|
const parseGroupPairs = (rawPairs) => {
|
|
if (!rawPairs) {
|
|
return [];
|
|
}
|
|
return rawPairs
|
|
.split('|||')
|
|
.map((entry) => {
|
|
const [idPart, namePart] = entry.split('::');
|
|
const groupName = String(namePart || '').trim();
|
|
if (!groupName) {
|
|
return null;
|
|
}
|
|
const numericId = Number(idPart);
|
|
return {
|
|
id: Number.isFinite(numericId) ? numericId : null,
|
|
name: groupName
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
};
|
|
|
|
const sanitizeUserRecord = (row) => ({
|
|
id: row.id,
|
|
username: row.username,
|
|
email: row.email,
|
|
isActive: Boolean(row.is_active),
|
|
avatarColor: row.avatar_color || null,
|
|
lastLogin: row.last_login || null,
|
|
createdAt: row.created_at || null,
|
|
updatedAt: row.updated_at || null,
|
|
groups: parseGroupPairs(row.group_pairs)
|
|
});
|
|
|
|
export function listUsers() {
|
|
const rows = selectUsersWithGroups.all();
|
|
return rows.map(sanitizeUserRecord);
|
|
}
|
|
|
|
export function getUserById(userId) {
|
|
const row = selectUserWithGroupsById.get(userId);
|
|
return row ? sanitizeUserRecord(row) : null;
|
|
}
|
|
|
|
const applyUserGroupAssignments = db.transaction((userId, groupIds) => {
|
|
deleteMembershipsByUser.run(userId);
|
|
groupIds.forEach((groupId) => {
|
|
insertMembershipForUser.run(userId, groupId);
|
|
});
|
|
});
|
|
|
|
export function updateUserGroups(userId, groupIds) {
|
|
const numericUserId = Number(userId);
|
|
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
|
|
const error = new Error('INVALID_USER_ID');
|
|
error.code = 'INVALID_USER_ID';
|
|
throw error;
|
|
}
|
|
|
|
const existingUser = getUserById(numericUserId);
|
|
if (!existingUser) {
|
|
const error = new Error('USER_NOT_FOUND');
|
|
error.code = 'USER_NOT_FOUND';
|
|
throw error;
|
|
}
|
|
|
|
const normalizedGroupIds = Array.isArray(groupIds)
|
|
? Array.from(
|
|
new Set(
|
|
groupIds
|
|
.map((value) => Number(value))
|
|
.filter((value) => Number.isFinite(value) && value > 0)
|
|
)
|
|
)
|
|
: [];
|
|
|
|
const missingGroupIds = [];
|
|
normalizedGroupIds.forEach((groupId) => {
|
|
const groupRow = selectGroupById.get(groupId);
|
|
if (!groupRow) {
|
|
missingGroupIds.push(groupId);
|
|
}
|
|
});
|
|
|
|
if (missingGroupIds.length > 0) {
|
|
const error = new Error('GROUP_NOT_FOUND');
|
|
error.code = 'GROUP_NOT_FOUND';
|
|
error.missingGroupIds = missingGroupIds;
|
|
throw error;
|
|
}
|
|
|
|
applyUserGroupAssignments(numericUserId, normalizedGroupIds);
|
|
return getUserById(numericUserId);
|
|
}
|