feat(auth): implement role seeding on application bootstrap

- Added `onApplicationBootstrap` to seed default roles if none exist.
- Introduced `seedRoles` method to handle role creation with logging.
- Updated `RbacRepository` with `countRoles` and `createRole` methods.
- Added unit tests to ensure role seeding logic functions correctly.
This commit is contained in:
Mathis HERRIOT
2026-01-21 10:45:25 +01:00
parent dd0a9e620b
commit 0548c418c7
3 changed files with 92 additions and 2 deletions

View File

@@ -9,6 +9,8 @@ describe("RbacService", () => {
const mockRbacRepository = {
findRolesByUserId: jest.fn(),
findPermissionsByUserId: jest.fn(),
countRoles: jest.fn(),
createRole: jest.fn(),
};
beforeEach(async () => {
@@ -58,4 +60,35 @@ describe("RbacService", () => {
expect(repository.findPermissionsByUserId).toHaveBeenCalledWith(userId);
});
});
describe("seedRoles", () => {
it("should be called on application bootstrap", async () => {
const seedRolesSpy = jest.spyOn(service, "seedRoles");
await service.onApplicationBootstrap();
expect(seedRolesSpy).toHaveBeenCalled();
});
it("should seed roles if none exist", async () => {
mockRbacRepository.countRoles.mockResolvedValue(0);
await service.seedRoles();
expect(repository.countRoles).toHaveBeenCalled();
expect(repository.createRole).toHaveBeenCalledTimes(3);
expect(repository.createRole).toHaveBeenCalledWith(
"Administrator",
"admin",
"Full system access",
);
});
it("should not seed roles if some already exist", async () => {
mockRbacRepository.countRoles.mockResolvedValue(3);
await service.seedRoles();
expect(repository.countRoles).toHaveBeenCalled();
expect(repository.createRole).not.toHaveBeenCalled();
});
});
});

View File

@@ -1,10 +1,49 @@
import { Injectable } from "@nestjs/common";
import { Injectable, Logger, OnApplicationBootstrap } from "@nestjs/common";
import { RbacRepository } from "./repositories/rbac.repository";
@Injectable()
export class RbacService {
export class RbacService implements OnApplicationBootstrap {
private readonly logger = new Logger(RbacService.name);
constructor(private readonly rbacRepository: RbacRepository) {}
async onApplicationBootstrap() {
this.logger.log("RbacService initialized, checking roles...");
await this.seedRoles();
}
async seedRoles() {
try {
const count = await this.rbacRepository.countRoles();
if (count === 0) {
this.logger.log("No roles found, seeding default roles...");
const defaultRoles = [
{ name: "Administrator", slug: "admin", description: "Full system access" },
{
name: "Moderator",
slug: "moderator",
description: "Access to moderation tools",
},
{ name: "User", slug: "user", description: "Standard user access" },
];
for (const role of defaultRoles) {
await this.rbacRepository.createRole(
role.name,
role.slug,
role.description,
);
this.logger.log(`Created role: ${role.slug}`);
}
this.logger.log("Default roles seeded successfully.");
} else {
this.logger.log(`${count} roles already exist, skipping seeding.`);
}
} catch (error) {
this.logger.error("Error during roles seeding:", error);
}
}
async getUserRoles(userId: string) {
return this.rbacRepository.findRolesByUserId(userId);
}

View File

@@ -39,4 +39,22 @@ export class RbacRepository {
return Array.from(new Set(result.map((p) => p.slug)));
}
async countRoles(): Promise<number> {
const result = await this.databaseService.db
.select({ count: roles.id })
.from(roles);
return result.length;
}
async createRole(name: string, slug: string, description?: string) {
return this.databaseService.db
.insert(roles)
.values({
name,
slug,
description,
})
.returning();
}
}