This commit is contained in:
jhartworks
2026-06-23 14:48:36 +02:00
parent a445fef521
commit f0cb94ce4e
8 changed files with 796 additions and 81 deletions
+113
View File
@@ -1,10 +1,123 @@
const SELECTION_SET_KEY = "selection:ids";
const SELECTION_SEQ_KEY = "selection:seq";
const ROLE_SET_KEY = "role:names";
export const DEFAULT_ROLE_DEFINITIONS = [
{
name: "viewer",
label: "Viewer",
permissions: ["view_dashboard", "view_trends"],
isSystem: true,
},
{
name: "technician",
label: "Techniker",
permissions: ["view_dashboard", "view_trends", "edit_aliases", "manage_sources", "view_debug"],
isSystem: true,
},
{
name: "admin",
label: "Admin",
permissions: ["view_dashboard", "view_trends", "edit_aliases", "manage_sources", "manage_users", "manage_roles", "view_debug"],
isSystem: true,
},
];
function safeParse(jsonValue) {
return jsonValue ? JSON.parse(jsonValue) : null;
}
function normalizeRoleName(value) {
const normalized = String(value || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9_-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 40);
return normalized || "viewer";
}
function normalizePermissions(input) {
return [...new Set((Array.isArray(input) ? input : [])
.map((entry) => String(entry || "").trim())
.filter(Boolean))].sort();
}
function normalizeRoleRecord(role) {
const systemRole = DEFAULT_ROLE_DEFINITIONS.find((entry) => entry.name === normalizeRoleName(role?.name));
return {
name: normalizeRoleName(role?.name),
label: String(role?.label || systemRole?.label || role?.name || "Rolle").trim() || systemRole?.label || "Rolle",
permissions: normalizePermissions(role?.permissions || systemRole?.permissions || []),
isSystem: Boolean(role?.isSystem ?? systemRole?.isSystem),
updatedAt: role?.updatedAt || new Date().toISOString(),
};
}
async function ensureDefaultRoles(redis) {
for (const definition of DEFAULT_ROLE_DEFINITIONS) {
const key = `role:${definition.name}`;
const existing = safeParse(await redis.get(key));
const payload = normalizeRoleRecord({
...definition,
...(existing || {}),
name: definition.name,
label: existing?.label || definition.label,
permissions: Array.isArray(existing?.permissions) && existing.permissions.length ? existing.permissions : definition.permissions,
isSystem: true,
});
await redis.set(key, JSON.stringify(payload));
await redis.sadd(ROLE_SET_KEY, definition.name);
}
}
export async function listRoles(redis) {
await ensureDefaultRoles(redis);
const names = await redis.smembers(ROLE_SET_KEY);
const items = await Promise.all(names.map((name) => redis.get(`role:${name}`)));
return items
.map((item) => safeParse(item))
.filter(Boolean)
.map((role) => normalizeRoleRecord(role))
.sort((left, right) => left.label.localeCompare(right.label, "de-DE"));
}
export async function getRoleMap(redis) {
const roles = await listRoles(redis);
return new Map(roles.map((role) => [role.name, role]));
}
export async function saveRole(redis, role, previousName = "") {
await ensureDefaultRoles(redis);
const existingName = normalizeRoleName(previousName || role?.name);
const payload = normalizeRoleRecord(role);
const current = safeParse(await redis.get(`role:${existingName}`));
if (current?.isSystem && existingName !== payload.name) {
throw new Error("Systemrollen können nicht umbenannt werden.");
}
const merged = normalizeRoleRecord({
...(current || {}),
...payload,
name: current?.isSystem ? existingName : payload.name,
isSystem: Boolean(current?.isSystem || payload.isSystem),
updatedAt: new Date().toISOString(),
});
if (existingName && existingName !== merged.name) {
await redis.del(`role:${existingName}`);
await redis.srem(ROLE_SET_KEY, existingName);
}
await redis.set(`role:${merged.name}`, JSON.stringify(merged));
await redis.sadd(ROLE_SET_KEY, merged.name);
return merged;
}
export async function listSelections(redis, user) {
const ids = await redis.smembers(SELECTION_SET_KEY);
const items = await Promise.all(ids.map((id) => redis.get(`selection:${id}`)));