1
0
Fork 0

chore(seed): update seed scripts for RBAC roles

Assign the developer role document to the sysadmin user on seed. Add
seedRoles script to create default RBAC roles.
This commit is contained in:
Jason Fraley 2026-08-19 19:48:31 -04:00
parent d3890cc191
commit fb74e6b0c2
2 changed files with 236 additions and 0 deletions

226
src/tools/seed/seedRoles.ts Normal file
View file

@ -0,0 +1,226 @@
import { getPayload } from "payload";
import config from "@payload-config";
import type { Permission } from "@/permissions";
const USER_PERMISSIONS: Permission[] = [
"users:read",
"ranks:read",
"profiles:read",
"awards:read",
"qualifications:read",
"assignments:read",
"experience:read",
"user-notifications:read",
"missions:read",
"mission-attendances:read",
"campaigns:read",
"factions:read",
"technologies:read",
"assets:read",
"resources:read",
"vehicles:read",
"structures:read",
"shipments:read",
"bank-accounts:read",
"bank-transactions:read",
"ledger-entries:read",
"locker-storages:read",
"loadouts:read",
"market-listings:read",
"market-negotiations:read",
"tickets:read",
"game-structures:read",
"game-vehicles:read",
"game-npcs:read",
"game-hard-resources:read",
"game-event-logs:read",
"maps:read",
"narrative-events:read",
"mission-files:read",
"mod-lists:read",
"game-rules:read",
"shims:read",
"shipments:create",
"shipments:update",
"structures:update",
"structures:delete",
];
const ADMIN_PERMISSIONS: Permission[] = [
...USER_PERMISSIONS,
"system:admin-access",
"users:read",
"users:update",
"users:delete",
"technologies:create",
"technologies:read",
"technologies:update",
"technologies:delete",
"tickets:read",
"tickets:update",
"tickets:staff",
"bank-accounts:create",
"bank-accounts:update",
"user-notifications:read",
"user-notifications:update",
"mission-attendances:create",
"mission-attendances:read",
"mission-attendances:update",
"mission-attendances:delete",
"missions:read",
"market-negotiations:read",
"market-negotiations:update",
"logistics:manage",
"banking:manage",
"discord:staff",
"discord:announce",
"structures:create",
];
interface BuiltinRole {
slug: string;
name: string;
description: string;
permissions: Permission[];
isSystem: boolean;
isSuperuser: boolean;
}
const BUILTIN_ROLES: BuiltinRole[] = [
{
slug: "guest",
name: "Guest",
description: "Default role for unauthenticated or basic users. No permissions.",
permissions: [],
isSystem: true,
isSuperuser: false,
},
{
slug: "user",
name: "User",
description: "Standard authenticated user. Can manage shipments, structures, and read profiles.",
permissions: [...USER_PERMISSIONS],
isSystem: true,
isSuperuser: false,
},
{
slug: "admin",
name: "Admin",
description: "Administrator. Can manage users, tickets, banking, logistics, and most collections.",
permissions: [...ADMIN_PERMISSIONS],
isSystem: true,
isSuperuser: false,
},
{
slug: "developer",
name: "Developer",
description: "Superuser. Bypasses all permission checks. Full access to everything.",
permissions: [],
isSystem: true,
isSuperuser: true,
},
];
const ENUM_TO_SLUG: Record<string, string> = {
guest: "guest",
user: "user",
trusted: "user",
admin: "admin",
developer: "developer",
};
export const seedRoles = async () => {
const payload = await getPayload({ config });
payload.logger.info("Seeding built-in RBAC roles...");
const roleIdMap = new Map<string, number>();
for (const role of BUILTIN_ROLES) {
const existing = await payload.find({
collection: "roles",
where: { slug: { equals: role.slug } },
limit: 1,
overrideAccess: true,
});
if (existing.docs.length > 0) {
const doc = existing.docs[0] as { id: number };
roleIdMap.set(role.slug, doc.id);
payload.logger.info(` Role "${role.slug}" already exists (id=${doc.id}), skipping.`);
continue;
}
const created = await payload.create({
collection: "roles",
data: {
name: role.name,
slug: role.slug,
description: role.description,
permissions: role.permissions,
isSystem: role.isSystem,
isSuperuser: role.isSuperuser,
},
overrideAccess: true,
});
roleIdMap.set(role.slug, created.id);
payload.logger.info(` Created role "${role.slug}" (id=${created.id}).`);
}
payload.logger.info("Migrating existing users from roles enum to roleDocs...");
const users = await payload.find({
collection: "users",
limit: 0,
depth: 0,
overrideAccess: true,
select: { roles: true, roleDocs: true },
});
let migrated = 0;
let skipped = 0;
for (const user of users.docs) {
const u = user as { id: number; roles?: string[] | null; roleDocs?: unknown[] | null };
if (u.roleDocs && Array.isArray(u.roleDocs) && u.roleDocs.length > 0) {
skipped++;
continue;
}
const enumRoles = u.roles ?? [];
if (enumRoles.length === 0) {
skipped++;
continue;
}
const roleDocIds: number[] = [];
const seenSlugs = new Set<string>();
for (const enumRole of enumRoles) {
const slug = ENUM_TO_SLUG[enumRole];
if (!slug || seenSlugs.has(slug)) continue;
seenSlugs.add(slug);
const roleId = roleIdMap.get(slug);
if (roleId) roleDocIds.push(roleId);
}
if (roleDocIds.length === 0) {
skipped++;
continue;
}
await payload.update({
collection: "users",
id: user.id,
data: { roleDocs: roleDocIds },
overrideAccess: true,
});
migrated++;
}
payload.logger.info(`Migration complete: ${migrated} users migrated, ${skipped} users skipped.`);
};
await seedRoles();

View file

@ -14,6 +14,15 @@ export const seedUsers = async () => {
},
});
const devRole = await payload.find({
collection: "roles",
where: { slug: { equals: "developer" } },
limit: 1,
depth: 0,
overrideAccess: true,
});
const devRoleId = devRole.docs[0]?.id;
payload.logger.info(`Creating initial sysadmin/developer user...`);
try {
const result = await payload.create({
@ -25,6 +34,7 @@ export const seedUsers = async () => {
discordUsername: "Z8MB1E",
steamId: "76561198091303179",
roles: ["developer"],
roleDocs: devRoleId ? [devRoleId] : [],
},
});