users group
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20260701160000_addUserGroupUsersTable extends Migration {
|
||||||
|
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
this.addSql(`
|
||||||
|
create table "user_group_users" (
|
||||||
|
"id" char(26) not null,
|
||||||
|
"created_at" timestamptz not null default now(),
|
||||||
|
"updated_at" timestamptz not null default now(),
|
||||||
|
"deleted_at" timestamptz null,
|
||||||
|
"user_id" char(26) not null,
|
||||||
|
"user_group_id" char(26) not null,
|
||||||
|
constraint "user_group_users_pkey" primary key ("id")
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
this.addSql(`create index "user_group_users_created_at_index" on "user_group_users" ("created_at");`);
|
||||||
|
this.addSql(`create index "user_group_users_deleted_at_index" on "user_group_users" ("deleted_at");`);
|
||||||
|
this.addSql(`create index "user_group_users_user_group_id_index" on "user_group_users" ("user_group_id");`);
|
||||||
|
this.addSql(`create index "user_group_users_user_id_index" on "user_group_users" ("user_id");`);
|
||||||
|
this.addSql(`alter table "user_group_users" add constraint "user_group_users_user_id_user_group_id_unique" unique ("user_id", "user_group_id");`);
|
||||||
|
this.addSql(`
|
||||||
|
alter table "user_group_users"
|
||||||
|
add constraint "user_group_users_user_id_foreign"
|
||||||
|
foreign key ("user_id") references "users" ("id") on update cascade;
|
||||||
|
`);
|
||||||
|
this.addSql(`
|
||||||
|
alter table "user_group_users"
|
||||||
|
add constraint "user_group_users_user_group_id_foreign"
|
||||||
|
foreign key ("user_group_id") references "user_groups" ("id") on update cascade;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`alter table "user_group_users" drop constraint if exists "user_group_users_user_group_id_foreign";`);
|
||||||
|
this.addSql(`alter table "user_group_users" drop constraint if exists "user_group_users_user_id_foreign";`);
|
||||||
|
this.addSql(`drop table if exists "user_group_users" cascade;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -769,6 +769,10 @@ export const enum SliderMessage {
|
|||||||
|
|
||||||
export const enum UserGroupMessage {
|
export const enum UserGroupMessage {
|
||||||
NOT_FOUND = 'گروه کاربری یافت نشد',
|
NOT_FOUND = 'گروه کاربری یافت نشد',
|
||||||
|
USER_NOT_IN_RESTAURANT = 'کاربر به این رستوران تعلق ندارد',
|
||||||
|
USER_NOT_IN_GROUP = 'کاربر در این گروه یافت نشد',
|
||||||
|
USERS_ADDED = 'کاربران با موفقیت به گروه اضافه شدند',
|
||||||
|
USER_REMOVED = 'کاربر با موفقیت از گروه حذف شد',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum InventoryMessage {
|
export const enum InventoryMessage {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { UserGroupService } from '../providers/user-group.service';
|
import { UserGroupService } from '../providers/user-group.service';
|
||||||
import { CreateUserGroupDto } from '../dto/create-user-group.dto';
|
import { CreateUserGroupDto } from '../dto/create-user-group.dto';
|
||||||
import { UpdateUserGroupDto } from '../dto/update-user-group.dto';
|
import { UpdateUserGroupDto } from '../dto/update-user-group.dto';
|
||||||
|
import { AddUsersToGroupDto } from '../dto/add-users-to-group.dto';
|
||||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||||
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
||||||
import { Permission } from 'src/common/enums/permission.enum';
|
import { Permission } from 'src/common/enums/permission.enum';
|
||||||
@@ -72,4 +73,44 @@ export class UserGroupsController {
|
|||||||
remove(@Param('groupId') groupId: string, @RestId() restId: string) {
|
remove(@Param('groupId') groupId: string, @RestId() restId: string) {
|
||||||
return this.userGroupService.remove(restId, groupId);
|
return this.userGroupService.remove(restId, groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_USERS)
|
||||||
|
@Get('admin/user-groups/:groupId/users')
|
||||||
|
@ApiOperation({ summary: 'Get all users in a user group' })
|
||||||
|
@ApiParam({ name: 'groupId', description: 'User group ID' })
|
||||||
|
getUsers(@Param('groupId') groupId: string, @RestId() restId: string) {
|
||||||
|
return this.userGroupService.getUsers(restId, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_USERS)
|
||||||
|
@Post('admin/user-groups/:groupId/users')
|
||||||
|
@ApiOperation({ summary: 'Add users to a user group' })
|
||||||
|
@ApiParam({ name: 'groupId', description: 'User group ID' })
|
||||||
|
@ApiBody({ type: AddUsersToGroupDto })
|
||||||
|
addUsers(
|
||||||
|
@Param('groupId') groupId: string,
|
||||||
|
@Body() dto: AddUsersToGroupDto,
|
||||||
|
@RestId() restId: string,
|
||||||
|
) {
|
||||||
|
return this.userGroupService.addUsers(restId, groupId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_USERS)
|
||||||
|
@Delete('admin/user-groups/:groupId/users/:userId')
|
||||||
|
@ApiOperation({ summary: 'Remove a user from a user group' })
|
||||||
|
@ApiParam({ name: 'groupId', description: 'User group ID' })
|
||||||
|
@ApiParam({ name: 'userId', description: 'User ID' })
|
||||||
|
removeUser(
|
||||||
|
@Param('groupId') groupId: string,
|
||||||
|
@Param('userId') userId: string,
|
||||||
|
@RestId() restId: string,
|
||||||
|
) {
|
||||||
|
return this.userGroupService.removeUser(restId, groupId, userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { ArrayMinSize, IsArray, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class AddUsersToGroupDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: ['01HXABCDEFGHIJKLMNOPQRSTUV', '01HXABCDEFGHIJKLMNOPQRSTUW'],
|
||||||
|
description: 'User IDs to add to the group',
|
||||||
|
type: [String],
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsNotEmpty({ each: true })
|
||||||
|
userIds!: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Entity, Index, ManyToOne, Unique } from '@mikro-orm/core';
|
||||||
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
import { UserGroup } from './user-group.entity';
|
||||||
|
|
||||||
|
@Entity({ tableName: 'user_group_users' })
|
||||||
|
@Index({ properties: ['userGroup'] })
|
||||||
|
@Index({ properties: ['user'] })
|
||||||
|
@Unique({ properties: ['user', 'userGroup'] })
|
||||||
|
export class UserGroupUser extends BaseEntity {
|
||||||
|
@ManyToOne(() => User)
|
||||||
|
user!: User;
|
||||||
|
|
||||||
|
@ManyToOne(() => UserGroup)
|
||||||
|
userGroup!: UserGroup;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Entity, Index, ManyToOne, Property, Unique } from '@mikro-orm/core';
|
import { Collection, Entity, Index, ManyToOne, OneToMany, Property, Unique } from '@mikro-orm/core';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
|
import { UserGroupUser } from './user-group-user.entity';
|
||||||
|
|
||||||
@Entity({ tableName: 'user_groups' })
|
@Entity({ tableName: 'user_groups' })
|
||||||
@Index({ properties: ['restaurant'] })
|
@Index({ properties: ['restaurant'] })
|
||||||
@@ -14,4 +15,7 @@ export class UserGroup extends BaseEntity {
|
|||||||
|
|
||||||
@Property({ type: 'integer', default: 0 })
|
@Property({ type: 'integer', default: 0 })
|
||||||
count: number = 0;
|
count: number = 0;
|
||||||
|
|
||||||
|
@OneToMany(() => UserGroupUser, member => member.userGroup)
|
||||||
|
members = new Collection<UserGroupUser>(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql';
|
import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql';
|
||||||
import { UserGroup } from '../entities/user-group.entity';
|
import { UserGroup } from '../entities/user-group.entity';
|
||||||
import { UserGroupRepository } from '../repositories/user-group.repository';
|
import { UserGroupRepository } from '../repositories/user-group.repository';
|
||||||
|
import { UserGroupUserRepository } from '../repositories/user-group-user.repository';
|
||||||
|
import { UserRestaurantRepository } from '../repositories/user-restuarant.repository';
|
||||||
import { CreateUserGroupDto } from '../dto/create-user-group.dto';
|
import { CreateUserGroupDto } from '../dto/create-user-group.dto';
|
||||||
import { UpdateUserGroupDto } from '../dto/update-user-group.dto';
|
import { UpdateUserGroupDto } from '../dto/update-user-group.dto';
|
||||||
|
import { AddUsersToGroupDto } from '../dto/add-users-to-group.dto';
|
||||||
import { UserGroupMessage } from 'src/common/enums/message.enum';
|
import { UserGroupMessage } from 'src/common/enums/message.enum';
|
||||||
import { RestaurantsService } from 'src/modules/restaurants/providers/restaurants.service';
|
import { RestaurantsService } from 'src/modules/restaurants/providers/restaurants.service';
|
||||||
|
import { User } from '../entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserGroupService {
|
export class UserGroupService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly userGroupRepository: UserGroupRepository,
|
private readonly userGroupRepository: UserGroupRepository,
|
||||||
|
private readonly userGroupUserRepository: UserGroupUserRepository,
|
||||||
|
private readonly userRestaurantRepository: UserRestaurantRepository,
|
||||||
private readonly restService: RestaurantsService,
|
private readonly restService: RestaurantsService,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) {}
|
) {}
|
||||||
@@ -61,4 +67,75 @@ export class UserGroupService {
|
|||||||
group.deletedAt = new Date();
|
group.deletedAt = new Date();
|
||||||
await this.em.persistAndFlush(group);
|
await this.em.persistAndFlush(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUsers(restId: string, groupId: string): Promise<User[]> {
|
||||||
|
const group = await this.findOneOrFail(restId, groupId);
|
||||||
|
const links = await this.userGroupUserRepository.find(
|
||||||
|
{ userGroup: group },
|
||||||
|
{ populate: ['user'], orderBy: { createdAt: 'asc' } },
|
||||||
|
);
|
||||||
|
return links.map(link => link.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addUsers(restId: string, groupId: string, dto: AddUsersToGroupDto): Promise<UserGroup> {
|
||||||
|
const group = await this.findOneOrFail(restId, groupId);
|
||||||
|
const uniqueUserIds = [...new Set(dto.userIds)];
|
||||||
|
|
||||||
|
const userRestaurants = await this.userRestaurantRepository.find(
|
||||||
|
{
|
||||||
|
restaurant: { id: restId },
|
||||||
|
user: { id: { $in: uniqueUserIds } },
|
||||||
|
},
|
||||||
|
{ populate: ['user'] },
|
||||||
|
);
|
||||||
|
|
||||||
|
const validUserIds = new Set(userRestaurants.map(link => link.user.id));
|
||||||
|
const invalidUserIds = uniqueUserIds.filter(id => !validUserIds.has(id));
|
||||||
|
|
||||||
|
if (invalidUserIds.length > 0) {
|
||||||
|
throw new BadRequestException(UserGroupMessage.USER_NOT_IN_RESTAURANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
let addedCount = 0;
|
||||||
|
|
||||||
|
for (const userId of uniqueUserIds) {
|
||||||
|
const existing = await this.userGroupUserRepository.findOne({
|
||||||
|
userGroup: group,
|
||||||
|
user: { id: userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userGroupUserRepository.create({
|
||||||
|
user: this.em.getReference(User, userId),
|
||||||
|
userGroup: group,
|
||||||
|
});
|
||||||
|
addedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedCount > 0) {
|
||||||
|
group.count += addedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.em.flush();
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeUser(restId: string, groupId: string, userId: string): Promise<UserGroup> {
|
||||||
|
const group = await this.findOneOrFail(restId, groupId);
|
||||||
|
const link = await this.userGroupUserRepository.findOne({
|
||||||
|
userGroup: group,
|
||||||
|
user: { id: userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!link) {
|
||||||
|
throw new NotFoundException(UserGroupMessage.USER_NOT_IN_GROUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.em.removeAndFlush(link);
|
||||||
|
group.count = Math.max(0, group.count - 1);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||||
|
import { UserGroupUser } from '../entities/user-group-user.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserGroupUserRepository extends EntityRepository<UserGroupUser> {
|
||||||
|
constructor(readonly em: EntityManager) {
|
||||||
|
super(em, UserGroupUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,17 +16,19 @@ import { UserRestaurant } from './entities/user-restuarant.entity';
|
|||||||
import { UserRestaurantRepository } from './repositories/user-restuarant.repository';
|
import { UserRestaurantRepository } from './repositories/user-restuarant.repository';
|
||||||
import { UserRestaurantCrone } from './crone/user-restuarant.crone';
|
import { UserRestaurantCrone } from './crone/user-restuarant.crone';
|
||||||
import { UserGroup } from './entities/user-group.entity';
|
import { UserGroup } from './entities/user-group.entity';
|
||||||
|
import { UserGroupUser } from './entities/user-group-user.entity';
|
||||||
import { UserGroupRepository } from './repositories/user-group.repository';
|
import { UserGroupRepository } from './repositories/user-group.repository';
|
||||||
|
import { UserGroupUserRepository } from './repositories/user-group-user.repository';
|
||||||
import { UserGroupService } from './providers/user-group.service';
|
import { UserGroupService } from './providers/user-group.service';
|
||||||
import { UserGroupsController } from './controllers/user-groups.controller';
|
import { UserGroupsController } from './controllers/user-groups.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UserService, WalletService, UserGroupService,
|
providers: [UserService, WalletService, UserGroupService,
|
||||||
UserRepository, WalletTransactionRepository,
|
UserRepository, WalletTransactionRepository,
|
||||||
PointTransactionRepository, UserRestaurantRepository, UserGroupRepository, UserRestaurantCrone],
|
PointTransactionRepository, UserRestaurantRepository, UserGroupRepository, UserGroupUserRepository, UserRestaurantCrone],
|
||||||
controllers: [UsersController, UserGroupsController],
|
controllers: [UsersController, UserGroupsController],
|
||||||
imports: [
|
imports: [
|
||||||
MikroOrmModule.forFeature([User, UserAddress, WalletTransaction, PointTransaction, UserRestaurant, UserGroup]),
|
MikroOrmModule.forFeature([User, UserAddress, WalletTransaction, PointTransaction, UserRestaurant, UserGroup, UserGroupUser]),
|
||||||
JwtModule,
|
JwtModule,
|
||||||
RestaurantsModule,
|
RestaurantsModule,
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user