This commit is contained in:
2026-02-14 16:17:20 +03:30
parent eb4d19ab81
commit 7bf7d0b1c3
18 changed files with 135 additions and 88 deletions
+40 -2
View File
@@ -58,6 +58,10 @@ export const enum AuthMessage {
NATIONAL_CODE_INVALID = 'کد ملی نامعتبر است', NATIONAL_CODE_INVALID = 'کد ملی نامعتبر است',
INVALID_REFRESH_TOKEN = 'توکن رفرش نامعتبر است', INVALID_REFRESH_TOKEN = 'توکن رفرش نامعتبر است',
REFRESH_TOKEN_EXPIRED = 'توکن رفرش منقضی شده است', REFRESH_TOKEN_EXPIRED = 'توکن رفرش منقضی شده است',
REFRESH_TOKEN_MUST_BE_STRING = 'توکن رفرش باید یک رشته باشد',
REFRESH_TOKEN_REQUIRED = 'توکن رفرش الزامی است',
FAILED_TO_REFRESH_TOKEN = 'تمدید توکن با خطا مواجه شد',
OTP_EXPIRED_OR_NOT_FOUND = 'کد منقضی شده یا یافت نشد',
LOGOUT_SUCCESS = 'خروج با موفقیت انجام شد', LOGOUT_SUCCESS = 'خروج با موفقیت انجام شد',
CURRENT_PASSWORD_INVALID = 'رمز عبور فعلی نامعتبر است', CURRENT_PASSWORD_INVALID = 'رمز عبور فعلی نامعتبر است',
TOKEN_EXPIRED_OR_INVALID = 'توکن منقضی شده یا نامعتبر است', TOKEN_EXPIRED_OR_INVALID = 'توکن منقضی شده یا نامعتبر است',
@@ -73,6 +77,11 @@ export const enum AuthMessage {
} }
export const enum ProductMessage { export const enum ProductMessage {
NOT_FOUND = 'محصولی با این مشخصات یافت نشد', NOT_FOUND = 'محصولی با این مشخصات یافت نشد',
ATTRIBUTE_REQUIRED_FOR_VARIANTS = 'برای محصولات با چند نوع، ویژگی الزامی است',
VARIANT_NOT_FOUND = 'نوع محصول یافت نشد',
PRODUCT_NOT_BELONGS_TO_SHOP = 'محصول به فروشگاه شما تعلق ندارد',
FAVORITE_REMOVED = 'محصول از علاقه‌مندی‌ها حذف شد',
FAVORITE_ADDED = 'محصول به علاقه‌مندی‌ها اضافه شد',
} }
export const enum CategoryMessage { export const enum CategoryMessage {
NOT_FOUND = 'دسته‌بندی مورد نظر یافت نشد', NOT_FOUND = 'دسته‌بندی مورد نظر یافت نشد',
@@ -129,6 +138,11 @@ export const enum UserMessage {
PUSH_TOKEN_ADDED_SUCCESSFULLY = 'توکن پوش با موفقیت اضافه شد', PUSH_TOKEN_ADDED_SUCCESSFULLY = 'توکن پوش با موفقیت اضافه شد',
NO_PUSH_TOKENS_FOUND = 'توکن پوشی برای این کاربر یافت نشد', NO_PUSH_TOKENS_FOUND = 'توکن پوشی برای این کاربر یافت نشد',
PUSH_TOKEN_REMOVED_SUCCESSFULLY = 'توکن پوش با موفقیت حذف شد', PUSH_TOKEN_REMOVED_SUCCESSFULLY = 'توکن پوش با موفقیت حذف شد',
ADDRESS_NOT_FOUND = 'آدرس یافت نشد',
POINTS_MUST_BE_GREATER_THAN_ZERO = 'امتیاز تبدیل باید بیشتر از صفر باشد',
SHOP_SCORE_NOT_FOUND = 'امتیاز فروشگاه یافت نشد',
SHOP_PURCHASE_SCORE_NOT_FOUND = 'امتیاز خرید فروشگاه یافت نشد',
SHOP_PURCHASE_AMOUNT_NOT_FOUND = 'مبلغ خرید فروشگاه یافت نشد',
} }
export const enum CommonMessage { export const enum CommonMessage {
@@ -291,7 +305,7 @@ export const enum EmailMessage {
MESSAGE_MARKED_AS_UNSEEN_SUCCESSFULLY = 'پیام با موفقیت به عنوان خوانده نشده علامت گذاری شد', MESSAGE_MARKED_AS_UNSEEN_SUCCESSFULLY = 'پیام با موفقیت به عنوان خوانده نشده علامت گذاری شد',
MESSAGE_MARK_AS_UNSEEN_FAILED = 'علامت گذاری پیام به عنوان خوانده نشده با خطا مواجه شد', MESSAGE_MARK_AS_UNSEEN_FAILED = 'علامت گذاری پیام به عنوان خوانده نشده با خطا مواجه شد',
ATTACHMENT_ID_MUST_BE_STRING = 'شناسه پیوست باید یک رشته باشد', ATTACHMENT_ID_MUST_BE_STRING = 'شناسه پیوست باید یک رشته باشد',
IS_FORWARD_MUST_BE_BOOLEAN = 'IS_FORWARD_MUST_BE_BOOLEAN', IS_FORWARD_MUST_BE_BOOLEAN = 'وضعیت فوروارد باید بولین باشد',
// Favorite/Star messages (Gmail-like behavior) // Favorite/Star messages (Gmail-like behavior)
MESSAGE_MARKED_AS_FAVORITE_SUCCESSFULLY = 'پیام با موفقیت به عنوان مورد علاقه علامت گذاری شد', MESSAGE_MARKED_AS_FAVORITE_SUCCESSFULLY = 'پیام با موفقیت به عنوان مورد علاقه علامت گذاری شد',
@@ -300,7 +314,7 @@ export const enum EmailMessage {
SPAM_EMPTYED_SUCCESSFULLY = 'همه پیام های اسپم با موفقیت حذف شدند', SPAM_EMPTYED_SUCCESSFULLY = 'همه پیام های اسپم با موفقیت حذف شدند',
SAVE_DRAFT_SUCCESS = 'پیش نویس با موفقیت ذخیره شد', SAVE_DRAFT_SUCCESS = 'پیش نویس با موفقیت ذخیره شد',
HTML_CONTENT_REQUIRED = 'محتوای HTML مورد نیاز است', HTML_CONTENT_REQUIRED = 'محتوای HTML مورد نیاز است',
TEXT_CONTENT_REQUIRED = 'TEXT_CONTENT_REQUIRED', TEXT_CONTENT_REQUIRED = 'محتوای متنی الزامی است',
} }
export const enum WebSocketMessage { export const enum WebSocketMessage {
@@ -318,6 +332,8 @@ export const enum WebSocketMessage {
LEFT_ROOM_SUCCESS = 'با موفقیت از اتاق خارج شدید', LEFT_ROOM_SUCCESS = 'با موفقیت از اتاق خارج شدید',
ROOM_NOT_FOUND = 'اتاق یافت نشد', ROOM_NOT_FOUND = 'اتاق یافت نشد',
UNAUTHORIZED_ROOM_ACCESS = 'دسترسی غیرمجاز به اتاق', UNAUTHORIZED_ROOM_ACCESS = 'دسترسی غیرمجاز به اتاق',
ADMIN_LEFT_ROOM = 'ادمین با موفقیت از اتاق خارج شد',
ADMIN_JOINED_ROOM = 'ادمین با موفقیت به اتاق متصل شد',
// Session Management Messages // Session Management Messages
WS_SESSION_CREATED = 'جلسه وب سوکت ایجاد شد', WS_SESSION_CREATED = 'جلسه وب سوکت ایجاد شد',
@@ -347,6 +363,7 @@ export const enum WebSocketMessage {
// Authentication errors // Authentication errors
AUTHENTICATION_REQUIRED = 'احراز هویت ضروری است', AUTHENTICATION_REQUIRED = 'احراز هویت ضروری است',
INVALID_TOKEN_PAYLOAD = 'محتویات توکن نامعتبر است',
INVALID_TOKEN = 'توکن احراز هویت نامعتبر است', INVALID_TOKEN = 'توکن احراز هویت نامعتبر است',
TOKEN_EXPIRED = 'توکن منقضی شده است', TOKEN_EXPIRED = 'توکن منقضی شده است',
UNAUTHORIZED_ACCESS = 'دسترسی غیرمجاز', UNAUTHORIZED_ACCESS = 'دسترسی غیرمجاز',
@@ -398,8 +415,19 @@ export const enum SmsMessage {
SEND_SMS_ERROR = 'خطا در ارسال پیامک', SEND_SMS_ERROR = 'خطا در ارسال پیامک',
} }
export const enum AdminMessage {
ROLE_NOT_FOUND = 'نقش یافت نشد',
ROLE_NOT_BELONGS_TO_SHOP = 'نقش به این فروشگاه تعلق ندارد',
SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
ADMIN_NOT_FOUND = 'ادمین یافت نشد',
PHONE_ALREADY_TAKEN = 'این شماره قبلا توسط ادمین دیگری استفاده شده است',
ADMIN_ROLE_NOT_FOUND = 'نقش ادمین یافت نشد',
}
export const enum RestMessage { export const enum RestMessage {
NOT_FOUND = 'کسب و کار یافت نشد', NOT_FOUND = 'کسب و کار یافت نشد',
DUPLICATE_SUBSCRIPTION_ID = 'شناسه اشتراک تکراری است',
SYSTEM_ROLE_NOT_FOUND = 'نقش سیستمی یافت نشد',
BUSINESS_ID_REQUIRED = 'شناسه کسب و کار مورد نیاز است', BUSINESS_ID_REQUIRED = 'شناسه کسب و کار مورد نیاز است',
SLUG_REQUIRED = 'اسلاگ مورد نیاز است', SLUG_REQUIRED = 'اسلاگ مورد نیاز است',
SLUG_MUST_BE_STRING = 'اسلاگ باید یک رشته باشد', SLUG_MUST_BE_STRING = 'اسلاگ باید یک رشته باشد',
@@ -522,6 +550,9 @@ export const enum DnsRecordMessage {
export const enum RoleMessage { export const enum RoleMessage {
NOT_FOUND = 'نقش یافت نشد', NOT_FOUND = 'نقش یافت نشد',
ROLE_ALREADY_EXISTS_FOR_SHOP = 'نقشی با این نام قبلا برای این فروشگاه ثبت شده است',
ONE_OR_MORE_PERMISSIONS_NOT_FOUND = 'یک یا چند دسترسی یافت نشد',
ROLE_HAS_ADMINS = 'این نقش دارای ادمین است و نمی‌توان حذف کرد',
} }
export const enum SignatureMessage { export const enum SignatureMessage {
@@ -584,6 +615,7 @@ export const enum NotificationMessage {
CHANGE_PASSWORD_MESSAGE = 'رمز عبور شما با موفقیت تغییر کرد', CHANGE_PASSWORD_MESSAGE = 'رمز عبور شما با موفقیت تغییر کرد',
CHANGE_PASSWORD = 'تغییر رمز عبور', CHANGE_PASSWORD = 'تغییر رمز عبور',
NOT_FOUND = 'اعلان یافت نشد', NOT_FOUND = 'اعلان یافت نشد',
NOTIFICATION_PREFERENCE_NOT_FOUND = 'تنظیمات اعلان یافت نشد',
READ_SUCCESS = 'اعلان با موفقیت خوانده شد', READ_SUCCESS = 'اعلان با موفقیت خوانده شد',
ORDER_CREATED = 'سفارش جدید ایجاد شد', ORDER_CREATED = 'سفارش جدید ایجاد شد',
ORDER_STATUS_CHANGED = 'وضعیت سفارش تغییر کرد', ORDER_STATUS_CHANGED = 'وضعیت سفارش تغییر کرد',
@@ -659,6 +691,9 @@ export const enum ReviewMessage {
COMMENT_CREATED = 'نظر با موفقیت ثبت شد', COMMENT_CREATED = 'نظر با موفقیت ثبت شد',
COMMENT_UPDATED = 'نظر با موفقیت به‌روزرسانی شد', COMMENT_UPDATED = 'نظر با موفقیت به‌روزرسانی شد',
COMMENT_DELETED = 'نظر با موفقیت حذف شد', COMMENT_DELETED = 'نظر با موفقیت حذف شد',
PRODUCT_NOT_IN_ORDER = 'محصول در سفارش مشخص شده وجود ندارد',
CREATE_REVIEW_FAILED = 'ثبت نظر با خطا مواجه شد',
ONLY_ADMIN_CAN_CHANGE_STATUS = 'فقط ادمین می‌تواند وضعیت نظر را تغییر دهد',
} }
export const enum IconMessage { export const enum IconMessage {
@@ -677,6 +712,7 @@ export const enum GroupMessage {
export const enum OrderMessage { export const enum OrderMessage {
NOT_FOUND = 'سفارش یافت نشد', NOT_FOUND = 'سفارش یافت نشد',
ORDER_NOT_FOUND_OR_NOT_OWNED = 'سفارش یافت نشد یا به کاربر فعلی تعلق ندارد',
PAYMENT_METHOD_MISSING = 'روش پرداخت سفارش مشخص نشده است', PAYMENT_METHOD_MISSING = 'روش پرداخت سفارش مشخص نشده است',
INVALID_STATUS_TRANSITION = 'انتقال وضعیت نامعتبر است', INVALID_STATUS_TRANSITION = 'انتقال وضعیت نامعتبر است',
CART_EMPTY = 'سبد خرید خالی است. لطفا قبل از ایجاد سفارش، آیتم‌هایی به سبد اضافه کنید', CART_EMPTY = 'سبد خرید خالی است. لطفا قبل از ایجاد سفارش، آیتم‌هایی به سبد اضافه کنید',
@@ -696,6 +732,7 @@ export const enum OrderMessage {
} }
export const enum CartMessage { export const enum CartMessage {
AT_LEAST_ONE_ITEM_REQUIRED = 'حداقل یک آیتم الزامی است',
TABLE_NUMBER_REQUIRED = 'شماره میز برای سفارش در محل الزامی است', TABLE_NUMBER_REQUIRED = 'شماره میز برای سفارش در محل الزامی است',
CAR_ADDRESS_REQUIRED = 'آدرس خودرو برای تحویل به خودرو الزامی است', CAR_ADDRESS_REQUIRED = 'آدرس خودرو برای تحویل به خودرو الزامی است',
ADDRESS_REQUIRED = 'آدرس برای تحویل پیک الزامی است', ADDRESS_REQUIRED = 'آدرس برای تحویل پیک الزامی است',
@@ -744,6 +781,7 @@ export const enum PaymentMessage {
PAYMENT_METHOD_NOT_CASH = 'روش پرداخت نقدی نیست', PAYMENT_METHOD_NOT_CASH = 'روش پرداخت نقدی نیست',
PAYMENT_ALREADY_PAID = 'پرداخت قبلا انجام شده است', PAYMENT_ALREADY_PAID = 'پرداخت قبلا انجام شده است',
EXISTING_PENDING_PAYMENT_MISMATCH = 'پرداخت در انتظار موجود با روش پرداخت سفارش مطابقت ندارد', EXISTING_PENDING_PAYMENT_MISMATCH = 'پرداخت در انتظار موجود با روش پرداخت سفارش مطابقت ندارد',
INVALID_TRANSACTION_TYPE = 'نوع تراکنش نامعتبر است',
ZARINPAL_INVALID_RESPONSE = 'پاسخ نامعتبر از API زرین‌پال', ZARINPAL_INVALID_RESPONSE = 'پاسخ نامعتبر از API زرین‌پال',
ZARINPAL_PAYMENT_REQUEST_ERROR = 'خطا در درخواست پرداخت زرین‌پال', ZARINPAL_PAYMENT_REQUEST_ERROR = 'خطا در درخواست پرداخت زرین‌پال',
ZARINPAL_VERIFY_ERROR = 'خطا در تایید پرداخت زرین‌پال', ZARINPAL_VERIFY_ERROR = 'خطا در تایید پرداخت زرین‌پال',
+1 -1
View File
@@ -43,6 +43,6 @@ export class HttpExceptionFilter implements ExceptionFilter {
return Array.isArray(response.message) ? response.message : [response.message]; return Array.isArray(response.message) ? response.message : [response.message];
} }
return 'something wrong'; return 'مشکلی پیش آمده است';
} }
} }
+13 -12
View File
@@ -11,6 +11,7 @@ import { UpdateAdminDto } from '../dto/update-admin.dto';
import { AdminRole } from '../entities/adminRole.entity'; import { AdminRole } from '../entities/adminRole.entity';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../utils/phone.util';
import { AdminRepository } from '../repositories/admin.repository'; import { AdminRepository } from '../repositories/admin.repository';
import { AdminMessage, RoleMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
@@ -55,15 +56,15 @@ export class AdminService {
await this.em.persistAndFlush(admin); await this.em.persistAndFlush(admin);
} }
const role = await this.em.findOne(Role, { id: roleId, isSystem: false }); const role = await this.em.findOne(Role, { id: roleId, isSystem: false });
if (!role) throw new NotFoundException('Role not found'); if (!role) throw new NotFoundException(AdminMessage.ROLE_NOT_FOUND);
// Validate that role belongs to the shop or is general (shop: null) // Validate that role belongs to the shop or is general (shop: null)
if (role.shop && role.shop.id !== shopId) { if (role.shop && role.shop.id !== shopId) {
throw new BadRequestException('Role does not belong to this shop'); throw new BadRequestException(AdminMessage.ROLE_NOT_BELONGS_TO_SHOP);
} }
const shop = await this.em.findOne(Shop, { id: shopId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException(AdminMessage.SHOP_NOT_FOUND);
let adminRole = await this.adminRoleRepository.findOne({ let adminRole = await this.adminRoleRepository.findOne({
admin: admin, admin: admin,
@@ -105,7 +106,7 @@ export class AdminService {
const shop = await this.em.findOne(Shop, { id: shopId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException(AdminMessage.SHOP_NOT_FOUND);
let adminRole = await this.adminRoleRepository.findOne({ let adminRole = await this.adminRoleRepository.findOne({
admin: admin, admin: admin,
@@ -128,7 +129,7 @@ export class AdminService {
async getShopAdminsBySuperAdmin(shopId: string): Promise<Admin[]> { async getShopAdminsBySuperAdmin(shopId: string): Promise<Admin[]> {
const shop = await this.em.findOne(Shop, { id: shopId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException(AdminMessage.SHOP_NOT_FOUND);
return this.adminRepository.find({ roles: { shop: shop } }, { populate: ['roles', 'roles.role'] }); return this.adminRepository.find({ roles: { shop: shop } }, { populate: ['roles', 'roles.role'] });
} }
@@ -140,7 +141,7 @@ export class AdminService {
); );
if (!admin) { if (!admin) {
throw new NotFoundException('Admin not found'); throw new NotFoundException(AdminMessage.ADMIN_NOT_FOUND);
} }
const { roleId, ...rest } = dto; const { roleId, ...rest } = dto;
@@ -159,7 +160,7 @@ export class AdminService {
id: { $ne: adminId }, // Exclude current admin from duplicate check id: { $ne: adminId }, // Exclude current admin from duplicate check
}); });
if (exists) { if (exists) {
throw new ConflictException('This Phone Number is already taken'); throw new ConflictException(AdminMessage.PHONE_ALREADY_TAKEN);
} }
admin.phone = normalizedPhone; admin.phone = normalizedPhone;
} }
@@ -168,12 +169,12 @@ export class AdminService {
if (roleId) { if (roleId) {
const role = await this.em.findOne(Role, { id: roleId, isSystem: false }, { populate: ['shop'] }); const role = await this.em.findOne(Role, { id: roleId, isSystem: false }, { populate: ['shop'] });
if (!role) { if (!role) {
throw new NotFoundException('Role not found'); throw new NotFoundException(AdminMessage.ROLE_NOT_FOUND);
} }
// Validate that role belongs to the shop or is general (shop: null) // Validate that role belongs to the shop or is general (shop: null)
if (role.shop && role.shop.id !== shopId) { if (role.shop && role.shop.id !== shopId) {
throw new BadRequestException('Role does not belong to this shop'); throw new BadRequestException(AdminMessage.ROLE_NOT_BELONGS_TO_SHOP);
} }
// Find existing AdminRole for this admin and shop // Find existing AdminRole for this admin and shop
@@ -187,7 +188,7 @@ export class AdminService {
existingAdminRole.role = role; existingAdminRole.role = role;
} else { } else {
const shop = await this.em.findOne(Shop, { id: shopId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) throw new NotFoundException('Shop not found'); if (!shop) throw new NotFoundException(AdminMessage.SHOP_NOT_FOUND);
// Create new AdminRole using repository for consistency // Create new AdminRole using repository for consistency
const newAdminRole = this.adminRoleRepository.create({ const newAdminRole = this.adminRoleRepository.create({
admin, admin,
@@ -205,7 +206,7 @@ export class AdminService {
async remove(adminId: string, shopId: string): Promise<void> { async remove(adminId: string, shopId: string): Promise<void> {
const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, shop: { id: shopId } }); const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, shop: { id: shopId } });
if (!adminRole) { if (!adminRole) {
throw new NotFoundException('Admin role not found'); throw new NotFoundException(AdminMessage.ADMIN_ROLE_NOT_FOUND);
} }
return this.em.removeAndFlush(adminRole); return this.em.removeAndFlush(adminRole);
} }
@@ -226,7 +227,7 @@ export class AdminService {
); );
if (!admin) { if (!admin) {
throw new BadRequestException('Admin not found'); throw new BadRequestException(AdminMessage.ADMIN_NOT_FOUND);
} }
// Ensure all roles are populated // Ensure all roles are populated
+3 -2
View File
@@ -1,9 +1,10 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
import { AuthMessage } from 'src/common/enums/message.enum';
export class RefreshTokenDto { export class RefreshTokenDto {
@ApiProperty({ description: 'Refresh token' }) @ApiProperty({ description: 'Refresh token' })
@IsString({ message: 'Refresh token must be a string' }) @IsString({ message: AuthMessage.REFRESH_TOKEN_MUST_BE_STRING })
@IsNotEmpty({ message: 'Refresh token is required' }) @IsNotEmpty({ message: AuthMessage.REFRESH_TOKEN_REQUIRED })
refreshToken: string; refreshToken: string;
} }
+4 -4
View File
@@ -55,8 +55,8 @@ export class AuthService {
const normalizedPhone = normalizePhone(phone); const normalizedPhone = normalizePhone(phone);
const key = this.userOtpKey(slug, normalizedPhone); const key = this.userOtpKey(slug, normalizedPhone);
const cachedCode = await this.cacheService.get(key); const cachedCode = await this.cacheService.get(key);
if (!cachedCode) throw new BadRequestException('OTP expired or not found'); if (!cachedCode) throw new BadRequestException(AuthMessage.OTP_EXPIRED_OR_NOT_FOUND);
if (cachedCode !== code) throw new BadRequestException('Invalid OTP'); if (cachedCode !== code) throw new BadRequestException(AuthMessage.INVALID_OTP);
await this.cacheService.del(key); await this.cacheService.del(key);
@@ -80,9 +80,9 @@ export class AuthService {
const key = this.adminOtpKey(slug, normalizedPhone); const key = this.adminOtpKey(slug, normalizedPhone);
const cachedCode = await this.cacheService.get(key); const cachedCode = await this.cacheService.get(key);
if (!cachedCode) throw new BadRequestException('OTP expired or not found'); if (!cachedCode) throw new BadRequestException(AuthMessage.OTP_EXPIRED_OR_NOT_FOUND);
if (cachedCode !== code) throw new BadRequestException('Invalid OTP'); if (cachedCode !== code) throw new BadRequestException(AuthMessage.INVALID_OTP);
await this.cacheService.del(key); await this.cacheService.del(key);
const admin = await this.adminService.findByPhoneAndShopSlug(normalizedPhone, slug); const admin = await this.adminService.findByPhoneAndShopSlug(normalizedPhone, slug);
+1 -1
View File
@@ -139,7 +139,7 @@ export class TokensService {
} catch (error) { } catch (error) {
this.logger.error('Failed to generate new tokens after refresh', error); this.logger.error('Failed to generate new tokens after refresh', error);
// Transaction will rollback automatically, preserving the old token // Transaction will rollback automatically, preserving the old token
throw new UnauthorizedException('Failed to refresh token'); throw new UnauthorizedException(AuthMessage.FAILED_TO_REFRESH_TOKEN);
} }
}); });
} }
+2 -1
View File
@@ -1,6 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize, IsInt, Min, IsString } from 'class-validator'; import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize, IsInt, Min, IsString } from 'class-validator';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { CartMessage } from 'src/common/enums/message.enum';
class AddItemToCartDto { class AddItemToCartDto {
@ApiProperty({ description: 'Quantity of the product item', example: 1, minimum: 1 }) @ApiProperty({ description: 'Quantity of the product item', example: 1, minimum: 1 })
@@ -27,7 +28,7 @@ export class BulkAddItemsToCartDto {
}) })
@IsNotEmpty() @IsNotEmpty()
@IsArray() @IsArray()
@ArrayMinSize(1, { message: 'At least one item is required' }) @ArrayMinSize(1, { message: CartMessage.AT_LEAST_ONE_ITEM_REQUIRED })
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => AddItemToCartDto) @Type(() => AddItemToCartDto)
items!: AddItemToCartDto[]; items!: AddItemToCartDto[];
@@ -4,6 +4,7 @@ import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload'; import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload';
import { WebSocketMessage } from 'src/common/enums/message.enum';
export interface AuthenticatedSocket extends Socket { export interface AuthenticatedSocket extends Socket {
adminId?: string; adminId?: string;
@@ -33,7 +34,7 @@ export class WsAdminAuthGuard implements CanActivate {
auth: client.handshake.auth, auth: client.handshake.auth,
query: client.handshake.query, query: client.handshake.query,
}); });
throw new WsException('Authentication required'); throw new WsException(WebSocketMessage.AUTHENTICATION_REQUIRED);
} }
const secret = this.configService.getOrThrow<string>('JWT_SECRET'); const secret = this.configService.getOrThrow<string>('JWT_SECRET');
@@ -43,7 +44,7 @@ export class WsAdminAuthGuard implements CanActivate {
if (!payload.adminId || !payload.shopId) { if (!payload.adminId || !payload.shopId) {
this.logger.error('Invalid token payload structure', payload); this.logger.error('Invalid token payload structure', payload);
throw new WsException('Invalid token payload'); throw new WsException(WebSocketMessage.INVALID_TOKEN_PAYLOAD);
} }
// Attach admin info to socket // Attach admin info to socket
@@ -61,7 +62,7 @@ export class WsAdminAuthGuard implements CanActivate {
error: error instanceof Error ? error.message : 'Unknown error', error: error instanceof Error ? error.message : 'Unknown error',
socketId: client.id, socketId: client.id,
}); });
throw new WsException('Authentication failed'); throw new WsException(WebSocketMessage.WS_AUTHENTICATION_FAILED);
} }
} }
@@ -14,6 +14,7 @@ import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-au
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { ExecutionContext } from '@nestjs/common'; import { ExecutionContext } from '@nestjs/common';
import { IInAppNotificationPayload } from './interfaces/notification.interface'; import { IInAppNotificationPayload } from './interfaces/notification.interface';
import { WebSocketMessage } from 'src/common/enums/message.enum';
@WebSocketGateway({ @WebSocketGateway({
cors: { cors: {
@@ -53,7 +54,7 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
this.logger.error( this.logger.error(
`Connection authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`, `Connection authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
); );
void client.emit('error', { message: 'Authentication failed' }); void client.emit('error', { message: WebSocketMessage.WS_AUTHENTICATION_FAILED });
client.disconnect(); client.disconnect();
} }
} }
@@ -89,14 +90,14 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
const room = this.getRoom(client.adminId!, client.shopId!); const room = this.getRoom(client.adminId!, client.shopId!);
void client.leave(room); void client.leave(room);
this.logger.log(`Admin ${client.adminId} (Client ${client.id}) left room: ${room}`); this.logger.log(`Admin ${client.adminId} (Client ${client.id}) left room: ${room}`);
void client.emit('left', { room, message: 'Successfully Admin left room' }); void client.emit('left', { room, message: WebSocketMessage.ADMIN_LEFT_ROOM });
} }
handleJoinRoom(client: AuthenticatedSocket) { handleJoinRoom(client: AuthenticatedSocket) {
const room = this.getRoom(client.adminId!, client.shopId!); const room = this.getRoom(client.adminId!, client.shopId!);
void client.join(room); void client.join(room);
this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`); this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`);
void client.emit('joined', { room, message: 'Successfully Admin joined room' }); void client.emit('joined', { room, message: WebSocketMessage.ADMIN_JOINED_ROOM });
} }
private async authenticateConnection(client: Socket): Promise<void> { private async authenticateConnection(client: Socket): Promise<void> {
@@ -5,6 +5,7 @@ import { Shop } from '../../shops/entities/shop.entity';
import { CreatePreferenceDto } from '../dto/create-preference.dto'; import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { UpdatePreferenceDto } from '../dto/update-preference.dto'; import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { NotifTitleEnum } from '../interfaces/notification.interface'; import { NotifTitleEnum } from '../interfaces/notification.interface';
import { NotificationMessage, RestMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
export class NotificationPreferenceService { export class NotificationPreferenceService {
@@ -13,7 +14,7 @@ export class NotificationPreferenceService {
async create(shopId: string, dto: CreatePreferenceDto): Promise<NotificationPreference> { async create(shopId: string, dto: CreatePreferenceDto): Promise<NotificationPreference> {
const shop = await this.em.findOne(Shop, { id: shopId }); const shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException('Shop not found'); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
const preference = this.em.create(NotificationPreference, { const preference = this.em.create(NotificationPreference, {
@@ -46,7 +47,7 @@ export class NotificationPreferenceService {
// ): Promise<NotificationPreference> { // ): Promise<NotificationPreference> {
// const preference = await this.findByRestaurantAndType(shopId, notificationType); // const preference = await this.findByRestaurantAndType(shopId, notificationType);
// if (!preference) { // if (!preference) {
// throw new NotFoundException('Notification preference not found'); // throw new NotFoundException(NotificationMessage.NOTIFICATION_PREFERENCE_NOT_FOUND);
// } // }
// preference.enabled = enabled; // preference.enabled = enabled;
@@ -64,7 +65,7 @@ export class NotificationPreferenceService {
shop: { id: shopId }, shop: { id: shopId },
}); });
if (!preference) { if (!preference) {
throw new NotFoundException('Notification preference not found'); throw new NotFoundException(NotificationMessage.NOTIFICATION_PREFERENCE_NOT_FOUND);
} }
this.em.assign(preference, dto); this.em.assign(preference, dto);
@@ -78,7 +79,7 @@ export class NotificationPreferenceService {
id: preferenceId, id: preferenceId,
}); });
if (!preference) { if (!preference) {
throw new NotFoundException('Notification preference not found'); throw new NotFoundException(NotificationMessage.NOTIFICATION_PREFERENCE_NOT_FOUND);
} }
await this.em.removeAndFlush(preference); await this.em.removeAndFlush(preference);
} }
@@ -7,6 +7,7 @@ import { NotificationsGateway } from '../notifications.gateway';
import { NotifChannelEnum, NotifRequest, NotifTitleEnum } from '../interfaces/notification.interface'; import { NotifChannelEnum, NotifRequest, NotifTitleEnum } from '../interfaces/notification.interface';
import { SmsLogRepository } from '../repositories/sms-log.repository'; import { SmsLogRepository } from '../repositories/sms-log.repository';
import { PaginatedResult } from '../../../common/interfaces/pagination.interface'; import { PaginatedResult } from '../../../common/interfaces/pagination.interface';
import { NotificationMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
export class NotificationService { export class NotificationService {
@@ -96,7 +97,7 @@ export class NotificationService {
async findOne(id: string): Promise<Notification> { async findOne(id: string): Promise<Notification> {
const notification = await this.em.findOne(Notification, { id }, { populate: ['shop', 'user'] }); const notification = await this.em.findOne(Notification, { id }, { populate: ['shop', 'user'] });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException(NotificationMessage.NOT_FOUND);
} }
return notification; return notification;
} }
@@ -188,7 +189,7 @@ export class NotificationService {
shop: { id: shopId }, shop: { id: shopId },
}); });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException(NotificationMessage.NOT_FOUND);
} }
notification.seenAt = new Date(); notification.seenAt = new Date();
await this.em.persistAndFlush(notification); await this.em.persistAndFlush(notification);
@@ -200,7 +201,7 @@ export class NotificationService {
shop: { id: shopId }, shop: { id: shopId },
}); });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException(NotificationMessage.NOT_FOUND);
} }
notification.seenAt = new Date(); notification.seenAt = new Date();
await this.em.persistAndFlush(notification); await this.em.persistAndFlush(notification);
@@ -113,11 +113,11 @@ export class PaymentsService {
}); });
if (!walletTransaction) { if (!walletTransaction) {
throw new NotFoundException('User wallet not found'); throw new NotFoundException(PaymentMessage.WALLET_NOT_FOUND);
} }
if (walletTransaction.balance < ctx.amount) { if (walletTransaction.balance < ctx.amount) {
throw new BadRequestException('Insufficient wallet balance'); throw new BadRequestException(PaymentMessage.INSUFFICIENT_WALLET_BALANCE);
} }
const newBalance = walletTransaction.balance - ctx.amount; const newBalance = walletTransaction.balance - ctx.amount;
@@ -6,7 +6,7 @@ import { CategoryRepository } from '../repositories/category.repository';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core'; import { RequiredEntityData } from '@mikro-orm/core';
import { Product } from '../entities/product.entity'; import { Product } from '../entities/product.entity';
import { CategoryMessage, ProductMessage } from 'src/common/enums/message.enum'; import { CategoryMessage, ProductMessage, UserMessage } from 'src/common/enums/message.enum';
import { Favorite } from '../entities/favorite.entity'; import { Favorite } from '../entities/favorite.entity';
import { Variant } from '../entities/variant.entity'; import { Variant } from '../entities/variant.entity';
import { ShopService } from 'src/modules/shops/providers/shops.service'; import { ShopService } from 'src/modules/shops/providers/shops.service';
@@ -24,7 +24,6 @@ export class ProductService {
private readonly favoriteRepository: FavoriteRepository, private readonly favoriteRepository: FavoriteRepository,
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
// TODO : Messages must be in persian
async create(shopId: string, dto: CreateProductDto) { async create(shopId: string, dto: CreateProductDto) {
const { categoryId, variants, ...rest } = dto; const { categoryId, variants, ...rest } = dto;
const shop = await this.shopService.findOrFail(shopId); const shop = await this.shopService.findOrFail(shopId);
@@ -32,7 +31,7 @@ export class ProductService {
const category = await this.categoryService.findOrFail(categoryId) const category = await this.categoryService.findOrFail(categoryId)
if (variants.length > 1 && !dto.attribute) { if (variants.length > 1 && !dto.attribute) {
throw new BadRequestException('Attribute is required for multiple variants'); throw new BadRequestException(ProductMessage.ATTRIBUTE_REQUIRED_FOR_VARIANTS);
} }
const product = await this.em.transactional(async em => { const product = await this.em.transactional(async em => {
@@ -144,7 +143,7 @@ export class ProductService {
// Update existing variant // Update existing variant
const existingVariant = existingVariantsMap.get(id); const existingVariant = existingVariantsMap.get(id);
if (!existingVariant) { if (!existingVariant) {
throw new NotFoundException('Variant not found'); throw new NotFoundException(ProductMessage.VARIANT_NOT_FOUND);
} }
// Update the variant fields // Update the variant fields
@@ -183,7 +182,7 @@ export class ProductService {
const product = await this.findOrFail(id) const product = await this.findOrFail(id)
if (product.shop.id !== shopId) { if (product.shop.id !== shopId) {
throw new BadRequestException("Product doesnt belongs to your shop") throw new BadRequestException(ProductMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
} }
product.deletedAt = new Date(); product.deletedAt = new Date();
@@ -198,7 +197,7 @@ export class ProductService {
const product = await this.em.findOne(Product, productId); const product = await this.em.findOne(Product, productId);
if (!user) { if (!user) {
throw new NotFoundException('User not found'); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
if (!product) { if (!product) {
throw new NotFoundException(ProductMessage.NOT_FOUND); throw new NotFoundException(ProductMessage.NOT_FOUND);
@@ -211,12 +210,12 @@ export class ProductService {
if (favorite) { if (favorite) {
await this.em.removeAndFlush(favorite); await this.em.removeAndFlush(favorite);
return { message: 'Favorite removed successfully' }; return { message: ProductMessage.FAVORITE_REMOVED };
} }
const newFavorite = this.em.create(Favorite, { user, product }); const newFavorite = this.em.create(Favorite, { user, product });
await this.em.persistAndFlush(newFavorite); await this.em.persistAndFlush(newFavorite);
return { message: 'Favorite added successfully' }; return { message: ProductMessage.FAVORITE_ADDED };
} }
@@ -245,7 +244,7 @@ export class ProductService {
populate: ['product', 'product.shop', 'product.variants'], populate: ['product', 'product.shop', 'product.variants'],
}); });
if (!variant) throw new NotFoundException('Variant not found'); if (!variant) throw new NotFoundException(ProductMessage.VARIANT_NOT_FOUND);
return variant; return variant;
} }
@@ -8,7 +8,7 @@ import { UserRepository } from '../../users/repositories/user.repository';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core'; import { RequiredEntityData } from '@mikro-orm/core';
import { Review } from '../entities/review.entity'; import { Review } from '../entities/review.entity';
import { ProductMessage, UserMessage, ReviewMessage } from 'src/common/enums/message.enum'; import { OrderMessage, ProductMessage, RestMessage, UserMessage, ReviewMessage } from 'src/common/enums/message.enum';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../orders/entities/order.entity';
import { OrderItem } from '../../orders/entities/order-item.entity'; import { OrderItem } from '../../orders/entities/order-item.entity';
import { Shop } from '../../shops/entities/shop.entity'; import { Shop } from '../../shops/entities/shop.entity';
@@ -43,7 +43,7 @@ export class ReviewService {
// Find order and verify it belongs to the user, populate shop to get shopId // Find order and verify it belongs to the user, populate shop to get shopId
const order = await this.em.findOne(Order, { id: orderId, user: { id: userId } }, { populate: ['shop'] }); const order = await this.em.findOne(Order, { id: orderId, user: { id: userId } }, { populate: ['shop'] });
if (!order) { if (!order) {
throw new NotFoundException('Order not found or does not belong to the current user'); throw new NotFoundException(OrderMessage.ORDER_NOT_FOUND_OR_NOT_OWNED);
} }
// Verify the product is in the order (check through variant.product) // Verify the product is in the order (check through variant.product)
@@ -54,7 +54,7 @@ export class ReviewService {
populate: ['variant', 'variant.product'], populate: ['variant', 'variant.product'],
}); });
if (!orderItem) { if (!orderItem) {
throw new BadRequestException('Product is not in the specified order'); throw new BadRequestException(ReviewMessage.PRODUCT_NOT_IN_ORDER);
} }
// Check if user already commented on this product for this order // Check if user already commented on this product for this order
@@ -81,7 +81,7 @@ export class ReviewService {
const review = this.reviewRepository.create(data); const review = this.reviewRepository.create(data);
if (!review) { if (!review) {
throw new BadRequestException('Failed to create review entity'); throw new BadRequestException(ReviewMessage.CREATE_REVIEW_FAILED);
} }
await this.em.persistAndFlush(review); await this.em.persistAndFlush(review);
@@ -115,7 +115,7 @@ export class ReviewService {
const { shopSlug: restuarantSlug, ...restDto } = dto; const { shopSlug: restuarantSlug, ...restDto } = dto;
const shop = await this.em.findOne(Shop, { slug: restuarantSlug }); const shop = await this.em.findOne(Shop, { slug: restuarantSlug });
if (!shop) { if (!shop) {
throw new NotFoundException('RestaurantMessage.NOT_FOUND'); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
return this.reviewRepository.findAllPaginated({ ...restDto, shopId: shop.id }); return this.reviewRepository.findAllPaginated({ ...restDto, shopId: shop.id });
} }
@@ -144,7 +144,7 @@ export class ReviewService {
// Users can only update rating and comment, not status // Users can only update rating and comment, not status
if (!isAdmin && dto.status !== undefined) { if (!isAdmin && dto.status !== undefined) {
throw new BadRequestException('Only admins can change review status'); throw new BadRequestException(ReviewMessage.ONLY_ADMIN_CAN_CHANGE_STATUS);
} }
const oldRating = review.rating; const oldRating = review.rating;
+9 -8
View File
@@ -8,6 +8,7 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { CreateRoleDto } from '../dto/create-role.dto'; import { CreateRoleDto } from '../dto/create-role.dto';
import { UpdateRoleDto } from '../dto/update-role.dto'; import { UpdateRoleDto } from '../dto/update-role.dto';
import { RolePermission } from '../entities/rolePermission.entity'; import { RolePermission } from '../entities/rolePermission.entity';
import { RestMessage, RoleMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
export class RolesService { export class RolesService {
@@ -25,14 +26,14 @@ export class RolesService {
// Check if role already exists // Check if role already exists
const existing = await this.roleRepository.findOne({ name, shop: shopId ? { id: shopId } : null }); const existing = await this.roleRepository.findOne({ name, shop: shopId ? { id: shopId } : null });
if (existing) { if (existing) {
throw new BadRequestException('Role with this name already exists for the shop'); throw new BadRequestException(RoleMessage.ROLE_ALREADY_EXISTS_FOR_SHOP);
} }
let shop: Shop | null = null; let shop: Shop | null = null;
if (shopId) { if (shopId) {
shop = await this.em.findOne(Shop, { id: shopId }); shop = await this.em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException('Shop not found'); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
} }
@@ -46,7 +47,7 @@ export class RolesService {
if (permissionIds && permissionIds.length > 0) { if (permissionIds && permissionIds.length > 0) {
const permissions = await this.permissionRepository.find({ id: { $in: permissionIds } }); const permissions = await this.permissionRepository.find({ id: { $in: permissionIds } });
if (permissions.length !== permissionIds.length) { if (permissions.length !== permissionIds.length) {
throw new BadRequestException('One or more permissions not found'); throw new BadRequestException(RoleMessage.ONE_OR_MORE_PERMISSIONS_NOT_FOUND);
} }
permissions.forEach(p => role.permissions.add(p)); permissions.forEach(p => role.permissions.add(p));
} }
@@ -72,7 +73,7 @@ export class RolesService {
{ populate: ['permissions', 'shop'] }, { populate: ['permissions', 'shop'] },
); );
if (!role) { if (!role) {
throw new NotFoundException('Role not found'); throw new NotFoundException(RoleMessage.NOT_FOUND);
} }
return role; return role;
} }
@@ -83,7 +84,7 @@ export class RolesService {
{ populate: ['permissions', 'shop'] }, { populate: ['permissions', 'shop'] },
); );
if (!role) { if (!role) {
throw new NotFoundException('Role not found'); throw new NotFoundException(RoleMessage.NOT_FOUND);
} }
if (dto.name) { if (dto.name) {
@@ -95,7 +96,7 @@ export class RolesService {
role.permissions.removeAll(); role.permissions.removeAll();
const permissions = await this.permissionRepository.find({ id: { $in: dto.permissionIds } }); const permissions = await this.permissionRepository.find({ id: { $in: dto.permissionIds } });
if (permissions.length !== dto.permissionIds.length) { if (permissions.length !== dto.permissionIds.length) {
throw new BadRequestException('One or more permissions not found'); throw new BadRequestException(RoleMessage.ONE_OR_MORE_PERMISSIONS_NOT_FOUND);
} }
permissions.forEach(p => role.permissions.add(p)); permissions.forEach(p => role.permissions.add(p));
} }
@@ -122,10 +123,10 @@ export class RolesService {
{ populate: ['permissions', 'shop'] }, { populate: ['permissions', 'shop'] },
); );
if (!role) { if (!role) {
throw new NotFoundException('Role not found'); throw new NotFoundException(RoleMessage.NOT_FOUND);
} }
if (!role.admins.isEmpty()) { if (!role.admins.isEmpty()) {
throw new BadRequestException('Role has admins'); throw new BadRequestException(RoleMessage.ROLE_HAS_ADMINS);
} }
// Hard delete pivot table entries (role_permissions) before soft deleting the role // Hard delete pivot table entries (role_permissions) before soft deleting the role
+5 -5
View File
@@ -46,7 +46,7 @@ export class ShopService {
const validateSubscriptionId = await em.findOne(Shop, { subscriptionId: dto.subscriptionId }) const validateSubscriptionId = await em.findOne(Shop, { subscriptionId: dto.subscriptionId })
if (validateSubscriptionId) { if (validateSubscriptionId) {
throw new BadRequestException("Duplicate subscriptionId: " + dto.subscriptionId) throw new BadRequestException(RestMessage.DUPLICATE_SUBSCRIPTION_ID);
} }
// Create shop // Create shop
@@ -64,7 +64,7 @@ export class ShopService {
const role = await em.findOne(Role, { isSystem: true }); const role = await em.findOne(Role, { isSystem: true });
if (!role) { if (!role) {
throw new BadRequestException(`System role not found`); throw new BadRequestException(RestMessage.SYSTEM_ROLE_NOT_FOUND);
} }
const normalizedPhone = normalizePhone(dto.phone); const normalizedPhone = normalizePhone(dto.phone);
@@ -116,7 +116,7 @@ export class ShopService {
const shop = await this.em.findOne(Shop, { slug }); const shop = await this.em.findOne(Shop, { slug });
if (!shop) { if (!shop) {
throw new NotFoundException(`Shop with slug "${slug}" not found`); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
return shop; return shop;
@@ -235,7 +235,7 @@ export class ShopService {
async findOrFail(shopId: string) { async findOrFail(shopId: string) {
const shop = await this.shopRepository.findOne({ id: shopId }, { populate: ['deliveries'] }) const shop = await this.shopRepository.findOne({ id: shopId }, { populate: ['deliveries'] })
if (!shop) { if (!shop) {
throw new BadRequestException("Shop not found") throw new BadRequestException(RestMessage.NOT_FOUND);
} }
return shop return shop
} }
@@ -243,7 +243,7 @@ export class ShopService {
async findOrFailBySlug(shopSlug: string) { async findOrFailBySlug(shopSlug: string) {
const shop = await this.shopRepository.findOne({ slug: shopSlug }, { populate: ['deliveries'] }) const shop = await this.shopRepository.findOne({ slug: shopSlug }, { populate: ['deliveries'] })
if (!shop) { if (!shop) {
throw new BadRequestException("Shop not found") throw new BadRequestException(RestMessage.NOT_FOUND);
} }
return shop return shop
} }
+18 -17
View File
@@ -18,6 +18,7 @@ import { WalletTransactionRepository } from '../repositories/wallet-transaction.
import { WalletService } from './wallet.service'; import { WalletService } from './wallet.service';
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet'; import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
import { WalletTransaction } from '../entities/wallet-transaction.entity'; import { WalletTransaction } from '../entities/wallet-transaction.entity';
import { RestMessage, UserMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
export class UserService { export class UserService {
@@ -56,7 +57,7 @@ export class UserService {
// async updateUser(userId: string, dto: UpdateUserDto): Promise<User> { // async updateUser(userId: string, dto: UpdateUserDto): Promise<User> {
// const user = await this.em.findOneOrFail(User, userId, {}).catch(() => { // const user = await this.em.findOneOrFail(User, userId, {}).catch(() => {
// throw new NotFoundException(`User with ID ${userId} not found.`); // throw new NotFoundException(UserMessage.USER_NOT_FOUND);
// }); // });
// this.em.assign(user, { ...dto }); // this.em.assign(user, { ...dto });
@@ -86,7 +87,7 @@ export class UserService {
// get registerscore from shop // get registerscore from shop
const shop = await this.restaurantRepository.findOne({ id: shopId }); const shop = await this.restaurantRepository.findOne({ id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException(`Shop not found.`); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
const points = Number(shop.score?.registerScore || 0); const points = Number(shop.score?.registerScore || 0);
const createData = _raw as unknown as RequiredEntityData<User>; const createData = _raw as unknown as RequiredEntityData<User>;
@@ -107,7 +108,7 @@ export class UserService {
async updateUser(userId: string, shopId: string, dto: UpdateUserDto): Promise<User> { async updateUser(userId: string, shopId: string, dto: UpdateUserDto): Promise<User> {
const user = await this.userRepository.findOne({ id: userId }); const user = await this.userRepository.findOne({ id: userId });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
// Normalize date strings into Date objects if provided // Normalize date strings into Date objects if provided
@@ -180,7 +181,7 @@ export class UserService {
async getUserAddresses(userId: string): Promise<UserAddress[]> { async getUserAddresses(userId: string): Promise<UserAddress[]> {
const user = await this.userRepository.findOne({ id: userId }, { populate: ['addresses'] }); const user = await this.userRepository.findOne({ id: userId }, { populate: ['addresses'] });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
// Load addresses if not already loaded // Load addresses if not already loaded
@@ -191,7 +192,7 @@ export class UserService {
async createUserAddress(userId: string, dto: CreateUserAddressDto): Promise<UserAddress> { async createUserAddress(userId: string, dto: CreateUserAddressDto): Promise<UserAddress> {
const user = await this.userRepository.findOne({ id: userId }); const user = await this.userRepository.findOne({ id: userId });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
// If setting as default, unset other default addresses // If setting as default, unset other default addresses
@@ -228,7 +229,7 @@ export class UserService {
}); });
if (!address) { if (!address) {
throw new NotFoundException(`Address with ID ${addressId} not found for user ${userId}.`); throw new NotFoundException(UserMessage.ADDRESS_NOT_FOUND);
} }
return address; return address;
@@ -241,7 +242,7 @@ export class UserService {
}); });
if (!address) { if (!address) {
throw new NotFoundException(`Address with ID ${addressId} not found for user ${userId}.`); throw new NotFoundException(UserMessage.ADDRESS_NOT_FOUND);
} }
// If setting as default, unset other default addresses // If setting as default, unset other default addresses
@@ -271,7 +272,7 @@ export class UserService {
}); });
if (!address) { if (!address) {
throw new NotFoundException(`Address with ID ${addressId} not found for user ${userId}.`); throw new NotFoundException(UserMessage.ADDRESS_NOT_FOUND);
} }
// Soft delete the address // Soft delete the address
@@ -286,7 +287,7 @@ export class UserService {
}); });
if (!address) { if (!address) {
throw new NotFoundException(`Address with ID ${addressId} not found for user ${userId}.`); throw new NotFoundException(UserMessage.ADDRESS_NOT_FOUND);
} }
// Unset all other default addresses // Unset all other default addresses
@@ -310,11 +311,11 @@ export class UserService {
return this.em.transactional(async em => { return this.em.transactional(async em => {
const user = await em.findOne(User, { id: userId }); const user = await em.findOne(User, { id: userId });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
const shop = await em.findOne(Shop, { id: shopId }); const shop = await em.findOne(Shop, { id: shopId });
if (!shop) { if (!shop) {
throw new NotFoundException(`Shop with ID ${shopId} not found.`); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
let walletTransaction = await em.findOne(WalletTransaction, { user: { id: userId }, shop: { id: shopId } }, let walletTransaction = await em.findOne(WalletTransaction, { user: { id: userId }, shop: { id: shopId } },
{ orderBy: { createdAt: 'DESC' } }); { orderBy: { createdAt: 'DESC' } });
@@ -336,17 +337,17 @@ export class UserService {
// Validate points to convert // Validate points to convert
if (pointsToConvert <= 0) { if (pointsToConvert <= 0) {
throw new BadRequestException('Points to convert must be greater than 0.'); throw new BadRequestException(UserMessage.POINTS_MUST_BE_GREATER_THAN_ZERO);
} }
if (!shop.score) { if (!shop.score) {
throw new BadRequestException('Shop score not found.'); throw new BadRequestException(UserMessage.SHOP_SCORE_NOT_FOUND);
} }
if (!shop.score.purchaseScore) { if (!shop.score.purchaseScore) {
throw new BadRequestException('Shop purchase score not found.'); throw new BadRequestException(UserMessage.SHOP_PURCHASE_SCORE_NOT_FOUND);
} }
if (!shop.score.purchaseAmount) { if (!shop.score.purchaseAmount) {
throw new BadRequestException('Shop purchase amount not found.'); throw new BadRequestException(UserMessage.SHOP_PURCHASE_AMOUNT_NOT_FOUND);
} }
const walletIncreaseAmount = const walletIncreaseAmount =
(Number(pointsToConvert) * Number(shop.score.purchaseAmount)) / Number(shop.score.purchaseScore); (Number(pointsToConvert) * Number(shop.score.purchaseAmount)) / Number(shop.score.purchaseScore);
@@ -388,7 +389,7 @@ export class UserService {
async findOneOrFail(userId: string): Promise<User> { async findOneOrFail(userId: string): Promise<User> {
const user = await this.userRepository.findOne({ id: userId }); const user = await this.userRepository.findOne({ id: userId });
if (!user) { if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`); throw new NotFoundException(UserMessage.USER_NOT_FOUND);
} }
return user; return user;
} }
@@ -396,7 +397,7 @@ export class UserService {
async getUserAddressOrFail(addressId: string): Promise<UserAddress> { async getUserAddressOrFail(addressId: string): Promise<UserAddress> {
const address = await this.em.findOne(UserAddress, { id: addressId }, { populate: ['user'] }); const address = await this.em.findOne(UserAddress, { id: addressId }, { populate: ['user'] });
if (!address) { if (!address) {
throw new NotFoundException("User address not found."); throw new NotFoundException(UserMessage.ADDRESS_NOT_FOUND);
} }
return address; return address;
} }
@@ -8,6 +8,7 @@ import { CreateWalletTransactionDto } from '../dto/create-wallet-transaction.dto
import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { WalletTransactionType } from '../interface/wallet'; import { WalletTransactionType } from '../interface/wallet';
import { PointTransactionRepository } from '../repositories/point-transaction.repository'; import { PointTransactionRepository } from '../repositories/point-transaction.repository';
import { PaymentMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
export class WalletService { export class WalletService {
@@ -44,7 +45,7 @@ export class WalletService {
}); });
if (!walletTransaction) { if (!walletTransaction) {
throw new NotFoundException(`User wallet not found for user ${userId} and shop ${shopId}.`); throw new NotFoundException(PaymentMessage.WALLET_NOT_FOUND);
} }
// Build the 'where' filter query // Build the 'where' filter query
@@ -127,7 +128,7 @@ export class WalletService {
// Validate amount // Validate amount
if (amount <= 0) { if (amount <= 0) {
throw new BadRequestException('Transaction amount must be greater than 0.'); throw new BadRequestException(PaymentMessage.AMOUNT_MUST_BE_GREATER_THAN_ZERO);
} }
// Find the user's wallet for this shop // Find the user's wallet for this shop
@@ -137,7 +138,7 @@ export class WalletService {
}); });
if (!walletTransaction) { if (!walletTransaction) {
throw new NotFoundException(`User wallet not found for user ${userId} and shop ${shopId}.`); throw new NotFoundException(PaymentMessage.WALLET_NOT_FOUND);
} }
// Calculate new balance // Calculate new balance
@@ -148,11 +149,11 @@ export class WalletService {
newBalance = currentBalance + amount; newBalance = currentBalance + amount;
} else if (type === WalletTransactionType.DEBIT) { } else if (type === WalletTransactionType.DEBIT) {
if (currentBalance < amount) { if (currentBalance < amount) {
throw new BadRequestException(`Insufficient wallet balance. Current: ${currentBalance}, Required: ${amount}.`); throw new BadRequestException(PaymentMessage.INSUFFICIENT_WALLET_BALANCE);
} }
newBalance = currentBalance - amount; newBalance = currentBalance - amount;
} else { } else {
throw new BadRequestException(`Invalid transaction type: ${type}`); throw new BadRequestException(PaymentMessage.INVALID_TRANSACTION_TYPE);
} }
// Update wallet balance // Update wallet balance