diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index 82a6917..9a033b5 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -12,10 +12,10 @@ import { AdminLoginTransformer } from '../transformers/admin-login.transformer'; import { UserLoginTransformer } from '../transformers/user-login.transformer'; import { ConfigService } from '@nestjs/config'; import { normalizePhone } from '../../utils/phone.util'; +import { PermissionsService } from 'src/modules/roles/providers/permissions.service'; @Injectable() export class AuthService { - readonly ADMIN_PERMISSIONS_KEY = 'admin-perms'; private OTP_EXPIRATION_TIME: number; constructor( private readonly cacheService: CacheService, @@ -25,6 +25,7 @@ export class AuthService { private readonly restRepository: RestRepository, private readonly adminRepository: AdminRepository, private readonly configService: ConfigService, + private readonly permissionsService: PermissionsService, ) { this.OTP_EXPIRATION_TIME = this.configService.get('OTP_EXPIRATION_TIME') ?? 240; } @@ -106,6 +107,7 @@ export class AuthService { } const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, rest.id, true, slug); + await this.permissionsService.refreshAdminPermissionsCache(admin.id, rest.id); const adminResponse = await AdminLoginTransformer.transform(admin, rest); @@ -130,6 +132,7 @@ export class AuthService { } const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, rest.id, true, slug); + await this.permissionsService.refreshAdminPermissionsCache(admin.id, rest.id); const adminResponse = await AdminLoginTransformer.transform(admin, rest); diff --git a/src/modules/roles/controllers/roles.controller.ts b/src/modules/roles/controllers/roles.controller.ts index f727fc0..84e2812 100644 --- a/src/modules/roles/controllers/roles.controller.ts +++ b/src/modules/roles/controllers/roles.controller.ts @@ -32,7 +32,6 @@ export class RolesController { @Get('admin/roles/permissions') @UseGuards(AdminAuthGuard) - @Permissions(Permission.MANAGE_ROLES) @ApiBearerAuth() @ApiOperation({ summary: 'Get all permissions that the admin has' }) async findAllPermissions(@AdminId() adminId: string, @RestId() restId: string) { diff --git a/src/modules/roles/providers/permissions.service.ts b/src/modules/roles/providers/permissions.service.ts index 2282f6f..3e2a1ea 100644 --- a/src/modules/roles/providers/permissions.service.ts +++ b/src/modules/roles/providers/permissions.service.ts @@ -31,44 +31,80 @@ export class PermissionsService { * @returns Array of permission names (string[]) */ async getAdminPermissions(adminId: string, restId: string): Promise { - const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`; + const cachedPermissions = await this.readAdminPermissionsFromCache(adminId, restId); + if (cachedPermissions !== undefined) { + return cachedPermissions; + } - // Try to get from cache first - const cachedPermissions = await this.cacheService.get(cacheKey); - if (cachedPermissions) { + const permissionNames = await this.fetchAdminPermissionsFromDb(adminId, restId); + await this.cacheAdminPermissions(adminId, restId, permissionNames); + return permissionNames; + } + + /** + * Refresh admin permissions in Redis (used after login or role changes) + */ + async refreshAdminPermissionsCache(adminId: string, restId: string): Promise { + await this.invalidateAdminPermissionsCache(adminId, restId); + const permissionNames = await this.fetchAdminPermissionsFromDb(adminId, restId); + await this.cacheAdminPermissions(adminId, restId, permissionNames); + return permissionNames; + } + + private getCacheKey(adminId: string, restId: string): string { + return `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`; + } + + private async readAdminPermissionsFromCache(adminId: string, restId: string): Promise { + const cacheKey = this.getCacheKey(adminId, restId); + const cachedPermissions = await this.cacheService.get(cacheKey); + if (cachedPermissions === undefined || cachedPermissions === null) { + return undefined; + } + + if (Array.isArray(cachedPermissions) && cachedPermissions.every((p): p is string => typeof p === 'string')) { + return cachedPermissions; + } + + if (typeof cachedPermissions === 'string') { try { const parsed: unknown = JSON.parse(cachedPermissions); - // Ensure it's an array of strings if (Array.isArray(parsed) && parsed.every((p): p is string => typeof p === 'string')) { return parsed; } - // If invalid format, continue to fetch from DB } catch { - // If parsing fails, continue to fetch from DB + return undefined; } } - // If not in cache, fetch from database + return undefined; + } + + private async cacheAdminPermissions(adminId: string, restId: string, permissionNames: string[]): Promise { + const cacheKey = this.getCacheKey(adminId, restId); + await this.cacheService.set(cacheKey, JSON.stringify(permissionNames), this.ADMIN_PERMISSIONS_TTL); + } + + private async fetchAdminPermissionsFromDb(adminId: string, restId: string): Promise { const admin = await this.adminRepository.findOne( { id: adminId, roles: { restaurant: { id: restId } } }, - { populate: ['roles', 'roles.role', 'roles.role.permissions'] }, + { + populate: ['roles', 'roles.role', 'roles.role.permissions'], + populateFilter: { roles: { restaurant: { id: restId } } }, + }, ); - if (!admin || !admin.roles) { - await this.cacheService.set(cacheKey, JSON.stringify([]), this.ADMIN_PERMISSIONS_TTL); + if (!admin) { return []; } - // Ensure roles collection is loaded await admin.roles.loadItems(); - // Extract permission names as array of strings const permissions = await Promise.all( admin.roles .getItems() - .filter(r => r.role) // Filter out any null/undefined roles + .filter(r => r.role) .map(async r => { - // Ensure permissions collection is initialized if (!r.role.permissions.isInitialized()) { await r.role.permissions.loadItems(); } @@ -76,10 +112,7 @@ export class PermissionsService { }), ); - const permissionNames = permissions.flat().map(p => p.name); - await this.cacheService.set(cacheKey, JSON.stringify(permissionNames), this.ADMIN_PERMISSIONS_TTL); - - return permissionNames; + return permissions.flat().map(p => p.name); } async getAdminFullPermissions(adminId: string, restId: string): Promise { @@ -104,7 +137,6 @@ export class PermissionsService { * @param restId - The restaurant ID */ async invalidateAdminPermissionsCache(adminId: string, restId: string): Promise { - const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`; - await this.cacheService.del(cacheKey); + await this.cacheService.del(this.getCacheKey(adminId, restId)); } }