From d046dc21accc266f352eb62293ba1c99b90d7f28 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 13 Jan 2026 23:28:33 +0330 Subject: [PATCH] role module --- .../admin/controllers/admin.controller.ts | 61 ++---- src/modules/admin/dto/create-admin.dto.ts | 3 +- .../dto/create-my-restaurant-admin.dto.ts | 24 --- src/modules/admin/entities/admin.entity.ts | 17 +- .../admin/entities/adminRole.entity.ts | 21 -- src/modules/admin/providers/admin.service.ts | 183 +++++------------- .../admin/repositories/admin.repository.ts | 43 +--- .../roles/controllers/roles.controller.ts | 44 ++--- src/modules/roles/dto/create-role.dto.ts | 7 +- src/modules/roles/dto/update-role.dto.ts | 14 +- .../roles/entities/permission.entity.ts | 10 +- src/modules/roles/entities/role.entity.ts | 22 ++- .../roles/providers/permissions.service.ts | 82 +------- src/modules/roles/providers/roles.service.ts | 63 +++--- .../roles/respository/role.repository.ts | 13 ++ src/modules/roles/roles.module.ts | 5 +- src/seeders/data/foods.data.ts | 1 + 17 files changed, 171 insertions(+), 442 deletions(-) delete mode 100644 src/modules/admin/dto/create-my-restaurant-admin.dto.ts delete mode 100644 src/modules/admin/entities/adminRole.entity.ts create mode 100644 src/modules/roles/respository/role.repository.ts diff --git a/src/modules/admin/controllers/admin.controller.ts b/src/modules/admin/controllers/admin.controller.ts index 03a0826..2f1e8c5 100644 --- a/src/modules/admin/controllers/admin.controller.ts +++ b/src/modules/admin/controllers/admin.controller.ts @@ -4,26 +4,35 @@ import { CreateAdminDto } from '../dto/create-admin.dto'; import { UpdateAdminDto } from '../dto/update-admin.dto'; import { ApiTags, ApiOperation, ApiBody, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; -import { SuperAdminAuthGuard } from 'src/modules/auth/guards/superAdminAuth.guard'; -import { } from 'src/common/decorators'; import { AdminId } from 'src/common/decorators/admin-id.decorator'; import { Admin } from '../entities/admin.entity'; import { Permission } from 'src/common/enums/permission.enum'; import { Permissions } from 'src/common/decorators/permissions.decorator'; -import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto'; +// TODO : if admin deleted then recreated it must be doable @ApiBearerAuth() @ApiTags('admin') @Controller() export class AdminController { constructor(private readonly adminService: AdminService) { } + @Post('admin/admin') + @UseGuards(AdminAuthGuard) + @Permissions(Permission.MANAGE_ADMINS) + @HttpCode(HttpStatus.CREATED) + @ApiOperation({ summary: 'Create a new admin' }) + @ApiBody({ type: CreateAdminDto }) + async create(@Body() dto: CreateAdminDto,) { + const admin = await this.adminService.create(dto); + return admin; + } + @Get('admin/admins') @UseGuards(AdminAuthGuard) @Permissions(Permission.MANAGE_ADMINS) @ApiOperation({ summary: 'admin' }) async getAll() { - const admin = await this.adminService.findAllBy(); + const admin = await this.adminService.findAll(); return admin; } @@ -36,16 +45,6 @@ export class AdminController { return admin; } - @Post('admin/admins') - @UseGuards(AdminAuthGuard) - @Permissions(Permission.MANAGE_ADMINS) - @HttpCode(HttpStatus.CREATED) - @ApiOperation({ summary: 'Create a new admin' }) - @ApiBody({ type: CreateAdminDto }) - async create(@Body() dto: CreateAdminDto,) { - const admin = await this.adminService.createAdminForMyRestaurant(, dto); - return admin; - } @Patch('admin/admins/:adminId') @UseGuards(AdminAuthGuard) @@ -54,7 +53,7 @@ export class AdminController { @ApiParam({ name: 'adminId', description: 'Admin ID' }) @ApiBody({ type: UpdateAdminDto }) update(@Param('adminId') adminId: string, @Body() dto: UpdateAdminDto,): Promise { - return this.adminService.update(adminId, , dto); + return this.adminService.update(adminId, dto); } @Get('admin/admins/:adminId') @@ -75,36 +74,4 @@ export class AdminController { return this.adminService.remove(adminId,); } - /** Super Admin Endpoints */ - @UseGuards(SuperAdminAuthGuard) - @Get('super-admin/restaurants/:/admins') - @ApiOperation({ summary: 'Get admins for a specific restaurant (Super Admin only)' }) - @ApiParam({ name: '', description: 'Restaurant ID' }) - getAdminForRestaurant(@Param('') : string): Promise { - return this.adminService.getAdminsForRestaurantBySuperAdmin(); - } - - @UseGuards(SuperAdminAuthGuard) - @Post('super-admin/restaurants/:/admins') - @ApiOperation({ summary: 'Create admin for a specific restaurant (Super Admin only)' }) - @ApiParam({ name: '', description: 'Restaurant ID' }) - @ApiBody({ type: CreateMyRestaurantAdminDto }) - createAdminForRestaurant( - @Param('') : string, - @Body() dto: CreateMyRestaurantAdminDto, - ): Promise { - return this.adminService.createAdminForRestaurantBySuperAdmin(, dto); - } - - @UseGuards(SuperAdminAuthGuard) - @Delete('super-admin/restaurants/:/admins/:adminId') - @ApiOperation({ summary: 'Revoke admin role from a restaurant (Super Admin only)' }) - @ApiParam({ name: '', description: 'Restaurant ID' }) - @ApiParam({ name: 'adminId', description: 'Admin ID' }) - revokeAdminFromRestaurant( - @Param('') : string, - @Param('adminId') adminId: string, - ): Promise { - return this.adminService.remove(adminId,); - } } diff --git a/src/modules/admin/dto/create-admin.dto.ts b/src/modules/admin/dto/create-admin.dto.ts index 8acff49..81430bd 100644 --- a/src/modules/admin/dto/create-admin.dto.ts +++ b/src/modules/admin/dto/create-admin.dto.ts @@ -1,10 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsMobilePhone, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateAdminDto { @ApiProperty({ description: 'Mobile phone number' }) @IsNotEmpty() @IsString() + @IsMobilePhone('fa-IR') phone!: string; @ApiProperty({ description: 'First name', required: false }) diff --git a/src/modules/admin/dto/create-my-restaurant-admin.dto.ts b/src/modules/admin/dto/create-my-restaurant-admin.dto.ts deleted file mode 100644 index 9387a01..0000000 --- a/src/modules/admin/dto/create-my-restaurant-admin.dto.ts +++ /dev/null @@ -1,24 +0,0 @@ -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; -} diff --git a/src/modules/admin/entities/admin.entity.ts b/src/modules/admin/entities/admin.entity.ts index 2ef4985..3046ab9 100644 --- a/src/modules/admin/entities/admin.entity.ts +++ b/src/modules/admin/entities/admin.entity.ts @@ -1,10 +1,15 @@ -import { Cascade, Collection, Entity, OneToMany, Property } from '@mikro-orm/core'; +import { Entity, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; -import { AdminRole } from './adminRole.entity'; import { normalizePhone } from '../../util/phone.util'; +import { Role } from 'src/modules/roles/entities/role.entity'; +import { ulid } from 'ulid'; @Entity({ tableName: 'admins' }) export class Admin extends BaseEntity { + + @PrimaryKey({ type: 'string', columnType: 'char(26)' }) + id: string = ulid() + @Property({ nullable: true }) firstName?: string; @@ -22,10 +27,6 @@ export class Admin extends BaseEntity { this._phone = normalizePhone(value); } - // Add the new role property - @OneToMany(() => AdminRole, adminRole => adminRole.admin, { - cascade: [Cascade.ALL], - orphanRemoval: true, - }) - roles = new Collection(this); + @OneToOne(() => Role) + role: Role } diff --git a/src/modules/admin/entities/adminRole.entity.ts b/src/modules/admin/entities/adminRole.entity.ts deleted file mode 100644 index 1e001c3..0000000 --- a/src/modules/admin/entities/adminRole.entity.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Entity, Index, ManyToOne, Unique } from '@mikro-orm/core'; -import { BaseEntity } from '../../../common/entities/base.entity'; -import { Role } from '../../roles/entities/role.entity'; -import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; -import { Admin } from './admin.entity'; - -@Entity({ tableName: 'admin_roles' }) -@Unique({ properties: ['admin', 'restaurant'] }) -@Index({ properties: ['admin', 'restaurant'] }) -@Index({ properties: ['admin'] }) -@Index({ properties: ['restaurant'] }) -export class AdminRole extends BaseEntity { - @ManyToOne(() => Admin) - admin!: Admin; - - @ManyToOne(() => Role) - role!: Role; - - @ManyToOne(() => Restaurant, { nullable: true }) - restaurant?: Restaurant; -} diff --git a/src/modules/admin/providers/admin.service.ts b/src/modules/admin/providers/admin.service.ts index 5196cf9..75dbf72 100644 --- a/src/modules/admin/providers/admin.service.ts +++ b/src/modules/admin/providers/admin.service.ts @@ -1,135 +1,72 @@ import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@mikro-orm/nestjs'; -import { EntityRepository } from '@mikro-orm/core'; import { Admin } from '../entities/admin.entity'; import { Role } from '../../roles/entities/role.entity'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; -import { EntityManager } from '@mikro-orm/postgresql'; -import { CacheService } from '../../util/cache.service'; -import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto'; +import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql'; import { UpdateAdminDto } from '../dto/update-admin.dto'; -import { AdminRole } from '../entities/adminRole.entity'; import { normalizePhone } from '../../util/phone.util'; +import { CreateAdminDto } from '../dto/create-admin.dto'; +import { AdminRepository } from '../repositories/admin.repository'; +import { RoleRepository } from '../repositories/role.repository'; @Injectable() export class AdminService { constructor( - @InjectRepository(Admin) - private readonly adminRepository: EntityRepository, - @InjectRepository(AdminRole) - private readonly adminRoleRepository: EntityRepository, + private readonly adminRepository: AdminRepository, + private readonly roleRepository: RoleRepository, private readonly em: EntityManager, - private readonly cacheService: CacheService, ) { } + async create(dto: CreateAdminDto) { + const { phone, firstName, lastName, roleId } = dto; + + const normalizedPhone = normalizePhone(phone); + + const exist = await this.adminRepository.findOne({ + phone: normalizedPhone, + }); + if (exist) { + throw new NotFoundException('This phone number is already used'); + } + + const role = await this.roleRepository.findOne({ id: roleId }) + if (!role) { + throw new NotFoundException('Role not found'); + } + + const createData: RequiredEntityData = { + phone: normalizedPhone, + firstName, + lastName, + role + } + const admin = this.adminRepository.create(createData) + + await this.em.persistAndFlush(admin); + + return admin; + } + async findByPhone(phone: string): Promise { const normalizedPhone = normalizePhone(phone); return this.adminRepository.findOne({ phone: normalizedPhone }); } - async findById(adminId: string, : string): Promise { + async findById(adminId: string): Promise { const admin = await this.adminRepository.findOne( - { id: adminId, roles: { restaurant: { id: } } }, - { populate: ['roles', 'roles.role'] }, + { id: adminId }, + { populate: ['role'] }, ); return admin; } - async findAllBy(: string): Promise { - return this.adminRepository.find({ roles: { restaurant: { id: } } }, { populate: ['roles', 'roles.role'] }); + async findAll(): Promise { + return this.adminRepository.find({}, { populate: ['role', 'roles.role'] }); } - async createAdminForMyRestaurant(: string, dto: CreateMyRestaurantAdminDto) { - const { phone, firstName, lastName, roleId } = dto; - const normalizedPhone = normalizePhone(phone); - let admin: Admin | null = null; - admin = await this.adminRepository.findOne({ - phone: normalizedPhone, - }); - if (!admin) { - admin = this.adminRepository.create({ - phone: normalizedPhone, - firstName, - lastName, - }); - await this.em.persistAndFlush(admin); - } - const role = await this.em.findOne(Role, { id: roleId, isSystem: false }); - if (!role) throw new NotFoundException('Role not found'); - - const restaurant = await this.em.findOne(Restaurant, { id: }); - if (!restaurant) throw new NotFoundException('Restaurant not found'); - - let adminRole = await this.adminRoleRepository.findOne({ - admin: admin, - restaurant: restaurant, - }); - - if (!adminRole) { - adminRole = this.adminRoleRepository.create({ - admin: admin, - role, - restaurant, - }); - } else { - adminRole.role = role; - } - - await this.em.persistAndFlush(adminRole); - return admin; - } - - async createAdminForRestaurantBySuperAdmin(: string, dto: CreateMyRestaurantAdminDto) { - const { phone, firstName, lastName, roleId } = dto; - const normalizedPhone = normalizePhone(phone); - let admin: Admin | null = null; - admin = await this.adminRepository.findOne({ - phone: normalizedPhone, - }); - if (!admin) { - admin = this.adminRepository.create({ - phone: normalizedPhone, - firstName, - lastName, - }); - await this.em.persistAndFlush(admin); - } - const role = await this.em.findOne(Role, { id: roleId }); - if (!role) throw new NotFoundException('Role* not found' + roleId); - - const restaurant = await this.em.findOne(Restaurant, { id: }); - if (!restaurant) throw new NotFoundException('Restaurant not found'); - - let adminRole = await this.adminRoleRepository.findOne({ - admin: admin, - restaurant: restaurant, - }); - - if (!adminRole) { - adminRole = this.adminRoleRepository.create({ - admin: admin, - role, - restaurant, - }); - } else { - adminRole.role = role; - } - - await this.em.persistAndFlush(adminRole); - return admin; - } - - async getAdminsForRestaurantBySuperAdmin(: string): Promise { - const restaurant = await this.em.findOne(Restaurant, { id: }); - if (!restaurant) throw new NotFoundException('Restaurant not found'); - - return this.adminRepository.find({ roles: { restaurant: restaurant } }, { populate: ['roles', 'roles.role'] }); - } - - async update(adminId: string, : string, dto: UpdateAdminDto): Promise { + async update(adminId: string, dto: UpdateAdminDto): Promise { const admin = await this.adminRepository.findOne( - { id: adminId, roles: { restaurant: { id: } } }, - { populate: ['roles', 'roles.role', 'roles.restaurant'] }, + { id: adminId }, + { populate: ['role'] }, ); if (!admin) { @@ -160,38 +97,20 @@ export class AdminService { if (!role) { throw new NotFoundException('Role not found'); } - - // Find existing AdminRole for this admin and restaurant - const existingAdminRole = await this.em.findOne(AdminRole, { - admin: { id: adminId }, - restaurant: { id: }, - }); - - if (existingAdminRole) { - // Update existing role - existingAdminRole.role = role; - } else { - const restaurant = await this.em.findOne(Restaurant, { id: }); - if (!restaurant) throw new NotFoundException('Restaurant not found'); - // Create new AdminRole - const newAdminRole = this.em.create(AdminRole, { - admin, - role, - restaurant, - }); - admin.roles.add(newAdminRole); - } + admin.role = role } await this.em.persistAndFlush(admin); return admin; } - async remove(adminId: string, : string): Promise { - const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, restaurant: { id: } }); - if (!adminRole) { + async remove(adminId: string): Promise { + const admin = await this.adminRepository.findOne({ id: adminId }); + if (!admin) { throw new NotFoundException('Admin role not found'); } - return this.em.removeAndFlush(adminRole); + // admin.deletedAt=new Date() + // TODO :L is this correct to soft delete + return this.em.removeAndFlush(admin); } } diff --git a/src/modules/admin/repositories/admin.repository.ts b/src/modules/admin/repositories/admin.repository.ts index a1a7c34..9e4cba6 100644 --- a/src/modules/admin/repositories/admin.repository.ts +++ b/src/modules/admin/repositories/admin.repository.ts @@ -1,57 +1,20 @@ import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; import { Admin } from '../entities/admin.entity'; -import { AdminRole } from '../entities/adminRole.entity'; -import { RestRepository } from '../../restaurants/repositories/rest.repository'; import { Injectable } from '@nestjs/common'; -import { normalizePhone } from '../../util/phone.util'; @Injectable() export class AdminRepository extends EntityRepository { constructor( readonly em: EntityManager, - private readonly restRepository: RestRepository, ) { super(em, Admin); } - async findByPhoneAndRestaurantSlug(phone: string, slug: string): Promise { - console.log('phone', phone); - const normalizedPhone = normalizePhone(phone); - // First, find the restaurant by slug using the same repository as auth service - const restaurant = await this.restRepository.findOne({ slug }); - if (!restaurant) { - return null; - } - - // Find AdminRole that matches the admin phone and restaurant - const adminRole = await this.em.findOne( - AdminRole, - { - admin: { phone: normalizedPhone }, - restaurant: { id: restaurant.id }, - }, - { populate: ['admin', 'admin.roles', 'role', 'role.permissions', 'restaurant'] }, - ); - - if (!adminRole || !adminRole.admin) { - return null; - } - - // Ensure all roles are populated - await adminRole.admin.roles.loadItems(); - for (const role of adminRole.admin.roles.getItems()) { - if (role.role && role.role.permissions && !role.role.permissions.isInitialized()) { - await role.role.permissions.loadItems(); - } - } - - return adminRole.admin; - } - async findAdminsWithPermission(: string, permission: string): Promise { + async findAdminsWithPermission(permission: string): Promise { const admins = await this.em.find( Admin, - { roles: { restaurant: { id: }, role: { permissions: { name: permission } } } }, - { populate: ['roles', 'roles.role', 'roles.role.permissions'] }, + { role: { permissions: { name: permission } } }, + { populate: ['role', 'roles.permissions'] }, ); return admins; } diff --git a/src/modules/roles/controllers/roles.controller.ts b/src/modules/roles/controllers/roles.controller.ts index e7d362f..cca045e 100644 --- a/src/modules/roles/controllers/roles.controller.ts +++ b/src/modules/roles/controllers/roles.controller.ts @@ -1,13 +1,11 @@ import { Controller, Get, Post, Body, Param, Patch, Delete, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBody, ApiBearerAuth, ApiHeader } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBody, ApiBearerAuth } from '@nestjs/swagger'; import { RolesService } from '../providers/roles.service'; import { PermissionsService } from '../providers/permissions.service'; import { CreateRoleDto } from '../dto/create-role.dto'; import { UpdateRoleDto } from '../dto/update-role.dto'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; -import { SuperAdminAuthGuard } from 'src/modules/auth/guards/superAdminAuth.guard'; import { Permissions } from 'src/common/decorators/permissions.decorator'; -import { } from 'src/common/decorators'; import { AdminId } from 'src/common/decorators/admin-id.decorator'; import { Permission } from 'src/common/enums/permission.enum'; @@ -21,13 +19,23 @@ export class RolesController { private readonly permissionService: PermissionsService, ) { } + @Post('admin/role') + @UseGuards(AdminAuthGuard) + @Permissions(Permission.MANAGE_ROLES) + @ApiBearerAuth() + @ApiOperation({ summary: 'Create a new role' }) + @ApiBody({ type: CreateRoleDto }) + create(@Body() dto: CreateRoleDto,) { + return this.roleService.create(dto); + } + @Get('admin/roles') @UseGuards(AdminAuthGuard) @Permissions(Permission.MANAGE_ROLES) @ApiBearerAuth() @ApiOperation({ summary: 'Get all through restaurant roles with pagination and filters' }) findAll() { - return this.roleService.findAllGeneralAndRestaurantRoles(); + return this.roleService.findAll(); } @Get('admin/roles/permissions') @@ -36,30 +44,20 @@ export class RolesController { @ApiBearerAuth() @ApiOperation({ summary: 'Get all permissions that the admin has' }) async findAllPermissions(@AdminId() adminId: string,) { - const adminPermissionNames = await this.permissionService.getAdminFullPermissions(adminId,); + const adminPermissionNames = await this.permissionService.getAdminPermissions(adminId); return adminPermissionNames; } - @Post('admin/roles') - @UseGuards(AdminAuthGuard) - @Permissions(Permission.MANAGE_ROLES) - @ApiBearerAuth() - @ApiOperation({ summary: 'Create a new role' }) - @ApiBody({ type: CreateRoleDto }) - create(@Body() dto: CreateRoleDto,) { - return this.roleService.createRestaurantRole(dto,); - } - @Get('admin/roles/:id') @UseGuards(AdminAuthGuard) @Permissions(Permission.MANAGE_ROLES) @ApiBearerAuth() @ApiOperation({ summary: 'Get a specific role by ID' }) findOne(@Param('id') id: string,) { - return this.roleService.findOne(, id); + return this.roleService.findOne( id); } @Patch('admin/roles/:id') @@ -69,7 +67,7 @@ export class RolesController { @ApiOperation({ summary: 'Update a role' }) @ApiBody({ type: UpdateRoleDto }) update(@Param('id') id: string, @Body() dto: UpdateRoleDto,) { - return this.roleService.update(, id, dto); + return this.roleService.update(id, dto); } @Delete('admin/roles/:id') @@ -78,16 +76,8 @@ export class RolesController { @ApiBearerAuth() @ApiOperation({ summary: 'Delete a role' }) remove(@Param('id') id: string,) { - return this.roleService.remove(, id); + return this.roleService.remove( id); } - /** Super Admin Endpoints */ - @UseGuards(SuperAdminAuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Get all system roles for super-admin' }) - @Get('super-admin/system-roles') - @ApiOperation({ summary: 'Get all system roles for super-admin' }) - findAllSystemRoles() { - return this.roleService.findAllSystemRoles(); - } + } diff --git a/src/modules/roles/dto/create-role.dto.ts b/src/modules/roles/dto/create-role.dto.ts index 8048ef3..bec5e12 100644 --- a/src/modules/roles/dto/create-role.dto.ts +++ b/src/modules/roles/dto/create-role.dto.ts @@ -5,7 +5,12 @@ export class CreateRoleDto { @ApiProperty({ description: 'Role name' }) @IsNotEmpty() @IsString() - name!: string; + name: string; + + @ApiProperty({ description: 'farsi Role title' }) + @IsNotEmpty() + @IsString() + title: string; @ApiProperty({ description: 'List of permission IDs', isArray: true, required: false }) @IsOptional() diff --git a/src/modules/roles/dto/update-role.dto.ts b/src/modules/roles/dto/update-role.dto.ts index ccf9939..ad4dbaf 100644 --- a/src/modules/roles/dto/update-role.dto.ts +++ b/src/modules/roles/dto/update-role.dto.ts @@ -1,14 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString, IsArray } from 'class-validator'; +import { PartialType } from '@nestjs/swagger'; +import { CreateRoleDto } from './create-role.dto'; -export class UpdateRoleDto { - @ApiProperty({ description: 'Role name', required: false }) - @IsOptional() - @IsString() - name?: string; +export class UpdateRoleDto extends PartialType(CreateRoleDto){ - @ApiProperty({ description: 'List of permission IDs', isArray: true, required: false }) - @IsOptional() - @IsArray() - permissionIds?: string[]; } diff --git a/src/modules/roles/entities/permission.entity.ts b/src/modules/roles/entities/permission.entity.ts index 2dab209..c10d895 100755 --- a/src/modules/roles/entities/permission.entity.ts +++ b/src/modules/roles/entities/permission.entity.ts @@ -1,17 +1,19 @@ -import { Entity, Property, Unique, ManyToMany, Collection } from '@mikro-orm/core'; +import { Entity, Property, Unique, ManyToMany, Collection, PrimaryKey } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Role } from './role.entity'; +import { ulid } from 'ulid'; @Entity({ tableName: 'permissions' }) export class Permission extends BaseEntity { - @Property() - @Unique() + @PrimaryKey({ type: 'string', columnType: 'char(26)' }) + id: string = ulid() + + @Property({ unique: true }) name!: string; @Property() title!: string; - @ManyToMany({ entity: () => Role, mappedBy: 'permissions' }) roles = new Collection(this); } diff --git a/src/modules/roles/entities/role.entity.ts b/src/modules/roles/entities/role.entity.ts index 620d681..39fa828 100755 --- a/src/modules/roles/entities/role.entity.ts +++ b/src/modules/roles/entities/role.entity.ts @@ -1,15 +1,20 @@ -import { Collection, Entity, Index, ManyToMany, OneToMany, ManyToOne, Property } from '@mikro-orm/core'; +import { Collection, Entity, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Permission } from './permission.entity'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { RolePermission } from './rolePermission.entity'; -import { AdminRole } from 'src/modules/admin/entities/adminRole.entity'; +import { Admin } from 'src/modules/admin/entities/admin.entity'; +import { ulid } from 'ulid'; @Entity({ tableName: 'roles' }) -@Index({ properties: ['restaurant'] }) export class Role extends BaseEntity { + @PrimaryKey({ type: 'string', columnType: 'char(26)' }) + id: string = ulid() + + @Property({ unique: true }) + name: string; + @Property() - name!: string; + title: string; @Property({ default: false }) isSystem: boolean = false; @@ -17,9 +22,6 @@ export class Role extends BaseEntity { @ManyToMany({ entity: () => Permission, pivotEntity: () => RolePermission, inversedBy: p => p.roles }) permissions = new Collection(this); - @ManyToOne(() => Restaurant, { nullable: true }) - restaurant?: Restaurant; - - @OneToMany(() => AdminRole, adminRole => adminRole.role) - admins = new Collection(this); + @OneToMany(() => Admin, admin => admin.role) + admins = new Collection(this); } diff --git a/src/modules/roles/providers/permissions.service.ts b/src/modules/roles/providers/permissions.service.ts index 9d0bf5c..5be75cb 100644 --- a/src/modules/roles/providers/permissions.service.ts +++ b/src/modules/roles/providers/permissions.service.ts @@ -1,21 +1,17 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@mikro-orm/nestjs'; -import { EntityManager, EntityRepository } from '@mikro-orm/core'; +import { EntityRepository } from '@mikro-orm/core'; import { Permission } from '../entities/permission.entity'; -import { CacheService } from 'src/modules/util/cache.service'; import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; -import { AdminRole } from 'src/modules/admin/entities/adminRole.entity'; + @Injectable() export class PermissionsService { - private readonly ADMIN_PERMISSIONS_KEY = 'admin-perms'; constructor( @InjectRepository(Permission) private readonly permissionRepository: EntityRepository, - private readonly cacheService: CacheService, private readonly adminRepository: AdminRepository, - private readonly em: EntityManager, ) { } async findAll() { @@ -23,83 +19,21 @@ export class PermissionsService { return permissions; } - /** - * Get admin permissions from cache or database - * @param adminId - The admin ID - * @param - The restaurant ID - * @returns Array of permission names (string[]) - */ - async getAdminPermissions(adminId: string, : string): Promise { - const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${}`; - // 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 - } - } + async getAdminPermissions(adminId: string) { - // If not in cache, fetch from database const admin = await this.adminRepository.findOne( - { id: adminId, roles: { restaurant: { id: } } }, - { populate: ['roles', 'roles.role', 'roles.role.permissions'] }, + { id: adminId }, + { populate: ['role', 'role.permissions'] }, ); - if (!admin || !admin.roles) { + if (!admin || !admin.role) { return []; } - // Ensure roles collection is loaded - await admin.roles.loadItems(); + const permissions = admin.role.permissions.map(p => p) - // 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 - .map(async r => { - // Ensure permissions collection is initialized - if (!r.role.permissions.isInitialized()) { - await r.role.permissions.loadItems(); - } - return r.role.permissions.getItems(); - }), - ); - - return permissions.flat().map(p => p.name); + return permissions } - async getAdminFullPermissions(adminId: string, : string): Promise { - const listOfPermissions = [] - const adminRoles = await this.em.findOne(AdminRole, { - admin: { - id: adminId, - }, - restaurant: { - id: , - }, - }, { populate: ['role', 'role.permissions'] }); - if (adminRoles) { - listOfPermissions.push(...adminRoles.role.permissions.getItems()); - } - return listOfPermissions.map(permission => permission); - } - - /** - * Invalidate admin permissions cache - * @param adminId - The admin ID - * @param - The restaurant ID - */ - async invalidateAdminPermissionsCache(adminId: string, : string): Promise { - const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${}`; - await this.cacheService.del(cacheKey); - } } diff --git a/src/modules/roles/providers/roles.service.ts b/src/modules/roles/providers/roles.service.ts index 35d43a6..3c16429 100644 --- a/src/modules/roles/providers/roles.service.ts +++ b/src/modules/roles/providers/roles.service.ts @@ -3,42 +3,33 @@ import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository, FilterQuery } from '@mikro-orm/core'; import { Role } from '../entities/role.entity'; import { Permission } from '../entities/permission.entity'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { CreateRoleDto } from '../dto/create-role.dto'; import { UpdateRoleDto } from '../dto/update-role.dto'; import { RolePermission } from '../entities/rolePermission.entity'; +import { RoleRepository } from '../respository/role.repository'; @Injectable() export class RolesService { constructor( - @InjectRepository(Role) - private readonly roleRepository: EntityRepository, + private readonly roleRepository: RoleRepository, @InjectRepository(Permission) private readonly permissionRepository: EntityRepository, private readonly em: EntityManager, ) { } - async createRestaurantRole(dto: CreateRoleDto, : string) { - const { name, permissionIds } = dto; + async create(dto: CreateRoleDto) { + const { name, title, permissionIds } = dto; // Check if role already exists - const existing = await this.roleRepository.findOne({ name, restaurant: ? { id: } : null }); + const existing = await this.roleRepository.findOne({ name }); if (existing) { - throw new BadRequestException('Role with this name already exists for the restaurant'); - } - - let restaurant: Restaurant | null = null; - if () { - restaurant = await this.em.findOne(Restaurant, { id: }); - if (!restaurant) { - throw new NotFoundException('Restaurant not found'); - } + throw new BadRequestException('Role with this key already exists'); } const role = this.roleRepository.create({ name, - restaurant, + title, isSystem: false, }); @@ -55,21 +46,21 @@ export class RolesService { return role; } - async findAllGeneralAndRestaurantRoles(: string) { - const where: FilterQuery = { $or: [{ restaurant: }, { restaurant: null }], isSystem: false }; + async findAll() { + const where: FilterQuery = { isSystem: false }; const roles = await this.roleRepository.find(where, { orderBy: { createdAt: 'desc' }, - populate: ['permissions', 'restaurant'], + populate: ['permissions'], }); return roles; } - async findOne(: string, id: string) { + async findOne(id: string) { const role = await this.roleRepository.findOne( - { id, restaurant: { id: } }, - { populate: ['permissions', 'restaurant'] }, + { id }, + { populate: ['permissions'] }, ); if (!role) { throw new NotFoundException('Role not found'); @@ -77,10 +68,10 @@ export class RolesService { return role; } - async update(: string, id: string, dto: UpdateRoleDto) { + async update(id: string, dto: UpdateRoleDto) { const role = await this.roleRepository.findOne( - { id, restaurant: { id: } }, - { populate: ['permissions', 'restaurant'] }, + { id }, + { populate: ['permissions'] }, ); if (!role) { throw new NotFoundException('Role not found'); @@ -90,6 +81,10 @@ export class RolesService { role.name = dto.name; } + if (dto.title) { + role.title = dto.title; + } + if (dto.permissionIds && dto.permissionIds.length >= 0) { // Clear existing permissions and add new ones role.permissions.removeAll(); @@ -104,22 +99,10 @@ export class RolesService { return role; } - async findAllSystemRoles() { - const roles = await this.roleRepository.find( - { isSystem: true }, - { - orderBy: { createdAt: 'desc' }, - populate: ['permissions', 'restaurant'], - }, - ); - - return roles; - } - - async remove(: string, id: string) { + async remove(id: string) { const role = await this.roleRepository.findOne( - { id, restaurant: { id: } }, - { populate: ['permissions', 'restaurant'] }, + { id }, + { populate: ['permissions'] }, ); if (!role) { throw new NotFoundException('Role not found'); diff --git a/src/modules/roles/respository/role.repository.ts b/src/modules/roles/respository/role.repository.ts new file mode 100644 index 0000000..9233249 --- /dev/null +++ b/src/modules/roles/respository/role.repository.ts @@ -0,0 +1,13 @@ +import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; + import { Injectable } from '@nestjs/common'; +import { Role } from '../entities/role.entity'; + +@Injectable() +export class RoleRepository extends EntityRepository { + constructor( + readonly em: EntityManager, + ) { + super(em, Role); + } + +} diff --git a/src/modules/roles/roles.module.ts b/src/modules/roles/roles.module.ts index 41d0c5e..1a78bd2 100644 --- a/src/modules/roles/roles.module.ts +++ b/src/modules/roles/roles.module.ts @@ -9,12 +9,13 @@ import { RolePermission } from './entities/rolePermission.entity'; import { JwtModule } from '@nestjs/jwt'; import { UtilsModule } from '../util/utils.module'; import { AdminModule } from '../admin/admin.module'; +import { RoleRepository } from './respository/role.repository'; @Global() @Module({ imports: [MikroOrmModule.forFeature([Role, Permission, RolePermission]), JwtModule, UtilsModule, AdminModule], controllers: [RolesController], - providers: [RolesService, PermissionsService], - exports: [RolesService, PermissionsService], + providers: [RolesService, PermissionsService, RoleRepository], + exports: [RolesService, PermissionsService, RoleRepository], }) export class RolesModule { } diff --git a/src/seeders/data/foods.data.ts b/src/seeders/data/foods.data.ts index 9ee5f12..e8b13bf 100644 --- a/src/seeders/data/foods.data.ts +++ b/src/seeders/data/foods.data.ts @@ -12,6 +12,7 @@ export interface productData { images: string[]; content?: string[]; weekDays?: number[]; + mealTypes?: MealType[]; discount?: number; score?: number;