read permissions from cache

This commit is contained in:
2026-06-23 11:04:53 +03:30
parent 20874e743f
commit f26d341768
3 changed files with 57 additions and 23 deletions
+4 -1
View File
@@ -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<number>('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);
@@ -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) {
@@ -31,44 +31,80 @@ export class PermissionsService {
* @returns Array of permission names (string[])
*/
async getAdminPermissions(adminId: string, restId: string): Promise<string[]> {
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<string>(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<string[]> {
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<string[] | undefined> {
const cacheKey = this.getCacheKey(adminId, restId);
const cachedPermissions = await this.cacheService.get<string | string[]>(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<void> {
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<string[]> {
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<Permission[]> {
@@ -104,7 +137,6 @@ export class PermissionsService {
* @param restId - The restaurant ID
*/
async invalidateAdminPermissionsCache(adminId: string, restId: string): Promise<void> {
const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`;
await this.cacheService.del(cacheKey);
await this.cacheService.del(this.getCacheKey(adminId, restId));
}
}