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