76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
const NAME_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
const TEXT_CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
|
export function validateGroupDetails(nameValue: unknown, descriptionValue: unknown) {
|
|
const name = String(nameValue ?? "").trim();
|
|
const description = String(descriptionValue ?? "").trim();
|
|
if (
|
|
name.length < 1 ||
|
|
name.length > 50 ||
|
|
NAME_CONTROL_CHARACTERS.test(name) ||
|
|
description.length > 500 ||
|
|
TEXT_CONTROL_CHARACTERS.test(description)
|
|
) return null;
|
|
return { name, description };
|
|
}
|
|
|
|
export function adminGroupReturnPath(
|
|
value: unknown,
|
|
result: "saved=group" | "error=invalid-group-assignment",
|
|
) {
|
|
const requested = String(value ?? "");
|
|
let pathname = "/admin/users";
|
|
const parameters = new URLSearchParams();
|
|
if (requested.startsWith("/")) {
|
|
const url = new URL(requested, "http://internal");
|
|
if (url.pathname === "/admin/users") {
|
|
const search = url.searchParams.get("q")?.trim().slice(0, 100);
|
|
if (search) parameters.set("q", search);
|
|
} else if (/^\/admin\/groups\/[0-9a-f-]{36}$/i.test(url.pathname)) {
|
|
pathname = url.pathname;
|
|
}
|
|
}
|
|
const [key, resultValue] = result.split("=", 2) as ["saved" | "error", string];
|
|
parameters.set(key, resultValue);
|
|
return `${pathname}?${parameters.toString()}`;
|
|
}
|
|
|
|
export function editableGroupName(currentName: string, isDefault: boolean, requestedName: string) {
|
|
return isDefault ? currentName : requestedName;
|
|
}
|
|
|
|
export function effectiveGroupMemberCount(
|
|
totalUsers: number,
|
|
assignedGroupIds: string[],
|
|
group: { id: string; isDefault: boolean },
|
|
) {
|
|
return group.isDefault
|
|
? Math.max(0, totalUsers - assignedGroupIds.length)
|
|
: assignedGroupIds.filter((groupId) => groupId === group.id).length;
|
|
}
|
|
|
|
export function isEffectiveGroupMember(
|
|
userId: string,
|
|
assignmentByUser: Record<string, string>,
|
|
group: { id: string; isDefault: boolean },
|
|
) {
|
|
return group.isDefault ? !assignmentByUser[userId] : assignmentByUser[userId] === group.id;
|
|
}
|
|
|
|
export function groupSlug(name: string, existingSlugs: Set<string>) {
|
|
const normalized = name
|
|
.normalize("NFKD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, "-")
|
|
.replace(/^-+|-+$/g, "") || "group";
|
|
const base = normalized.slice(0, 50).replace(/-+$/g, "") || "group";
|
|
if (!existingSlugs.has(base)) return base;
|
|
for (let suffix = 2; suffix < 10_000; suffix += 1) {
|
|
const suffixText = `-${suffix}`;
|
|
const candidate = `${base.slice(0, 50 - suffixText.length).replace(/-+$/g, "")}${suffixText}`;
|
|
if (!existingSlugs.has(candidate)) return candidate;
|
|
}
|
|
throw new Error("Could not generate a unique group slug");
|
|
}
|