diff --git a/database/migrations/.snapshot-postgres.json b/database/migrations/.snapshot-postgres.json index 19242db..610798a 100644 --- a/database/migrations/.snapshot-postgres.json +++ b/database/migrations/.snapshot-postgres.json @@ -1113,6 +1113,16 @@ "length": 255, "mappedType": "string" }, + "card_owner": { + "name": "card_owner", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, "enabled": { "name": "enabled", "type": "boolean", @@ -5428,6 +5438,173 @@ }, "nativeEnums": {} }, + { + "columns": { + "id": { + "name": "id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "user_id": { + "name": "user_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "order_count": { + "name": "order_count", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + }, + "total_order_amount": { + "name": "total_order_amount", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" + } + }, + "name": "user_restuarant", + "schema": "public", + "indexes": [ + { + "keyName": "user_restuarant_created_at_index", + "columnNames": [ + "created_at" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "user_restuarant_deleted_at_index", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "user_restuarant_restaurant_id_index", + "columnNames": [ + "restaurant_id" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "user_restuarant_user_id_restaurant_id_unique", + "columnNames": [ + "user_id", + "restaurant_id" + ], + "composite": true, + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "user_restuarant_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "user_restuarant_user_id_foreign": { + "constraintName": "user_restuarant_user_id_foreign", + "columnNames": [ + "user_id" + ], + "localTableName": "public.user_restuarant", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.users", + "updateRule": "cascade" + }, + "user_restuarant_restaurant_id_foreign": { + "constraintName": "user_restuarant_restaurant_id_foreign", + "columnNames": [ + "restaurant_id" + ], + "localTableName": "public.user_restuarant", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.restaurants", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, { "columns": { "id": { diff --git a/database/migrations/Migration20260618162454_add_user_restuarant_table.ts b/database/migrations/Migration20260618162454_add_user_restuarant_table.ts new file mode 100644 index 0000000..76af803 --- /dev/null +++ b/database/migrations/Migration20260618162454_add_user_restuarant_table.ts @@ -0,0 +1,20 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20260618162454_add_user_restuarant_table extends Migration { + + override async up(): Promise { + this.addSql(`create table "user_restuarant" ("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, "restaurant_id" char(26) not null, "order_count" int not null, "total_order_amount" int not null, constraint "user_restuarant_pkey" primary key ("id"));`); + this.addSql(`create index "user_restuarant_created_at_index" on "user_restuarant" ("created_at");`); + this.addSql(`create index "user_restuarant_deleted_at_index" on "user_restuarant" ("deleted_at");`); + this.addSql(`create index "user_restuarant_restaurant_id_index" on "user_restuarant" ("restaurant_id");`); + this.addSql(`alter table "user_restuarant" add constraint "user_restuarant_user_id_restaurant_id_unique" unique ("user_id", "restaurant_id");`); + + this.addSql(`alter table "user_restuarant" add constraint "user_restuarant_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade;`); + this.addSql(`alter table "user_restuarant" add constraint "user_restuarant_restaurant_id_foreign" foreign key ("restaurant_id") references "restaurants" ("id") on update cascade;`); + } + + override async down(): Promise { + this.addSql(`drop table if exists "user_restuarant";`); + } + +} diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index fb18bfa..82a6917 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -72,6 +72,9 @@ export class AuthService { if (!rest) { throw new BadRequestException(RestMessage.NOT_FOUND); } + + // add user to restuarant + await this.userService.addUserToRestaurant(user, rest); const tokens = await this.tokensService.generateAccessAndRefreshToken(user.id, rest.id, false, slug); diff --git a/src/modules/users/controllers/users.controller.ts b/src/modules/users/controllers/users.controller.ts index 4b71ed4..13940cb 100644 --- a/src/modules/users/controllers/users.controller.ts +++ b/src/modules/users/controllers/users.controller.ts @@ -179,7 +179,7 @@ export class UsersController { query: FindUsersDto, @RestId() restId: string, ) { - return this.userService.findAll(restId, query); + return this.userService.findPaginated(restId, query); } @UseGuards(AdminAuthGuard) diff --git a/src/modules/users/entities/user-restuarant.entity.ts b/src/modules/users/entities/user-restuarant.entity.ts new file mode 100644 index 0000000..63dfd52 --- /dev/null +++ b/src/modules/users/entities/user-restuarant.entity.ts @@ -0,0 +1,22 @@ +import { Entity, ManyToOne, Index, Property, Unique } from '@mikro-orm/core'; +import { BaseEntity } from '../../../common/entities/base.entity'; +import { User } from './user.entity'; +import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; + +@Entity({ tableName: 'user_restuarant' }) +@Index({ properties: ['restaurant'] }) +@Unique({ properties: ['user', 'restaurant'] }) +export class UserRestaurant extends BaseEntity { + @ManyToOne(() => User) + user!: User; + + @ManyToOne(() => Restaurant) + restaurant!: Restaurant; + + @Property({ type: 'integer' }) + orderCount!: number; + + @Property({ type: 'integer' }) + totalOrderAmount!: number; + +} diff --git a/src/modules/users/providers/user.service.ts b/src/modules/users/providers/user.service.ts index e177bd9..8ccf78c 100644 --- a/src/modules/users/providers/user.service.ts +++ b/src/modules/users/providers/user.service.ts @@ -18,6 +18,8 @@ import { WalletTransactionRepository } from '../repositories/wallet-transaction. import { WalletService } from './wallet.service'; import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet'; import { WalletTransaction } from '../entities/wallet-transaction.entity'; +import { UserRestaurantRepository } from '../repositories/user-restuarant.repository'; +import { UserRestaurant } from '../entities/user-restuarant.entity'; @Injectable() export class UserService { @@ -27,6 +29,7 @@ export class UserService { private readonly walletTransactionRepository: WalletTransactionRepository, private readonly walletService: WalletService, private readonly em: EntityManager, + private readonly userRestaurantRepository: UserRestaurantRepository, ) { } async findOrCreateByPhone(phone: string): Promise { @@ -129,18 +132,16 @@ export class UserService { return user; } - async findAll(restId: string, dto: FindUsersDto): Promise> { + async findPaginated(restId: string, dto: FindUsersDto): Promise> { const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto; // 1. Calculate pagination const offset = (page - 1) * limit; // 2. Build the 'where' filter query - const where: FilterQuery = { - orders: { - restaurant: { - id: restId, - }, + const where: FilterQuery = { + restaurant: { + id: restId, }, }; @@ -151,19 +152,22 @@ export class UserService { const normalizedSearchPattern = `%${normalizedSearch}%`; // $ilike is case-insensitive (PostgreSQL). Use $like for MySQL (often CI by default) // Search with both original and normalized phone patterns to handle various input formats - where.$or = [ - { firstName: { $ilike: searchPattern } }, - { lastName: { $ilike: searchPattern } }, - { phone: { $ilike: searchPattern } }, - ...(normalizedSearch !== search ? [{ phone: { $ilike: normalizedSearchPattern } }] : []), - ]; + where.user = { + $or: [ + { firstName: { $ilike: searchPattern } }, + { lastName: { $ilike: searchPattern } }, + { phone: { $ilike: searchPattern } }, + ...(normalizedSearch !== search ? [{ phone: { $ilike: normalizedSearchPattern } }] : []), + ], + }; } // 5. Execute the query using findAndCount - const [users, total] = await this.userRepository.findAndCount(where, { + const [userRestaurants, total] = await this.userRestaurantRepository.findAndCount(where, { limit, offset, orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, + populate: ['user'], }); // 6. Calculate total pages @@ -171,7 +175,7 @@ export class UserService { // 7. Return the paginated result return { - data: users, + data: userRestaurants, meta: { total, page, @@ -225,6 +229,33 @@ export class UserService { return address; } + // add user to retuarant customers list + async addUserToRestaurant(user: User, restaurant: Restaurant) { + // const restaurant = await this.restaurantRepository.findOne({ id: restId }); + // if (!restaurant) { + // throw new NotFoundException(`Restaurant with ID ${restId} not found.`); + // } + // const user = await this.userRepository.findOne({ id: userId }); + // if (!user) { + // throw new NotFoundException(`User with ID ${userId} not found.`); + // } + + const existingUSer = await this.userRestaurantRepository.findOne({ user, restaurant }); + + if (existingUSer) { + return existingUSer; + } + + const userRestaurant = this.userRestaurantRepository.create({ + user, + restaurant, + orderCount: 0, + totalOrderAmount: 0, + }); + await this.em.persistAndFlush(userRestaurant); + return userRestaurant; + } + async getUserAddress(userId: string, addressId: string): Promise { const address = await this.em.findOne(UserAddress, { id: addressId, diff --git a/src/modules/users/repositories/user-restuarant.repository.ts b/src/modules/users/repositories/user-restuarant.repository.ts new file mode 100644 index 0000000..a55d144 --- /dev/null +++ b/src/modules/users/repositories/user-restuarant.repository.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; +import { UserRestaurant } from '../entities/user-restuarant.entity'; + +@Injectable() +export class UserRestaurantRepository extends EntityRepository { + constructor(readonly em: EntityManager) { + super(em, UserRestaurant); + } +} diff --git a/src/modules/users/user.module.ts b/src/modules/users/user.module.ts index 61a74d1..0ddb3b0 100644 --- a/src/modules/users/user.module.ts +++ b/src/modules/users/user.module.ts @@ -12,15 +12,19 @@ import { WalletService } from './providers/wallet.service'; import { WalletTransaction } from './entities/wallet-transaction.entity'; import { PointTransaction } from './entities/point-transaction.entity'; import { PointTransactionRepository } from './repositories/point-transaction.repository'; +import { UserRestaurant } from './entities/user-restuarant.entity'; +import { UserRestaurantRepository } from './repositories/user-restuarant.repository'; @Module({ - providers: [UserService, WalletService, UserRepository, WalletTransactionRepository, PointTransactionRepository], + providers: [UserService, WalletService, + UserRepository, WalletTransactionRepository, + PointTransactionRepository, UserRestaurantRepository], controllers: [UsersController], imports: [ - MikroOrmModule.forFeature([User, UserAddress, WalletTransaction, PointTransaction]), + MikroOrmModule.forFeature([User, UserAddress, WalletTransaction, PointTransaction, UserRestaurant]), JwtModule, RestaurantsModule, ], exports: [UserService, WalletService, UserRepository, WalletTransactionRepository, PointTransactionRepository], }) -export class UserModule {} +export class UserModule { }