import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository } from '@mikro-orm/core'; import { Admin } from '../entities/admin.entity'; import { Role } from '../entities/role.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { CacheService } from '../../utils/cache.service'; import { AdminDetailTransformer, AdminDetailResponse } from '../transformers/admin-detail.transformer'; @Injectable() export class AdminService { private readonly ADMIN_PERMISSIONS_KEY = 'admin-perms'; constructor( @InjectRepository(Admin) private readonly adminRepository: EntityRepository, private readonly em: EntityManager, private readonly cacheService: CacheService, ) {} async findByPhone(phone: string): Promise { return this.adminRepository.findOne({ phone }); } async findById(id: string): Promise { const admin = await this.adminRepository.findOne({ id }, { populate: ['role', 'role.permissions', 'restaurant'] }); if (!admin) { return null; } return AdminDetailTransformer.transform(admin); } async findAll(): Promise { return this.adminRepository.findAll(); } async create(data: { phone: string; firstName?: string; lastName?: string; roleId: string; restId: string }) { const { phone, firstName, lastName, roleId, restId } = data; // check existing const existing = await this.adminRepository.findOne({ phone, restaurant: { id: restId } }); if (existing) { return existing; } // load Role and Restaurant using the EntityManager const role = await this.em.findOne(Role, { id: roleId }); if (!role) throw new Error('Role not found'); const restaurant = await this.em.findOne(Restaurant, { id: restId }); if (!restaurant) throw new Error('Restaurant not found'); const admin = this.adminRepository.create({ phone, firstName, lastName, role, restaurant }); await this.em.persistAndFlush(admin); return admin; } /** * Get admin permissions from cache or database * @param adminId - The admin ID * @param restId - The restaurant ID * @returns Array of permission names (string[]) */ async getAdminPermissions(adminId: string, restId: string): Promise { const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`; // Try to get from cache first const cachedPermissions = await this.cacheService.get(cacheKey); if (cachedPermissions) { 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 } } // If not in cache, fetch from database const admin = await this.adminRepository.findOne( { id: adminId, restaurant: { id: restId } }, { populate: ['role', 'role.permissions'] }, ); if (!admin || !admin.role) { return []; } // Extract permission names as array of strings const permissions = await admin.role.permissions.loadItems(); const permissionNames: string[] = permissions .map(p => p.name) .filter((name): name is string => typeof name === 'string'); // Store in cache as JSON stringified array of strings (1 hour TTL) await this.cacheService.set(cacheKey, JSON.stringify(permissionNames), 3600); return permissionNames; } /** * Invalidate admin permissions cache * @param adminId - The admin ID * @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); } }