diff --git a/backend/src/auth/rbac.service.spec.ts b/backend/src/auth/rbac.service.spec.ts index a9d767b..9ffb2ab 100644 --- a/backend/src/auth/rbac.service.spec.ts +++ b/backend/src/auth/rbac.service.spec.ts @@ -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(); + }); + }); }); diff --git a/backend/src/auth/rbac.service.ts b/backend/src/auth/rbac.service.ts index 5afc32a..2bfca56 100644 --- a/backend/src/auth/rbac.service.ts +++ b/backend/src/auth/rbac.service.ts @@ -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); } diff --git a/backend/src/auth/repositories/rbac.repository.ts b/backend/src/auth/repositories/rbac.repository.ts index 0b1d1a3..e88a6f4 100644 --- a/backend/src/auth/repositories/rbac.repository.ts +++ b/backend/src/auth/repositories/rbac.repository.ts @@ -39,4 +39,22 @@ export class RbacRepository { return Array.from(new Set(result.map((p) => p.slug))); } + + async countRoles(): Promise { + 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(); + } }