This commit is contained in:
2026-02-10 10:15:35 +03:30
parent e079527687
commit c060be3069
65 changed files with 461 additions and 461 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
export { UserId } from './user-id.decorator'; export { UserId } from './user-id.decorator';
export { AdminId } from './admin-id.decorator'; export { AdminId } from './admin-id.decorator';
export { ShopId, RestId } from './rest-id.decorator'; export { ShopId, RestId } from './shop-id.decorator';
export { Permissions, PERMISSIONS_KEY } from './permissions.decorator'; export { Permissions, PERMISSIONS_KEY } from './permissions.decorator';
export { RateLimit } from './rate-limit.decorator'; export { RateLimit } from './rate-limit.decorator';
+1 -1
View File
@@ -1,7 +1,7 @@
import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
import type { Request } from 'express'; import type { Request } from 'express';
export const RestSlug = createParamDecorator((data: unknown, ctx: ExecutionContext): string => { export const ShopSlug = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest<Request & { slug?: string }>(); const request = ctx.switchToHttp().getRequest<Request & { slug?: string }>();
return request.slug || ''; return request.slug || '';
}); });
@@ -10,7 +10,7 @@ import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { Admin } from '../entities/admin.entity'; import { Admin } from '../entities/admin.entity';
import { Permission } from 'src/common/enums/permission.enum'; import { Permission } from 'src/common/enums/permission.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permissions } from 'src/common/decorators/permissions.decorator';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto'; import { CreateMyShopAdminDto } from '../dto/create-shop-admin.dto';
@ApiBearerAuth() @ApiBearerAuth()
@ApiTags('admin') @ApiTags('admin')
@@ -22,8 +22,8 @@ export class AdminController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Permissions(Permission.MANAGE_ADMINS) @Permissions(Permission.MANAGE_ADMINS)
@ApiOperation({ summary: 'admin' }) @ApiOperation({ summary: 'admin' })
async getAll(@ShopId() restId: string) { async getAll(@ShopId() shopId: string) {
const admin = await this.adminService.findAllByRestaurantId(restId); const admin = await this.adminService.findAllByShopId(shopId);
return admin; return admin;
} }
@@ -31,8 +31,8 @@ export class AdminController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Permissions(Permission.MANAGE_ADMINS) @Permissions(Permission.MANAGE_ADMINS)
@ApiOperation({ summary: 'admin' }) @ApiOperation({ summary: 'admin' })
async getMe(@AdminId() adminId: string, @ShopId() restId: string) { async getMe(@AdminId() adminId: string, @ShopId() shopId: string) {
const admin = await this.adminService.findById(adminId, restId); const admin = await this.adminService.findById(adminId, shopId);
return admin; return admin;
} }
@@ -42,8 +42,8 @@ export class AdminController {
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create a new admin' }) @ApiOperation({ summary: 'Create a new admin' })
@ApiBody({ type: CreateAdminDto }) @ApiBody({ type: CreateAdminDto })
async create(@Body() dto: CreateAdminDto, @ShopId() restId: string) { async create(@Body() dto: CreateAdminDto, @ShopId() shopId: string) {
const admin = await this.adminService.createAdminForMyRestaurant(restId, dto); const admin = await this.adminService.createAdminForShop(shopId, dto);
return admin; return admin;
} }
@@ -53,8 +53,8 @@ export class AdminController {
@ApiOperation({ summary: 'Update an admin' }) @ApiOperation({ summary: 'Update an admin' })
@ApiParam({ name: 'adminId', description: 'Admin ID' }) @ApiParam({ name: 'adminId', description: 'Admin ID' })
@ApiBody({ type: UpdateAdminDto }) @ApiBody({ type: UpdateAdminDto })
update(@Param('adminId') adminId: string, @Body() dto: UpdateAdminDto, @ShopId() restId: string): Promise<Admin> { update(@Param('adminId') adminId: string, @Body() dto: UpdateAdminDto, @ShopId() shopId: string): Promise<Admin> {
return this.adminService.update(adminId, restId, dto); return this.adminService.update(adminId, shopId, dto);
} }
@Get('admin/admins/:adminId') @Get('admin/admins/:adminId')
@@ -62,8 +62,8 @@ export class AdminController {
@Permissions(Permission.MANAGE_ADMINS) @Permissions(Permission.MANAGE_ADMINS)
@ApiOperation({ summary: 'Get an admin by ID' }) @ApiOperation({ summary: 'Get an admin by ID' })
@ApiParam({ name: 'id', description: 'Admin ID' }) @ApiParam({ name: 'id', description: 'Admin ID' })
getById(@Param('adminId') adminId: string, @ShopId() restId: string): Promise<Admin | null> { getById(@Param('adminId') adminId: string, @ShopId() shopId: string): Promise<Admin | null> {
return this.adminService.findById(adminId, restId); return this.adminService.findById(adminId, shopId);
} }
@Delete('admin/admins/:adminId') @Delete('admin/admins/:adminId')
@@ -71,40 +71,40 @@ export class AdminController {
@Permissions(Permission.MANAGE_ADMINS) @Permissions(Permission.MANAGE_ADMINS)
@ApiOperation({ summary: 'Delete an admin by ID' }) @ApiOperation({ summary: 'Delete an admin by ID' })
@ApiParam({ name: 'id', description: 'Admin ID' }) @ApiParam({ name: 'id', description: 'Admin ID' })
deleteById(@Param('adminId') adminId: string, @ShopId() restId: string): Promise<void> { deleteById(@Param('adminId') adminId: string, @ShopId() shopId: string): Promise<void> {
return this.adminService.remove(adminId, restId); return this.adminService.remove(adminId, shopId);
} }
/** Super Admin Endpoints */ /** Super Admin Endpoints */
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Get('super-admin/shops/:restaurantId/admins') @Get('super-admin/shops/:shopId/admins')
@ApiOperation({ summary: 'Get admins for a specific shop (Super Admin only)' }) @ApiOperation({ summary: 'Get admins for a specific shop (Super Admin only)' })
@ApiParam({ name: 'restaurantId', description: 'Shop ID' }) @ApiParam({ name: 'shopId', description: 'Shop ID' })
getAdminForRestaurant(@Param('restaurantId') restaurantId: string): Promise<Admin[]> { getAdminForRestaurant(@Param('shopId') shopId: string): Promise<Admin[]> {
return this.adminService.getAdminsForRestaurantBySuperAdmin(restaurantId); return this.adminService.getShopAdminsBySuperAdmin(shopId);
} }
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Post('super-admin/shops/:restaurantId/admins') @Post('super-admin/shops/:shopId/admins')
@ApiOperation({ summary: 'Create admin for a specific shop (Super Admin only)' }) @ApiOperation({ summary: 'Create admin for a specific shop (Super Admin only)' })
@ApiParam({ name: 'restaurantId', description: 'Shop ID' }) @ApiParam({ name: 'shopId', description: 'Shop ID' })
@ApiBody({ type: CreateMyRestaurantAdminDto }) @ApiBody({ type: CreateMyShopAdminDto })
createAdminForRestaurant( createAdminForRestaurant(
@Param('restaurantId') restaurantId: string, @Param('shopId') shopId: string,
@Body() dto: CreateMyRestaurantAdminDto, @Body() dto: CreateMyShopAdminDto,
): Promise<Admin> { ): Promise<Admin> {
return this.adminService.createAdminForRestaurantBySuperAdmin(restaurantId, dto); return this.adminService.createAdminForShopBySuperAdmin(shopId, dto);
} }
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Delete('super-admin/shops/:restaurantId/admins/:adminId') @Delete('super-admin/shops/:shopId/admins/:adminId')
@ApiOperation({ summary: 'Revoke admin role from a shop (Super Admin only)' }) @ApiOperation({ summary: 'Revoke admin role from a shop (Super Admin only)' })
@ApiParam({ name: 'restaurantId', description: 'Shop ID' }) @ApiParam({ name: 'shopId', description: 'Shop ID' })
@ApiParam({ name: 'adminId', description: 'Admin ID' }) @ApiParam({ name: 'adminId', description: 'Admin ID' })
revokeAdminFromRestaurant( revokeAdminFromRestaurant(
@Param('restaurantId') restaurantId: string, @Param('shopId') shopId: string,
@Param('adminId') adminId: string, @Param('adminId') adminId: string,
): Promise<void> { ): Promise<void> {
return this.adminService.remove(adminId, restaurantId); return this.adminService.remove(adminId, shopId);
} }
} }
@@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class CreateMyRestaurantAdminDto { export class CreateMyShopAdminDto {
@ApiProperty({ description: 'Mobile phone number' }) @ApiProperty({ description: 'Mobile phone number' })
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
+17 -17
View File
@@ -6,7 +6,7 @@ import { Role } from '../../roles/entities/role.entity';
import { Shop } from '../../shops/entities/shop.entity'; import { Shop } from '../../shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { CacheService } from '../../utils/cache.service'; import { CacheService } from '../../utils/cache.service';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto'; import { CreateMyShopAdminDto } from '../dto/create-shop-admin.dto';
import { UpdateAdminDto } from '../dto/update-admin.dto'; import { UpdateAdminDto } from '../dto/update-admin.dto';
import { AdminRole } from '../entities/adminRole.entity'; import { AdminRole } from '../entities/adminRole.entity';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../utils/phone.util';
@@ -27,19 +27,19 @@ export class AdminService {
return this.adminRepository.findOne({ phone: normalizedPhone }); return this.adminRepository.findOne({ phone: normalizedPhone });
} }
async findById(adminId: string, restId: string): Promise<Admin | null> { async findById(adminId: string, shopId: string): Promise<Admin | null> {
const admin = await this.adminRepository.findOne( const admin = await this.adminRepository.findOne(
{ id: adminId, roles: { shop: { id: restId } } }, { id: adminId, roles: { shop: { id: shopId } } },
{ populate: ['roles', 'roles.role'] }, { populate: ['roles', 'roles.role'] },
); );
return admin; return admin;
} }
async findAllByRestaurantId(restId: string): Promise<Admin[]> { async findAllByShopId(shopId: string): Promise<Admin[]> {
return this.adminRepository.find({ roles: { shop: { id: restId } } }, { populate: ['roles', 'roles.role'] }); return this.adminRepository.find({ roles: { shop: { id: shopId } } }, { populate: ['roles', 'roles.role'] });
} }
async createAdminForMyRestaurant(restId: string, dto: CreateMyRestaurantAdminDto) { async createAdminForShop(shopId: string, dto: CreateMyShopAdminDto) {
const { phone, firstName, lastName, roleId } = dto; const { phone, firstName, lastName, roleId } = dto;
const normalizedPhone = normalizePhone(phone); const normalizedPhone = normalizePhone(phone);
let admin: Admin | null = null; let admin: Admin | null = null;
@@ -57,7 +57,7 @@ export class AdminService {
const role = await this.em.findOne(Role, { id: roleId, isSystem: false }); const role = await this.em.findOne(Role, { id: roleId, isSystem: false });
if (!role) throw new NotFoundException('Role not found'); if (!role) throw new NotFoundException('Role not found');
const shop = await this.em.findOne(Shop, { id: restId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException('Shop not found');
let adminRole = await this.adminRoleRepository.findOne({ let adminRole = await this.adminRoleRepository.findOne({
@@ -79,7 +79,7 @@ export class AdminService {
return admin; return admin;
} }
async createAdminForRestaurantBySuperAdmin(restId: string, dto: CreateMyRestaurantAdminDto) { async createAdminForShopBySuperAdmin(shopId: string, dto: CreateMyShopAdminDto) {
const { phone, firstName, lastName, roleId } = dto; const { phone, firstName, lastName, roleId } = dto;
const normalizedPhone = normalizePhone(phone); const normalizedPhone = normalizePhone(phone);
let admin: Admin | null = null; let admin: Admin | null = null;
@@ -97,7 +97,7 @@ export class AdminService {
const role = await this.em.findOne(Role, { id: roleId }); const role = await this.em.findOne(Role, { id: roleId });
if (!role) throw new NotFoundException('Role* not found' + roleId); if (!role) throw new NotFoundException('Role* not found' + roleId);
const shop = await this.em.findOne(Shop, { id: restId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException('Shop not found');
let adminRole = await this.adminRoleRepository.findOne({ let adminRole = await this.adminRoleRepository.findOne({
@@ -119,16 +119,16 @@ export class AdminService {
return admin; return admin;
} }
async getAdminsForRestaurantBySuperAdmin(restId: string): Promise<Admin[]> { async getShopAdminsBySuperAdmin(shopId: string): Promise<Admin[]> {
const shop = await this.em.findOne(Shop, { id: restId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException('Shop not found');
return this.adminRepository.find({ roles: { shop: shop } }, { populate: ['roles', 'roles.role'] }); return this.adminRepository.find({ roles: { shop: shop } }, { populate: ['roles', 'roles.role'] });
} }
async update(adminId: string, restId: string, dto: UpdateAdminDto): Promise<Admin> { async update(adminId: string, shopId: string, dto: UpdateAdminDto): Promise<Admin> {
const admin = await this.adminRepository.findOne( const admin = await this.adminRepository.findOne(
{ id: adminId, roles: { shop: { id: restId } } }, { id: adminId, roles: { shop: { id: shopId } } },
{ populate: ['roles', 'roles.role', 'roles.shop'] }, { populate: ['roles', 'roles.role', 'roles.shop'] },
); );
@@ -164,14 +164,14 @@ export class AdminService {
// Find existing AdminRole for this admin and shop // Find existing AdminRole for this admin and shop
const existingAdminRole = await this.em.findOne(AdminRole, { const existingAdminRole = await this.em.findOne(AdminRole, {
admin: { id: adminId }, admin: { id: adminId },
shop: { id: restId }, shop: { id: shopId },
}); });
if (existingAdminRole) { if (existingAdminRole) {
// Update existing role // Update existing role
existingAdminRole.role = role; existingAdminRole.role = role;
} else { } else {
const shop = await this.em.findOne(Shop, { id: restId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException('Shop not found');
// Create new AdminRole // Create new AdminRole
const newAdminRole = this.em.create(AdminRole, { const newAdminRole = this.em.create(AdminRole, {
@@ -187,8 +187,8 @@ export class AdminService {
return admin; return admin;
} }
async remove(adminId: string, restId: string): Promise<void> { async remove(adminId: string, shopId: string): Promise<void> {
const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, shop: { id: restId } }); const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, shop: { id: shopId } });
if (!adminRole) { if (!adminRole) {
throw new NotFoundException('Admin role not found'); throw new NotFoundException('Admin role not found');
} }
@@ -10,10 +10,10 @@ export class AdminRepository extends EntityRepository<Admin> {
super(em, Admin); super(em, Admin);
} }
async findAdminsWithPermission(restaurantId: string, permission: string): Promise<Admin[]> { async findAdminsWithPermission(shopId: string, permission: string): Promise<Admin[]> {
const admins = await this.em.find( const admins = await this.em.find(
Admin, Admin,
{ roles: { shop: { id: restaurantId }, role: { permissions: { name: permission } } } }, { roles: { shop: { id: shopId }, role: { permissions: { name: permission } } } },
{ populate: ['roles', 'roles.role', 'roles.role.permissions'] }, { populate: ['roles', 'roles.role', 'roles.role.permissions'] },
); );
return admins; return admins;
@@ -8,7 +8,7 @@ export enum RefreshTokenType {
} }
@Entity({ tableName: 'refreshtokens' }) @Entity({ tableName: 'refreshtokens' })
@Index({ properties: ['ownerId', 'restId', 'type'] }) @Index({ properties: ['ownerId', 'shopId', 'type'] })
@Index({ properties: ['hashedToken'] }) @Index({ properties: ['hashedToken'] })
@Index({ properties: ['expiresAt'] }) @Index({ properties: ['expiresAt'] })
export class RefreshToken extends BaseEntity { export class RefreshToken extends BaseEntity {
@@ -19,7 +19,7 @@ export class RefreshToken extends BaseEntity {
ownerId!: string; ownerId!: string;
@Property() @Property()
restId!: string; shopId!: string;
@Enum(() => RefreshTokenType) @Enum(() => RefreshTokenType)
type!: RefreshTokenType; type!: RefreshTokenType;
+1 -1
View File
@@ -82,7 +82,7 @@ export class AdminAuthGuard implements CanActivate {
if (!hasPermission) { if (!hasPermission) {
this.logger.warn('Insufficient permissions', { this.logger.warn('Insufficient permissions', {
adminId: payload.adminId, adminId: payload.adminId,
restId: payload.shopId, shopId: payload.shopId,
required: requiredPermissions, required: requiredPermissions,
has: adminPermission, has: adminPermission,
}); });
+2 -2
View File
@@ -19,7 +19,7 @@ export class OptionalAuthGuard implements CanActivate {
private readonly jwtService: JwtService, private readonly jwtService: JwtService,
@Inject(ConfigService) @Inject(ConfigService)
private readonly configService: ConfigService, private readonly configService: ConfigService,
) {} ) { }
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<UserOptionalAuthRequest>(); const request = context.switchToHttp().getRequest<UserOptionalAuthRequest>();
@@ -73,7 +73,7 @@ export class OptionalAuthGuard implements CanActivate {
this.logger.debug('Token verification failed in OptionalAuthGuard', { this.logger.debug('Token verification failed in OptionalAuthGuard', {
error: err instanceof Error ? err.message : 'Unknown error', error: err instanceof Error ? err.message : 'Unknown error',
}); });
// Set restId from slug // Set shopId from slug
request['slug'] = slug; request['slug'] = slug;
+16 -16
View File
@@ -31,12 +31,12 @@ export class AuthService {
this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240; this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240;
} }
private userOtpKey(restaurantSlug: string, phone: string) { private userOtpKey(shopSlug: string, phone: string) {
return `otp:${restaurantSlug}:${phone}`; return `otp:${shopSlug}:${phone}`;
} }
private adminOtpKey(restaurantSlug: string, phone: string) { private adminOtpKey(shopSlug: string, phone: string) {
return `otp-admin:${restaurantSlug}:${phone}`; return `otp-admin:${shopSlug}:${phone}`;
} }
async requestOtp(dto: RequestOtpDto, isAdmin: boolean) { async requestOtp(dto: RequestOtpDto, isAdmin: boolean) {
@@ -62,15 +62,15 @@ export class AuthService {
const user = await this.userService.findOrCreateByPhone(normalizedPhone); const user = await this.userService.findOrCreateByPhone(normalizedPhone);
const rest = await this.shopRepository.findOne({ slug }); const shop = await this.shopRepository.findOne({ slug });
if (!rest) { if (!shop) {
throw new BadRequestException(RestMessage.NOT_FOUND); throw new BadRequestException(RestMessage.NOT_FOUND);
} }
const tokens = await this.tokensService.generateAccessAndRefreshToken(user.id, rest.id, false, slug); const tokens = await this.tokensService.generateAccessAndRefreshToken(user.id, shop.id, false, slug);
const userResponse = UserLoginTransformer.transform(user, rest); const userResponse = UserLoginTransformer.transform(user, shop);
return { tokens, user: userResponse }; return { tokens, user: userResponse };
} }
@@ -87,15 +87,15 @@ export class AuthService {
const admin = await this.adminService.findByPhoneAndShopSlug(normalizedPhone, slug); const admin = await this.adminService.findByPhoneAndShopSlug(normalizedPhone, slug);
const rest = await this.shopRepository.findOne({ slug }); const shop = await this.shopRepository.findOne({ slug });
if (!rest) { if (!shop) {
throw new BadRequestException(RestMessage.NOT_FOUND); throw new BadRequestException(RestMessage.NOT_FOUND);
} }
const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, rest.id, true, slug); const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, shop.id, true, slug);
const adminResponse = await AdminLoginTransformer.transform(admin, rest); const adminResponse = await AdminLoginTransformer.transform(admin, shop);
return { tokens, admin: adminResponse }; return { tokens, admin: adminResponse };
} }
@@ -111,15 +111,15 @@ export class AuthService {
throw new BadRequestException(AuthMessage.ADMIN_NOT_FOUND); throw new BadRequestException(AuthMessage.ADMIN_NOT_FOUND);
} }
const rest = await this.shopRepository.findOne({ slug }); const shop = await this.shopRepository.findOne({ slug });
if (!rest) { if (!shop) {
throw new BadRequestException(RestMessage.NOT_FOUND); throw new BadRequestException(RestMessage.NOT_FOUND);
} }
const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, rest.id, true, slug); const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, shop.id, true, slug);
const adminResponse = await AdminLoginTransformer.transform(admin, rest); const adminResponse = await AdminLoginTransformer.transform(admin, shop);
return { tokens, admin: adminResponse }; return { tokens, admin: adminResponse };
} }
+4 -4
View File
@@ -56,7 +56,7 @@ export class TokensService {
async storeRefreshToken( async storeRefreshToken(
ownerId: string, ownerId: string,
restId: string, shopId: string,
refreshToken: string, refreshToken: string,
type: RefreshTokenType, type: RefreshTokenType,
em?: EntityManager, em?: EntityManager,
@@ -70,7 +70,7 @@ export class TokensService {
const token = entityManager.create(RefreshToken, { const token = entityManager.create(RefreshToken, {
hashedToken, hashedToken,
ownerId, ownerId,
restId, shopId,
type, type,
expiresAt, expiresAt,
}); });
@@ -106,11 +106,11 @@ export class TokensService {
// Store token data before removal // Store token data before removal
const ownerId = token.ownerId; const ownerId = token.ownerId;
const restId = token.restId; const shopId = token.shopId;
const isAdmin = token.type === RefreshTokenType.ADMIN; const isAdmin = token.type === RefreshTokenType.ADMIN;
// Verify shop still exists // Verify shop still exists
const shop = await em.findOne(Shop, { id: restId }); const shop = await em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN); throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN);
} }
@@ -5,7 +5,7 @@ import { ApplyCouponDto } from '../dto/apply-coupon.dto';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } from '@nestjs/swagger';
import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { UserId } from 'src/common/decorators/user-id.decorator'; import { UserId } from 'src/common/decorators/user-id.decorator';
import { ShopId } from 'src/common/decorators/rest-id.decorator'; import { ShopId } from 'src/common/decorators/shop-id.decorator';
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
import { API_HEADER_SLUG } from 'src/common/constants/index'; import { API_HEADER_SLUG } from 'src/common/constants/index';
@@ -103,8 +103,8 @@ export class CartCalculationService {
/** /**
* Calculate tax * Calculate tax
*/ */
async calculateTax(restaurantId: string, amountAfterDiscounts: number): Promise<number> { async calculateTax(shopId: string, amountAfterDiscounts: number): Promise<number> {
const shop = await this.em.findOne(Shop, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: shopId });
const vat = shop?.vat ? Number(shop.vat) : 0; const vat = shop?.vat ? Number(shop.vat) : 0;
if (!vat || vat <= 0) return 0; if (!vat || vat <= 0) return 0;
return (Math.max(0, amountAfterDiscounts) * vat) / 100; return (Math.max(0, amountAfterDiscounts) * vat) / 100;
@@ -20,8 +20,8 @@ import { OptionalAuthGuard } from 'src/modules/auth/guards/optinalAuth.guard';
import { API_HEADER_SLUG } from 'src/common/constants'; import { API_HEADER_SLUG } from 'src/common/constants';
import { Permission } from 'src/common/enums/permission.enum'; import { Permission } from 'src/common/enums/permission.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permissions } from 'src/common/decorators/permissions.decorator';
import { ShopId } from 'src/common/decorators/rest-id.decorator'; import { ShopId } from 'src/common/decorators/shop-id.decorator';
import { RestSlug } from 'src/common/decorators/rest-slug.decorator'; import { ShopSlug } from 'src/common/decorators/rest-slug.decorator';
@ApiTags('contact') @ApiTags('contact')
@Controller() @Controller()
@@ -32,8 +32,8 @@ export class ContactController {
@Post('public/contact') @Post('public/contact')
@ApiOperation({ summary: 'Create a new contact (public endpoint)' }) @ApiOperation({ summary: 'Create a new contact (public endpoint)' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
async create(@Body() createContactDto: CreateContactDto, @RestSlug() slug: string, @ShopId() restId?: string) { async create(@Body() createContactDto: CreateContactDto, @ShopSlug() slug: string, @ShopId() shopId?: string) {
return this.contactService.create(createContactDto, restId, slug); return this.contactService.create(createContactDto, shopId, slug);
} }
@@ -48,8 +48,8 @@ export class ContactController {
@ApiQuery({ name: 'orderBy', required: false, type: String }) @ApiQuery({ name: 'orderBy', required: false, type: String })
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] }) @ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
@ApiQuery({ name: 'status', required: false, enum: ['new', 'seen'] }) @ApiQuery({ name: 'status', required: false, enum: ['new', 'seen'] })
async findAllPaginated(@ShopId() restId: string, @Query() dto: FindContactsDto) { async findAllPaginated(@ShopId() shopId: string, @Query() dto: FindContactsDto) {
return this.contactService.findAllPaginatedAdmin(dto, restId); return this.contactService.findAllPaginatedAdmin(dto, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -58,8 +58,8 @@ export class ContactController {
@Get('admin/contact/:id') @Get('admin/contact/:id')
@ApiOperation({ summary: 'Get contact details by ID (admin only)' }) @ApiOperation({ summary: 'Get contact details by ID (admin only)' })
@ApiParam({ name: 'id', description: 'Contact ID' }) @ApiParam({ name: 'id', description: 'Contact ID' })
async findOne(@ShopId() restId: string, @Param('id') id: string) { async findOne(@ShopId() shopId: string, @Param('id') id: string) {
return this.contactService.findOne(id, restId); return this.contactService.findOne(id, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -68,8 +68,8 @@ export class ContactController {
@Patch('admin/contact/:id/status') @Patch('admin/contact/:id/status')
@ApiOperation({ summary: 'Update contact status (admin only)' }) @ApiOperation({ summary: 'Update contact status (admin only)' })
@ApiParam({ name: 'id', description: 'Contact ID' }) @ApiParam({ name: 'id', description: 'Contact ID' })
async updateStatus(@ShopId() restId: string, @Param('id') id: string, @Body() dto: UpdateContactStatusDto) { async updateStatus(@ShopId() shopId: string, @Param('id') id: string, @Body() dto: UpdateContactStatusDto) {
return this.contactService.updateStatus(id, dto, restId); return this.contactService.updateStatus(id, dto, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -78,8 +78,8 @@ export class ContactController {
@Delete('admin/contact/:id') @Delete('admin/contact/:id')
@ApiOperation({ summary: 'Hard delete a contact (admin only)' }) @ApiOperation({ summary: 'Hard delete a contact (admin only)' })
@ApiParam({ name: 'id', description: 'Contact ID' }) @ApiParam({ name: 'id', description: 'Contact ID' })
async remove(@ShopId() restId: string, @Param('id') id: string) { async remove(@ShopId() shopId: string, @Param('id') id: string) {
await this.contactService.remove(id, restId); await this.contactService.remove(id, shopId);
} }
/*** Super Admin ***/ /*** Super Admin ***/
@@ -17,18 +17,18 @@ export class ContactService {
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
async create(dto: CreateContactDto, restId?: string, slug?: string): Promise<Contact> { async create(dto: CreateContactDto, shopId?: string, slug?: string): Promise<Contact> {
let shop: Shop | null = null; let shop: Shop | null = null;
if ((restId || slug) && dto.scope === ContactScope.SHOP) { if ((shopId || slug) && dto.scope === ContactScope.SHOP) {
shop = await this.em.findOne(Shop, { shop = await this.em.findOne(Shop, {
...(restId ? { id: restId } : {}) ...(shopId ? { id: shopId } : {})
, ...(slug ? { slug: slug } : {}) , ...(slug ? { slug: slug } : {})
}); });
if (!shop) { if (!shop) {
throw new NotFoundException(`Shop with ID ${restId} not found`); throw new NotFoundException(`Shop with ID ${shopId} not found`);
} }
} }
@@ -41,7 +41,7 @@ export class ContactService {
return contact; return contact;
} }
async findAllPaginatedAdmin(dto: FindContactsDto, restaurantId?: string): Promise<PaginatedResult<Contact>> { async findAllPaginatedAdmin(dto: FindContactsDto, shopId?: string): Promise<PaginatedResult<Contact>> {
return this.contactRepository.findAllPaginated({ return this.contactRepository.findAllPaginated({
page: dto.page, page: dto.page,
limit: dto.limit, limit: dto.limit,
@@ -50,7 +50,7 @@ export class ContactService {
order: dto.order, order: dto.order,
status: dto.status, status: dto.status,
scope: ContactScope.SHOP, scope: ContactScope.SHOP,
restaurantId shopId
}); });
} }
async findAllPaginatedSuper(dto: FindContactsDto): Promise<PaginatedResult<Contact>> { async findAllPaginatedSuper(dto: FindContactsDto): Promise<PaginatedResult<Contact>> {
@@ -65,10 +65,10 @@ export class ContactService {
}); });
} }
async findOne(id: string, restaurantId?: string): Promise<Contact> { async findOne(id: string, shopId?: string): Promise<Contact> {
const where: any = { id }; const where: any = { id };
if (restaurantId) { if (shopId) {
where.shop = restaurantId; where.shop = shopId;
} }
const contact = await this.contactRepository.findOne(where); const contact = await this.contactRepository.findOne(where);
@@ -78,10 +78,10 @@ export class ContactService {
return contact; return contact;
} }
async updateStatus(id: string, dto: UpdateContactStatusDto, restaurantId?: string): Promise<Contact> { async updateStatus(id: string, dto: UpdateContactStatusDto, shopId?: string): Promise<Contact> {
const where: any = { id }; const where: any = { id };
if (restaurantId) { if (shopId) {
where.shop = restaurantId; where.shop = shopId;
} }
const contact = await this.contactRepository.findOne(where); const contact = await this.contactRepository.findOne(where);
@@ -93,10 +93,10 @@ export class ContactService {
return contact; return contact;
} }
async remove(id: string, restaurantId?: string): Promise<void> { async remove(id: string, shopId?: string): Promise<void> {
const where: any = { id }; const where: any = { id };
if (restaurantId) { if (shopId) {
where.shop = restaurantId; where.shop = shopId;
} }
const contact = await this.contactRepository.findOne(where); const contact = await this.contactRepository.findOne(where);
@@ -13,7 +13,7 @@ type FindContactsOpts = {
order?: 'asc' | 'desc'; order?: 'asc' | 'desc';
status?: ContactStatusEnum; status?: ContactStatusEnum;
scope?: ContactScope; scope?: ContactScope;
restaurantId?: string; shopId?: string;
}; };
@Injectable() @Injectable()
@@ -27,7 +27,7 @@ export class ContactRepository extends EntityRepository<Contact> {
* Supports: search (subject/content/phone), status, ordering. * Supports: search (subject/content/phone), status, ordering.
*/ */
async findAllPaginated(opts: FindContactsOpts = {}): Promise<PaginatedResult<Contact>> { async findAllPaginated(opts: FindContactsOpts = {}): Promise<PaginatedResult<Contact>> {
const { page = 1, limit = 10, search, scope, orderBy = 'createdAt', order = 'desc', status, restaurantId } = opts; const { page = 1, limit = 10, search, scope, orderBy = 'createdAt', order = 'desc', status, shopId } = opts;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
@@ -39,8 +39,8 @@ export class ContactRepository extends EntityRepository<Contact> {
if (scope) { if (scope) {
where.scope = scope; where.scope = scope;
} }
if (restaurantId) { if (shopId) {
where.shop = restaurantId; where.shop = shopId;
} }
if (search) { if (search) {
@@ -25,12 +25,12 @@ export class CouponRepository extends EntityRepository<Coupon> {
* Find coupons with pagination and optional filters. * Find coupons with pagination and optional filters.
* Supports: search (code/name), type, isActive, ordering. * Supports: search (code/name), type, isActive, ordering.
*/ */
async findAllPaginated(restId: string, opts: FindCouponsOpts = {}): Promise<PaginatedResult<Coupon>> { async findAllPaginated(shopId: string, opts: FindCouponsOpts = {}): Promise<PaginatedResult<Coupon>> {
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', type, isActive } = opts; const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', type, isActive } = opts;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const where: FilterQuery<Coupon> = { shop: { id: restId } }; const where: FilterQuery<Coupon> = { shop: { id: shopId } };
if (typeof isActive === 'boolean') { if (typeof isActive === 'boolean') {
where.isActive = isActive; where.isActive = isActive;
@@ -13,7 +13,7 @@ import {
import { DeliveryService } from '../providers/delivery.service'; import { DeliveryService } from '../providers/delivery.service';
import { CreateDeliveryDto } from '../dto/create-delivery.dto'; import { CreateDeliveryDto } from '../dto/create-delivery.dto';
import { UpdateDeliveryDto } from '../dto/update-delivery.dto'; import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
import { ShopId } from 'src/common/decorators/rest-id.decorator'; import { ShopId } from 'src/common/decorators/shop-id.decorator';
import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { Delivery } from '../entities/delivery.entity'; import { Delivery } from '../entities/delivery.entity';
@@ -31,8 +31,8 @@ export class DeliveryController {
@Get('public/delivery-methods/shop') @Get('public/delivery-methods/shop')
@ApiOperation({ summary: 'Get shop delivery methods' }) @ApiOperation({ summary: 'Get shop delivery methods' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
findAllDeliveryMethods(@ShopId() restId: string) { findAllDeliveryMethods(@ShopId() shopId: string) {
return this.deliveryService.findAllForRestaurantId(restId); return this.deliveryService.findAllForRestaurantId(shopId);
} }
/*** Admin ***/ /*** Admin ***/
@@ -42,8 +42,8 @@ export class DeliveryController {
@Post('admin/delivery-methods/shop') @Post('admin/delivery-methods/shop')
@ApiOperation({ summary: 'Create a delivery method for a shop' }) @ApiOperation({ summary: 'Create a delivery method for a shop' })
@ApiBody({ type: CreateDeliveryDto }) @ApiBody({ type: CreateDeliveryDto })
create(@Body() createDto: CreateDeliveryDto, @ShopId() restId: string) { create(@Body() createDto: CreateDeliveryDto, @ShopId() shopId: string) {
return this.deliveryService.create(restId, createDto); return this.deliveryService.create(shopId, createDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -51,8 +51,8 @@ export class DeliveryController {
@Permissions(Permission.MANAGE_DELIVERY) @Permissions(Permission.MANAGE_DELIVERY)
@Get('admin/delivery-methods/shop') @Get('admin/delivery-methods/shop')
@ApiOperation({ summary: 'Get the shop delivery methods' }) @ApiOperation({ summary: 'Get the shop delivery methods' })
findRestaurantDeliveryMethods(@ShopId() restId: string) { findRestaurantDeliveryMethods(@ShopId() shopId: string) {
return this.deliveryService.findAllForRestaurantId(restId); return this.deliveryService.findAllForRestaurantId(shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -61,8 +61,8 @@ export class DeliveryController {
@Get('admin/delivery-methods/shop/:deliveryId') @Get('admin/delivery-methods/shop/:deliveryId')
@ApiOperation({ summary: 'Get a shop delivery method by delivery ID' }) @ApiOperation({ summary: 'Get a shop delivery method by delivery ID' })
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
findOne(@Param('deliveryId') deliveryId: string, @ShopId() restId: string) { findOne(@Param('deliveryId') deliveryId: string, @ShopId() shopId: string) {
return this.deliveryService.findOrFail(restId, deliveryId); return this.deliveryService.findOrFail(shopId, deliveryId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -72,8 +72,8 @@ export class DeliveryController {
@ApiOperation({ summary: 'Update a shop delivery method' }) @ApiOperation({ summary: 'Update a shop delivery method' })
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
@ApiBody({ type: UpdateDeliveryDto }) @ApiBody({ type: UpdateDeliveryDto })
update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @ShopId() restId: string) { update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @ShopId() shopId: string) {
return this.deliveryService.update(restId, deliveryId, updateDto); return this.deliveryService.update(shopId, deliveryId, updateDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -82,7 +82,7 @@ export class DeliveryController {
@Delete('admin/delivery-methods/shop/:deliveryId') @Delete('admin/delivery-methods/shop/:deliveryId')
@ApiOperation({ summary: 'Delete a shop delivery method' }) @ApiOperation({ summary: 'Delete a shop delivery method' })
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
remove(@Param('deliveryId') deliveryId: string, @ShopId() restId: string) { remove(@Param('deliveryId') deliveryId: string, @ShopId() shopId: string) {
return this.deliveryService.remove(restId, deliveryId); return this.deliveryService.remove(shopId, deliveryId);
} }
} }
@@ -16,15 +16,15 @@ export class DeliveryService {
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> { async create(shopId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
const shop = await this.shopRepository.findOne({ id: restId }); const shop = await this.shopRepository.findOne({ id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException(DeliveryMessage.SHOP_NOT_FOUND); throw new NotFoundException(DeliveryMessage.SHOP_NOT_FOUND);
} }
// Check if the delivery method with the same name already exists for this shop // Check if the delivery method with the same name already exists for this shop
const existing = await this.deliveryRepository.findOne({ const existing = await this.deliveryRepository.findOne({
shop: { id: restId }, shop: { id: shopId },
method: createDto.method, method: createDto.method,
}); });
@@ -50,14 +50,14 @@ export class DeliveryService {
return delivery; return delivery;
} }
async findAllForRestaurantId(restId: string): Promise<Delivery[]> { async findAllForRestaurantId(shopId: string): Promise<Delivery[]> {
return this.deliveryRepository.find({ shop: { id: restId } }, { orderBy: { order: 'asc' } }); return this.deliveryRepository.find({ shop: { id: shopId } }, { orderBy: { order: 'asc' } });
} }
async findOrFail(restId: string, deliveryId: string): Promise<Delivery> { async findOrFail(shopId: string, deliveryId: string): Promise<Delivery> {
const delivery = await this.deliveryRepository.findOne({ const delivery = await this.deliveryRepository.findOne({
id: deliveryId, id: deliveryId,
shop: { id: restId }, shop: { id: shopId },
}); });
if (!delivery) { if (!delivery) {
@@ -67,9 +67,9 @@ export class DeliveryService {
return delivery; return delivery;
} }
async update(restId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise<Delivery> { async update(shopId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise<Delivery> {
const delivery = await this.deliveryRepository.findOne({ const delivery = await this.deliveryRepository.findOne({
shop: { id: restId }, shop: { id: shopId },
id: deliveryId, id: deliveryId,
}); });
@@ -80,7 +80,7 @@ export class DeliveryService {
// If method is being updated, check for conflicts // If method is being updated, check for conflicts
if (updateDto.method !== undefined && updateDto.method !== delivery.method) { if (updateDto.method !== undefined && updateDto.method !== delivery.method) {
const existing = await this.deliveryRepository.findOne({ const existing = await this.deliveryRepository.findOne({
shop: { id: restId }, shop: { id: shopId },
method: updateDto.method, method: updateDto.method,
id: { $ne: deliveryId }, id: { $ne: deliveryId },
}); });
@@ -96,9 +96,9 @@ export class DeliveryService {
return delivery; return delivery;
} }
async remove(restId: string, deliveryId: string): Promise<void> { async remove(shopId: string, deliveryId: string): Promise<void> {
const delivery = await this.deliveryRepository.findOne({ const delivery = await this.deliveryRepository.findOne({
shop: { id: restId }, shop: { id: shopId },
id: deliveryId, id: deliveryId,
}); });
@@ -5,7 +5,7 @@ import { NotificationPreferenceService } from '../services/notification-preferen
import { AuthGuard } from '../../auth/guards/auth.guard'; import { AuthGuard } from '../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator'; import { UserId } from '../../../common/decorators/user-id.decorator';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard'; import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
import { ShopId } from '../../../common/decorators/rest-id.decorator'; import { ShopId } from '../../../common/decorators/shop-id.decorator';
import { UpdatePreferenceDto } from '../dto/update-preference.dto'; import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { CreatePreferenceDto } from '../dto/create-preference.dto'; import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { AdminId } from 'src/common/decorators/admin-id.decorator'; import { AdminId } from 'src/common/decorators/admin-id.decorator';
@@ -38,14 +38,14 @@ export class NotificationsController {
@ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' }) @ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' })
async getUserNotifications( async getUserNotifications(
@UserId() userId: string, @UserId() userId: string,
@ShopId() restaurantId: string, @ShopId() shopId: string,
@Query('limit') limit?: number, @Query('limit') limit?: number,
@Query('cursor') cursor?: string, @Query('cursor') cursor?: string,
@Query('status') status?: 'seen' | 'unseen', @Query('status') status?: 'seen' | 'unseen',
) { ) {
return await this.notificationService.findByUserAndRestaurant( return await this.notificationService.findByUserAndRestaurant(
userId, userId,
restaurantId, shopId,
limit ? parseInt(limit.toString(), 10) : 50, limit ? parseInt(limit.toString(), 10) : 50,
cursor, cursor,
status, status,
@@ -57,8 +57,8 @@ export class NotificationsController {
@Get('public/notifications/unseen-count') @Get('public/notifications/unseen-count')
@ApiOperation({ summary: 'Get unseen notifications count for user' }) @ApiOperation({ summary: 'Get unseen notifications count for user' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
async getUserUnseenCount(@UserId() userId: string, @ShopId() restaurantId: string) { async getUserUnseenCount(@UserId() userId: string, @ShopId() shopId: string) {
const count = await this.notificationService.countUnseenByUserAndRestaurant(userId, restaurantId); const count = await this.notificationService.countUnseenByUserAndRestaurant(userId, shopId);
return { count }; return { count };
} }
@@ -67,8 +67,8 @@ export class NotificationsController {
@Put('public/notifications/:id') @Put('public/notifications/:id')
@ApiOperation({ summary: 'Read a notification ' }) @ApiOperation({ summary: 'Read a notification ' })
@ApiParam({ name: 'id', description: 'Notification ID' }) @ApiParam({ name: 'id', description: 'Notification ID' })
async readNotificationUser(@ShopId() restaurantId: string, @Param('id') id: string, @UserId() userId: string) { async readNotificationUser(@ShopId() shopId: string, @Param('id') id: string, @UserId() userId: string) {
await this.notificationService.readNotificationAsUser(id, userId, restaurantId); await this.notificationService.readNotificationAsUser(id, userId, shopId);
return { message: NotificationMessage.READ_SUCCESS }; return { message: NotificationMessage.READ_SUCCESS };
} }
@@ -76,8 +76,8 @@ export class NotificationsController {
@ApiBearerAuth() @ApiBearerAuth()
@Put('public/notifications/read/all') @Put('public/notifications/read/all')
@ApiOperation({ summary: 'Read all notification ' }) @ApiOperation({ summary: 'Read all notification ' })
async readAllNotificationUser(@ShopId() restaurantId: string, @UserId() userId: string) { async readAllNotificationUser(@ShopId() shopId: string, @UserId() userId: string) {
await this.notificationService.readAllNotifsAsUser(userId, restaurantId); await this.notificationService.readAllNotifsAsUser(userId, shopId);
return { message: NotificationMessage.READ_SUCCESS }; return { message: NotificationMessage.READ_SUCCESS };
} }
/* ***************** Admin Endpoints ***************** */ /* ***************** Admin Endpoints ***************** */
@@ -96,21 +96,21 @@ export class NotificationsController {
@ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' }) @ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' })
async getAdminNotifications( async getAdminNotifications(
@AdminId() adminId: string, @AdminId() adminId: string,
@ShopId() restaurantId: string, @ShopId() shopId: string,
@Query('limit') limit?: number, @Query('limit') limit?: number,
@Query('cursor') cursor?: string, @Query('cursor') cursor?: string,
@Query('status') status?: 'seen' | 'unseen', @Query('status') status?: 'seen' | 'unseen',
) { ) {
const parsedLimit = limit ? parseInt(limit.toString(), 10) : 50; const parsedLimit = limit ? parseInt(limit.toString(), 10) : 50;
return await this.notificationService.findByRestaurant(restaurantId, adminId, parsedLimit, cursor, status); return await this.notificationService.findByRestaurant(shopId, adminId, parsedLimit, cursor, status);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/notifications/unseen-count') @Get('admin/notifications/unseen-count')
@ApiOperation({ summary: 'Get unseen notifications count for admin' }) @ApiOperation({ summary: 'Get unseen notifications count for admin' })
async getAdminUnseenCount(@AdminId() adminId: string, @ShopId() restaurantId: string) { async getAdminUnseenCount(@AdminId() adminId: string, @ShopId() shopId: string) {
const count = await this.notificationService.countUnseenByRestaurant(adminId, restaurantId); const count = await this.notificationService.countUnseenByRestaurant(adminId, shopId);
return { count }; return { count };
} }
@@ -119,8 +119,8 @@ export class NotificationsController {
@Put('admin/notifications/:id') @Put('admin/notifications/:id')
@ApiOperation({ summary: 'Read a notification ' }) @ApiOperation({ summary: 'Read a notification ' })
@ApiParam({ name: 'id', description: 'Notification ID' }) @ApiParam({ name: 'id', description: 'Notification ID' })
async readNotificationAdmin(@ShopId() restaurantId: string, @AdminId() adminId: string, @Param('id') id: string) { async readNotificationAdmin(@ShopId() shopId: string, @AdminId() adminId: string, @Param('id') id: string) {
await this.notificationService.readNotificationAdmin(id, adminId, restaurantId); await this.notificationService.readNotificationAdmin(id, adminId, shopId);
return { message: NotificationMessage.READ_SUCCESS }; return { message: NotificationMessage.READ_SUCCESS };
} }
@@ -128,8 +128,8 @@ export class NotificationsController {
@ApiBearerAuth() @ApiBearerAuth()
@Put('admin/notifications/read/all') @Put('admin/notifications/read/all')
@ApiOperation({ summary: 'Read all notification ' }) @ApiOperation({ summary: 'Read all notification ' })
async readAllNotificationAdmin(@ShopId() restaurantId: string, @AdminId() adminId: string, @Param('id') id: string) { async readAllNotificationAdmin(@ShopId() shopId: string, @AdminId() adminId: string, @Param('id') id: string) {
await this.notificationService.readAllNotifsAsAdmin(adminId, restaurantId); await this.notificationService.readAllNotifsAsAdmin(adminId, shopId);
return { message: NotificationMessage.READ_SUCCESS }; return { message: NotificationMessage.READ_SUCCESS };
} }
@@ -138,8 +138,8 @@ export class NotificationsController {
@Permissions(Permission.MANAGE_SETTINGS) @Permissions(Permission.MANAGE_SETTINGS)
@Get('admin/notification-preferences') @Get('admin/notification-preferences')
@ApiOperation({ summary: 'Get all notification preferences for a shop' }) @ApiOperation({ summary: 'Get all notification preferences for a shop' })
async getPreferences(@ShopId() restaurantId: string) { async getPreferences(@ShopId() shopId: string) {
return this.preferenceService.findByRestaurant(restaurantId); return this.preferenceService.findByRestaurant(shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -149,11 +149,11 @@ export class NotificationsController {
@ApiOperation({ summary: 'Update notification channels' }) @ApiOperation({ summary: 'Update notification channels' })
@ApiParam({ name: 'id', description: 'Notification preference ID' }) @ApiParam({ name: 'id', description: 'Notification preference ID' })
async updatePreference( async updatePreference(
@ShopId() restaurantId: string, @ShopId() shopId: string,
@Param('id') preferenceId: string, @Param('id') preferenceId: string,
@Body() dto: UpdatePreferenceDto, @Body() dto: UpdatePreferenceDto,
) { ) {
return this.preferenceService.updatePreference(preferenceId, restaurantId, dto); return this.preferenceService.updatePreference(preferenceId, shopId, dto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -162,18 +162,18 @@ export class NotificationsController {
@Post('admin/notification-preferences') @Post('admin/notification-preferences')
@ApiOperation({ summary: 'Create notification preference' }) @ApiOperation({ summary: 'Create notification preference' })
async createPreference( async createPreference(
@ShopId() restaurantId: string, @ShopId() shopId: string,
@Body() dto: CreatePreferenceDto, @Body() dto: CreatePreferenceDto,
) { ) {
return this.preferenceService.create(restaurantId, dto); return this.preferenceService.create(shopId, dto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/notifications/sms-usage') @Get('admin/notifications/sms-usage')
@ApiOperation({ summary: 'Get SMS usage for my shop' }) @ApiOperation({ summary: 'Get SMS usage for my shop' })
async getRestaurantSmsUsage(@ShopId() restaurantId: string) { async getRestaurantSmsUsage(@ShopId() shopId: string) {
const smsCount = await this.notificationService.getSmsCountByRestaurantId(restaurantId); const smsCount = await this.notificationService.getSmsCountByRestaurantId(shopId);
return { smsCount }; return { smsCount };
} }
@@ -4,23 +4,23 @@ import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard';
/** /**
* Extract shop ID from authenticated WebSocket client * Extract shop ID from authenticated WebSocket client
* Use this decorator in WebSocket handlers to get the restId from the authenticated admin * Use this decorator in WebSocket handlers to get the shopId from the authenticated admin
* *
* @example * @example
* ```typescript * ```typescript
* @SubscribeMessage('get:notifications') * @SubscribeMessage('get:notifications')
* handleGetNotifications(@WsRestId() restId: string) { * handleGetNotifications(@WsRestId() shopId: string) {
* // restId is automatically extracted from authenticated admin * // shopId is automatically extracted from authenticated admin
* } * }
* ``` * ```
*/ */
export const WsRestId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => { export const WsRestId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
const client = ctx.switchToWs().getClient<AuthenticatedSocket>(); const client = ctx.switchToWs().getClient<AuthenticatedSocket>();
const restId = client.restId; const shopId = client.shopId;
if (!restId) { if (!shopId) {
throw new Error('Shop ID not found. Ensure WsAdminAuthGuard is applied.'); throw new Error('Shop ID not found. Ensure WsAdminAuthGuard is applied.');
} }
return restId; return shopId;
}); });
@@ -5,7 +5,7 @@ export class SendNotificationDto {
@ApiProperty({ description: 'Shop ID (ULID)' }) @ApiProperty({ description: 'Shop ID (ULID)' })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
restaurantId: string; shopId: string;
@ApiPropertyOptional({ description: 'User ID (ULID)' }) @ApiPropertyOptional({ description: 'User ID (ULID)' })
@IsString() @IsString()
@@ -1,7 +1,7 @@
export class SmsSentEvent { export class SmsSentEvent {
constructor( constructor(
public readonly phoneNumber: string, public readonly phoneNumber: string,
public readonly restaurantId: string, public readonly shopId: string,
) {} ) { }
} }
@@ -7,7 +7,7 @@ import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload';
export interface AuthenticatedSocket extends Socket { export interface AuthenticatedSocket extends Socket {
adminId?: string; adminId?: string;
restId?: string; shopId?: string;
} }
@Injectable() @Injectable()
@@ -48,9 +48,9 @@ export class WsAdminAuthGuard implements CanActivate {
// Attach admin info to socket // Attach admin info to socket
(client as AuthenticatedSocket).adminId = payload.adminId; (client as AuthenticatedSocket).adminId = payload.adminId;
(client as AuthenticatedSocket).restId = payload.shopId; (client as AuthenticatedSocket).shopId = payload.shopId;
this.logger.log(`Admin authenticated via WebSocket: ${payload.adminId}, restId: ${payload.shopId}`); this.logger.log(`Admin authenticated via WebSocket: ${payload.adminId}, shopId: ${payload.shopId}`);
return true; return true;
} catch (error) { } catch (error) {
@@ -3,7 +3,7 @@ import type { NotifTitleEnum, recipientType } from './notification.interface';
export interface SmsNotificationQueueJob { export interface SmsNotificationQueueJob {
recipient: recipientType; recipient: recipientType;
templateId: string; templateId: string;
restaurantId: string; shopId: string;
parameters?: Record<string, string>; parameters?: Record<string, string>;
} }
export interface PushNotificationQueueJob { export interface PushNotificationQueueJob {
@@ -18,7 +18,7 @@ export interface PushNotificationQueueJob {
pushTokens?: string[]; // Multiple FCM tokens for bulk push notifications pushTokens?: string[]; // Multiple FCM tokens for bulk push notifications
} }
export interface InAppNotificationQueueJob { export interface InAppNotificationQueueJob {
recipient: { adminId: string; restaurantId: string }; recipient: { adminId: string; shopId: string };
subject: NotifTitleEnum; subject: NotifTitleEnum;
body: string; body: string;
notificationId: string; notificationId: string;
@@ -40,7 +40,7 @@ export interface NotifRequest {
// timestamp: Date; // timestamp: Date;
// notifType: NotifTypeEnum; // notifType: NotifTypeEnum;
// channels: NotifChannelEnum[]; // channels: NotifChannelEnum[];
restaurantId: string; shopId: string;
recipients: recipientType[]; recipients: recipientType[];
message: NotifRequestMessage; message: NotifRequestMessage;
metadata: { metadata: {
@@ -19,15 +19,15 @@ export class SmsListeners {
async handleSmsSent(event: SmsSentEvent) { async handleSmsSent(event: SmsSentEvent) {
try { try {
this.logger.log( this.logger.log(
`SMS sent event received: phone ${event.phoneNumber} for shop: ${event.restaurantId}`, `SMS sent event received: phone ${event.phoneNumber} for shop: ${event.shopId}`,
); );
// Get the shop entity // Get the shop entity
const shop = await this.shopRepository.findOne({ id: event.restaurantId }); const shop = await this.shopRepository.findOne({ id: event.shopId });
if (!shop) { if (!shop) {
this.logger.warn( this.logger.warn(
`Shop not found for SMS log: ${event.restaurantId}`, `Shop not found for SMS log: ${event.shopId}`,
); );
return; return;
} }
@@ -46,7 +46,7 @@ export class SmsListeners {
); );
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Failed to create SMS log for event: ${event.restaurantId}`, `Failed to create SMS log for event: ${event.shopId}`,
error instanceof Error ? error.stack : String(error), error instanceof Error ? error.stack : String(error),
); );
} }
@@ -34,7 +34,7 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
@Inject(forwardRef(() => NotificationService)) @Inject(forwardRef(() => NotificationService))
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
private readonly moduleRef: ModuleRef, private readonly moduleRef: ModuleRef,
) {} ) { }
async handleConnection(client: Socket) { async handleConnection(client: Socket) {
try { try {
@@ -70,8 +70,8 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
// } // }
} }
sendInAppNotification(repipient: { adminId: string; restaurantId: string }, payload: IInAppNotificationPayload) { sendInAppNotification(repipient: { adminId: string; shopId: string }, payload: IInAppNotificationPayload) {
const room = this.getRoom(repipient.adminId, repipient.restaurantId); const room = this.getRoom(repipient.adminId, repipient.shopId);
this.logger.log(`Sending in app notification to admin: ${repipient.adminId}`); this.logger.log(`Sending in app notification to admin: ${repipient.adminId}`);
this.server.to(room).emit('notifications', payload, async () => { this.server.to(room).emit('notifications', payload, async () => {
@@ -81,19 +81,19 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
this.logger.log(`In app notification sent to admin: ${repipient.adminId}`); this.logger.log(`In app notification sent to admin: ${repipient.adminId}`);
} }
private getRoom(adminId: string, restaurantId: string) { private getRoom(adminId: string, shopId: string) {
return `shop:${restaurantId}-admin:${adminId}`; return `shop:${shopId}-admin:${adminId}`;
} }
handleLeaveRoom(client: AuthenticatedSocket) { handleLeaveRoom(client: AuthenticatedSocket) {
const room = this.getRoom(client.adminId!, client.restId!); const room = this.getRoom(client.adminId!, client.shopId!);
void client.leave(room); void client.leave(room);
this.logger.log(`Admin ${client.adminId} (Client ${client.id}) left room: ${room}`); this.logger.log(`Admin ${client.adminId} (Client ${client.id}) left room: ${room}`);
void client.emit('left', { room, message: 'Successfully Admin left room' }); void client.emit('left', { room, message: 'Successfully Admin left room' });
} }
handleJoinRoom(client: AuthenticatedSocket) { handleJoinRoom(client: AuthenticatedSocket) {
const room = this.getRoom(client.adminId!, client.restId!); const room = this.getRoom(client.adminId!, client.shopId!);
void client.join(room); void client.join(room);
this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`); this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`);
void client.emit('joined', { room, message: 'Successfully Admin joined room' }); void client.emit('joined', { room, message: 'Successfully Admin joined room' });
@@ -27,7 +27,7 @@ export class InAppProcessor extends WorkerHost {
try { try {
this.notificationsGateway.sendInAppNotification( this.notificationsGateway.sendInAppNotification(
{ adminId: recipient.adminId, restaurantId: recipient.restaurantId }, { adminId: recipient.adminId, shopId: recipient.shopId },
{ subject, body, notificationId }, { subject, body, notificationId },
); );
@@ -23,7 +23,7 @@ export class SmsProcessor extends WorkerHost {
} }
async process(job: Job<SmsNotificationQueueJob>) { async process(job: Job<SmsNotificationQueueJob>) {
const { recipient, templateId, parameters, restaurantId } = job.data; const { recipient, templateId, parameters, shopId } = job.data;
this.logger.log(`Processing SMS notification - Recipient: ${JSON.stringify(recipient)}, templateID: ${templateId}`); this.logger.log(`Processing SMS notification - Recipient: ${JSON.stringify(recipient)}, templateID: ${templateId}`);
@@ -58,7 +58,7 @@ export class SmsProcessor extends WorkerHost {
this.logger.log(`SMS notification sent successfully to ${phone}`); this.logger.log(`SMS notification sent successfully to ${phone}`);
this.eventEmitter.emit(SmsSentEvent.name, new SmsSentEvent(phone, restaurantId)); this.eventEmitter.emit(SmsSentEvent.name, new SmsSentEvent(phone, shopId));
return { return {
success: true, success: true,
@@ -12,13 +12,13 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
async getSmsCountByRestaurant( async getSmsCountByRestaurant(
page: number = 1, page: number = 1,
limit: number = 10, limit: number = 10,
): Promise<PaginatedResult<{ restaurantId: string; restaurantName: string; smsCount: number }>> { ): Promise<PaginatedResult<{ shopId: string; shopName: string; smsCount: number }>> {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
// Get total count of unique shops with SMS logs // Get total count of unique shops with SMS logs
const totalResult = await this.em.execute( const totalResult = await this.em.execute(
` `
SELECT COUNT(DISTINCT sl.restaurant_id) as total SELECT COUNT(DISTINCT sl.shop_id) as total
FROM sms_logs sl FROM sms_logs sl
WHERE sl.restaurant_id IS NOT NULL WHERE sl.restaurant_id IS NOT NULL
`, `,
@@ -29,11 +29,11 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
const results = await this.em.execute( const results = await this.em.execute(
` `
SELECT SELECT
r.id as "restaurantId", r.id as "shopId",
r.name as "restaurantName", r.name as "shopName",
COUNT(sl.id)::int as "smsCount" COUNT(sl.id)::int as "smsCount"
FROM sms_logs sl FROM sms_logs sl
INNER JOIN shops r ON sl.restaurant_id = r.id INNER JOIN shops r ON sl.shop_id = r.id
GROUP BY r.id, r.name GROUP BY r.id, r.name
ORDER BY "smsCount" DESC, r.name ASC ORDER BY "smsCount" DESC, r.name ASC
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
@@ -42,8 +42,8 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
); );
const data = results.map((row: any) => ({ const data = results.map((row: any) => ({
restaurantId: row.restaurantId, shopId: row.shopId,
restaurantName: row.restaurantName || 'Unknown', shopName: row.shopName || 'Unknown',
smsCount: parseInt(row.smsCount || '0', 10), smsCount: parseInt(row.smsCount || '0', 10),
})); }));
@@ -60,14 +60,14 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
}; };
} }
async getSmsCountByRestaurantId(restaurantId: string): Promise<number> { async getSmsCountByRestaurantId(shopId: string): Promise<number> {
const result = await this.em.execute( const result = await this.em.execute(
` `
SELECT COUNT(sl.id)::int as "smsCount" SELECT COUNT(sl.id)::int as "smsCount"
FROM sms_logs sl FROM sms_logs sl
WHERE sl.restaurant_id = ? WHERE sl.shop_id = ?
`, `,
[restaurantId], [shopId],
); );
return parseInt(result[0]?.smsCount || '0', 10); return parseInt(result[0]?.smsCount || '0', 10);
@@ -10,8 +10,8 @@ import { NotifTitleEnum } from '../interfaces/notification.interface';
export class NotificationPreferenceService { export class NotificationPreferenceService {
constructor(private readonly em: EntityManager) { } constructor(private readonly em: EntityManager) { }
async create(restaurantId: string, dto: CreatePreferenceDto): Promise<NotificationPreference> { async create(shopId: string, dto: CreatePreferenceDto): Promise<NotificationPreference> {
const shop = await this.em.findOne(Shop, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException('Shop not found'); throw new NotFoundException('Shop not found');
} }
@@ -26,25 +26,25 @@ export class NotificationPreferenceService {
return preference; return preference;
} }
async findByRestaurant(restaurantId: string): Promise<NotificationPreference[]> { async findByRestaurant(shopId: string): Promise<NotificationPreference[]> {
return this.em.find(NotificationPreference, { return this.em.find(NotificationPreference, {
shop: { id: restaurantId }, shop: { id: shopId },
}); });
} }
async findByRestaurantAndType(restaurantId: string, title: NotifTitleEnum): Promise<NotificationPreference | null> { async findByRestaurantAndType(shopId: string, title: NotifTitleEnum): Promise<NotificationPreference | null> {
return this.em.findOne(NotificationPreference, { return this.em.findOne(NotificationPreference, {
shop: { id: restaurantId }, shop: { id: shopId },
title, title,
}); });
} }
// async updateEnabled( // async updateEnabled(
// restaurantId: string, // shopId: string,
// notificationType: string, // notificationType: string,
// enabled: boolean, // enabled: boolean,
// ): Promise<NotificationPreference> { // ): Promise<NotificationPreference> {
// const preference = await this.findByRestaurantAndType(restaurantId, notificationType); // const preference = await this.findByRestaurantAndType(shopId, notificationType);
// if (!preference) { // if (!preference) {
// throw new NotFoundException('Notification preference not found'); // throw new NotFoundException('Notification preference not found');
// } // }
@@ -56,12 +56,12 @@ export class NotificationPreferenceService {
async updatePreference( async updatePreference(
preferenceId: string, preferenceId: string,
restaurantId: string, shopId: string,
dto: UpdatePreferenceDto, dto: UpdatePreferenceDto,
): Promise<NotificationPreference> { ): Promise<NotificationPreference> {
const preference = await this.em.findOne(NotificationPreference, { const preference = await this.em.findOne(NotificationPreference, {
id: preferenceId, id: preferenceId,
shop: { id: restaurantId }, shop: { id: shopId },
}); });
if (!preference) { if (!preference) {
throw new NotFoundException('Notification preference not found'); throw new NotFoundException('Notification preference not found');
@@ -72,9 +72,9 @@ export class NotificationPreferenceService {
return preference; return preference;
} }
async delete(restaurantId: string, preferenceId: string): Promise<void> { async delete(shopId: string, preferenceId: string): Promise<void> {
const preference = await this.em.findOne(NotificationPreference, { const preference = await this.em.findOne(NotificationPreference, {
shop: { id: restaurantId }, shop: { id: shopId },
id: preferenceId, id: preferenceId,
}); });
if (!preference) { if (!preference) {
@@ -21,12 +21,12 @@ export class NotificationService {
) { } ) { }
async sendNotification(params: NotifRequest): Promise<Notification[]> { async sendNotification(params: NotifRequest): Promise<Notification[]> {
const { recipients, message, metadata, restaurantId } = params; const { recipients, message, metadata, shopId } = params;
// create Database notifications // create Database notifications
const notifications = await this.createAdminBulkNotifications( const notifications = await this.createAdminBulkNotifications(
recipients.map(recipient => ({ recipients.map(recipient => ({
restaurantId, shopId,
title: message.title, title: message.title,
content: message.content, content: message.content,
adminId: 'adminId' in recipient ? recipient.adminId : null, adminId: 'adminId' in recipient ? recipient.adminId : null,
@@ -35,10 +35,10 @@ export class NotificationService {
); );
// get admin prefrences // get admin prefrences
const preference = await this.preferenceService.findByRestaurantAndType(restaurantId, message.title); const preference = await this.preferenceService.findByRestaurantAndType(shopId, message.title);
if (preference?.channels?.length === 0) { if (preference?.channels?.length === 0) {
this.logger.warn(`Notification type is NONE for shop ${restaurantId}, title ${message.title}`); this.logger.warn(`Notification type is NONE for shop ${shopId}, title ${message.title}`);
return notifications; return notifications;
} }
@@ -46,7 +46,7 @@ export class NotificationService {
if (preference?.channels?.includes(NotifChannelEnum.IN_APP)) { if (preference?.channels?.includes(NotifChannelEnum.IN_APP)) {
await this.queueService.addBulkInAppNotifications( await this.queueService.addBulkInAppNotifications(
notifications.map(notification => ({ notifications.map(notification => ({
recipient: { adminId: notification.admin?.id || '', restaurantId }, recipient: { adminId: notification.admin?.id || '', shopId },
subject: message.title, subject: message.title,
body: message.content, body: message.content,
notificationId: (notification as any).id, notificationId: (notification as any).id,
@@ -61,19 +61,19 @@ export class NotificationService {
recipient, recipient,
templateId: message.sms.templateId, templateId: message.sms.templateId,
parameters: message.sms.parameters, parameters: message.sms.parameters,
restaurantId, shopId,
})), })),
); );
} }
this.logger.log(`Queued notification for shop ${restaurantId}, title ${message.title}`); this.logger.log(`Queued notification for shop ${shopId}, title ${message.title}`);
return notifications; return notifications;
} }
async createAdminBulkNotifications( async createAdminBulkNotifications(
params: { params: {
restaurantId: string; shopId: string;
title: NotifTitleEnum; title: NotifTitleEnum;
content: string; content: string;
adminId: string | null; adminId: string | null;
@@ -82,7 +82,7 @@ export class NotificationService {
): Promise<Notification[]> { ): Promise<Notification[]> {
const notifications = params.map(param => { const notifications = params.map(param => {
return this.em.create(Notification, { return this.em.create(Notification, {
shop: param.restaurantId, shop: param.shopId,
admin: param.adminId, admin: param.adminId,
user: param.userId && param.userId.trim() !== '' ? param.userId : null, user: param.userId && param.userId.trim() !== '' ? param.userId : null,
title: param.title, title: param.title,
@@ -102,14 +102,14 @@ export class NotificationService {
} }
async findByRestaurant( async findByRestaurant(
restaurantId: string, shopId: string,
adminId: string, adminId: string,
limit = 50, limit = 50,
cursor?: string, cursor?: string,
status?: 'seen' | 'unseen', status?: 'seen' | 'unseen',
): Promise<{ data: Notification[]; nextCursor: string | null }> { ): Promise<{ data: Notification[]; nextCursor: string | null }> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
shop: { id: restaurantId }, shop: { id: shopId },
admin: { id: adminId }, admin: { id: adminId },
}; };
@@ -143,14 +143,14 @@ export class NotificationService {
async findByUserAndRestaurant( async findByUserAndRestaurant(
userId: string, userId: string,
restaurantId: string, shopId: string,
limit = 50, limit = 50,
cursor?: string, cursor?: string,
status?: 'seen' | 'unseen', status?: 'seen' | 'unseen',
): Promise<{ data: Notification[]; nextCursor: string | null }> { ): Promise<{ data: Notification[]; nextCursor: string | null }> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
user: { id: userId }, user: { id: userId },
shop: { id: restaurantId }, shop: { id: shopId },
}; };
// Filter by status (seen/unseen) // Filter by status (seen/unseen)
@@ -181,11 +181,11 @@ export class NotificationService {
}; };
} }
async readNotificationAdmin(id: string, adminId: string, restaurantId: string): Promise<void> { async readNotificationAdmin(id: string, adminId: string, shopId: string): Promise<void> {
const notification = await this.em.findOne(Notification, { const notification = await this.em.findOne(Notification, {
id, id,
admin: { id: adminId }, admin: { id: adminId },
shop: { id: restaurantId }, shop: { id: shopId },
}); });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException('Notification not found');
@@ -193,11 +193,11 @@ export class NotificationService {
notification.seenAt = new Date(); notification.seenAt = new Date();
await this.em.persistAndFlush(notification); await this.em.persistAndFlush(notification);
} }
async readNotificationAsUser(id: string, userId: string, restaurantId: string): Promise<void> { async readNotificationAsUser(id: string, userId: string, shopId: string): Promise<void> {
const notification = await this.em.findOne(Notification, { const notification = await this.em.findOne(Notification, {
id, id,
user: { id: userId }, user: { id: userId },
shop: { id: restaurantId }, shop: { id: shopId },
}); });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException('Notification not found');
@@ -206,11 +206,11 @@ export class NotificationService {
await this.em.persistAndFlush(notification); await this.em.persistAndFlush(notification);
} }
async findByRestaurantAndType(restaurantId: string, title: NotifTitleEnum, limit = 50): Promise<Notification[]> { async findByRestaurantAndType(shopId: string, title: NotifTitleEnum, limit = 50): Promise<Notification[]> {
return this.em.find( return this.em.find(
Notification, Notification,
{ {
shop: { id: restaurantId }, shop: { id: shopId },
title, title,
}, },
{ {
@@ -221,10 +221,10 @@ export class NotificationService {
); );
} }
async findByAdminAndRestaurant(adminId: string, restaurantId: string, limit = 50): Promise<Notification[]> { async findByAdminAndRestaurant(adminId: string, shopId: string, limit = 50): Promise<Notification[]> {
return this.em.find( return this.em.find(
Notification, Notification,
{ admin: { id: adminId }, shop: { id: restaurantId } }, { admin: { id: adminId }, shop: { id: shopId } },
{ {
orderBy: { createdAt: 'DESC' }, orderBy: { createdAt: 'DESC' },
limit, limit,
@@ -232,37 +232,37 @@ export class NotificationService {
); );
} }
async countUnseenByUserAndRestaurant(userId: string, restaurantId: string): Promise<number> { async countUnseenByUserAndRestaurant(userId: string, shopId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
user: { id: userId }, user: { id: userId },
shop: { id: restaurantId }, shop: { id: shopId },
seenAt: null, seenAt: null,
}; };
return this.em.count(Notification, where); return this.em.count(Notification, where);
} }
async countUnseenByRestaurant(adminId: string, restaurantId: string): Promise<number> { async countUnseenByRestaurant(adminId: string, shopId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
admin: { id: adminId }, admin: { id: adminId },
shop: { id: restaurantId }, shop: { id: shopId },
seenAt: null, seenAt: null,
}; };
return this.em.count(Notification, where); return this.em.count(Notification, where);
} }
readAllNotifsAsUser(userId: string, restaurantId: string): Promise<number> { readAllNotifsAsUser(userId: string, shopId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
user: { id: userId }, user: { id: userId },
shop: { id: restaurantId }, shop: { id: shopId },
seenAt: null, seenAt: null,
}; };
return this.em.nativeUpdate(Notification, where, { seenAt: new Date() }); return this.em.nativeUpdate(Notification, where, { seenAt: new Date() });
} }
readAllNotifsAsAdmin(adminId: string, restaurantId: string): Promise<number> { readAllNotifsAsAdmin(adminId: string, shopId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
admin: { id: adminId }, admin: { id: adminId },
shop: { id: restaurantId }, shop: { id: shopId },
seenAt: null, seenAt: null,
}; };
return this.em.nativeUpdate(Notification, where, { seenAt: new Date() }); return this.em.nativeUpdate(Notification, where, { seenAt: new Date() });
@@ -271,11 +271,11 @@ export class NotificationService {
async getSmsCountByRestaurant( async getSmsCountByRestaurant(
page: number = 1, page: number = 1,
limit: number = 10, limit: number = 10,
): Promise<PaginatedResult<{ restaurantId: string; restaurantName: string; smsCount: number }>> { ): Promise<PaginatedResult<{ shopId: string; shopName: string; smsCount: number }>> {
return this.smsLogRepository.getSmsCountByRestaurant(page, limit); return this.smsLogRepository.getSmsCountByRestaurant(page, limit);
} }
async getSmsCountByRestaurantId(restaurantId: string): Promise<number> { async getSmsCountByRestaurantId(shopId: string): Promise<number> {
return this.smsLogRepository.getSmsCountByRestaurantId(restaurantId); return this.smsLogRepository.getSmsCountByRestaurantId(shopId);
} }
} }
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiHeader, ApiBody } fr
import { OrdersService } from '../providers/orders.service'; import { OrdersService } from '../providers/orders.service';
import { AuthGuard } from '../../auth/guards/auth.guard'; import { AuthGuard } from '../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator'; import { UserId } from '../../../common/decorators/user-id.decorator';
import { ShopId } from 'src/common/decorators/rest-id.decorator'; import { ShopId } from 'src/common/decorators/shop-id.decorator';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard'; import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
import { FindOrdersDto } from '../dto/find-orders.dto'; import { FindOrdersDto } from '../dto/find-orders.dto';
import { OrderStatus } from '../interface/order.interface'; import { OrderStatus } from '../interface/order.interface';
@@ -22,16 +22,16 @@ export class OrdersController {
@Post('public/checkout') @Post('public/checkout')
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiOperation({ summary: 'Checkout : create order and payment record' }) @ApiOperation({ summary: 'Checkout : create order and payment record' })
checkout(@UserId() userId: string, @ShopId() restaurantId: string) { checkout(@UserId() userId: string, @ShopId() shopId: string) {
return this.ordersService.checkout(userId, restaurantId); return this.ordersService.checkout(userId, shopId);
} }
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@Get('public/orders') @Get('public/orders')
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiOperation({ summary: 'Get all orders with pagination and filters' }) @ApiOperation({ summary: 'Get all orders with pagination and filters' })
findAll(@ShopId() restId: string, @Query() dto: FindOrdersDto, @UserId() userId: string) { findAll(@ShopId() shopId: string, @Query() dto: FindOrdersDto, @UserId() userId: string) {
return this.ordersService.findAllForUser(restId, dto, userId); return this.ordersService.findAllForUser(shopId, dto, userId);
} }
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@@ -39,8 +39,8 @@ export class OrdersController {
@ApiParam({ name: 'orderId', description: 'Order ID' }) @ApiParam({ name: 'orderId', description: 'Order ID' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@Get('public/orders/:orderId') @Get('public/orders/:orderId')
findOne(@Param('orderId') orderId: string, @ShopId() restId: string) { findOne(@Param('orderId') orderId: string, @ShopId() shopId: string) {
return this.ordersService.findOne(orderId, restId); return this.ordersService.findOne(orderId, shopId);
} }
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@@ -58,9 +58,9 @@ export class OrdersController {
@Body() dto: UpdateOrderStatusDto, @Body() dto: UpdateOrderStatusDto,
@Param('id') orderId: string, @Param('id') orderId: string,
@Param('status') status: OrderStatus, @Param('status') status: OrderStatus,
@ShopId() restId: string, @ShopId() shopId: string,
) { ) {
return this.ordersService.changeOrderStatus(orderId, restId, status, 'user', dto?.desc); return this.ordersService.changeOrderStatus(orderId, shopId, status, 'user', dto?.desc);
} }
/******************** Admin Routes **********************/ /******************** Admin Routes **********************/
@@ -68,8 +68,8 @@ export class OrdersController {
@Permissions(Permission.MANAGE_ORDERS) @Permissions(Permission.MANAGE_ORDERS)
@Get('admin/orders') @Get('admin/orders')
@ApiOperation({ summary: 'Get all orders with pagination and filters' }) @ApiOperation({ summary: 'Get all orders with pagination and filters' })
findAllAdmin(@ShopId() restId: string, @Query() dto: FindOrdersDto) { findAllAdmin(@ShopId() shopId: string, @Query() dto: FindOrdersDto) {
return this.ordersService.findAllForAdmin(restId, dto); return this.ordersService.findAllForAdmin(shopId, dto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -77,8 +77,8 @@ export class OrdersController {
@ApiOperation({ summary: 'Get an order By id for User' }) @ApiOperation({ summary: 'Get an order By id for User' })
@ApiParam({ name: 'orderId', description: 'Order ID' }) @ApiParam({ name: 'orderId', description: 'Order ID' })
@Get('admin/orders/:orderId') @Get('admin/orders/:orderId')
findOneAsAdmin(@Param('orderId') orderId: string, @ShopId() restId: string) { findOneAsAdmin(@Param('orderId') orderId: string, @ShopId() shopId: string) {
return this.ordersService.findOne(orderId, restId); return this.ordersService.findOne(orderId, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -96,17 +96,17 @@ export class OrdersController {
@Param('orderId') orderId: string, @Param('orderId') orderId: string,
@Body() dto: UpdateOrderStatusDto, @Body() dto: UpdateOrderStatusDto,
@Param('status') status: OrderStatus, @Param('status') status: OrderStatus,
@ShopId() restId: string, @ShopId() shopId: string,
) { ) {
return this.ordersService.changeOrderStatus(orderId, restId, status, 'admin', dto?.desc); return this.ordersService.changeOrderStatus(orderId, shopId, status, 'admin', dto?.desc);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Permissions(Permission.VIEW_REPORTS) @Permissions(Permission.VIEW_REPORTS)
@ApiOperation({ summary: 'Get Stats for report page' }) @ApiOperation({ summary: 'Get Stats for report page' })
@Get('admin/orders/stats') @Get('admin/orders/stats')
findStats(@ShopId() restId: string) { findStats(@ShopId() shopId: string) {
return this.ordersService.getStats(restId); return this.ordersService.getStats(shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -114,7 +114,7 @@ export class OrdersController {
@ApiOperation({ summary: 'Get product sales pie chart data for last month' }) @ApiOperation({ summary: 'Get product sales pie chart data for last month' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@Get('admin/orders/product-sales-pie-chart') @Get('admin/orders/product-sales-pie-chart')
getFoodSalesPieChart(@ShopId() restId: string) { getFoodSalesPieChart(@ShopId() shopId: string) {
return this.ordersService.getFoodSalesPieChart(restId); return this.ordersService.getFoodSalesPieChart(shopId);
} }
} }
+2 -2
View File
@@ -129,7 +129,7 @@ export class OrdersCrone {
} }
const previousStatus = reloadedOrder.status; const previousStatus = reloadedOrder.status;
const restaurantId = const shopId =
typeof reloadedOrder.shop === 'string' typeof reloadedOrder.shop === 'string'
? reloadedOrder.shop ? reloadedOrder.shop
: reloadedOrder.shop.id; : reloadedOrder.shop.id;
@@ -152,7 +152,7 @@ export class OrdersCrone {
reloadedOrder.id, reloadedOrder.id,
reloadedOrder.user?.id || '', reloadedOrder.user?.id || '',
String(reloadedOrder.orderNumber) || '', String(reloadedOrder.orderNumber) || '',
restaurantId, shopId,
previousStatus, previousStatus,
OrderStatus.COMPLETED, OrderStatus.COMPLETED,
'admin', 'admin',
+2 -2
View File
@@ -3,7 +3,7 @@ import type { OrderStatus, StatusTransitionRef } from '../interface/order.interf
export class OrderCreatedEvent { export class OrderCreatedEvent {
constructor( constructor(
public readonly orderId: string, public readonly orderId: string,
public readonly restaurantId: string, public readonly shopId: string,
public readonly orderNumber: string, public readonly orderNumber: string,
public readonly total: number, public readonly total: number,
) {} ) {}
@@ -14,7 +14,7 @@ export class OrderStatusChangedEvent {
public readonly orderId: string, public readonly orderId: string,
public readonly userId: string, public readonly userId: string,
public readonly orderNumber: string, public readonly orderNumber: string,
public readonly restaurantId: string, public readonly shopId: string,
public readonly previousStatus: OrderStatus, public readonly previousStatus: OrderStatus,
public readonly newStatus: OrderStatus, public readonly newStatus: OrderStatus,
public readonly changedBy: StatusTransitionRef, public readonly changedBy: StatusTransitionRef,
+14 -14
View File
@@ -47,7 +47,7 @@ export class OrderListeners {
async handleOrderCreated(event: OrderCreatedEvent) { async handleOrderCreated(event: OrderCreatedEvent) {
try { try {
this.logger.log( this.logger.log(
`Order created event received: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, `Order created event received: ${event.orderId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
); );
const order = await this.OrderRepository.findOne(event.orderId); const order = await this.OrderRepository.findOne(event.orderId);
@@ -57,13 +57,13 @@ export class OrderListeners {
// get admnin os restuaraant that have order permissuins // get admnin os restuaraant that have order permissuins
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS); const admins = await this.adminService.findAdminsWithPermission(event.shopId, Permission.MANAGE_ORDERS);
const recipients = admins.map(admin => ({ const recipients = admins.map(admin => ({
adminId: admin.id, adminId: admin.id,
})); }));
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.ORDER_CREATED, title: NotifTitleEnum.ORDER_CREATED,
content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`, content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
@@ -91,7 +91,7 @@ export class OrderListeners {
}); });
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Failed to send notification for order created event: ${event.restaurantId}`, `Failed to send notification for order created event: ${event.shopId}`,
error instanceof Error ? error.stack : String(error), error instanceof Error ? error.stack : String(error),
); );
} }
@@ -101,7 +101,7 @@ export class OrderListeners {
async handleOrderStatusChanged(event: OrderStatusChangedEvent) { async handleOrderStatusChanged(event: OrderStatusChangedEvent) {
try { try {
this.logger.log( this.logger.log(
`Order status changed event received: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, `Order status changed event received: ${event.orderId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
); );
//TODO : REFACTOR to use queue or other way to handle this //TODO : REFACTOR to use queue or other way to handle this
const recipients = [ const recipients = [
@@ -113,21 +113,21 @@ export class OrderListeners {
if (!event?.userId) { if (!event?.userId) {
this.logger.log( this.logger.log(
`User not found for order: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, `User not found for order: ${event.orderId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
); );
} }
// const shop = await this.RestaurantRepository.findOne(event.restaurantId); // const shop = await this.RestaurantRepository.findOne(event.shopId);
// if (!shop) { // if (!shop) {
// this.logger.log( // this.logger.log(
// `Shop not found for order: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, // `Shop not found for order: ${event.orderId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
// ); // );
// return; // return;
// } // }
// const score = shop.score; // const score = shop.score;
// if (!score) { // if (!score) {
// this.logger.log( // this.logger.log(
// `Score not found for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, // `Score not found for shop: ${event.shopId} and order number: ${event.orderNumber}`,
// ); // );
// return; // return;
// } // }
@@ -136,18 +136,18 @@ export class OrderListeners {
// const order = await this.OrderRepository.findOne(event.orderId); // const order = await this.OrderRepository.findOne(event.orderId);
// if (!order) { // if (!order) {
// this.logger.log( // this.logger.log(
// `Order not found for order: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, // `Order not found for order: ${event.orderId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
// ); // );
// return; // return;
// } // }
// this.userService.createWalletTransaction(event.userId, event.restaurantId, { // this.userService.createWalletTransaction(event.userId, event.shopId, {
// amount: order.subTotal, // amount: order.subTotal,
// type: WalletTransactionType.CREDIT, // type: WalletTransactionType.CREDIT,
// reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT, // reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT,
// }); // });
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.ORDER_STATUS_CHANGED, title: NotifTitleEnum.ORDER_STATUS_CHANGED,
content: `لطفابرای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`, content: `لطفابرای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
@@ -174,7 +174,7 @@ export class OrderListeners {
}); });
} else { } else {
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.ORDER_STATUS_CHANGED, title: NotifTitleEnum.ORDER_STATUS_CHANGED,
content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`, content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
@@ -203,7 +203,7 @@ export class OrderListeners {
} }
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Failed to send notification for order status changed event: ${event.restaurantId}`, `Failed to send notification for order status changed event: ${event.shopId}`,
error instanceof Error ? error.stack : String(error), error instanceof Error ? error.stack : String(error),
); );
} }
@@ -31,7 +31,7 @@ export class OrderRepository extends EntityRepository<Order> {
* Find orders with pagination and optional filters. * Find orders with pagination and optional filters.
* Supports: statuses, paymentStatus, search (orderNumber), date range, ordering. * Supports: statuses, paymentStatus, search (orderNumber), date range, ordering.
*/ */
async findAllPaginated(restId: string, opts: FindOrdersOpts = {}): Promise<PaginatedResult<Order>> { async findAllPaginated(shopId: string, opts: FindOrdersOpts = {}): Promise<PaginatedResult<Order>> {
const { const {
page = 1, page = 1,
limit = 10, limit = 10,
@@ -47,7 +47,7 @@ export class OrderRepository extends EntityRepository<Order> {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const where: FilterQuery<Order> = { shop: { id: restId } }; const where: FilterQuery<Order> = { shop: { id: shopId } };
// Filter by statuses // Filter by statuses
if (statuses) { if (statuses) {
@@ -35,8 +35,8 @@ export class PaymentsController {
@ApiOperation({ summary: 'Get the shop payment methods' }) @ApiOperation({ summary: 'Get the shop payment methods' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiNotFoundResponse({ description: 'Shop payment methods not found' }) @ApiNotFoundResponse({ description: 'Shop payment methods not found' })
getTheRestaurantPaymentMethods(@ShopId() restId: string) { getTheRestaurantPaymentMethods(@ShopId() shopId: string) {
return this.paymentMethodService.findByRestaurant(restId); return this.paymentMethodService.findByRestaurant(shopId);
} }
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@@ -44,8 +44,8 @@ export class PaymentsController {
@Get('public/payments') @Get('public/payments')
@ApiOperation({ summary: 'Get all the shop payments for user' }) @ApiOperation({ summary: 'Get all the shop payments for user' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
findAllByRestaurant(@UserId() userId: string, @ShopId() restId: string) { findAllByRestaurant(@UserId() userId: string, @ShopId() shopId: string) {
return this.paymentsService.findAllPaymentsByRestaurantId(restId, userId); return this.paymentsService.findAllPaymentsByRestaurantId(shopId, userId);
} }
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@@ -73,8 +73,8 @@ export class PaymentsController {
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/payments/methods') @Get('admin/payments/methods')
@ApiOperation({ summary: 'Get shop all payment methods' }) @ApiOperation({ summary: 'Get shop all payment methods' })
findByRestaurant(@ShopId() restId: string) { findByRestaurant(@ShopId() shopId: string) {
return this.paymentMethodService.findByRestaurant(restId); return this.paymentMethodService.findByRestaurant(shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -83,8 +83,8 @@ export class PaymentsController {
@Post('admin/payments/methods') @Post('admin/payments/methods')
@ApiOperation({ summary: 'Create a new shop payment method' }) @ApiOperation({ summary: 'Create a new shop payment method' })
@ApiBody({ type: CreatePaymentMethodDto }) @ApiBody({ type: CreatePaymentMethodDto })
createRestaurantPaymentMethod(@ShopId() restId: string, @Body() createPaymentMethodDto: CreatePaymentMethodDto) { createRestaurantPaymentMethod(@ShopId() shopId: string, @Body() createPaymentMethodDto: CreatePaymentMethodDto) {
return this.paymentMethodService.create(restId, createPaymentMethodDto); return this.paymentMethodService.create(shopId, createPaymentMethodDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -137,7 +137,7 @@ export class PaymentsController {
@Get('admin/payments/chart') @Get('admin/payments/chart')
@ApiOperation({ summary: 'Get payment chart data with date and period filters' }) @ApiOperation({ summary: 'Get payment chart data with date and period filters' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
getPaymentChart(@Query() query: PaymentChartDto, @ShopId() restId: string) { getPaymentChart(@Query() query: PaymentChartDto, @ShopId() shopId: string) {
return this.paymentsService.getChartData(query, restId); return this.paymentsService.getChartData(query, shopId);
} }
} }
@@ -3,7 +3,7 @@ import { PaymentMethodEnum } from "../interface/payment";
export class paymentSucceedEvent { export class paymentSucceedEvent {
constructor( constructor(
public readonly orderId: string, public readonly orderId: string,
public readonly restaurantId: string, public readonly shopId: string,
public readonly orderNumber: string, public readonly orderNumber: string,
public readonly paymentMethod: PaymentMethodEnum, public readonly paymentMethod: PaymentMethodEnum,
public readonly total: number public readonly total: number
@@ -16,7 +16,7 @@ export class onlinePaymentSucceedEvent {
constructor( constructor(
public readonly paymentId: string, public readonly paymentId: string,
public readonly orderId: string, public readonly orderId: string,
public readonly restaurantId: string, public readonly shopId: string,
public readonly orderNumber: string, public readonly orderNumber: string,
public readonly total: number, public readonly total: number,
) { } ) { }
@@ -36,16 +36,16 @@ export class PaymentListeners {
async handlePaymentSucceed(event: paymentSucceedEvent) { async handlePaymentSucceed(event: paymentSucceedEvent) {
try { try {
this.logger.log( this.logger.log(
`Payment paid event received: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, `Payment paid event received: ${event.orderId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
); );
// get admnin os restuaraant that have order permissuins // get admnin os restuaraant that have order permissuins
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS); const admins = await this.adminService.findAdminsWithPermission(event.shopId, Permission.MANAGE_ORDERS);
// const order=await // const order=await
const recipients = admins.map(admin => ({ const recipients = admins.map(admin => ({
adminId: admin.id, adminId: admin.id,
})); }));
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.PAYMENT_SUCCESS, title: NotifTitleEnum.PAYMENT_SUCCESS,
content: `پرداخت سفارش شماره ${event.orderNumber} با روش ${this.getPaymentMethodFarsi(event.paymentMethod)} و مبلغ ${event.total} تومان با موفقیت انجام شد`, content: `پرداخت سفارش شماره ${event.orderNumber} با روش ${this.getPaymentMethodFarsi(event.paymentMethod)} و مبلغ ${event.total} تومان با موفقیت انجام شد`,
@@ -73,7 +73,7 @@ export class PaymentListeners {
}); });
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Failed to send notification for order created event: ${event.restaurantId}`, `Failed to send notification for order created event: ${event.shopId}`,
error instanceof Error ? error.stack : String(error), error instanceof Error ? error.stack : String(error),
); );
} }
@@ -84,16 +84,16 @@ export class PaymentListeners {
try { try {
this.logger.log( this.logger.log(
`Online payment succeed event received: ${event.paymentId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`, `Online payment succeed event received: ${event.paymentId} for shop: ${event.shopId} and order number: ${event.orderNumber}`,
); );
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS); const admins = await this.adminService.findAdminsWithPermission(event.shopId, Permission.MANAGE_ORDERS);
const recipients = admins.map(admin => ({ const recipients = admins.map(admin => ({
adminId: admin.id, adminId: admin.id,
})); }));
// admin notifs // admin notifs
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.ORDER_CREATED, title: NotifTitleEnum.ORDER_CREATED,
content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`, content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
@@ -120,10 +120,10 @@ export class PaymentListeners {
}, },
}); });
const order = await this.orderService.findOne(event.orderId, event.restaurantId); const order = await this.orderService.findOne(event.orderId, event.shopId);
if (!order) { if (!order) {
this.logger.error( this.logger.error(
`Order not found: ${event.orderId} for shop: ${event.restaurantId}`, `Order not found: ${event.orderId} for shop: ${event.shopId}`,
); );
return; return;
} }
@@ -134,7 +134,7 @@ export class PaymentListeners {
]; ];
// user notif // user notif
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.PAYMENT_SUCCESS, title: NotifTitleEnum.PAYMENT_SUCCESS,
content: `پرداخت سفارش شماره ${order.orderNumber} با روش ${this.getPaymentMethodFarsi(order.paymentMethod.method)} و مبلغ ${order.total} تومان با موفقیت انجام شد`, content: `پرداخت سفارش شماره ${order.orderNumber} با روش ${this.getPaymentMethodFarsi(order.paymentMethod.method)} و مبلغ ${order.total} تومان با موفقیت انجام شد`,
@@ -162,7 +162,7 @@ export class PaymentListeners {
}); });
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Failed to send notification for online payment succeed event: ${event.restaurantId}`, `Failed to send notification for online payment succeed event: ${event.shopId}`,
error instanceof Error ? error.stack : String(error), error instanceof Error ? error.stack : String(error),
); );
} }
@@ -15,9 +15,9 @@ export class PaymentMethodService {
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> { async create(shopId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> {
// Check if shop exists // Check if shop exists
const shop = await this.em.findOne(Shop, { id: restId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
@@ -42,8 +42,8 @@ export class PaymentMethodService {
return paymentMethod; return paymentMethod;
} }
async findByRestaurant(restaurantId: string): Promise<PaymentMethod[]> { async findByRestaurant(shopId: string): Promise<PaymentMethod[]> {
return this.paymentMethodRepository.find({ shop: { id: restaurantId } }, { populate: ['shop'] }); return this.paymentMethodRepository.find({ shop: { id: shopId } }, { populate: ['shop'] });
} }
async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> { async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> {
@@ -235,10 +235,10 @@ export class PaymentsService {
return payment; return payment;
} }
findAllPaymentsByRestaurantId(restId: string, userId: string) { findAllPaymentsByRestaurantId(shopId: string, userId: string) {
return this.em.find( return this.em.find(
Payment, Payment,
{ order: { shop: { id: restId }, user: { id: userId } } }, { order: { shop: { id: shopId }, user: { id: userId } } },
{ populate: ['order', 'order.paymentMethod'] }, { populate: ['order', 'order.paymentMethod'] },
); );
} }
@@ -328,7 +328,7 @@ export class PaymentsService {
return payment; return payment;
} }
async getChartData(dto: PaymentChartDto, restaurantId: string): Promise<Array<{ date: string; cash: number; online: number }>> { async getChartData(dto: PaymentChartDto, shopId: string): Promise<Array<{ date: string; cash: number; online: number }>> {
const { startDate, endDate, type = ChartPeriodEnum.Daily } = dto; const { startDate, endDate, type = ChartPeriodEnum.Daily } = dto;
const start = startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const start = startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
@@ -351,8 +351,8 @@ export class PaymentsService {
const params: any[] = [startOfDay, endOfDay]; const params: any[] = [startOfDay, endOfDay];
let restaurantFilter = ''; let restaurantFilter = '';
if (restaurantId) { if (shopId) {
params.push(restaurantId); params.push(shopId);
restaurantFilter = `AND o.restaurant_id = ?`; restaurantFilter = `AND o.restaurant_id = ?`;
} }
@@ -374,7 +374,7 @@ export class PaymentsService {
ORDER BY DATE_TRUNC('${pgPeriod}', CAST(p.paid_at AS timestamp)) ASC ORDER BY DATE_TRUNC('${pgPeriod}', CAST(p.paid_at AS timestamp)) ASC
`; `;
this.logger.debug(`Chart query params: startOfDay=${startOfDay.toISOString()}, endOfDay=${endOfDay.toISOString()}, restaurantId=${restaurantId}`); this.logger.debug(`Chart query params: startOfDay=${startOfDay.toISOString()}, endOfDay=${endOfDay.toISOString()}, shopId=${shopId}`);
const result = await this.em.execute(query, params); const result = await this.em.execute(query, params);
this.logger.debug(`Chart query returned ${result.length} rows`); this.logger.debug(`Chart query returned ${result.length} rows`);
@@ -58,8 +58,8 @@ export class FoodController {
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'get my favorites' }) @ApiOperation({ summary: 'get my favorites' })
getMyFavorites(@UserId() userId: string, @ShopId() restId: string) { getMyFavorites(@UserId() userId: string, @ShopId() shopId: string) {
return this.productService.getMyFavorites(userId, restId); return this.productService.getMyFavorites(userId, shopId);
} }
/* ---------------------------------- Admin ---------------------------------- */ /* ---------------------------------- Admin ---------------------------------- */
@@ -68,8 +68,8 @@ export class FoodController {
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Post('admin/products') @Post('admin/products')
@ApiOperation({ summary: 'Create a new product' }) @ApiOperation({ summary: 'Create a new product' })
create(@Body() createFoodDto: CreateProductDto, @ShopId() restId: string) { create(@Body() createFoodDto: CreateProductDto, @ShopId() shopId: string) {
return this.productService.create(restId, createFoodDto); return this.productService.create(shopId, createFoodDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -84,8 +84,8 @@ export class FoodController {
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] }) @ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
@ApiQuery({ name: 'categoryId', required: false, type: String }) @ApiQuery({ name: 'categoryId', required: false, type: String })
@ApiQuery({ name: 'isActive', required: false, type: Boolean }) @ApiQuery({ name: 'isActive', required: false, type: Boolean })
async findAll(@Query() dto: FindProductsDto, @ShopId() restId: string) { async findAll(@Query() dto: FindProductsDto, @ShopId() shopId: string) {
const result = await this.productService.findAll(restId, dto); const result = await this.productService.findAll(shopId, dto);
return result; return result;
} }
@@ -95,8 +95,8 @@ export class FoodController {
@Get('admin/products/:id') @Get('admin/products/:id')
@ApiOperation({ summary: 'Get a product by id' }) @ApiOperation({ summary: 'Get a product by id' })
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
findById(@Param('id') id: string, @ShopId() restId: string) { findById(@Param('id') id: string, @ShopId() shopId: string) {
return this.productService.findAdminById(restId, id); return this.productService.findAdminById(shopId, id);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -106,15 +106,15 @@ export class FoodController {
@ApiOperation({ summary: 'Update a product' }) @ApiOperation({ summary: 'Update a product' })
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
@ApiBody({ type: UpdateFoodDto }) @ApiBody({ type: UpdateFoodDto })
update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto, @ShopId() restId: string) { update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto, @ShopId() shopId: string) {
return this.productService.update(restId, id, updateFoodDto); return this.productService.update(shopId, id, updateFoodDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Delete('admin/products/:id') @Delete('admin/products/:id')
@ApiOperation({ summary: 'Delete (soft) a product' }) @ApiOperation({ summary: 'Delete (soft) a product' })
remove(@Param('id') id: string, @ShopId() restId: string) { remove(@Param('id') id: string, @ShopId() shopId: string) {
return this.productService.remove(restId, id); return this.productService.remove(shopId, id);
} }
} }
@@ -23,12 +23,12 @@ export class ProductRepository extends EntityRepository<Product> {
* Find products with pagination and optional filters. * Find products with pagination and optional filters.
* Supports: search (title/content), categoryId, isActive, ordering. * Supports: search (title/content), categoryId, isActive, ordering.
*/ */
async findAllPaginated(restId: string, opts: FindFoodsOpts = {}): Promise<PaginatedResult<Product>> { async findAllPaginated(shopId: string, opts: FindFoodsOpts = {}): Promise<PaginatedResult<Product>> {
const { page = 1, limit = 10, search, orderBy = 'order', order = 'asc', categoryId, isActive } = opts; const { page = 1, limit = 10, search, orderBy = 'order', order = 'asc', categoryId, isActive } = opts;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const where: FilterQuery<Product> = { shop: { id: restId } }; const where: FilterQuery<Product> = { shop: { id: shopId } };
if (typeof isActive === 'boolean') { if (typeof isActive === 'boolean') {
where.isActive = isActive; where.isActive = isActive;
@@ -48,7 +48,7 @@ export class ProductRepository extends EntityRepository<Product> {
limit, limit,
offset, offset,
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
populate: ['category','variants'], populate: ['category', 'variants'],
}); });
const totalPages = Math.ceil(total / limit); const totalPages = Math.ceil(total / limit);
@@ -18,7 +18,7 @@ import {
import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { UserId } from 'src/common/decorators/user-id.decorator'; import { UserId } from 'src/common/decorators/user-id.decorator';
import { ShopId } from 'src/common/decorators/rest-id.decorator'; import { ShopId } from 'src/common/decorators/shop-id.decorator';
import { ReviewStatus } from '../enums/review-status.enum'; import { ReviewStatus } from '../enums/review-status.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permissions } from 'src/common/decorators/permissions.decorator';
import { Permission } from 'src/common/enums/permission.enum'; import { Permission } from 'src/common/enums/permission.enum';
@@ -88,8 +88,8 @@ export class ReviewController {
@Permissions(Permission.MANAGE_REVIEWS) @Permissions(Permission.MANAGE_REVIEWS)
@Get('admin/reviews') @Get('admin/reviews')
@ApiOperation({ summary: 'Get all reviews (admin - including unapproved)' }) @ApiOperation({ summary: 'Get all reviews (admin - including unapproved)' })
findAllAdmin(@Query() dto: FindReviewsDto, @ShopId() restId: string) { findAllAdmin(@Query() dto: FindReviewsDto, @ShopId() shopId: string) {
return this.reviewService.findAll({ ...dto, shopId: restId }); return this.reviewService.findAll({ ...dto, shopId: shopId });
} }
@Patch('admin/reviews/:id') @Patch('admin/reviews/:id')
@@ -106,8 +106,8 @@ export class ReviewController {
@Permissions(Permission.MANAGE_REVIEWS) @Permissions(Permission.MANAGE_REVIEWS)
@Get('admin/reviews/:id') @Get('admin/reviews/:id')
@ApiOperation({ summary: 'review detail' }) @ApiOperation({ summary: 'review detail' })
reviewDetail(@Param('id') reviewId: string, @ShopId() restaurantId: string) { reviewDetail(@Param('id') reviewId: string, @ShopId() shopId: string) {
return this.reviewService.findById(reviewId, restaurantId); return this.reviewService.findById(reviewId, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -118,9 +118,9 @@ export class ReviewController {
changeStatus( changeStatus(
@Param('id') reviewId: string, @Param('id') reviewId: string,
@Body() changeStatusDto: ChangeStatusDto, @Body() changeStatusDto: ChangeStatusDto,
@ShopId() restaurantId: string, @ShopId() shopId: string,
) { ) {
return this.reviewService.changeStatus(reviewId, changeStatusDto.status, restaurantId); return this.reviewService.changeStatus(reviewId, changeStatusDto.status, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
+2 -2
View File
@@ -1,13 +1,13 @@
export class ReviewCreatedEvent { export class ReviewCreatedEvent {
constructor( constructor(
public readonly reviewId: string, public readonly reviewId: string,
public readonly restaurantId: string, public readonly shopId: string,
public readonly userId: string, public readonly userId: string,
public readonly foodId: string, public readonly foodId: string,
public readonly foodName: string, public readonly foodName: string,
public readonly orderId: string, public readonly orderId: string,
public readonly orderNumber: string | number, public readonly orderNumber: string | number,
public readonly rating: number, public readonly rating: number,
) {} ) { }
} }
@@ -24,22 +24,22 @@ export class ReviewListeners {
async handleReviewCreated(event: ReviewCreatedEvent) { async handleReviewCreated(event: ReviewCreatedEvent) {
try { try {
this.logger.log( this.logger.log(
`Review created event received: ${event.reviewId} for shop: ${event.restaurantId}, product: ${event.foodName}, rating: ${event.rating}`, `Review created event received: ${event.reviewId} for shop: ${event.shopId}, product: ${event.foodName}, rating: ${event.rating}`,
); );
// Get admins of shop that have review management permissions // Get admins of shop that have review management permissions
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_REVIEWS); const admins = await this.adminService.findAdminsWithPermission(event.shopId, Permission.MANAGE_REVIEWS);
const recipients = admins.map(admin => ({ const recipients = admins.map(admin => ({
adminId: admin.id, adminId: admin.id,
})); }));
if (recipients.length === 0) { if (recipients.length === 0) {
this.logger.warn(`No admins found with MANAGE_REVIEWS permission for shop ${event.restaurantId}`); this.logger.warn(`No admins found with MANAGE_REVIEWS permission for shop ${event.shopId}`);
return; return;
} }
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, shopId: event.shopId,
message: { message: {
title: NotifTitleEnum.REVIEW_CREATED, title: NotifTitleEnum.REVIEW_CREATED,
content: `نظر جدید برای غذا "${event.foodName}" با امتیاز ${event.rating} از 5 برای سفارش شماره ${event.orderNumber} ثبت شد`, content: `نظر جدید برای غذا "${event.foodName}" با امتیاز ${event.rating} از 5 برای سفارش شماره ${event.orderNumber} ثبت شد`,
@@ -40,7 +40,7 @@ export class ReviewService {
throw new NotFoundException(UserMessage.USER_NOT_FOUND); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
// Find order and verify it belongs to the user, populate shop to get restaurantId // Find order and verify it belongs to the user, populate shop to get shopId
const order = await this.em.findOne(Order, { id: orderId, user: { id: userId } }, { populate: ['shop'] }); const order = await this.em.findOne(Order, { id: orderId, user: { id: userId } }, { populate: ['shop'] });
if (!order) { if (!order) {
throw new NotFoundException('Order not found or does not belong to the current user'); throw new NotFoundException('Order not found or does not belong to the current user');
@@ -117,12 +117,12 @@ export class ReviewService {
if (!shop) { if (!shop) {
throw new NotFoundException('RestaurantMessage.NOT_FOUND'); throw new NotFoundException('RestaurantMessage.NOT_FOUND');
} }
return this.reviewRepository.findAllPaginated({ ...restDto, restId: shop.id }); return this.reviewRepository.findAllPaginated({ ...restDto, shopId: shop.id });
} }
async findById(id: string, restaurantId: string): Promise<Review> { async findById(id: string, shopId: string): Promise<Review> {
const review = await this.reviewRepository.findOne( const review = await this.reviewRepository.findOne(
{ id, order: { shop: { id: restaurantId } } }, { id, order: { shop: { id: shopId } } },
{ populate: ['product', 'user', 'order'] }, { populate: ['product', 'user', 'order'] },
); );
if (!review) { if (!review) {
@@ -166,8 +166,8 @@ export class ReviewService {
return review; return review;
} }
async changeStatus(reviewId: string, status: ReviewStatus, restaurantId: string): Promise<Review> { async changeStatus(reviewId: string, status: ReviewStatus, shopId: string): Promise<Review> {
const review = await this.reviewRepository.findOne({ id: reviewId, order: { shop: { id: restaurantId } } }); const review = await this.reviewRepository.findOne({ id: reviewId, order: { shop: { id: shopId } } });
if (!review) { if (!review) {
throw new NotFoundException(ReviewMessage.NOT_FOUND); throw new NotFoundException(ReviewMessage.NOT_FOUND);
} }
@@ -12,7 +12,7 @@ type FindReviewsOpts = {
userId?: string; userId?: string;
status?: ReviewStatus; status?: ReviewStatus;
orderBy?: string; orderBy?: string;
restId?: string; shopId?: string;
order?: 'asc' | 'desc'; order?: 'asc' | 'desc';
}; };
@@ -27,7 +27,7 @@ export class ReviewRepository extends EntityRepository<Review> {
* Supports: foodId, userId, status, ordering. * Supports: foodId, userId, status, ordering.
*/ */
async findAllPaginated(opts: FindReviewsOpts = {}): Promise<PaginatedResult<Review>> { async findAllPaginated(opts: FindReviewsOpts = {}): Promise<PaginatedResult<Review>> {
const { page = 1, limit = 10, foodId, restId, userId, status, orderBy = 'createdAt', order = 'desc' } = opts; const { page = 1, limit = 10, foodId, shopId, userId, status, orderBy = 'createdAt', order = 'desc' } = opts;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
@@ -37,8 +37,8 @@ export class ReviewRepository extends EntityRepository<Review> {
where.product = { id: foodId }; where.product = { id: foodId };
} }
if (restId) { if (shopId) {
where.product = { shop: { id: restId } }; where.product = { shop: { id: shopId } };
} }
if (userId) { if (userId) {
@@ -26,8 +26,8 @@ export class RolesController {
@Permissions(Permission.MANAGE_ROLES) @Permissions(Permission.MANAGE_ROLES)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Get all through shop roles with pagination and filters' }) @ApiOperation({ summary: 'Get all through shop roles with pagination and filters' })
findAll(@ShopId() restId: string) { findAll(@ShopId() shopId: string) {
return this.roleService.findAllGeneralAndRestaurantRoles(restId); return this.roleService.findAllGeneralAndRestaurantRoles(shopId);
} }
@Get('admin/roles/permissions') @Get('admin/roles/permissions')
@@ -35,9 +35,9 @@ export class RolesController {
// @Permissions(Permission.MANAGE_ROLES) // @Permissions(Permission.MANAGE_ROLES)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Get all Admin permissions ' }) @ApiOperation({ summary: 'Get all Admin permissions ' })
async findAllPermissions(@AdminId() adminId: string, @ShopId() restId: string) { async findAllPermissions(@AdminId() adminId: string, @ShopId() shopId: string) {
console.log(adminId, restId) console.log(adminId, shopId)
const adminPermissionNames = await this.permissionService.getAdminFullPermissions(adminId, restId); const adminPermissionNames = await this.permissionService.getAdminFullPermissions(adminId, shopId);
return adminPermissionNames; return adminPermissionNames;
} }
@@ -50,8 +50,8 @@ export class RolesController {
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Create a new role' }) @ApiOperation({ summary: 'Create a new role' })
@ApiBody({ type: CreateRoleDto }) @ApiBody({ type: CreateRoleDto })
create(@Body() dto: CreateRoleDto, @ShopId() restId: string) { create(@Body() dto: CreateRoleDto, @ShopId() shopId: string) {
return this.roleService.createRestaurantRole(dto, restId); return this.roleService.createRestaurantRole(dto, shopId);
} }
@Get('admin/roles/:id') @Get('admin/roles/:id')
@@ -59,8 +59,8 @@ export class RolesController {
@Permissions(Permission.MANAGE_ROLES) @Permissions(Permission.MANAGE_ROLES)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Get a specific role by ID' }) @ApiOperation({ summary: 'Get a specific role by ID' })
findOne(@Param('id') id: string, @ShopId() restId: string) { findOne(@Param('id') id: string, @ShopId() shopId: string) {
return this.roleService.findOne(restId, id); return this.roleService.findOne(shopId, id);
} }
@Patch('admin/roles/:id') @Patch('admin/roles/:id')
@@ -69,8 +69,8 @@ export class RolesController {
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Update a role' }) @ApiOperation({ summary: 'Update a role' })
@ApiBody({ type: UpdateRoleDto }) @ApiBody({ type: UpdateRoleDto })
update(@Param('id') id: string, @Body() dto: UpdateRoleDto, @ShopId() restId: string) { update(@Param('id') id: string, @Body() dto: UpdateRoleDto, @ShopId() shopId: string) {
return this.roleService.update(restId, id, dto); return this.roleService.update(shopId, id, dto);
} }
@Delete('admin/roles/:id') @Delete('admin/roles/:id')
@@ -78,8 +78,8 @@ export class RolesController {
@Permissions(Permission.MANAGE_ROLES) @Permissions(Permission.MANAGE_ROLES)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Delete a role' }) @ApiOperation({ summary: 'Delete a role' })
remove(@Param('id') id: string, @ShopId() restId: string) { remove(@Param('id') id: string, @ShopId() shopId: string) {
return this.roleService.remove(restId, id); return this.roleService.remove(shopId, id);
} }
/** Super Admin Endpoints */ /** Super Admin Endpoints */
@@ -24,8 +24,8 @@ export class PermissionsService {
} }
async getAdminPermissions(adminId: string, restId: string): Promise<string[]> { async getAdminPermissions(adminId: string, shopId: string): Promise<string[]> {
const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`; const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${shopId}`;
// Try to get from cache first // Try to get from cache first
const cachedPermissions = await this.cacheService.get<string>(cacheKey); const cachedPermissions = await this.cacheService.get<string>(cacheKey);
@@ -44,7 +44,7 @@ export class PermissionsService {
// If not in cache, fetch from database // If not in cache, fetch from database
const admin = await this.adminRepository.findOne( const admin = await this.adminRepository.findOne(
{ id: adminId, roles: { shop: { id: restId } } }, { id: adminId, roles: { shop: { id: shopId } } },
{ populate: ['roles', 'roles.role', 'roles.role.permissions'] }, { populate: ['roles', 'roles.role', 'roles.role.permissions'] },
); );
@@ -72,14 +72,14 @@ export class PermissionsService {
return permissions.flat().map(p => p.name); return permissions.flat().map(p => p.name);
} }
async getAdminFullPermissions(adminId: string, restId: string): Promise<Permission[]> { async getAdminFullPermissions(adminId: string, shopId: string): Promise<Permission[]> {
const listOfPermissions = [] const listOfPermissions = []
const adminRoles = await this.em.findOne(AdminRole, { const adminRoles = await this.em.findOne(AdminRole, {
admin: { admin: {
id: adminId, id: adminId,
}, },
shop: { shop: {
id: restId, id: shopId,
}, },
}, { populate: ['role', 'role.permissions'] }); }, { populate: ['role', 'role.permissions'] });
if (adminRoles) { if (adminRoles) {
@@ -89,8 +89,8 @@ export class PermissionsService {
} }
async invalidateAdminPermissionsCache(adminId: string, restId: string): Promise<void> { async invalidateAdminPermissionsCache(adminId: string, shopId: string): Promise<void> {
const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${restId}`; const cacheKey = `${this.ADMIN_PERMISSIONS_KEY}:${adminId}:${shopId}`;
await this.cacheService.del(cacheKey); await this.cacheService.del(cacheKey);
} }
} }
+12 -12
View File
@@ -19,18 +19,18 @@ export class RolesService {
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
async createRestaurantRole(dto: CreateRoleDto, restId: string) { async createRestaurantRole(dto: CreateRoleDto, shopId: string) {
const { name, permissionIds } = dto; const { name, permissionIds } = dto;
// Check if role already exists // Check if role already exists
const existing = await this.roleRepository.findOne({ name, shop: restId ? { id: restId } : null }); const existing = await this.roleRepository.findOne({ name, shop: shopId ? { id: shopId } : null });
if (existing) { if (existing) {
throw new BadRequestException('Role with this name already exists for the shop'); throw new BadRequestException('Role with this name already exists for the shop');
} }
let shop: Shop | null = null; let shop: Shop | null = null;
if (restId) { if (shopId) {
shop = await this.em.findOne(Shop, { id: restId }); shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException('Shop not found'); throw new NotFoundException('Shop not found');
} }
@@ -55,8 +55,8 @@ export class RolesService {
return role; return role;
} }
async findAllGeneralAndRestaurantRoles(restId: string) { async findAllGeneralAndRestaurantRoles(shopId: string) {
const where: FilterQuery<Role> = { $or: [{ shop: restId }, { shop: null }], isSystem: false }; const where: FilterQuery<Role> = { $or: [{ shop: shopId }, { shop: null }], isSystem: false };
const roles = await this.roleRepository.find(where, { const roles = await this.roleRepository.find(where, {
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
@@ -66,9 +66,9 @@ export class RolesService {
return roles; return roles;
} }
async findOne(restId: string, id: string) { async findOne(shopId: string, id: string) {
const role = await this.roleRepository.findOne( const role = await this.roleRepository.findOne(
{ id, shop: { id: restId } }, { id, shop: { id: shopId } },
{ populate: ['permissions', 'shop'] }, { populate: ['permissions', 'shop'] },
); );
if (!role) { if (!role) {
@@ -77,9 +77,9 @@ export class RolesService {
return role; return role;
} }
async update(restId: string, id: string, dto: UpdateRoleDto) { async update(shopId: string, id: string, dto: UpdateRoleDto) {
const role = await this.roleRepository.findOne( const role = await this.roleRepository.findOne(
{ id, shop: { id: restId } }, { id, shop: { id: shopId } },
{ populate: ['permissions', 'shop'] }, { populate: ['permissions', 'shop'] },
); );
if (!role) { if (!role) {
@@ -116,9 +116,9 @@ export class RolesService {
return roles; return roles;
} }
async remove(restId: string, id: string) { async remove(shopId: string, id: string) {
const role = await this.roleRepository.findOne( const role = await this.roleRepository.findOne(
{ id, shop: { id: restId } }, { id, shop: { id: shopId } },
{ populate: ['permissions', 'shop'] }, { populate: ['permissions', 'shop'] },
); );
if (!role) { if (!role) {
@@ -17,7 +17,7 @@ import { UpdateScheduleDto } from '../dto/update-schedule.dto';
import { FindSchedulesDto } from '../dto/find-schedules.dto'; import { FindSchedulesDto } from '../dto/find-schedules.dto';
import { Schedule } from '../entities/schedule.entity'; import { Schedule } from '../entities/schedule.entity';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { ShopId } from 'src/common/decorators/rest-id.decorator'; import { ShopId } from 'src/common/decorators/shop-id.decorator';
import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permissions } from 'src/common/decorators/permissions.decorator';
import { Permission } from 'src/common/enums/permission.enum'; import { Permission } from 'src/common/enums/permission.enum';
import { API_HEADER_SLUG } from 'src/common/constants'; import { API_HEADER_SLUG } from 'src/common/constants';
@@ -45,8 +45,8 @@ export class ScheduleController {
@ApiOperation({ summary: 'Create a new schedule' }) @ApiOperation({ summary: 'Create a new schedule' })
@ApiBody({ type: CreateScheduleDto }) @ApiBody({ type: CreateScheduleDto })
@ApiCreatedResponse({ description: 'Schedule created successfully', type: Schedule }) @ApiCreatedResponse({ description: 'Schedule created successfully', type: Schedule })
create(@Body() createScheduleDto: CreateScheduleDto, @ShopId() restId: string) { create(@Body() createScheduleDto: CreateScheduleDto, @ShopId() shopId: string) {
return this.scheduleService.create(restId, createScheduleDto); return this.scheduleService.create(shopId, createScheduleDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -63,9 +63,9 @@ export class ScheduleController {
@ApiOkResponse({ description: 'List of schedules', type: [Schedule] }) @ApiOkResponse({ description: 'List of schedules', type: [Schedule] })
findAll( findAll(
@Query(new ValidationPipe({ transform: true, whitelist: true })) query: FindSchedulesDto, @Query(new ValidationPipe({ transform: true, whitelist: true })) query: FindSchedulesDto,
@ShopId() restId: string, @ShopId() shopId: string,
) { ) {
return this.scheduleService.findAll(restId, query); return this.scheduleService.findAll(shopId, query);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -76,8 +76,8 @@ export class ScheduleController {
@ApiParam({ name: 'id', description: 'Schedule ID' }) @ApiParam({ name: 'id', description: 'Schedule ID' })
@ApiOkResponse({ description: 'Schedule details', type: Schedule }) @ApiOkResponse({ description: 'Schedule details', type: Schedule })
@ApiNotFoundResponse({ description: 'Schedule not found' }) @ApiNotFoundResponse({ description: 'Schedule not found' })
findOne(@Param('id') id: string, @ShopId() restId: string) { findOne(@Param('id') id: string, @ShopId() shopId: string) {
return this.scheduleService.findOne(id, restId); return this.scheduleService.findOne(id, shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -89,8 +89,8 @@ export class ScheduleController {
@ApiBody({ type: UpdateScheduleDto }) @ApiBody({ type: UpdateScheduleDto })
@ApiOkResponse({ description: 'Schedule updated successfully', type: Schedule }) @ApiOkResponse({ description: 'Schedule updated successfully', type: Schedule })
@ApiNotFoundResponse({ description: 'Schedule not found' }) @ApiNotFoundResponse({ description: 'Schedule not found' })
update(@Param('id') id: string, @Body() updateScheduleDto: UpdateScheduleDto, @ShopId() restId: string) { update(@Param('id') id: string, @Body() updateScheduleDto: UpdateScheduleDto, @ShopId() shopId: string) {
return this.scheduleService.update(id, restId, updateScheduleDto); return this.scheduleService.update(id, shopId, updateScheduleDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -101,7 +101,7 @@ export class ScheduleController {
@ApiParam({ name: 'id', description: 'Schedule ID' }) @ApiParam({ name: 'id', description: 'Schedule ID' })
@ApiOkResponse({ description: 'Schedule deleted successfully' }) @ApiOkResponse({ description: 'Schedule deleted successfully' })
@ApiNotFoundResponse({ description: 'Schedule not found' }) @ApiNotFoundResponse({ description: 'Schedule not found' })
remove(@Param('id') id: string, @ShopId() restId: string) { remove(@Param('id') id: string, @ShopId() shopId: string) {
return this.scheduleService.remove(id, restId); return this.scheduleService.remove(id, shopId);
} }
} }
@@ -41,8 +41,8 @@ export class ShopController {
@Permissions(Permission.UPDATE_RESTAURANT) @Permissions(Permission.UPDATE_RESTAURANT)
@ApiOperation({ summary: 'Get shop by ID from request' }) @ApiOperation({ summary: 'Get shop by ID from request' })
@Get('admin/shops/my-shop') @Get('admin/shops/my-shop')
async findOne(@ShopId() restId: string) { async findOne(@ShopId() shopId: string) {
return await this.shopService.findOne(restId); return await this.shopService.findOne(shopId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -51,8 +51,8 @@ export class ShopController {
@ApiOperation({ summary: 'Update a shop' }) @ApiOperation({ summary: 'Update a shop' })
@ApiBody({ type: UpdateRestaurantDto }) @ApiBody({ type: UpdateRestaurantDto })
@Patch('admin/shops/my-shop') @Patch('admin/shops/my-shop')
updateMyRestaurant(@ShopId() restId: string, @Body() updateRestaurantDto: UpdateRestaurantDto) { updateMyRestaurant(@ShopId() shopId: string, @Body() updateRestaurantDto: UpdateRestaurantDto) {
return this.shopService.update(restId, updateRestaurantDto); return this.shopService.update(shopId, updateRestaurantDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -16,5 +16,5 @@ export class Schedule extends BaseEntity {
isActive: boolean = true; isActive: boolean = true;
@Property() @Property()
restId!: string; shopId!: string;
} }
+14 -14
View File
@@ -19,10 +19,10 @@ export class ScheduleService {
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
async create(restId: string, createScheduleDto: CreateScheduleDto): Promise<Schedule> { async create(shopId: string, createScheduleDto: CreateScheduleDto): Promise<Schedule> {
const rest = await this.restRepository.findOne({ id: restId }); const rest = await this.restRepository.findOne({ id: shopId });
if (!rest) { if (!rest) {
throw new NotFoundException(`رستوران با شناسه ${restId} یافت نشد.`); throw new NotFoundException(`رستوران با شناسه ${shopId} یافت نشد.`);
} }
const data: RequiredEntityData<Schedule> = { const data: RequiredEntityData<Schedule> = {
@@ -30,7 +30,7 @@ export class ScheduleService {
openTime: createScheduleDto.openTime, openTime: createScheduleDto.openTime,
closeTime: createScheduleDto.closeTime, closeTime: createScheduleDto.closeTime,
isActive: createScheduleDto.isActive ?? true, isActive: createScheduleDto.isActive ?? true,
restId, shopId,
}; };
const schedule = this.scheduleRepository.create(data); const schedule = this.scheduleRepository.create(data);
@@ -38,8 +38,8 @@ export class ScheduleService {
return schedule; return schedule;
} }
async findAll(restId?: string, query?: FindSchedulesDto): Promise<Schedule[]> { async findAll(shopId?: string, query?: FindSchedulesDto): Promise<Schedule[]> {
const where: FilterQuery<Schedule> = restId ? { restId } : {}; const where: FilterQuery<Schedule> = shopId ? { shopId } : {};
if (query?.weekDay !== undefined) { if (query?.weekDay !== undefined) {
where.weekDay = query.weekDay; where.weekDay = query.weekDay;
@@ -57,7 +57,7 @@ export class ScheduleService {
const weekDay = today.getDay(); // 0 = Sunday, 6 = Saturday const weekDay = today.getDay(); // 0 = Sunday, 6 = Saturday
const where: FilterQuery<Schedule> = { const where: FilterQuery<Schedule> = {
restId: shop.id.toString(), shopId: shop.id.toString(),
weekDay, weekDay,
isActive: true, isActive: true,
}; };
@@ -72,23 +72,23 @@ export class ScheduleService {
} }
const where: FilterQuery<Schedule> = { const where: FilterQuery<Schedule> = {
restId: shop.id.toString(), shopId: shop.id.toString(),
isActive: true, isActive: true,
}; };
return this.scheduleRepository.find(where); return this.scheduleRepository.find(where);
} }
async findOne(id: string, restId: string): Promise<Schedule> { async findOne(id: string, shopId: string): Promise<Schedule> {
const schedule = await this.scheduleRepository.findOne({ id, restId }); const schedule = await this.scheduleRepository.findOne({ id, shopId });
if (!schedule) { if (!schedule) {
throw new NotFoundException(`برنامه با شناسه ${id} یافت نشد.`); throw new NotFoundException(`برنامه با شناسه ${id} یافت نشد.`);
} }
return schedule; return schedule;
} }
async update(id: string, restId: string, updateScheduleDto: UpdateScheduleDto): Promise<Schedule> { async update(id: string, shopId: string, updateScheduleDto: UpdateScheduleDto): Promise<Schedule> {
const schedule = await this.scheduleRepository.findOne({ id, restId }); const schedule = await this.scheduleRepository.findOne({ id, shopId });
if (!schedule) { if (!schedule) {
throw new NotFoundException(`برنامه با شناسه ${id} یافت نشد.`); throw new NotFoundException(`برنامه با شناسه ${id} یافت نشد.`);
} }
@@ -112,8 +112,8 @@ export class ScheduleService {
return schedule; return schedule;
} }
async remove(id: string, restId: string): Promise<void> { async remove(id: string, shopId: string): Promise<void> {
const schedule = await this.scheduleRepository.findOne({ id, restId }); const schedule = await this.scheduleRepository.findOne({ id, shopId });
if (!schedule) { if (!schedule) {
throw new NotFoundException(`برنامه با شناسه ${id} یافت نشد.`); throw new NotFoundException(`برنامه با شناسه ${id} یافت نشد.`);
} }
+3 -3
View File
@@ -8,7 +8,7 @@ import { ShopRepository } from '../repositories/rest.repository';
import { RestMessage } from 'src/common/enums/message.enum'; import { RestMessage } from 'src/common/enums/message.enum';
import { FindRestaurantsDto } from '../dto/find-shops.dto'; import { FindRestaurantsDto } from '../dto/find-shops.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { Admin } from '../../admin/entities/admin.entity'; import { Admin } from '../../admin/entities/admin.entity';
import { AdminRole } from '../../admin/entities/adminRole.entity'; import { AdminRole } from '../../admin/entities/adminRole.entity';
import { Role } from '../../roles/entities/role.entity'; import { Role } from '../../roles/entities/role.entity';
import { normalizePhone } from 'src/modules/utils/phone.util'; import { normalizePhone } from 'src/modules/utils/phone.util';
@@ -62,7 +62,7 @@ export class ShopService {
subscriptionStartDate: dto.subscriptionStartDate, subscriptionStartDate: dto.subscriptionStartDate,
}); });
const role = await em.findOne(Role, { isSystem:true }); const role = await em.findOne(Role, { isSystem: true });
if (!role) { if (!role) {
throw new BadRequestException(`System role not found`); throw new BadRequestException(`System role not found`);
} }
@@ -219,7 +219,7 @@ export class ShopService {
await em.nativeDelete(Delivery, { shop: id }); await em.nativeDelete(Delivery, { shop: id });
// Delete schedules // Delete schedules
await em.nativeDelete(Schedule, { restId: id }); await em.nativeDelete(Schedule, { shopId: id });
// Delete notification preferences // Delete notification preferences
await em.nativeDelete(NotificationPreference, { shop: id }); await em.nativeDelete(NotificationPreference, { shop: id });
@@ -39,8 +39,8 @@ export class UsersController {
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiBody({ type: UpdateUserDto }) @ApiBody({ type: UpdateUserDto })
@Patch('public/user/update') @Patch('public/user/update')
async updateUser(@UserId() userId: string, @ShopId() restId: string, @Body() dto: UpdateUserDto) { async updateUser(@UserId() userId: string, @ShopId() shopId: string, @Body() dto: UpdateUserDto) {
const user = await this.userService.updateUser(userId, restId, dto); const user = await this.userService.updateUser(userId, shopId, dto);
return user; return user;
} }
@@ -60,8 +60,8 @@ export class UsersController {
@ApiOperation({ summary: 'Get User Wallet' }) @ApiOperation({ summary: 'Get User Wallet' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@Get('public/user/wallet/balance') @Get('public/user/wallet/balance')
async getUserWalletBalance(@UserId() userId: string, @ShopId() restId: string) { async getUserWalletBalance(@UserId() userId: string, @ShopId() shopId: string) {
const wallet = await this.walletService.getUserCurrentWalletBalance(userId, restId); const wallet = await this.walletService.getUserCurrentWalletBalance(userId, shopId);
return wallet; return wallet;
} }
@@ -70,8 +70,8 @@ export class UsersController {
@ApiOperation({ summary: 'Get User Wallet' }) @ApiOperation({ summary: 'Get User Wallet' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@Get('public/user/points/balance') @Get('public/user/points/balance')
async getUserWallet(@UserId() userId: string, @ShopId() restId: string) { async getUserWallet(@UserId() userId: string, @ShopId() shopId: string) {
const wallet = await this.walletService.getUserCurrentPoinrBalance(userId, restId); const wallet = await this.walletService.getUserCurrentPoinrBalance(userId, shopId);
return wallet; return wallet;
} }
@@ -82,11 +82,11 @@ export class UsersController {
@Get('public/user/wallet/transactions') @Get('public/user/wallet/transactions')
async getUserWalletTransactions( async getUserWalletTransactions(
@UserId() userId: string, @UserId() userId: string,
@ShopId() restId: string, @ShopId() shopId: string,
@Query(new ValidationPipe({ transform: true, whitelist: true })) @Query(new ValidationPipe({ transform: true, whitelist: true }))
query: FindWalletTransactionsDto, query: FindWalletTransactionsDto,
) { ) {
const transactions = await this.userService.getUserWalletTransactions(userId, restId, query); const transactions = await this.userService.getUserWalletTransactions(userId, shopId, query);
return transactions; return transactions;
} }
@@ -159,8 +159,8 @@ export class UsersController {
@ApiOperation({ summary: 'Convert user score (points) to wallet balance' }) @ApiOperation({ summary: 'Convert user score (points) to wallet balance' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@Post('public/user/convert-score-to-wallet') @Post('public/user/convert-score-to-wallet')
async convertScoreToWallet(@UserId() userId: string, @ShopId() restId: string) { async convertScoreToWallet(@UserId() userId: string, @ShopId() shopId: string) {
await this.userService.convertScoreToWallet(userId, restId); await this.userService.convertScoreToWallet(userId, shopId);
return { return {
message: UserSuccessMessage.SCORE_CONVERTED_SUCCESS, message: UserSuccessMessage.SCORE_CONVERTED_SUCCESS,
}; };
@@ -176,8 +176,8 @@ export class UsersController {
async findAllRestaurantUsers( async findAllRestaurantUsers(
@Query(new ValidationPipe({ transform: true, whitelist: true })) @Query(new ValidationPipe({ transform: true, whitelist: true }))
query: FindUsersDto, query: FindUsersDto,
@ShopId() restId: string, @ShopId() shopId: string,
) { ) {
return this.userService.findAll(restId, query); return this.userService.findAll(shopId, query);
} }
} }
+15 -15
View File
@@ -75,7 +75,7 @@ export class UserService {
return this.userRepository.findOne({ id }); return this.userRepository.findOne({ id });
} }
// todo : transaction code here // todo : transaction code here
async create(phone: string, restId: string): Promise<User> { async create(phone: string, shopId: string): Promise<User> {
const normalizedPhone = normalizePhone(phone); const normalizedPhone = normalizePhone(phone);
const _raw = { const _raw = {
phone: normalizedPhone, phone: normalizedPhone,
@@ -84,7 +84,7 @@ export class UserService {
marriageDate: undefined, marriageDate: undefined,
}; };
// get registerscore from shop // get registerscore from shop
const shop = await this.restaurantRepository.findOne({ id: restId }); const shop = await this.restaurantRepository.findOne({ id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException(`Shop not found.`); throw new NotFoundException(`Shop not found.`);
} }
@@ -104,7 +104,7 @@ export class UserService {
return user; return user;
} }
async updateUser(userId: string, restId: string, dto: UpdateUserDto): Promise<User> { async updateUser(userId: string, shopId: string, dto: UpdateUserDto): Promise<User> {
const user = await this.userRepository.findOne({ id: userId }); const user = await this.userRepository.findOne({ id: userId });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(`User with ID ${userId} not found.`);
@@ -125,7 +125,7 @@ export class UserService {
return user; return user;
} }
async findAll(restId: string, dto: FindUsersDto): Promise<PaginatedResult<User>> { async findAll(shopId: string, dto: FindUsersDto): Promise<PaginatedResult<User>> {
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto; const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto;
// 1. Calculate pagination // 1. Calculate pagination
@@ -135,7 +135,7 @@ export class UserService {
const where: FilterQuery<User> = { const where: FilterQuery<User> = {
orders: { orders: {
shop: { shop: {
id: restId, id: shopId,
}, },
}, },
}; };
@@ -306,17 +306,17 @@ export class UserService {
return address; return address;
} }
async convertScoreToWallet(userId: string, restId: string) { async convertScoreToWallet(userId: string, shopId: string) {
return this.em.transactional(async em => { return this.em.transactional(async em => {
const user = await em.findOne(User, { id: userId }); const user = await em.findOne(User, { id: userId });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(`User with ID ${userId} not found.`);
} }
const shop = await em.findOne(Shop, { id: restId }); const shop = await em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException(`Shop with ID ${restId} not found.`); throw new NotFoundException(`Shop with ID ${shopId} not found.`);
} }
let walletTransaction = await em.findOne(WalletTransaction, { user: { id: userId }, shop: { id: restId } }, let walletTransaction = await em.findOne(WalletTransaction, { user: { id: userId }, shop: { id: shopId } },
{ orderBy: { createdAt: 'DESC' } }); { orderBy: { createdAt: 'DESC' } });
if (!walletTransaction) { if (!walletTransaction) {
@@ -370,18 +370,18 @@ export class UserService {
}); });
} }
getUserWallet(userId: string, restId: string): Promise<WalletTransaction | null> { getUserWallet(userId: string, shopId: string): Promise<WalletTransaction | null> {
return this.walletTransactionRepository.findOne({ user: { id: userId }, shop: { id: restId } }, return this.walletTransactionRepository.findOne({ user: { id: userId }, shop: { id: shopId } },
{ orderBy: { createdAt: 'DESC' } }); { orderBy: { createdAt: 'DESC' } });
} }
getUserWalletTransactions(userId: string, restId: string, dto: FindWalletTransactionsDto) { getUserWalletTransactions(userId: string, shopId: string, dto: FindWalletTransactionsDto) {
return this.walletService.getUserWalletTransactions(userId, restId, dto); return this.walletService.getUserWalletTransactions(userId, shopId, dto);
} }
async createWalletTransaction(userId: string, restId: string, dto: CreateWalletTransactionDto) { async createWalletTransaction(userId: string, shopId: string, dto: CreateWalletTransactionDto) {
return this.em.transactional(async em => { return this.em.transactional(async em => {
return this.walletService.createTransaction(em, userId, restId, dto); return this.walletService.createTransaction(em, userId, shopId, dto);
}); });
} }
+10 -10
View File
@@ -14,11 +14,11 @@ export class WalletService {
constructor( constructor(
private readonly walletTransactionRepository: WalletTransactionRepository, private readonly walletTransactionRepository: WalletTransactionRepository,
private readonly pointTransactionRepository: PointTransactionRepository, private readonly pointTransactionRepository: PointTransactionRepository,
) {} ) { }
async getUserWalletTransactions( async getUserWalletTransactions(
userId: string, userId: string,
restId: string, shopId: string,
dto: FindWalletTransactionsDto, dto: FindWalletTransactionsDto,
): Promise<PaginatedResult<WalletTransaction>> { ): Promise<PaginatedResult<WalletTransaction>> {
const { const {
@@ -40,17 +40,17 @@ export class WalletService {
// Find the user's wallet for this shop // Find the user's wallet for this shop
const walletTransaction = await this.walletTransactionRepository.findOne({ const walletTransaction = await this.walletTransactionRepository.findOne({
user: { id: userId }, user: { id: userId },
shop: { id: restId }, shop: { id: shopId },
}); });
if (!walletTransaction) { if (!walletTransaction) {
throw new NotFoundException(`User wallet not found for user ${userId} and shop ${restId}.`); throw new NotFoundException(`User wallet not found for user ${userId} and shop ${shopId}.`);
} }
// Build the 'where' filter query // Build the 'where' filter query
const where: FilterQuery<WalletTransaction> = { const where: FilterQuery<WalletTransaction> = {
user: { id: userId }, user: { id: userId },
shop: { id: restId }, shop: { id: shopId },
}; };
// Add type filter // Add type filter
@@ -107,11 +107,11 @@ export class WalletService {
}; };
} }
getUserWallet(userId: string, restId: string): Promise<WalletTransaction | null> { getUserWallet(userId: string, shopId: string): Promise<WalletTransaction | null> {
return this.walletTransactionRepository.findOne( return this.walletTransactionRepository.findOne(
{ {
user: { id: userId }, user: { id: userId },
shop: { id: restId }, shop: { id: shopId },
}, },
{ orderBy: { createdAt: 'DESC' } }, { orderBy: { createdAt: 'DESC' } },
); );
@@ -120,7 +120,7 @@ export class WalletService {
async createTransaction( async createTransaction(
em: EntityManager, em: EntityManager,
userId: string, userId: string,
restId: string, shopId: string,
dto: CreateWalletTransactionDto, dto: CreateWalletTransactionDto,
): Promise<WalletTransaction> { ): Promise<WalletTransaction> {
const { amount, type, reason } = dto; const { amount, type, reason } = dto;
@@ -133,11 +133,11 @@ export class WalletService {
// Find the user's wallet for this shop // Find the user's wallet for this shop
const walletTransaction = await em.findOne(WalletTransaction, { const walletTransaction = await em.findOne(WalletTransaction, {
user: { id: userId }, user: { id: userId },
shop: { id: restId }, shop: { id: shopId },
}); });
if (!walletTransaction) { if (!walletTransaction) {
throw new NotFoundException(`User wallet not found for user ${userId} and shop ${restId}.`); throw new NotFoundException(`User wallet not found for user ${userId} and shop ${shopId}.`);
} }
// Calculate new balance // Calculate new balance
@@ -7,10 +7,10 @@ export class PointTransactionRepository extends EntityRepository<PointTransactio
constructor(readonly em: EntityManager) { constructor(readonly em: EntityManager) {
super(em, PointTransaction); super(em, PointTransaction);
} }
async getcurrentPointBalance(userId: string, restaurantId: string): Promise<number> { async getcurrentPointBalance(userId: string, shopId: string): Promise<number> {
const pointTransaction = await this.em.findOne(PointTransaction, { const pointTransaction = await this.em.findOne(PointTransaction, {
user: { id: userId }, user: { id: userId },
shop: { id: restaurantId }, shop: { id: shopId },
}); });
return pointTransaction?.balance || 0; return pointTransaction?.balance || 0;
} }
@@ -7,12 +7,12 @@ export class WalletTransactionRepository extends EntityRepository<WalletTransact
constructor(readonly em: EntityManager) { constructor(readonly em: EntityManager) {
super(em, WalletTransaction); super(em, WalletTransaction);
} }
async getCurrentWalletBalance(userId: string, restaurantId: string): Promise<number> { async getCurrentWalletBalance(userId: string, shopId: string): Promise<number> {
const walletTransaction = await this.em.findOne( const walletTransaction = await this.em.findOne(
WalletTransaction, WalletTransaction,
{ {
user: { id: userId }, user: { id: userId },
shop: { id: restaurantId }, shop: { id: shopId },
}, },
{ orderBy: { createdAt: 'desc' } }, { orderBy: { createdAt: 'desc' } },
); );
+2 -2
View File
@@ -13,7 +13,7 @@ export class SchedulesSeeder {
// Create 3 schedules per day // Create 3 schedules per day
for (const timeSlot of timeSlots) { for (const timeSlot of timeSlots) {
const existing = await em.findOne(Schedule, { const existing = await em.findOne(Schedule, {
restId: shop.id, shopId: shop.id,
weekDay, weekDay,
openTime: timeSlot.openTime, openTime: timeSlot.openTime,
closeTime: timeSlot.closeTime, closeTime: timeSlot.closeTime,
@@ -21,7 +21,7 @@ export class SchedulesSeeder {
if (!existing) { if (!existing) {
const schedule = em.create(Schedule, { const schedule = em.create(Schedule, {
restId: shop.id, shopId: shop.id,
weekDay, weekDay,
openTime: timeSlot.openTime, openTime: timeSlot.openTime,
closeTime: timeSlot.closeTime, closeTime: timeSlot.closeTime,