read permissions from cache
This commit is contained in:
@@ -12,10 +12,10 @@ import { AdminLoginTransformer } from '../transformers/admin-login.transformer';
|
|||||||
import { UserLoginTransformer } from '../transformers/user-login.transformer';
|
import { UserLoginTransformer } from '../transformers/user-login.transformer';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { normalizePhone } from '../../utils/phone.util';
|
import { normalizePhone } from '../../utils/phone.util';
|
||||||
|
import { PermissionsService } from 'src/modules/roles/providers/permissions.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
readonly ADMIN_PERMISSIONS_KEY = 'admin-perms';
|
|
||||||
private OTP_EXPIRATION_TIME: number;
|
private OTP_EXPIRATION_TIME: number;
|
||||||
constructor(
|
constructor(
|
||||||
private readonly cacheService: CacheService,
|
private readonly cacheService: CacheService,
|
||||||
@@ -25,6 +25,7 @@ export class AuthService {
|
|||||||
private readonly restRepository: RestRepository,
|
private readonly restRepository: RestRepository,
|
||||||
private readonly adminRepository: AdminRepository,
|
private readonly adminRepository: AdminRepository,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
private readonly permissionsService: PermissionsService,
|
||||||
) {
|
) {
|
||||||
this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240;
|
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);
|
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);
|
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);
|
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);
|
const adminResponse = await AdminLoginTransformer.transform(admin, rest);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export class RolesController {
|
|||||||
|
|
||||||
@Get('admin/roles/permissions')
|
@Get('admin/roles/permissions')
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Permissions(Permission.MANAGE_ROLES)
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOperation({ summary: 'Get all permissions that the admin has' })
|
@ApiOperation({ summary: 'Get all permissions that the admin has' })
|
||||||
async findAllPermissions(@AdminId() adminId: string, @RestId() restId: string) {
|
async findAllPermissions(@AdminId() adminId: string, @RestId() restId: string) {
|
||||||
|
|||||||
@@ -31,44 +31,80 @@ export class PermissionsService {
|
|||||||
* @returns Array of permission names (string[])
|
* @returns Array of permission names (string[])
|
||||||
*/
|
*/
|
||||||
async getAdminPermissions(adminId: string, restId: string): Promise<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 permissionNames = await this.fetchAdminPermissionsFromDb(adminId, restId);
|
||||||
const cachedPermissions = await this.cacheService.get<string>(cacheKey);
|
await this.cacheAdminPermissions(adminId, restId, permissionNames);
|
||||||
if (cachedPermissions) {
|
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 {
|
try {
|
||||||
const parsed: unknown = JSON.parse(cachedPermissions);
|
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')) {
|
if (Array.isArray(parsed) && parsed.every((p): p is string => typeof p === 'string')) {
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
// If invalid format, continue to fetch from DB
|
|
||||||
} catch {
|
} 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(
|
const admin = await this.adminRepository.findOne(
|
||||||
{ id: adminId, roles: { restaurant: { id: restId } } },
|
{ 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) {
|
if (!admin) {
|
||||||
await this.cacheService.set(cacheKey, JSON.stringify([]), this.ADMIN_PERMISSIONS_TTL);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure roles collection is loaded
|
|
||||||
await admin.roles.loadItems();
|
await admin.roles.loadItems();
|
||||||
|
|
||||||
// Extract permission names as array of strings
|
|
||||||
const permissions = await Promise.all(
|
const permissions = await Promise.all(
|
||||||
admin.roles
|
admin.roles
|
||||||
.getItems()
|
.getItems()
|
||||||
.filter(r => r.role) // Filter out any null/undefined roles
|
.filter(r => r.role)
|
||||||
.map(async r => {
|
.map(async r => {
|
||||||
// Ensure permissions collection is initialized
|
|
||||||
if (!r.role.permissions.isInitialized()) {
|
if (!r.role.permissions.isInitialized()) {
|
||||||
await r.role.permissions.loadItems();
|
await r.role.permissions.loadItems();
|
||||||
}
|
}
|
||||||
@@ -76,10 +112,7 @@ export class PermissionsService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionNames = permissions.flat().map(p => p.name);
|
return permissions.flat().map(p => p.name);
|
||||||
await this.cacheService.set(cacheKey, JSON.stringify(permissionNames), this.ADMIN_PERMISSIONS_TTL);
|
|
||||||
|
|
||||||
return permissionNames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAdminFullPermissions(adminId: string, restId: string): Promise<Permission[]> {
|
async getAdminFullPermissions(adminId: string, restId: string): Promise<Permission[]> {
|
||||||
@@ -104,7 +137,6 @@ export class PermissionsService {
|
|||||||
* @param restId - The restaurant ID
|
* @param restId - The restaurant ID
|
||||||
*/
|
*/
|
||||||
async invalidateAdminPermissionsCache(adminId: string, restId: string): Promise<void> {
|
async invalidateAdminPermissionsCache(adminId: string, restId: string): Promise<void> {
|
||||||
const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`;
|
await this.cacheService.del(this.getCacheKey(adminId, restId));
|
||||||
await this.cacheService.del(cacheKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user