Implement indexing for users_roles table and enhance roles seeding logic to prevent FK constraint errors

This commit is contained in:
Raj Nandan Sharma
2026-03-31 21:58:43 +05:30
parent 52f8c50f50
commit 8362a73058
3 changed files with 19 additions and 8 deletions
@@ -45,6 +45,7 @@ export async function up(knex: Knex): Promise<void> {
table.timestamp("updated_at").defaultTo(knex.fn.now());
table.primary(["roles_id", "users_id"]);
table.index("users_id", "idx_users_roles_users_id");
});
}
}
@@ -34,9 +34,17 @@ export async function up(knex: Knex): Promise<void> {
}
}
// 2. Migrate users.role → users_roles
// 2. Read users.role into memory BEFORE dropping the column.
// On SQLite, dropColumn recreates the table (create → copy → drop → rename),
// which can discard DML inserts to tables with FKs pointing at users.
const users: Array<{ id: number; role: string }> = await knex("users").select("id", "role");
// 3. Drop the column first.
await knex.schema.alterTable("users", (table) => {
table.dropColumn("role");
});
// 4. Now populate users_roles from the in-memory snapshot.
for (const user of users) {
const newRoleId = ROLE_MAP[user.role] ?? "member";
@@ -51,11 +59,6 @@ export async function up(knex: Knex): Promise<void> {
});
}
}
// 3. Now safe to drop the column
await knex.schema.alterTable("users", (table) => {
table.dropColumn("role");
});
}
// Reverse map: pick the highest-precedence role when backfilling.
+9 -2
View File
@@ -45,14 +45,21 @@ export async function seed(knex: Knex): Promise<void> {
}
// 2. Seed roles_permissions for readonly roles
// Only insert permissions that actually exist in the permissions table
// to avoid FK constraint errors if permissions seed hasn't run yet.
const existingPermRows: Array<{ id: string }> = await knex("permissions").select("id");
const existingPermIds = new Set(existingPermRows.map((p) => p.id));
for (const [roleId, permissionIds] of Object.entries(rolePermissions)) {
const validPermissionIds = permissionIds.filter((id) => existingPermIds.has(id));
const existingPerms: Array<{ permissions_id: string }> = await knex("roles_permissions")
.where("roles_id", roleId)
.select("permissions_id");
const existingSet = new Set(existingPerms.map((e) => e.permissions_id));
// Insert missing permissions
for (const permId of permissionIds) {
for (const permId of validPermissionIds) {
if (!existingSet.has(permId)) {
await knex("roles_permissions").insert({
roles_id: roleId,
@@ -65,7 +72,7 @@ export async function seed(knex: Knex): Promise<void> {
}
// Remove permissions no longer assigned to this role
const desiredSet = new Set(permissionIds);
const desiredSet = new Set(validPermissionIds);
const toRemove = existingPerms.filter((e) => !desiredSet.has(e.permissions_id)).map((e) => e.permissions_id);
if (toRemove.length > 0) {
await knex("roles_permissions").where("roles_id", roleId).whereIn("permissions_id", toRemove).del();