fix import errors

This commit is contained in:
2026-02-08 16:51:35 +03:30
parent c305c8d06f
commit 2394cb5c23
132 changed files with 338 additions and 36761 deletions
-2
View File
@@ -20,7 +20,6 @@ import { CouponModule } from './modules/coupons/coupon.module';
import { ReviewModule } from './modules/review/review.module';
import { NotificationsModule } from './modules/notifications/notifications.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { PagerModule } from './modules/pager/pager.module';
import { ContactModule } from './modules/contact/contact.module';
import { IconsModule } from './modules/icons/icons.module';
import { CacheModule } from '@nestjs/cache-manager';
@@ -54,7 +53,6 @@ import { cacheConfig } from './config/cache.config';
ReviewModule,
NotificationsModule,
EventEmitterModule.forRoot(),
PagerModule,
ContactModule,
IconsModule,
],
+3 -1
View File
@@ -1,3 +1,5 @@
export { UserId } from './user-id.decorator';
export { ShopId } from './rest-id.decorator';
export { AdminId } from './admin-id.decorator';
export { ShopId, RestId } from './rest-id.decorator';
export { Permissions, PERMISSIONS_KEY } from './permissions.decorator';
export { RateLimit } from './rate-limit.decorator';
@@ -16,3 +16,6 @@ export const ShopId = createParamDecorator((data: unknown, ctx: ExecutionContext
const request = ctx.switchToHttp().getRequest<Request & { shopId?: string }>();
return request.shopId || '';
});
// Alias for backward compatibility
export const RestId = ShopId;
+2 -2
View File
@@ -9,7 +9,7 @@ import { Permission } from '../roles/entities/permission.entity';
import { RolePermission } from '../roles/entities/rolePermission.entity';
import { AdminController } from './controllers/admin.controller';
import { UtilsModule } from '../utils/utils.module';
import { ShopsModule } from '../../../shops/shops.module';
import { ShopsModule } from '../shops/shops.module';
import { AdminRole } from './entities/adminRole.entity';
@Module({
@@ -19,7 +19,7 @@ import { AdminRole } from './entities/adminRole.entity';
MikroOrmModule.forFeature([Admin, Role, Permission, RolePermission, AdminRole]),
JwtModule,
UtilsModule,
forwardRef(() => RestaurantsModule),
forwardRef(() => ShopsModule),
],
exports: [AdminService, AdminRepository],
})
@@ -5,12 +5,12 @@ 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 { RestId } from 'src/common/decorators';
import { ShopId } 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-shop-admin.dto';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto';
@ApiBearerAuth()
@ApiTags('admin')
+2 -2
View File
@@ -1,7 +1,7 @@
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 { AdminRole } from './adminRole.entity';
import { normalizePhone } from '../../../utils/phone.util';
import { normalizePhone } from '../../utils/phone.util';
@Entity({ tableName: 'admins' })
export class Admin extends BaseEntity {
@@ -1,7 +1,7 @@
import { Entity, Index, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Role } from '../../../roles/entities/role.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Role } from '../../roles/entities/role.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { Admin } from './admin.entity';
@Entity({ tableName: 'admin_roles' })
+5 -5
View File
@@ -2,14 +2,14 @@ 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 { Shop } from ../../../shops/entities/shop.entity';
import { Role } from '../../roles/entities/role.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { CacheService } from '../../../utils/cache.service';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-shop-admin.dto';
import { CacheService } from '../../utils/cache.service';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto';
import { UpdateAdminDto } from '../dto/update-admin.dto';
import { AdminRole } from '../entities/adminRole.entity';
import { normalizePhone } from '../../../utils/phone.util';
import { normalizePhone } from '../../utils/phone.util';
@Injectable()
export class AdminService {
@@ -1,15 +1,15 @@
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { Admin } from '../entities/admin.entity';
import { AdminRole } from '../entities/adminRole.entity';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { ShopRepository } from '../../shops/repositories/rest.repository';
import { Injectable } from '@nestjs/common';
import { normalizePhone } from '../../../utils/phone.util';
import { normalizePhone } from '../../utils/phone.util';
@Injectable()
export class AdminRepository extends EntityRepository<Admin> {
constructor(
readonly em: EntityManager,
private readonly restRepository: RestRepository,
private readonly shopRepository: ShopRepository,
) {
super(em, Admin);
}
@@ -18,7 +18,7 @@ export class AdminRepository extends EntityRepository<Admin> {
console.log('phone', phone);
const normalizedPhone = normalizePhone(phone);
// First, find the shop by slug using the same repository as auth service
const shop = await this.restRepository.findOne({ slug });
const shop = await this.shopRepository.findOne({ slug });
if (!shop) {
return null;
}
+2 -2
View File
@@ -7,7 +7,7 @@ import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { AdminModule } from '../admin/admin.module';
import { TokensService } from './services/tokens.service';
import { RestaurantsModule } from ../../../shops.module';
import { ShopsModule } from '../shops/shops.module';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from '../users/entities/user.entity';
import { AdminAuthGuard } from './guards/adminAuth.guard';
@@ -33,7 +33,7 @@ import { NotificationsModule } from '../notifications/notifications.module';
inject: [ConfigService],
}),
forwardRef(() => AdminModule),
forwardRef(() => RestaurantsModule),
forwardRef(() => ShopsModule),
MikroOrmModule.forFeature([User, RefreshToken]),
forwardRef(() => NotificationsModule),
@@ -1,6 +1,6 @@
import { Entity, Enum, Index, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
export enum RefreshTokenType {
USER = 'user',
+1 -1
View File
@@ -12,7 +12,7 @@ import { Request } from 'express';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../interfaces/IToken-payload';
import { PERMISSIONS_KEY } from '../../../../common/decorators/permissions.decorator';
import { PERMISSIONS_KEY } from '../../../common/decorators/permissions.decorator';
import { PermissionsService } from 'src/modules/roles/providers/permissions.service';
export interface AdminAuthRequest extends Request {
+9 -9
View File
@@ -1,17 +1,17 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { RequestOtpDto } from '../dto/request-otp.dto';
import { CacheService } from '../../../utils/cache.service';
import { SmsService } from '../../../notifications/services/sms.service';
import { UserService } from '../../../users/providers/user.service';
import { CacheService } from 'src/modules/utils/cache.service';
import { SmsService } from 'src/modules/notifications/services/sms.service';
import { UserService } from 'src/modules/users/providers/user.service';
import { randomInt } from 'crypto';
import { TokensService } from './tokens.service';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { ShopRepository } from 'src/modules/shops/repositories/rest.repository';
import { AuthMessage, RestMessage } from 'src/common/enums/message.enum';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { AdminLoginTransformer } from '../transformers/admin-login.transformer';
import { UserLoginTransformer } from '../transformers/user-login.transformer';
import { ConfigService } from '@nestjs/config';
import { normalizePhone } from '../../../utils/phone.util';
import { normalizePhone } from 'src/modules/utils/phone.util';
@Injectable()
export class AuthService {
@@ -22,7 +22,7 @@ export class AuthService {
private readonly smsService: SmsService,
private readonly userService: UserService,
private readonly tokensService: TokensService,
private readonly restRepository: RestRepository,
private readonly shopRepository: ShopRepository,
private readonly adminRepository: AdminRepository,
private readonly configService: ConfigService,
) {
@@ -60,7 +60,7 @@ export class AuthService {
const user = await this.userService.findOrCreateByPhone(normalizedPhone);
const rest = await this.restRepository.findOne({ slug });
const rest = await this.shopRepository.findOne({ slug });
if (!rest) {
throw new BadRequestException(RestMessage.NOT_FOUND);
@@ -89,7 +89,7 @@ export class AuthService {
throw new BadRequestException(AuthMessage.ADMIN_NOT_FOUND);
}
const rest = await this.restRepository.findOne({ slug });
const rest = await this.shopRepository.findOne({ slug });
if (!rest) {
throw new BadRequestException(RestMessage.NOT_FOUND);
@@ -113,7 +113,7 @@ export class AuthService {
throw new BadRequestException(AuthMessage.ADMIN_NOT_FOUND);
}
const rest = await this.restRepository.findOne({ slug });
const rest = await this.shopRepository.findOne({ slug });
if (!rest) {
throw new BadRequestException(RestMessage.NOT_FOUND);
+2 -2
View File
@@ -6,10 +6,10 @@ import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import dayjs from 'dayjs';
import { AuthMessage } from '../../../../common/enums/message.enum';
import { AuthMessage } from '../../../common/enums/message.enum';
import { RefreshToken, RefreshTokenType } from '../entities/refresh-token.entity';
import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from 'src/modules/shops/entities/shop.entity';
@Injectable()
export class TokensService {
@@ -1,5 +1,5 @@
import type { Admin } from '../../../admin/entities/admin.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import type { Admin } from 'src/modules/admin/entities/admin.entity';
import type { Shop } from 'src/modules/shops/entities/shop.entity';
export interface AdminLoginResponse {
id: string;
@@ -1,5 +1,5 @@
import type { User } from '../../../users/entities/user.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import type { User } from 'src/modules/users/entities/user.entity';
import type { Shop } from 'src/modules/shops/entities/shop.entity';
export interface UserLoginResponse {
id: string;
@@ -3,7 +3,7 @@ import { CartService } from '../providers/cart.service';
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
import { ApplyCouponDto } from '../dto/apply-coupon.dto';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } from '@nestjs/swagger';
import { AuthGuard } from '../../../auth/guards/auth.guard';
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { UserId } from 'src/common/decorators/user-id.decorator';
import { ShopId } from 'src/common/decorators/rest-id.decorator';
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
@@ -6,7 +6,7 @@ import { DeliveryFeeTypeEnum } from '../../delivery/interface/delivery';
import { CouponType } from '../../coupons/interface/coupon';
import { Product } from '../../products/entities/product.entity';
import { Cart } from '../interfaces/cart.interface';
import { CouponService } from '../../../coupons/providers/coupon.service';
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
import { GeographicUtils } from '../utils/geographic.utils';
@Injectable()
@@ -1,5 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { Product } from ../../../products/entities/product.entity';
import { Product } from '../../products/entities/product.entity';
import { Cart, CartItem } from '../interfaces/cart.interface';
import { CartValidationService } from './cart-validation.service';
import { CartCalculationService } from './cart-calculation.service';
@@ -1,15 +1,15 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { Product } from '../../../products/entities/product.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { UserAddress } from '../../../users/entities/user-address.entity';
import { User } from '../../../users/entities/user.entity';
import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { Delivery } from '../../../delivery/entities/delivery.entity';
import { DeliveryMethodEnum } from '../../../delivery/interface/delivery';
import { PointTransaction } from '../../../users/entities/point-transaction.entity';
import { Order } from '../../../orders/entities/order.entity';
import { OrderStatus } from '../../../orders/interface/order.interface';
import { Product } from 'src/modules/products/entities/product.entity';
import { Shop } from 'src/modules/shops/entities/shop.entity';
import { UserAddress } from 'src/modules/users/entities/user-address.entity';
import { User } from 'src/modules/users/entities/user.entity';
import { PaymentMethod } from 'src/modules/payments/entities/payment-method.entity';
import { Delivery } from 'src/modules/delivery/entities/delivery.entity';
import { DeliveryMethodEnum } from 'src/modules/delivery/interface/delivery';
import { PointTransaction } from 'src/modules/users/entities/point-transaction.entity';
import { Order } from 'src/modules/orders/entities/order.entity';
import { OrderStatus } from 'src/modules/orders/interface/order.interface';
import { Cart } from '../interfaces/cart.interface';
import { GeographicUtils } from '../utils/geographic.utils';
import { CartMessage } from 'src/common/enums/message.enum';
@@ -28,11 +28,11 @@ export class CartValidationService {
async validateAndGetFood(foodId: string, restaurantId: string, quantity: number): Promise<Product> {
const product = await this.em.findOne(Product, { id: foodId }, { populate: ['shop'] });
if (!product) {
throw new NotFoundException(CartMessage.FOOD_NOT_FOUND);
throw new NotFoundException(CartMessage.PRODUCT_NOT_FOUND);
}
if (product.shop.id !== restaurantId) {
throw new BadRequestException(CartMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
throw new BadRequestException(CartMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
}
return product;
@@ -67,7 +67,7 @@ export class CartValidationService {
async getFoodOrFail(foodId: string): Promise<Product> {
const product = await this.em.findOne(Product, { id: foodId });
if (!product) {
throw new NotFoundException(CartMessage.FOOD_NOT_FOUND);
throw new NotFoundException(CartMessage.PRODUCT_NOT_FOUND);
}
return product;
}
@@ -78,7 +78,7 @@ export class CartValidationService {
async getRestaurantOrFail(restaurantId: string): Promise<Shop> {
const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!shop) {
throw new NotFoundException(CartMessage.RESTAURANT_NOT_FOUND);
throw new NotFoundException(CartMessage.SHOP_NOT_FOUND);
}
return shop;
}
@@ -140,7 +140,7 @@ export class CartValidationService {
});
if (!deliveryMethod) {
throw new NotFoundException(CartMessage.DELIVERY_METHOD_NOT_FOUND_FOR_RESTAURANT);
throw new NotFoundException(CartMessage.DELIVERY_METHOD_NOT_FOUND_FOR_SHOP);
}
return deliveryMethod;
@@ -269,5 +269,12 @@ export class CartValidationService {
return new Map(products.map(f => [f.id, f]));
}
/**
* Validate stock for a product
*/
validateStock(product: Product, quantity: number): void {
// TODO: Implement stock validation logic
// For now, assume unlimited stock
}
}
+6 -6
View File
@@ -1,12 +1,12 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { DeliveryMethodEnum } from '../../../delivery/interface/delivery';
import { PaymentMethodEnum } from '../../../payments/interface/payment';
import { OrderCouponDetail } from '../../../orders/interface/order.interface';
import { DeliveryMethodEnum } from 'src/modules/delivery/interface/delivery';
import { PaymentMethodEnum } from 'src/modules/payments/interface/payment';
import { OrderCouponDetail } from 'src/modules/orders/interface/order.interface';
import { Cart } from '../interfaces/cart.interface';
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
import { ApplyCouponDto } from '../dto/apply-coupon.dto';
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
import { CouponService } from '../../../coupons/providers/coupon.service';
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
import { CartRepository } from '../repositories/cart.repository';
import { CartValidationService } from './cart-validation.service';
import { CartCalculationService } from './cart-calculation.service';
@@ -235,7 +235,7 @@ export class CartService {
}
// Check maxUsesPerUser limit by counting how many orders this user has with this coupon
await this.validationService.assertCouponUsageLimit(userId, coupon.id, coupon.maxUsesPerUser);
await this.validationService.assertCouponUsageLimit(userId, (coupon as any).id, coupon.maxUsesPerUser);
await this.validationService.assertCouponMatchesCartIfRestricted(
cart,
coupon.foodCategories,
@@ -243,7 +243,7 @@ export class CartService {
);
const couponDetail: OrderCouponDetail = {
couponId: coupon.id,
couponId: (coupon as any).id,
couponName: coupon.name,
couponCode: coupon.code,
type: coupon.type,
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { CacheService } from '../../../utils/cache.service';
import { CacheService } from 'src/modules/utils/cache.service';
import { Cart } from '../interfaces/cart.interface';
@Injectable()
@@ -1,7 +1,7 @@
import { Entity, Enum, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from 'src/common/entities/base.entity';
import { ContactScope, ContactStatusEnum } from '../interface/interface';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
@Entity({ tableName: 'contacts' })
export class Contact extends BaseEntity {
@@ -8,7 +8,7 @@ import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { ContactStatusEnum } from '../interface/interface';
import { EntityManager } from '@mikro-orm/postgresql';
import { ContactScope } from '../interface/interface';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
@Injectable()
export class ContactService {
@@ -16,7 +16,7 @@ import {
ApiHeader,
} from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { RestId } from 'src/common/decorators';
import { ShopId } from 'src/common/decorators';
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { API_HEADER_SLUG } from 'src/common/constants';
import { Permission } from 'src/common/enums/permission.enum';
+2 -2
View File
@@ -4,12 +4,12 @@ import { CouponController } from './controllers/coupon.controller';
import { CouponRepository } from './repositories/coupon.repository';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Coupon } from './entities/coupon.entity';
import { RestaurantsModule } from ../../../shops.module';
import { ShopsModule } from '../shops/shops.module';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [MikroOrmModule.forFeature([Coupon]), RestaurantsModule, AuthModule, JwtModule],
imports: [MikroOrmModule.forFeature([Coupon]), ShopsModule, AuthModule, JwtModule],
controllers: [CouponController],
providers: [CouponService, CouponRepository],
exports: [CouponRepository, CouponService],
@@ -1,7 +1,7 @@
import { Entity, Index, ManyToOne, Property, Enum, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { normalizePhone } from '../../../utils/phone.util';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { normalizePhone } from '../../utils/phone.util';
import { CouponType } from '../interface/coupon';
@Entity({ tableName: 'coupons' })
@@ -3,7 +3,7 @@ import { CreateCouponDto } from '../dto/create-coupon.dto';
import { UpdateCouponDto } from '../dto/update-coupon.dto';
import { FindCouponsDto } from '../dto/find-coupons.dto';
import { CouponRepository } from '../repositories/coupon.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { ShopRepository } from '../../shops/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Coupon } from '../entities/coupon.entity';
@@ -15,12 +15,12 @@ import { randomBytes } from 'node:crypto';
export class CouponService {
constructor(
private readonly couponRepository: CouponRepository,
private readonly restRepository: RestRepository,
private readonly shopRepository: ShopRepository,
private readonly em: EntityManager,
) {}
async create(restId: string, createCouponDto: CreateCouponDto): Promise<Coupon> {
const shop = await this.restRepository.findOne({ id: restId });
const shop = await this.shopRepository.findOne({ id: restId });
if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
@@ -151,7 +151,7 @@ export class CouponService {
if (!coupon) {
throw new NotFoundException(CouponMessage.NOT_FOUND);
}
coupon.deletedAt = new Date();
(coupon as any).deletedAt = new Date();
await this.em.persistAndFlush(coupon);
}
@@ -258,7 +258,7 @@ export class CouponService {
}
async generateCouponCode(restId: string): Promise<string> {
const shop = await this.restRepository.findOne({ id: restId });
const shop = await this.shopRepository.findOne({ id: restId });
if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
+2 -2
View File
@@ -3,7 +3,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { JwtModule } from '@nestjs/jwt';
import { DeliveryController } from './controllers/delivery.controller';
import { DeliveryService } from './providers/delivery.service';
import { RestaurantsModule } from ../../../shops.module';
import { ShopsModule } from '../shops/shops.module';
import { Delivery } from './entities/delivery.entity';
import { DeliveryRepository } from './repositories/delivery.repository';
@@ -11,6 +11,6 @@ import { DeliveryRepository } from './repositories/delivery.repository';
controllers: [DeliveryController],
providers: [DeliveryService, DeliveryRepository],
exports: [DeliveryService, DeliveryRepository],
imports: [MikroOrmModule.forFeature([Delivery]), JwtModule, RestaurantsModule],
imports: [MikroOrmModule.forFeature([Delivery]), JwtModule, ShopsModule],
})
export class DeliveryModule {}
@@ -1,6 +1,6 @@
import { Entity, Property, Enum, ManyToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery';
@Entity({ tableName: 'deliveries' })
@@ -4,7 +4,7 @@ import { Delivery } from '../entities/delivery.entity';
import { DeliveryRepository } from '../repositories/delivery.repository';
import { CreateDeliveryDto } from '../dto/create-delivery.dto';
import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { ShopRepository } from '../../shops/repositories/rest.repository';
import { DeliveryMethodEnum } from '../interface/delivery';
import { DeliveryMessage } from 'src/common/enums/message.enum';
@@ -12,14 +12,14 @@ import { DeliveryMessage } from 'src/common/enums/message.enum';
export class DeliveryService {
constructor(
private readonly deliveryRepository: DeliveryRepository,
private readonly restRepository: RestRepository,
private readonly shopRepository: ShopRepository,
private readonly em: EntityManager,
) {}
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
const shop = await this.restRepository.findOne({ id: restId });
const shop = await this.shopRepository.findOne({ id: restId });
if (!shop) {
throw new NotFoundException(DeliveryMessage.RESTAURANT_NOT_FOUND);
throw new NotFoundException(DeliveryMessage.SHOP_NOT_FOUND);
}
// Check if the delivery method with the same name already exists for this shop
@@ -1,14 +0,0 @@
import { Entity, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { User } from '../../users/entities/user.entity';
import { Product } from '../../products/entities/product.entity';
@Entity({ tableName: 'favorites' })
@Unique({ properties: ['user', 'product'] })
export class Favorite extends BaseEntity {
@ManyToOne(() => User)
user: User;
@ManyToOne(() => Product)
product: Product;
}
-55
View File
@@ -1,55 +0,0 @@
import { Cascade, Collection, Entity, Index, ManyToOne, OneToMany, Property, OneToOne } from '@mikro-orm/core';
import { Category } from './category.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../../modules/shops/entities/shop.entity';
import { Review } from 'src/modules/review/entities/review.entity';
import { Favorite } from './favorite.entity';
@Entity({ tableName: 'products' })
@Index({ properties: ['shop', 'isActive'] })
@Index({ properties: ['category', 'isActive'] })
@Index({ properties: ['isActive'] })
export class Product extends BaseEntity {
@ManyToOne(() => Shop)
shop: Shop;
@ManyToOne(() => Category)
category: Category;
@OneToMany(() => Review, review => review.product, { cascade: [Cascade.ALL], orphanRemoval: true })
reviews = new Collection<Review>(this);
@OneToMany(() => Favorite, favorite => favorite.product)
favorites = new Collection<Favorite>(this);
@Property({ nullable: true })
title?: string;
@Property({ type: 'text', nullable: true })
desc?: string;
@Property({ type: 'json', nullable: true })
content?: string[];
@Property({ type: 'decimal', precision: 10, scale: 0, nullable: true })
price?: number;
@Property({ type: 'int', nullable: true })
order?: number;
@Property({ type: 'boolean', default: true })
isActive: boolean = true;
@Property({ type: 'json', nullable: true })
images?: string[];
@Property({ type: 'float', default: null })
score?: number | null = null;
@Property({ type: 'float', default: 0 })
discount: number = 0;
@Property({ type: 'boolean', default: false })
isSpecialOffer: boolean = false;
}
-30
View File
@@ -1,30 +0,0 @@
import { Module } from '@nestjs/common';
import { ProductService } from './providers/product.service';
import { ProductStockCrone } from './crone/product.crone';
import { ProductController } from './controllers/product.controller';
import { CategoryController } from './controllers/category.controller';
import { CategoryService } from './providers/category.service';
import { ProductRepository } from './repositories/product.repository';
import { CategoryRepository } from './repositories/category.repository';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Category } from './entities/category.entity';
import { Product } from './entities/product.entity';
import { ShopsModule } from '../shops/shops.module';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
import { UtilsModule } from '../utils/utils.module';
import { Favorite } from './entities/favorite.entity';
@Module({
imports: [
MikroOrmModule.forFeature([Product, Category, Favorite]),
ShopsModule,
AuthModule,
JwtModule,
UtilsModule,
],
controllers: [ProductController, CategoryController],
providers: [ProductService, CategoryService, ProductRepository, CategoryRepository, ProductStockCrone],
exports: [ProductRepository, CategoryRepository],
})
export class ProductModule {}
+1 -1
View File
@@ -1,5 +1,5 @@
import { Entity, Index, Property, Collection, OneToMany, Cascade } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Icon } from './icon.entity';
@Entity({ tableName: 'icon_groups' })
+1 -1
View File
@@ -1,5 +1,5 @@
import { Entity, Index, Property, ManyToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Group } from './group.entity';
@Entity({ tableName: 'icons' })
@@ -2,10 +2,10 @@ import { Controller, Get, Body, Param, UseGuards, Query, Patch, Put, Post } from
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiHeader } from '@nestjs/swagger';
import { NotificationService } from '../services/notification.service';
import { NotificationPreferenceService } from '../services/notification-preference.service';
import { AuthGuard } from '../../../auth/guards/auth.guard';
import { UserId } from '../../../../common/decorators/user-id.decorator';
import { AdminAuthGuard } from '../../../auth/guards/adminAuth.guard';
import { RestId } from '../../../../common/decorators/rest-id.decorator';
import { AuthGuard } from '../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
import { ShopId } from '../../../common/decorators/rest-id.decorator';
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { AdminId } from 'src/common/decorators/admin-id.decorator';
@@ -1,6 +1,6 @@
import { Entity, Property, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { NotifTitleEnum } from '../interfaces/notification.interface';
import { NotifChannelEnum } from '../interfaces/notification.interface';
@@ -1,7 +1,7 @@
import { Entity, Property, ManyToOne, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { User } from '../../../users/entities/user.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { User } from '../../users/entities/user.entity';
import { NotifTitleEnum } from '../interfaces/notification.interface';
import { Admin } from 'src/modules/admin/entities/admin.entity';
@@ -1,5 +1,5 @@
import { Entity, Property, ManyToOne, PrimaryKey } from '@mikro-orm/core';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
@Entity({ tableName: 'sms_logs' })
export class SmsLog {
@@ -3,7 +3,7 @@ import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../../../auth/interfaces/IToken-payload';
import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload';
export interface AuthenticatedSocket extends Socket {
adminId?: string;
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { SmsSentEvent } from '../events/sms.events';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { ShopRepository } from '../../shops/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { SmsLog } from '../entities/smsLogs.entity';
@@ -10,7 +10,7 @@ export class SmsListeners {
private readonly logger = new Logger(SmsListeners.name);
constructor(
private readonly restRepository: RestRepository,
private readonly shopRepository: ShopRepository,
private readonly em: EntityManager,
) {
}
@@ -23,7 +23,7 @@ export class SmsListeners {
);
// Get the shop entity
const shop = await this.restRepository.findOne({ id: event.restaurantId });
const shop = await this.shopRepository.findOne({ id: event.restaurantId });
if (!shop) {
this.logger.warn(
@@ -12,7 +12,7 @@ import { SmsProcessor } from './processors/sms.processor';
import { PushProcessor } from './processors/push.processor';
import { NotificationsController } from './controllers/notifications.controller';
import { User } from '../users/entities/user.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../shops/entities/shop.entity';
import { AuthModule } from '../auth/auth.module';
import { ConfigService } from '@nestjs/config';
import { NotificationQueueNameEnum } from './constants/queue';
@@ -26,7 +26,7 @@ import { NotificationCrone } from './crone/notification.crone';
import { SmsService } from './services/sms.service';
import { SmsLog } from './entities/smsLogs.entity';
import { SmsLogRepository } from './repositories/sms-log.repository';
import { RestaurantsModule } from ../../../shops.module';
import { ShopsModule } from '../shops/shops.module';
import { SmsListeners } from './listeners/sms.listeners';
@Module({
@@ -52,7 +52,7 @@ import { SmsListeners } from './listeners/sms.listeners';
AdminModule,
forwardRef(() => UserModule),
UtilsModule,
forwardRef(() => RestaurantsModule),
forwardRef(() => ShopsModule),
],
controllers: [NotificationsController],
providers: [
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { SmsLog } from '../entities/smsLogs.entity';
import { PaginatedResult } from '../../../../common/interfaces/pagination.interface';
import { PaginatedResult } from '../../../common/interfaces/pagination.interface';
@Injectable()
export class SmsLogRepository extends EntityRepository<SmsLog> {
@@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { NotificationPreference } from '../entities/notification-preference.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { NotifTitleEnum } from '../interfaces/notification.interface';
@@ -5,9 +5,8 @@ import { NotificationPreferenceService } from './notification-preference.service
import { NotificationQueueService } from './notification-queue.service';
import { NotificationsGateway } from '../notifications.gateway';
import { NotifChannelEnum, NotifRequest, NotifTitleEnum } from '../interfaces/notification.interface';
import { SmsLog } from '../entities/smsLogs.entity';
import { SmsLogRepository } from '../repositories/sms-log.repository';
import { PaginatedResult } from '../../../../common/interfaces/pagination.interface';
import { PaginatedResult } from '../../../common/interfaces/pagination.interface';
@Injectable()
export class NotificationService {
@@ -50,7 +49,7 @@ export class NotificationService {
recipient: { adminId: notification.admin?.id || '', restaurantId },
subject: message.title,
body: message.content,
notificationId: notification.id,
notificationId: (notification as any).id,
})),
);
}
@@ -134,7 +133,7 @@ export class NotificationService {
const hasNextPage = notifications.length > limit;
const data = hasNextPage ? notifications.slice(0, limit) : notifications;
const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
const nextCursor = hasNextPage && data.length > 0 ? (data[data.length - 1] as any).id : null;
return {
data,
@@ -174,7 +173,7 @@ export class NotificationService {
// Check if there's a next page
const hasNextPage = notifications.length > limit;
const data = hasNextPage ? notifications.slice(0, limit) : notifications;
const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
const nextCursor = hasNextPage && data.length > 0 ? (data[data.length - 1] as any).id : null;
return {
data,
@@ -1,10 +1,10 @@
import { Controller, Get, Post, Param, UseGuards, Patch, Query, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiHeader, ApiBody } from '@nestjs/swagger';
import { OrdersService } from '../providers/orders.service';
import { AuthGuard } from '../../../auth/guards/auth.guard';
import { UserId } from '../../../../common/decorators/user-id.decorator';
import { AuthGuard } from '../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator';
import { ShopId } from 'src/common/decorators/rest-id.decorator';
import { AdminAuthGuard } from '../../../auth/guards/adminAuth.guard';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
import { FindOrdersDto } from '../dto/find-orders.dto';
import { OrderStatus } from '../interface/order.interface';
import { API_HEADER_SLUG } from 'src/common/constants/index';
+2 -2
View File
@@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Payment } from '../../../payments/entities/payment.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { Payment } from '../../payments/entities/payment.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
import { OrderStatus } from '../interface/order.interface';
import { Order } from '../entities/order.entity';
import { OrderStatusChangedEvent } from '../events/order.events';
+1 -1
View File
@@ -11,7 +11,7 @@ import {
import { Type, Transform } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { OrderStatus } from '../interface/order.interface';
import { PaymentStatusEnum } from '../../../payments/interface/payment';
import { PaymentStatusEnum } from '../../payments/interface/payment';
const sortOrderOptions = ['asc', 'desc'] as const;
type SortOrder = (typeof sortOrderOptions)[number];
@@ -1,7 +1,7 @@
import { Entity, Index, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Order } from './order.entity';
import { Product } from ../../../products/entities/product.entity';
import { Product } from '../../products/entities/product.entity';
@Entity({ tableName: 'order_items' })
@Index({ properties: ['order'] })
+5 -5
View File
@@ -11,13 +11,13 @@ import {
Unique,
type EventArgs,
} from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { OrderCouponDetail, OrderStatus } from '../interface/order.interface';
import { User } from '../../../users/entities/user.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { User } from '../../users/entities/user.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
import { OrderItem } from './order-item.entity';
import { Delivery } from '../../../delivery/entities/delivery.entity';
import { Delivery } from '../../delivery/entities/delivery.entity';
import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
import { Payment } from 'src/modules/payments/entities/payment.entity';
+2 -2
View File
@@ -5,8 +5,8 @@ import { OrdersController } from './controllers/orders.controller';
import { Order } from './entities/order.entity';
import { OrderItem } from './entities/order-item.entity';
import { User } from '../users/entities/user.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { Product } from ../../../products/entities/product.entity';
import { Shop } from '../shops/entities/shop.entity';
import { Product } from '../products/entities/product.entity';
import { UserAddress } from '../users/entities/user-address.entity';
import { PaymentMethod } from '../payments/entities/payment-method.entity';
import { CartModule } from '../cart/cart.module';
+13 -14
View File
@@ -2,17 +2,17 @@ import { Injectable, NotFoundException, BadRequestException, Logger } from '@nes
import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from '../entities/order.entity';
import { OrderItem } from '../entities/order-item.entity';
import { User } from '../../../users/entities/user.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { Product } from '../../../products/entities/product.entity';
import { CartService } from '../../../cart/providers/cart.service';
import { User } from '../../users/entities/user.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { Product } from '../../products/entities/product.entity';
import { CartService } from '../../cart/providers/cart.service';
import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { Cart } from '../../../cart/interfaces/cart.interface';
import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { PaymentsService } from '../../../payments/services/payments.service';
import { DeliveryMethodEnum } from '../../../delivery/interface/delivery';
import { Delivery } from '../../../delivery/entities/delivery.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
import { Cart } from '../../cart/interfaces/cart.interface';
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
import { PaymentsService } from '../../payments/services/payments.service';
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
import { Delivery } from '../../delivery/entities/delivery.entity';
import { OrderRepository } from '../repositories/order.repository';
import { FindOrdersDto } from '../dto/find-orders.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
@@ -88,7 +88,6 @@ export class OrdersService {
for (const itemData of validated.orderItemsData) {
const { product, quantity, unitPrice, discount } = itemData;
this.assertFoodHasSufficientStock(product, quantity);
const totalPrice = (unitPrice - discount) * quantity;
@@ -355,7 +354,7 @@ export class OrdersService {
private async getRestaurantOrFail(restaurantId: string): Promise<Shop> {
const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!shop) throw new NotFoundException(OrderMessage.RESTAURANT_NOT_FOUND);
if (!shop) throw new NotFoundException(OrderMessage.SHOP_NOT_FOUND);
return shop;
}
@@ -416,10 +415,10 @@ export class OrdersService {
for (const cartItem of cart.items) {
const product = await this.em.findOne(Product, { id: cartItem.foodId }, { populate: ['shop'] });
if (!product) throw new NotFoundException(OrderMessage.FOOD_NOT_FOUND);
if (!product) throw new NotFoundException(OrderMessage.PRODUCT_NOT_FOUND);
if (product.shop.id !== restaurantId) {
throw new BadRequestException(OrderMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
throw new BadRequestException(OrderMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
}
orderItemsData.push({
@@ -4,8 +4,8 @@ import { FilterQuery } from '@mikro-orm/core';
import { Order } from '../entities/order.entity';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { OrderStatus } from '../interface/order.interface';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { Review } from '../../../review/entities/review.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
import { Review } from '../../review/entities/review.entity';
type FindOrdersOpts = {
page?: number;
@@ -1,101 +0,0 @@
import { Controller, Get, Post, Body, UseGuards, Res, Req, Patch, Param } from '@nestjs/common';
import { PagerService } from '../providers/pager.service';
import { CreatePagerDto } from '../dto/create-pager.dto';
import { OptionalAuthGuard } from '../../../auth/guards/optinalAuth.guard';
import { ShopId } from 'src/common/decorators/rest-id.decorator';
import { UserId } from 'src/common/decorators/user-id.decorator';
import { RestSlug } from 'src/common/decorators/rest-slug.decorator';
import { ApiTags, ApiOperation, ApiBody, ApiHeader, ApiBearerAuth, ApiParam } from '@nestjs/swagger';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { AdminAuthGuard } from '../../../auth/guards/adminAuth.guard';
import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { UpdatePagerStatusDto } from '../dto/update-pager.dto';
import { PagerMessage } from 'src/common/enums/message.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator';
import { Permission } from 'src/common/enums/permission.enum';
import { API_HEADER_SLUG } from 'src/common/constants';
@ApiTags('pager')
@Controller()
export class PagerController {
constructor(private readonly pagerService: PagerService) { }
@UseGuards(OptionalAuthGuard)
@Post('public/pager')
@ApiOperation({ summary: 'Create a new pager request' })
@ApiHeader({ name: 'X-Slug', required: true, description: 'Shop slug identifier' })
@ApiHeader({ name: 'Authorization', required: false, description: 'Bearer token (optional authentication)' })
@ApiBody({ type: CreatePagerDto })
async create(
@Body() createPagerDto: CreatePagerDto,
@Req() request: FastifyRequest,
@Res() reply: FastifyReply,
@RestSlug() slug: string,
@ShopId() restId?: string,
@UserId() userId?: string,
) {
// Convert empty strings to undefined for cleaner validation
const normalizedRestId = restId || undefined;
const normalizedUserId = userId || undefined;
const userCookieId = request.cookies['pagerId'] || undefined;
const pager = await this.pagerService.create(createPagerDto, {
restId: normalizedRestId,
userId: normalizedUserId,
userCookieId,
slug,
});
reply.setCookie('pagerId', pager.cookieId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 1000 * 60 * 60 * 24 * 1, // 1 days
path: '/',
});
return reply.code(200).send({
success: true,
message: PagerMessage.CREATED_SUCCESS,
data: null,
});
}
@UseGuards(OptionalAuthGuard)
@Get('public/pager')
@ApiOperation({ summary: 'Get all pager requests for the current session' })
@ApiHeader(API_HEADER_SLUG)
@ApiHeader({ name: 'Authorization', required: false, description: 'Bearer token (optional authentication)' })
async findAll(@Req() request: FastifyRequest) {
const cookieId = request.cookies['pagerId'] || undefined;
if (!cookieId) {
return [];
}
return this.pagerService.findAll(cookieId);
}
/******************** Admin Routes **********************/
@UseGuards(AdminAuthGuard)
@Permissions(Permission.MANAGE_PAGER)
@ApiBearerAuth()
@Get('admin/pager')
@ApiOperation({ summary: 'Get all pager requests for the shop' })
async findAllAdmin(@ShopId() restId: string) {
return this.pagerService.findAllByRestaurantId(restId);
}
@UseGuards(AdminAuthGuard)
@Permissions(Permission.MANAGE_PAGER)
@ApiBearerAuth()
@Patch('admin/pager/:pagerId/status')
@ApiParam({ name: 'pagerId', required: true, type: String })
@ApiOperation({ summary: 'Update the status of a pager request' })
@ApiBody({ type: UpdatePagerStatusDto })
async updateStatus(
@Param('pagerId') pagerId: string,
@ShopId() restId: string,
@AdminId() adminId: string,
@Body() dto: UpdatePagerStatusDto,
) {
return this.pagerService.updateStatus(pagerId, restId, adminId, dto.status);
}
}
@@ -1,26 +0,0 @@
import type { ExecutionContext } from '@nestjs/common';
import { createParamDecorator } from '@nestjs/common';
import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard';
/**
* Extract admin ID from authenticated WebSocket client
* Use this decorator in WebSocket handlers to get the adminId from the authenticated admin
*
* @example
* ```typescript
* @SubscribeMessage('some:event')
* handleEvent(@WsAdminId() adminId: string) {
* // adminId is automatically extracted from authenticated admin
* }
* ```
*/
export const WsAdminId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
const client = ctx.switchToWs().getClient<AuthenticatedSocket>();
const adminId = client.adminId;
if (!adminId) {
throw new Error('Admin ID not found. Ensure WsAdminAuthGuard is applied.');
}
return adminId;
});
@@ -1,26 +0,0 @@
import type { ExecutionContext } from '@nestjs/common';
import { createParamDecorator } from '@nestjs/common';
import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard';
/**
* Extract shop ID from authenticated WebSocket client
* Use this decorator in WebSocket handlers to get the restId from the authenticated admin
*
* @example
* ```typescript
* @SubscribeMessage('join:shop')
* handleJoinRestaurant(@WsRestId() restId: string) {
* // restId is automatically extracted from authenticated admin
* }
* ```
*/
export const WsRestId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
const client = ctx.switchToWs().getClient<AuthenticatedSocket>();
const restId = client.restId;
if (!restId) {
throw new Error('Shop ID not found. Ensure WsAdminAuthGuard is applied.');
}
return restId;
});
-15
View File
@@ -1,15 +0,0 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class CreatePagerDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ description: 'Table number' })
tableNumber!: string;
@IsString()
@IsOptional()
@ApiPropertyOptional({ description: 'Message' })
message?: string;
}
@@ -1,208 +0,0 @@
import type { PagerStatus } from '../interface/pager';
/**
* Socket connection configuration for admin pager module
* Used by frontend to establish WebSocket connection
*/
export interface PagerSocketConnectionConfig {
/**
* WebSocket server URL
* Example: 'http://localhost:3000' or 'https://api.example.com'
*/
serverUrl: string;
/**
* Socket namespace (default: '/pager')
* The gateway is configured to use '/pager' namespace
*/
namespace?: string;
/**
* JWT authentication token for admin
* Token should contain adminId and restId in payload
* Can be passed via auth.token, auth.authorization, query.token, or headers.authorization
* Example: { auth: { token: 'your-jwt-token' } }
*/
token: string;
/**
* Optional: Additional connection options
*/
options?: {
/**
* Enable auto-reconnection
* @default true
*/
autoConnect?: boolean;
/**
* Connection timeout in milliseconds
* @default 5000
*/
timeout?: number;
/**
* Enable transports (websocket, polling)
* @default ['websocket', 'polling']
*/
transports?: ('websocket' | 'polling')[];
};
}
/**
* Socket event names for pager module
*/
export enum PagerSocketEvents {
// Client to Server Events
JOIN_RESTAURANT = 'join:shop',
LEAVE_ROOM = 'leave:room',
GET_PAGERS = 'get:pagers',
UPDATE_PAGER_STATUS = 'update:pager:status',
// Server to Client Events
JOINED = 'joined',
LEFT = 'left',
PAGER_CREATED = 'pager:created',
PAGER_STATUS_UPDATED = 'pager:status:updated',
PAGERS_LIST = 'pagers:list',
PAGER_STATUS_UPDATED_RESPONSE = 'pager:status:updated:response',
ERROR = 'error',
CONNECT = 'connect',
DISCONNECT = 'disconnect',
}
/**
* Payload for joining shop room
* Note: restId is automatically extracted from JWT token, no need to send it
*/
export type JoinRestaurantPayload = Record<string, never>;
/**
* Payload for leaving a room
*/
export interface LeaveRoomPayload {
room: string;
}
/**
* Payload for getting pagers list
* Note: restId is automatically extracted from JWT token, no need to send it
*/
export type GetPagersPayload = Record<string, never>;
/**
* Payload for updating pager status
* Note: restId and adminId are automatically extracted from JWT token
*/
export interface UpdatePagerStatusPayload {
pagerId: string;
status: PagerStatus;
}
/**
* Response when successfully joined a room
*/
export interface JoinedResponse {
room: string;
message: string;
}
/**
* Response when successfully left a room
*/
export interface LeftResponse {
room: string;
message: string;
}
/**
* User information in pager response
*/
export interface PagerUserInfo {
id: string;
firstName: string;
lastName?: string;
}
/**
* Staff information in pager response
*/
export interface PagerStaffInfo {
id: string;
firstName: string;
lastName?: string;
}
/**
* Pager data structure emitted by socket events
*/
export interface PagerSocketData {
id: string;
tableNumber: string;
message?: string;
status: PagerStatus;
cookieId?: string;
createdAt?: Date | string;
updatedAt?: Date | string;
user?: PagerUserInfo | null;
staff?: PagerStaffInfo | null;
}
/**
* Payload for pager:created event
*/
export interface PagerCreatedPayload {
pager: PagerSocketData;
}
/**
* Payload for pager:status:updated event
*/
export interface PagerStatusUpdatedPayload {
pager: PagerSocketData;
}
/**
* Payload for pagers:list event
*/
export interface PagersListPayload {
pagers: PagerSocketData[];
}
/**
* Payload for pager:status:updated:response event (response to update request)
*/
export interface PagerStatusUpdatedResponsePayload {
pager: PagerSocketData;
}
/**
* Error response from socket
*/
export interface SocketErrorResponse {
message: string;
code?: string;
details?: unknown;
}
/**
* Complete socket event map for type safety
*/
export interface PagerSocketEventMap {
// Client emits
[PagerSocketEvents.JOIN_RESTAURANT]: (payload?: JoinRestaurantPayload) => void;
[PagerSocketEvents.LEAVE_ROOM]: (payload: LeaveRoomPayload) => void;
[PagerSocketEvents.GET_PAGERS]: (payload?: GetPagersPayload) => void;
[PagerSocketEvents.UPDATE_PAGER_STATUS]: (payload: UpdatePagerStatusPayload) => void;
// Server emits
[PagerSocketEvents.JOINED]: (response: JoinedResponse) => void;
[PagerSocketEvents.LEFT]: (response: LeftResponse) => void;
[PagerSocketEvents.PAGER_CREATED]: (payload: PagerCreatedPayload) => void;
[PagerSocketEvents.PAGER_STATUS_UPDATED]: (payload: PagerStatusUpdatedPayload) => void;
[PagerSocketEvents.PAGERS_LIST]: (payload: PagersListPayload) => void;
[PagerSocketEvents.PAGER_STATUS_UPDATED_RESPONSE]: (payload: PagerStatusUpdatedResponsePayload) => void;
[PagerSocketEvents.ERROR]: (error: SocketErrorResponse) => void;
[PagerSocketEvents.CONNECT]: () => void;
[PagerSocketEvents.DISCONNECT]: (reason: string) => void;
}
-10
View File
@@ -1,10 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty } from 'class-validator';
import { PagerStatus } from '../interface/pager';
export class UpdatePagerStatusDto {
@ApiProperty({ description: 'Status of the pager', enum: PagerStatus })
@IsEnum(PagerStatus)
@IsNotEmpty()
status!: PagerStatus;
}
@@ -1,30 +0,0 @@
import { Entity, ManyToOne, Property, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { User } from '../../../users/entities/user.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { PagerStatus } from '../interface/pager';
import { Admin } from 'src/modules/admin/entities/admin.entity';
@Entity({ tableName: 'pagers' })
export class Pager extends BaseEntity {
@Property({ type: 'string' })
cookieId!: string;
@ManyToOne(() => Shop)
shop!: Shop;
@Property({ type: 'string' })
tableNumber!: string;
@ManyToOne(() => User, { nullable: true })
user?: User;
@ManyToOne(() => Admin, { nullable: true })
staff?: Admin;
@Property({ type: 'string', nullable: true })
message?: string;
@Enum(() => PagerStatus)
status: PagerStatus = PagerStatus.Pending;
}
-6
View File
@@ -1,6 +0,0 @@
export class PagerCreatedEvent {
constructor(
public readonly restaurantId: string,
public readonly tableNumber: string,
) {}
}
@@ -1,104 +0,0 @@
import { CanActivate, ExecutionContext, Injectable, Logger, Inject } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../../../auth/interfaces/IToken-payload';
export interface AuthenticatedSocket extends Socket {
adminId?: string;
restId?: string;
}
@Injectable()
export class WsAdminAuthGuard implements CanActivate {
private readonly logger = new Logger(WsAdminAuthGuard.name);
constructor(
@Inject(JwtService)
private readonly jwtService: JwtService,
@Inject(ConfigService)
private readonly configService: ConfigService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const client: Socket = context.switchToWs().getClient<Socket>();
try {
const token = this.extractToken(client);
if (!token) {
this.logger.warn('No token provided in WebSocket connection', {
socketId: client.id,
auth: client.handshake.auth,
query: client.handshake.query,
});
throw new WsException('Authentication required');
}
const secret = this.configService.getOrThrow<string>('JWT_SECRET');
const payload = await this.jwtService.verifyAsync<IAdminTokenPayload>(token, {
secret,
});
if (!payload.adminId || !payload.restId) {
this.logger.error('Invalid token payload structure', payload);
throw new WsException('Invalid token payload');
}
// Attach admin info to socket
(client as AuthenticatedSocket).adminId = payload.adminId;
(client as AuthenticatedSocket).restId = payload.restId;
this.logger.log(`Admin authenticated via WebSocket: ${payload.adminId}, restId: ${payload.restId}`);
return true;
} catch (error) {
if (error instanceof WsException) {
throw error;
}
this.logger.error('WebSocket authentication error', {
error: error instanceof Error ? error.message : 'Unknown error',
socketId: client.id,
});
throw new WsException('Authentication failed');
}
}
private extractToken(client: Socket): string | undefined {
// Try to get token from handshake auth (recommended for socket.io)
const auth = client.handshake.auth;
const authToken = (auth?.token as string | undefined) || (auth?.authorization as string | undefined);
if (authToken) {
// If it's "Bearer <token>", extract just the token
if (typeof authToken === 'string' && authToken.startsWith('Bearer ')) {
return authToken.substring(7);
}
return authToken;
}
// Try to get from query parameters (fallback)
const queryToken = client.handshake.query?.token || client.handshake.query?.authorization;
if (queryToken) {
if (typeof queryToken === 'string' && queryToken.startsWith('Bearer ')) {
return queryToken.substring(7);
}
return queryToken as string;
}
// Try to get from headers (if available)
const headers = client.handshake.headers;
const authHeader = headers.authorization || headers['authorization'];
if (authHeader && typeof authHeader === 'string') {
const [type, token] = authHeader.split(' ');
if (type?.toLowerCase() === 'bearer' && token) {
return token;
}
}
return undefined;
}
}
-6
View File
@@ -1,6 +0,0 @@
export enum PagerStatus {
Pending = 'pending',
Acknowledged = 'acknowledged',
Rejected = 'rejected',
Completed = 'completed',
}
@@ -1,64 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { PagerCreatedEvent } from '../events/pager.events';
import { Permission } from 'src/common/enums/permission.enum';
import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notification.interface';
import { NotificationService } from 'src/modules/notifications/services/notification.service';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class PagerListeners {
private readonly logger = new Logger(PagerListeners.name);
private readonly smsPatternPagerCreated: string
constructor(
private readonly adminService: AdminRepository,
private readonly notificationService: NotificationService,
private readonly configService: ConfigService,
) {
this.smsPatternPagerCreated = this.configService.get<string>('SMS_PATTERN_PAGER_CREATED') ?? '123';
}
@OnEvent(PagerCreatedEvent.name)
async handlePagerCreated(event: PagerCreatedEvent) {
try {
this.logger.log(`Pager created event received: ${event.restaurantId}`);
// get admnin os restuaraant that have pager permissuins
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_PAGER);
const recipients = admins.map(admin => ({
adminId: admin.id,
}));
await this.notificationService.sendNotification({
restaurantId: event.restaurantId,
message: {
title: NotifTitleEnum.PAGER_CREATED,
content: `میز شماره ${event.tableNumber} پیج جدیدی انجام داد`,
sms: {
templateId: this.smsPatternPagerCreated,
parameters: {
tableNumber: event.tableNumber.toString(),
},
},
pushNotif: {
title: ` پیج جدید`,
content: `میز شماره ${event.tableNumber} پیج جدیدی انجام داد`,
icon: `/assets/images/logo.png`,
action: {
type: NotifTitleEnum.PAGER_CREATED,
url: `/shops/${event.restaurantId}/pagers`,
},
},
},
recipients,
metadata: {
priority: 1,
},
});
} catch (error) {
this.logger.error(
`Failed to send notification for pager created event: ${event.restaurantId}`,
error instanceof Error ? error.stack : String(error),
);
}
}
}
-17
View File
@@ -1,17 +0,0 @@
import { Module } from '@nestjs/common';
import { PagerService } from './providers/pager.service';
import { PagerController } from './controllers/pager.controller';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Pager } from './entities/pager.entity';
import { JwtModule } from '@nestjs/jwt';
import { RolesModule } from '../roles/roles.module';
import { PagerListeners } from './listeners/notification.listeners';
import { AdminModule } from '../admin/admin.module';
import { NotificationsModule } from '../notifications/notifications.module';
@Module({
imports: [MikroOrmModule.forFeature([Pager]), JwtModule, RolesModule, AdminModule, NotificationsModule],
controllers: [PagerController],
providers: [PagerService, PagerListeners],
})
export class PagerModule {}
@@ -1,115 +0,0 @@
import { BadRequestException, Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common';
import { CreatePagerDto } from '../dto/create-pager.dto';
import { User } from '../../../users/entities/user.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { Shop } from ../../../shops/entities/shop.entity';
import { Pager } from '../entities/pager.entity';
import { PagerStatus } from '../interface/pager';
import { ulid } from 'ulid';
import { Admin } from '../../../admin/entities/admin.entity';
import { PagerCreatedEvent } from '../events/pager.events';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class PagerService {
constructor(
private readonly em: EntityManager,
// @Inject(forwardRef(() => PagerGateway))
// private readonly pagerGateway: PagerGateway,
private readonly eventEmitter: EventEmitter2,
) {}
async create(
createPagerDto: CreatePagerDto,
params: { restId?: string; userId?: string; slug: string; userCookieId?: string },
) {
const { restId, userId, slug, userCookieId } = params;
let shop: Shop | null = null;
let user: User | null = null;
if (!restId && !userId && !slug) {
throw new BadRequestException('Invalid parameters');
}
if (restId) {
shop = await this.em.findOne(Shop, { id: restId });
if (!shop) {
throw new NotFoundException('Shop not found');
}
}
if (userId) {
user = await this.em.findOne(User, { id: userId });
if (!user) {
throw new NotFoundException('User not found');
}
}
if (!shop && slug) {
shop = await this.em.findOne(Shop, { slug });
if (!shop) {
throw new NotFoundException('Shop not found');
}
}
const cookieId = userCookieId || ulid().toString();
// const existingPager = await this.em.findOne(Pager, {
// shop: { id: shop!.id },
// status: PagerStatus.Pending,
// cookieId,
// });
// if (existingPager) {
// throw new BadRequestException('Pager already exists');
// }
const pager = this.em.create(Pager, {
...createPagerDto,
shop: shop!,
user,
cookieId: ulid().toString(),
status: PagerStatus.Pending,
});
await this.em.persistAndFlush(pager);
// Populate relations before emitting
await this.em.populate(pager, ['shop']);
// Emit socket event for new pager
this.eventEmitter.emit(PagerCreatedEvent.name, new PagerCreatedEvent(pager.shop.id, pager.tableNumber));
return pager;
}
async findAll(cookieId: string) {
const pagers = await this.em.find(Pager, { cookieId }, { populate: ['user'] });
return pagers;
}
async findAllByRestaurantId(restId: string) {
const pagers = await this.em.find(Pager, { shop: { id: restId } }, { populate: ['user'] });
return pagers;
}
async updateStatus(pagerId: string, restId: string, staffId: string, status: PagerStatus) {
const pager = await this.em.findOne(
Pager,
{ id: pagerId, shop: { id: restId } },
{ populate: ['shop'] },
);
if (!pager) {
throw new NotFoundException('Pager not found *');
}
const staff = await this.em.findOne(Admin, { id: staffId });
if (!staff) {
throw new NotFoundException('Staff not found');
}
pager.status = status;
if (status === PagerStatus.Acknowledged) {
pager.staff = staff;
}
await this.em.persistAndFlush(pager);
// Populate relations before emitting
await this.em.populate(pager, ['shop', 'staff', 'user']);
// Emit socket event for status update
// this.pagerGateway.emitPagerStatusUpdated(pager);
return pager;
}
}
@@ -15,7 +15,7 @@ import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
import { VerifyPaymentDto } from '../dto/verify-payment.dto';
import { PaymentChartDto } from '../dto/payment-chart.dto';
import { RestId, UserId } from 'src/common/decorators';
import { ShopId, UserId } from 'src/common/decorators';
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { API_HEADER_SLUG } from 'src/common/constants';
import { Permissions } from 'src/common/decorators/permissions.decorator';
@@ -1,6 +1,6 @@
import { Entity, Enum, Index, ManyToOne, Property, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { PaymentGatewayEnum, PaymentMethodEnum } from '../interface/payment';
@Entity({ tableName: 'payment_methods' })
@@ -1,6 +1,6 @@
import { Entity, ManyToOne, Property, Enum, Index } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Order } from '../../../orders/entities/order.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Order } from '../../orders/entities/order.entity';
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
@Entity({ tableName: 'payments' })
+1 -1
View File
@@ -27,7 +27,7 @@ export interface IPaymentVerifyData {
cardPan?: string;
raw: Record<string, any>;
}
../../../../../../////// Zarinpal API v../../../../../../////////
// Zarinpal API
export interface IZarinpalRequestPayment {
amount: number;
merchant_id: string;
+1 -1
View File
@@ -4,7 +4,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { PaymentMethod } from './entities/payment-method.entity';
import { PaymentMethodRepository } from './repositories/payment-method.repository';
import { PaymentMethodService } from './services/payment-method.service';
import { Shop } from '../../../shops/entities/shop.entity';
import { Shop } from '../shops/entities/shop.entity';
import { PaymentsController } from './controllers/payments.controller';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@@ -5,7 +5,7 @@ import { PaymentMethodRepository } from '../repositories/payment-method.reposito
import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
import { RequiredEntityData } from '@mikro-orm/core';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { RestMessage, PaymentMessage } from 'src/common/enums/message.enum';
@Injectable()
@@ -2,7 +2,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
import { Payment } from '../entities/payment.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from '../../../orders/entities/order.entity';
import { Order } from '../../orders/entities/order.entity';
import { Logger } from '@nestjs/common';
import { GatewayManager } from '../gateways/gateway.manager';
import { WalletTransaction } from 'src/modules/users/entities/wallet-transaction.entity';
@@ -72,7 +72,7 @@ export class PaymentsService {
if (pm.method === PaymentMethodEnum.Online) {
if (!pm.gateway) throw new BadRequestException(PaymentMessage.GATEWAY_REQUIRED);
if (!pm.merchantId) throw new BadRequestException(PaymentMessage.MERCHANT_ID_REQUIRED);
if (!pm.shop?.domain) throw new BadRequestException(PaymentMessage.RESTAURANT_DOMAIN_REQUIRED);
if (!pm.shop?.domain) throw new BadRequestException(PaymentMessage.SHOP_DOMAIN_REQUIRED);
}
return {
@@ -15,7 +15,7 @@ import {
} from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { UseGuards } from '@nestjs/common';
import { RestId } from 'src/common/decorators';
import { ShopId } from 'src/common/decorators';
import { Permission } from 'src/common/enums/permission.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator';
import { API_HEADER_SLUG } from 'src/common/constants';
@@ -1,7 +1,7 @@
import { Entity, Index, Property, Collection, OneToMany, ManyToOne } from '@mikro-orm/core';
import { Product } from './product.entity';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../shops/entities/shop.entity';
@Entity({ tableName: 'categories' })
@Index({ properties: ['shop', 'isActive'] })
@@ -1,7 +1,7 @@
import { Entity, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { User } from '../../../users/entities/user.entity';
import { Product } from '../../../products/entities/product.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { User } from '../../users/entities/user.entity';
import { Product } from '../../products/entities/product.entity';
@Entity({ tableName: 'favorites' })
@Unique({ properties: ['user', 'product'] })
@@ -52,4 +52,7 @@ export class Product extends BaseEntity {
@Property({ type: 'boolean', default: false })
isSpecialOffer: boolean = false;
@Property({ type: 'decimal', precision: 10, scale: 0, nullable: true })
price?: number;
}
+4 -4
View File
@@ -1,7 +1,6 @@
import { Module } from '@nestjs/common';
import { ProductService } from './providers/product.service';
import { ProductStockCrone } from './crone/product.crone';
import { ProductController } from './controllers/product.controller';
import { FoodController } from './controllers/product.controller';
import { CategoryController } from './controllers/category.controller';
import { CategoryService } from './providers/category.service';
import { ProductRepository } from './repositories/product.repository';
@@ -22,9 +21,10 @@ import { Favorite } from './entities/favorite.entity';
AuthModule,
JwtModule,
UtilsModule,
ShopsModule
],
controllers: [ProductController, CategoryController],
providers: [ProductService, CategoryService, ProductRepository, CategoryRepository, ProductStockCrone],
controllers: [FoodController, CategoryController],
providers: [ProductService, CategoryService, ProductRepository, CategoryRepository],
exports: [ProductRepository, CategoryRepository],
})
export class ProductModule {}
@@ -6,7 +6,7 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Category } from '../entities/category.entity';
import { CategoryMessage, RestMessage } from 'src/common/enums/message.enum';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
@Injectable()
export class CategoryService {
@@ -1,112 +0,0 @@
import { Collection, Entity, Enum, Index, OneToMany, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Delivery } from '../../delivery/entities/delivery.entity';
import { PlanEnum } from '../interface/plan.interface';
@Entity({ tableName: 'shops' })
@Index({ properties: ['isActive'] })
@Index({ properties: ['slug', 'isActive'] })
export class Shop extends BaseEntity {
// --- اطلاعات پایه ---
@Property()
name!: string;
@Property({ unique: true })
slug!: string;
@Property({ nullable: true })
logo?: string;
@Property({ nullable: true })
address?: string;
@Property({ nullable: true })
menuColor?: string;
// --- مختصات جغرافیایی ---
@Property({ type: 'decimal', precision: 10, scale: 7, nullable: true })
latitude?: number;
@Property({ type: 'decimal', precision: 10, scale: 7, nullable: true })
longitude?: number;
@Property({ type: 'json', nullable: true })
serviceArea?: {
type: 'Polygon';
coordinates: number[][][];
};
// --- وضعیت‌ها ---
@Property({ default: true })
isActive: boolean = true;
@Property({ nullable: true })
establishedYear?: number;
@Property({ nullable: true })
phone?: string;
@Property({ nullable: true })
instagram?: string;
@Property({ nullable: true })
telegram?: string;
@Property({ nullable: true })
whatsapp?: string;
// --- توضیحات ---
@Property({ type: 'text', nullable: true })
description?: string;
// --- سئو ---
@Property({ nullable: true })
seoTitle?: string;
@Property({ nullable: true, type: 'text' })
seoDescription?: string;
@Property({ nullable: true, type: 'json' })
tagNames?: string[];
// --- تصاویر ---
@Property({ nullable: true, type: 'json' })
images?: string[];
// --- مالیات یا VAT ---
@Property({ type: 'decimal', default: 0 })
vat?: number = 0;
@Property()
domain!: string;
// --- روش‌های ارسال ---
@OneToMany(() => Delivery, delivery => delivery.shop)
deliveries = new Collection<Delivery>(this);
@Property({ type: 'json', nullable: true })
score: {
purchaseAmount: string;
purchaseScore: string;
scoreAmount: string;
scoreCredit: string;
// bonus score
birthdayScore: string;
registerScore: string;
marriageDateScore: string;
referrerScore: string;
} | null = null;
@Enum(() => PlanEnum)
plan: PlanEnum = PlanEnum.Base;
@Property({unique: true,nullable: true})
subscriptionId?: string;
@Property({nullable: true})
subscriptionEndDate?: Date;
@Property({nullable: true})
subscriptionStartDate?: Date;
}
@@ -1,21 +0,0 @@
import { Module, forwardRef } from '@nestjs/common';
import { ShopsService } from './providers/shops.service';
import { ShopsController } from './controllers/shops.controller';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Shop } from './entities/shop.entity';
import { RestRepository } from './repositories/rest.repository';
import { ScheduleRepository } from './repositories/schedule.repository';
import { Schedule } from './entities/schedule.entity';
import { ScheduleService } from './providers/schedule.service';
import { ScheduleController } from './controllers/schedule.controller';
import { ShopCrone } from './crone/shop.crone';
import { JwtModule } from '@nestjs/jwt';
import { AuthModule } from '../auth/auth.module';
@Module({
controllers: [ShopsController, ScheduleController],
providers: [ShopsService, RestRepository, ScheduleRepository, ScheduleService, ShopCrone],
imports: [MikroOrmModule.forFeature([Shop, Schedule]), JwtModule, forwardRef(() => AuthModule)],
exports: [RestRepository, ScheduleRepository, ScheduleService],
})
export class ShopsModule {}
+3 -3
View File
@@ -1,7 +1,7 @@
import { Entity, Index, ManyToOne, Property, Unique, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Product } from '../../../products/entities/product.entity';
import { User } from '../../../users/entities/user.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Product } from '../../products/entities/product.entity';
import { User } from '../../users/entities/user.entity';
import { Order } from 'src/modules/orders/entities/order.entity';
import { PositivePoint, NegativePoint } from '../enums/review-point.enum';
import { ReviewStatus } from '../enums/review-status.enum';
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql';
import { FoodRepository } from ../../../products/repositories/product.repository';
import { ProductRepository } from '../../products/repositories/product.repository';
import { ReviewRepository } from '../repositories/review.repository';
import { ReviewStatus } from '../enums/review-status.enum';
@@ -11,7 +11,7 @@ export class FoodRatingCronService {
constructor(
private readonly em: EntityManager,
private readonly foodRepository: FoodRepository,
private readonly foodRepository: ProductRepository,
private readonly reviewRepository: ReviewRepository,
) { }
@@ -3,25 +3,25 @@ import { CreateReviewDto } from '../dto/create-review.dto';
import { UpdateReviewDto } from '../dto/update-review.dto';
import { FindRestuarantReviewsDto, FindReviewsDto } from '../dto/find-reviews.dto';
import { ReviewRepository } from '../repositories/review.repository';
import { FoodRepository } from ../../../products/repositories/product.repository';
import { UserRepository } from '../../../users/repositories/user.repository';
import { ProductRepository } from '../../products/repositories/product.repository';
import { UserRepository } from '../../users/repositories/user.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Review } from '../entities/review.entity';
import { FoodMessage, UserMessage, ReviewMessage } from 'src/common/enums/message.enum';
import { Order } from '../../../orders/entities/order.entity';
import { OrderItem } from '../../../orders/entities/order-item.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { ProductMessage, UserMessage, ReviewMessage } from 'src/common/enums/message.enum';
import { Order } from '../../orders/entities/order.entity';
import { OrderItem } from '../../orders/entities/order-item.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { ReviewStatus } from '../enums/review-status.enum';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ReviewCreatedEvent } from '../events/review.events';
import { sanitizeText } from '../../../utils/sanitize.util';
import { sanitizeText } from '../../utils/sanitize.util';
@Injectable()
export class ReviewService {
constructor(
private readonly reviewRepository: ReviewRepository,
private readonly foodRepository: FoodRepository,
private readonly foodRepository: ProductRepository,
private readonly userRepository: UserRepository,
private readonly em: EntityManager,
private readonly eventEmitter: EventEmitter2,
@@ -32,7 +32,7 @@ export class ReviewService {
const product = await this.foodRepository.findOne({ id: foodId });
if (!product) {
throw new NotFoundException(FoodMessage.NOT_FOUND);
throw new NotFoundException(ProductMessage.NOT_FOUND);
}
const user = await this.userRepository.findOne({ id: userId });
+3 -2
View File
@@ -5,7 +5,7 @@ import { ReviewRepository } from './repositories/review.repository';
import { FoodRatingCronService } from './providers/product-rating-cron.service';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Review } from './entities/review.entity';
import { FoodModule } from ../../../products/product.module';
import { ProductModule } from '../products/product.module';
import { UserModule } from '../users/user.module';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@@ -14,7 +14,8 @@ import { AdminModule } from '../admin/admin.module';
import { NotificationsModule } from '../notifications/notifications.module';
@Module({
imports: [MikroOrmModule.forFeature([Review]), FoodModule, UserModule, AuthModule, JwtModule, AdminModule, NotificationsModule],
imports: [MikroOrmModule.forFeature([Review]), ProductModule,
UserModule, AuthModule, JwtModule, AdminModule, NotificationsModule],
controllers: [ReviewController],
providers: [ReviewService, ReviewRepository, FoodRatingCronService, ReviewListeners],
exports: [ReviewRepository],
@@ -7,7 +7,7 @@ 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 { RestId } from 'src/common/decorators';
import { ShopId } from 'src/common/decorators';
import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { Permission } from 'src/common/enums/permission.enum';
@@ -1,5 +1,5 @@
import { Entity, Property, Unique, ManyToMany, Collection } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Role } from './role.entity';
@Entity({ tableName: 'permissions' })
+2 -2
View File
@@ -1,7 +1,7 @@
import { Collection, Entity, Index, ManyToMany, OneToMany, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Permission } from './permission.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { RolePermission } from './rolePermission.entity';
import { AdminRole } from 'src/modules/admin/entities/adminRole.entity';
+1 -1
View File
@@ -3,7 +3,7 @@ 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 { Shop } from ../../../shops/entities/shop.entity';
import { Shop } from '../../shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { CreateRoleDto } from '../dto/create-role.dto';
import { UpdateRoleDto } from '../dto/update-role.dto';
@@ -1,8 +1,8 @@
import { Controller, Get, Post, Body, Patch, Param, UseGuards, Delete, Query } from '@nestjs/common';
import { RestaurantsService } from '../../../shops.service';
import { ShopService } from '../providers/shops.service';
import { CreateRestaurantDto } from '../dto/create-shop.dto';
import { UpdateRestaurantDto } from '../dto/update-shop.dto';
import { UpgradeSubscriptionDto } from '../dto/upgrade-subscription.dto';
import { UpdateSubscriptionDto } from '../dto/update-subscription.dto';
import { FindRestaurantsDto } from '../dto/find-shops.dto';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import {
@@ -14,7 +14,7 @@ import {
ApiTags,
ApiHeader,
} from '@nestjs/swagger';
import { RestId } from 'src/common/decorators';
import { ShopId } from 'src/common/decorators';
import { API_HEADER_SLUG } from 'src/common/constants';
import { SuperAdminAuthGuard } from 'src/modules/auth/guards/superAdminAuth.guard';
import { Permissions } from 'src/common/decorators/permissions.decorator';
@@ -22,8 +22,8 @@ import { Permission } from 'src/common/enums/permission.enum';
@ApiTags('shops')
@Controller()
export class RestaurantsController {
constructor(private readonly restaurantsService: RestaurantsService) { }
export class ShopController {
constructor(private readonly shopService: ShopService) { }
@Get('public/shops/:slug')
@ApiOperation({ summary: 'Get shop specification by slug' })
@@ -31,7 +31,7 @@ export class RestaurantsController {
@ApiParam({ name: 'slug', required: true, description: 'Shop slug' })
@ApiNotFoundResponse({ description: 'Shop not found' })
findBySlug(@Param('slug') slug: string) {
return this.restaurantsService.getRestaurantSpecification(slug);
return this.shopService.getRestaurantSpecification(slug);
}
/** Admin Endpoints */
@@ -42,7 +42,7 @@ export class RestaurantsController {
@ApiOperation({ summary: 'Get shop by ID from request' })
@Get('admin/shops/my-shop')
async findOne(@ShopId() restId: string) {
return await this.restaurantsService.findOne(restId);
return await this.shopService.findOne(restId);
}
@UseGuards(AdminAuthGuard)
@@ -52,7 +52,7 @@ export class RestaurantsController {
@ApiBody({ type: UpdateRestaurantDto })
@Patch('admin/shops/my-shop')
updateMyRestaurant(@ShopId() restId: string, @Body() updateRestaurantDto: UpdateRestaurantDto) {
return this.restaurantsService.update(restId, updateRestaurantDto);
return this.shopService.update(restId, updateRestaurantDto);
}
@UseGuards(AdminAuthGuard)
@@ -61,7 +61,7 @@ export class RestaurantsController {
@ApiOperation({ summary: 'Delete a shop' })
@Delete('admin/shops/:id')
remove(@Param('id') id: string) {
return this.restaurantsService.remove(id);
return this.shopService.remove(id);
}
/** Super Admin Endpoints */
@@ -70,7 +70,7 @@ export class RestaurantsController {
@ApiOperation({ summary: 'Get all shops with pagination and filters' })
@Get('super-admin/shops')
findAll(@Query() dto: FindRestaurantsDto) {
return this.restaurantsService.findAll(dto);
return this.shopService.findAll(dto);
}
@UseGuards(SuperAdminAuthGuard)
@@ -78,7 +78,7 @@ export class RestaurantsController {
@ApiOperation({ summary: 'Create a new shop' })
@Post('super-admin/shops')
createRestaurant(@Body() createRestaurantDto: CreateRestaurantDto) {
return this.restaurantsService.setupRestuarant(createRestaurantDto);
return this.shopService.setupRestuarant(createRestaurantDto);
}
@UseGuards(SuperAdminAuthGuard)
@@ -87,7 +87,7 @@ export class RestaurantsController {
@ApiParam({ name: 'subscriptionId', required: true, description: 'Subscription ID' })
@Get('super-admin/shops/subscription/:subscriptionId')
findOneBySubscriptionId(@Param('subscriptionId') subscriptionId: string) {
return this.restaurantsService.findOneBySubscriptionId(subscriptionId);
return this.shopService.findOneBySubscriptionId(subscriptionId);
}
@UseGuards(SuperAdminAuthGuard)
@@ -96,7 +96,7 @@ export class RestaurantsController {
@ApiParam({ name: 'id', required: true, description: 'Shop ID' })
@Get('super-admin/shops/:id')
findOneById(@Param('id') id: string) {
return this.restaurantsService.findOne(id);
return this.shopService.findOne(id);
}
@UseGuards(SuperAdminAuthGuard)
@@ -106,20 +106,20 @@ export class RestaurantsController {
@ApiBody({ type: UpdateRestaurantDto })
@Patch('super-admin/shops/:id')
updateRestaurant(@Param('id') id: string, @Body() updateRestaurantDto: UpdateRestaurantDto) {
return this.restaurantsService.update(id, updateRestaurantDto);
return this.shopService.update(id, updateRestaurantDto);
}
@UseGuards(SuperAdminAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Upgrade subscription for a shop' })
@ApiOperation({ summary: 'Update subscription end date for a shop' })
@ApiParam({ name: 'subscriptionId', required: true, description: 'Subscription ID' })
@ApiBody({ type: UpgradeSubscriptionDto })
@Patch('super-admin/shops/subscription/:subscriptionId/upgrade')
upgradeSubscription(
@ApiBody({ type: UpdateSubscriptionDto })
@Patch('super-admin/shops/subscription/:subscriptionId/update')
updateSubscription(
@Param('subscriptionId') subscriptionId: string,
@Body() upgradeSubscriptionDto: UpgradeSubscriptionDto
@Body() updateSubscriptionDto: UpdateSubscriptionDto
) {
return this.restaurantsService.upgradeSubscription(subscriptionId, upgradeSubscriptionDto);
return this.shopService.updateSubscription(subscriptionId, updateSubscriptionDto);
}
@UseGuards(SuperAdminAuthGuard)
@@ -128,7 +128,7 @@ export class RestaurantsController {
@ApiParam({ name: 'id', required: true, description: 'Shop ID' })
@Delete('super-admin/shops/:id/hard-delete')
hardDeleteRestaurant(@Param('id') id: string) {
return this.restaurantsService.hardDeleteRestaurant(id);
return this.shopService.hardDeleteRestaurant(id);
}
}
+2 -2
View File
@@ -4,8 +4,8 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { Shop } from '../entities/shop.entity';
@Injectable()
export class RestaurantCrone {
private readonly logger = new Logger(RestaurantCrone.name);
export class ShopCrone {
private readonly logger = new Logger(ShopCrone.name);
constructor(private readonly em: EntityManager) { }
+1 -5
View File
@@ -1,8 +1,7 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsNumber, IsDate, IsEnum, IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';
import { PlanEnum } from '../interface/plan.interface';
export class CreateRestaurantDto {
@ApiProperty({ example: 'کافه ژیوان', description: 'نام رستوران' })
@IsString()
@@ -28,9 +27,6 @@ export class CreateRestaurantDto {
@IsString()
phone: string;
@ApiProperty({ example: 'base', enum: PlanEnum, description: 'پلن رستوران' })
@IsEnum(PlanEnum)
plan!: PlanEnum;
@ApiProperty({ example: 'sub_1234567890', description: 'شناسه اشتراک' })
@IsString()
-10
View File
@@ -4,12 +4,10 @@ import {
IsNumber,
Min,
IsIn,
IsEnum,
IsBoolean,
} from 'class-validator';
import { Type, Transform } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { PlanEnum } from '../interface/plan.interface';
const sortOrderOptions = ['asc', 'desc'] as const;
type SortOrder = (typeof sortOrderOptions)[number];
@@ -49,14 +47,6 @@ export class FindRestaurantsDto {
@IsBoolean()
isActive?: boolean;
@ApiPropertyOptional({
enum: PlanEnum,
description: 'Filter by plan type',
})
@IsOptional()
@IsEnum(PlanEnum)
plan?: PlanEnum;
@ApiPropertyOptional({ default: 'createdAt' })
@IsOptional()
@IsString()
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsDate } from 'class-validator';
import { Type } from 'class-transformer';
export class UpdateSubscriptionDto {
@ApiProperty({ example: '2025-12-31', description: 'New subscription end date' })
@Type(() => Date)
@IsDate()
subscriptionEndDate!: Date;
}
@@ -1,13 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsDate, IsEnum } from 'class-validator';
import { IsDate } from 'class-validator';
import { Type } from 'class-transformer';
import { PlanEnum } from '../interface/plan.interface';
export class UpgradeSubscriptionDto {
@ApiProperty({ example: 'premium', enum: PlanEnum, description: 'New plan for the subscription' })
@IsEnum(PlanEnum)
newPlan!: PlanEnum;
export class UpdateSubscriptionDto {
@ApiProperty({ example: '2025-12-31', description: 'New subscription end date' })
@Type(() => Date)
@IsDate()
@@ -1,5 +1,5 @@
import { Entity, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
@Entity({ tableName: 'schedules' })
export class Schedule extends BaseEntity {
+1 -4
View File
@@ -1,8 +1,7 @@
import { Collection, Entity, Enum, Index, OneToMany, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Delivery } from '../../delivery/entities/delivery.entity';
import { PlanEnum } from '../interface/plan.interface';
@Entity({ tableName: 'shops' })
@Index({ properties: ['isActive'] })
@Index({ properties: ['slug', 'isActive'] })
@@ -97,8 +96,6 @@ export class Shop extends BaseEntity {
referrerScore: string;
} | null = null;
@Enum(() => PlanEnum)
plan: PlanEnum = PlanEnum.Base;
@Property({unique: true,nullable: true})
subscriptionId?: string;
@@ -1,4 +0,0 @@
export enum PlanEnum {
Base = 'base',
Premium = 'premium',
}
@@ -6,7 +6,7 @@ import { ScheduleRepository } from '../repositories/schedule.repository';
import { CreateScheduleDto } from '../dto/create-schedule.dto';
import { UpdateScheduleDto } from '../dto/update-schedule.dto';
import { FindSchedulesDto } from '../dto/find-schedules.dto';
import { shopRepository } from '../repositories/rest.repository';
import { ShopRepository } from '../repositories/rest.repository';
import { Shop } from '../entities/shop.entity';
import { RestMessage } from 'src/common/enums/message.enum';
// import { RestMessage } from 'src/common/enums/message.enum';
@@ -15,7 +15,7 @@ import { RestMessage } from 'src/common/enums/message.enum';
export class ScheduleService {
constructor(
private readonly scheduleRepository: ScheduleRepository,
private readonly restRepository: shopRepository,
private readonly restRepository: ShopRepository,
private readonly em: EntityManager,
) { }

Some files were not shown because too many files have changed in this diff Show More