feat(admin): streamline group management
This commit is contained in:
@@ -14,6 +14,7 @@ import { redirect } from "next/navigation";
|
||||
import { recordAdminEvent } from "@/lib/audit";
|
||||
import { requireAdminSession } from "@/lib/auth/require-admin";
|
||||
import { db } from "@/lib/database";
|
||||
import { adminGroupReturnPath } from "@/lib/group-management";
|
||||
|
||||
const USERNAME_PATTERN = /^[A-Za-z0-9_]{3,16}$/;
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
@@ -22,12 +23,6 @@ function userPath(userId: string, query?: string) {
|
||||
return `/admin/users/${encodeURIComponent(userId)}${query ? `?${query}` : ""}`;
|
||||
}
|
||||
|
||||
function userRegistryPath(search: string, result: "saved=group" | "error=invalid-group-assignment") {
|
||||
const query = new URLSearchParams(result);
|
||||
if (search) query.set("q", search);
|
||||
return `/admin/users?${query.toString()}`;
|
||||
}
|
||||
|
||||
async function targetUser(userId: string) {
|
||||
if (!UUID_PATTERN.test(userId)) return null;
|
||||
const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
||||
@@ -85,20 +80,26 @@ export async function assignUserGroupFromRegistry(formData: FormData) {
|
||||
const admin = await requireAdminSession();
|
||||
const userId = String(formData.get("userId") ?? "");
|
||||
const groupId = String(formData.get("groupId") ?? "");
|
||||
const search = String(formData.get("search") ?? "").trim().slice(0, 100);
|
||||
if (!UUID_PATTERN.test(userId) || !UUID_PATTERN.test(groupId)) redirect(userRegistryPath(search, "error=invalid-group-assignment"));
|
||||
|
||||
const [[user], [targetGroup]] = await Promise.all([
|
||||
db.select({ id: users.id }).from(users).where(eq(users.id, userId)).limit(1),
|
||||
db.select({ id: groups.id, name: groups.name, isDefault: groups.isDefault }).from(groups).where(eq(groups.id, groupId)).limit(1),
|
||||
]);
|
||||
if (!user || !targetGroup) redirect(userRegistryPath(search, "error=invalid-group-assignment"));
|
||||
const returnTo = formData.get("returnTo");
|
||||
if (!UUID_PATTERN.test(userId) || !UUID_PATTERN.test(groupId)) redirect(adminGroupReturnPath(returnTo, "error=invalid-group-assignment"));
|
||||
|
||||
const requestHeaders = await headers();
|
||||
const ipAddress = getClientIp(requestHeaders, process.env.TRUST_PROXY === "true");
|
||||
try {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.execute(sql`select ${users.id} from ${users} where ${users.id} = ${user.id} for update`);
|
||||
await tx.execute(sql`select pg_advisory_xact_lock(hashtext('minecraft-account-manager-group-membership'))`);
|
||||
await tx.execute(sql`select ${groups.id} from ${groups} where ${groups.id} = ${groupId} for update`);
|
||||
await tx.execute(sql`select ${users.id} from ${users} where ${users.id} = ${userId} for update`);
|
||||
const [[user], [targetGroup]] = await Promise.all([
|
||||
tx.select({ id: users.id }).from(users).where(eq(users.id, userId)).limit(1),
|
||||
tx.select({ id: groups.id, name: groups.name, isDefault: groups.isDefault }).from(groups).where(eq(groups.id, groupId)).limit(1),
|
||||
]);
|
||||
if (!user || !targetGroup) throw new Error("User or destination group no longer exists");
|
||||
const [membership] = await tx.select({ groupId: userGroupMemberships.groupId })
|
||||
.from(userGroupMemberships).where(eq(userGroupMemberships.userId, user.id)).limit(1);
|
||||
if (membership && membership.groupId !== targetGroup.id) {
|
||||
await tx.execute(sql`select ${groups.id} from ${groups} where ${groups.id} = ${membership.groupId} for update`);
|
||||
}
|
||||
const [previous] = await tx.select({ id: groups.id, name: groups.name })
|
||||
.from(userGroupMemberships)
|
||||
.innerJoin(groups, eq(groups.id, userGroupMemberships.groupId))
|
||||
@@ -131,9 +132,9 @@ export async function assignUserGroupFromRegistry(formData: FormData) {
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
redirect(userRegistryPath(search, "error=invalid-group-assignment"));
|
||||
redirect(adminGroupReturnPath(returnTo, "error=invalid-group-assignment"));
|
||||
}
|
||||
redirect(userRegistryPath(search, "saved=group"));
|
||||
redirect(adminGroupReturnPath(returnTo, "saved=group"));
|
||||
}
|
||||
|
||||
export async function updateUserName(formData: FormData) {
|
||||
|
||||
Reference in New Issue
Block a user