Files
dmenu-api/src/modules/admin/providers/admin.service.ts
T

116 lines
4.0 KiB
TypeScript

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<Admin>,
private readonly em: EntityManager,
private readonly cacheService: CacheService,
) {}
async findByPhone(phone: string): Promise<Admin | null> {
return this.adminRepository.findOne({ phone });
}
async findById(id: string): Promise<AdminDetailResponse | null> {
const admin = await this.adminRepository.findOne({ id }, { populate: ['role', 'role.permissions', 'restaurant'] });
if (!admin) {
return null;
}
return AdminDetailTransformer.transform(admin);
}
async findAll(): Promise<Admin[]> {
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<string[]> {
const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`;
// Try to get from cache first
const cachedPermissions = await this.cacheService.get<string>(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<void> {
const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`;
await this.cacheService.del(cacheKey);
}
}