Add AdminId decorator for extracting adminId from requests; refactor AdminController to utilize new decorator for fetching admin details and restaurant-specific admins; update AdminService to handle admin creation and retrieval by restaurant; remove unused role and permission entities for cleaner architecture.

This commit is contained in:
2025-11-18 16:45:44 +03:30
parent 9abab789fc
commit 8a217f6034
18 changed files with 254 additions and 206 deletions
@@ -0,0 +1,18 @@
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
import type { Request } from 'express';
/**
* Decorator to extract userId from the authenticated request.
* Must be used after AdminAuthGuard or AuthGuard that sets request.userId.
*
* @example
* @Get('/profile')
* @UseGuards(AdminAuthGuard)
* getProfile(@UserId() userId: string) {
* return this.userService.findById(userId);
* }
*/
export const AdminId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest<Request & { adminId?: string }>();
return request.adminId || '';
});
@@ -3,6 +3,8 @@ import { AdminService } from '../providers/admin.service';
import { CreateAdminDto } from '../dto/create-admin.dto'; import { CreateAdminDto } from '../dto/create-admin.dto';
import { ApiTags, ApiOperation, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { RestId } from 'src/common/decorators';
import { AdminId } from 'src/common/decorators/admin-id.decorator';
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@@ -11,19 +13,19 @@ import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
export class AdminController { export class AdminController {
constructor(private readonly adminService: AdminService) {} constructor(private readonly adminService: AdminService) {}
@Get() @Get('my-restaurant-admins')
@ApiOperation({ summary: 'admin' }) @ApiOperation({ summary: 'admin' })
@ApiResponse({ status: 201, description: 'Admins' }) @ApiResponse({ status: 201, description: 'Admins' })
async getAll() { async getAll(@RestId() restId: string) {
const admin = await this.adminService.findAll(); const admin = await this.adminService.findAllByRestaurantId(restId);
return admin; return admin;
} }
@Get('me') @Get('me')
@ApiOperation({ summary: 'admin' }) @ApiOperation({ summary: 'admin' })
@ApiResponse({ status: 201, description: 'Admins' }) @ApiResponse({ status: 201, description: 'Admins' })
async getMe() { async getMe(@AdminId() adminId: string) {
const admin = await this.adminService.findAll(); const admin = await this.adminService.findById(adminId);
return admin; return admin;
} }
@@ -33,7 +35,7 @@ export class AdminController {
@ApiBody({ type: CreateAdminDto }) @ApiBody({ type: CreateAdminDto })
@ApiResponse({ status: 201, description: 'Admin created' }) @ApiResponse({ status: 201, description: 'Admin created' })
async create(@Body() dto: CreateAdminDto) { async create(@Body() dto: CreateAdminDto) {
const admin = await this.adminService.create({ const admin = await this.adminService.createRestaurantBySuperAdmin({
phone: dto.phone, phone: dto.phone,
firstName: dto.firstName, firstName: dto.firstName,
lastName: dto.lastName, lastName: dto.lastName,
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class CreateMyRestaurantAdminDto {
@ApiProperty({ description: 'Mobile phone number' })
@IsNotEmpty()
@IsString()
phone!: string;
@ApiProperty({ description: 'First name', required: false })
@IsOptional()
@IsString()
firstName?: string;
@ApiProperty({ description: 'Last name', required: false })
@IsOptional()
@IsString()
lastName?: string;
@ApiProperty({ description: 'Role id for the admin' })
@IsNotEmpty()
@IsString()
roleId!: string;
}
+7 -10
View File
@@ -1,11 +1,8 @@
import { Entity, ManyToOne, Property } from '@mikro-orm/core'; import { Cascade, Collection, Entity, OneToMany, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
import { Role } from '../../roles/entities/role.entity'; import { AdminRole } from './adminRole.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
@Entity({ tableName: 'admins' }) @Entity({ tableName: 'admins' })
// Add the Unique constraint for the combination of 'phone' and 'restaurant'
// @Unique({ properties: ['phone', 'restaurant'] })
export class Admin extends BaseEntity { export class Admin extends BaseEntity {
@Property({ nullable: true }) @Property({ nullable: true })
firstName?: string; firstName?: string;
@@ -17,9 +14,9 @@ export class Admin extends BaseEntity {
phone!: string; phone!: string;
// Add the new role property // Add the new role property
@ManyToOne(() => Role) @OneToMany(() => AdminRole, adminRole => adminRole.admin, {
role!: Role; cascade: [Cascade.ALL],
orphanRemoval: true,
@ManyToOne(() => Restaurant, { nullable: true }) })
restaurant?: Restaurant; roles = new Collection<AdminRole>(this);
} }
@@ -1,13 +1,12 @@
import { Entity, ManyToOne } from '@mikro-orm/core'; import { Entity, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
import { Role } from './role.entity'; import { Role } from '../../roles/entities/role.entity';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
import { Admin } from './admin.entity'; import { Admin } from './admin.entity';
@Entity({ tableName: 'admin_roles' }) @Entity({ tableName: 'admin_roles' })
// Add the Unique constraint for the combination of 'phone' and 'restaurant'
// @Unique({ properties: ['phone', 'restaurant'] })
export class AdminRole extends BaseEntity { export class AdminRole extends BaseEntity {
@Unique({ properties: ['admin.id', 'role.id', 'restaurant.id'] })
@ManyToOne(() => Admin) @ManyToOne(() => Admin)
admin!: Admin; admin!: Admin;
@@ -1,16 +0,0 @@
import { Entity, Property, Unique, ManyToMany, Collection } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Role } from './role.entity';
@Entity({ tableName: 'permissions' })
export class Permission extends BaseEntity {
@Property()
@Unique()
name!: string;
@Property()
title!: string;
@ManyToMany({ entity: () => Role, mappedBy: r => r.permissions })
roles = new Collection<Role>(this);
}
-29
View File
@@ -1,29 +0,0 @@
// role.entity.ts
import { Collection, Entity, ManyToMany, OneToMany, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Admin } from './admin.entity';
import { Permission } from './permission.entity';
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
import { RolePermission } from './rolePermission.entity';
import { AdminRole } from './adminRole.entity';
@Entity({ tableName: 'roles' })
export class Role extends BaseEntity {
@Property()
name!: string;
@Property()
title!: string;
@ManyToMany({ entity: () => Permission, pivotEntity: () => RolePermission, inversedBy: p => p.roles })
permissions = new Collection<Permission>(this);
@OneToMany(() => Admin, admin => admin.role)
admins = new Collection<Admin>(this);
@ManyToOne(() => Restaurant, { nullable: true })
restaurant?: Restaurant;
@OneToMany(() => AdminRole, adminRoleRestaurant => adminRoleRestaurant.role)
adminRoles = new Collection<AdminRole>(this);
}
@@ -1,17 +0,0 @@
// role-permission.entity.ts (pivot)
import { Entity, ManyToOne } from '@mikro-orm/core';
import { Role } from './role.entity';
import { Permission } from './permission.entity';
@Entity({ tableName: 'role_permissions' })
export class RolePermission {
// دو ManyToOne دقیقاً؛ هر کدام می‌تونن primary: true باشند برای composite PK
@ManyToOne(() => Role, { primary: true })
role!: Role;
@ManyToOne(() => Permission, { primary: true })
permission!: Permission;
// **نکته:** اینجا *فقط* همین دو خصوصیت کافی است —
// از تعریف جداگانه‌ی roleId / permissionId با @PrimaryKey خودداری کن مگر بخوای نام/نوع ستون را سفارشی کنی.
}
+33 -71
View File
@@ -6,12 +6,10 @@ import { Role } from '../../roles/entities/role.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { CacheService } from '../../utils/cache.service'; import { CacheService } from '../../utils/cache.service';
import { AdminDetailTransformer, AdminDetailResponse } from '../transformers/admin-detail.transformer'; import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
private readonly ADMIN_PERMISSIONS_KEY = 'admin-perms';
constructor( constructor(
@InjectRepository(Admin) @InjectRepository(Admin)
private readonly adminRepository: EntityRepository<Admin>, private readonly adminRepository: EntityRepository<Admin>,
@@ -23,24 +21,44 @@ export class AdminService {
return this.adminRepository.findOne({ phone }); return this.adminRepository.findOne({ phone });
} }
async findById(id: string): Promise<AdminDetailResponse | null> { async findById(id: string): Promise<Admin | null> {
const admin = await this.adminRepository.findOne({ id }, { populate: ['role', 'role.permissions', 'restaurant'] }); const admin = await this.adminRepository.findOne({ id }, { populate: ['roles', 'roles.role'] });
return admin;
}
if (!admin) { async findAllByRestaurantId(restId: string): Promise<Admin[]> {
return null; return this.adminRepository.find({ roles: { restaurant: { id: restId } } }, { populate: ['roles', 'roles.role'] });
}
async createAdminForMyRestaurant(restId: string, dto: CreateMyRestaurantAdminDto) {
const { phone, firstName, lastName, roleId } = dto;
const currentAdmin = await this.adminRepository.findOne({
phone,
roles: { role: { id: roleId }, restaurant: { id: restId } },
});
if (currentAdmin) {
return currentAdmin;
} }
const role = await this.em.findOne(Role, { id: roleId });
return AdminDetailTransformer.transform(admin); if (!role) throw new Error('Role not found');
} const restaurant = await this.em.findOne(Restaurant, { id: restId });
async findAll(): Promise<Admin[]> { if (!restaurant) throw new Error('Restaurant not found');
return this.adminRepository.findAll(); const admin = this.adminRepository.create({ phone, firstName, lastName, roles: [{ role, restaurant }] });
await this.em.persistAndFlush(admin);
return admin;
} }
async create(data: { phone: string; firstName?: string; lastName?: string; roleId: string; restId: string }) { async createRestaurantBySuperAdmin(data: {
phone: string;
firstName?: string;
lastName?: string;
roleId: string;
restId: string;
}) {
const { phone, firstName, lastName, roleId, restId } = data; const { phone, firstName, lastName, roleId, restId } = data;
// check existing // check existing
const existing = await this.adminRepository.findOne({ phone, restaurant: { id: restId } }); const existing = await this.adminRepository.findOne({ phone, roles: { restaurant: { id: restId } } });
if (existing) { if (existing) {
return existing; return existing;
} }
@@ -52,64 +70,8 @@ export class AdminService {
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const restaurant = await this.em.findOne(Restaurant, { id: restId });
if (!restaurant) throw new Error('Restaurant not found'); if (!restaurant) throw new Error('Restaurant not found');
const admin = this.adminRepository.create({ phone, firstName, lastName, role, restaurant }); const admin = this.adminRepository.create({ phone, firstName, lastName, roles: [{ role, restaurant }] });
await this.em.persistAndFlush(admin); await this.em.persistAndFlush(admin);
return 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);
}
} }
@@ -8,11 +8,11 @@ export class AdminRepository extends EntityRepository<Admin> {
super(em, Admin); super(em, Admin);
} }
async findByPhoneAndrestaurantSlug(phone: string, slug: string): Promise<Admin | null> { async findByPhoneAndRestaurantSlug(phone: string, slug: string): Promise<Admin | null> {
return this.em.findOne( return this.em.findOne(
Admin, Admin,
{ phone, restaurant: { slug } }, { phone, roles: { role: { restaurant: { slug } } } },
{ populate: ['restaurant', 'role', 'role.permissions'] }, { populate: ['roles', 'roles.role'] },
); );
} }
} }
@@ -19,49 +19,34 @@ export interface AdminDetailResponse {
} }
export class AdminDetailTransformer { export class AdminDetailTransformer {
static async transform(admin: Admin): Promise<AdminDetailResponse | null> { static transform(admin: Admin): AdminDetailResponse | null {
if (!admin) { if (!admin) {
return null; return null;
} }
// Extract role information // Extract role information
const role = admin.role const role = admin.roles.getItems().find(r => r.role)?.role;
? {
id: admin.role.id,
name: admin.role.name,
title: admin.role.title,
}
: null;
if (!role) { if (!role) {
return null; return null;
} }
// Extract permissions - ensure collection is loaded if needed
let permissions: string[] = [];
if (admin.role?.permissions) {
if (admin.role.permissions.isInitialized()) {
permissions = admin.role.permissions.getItems().map(p => p.name);
} else {
await admin.role.permissions.loadItems();
permissions = admin.role.permissions.getItems().map(p => p.name);
}
}
return { return {
id: admin.id, id: admin.id,
firstName: admin.firstName, firstName: admin.firstName,
lastName: admin.lastName, lastName: admin.lastName,
phone: admin.phone, phone: admin.phone,
role, role: {
permissions, id: role.id,
restaurant: admin.restaurant name: role.name,
? { title: role.title,
id: admin.restaurant.id, },
name: admin.restaurant.name, permissions: role.permissions.getItems().map(p => p.name) || [],
slug: admin.restaurant.slug, restaurant: {
} id: role.restaurant?.id || '',
: undefined, name: role.restaurant?.name || '',
slug: role.restaurant?.slug || '',
},
}; };
} }
} }
// Extract permissions - ensure collection is loaded if needed
+3 -3
View File
@@ -13,7 +13,7 @@ import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../interfaces/IToken-payload'; import { IAdminTokenPayload } from '../interfaces/IToken-payload';
import { PERMISSIONS_KEY } from '../decorators/permissions.decorator'; import { PERMISSIONS_KEY } from '../decorators/permissions.decorator';
import { AdminService } from 'src/modules/admin/providers/admin.service'; import { PermissionsService } from 'src/modules/roles/providers/permissions.service';
export interface AdminAuthRequest extends Request { export interface AdminAuthRequest extends Request {
adminId: string; adminId: string;
@@ -31,7 +31,7 @@ export class AdminAuthGuard implements CanActivate {
private readonly configService: ConfigService, private readonly configService: ConfigService,
@Inject(ConfigService) @Inject(ConfigService)
private readonly adminService: AdminService, private readonly permissionsService: PermissionsService,
private readonly reflector: Reflector, private readonly reflector: Reflector,
) {} ) {}
@@ -62,7 +62,7 @@ export class AdminAuthGuard implements CanActivate {
if (!requiredPermissions || requiredPermissions.length === 0) return true; if (!requiredPermissions || requiredPermissions.length === 0) return true;
const adminPermission = await this.adminService.getAdminPermissions(payload.adminId, payload.restId); const adminPermission = await this.permissionsService.getAdminPermissions(payload.adminId, payload.restId);
if (!adminPermission || !Array.isArray(adminPermission)) { if (!adminPermission || !Array.isArray(adminPermission)) {
this.logger.error('No permissions found'); this.logger.error('No permissions found');
throw new ForbiddenException('No permissions found'); throw new ForbiddenException('No permissions found');
+2 -2
View File
@@ -38,7 +38,7 @@ export class AuthService {
async requestOtpAdmin(dto: RequestOtpDto) { async requestOtpAdmin(dto: RequestOtpDto) {
const { phone, slug } = dto; const { phone, slug } = dto;
const admin = await this.adminRepository.findByPhoneAndrestaurantSlug(phone, slug); const admin = await this.adminRepository.findByPhoneAndRestaurantSlug(phone, slug);
if (!admin) { if (!admin) {
throw new NotFoundException(); throw new NotFoundException();
} }
@@ -83,7 +83,7 @@ export class AuthService {
await this.cacheService.del(`otp-admin:${slug}:${phone}`); await this.cacheService.del(`otp-admin:${slug}:${phone}`);
const admin = await this.adminRepository.findByPhoneAndrestaurantSlug(phone, slug); const admin = await this.adminRepository.findByPhoneAndRestaurantSlug(phone, slug);
if (!admin) { if (!admin) {
throw new BadRequestException(AuthMessage.USER_NOT_FOUND); throw new BadRequestException(AuthMessage.USER_NOT_FOUND);
@@ -17,17 +17,60 @@ export interface AdminLoginResponse {
export class AdminLoginTransformer { export class AdminLoginTransformer {
static async transform(admin: Admin, restaurant?: Restaurant): Promise<AdminLoginResponse> { static async transform(admin: Admin, restaurant?: Restaurant): Promise<AdminLoginResponse> {
// Extract role name // Find the AdminRole that matches the restaurant (or get the first one)
const role = admin.role?.name || ''; let adminRole = admin.roles.getItems().find(r => (restaurant ? r.restaurant?.id === restaurant.id : true));
// If no match found, get the first one
if (!adminRole && admin.roles.getItems().length > 0) {
adminRole = admin.roles.getItems()[0];
}
if (!adminRole) {
return {
id: admin.id,
firstName: admin.firstName,
lastName: admin.lastName,
phone: admin.phone,
role: '',
permissions: [],
restaurant: restaurant
? {
id: restaurant.id,
name: restaurant.name,
slug: restaurant.slug,
}
: undefined,
};
}
// Get the role from AdminRole
const role = adminRole.role;
if (!role) {
return {
id: admin.id,
firstName: admin.firstName,
lastName: admin.lastName,
phone: admin.phone,
role: '',
permissions: [],
restaurant: restaurant
? {
id: restaurant.id,
name: restaurant.name,
slug: restaurant.slug,
}
: undefined,
};
}
// Extract permissions - ensure collection is loaded if needed // Extract permissions - ensure collection is loaded if needed
let permissions: string[] = []; let permissions: string[] = [];
if (admin.role?.permissions) { if (role.permissions) {
if (admin.role.permissions.isInitialized()) { if (role.permissions.isInitialized()) {
permissions = admin.role.permissions.getItems().map(p => p.name); permissions = role.permissions.getItems().map(p => p.name);
} else { } else {
await admin.role.permissions.loadItems(); await role.permissions.loadItems();
permissions = admin.role.permissions.getItems().map(p => p.name); permissions = role.permissions.getItems().map(p => p.name);
} }
} }
@@ -36,7 +79,7 @@ export class AdminLoginTransformer {
firstName: admin.firstName, firstName: admin.firstName,
lastName: admin.lastName, lastName: admin.lastName,
phone: admin.phone, phone: admin.phone,
role, role: role.name,
permissions, permissions,
restaurant: restaurant restaurant: restaurant
? { ? {
@@ -4,6 +4,7 @@ import { BaseEntity } from '../../../common/entities/base.entity';
import { Permission } from './permission.entity'; import { Permission } from './permission.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { RolePermission } from './rolePermission.entity'; import { RolePermission } from './rolePermission.entity';
import { AdminRole } from 'src/modules/admin/entities/adminRole.entity';
@Entity({ tableName: 'roles' }) @Entity({ tableName: 'roles' })
export class Role extends BaseEntity { export class Role extends BaseEntity {
@@ -18,4 +19,7 @@ export class Role extends BaseEntity {
@ManyToOne(() => Restaurant, { nullable: true }) @ManyToOne(() => Restaurant, { nullable: true })
restaurant?: Restaurant; restaurant?: Restaurant;
@OneToMany(() => AdminRole, adminRole => adminRole.role)
admins = new Collection<AdminRole>(this);
} }
@@ -2,16 +2,71 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@mikro-orm/nestjs'; import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/core'; import { EntityRepository } from '@mikro-orm/core';
import { Permission } from '../entities/permission.entity'; import { Permission } from '../entities/permission.entity';
import { CacheService } from 'src/modules/utils/cache.service';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
@Injectable() @Injectable()
export class PermissionsService { export class PermissionsService {
private readonly ADMIN_PERMISSIONS_KEY = 'admin-perms';
constructor( constructor(
@InjectRepository(Permission) @InjectRepository(Permission)
private readonly permissionRepository: EntityRepository<Permission>, private readonly permissionRepository: EntityRepository<Permission>,
private readonly cacheService: CacheService,
private readonly adminRepository: AdminRepository,
) {} ) {}
async findAll() { async findAll() {
const permissions = await this.permissionRepository.findAll(); const permissions = await this.permissionRepository.findAll();
return permissions; return permissions;
} }
/**
* 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, roles: { restaurant: { id: restId } } },
{ populate: ['roles', 'roles.role'] },
);
if (!admin || !admin.roles) {
return [];
}
// Extract permission names as array of strings
const permissions = admin.roles.map(r => r.role.permissions.getItems());
return permissions.flat().map(p => p.name);
}
/**
* 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);
}
} }
+3 -4
View File
@@ -7,12 +7,11 @@ import { Role } from './entities/role.entity';
import { Permission } from './entities/permission.entity'; import { Permission } from './entities/permission.entity';
import { RolePermission } from './entities/rolePermission.entity'; import { RolePermission } from './entities/rolePermission.entity';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { UtilsModule } from '../utils/utils.module';
import { AdminModule } from '../admin/admin.module';
@Module({ @Module({
imports: [ imports: [MikroOrmModule.forFeature([Role, Permission, RolePermission]), JwtModule, UtilsModule, AdminModule],
MikroOrmModule.forFeature([Role, Permission, RolePermission]),
JwtModule,
],
controllers: [RolesController], controllers: [RolesController],
providers: [RolesService, PermissionsService], providers: [RolesService, PermissionsService],
exports: [RolesService, PermissionsService], exports: [RolesService, PermissionsService],
+25 -3
View File
@@ -3,6 +3,7 @@ import { Seeder } from '@mikro-orm/seeder';
import { Category } from '../modules/foods/entities/category.entity'; import { Category } from '../modules/foods/entities/category.entity';
import { Food } from '../modules/foods/entities/food.entity'; import { Food } from '../modules/foods/entities/food.entity';
import { Admin } from '../modules/admin/entities/admin.entity'; import { Admin } from '../modules/admin/entities/admin.entity';
import { AdminRole } from '../modules/admin/entities/adminRole.entity';
import { Permission as PermissionEntity } from '../modules/roles/entities/permission.entity'; import { Permission as PermissionEntity } from '../modules/roles/entities/permission.entity';
import { Role } from '../modules/roles/entities/role.entity'; import { Role } from '../modules/roles/entities/role.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity'; import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
@@ -421,10 +422,17 @@ export class DatabaseSeeder extends Seeder {
phone: '09362532122', phone: '09362532122',
firstName: 'مرتضی', firstName: 'مرتضی',
lastName: 'مرتضایی', lastName: 'مرتضایی',
});
em.persist(admin);
// Create AdminRole linking admin to role and restaurant
const adminRole = em.create(AdminRole, {
admin,
role: restaurantRole, role: restaurantRole,
restaurant: zhivan, restaurant: zhivan,
}); });
em.persist(admin); em.persist(adminRole);
admin.roles.add(adminRole);
} }
const adminHamid = await em.findOne(Admin, { phone: '09185290775' }); const adminHamid = await em.findOne(Admin, { phone: '09185290775' });
@@ -433,10 +441,17 @@ export class DatabaseSeeder extends Seeder {
phone: '09185290775', phone: '09185290775',
firstName: 'حمید', firstName: 'حمید',
lastName: 'زرگامی', lastName: 'زرگامی',
});
em.persist(admin);
// Create AdminRole linking admin to role and restaurant
const adminRole = em.create(AdminRole, {
admin,
role: accountant, role: accountant,
restaurant: boote, restaurant: boote,
}); });
em.persist(admin); em.persist(adminRole);
admin.roles.add(adminRole);
} }
const adminMehrdad = await em.findOne(Admin, { phone: '0912928395' }); const adminMehrdad = await em.findOne(Admin, { phone: '0912928395' });
@@ -445,10 +460,17 @@ export class DatabaseSeeder extends Seeder {
phone: '0912928395', phone: '0912928395',
firstName: 'مهرداد', firstName: 'مهرداد',
lastName: 'مظفری', lastName: 'مظفری',
});
em.persist(admin);
// Create AdminRole linking admin to role (no restaurant for superAdmin)
const adminRole = em.create(AdminRole, {
admin,
role: superAdmin, role: superAdmin,
restaurant: null, // SuperAdmin doesn't belong to a specific restaurant restaurant: null, // SuperAdmin doesn't belong to a specific restaurant
}); });
em.persist(admin); em.persist(adminRole);
admin.roles.add(adminRole);
} }
await em.flush(); await em.flush();