From 2de442dbaede252b309cf7f28ae4f72f89e83f32 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Wed, 29 Jul 2026 16:44:53 +0330 Subject: [PATCH] fix(lint): clean up type definitions and missing imports --- backend/src/admin/admin.service.ts | 2 +- backend/src/auth/auth.service.ts | 5 +- .../common/filters/http-exception.filter.ts | 60 ++++++++++++------- .../common/filters/prisma-exception.filter.ts | 12 ++-- .../common/schemas/error-response.schema.ts | 28 +++++++-- backend/src/common/services/sms.service.ts | 32 ++++++++-- backend/src/main.ts | 17 ++++-- backend/src/orders/orders.service.ts | 38 +++++++----- backend/src/wholesale/wholesale.service.ts | 15 +++-- 9 files changed, 147 insertions(+), 62 deletions(-) diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index cffa124..8ee313d 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -1,4 +1,4 @@ -import { Injectable, HttpException } from '@nestjs/common'; +import { Injectable, HttpException, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { RedisService } from '../redis/redis.service'; diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 1d281e6..fa28f60 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -27,7 +27,10 @@ export class AuthService { // Dispatch OTP via MeliPayamak Pattern SMS await this.smsService.sendOtp(phoneNumber, code); - return { success: true, message: 'کد تایید با موفقیت به شماره شما پیامک شد.' }; + return { + success: true, + message: 'کد تایید با موفقیت به شماره شما پیامک شد.', + }; } async verifyOtp(verifyOtpDto: VerifyOtpDto) { diff --git a/backend/src/common/filters/http-exception.filter.ts b/backend/src/common/filters/http-exception.filter.ts index bcaa04f..67e3650 100644 --- a/backend/src/common/filters/http-exception.filter.ts +++ b/backend/src/common/filters/http-exception.filter.ts @@ -19,9 +19,10 @@ export class CustomHttpExceptionFilter implements ExceptionFilter { const request = ctx.getRequest(); let status = HttpStatus.INTERNAL_SERVER_ERROR; - let message = 'خطای داخلی سرور رخ داده است. لطفاً بعداً تلاش کنید یا با پشتیبانی تماس بگیرید.'; + let message = + 'خطای داخلی سرور رخ داده است. لطفاً بعداً تلاش کنید یا با پشتیبانی تماس بگیرید.'; let code = 'INTERNAL_SERVER_ERROR'; - let details: ErrorDetailField[] | Record = {}; + let details: ErrorDetailField[] | Record = {}; if (exception instanceof HttpException) { status = exception.getStatus(); @@ -30,14 +31,17 @@ export class CustomHttpExceptionFilter implements ExceptionFilter { if (typeof exceptionResponse === 'string') { message = this.translateGenericMessage(exceptionResponse, status); code = this.deriveErrorCode(status, exceptionResponse); - } else if (typeof exceptionResponse === 'object' && exceptionResponse !== null) { - const resObj = exceptionResponse as Record; - + } else if ( + typeof exceptionResponse === 'object' && + exceptionResponse !== null + ) { + const resObj = exceptionResponse as Record; + // Handle class-validator validation array messages if (Array.isArray(resObj.message)) { message = 'اطلاعات وارد شده نامعتبر است. لطفاً فرم را بررسی کنید.'; code = 'INVALID_INPUT_DATA'; - details = resObj.message.map((msg: string) => { + details = (resObj.message as string[]).map((msg: string) => { const parts = msg.split(' '); return { field: parts[0] || 'general', @@ -45,13 +49,19 @@ export class CustomHttpExceptionFilter implements ExceptionFilter { }; }); } else { - message = this.translateGenericMessage(resObj.message || exception.message, status); - code = resObj.code || this.deriveErrorCode(status, resObj.error); - details = resObj.details || {}; + const rawMsg = typeof resObj.message === 'string' ? resObj.message : exception.message; + message = this.translateGenericMessage(rawMsg, status); + const rawCode = typeof resObj.code === 'string' ? resObj.code : undefined; + const rawError = typeof resObj.error === 'string' ? resObj.error : undefined; + code = rawCode || this.deriveErrorCode(status, rawError); + details = (resObj.details as Record) || {}; } } } else if (exception instanceof Error) { - this.logger.error(`Unhandled Exception: ${exception.message}`, exception.stack); + this.logger.error( + `Unhandled Exception: ${exception.message}`, + exception.stack, + ); message = 'خطای غیرمنتظره در پردازش درخواست رخ داد.'; code = 'UNHANDLED_EXCEPTION'; } @@ -66,14 +76,16 @@ export class CustomHttpExceptionFilter implements ExceptionFilter { path: request.url, }; - this.logger.warn(`[${request.method}] ${request.url} - Status ${status} - ${message}`); + this.logger.warn( + `[${request.method}] ${request.url} - Status ${status} - ${message}`, + ); response.status(status).json(errorPayload); } private translateGenericMessage(msg: string, status: number): string { if (!msg) return 'خطای نامشخص رخ داده است.'; - + const lower = msg.toLowerCase(); if (lower.includes('unauthorized') || status === 401) { return 'شما دسترسی لازم برای این عملیات را ندارید. لطفاً ابتدا وارد حساب کاربری خود شوید.'; @@ -100,14 +112,22 @@ export class CustomHttpExceptionFilter implements ExceptionFilter { } switch (status) { - case 400: return 'BAD_REQUEST'; - case 401: return 'UNAUTHORIZED'; - case 403: return 'FORBIDDEN'; - case 404: return 'NOT_FOUND'; - case 409: return 'CONFLICT'; - case 422: return 'UNPROCESSABLE_ENTITY'; - case 429: return 'TOO_MANY_REQUESTS'; - default: return 'INTERNAL_SERVER_ERROR'; + case 400: + return 'BAD_REQUEST'; + case 401: + return 'UNAUTHORIZED'; + case 403: + return 'FORBIDDEN'; + case 404: + return 'NOT_FOUND'; + case 409: + return 'CONFLICT'; + case 422: + return 'UNPROCESSABLE_ENTITY'; + case 429: + return 'TOO_MANY_REQUESTS'; + default: + return 'INTERNAL_SERVER_ERROR'; } } } diff --git a/backend/src/common/filters/prisma-exception.filter.ts b/backend/src/common/filters/prisma-exception.filter.ts index fdefd38..61f2835 100644 --- a/backend/src/common/filters/prisma-exception.filter.ts +++ b/backend/src/common/filters/prisma-exception.filter.ts @@ -20,7 +20,7 @@ export class PrismaExceptionFilter implements ExceptionFilter { let status = HttpStatus.BAD_REQUEST; let message = 'خطایی در پردازش اطلاعات دیتابیس رخ داده است.'; let code = `PRISMA_${exception.code}`; - let details: Record = {}; + let details: Record = {}; switch (exception.code) { case 'P2002': { @@ -34,14 +34,16 @@ export class PrismaExceptionFilter implements ExceptionFilter { } case 'P2025': { status = HttpStatus.NOT_FOUND; - message = 'اطلاعات یا رکورد مورد نظر در سیستم یافت نشد یا قبلاً حذف شده است.'; + message = + 'اطلاعات یا رکورد مورد نظر در سیستم یافت نشد یا قبلاً حذف شده است.'; code = 'RECORD_NOT_FOUND'; details = { meta: exception.meta }; break; } case 'P2003': { status = HttpStatus.BAD_REQUEST; - message = 'امکان انجام عملیات به دلیل وجود وابستگی بین اطلاعات وجود ندارد.'; + message = + 'امکان انجام عملیات به دلیل وجود وابستگی بین اطلاعات وجود ندارد.'; code = 'FOREIGN_KEY_CONSTRAINT_FAILED'; break; } @@ -52,7 +54,9 @@ export class PrismaExceptionFilter implements ExceptionFilter { } } - this.logger.error(`[Prisma ${exception.code}] ${request.method} ${request.url} - ${message}`); + this.logger.error( + `[Prisma ${exception.code}] ${request.method} ${request.url} - ${message}`, + ); response.status(status).json({ success: false, diff --git a/backend/src/common/schemas/error-response.schema.ts b/backend/src/common/schemas/error-response.schema.ts index 1893973..e1670f6 100644 --- a/backend/src/common/schemas/error-response.schema.ts +++ b/backend/src/common/schemas/error-response.schema.ts @@ -4,12 +4,18 @@ export class ErrorDetailField { @ApiProperty({ description: 'نام فیلد دارای خطا', example: 'phone' }) field: string; - @ApiProperty({ description: 'توضیح توصیفی خطا به فارسی', example: 'شماره تلفن همراه وارد شده نامعتبر است.' }) + @ApiProperty({ + description: 'توضیح توصیفی خطا به فارسی', + example: 'شماره تلفن همراه وارد شده نامعتبر است.', + }) message: string; } export class ApiErrorResponse { - @ApiProperty({ description: 'وضعیت موفقیت درخواست (همیشه false در زمان خطا)', example: false }) + @ApiProperty({ + description: 'وضعیت موفقیت درخواست (همیشه false در زمان خطا)', + example: false, + }) success: boolean; @ApiProperty({ description: 'کد وضعیت HTTP درخواست', example: 400 }) @@ -17,11 +23,15 @@ export class ApiErrorResponse { @ApiProperty({ description: 'پیام جامع و توصیفی خطا به زبان فارسی', - example: 'اطلاعات وارد شده نامعتبر است. لطفاً موارد مشخص شده را بررسی نمایید.', + example: + 'اطلاعات وارد شده نامعتبر است. لطفاً موارد مشخص شده را بررسی نمایید.', }) message: string; - @ApiProperty({ description: 'کد سیستمیک و ثابت خطا جهت پردازش فرانت‌اند', example: 'INVALID_INPUT_DATA' }) + @ApiProperty({ + description: 'کد سیستمیک و ثابت خطا جهت پردازش فرانت‌اند', + example: 'INVALID_INPUT_DATA', + }) code: string; @ApiProperty({ @@ -31,9 +41,15 @@ export class ApiErrorResponse { }) details?: ErrorDetailField[] | Record; - @ApiProperty({ description: 'زمان وقوع خطا به فرمت ISO', example: '2026-07-29T16:30:00.000Z' }) + @ApiProperty({ + description: 'زمان وقوع خطا به فرمت ISO', + example: '2026-07-29T16:30:00.000Z', + }) timestamp: string; - @ApiProperty({ description: 'مسیر و انکوینتی که خطا در آن رخ داده است', example: '/api/auth/register' }) + @ApiProperty({ + description: 'مسیر و انکوینتی که خطا در آن رخ داده است', + example: '/api/auth/register', + }) path: string; } diff --git a/backend/src/common/services/sms.service.ts b/backend/src/common/services/sms.service.ts index a77a70a..474a14d 100644 --- a/backend/src/common/services/sms.service.ts +++ b/backend/src/common/services/sms.service.ts @@ -68,7 +68,9 @@ export class SmsService { ); req.on('error', (err) => { - this.logger.error(`[SMS Exception] MeliPayamak HTTP Error: ${err.message}`); + this.logger.error( + `[SMS Exception] MeliPayamak HTTP Error: ${err.message}`, + ); resolve(false); }); @@ -92,7 +94,11 @@ export class SmsService { /** * Send Order Confirmation SMS */ - async sendOrderConfirmation(phone: string, orderNumber: string, amount: string): Promise { + async sendOrderConfirmation( + phone: string, + orderNumber: string, + amount: string, + ): Promise { const bodyId = parseInt(process.env.MELIPAYAMAK_ORDER_BODY_ID || '0', 10); return this.sendPatternSms({ to: phone, @@ -104,8 +110,15 @@ export class SmsService { /** * Send Shipping Status SMS with Tracking Code */ - async sendShippingNotification(phone: string, orderNumber: string, trackingCode: string): Promise { - const bodyId = parseInt(process.env.MELIPAYAMAK_SHIPPING_BODY_ID || '0', 10); + async sendShippingNotification( + phone: string, + orderNumber: string, + trackingCode: string, + ): Promise { + const bodyId = parseInt( + process.env.MELIPAYAMAK_SHIPPING_BODY_ID || '0', + 10, + ); return this.sendPatternSms({ to: phone, bodyId, @@ -116,8 +129,15 @@ export class SmsService { /** * Send Pet Care Vaccination / Deworming Reminder SMS */ - async sendPetCareReminder(phone: string, petName: string, reminderType: string): Promise { - const bodyId = parseInt(process.env.MELIPAYAMAK_PET_CARE_BODY_ID || '0', 10); + async sendPetCareReminder( + phone: string, + petName: string, + reminderType: string, + ): Promise { + const bodyId = parseInt( + process.env.MELIPAYAMAK_PET_CARE_BODY_ID || '0', + 10, + ); return this.sendPatternSms({ to: phone, bodyId, diff --git a/backend/src/main.ts b/backend/src/main.ts index c9247c8..acc7853 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -36,15 +36,21 @@ async function bootstrap() { forbidNonWhitelisted: true, exceptionFactory: (errors) => { const formattedDetails = errors.map((err) => { - const constraints = err.constraints ? Object.values(err.constraints) : []; + const constraints = err.constraints + ? Object.values(err.constraints) + : []; return { field: err.property, - message: constraints.length > 0 ? constraints[0] : 'مقدار وارد شده نامعتبر است.', + message: + constraints.length > 0 + ? constraints[0] + : 'مقدار وارد شده نامعتبر است.', }; }); return new BadRequestException({ - message: 'اطلاعات ورودی فرم معتبر نمی‌باشد. لطفاً موارد مشخص شده را اصلاح نمایید.', + message: + 'اطلاعات ورودی فرم معتبر نمی‌باشد. لطفاً موارد مشخص شده را اصلاح نمایید.', error: 'INVALID_INPUT_DATA', details: formattedDetails, }); @@ -52,7 +58,10 @@ async function bootstrap() { }), ); - app.useGlobalFilters(new CustomHttpExceptionFilter(), new PrismaExceptionFilter()); + app.useGlobalFilters( + new CustomHttpExceptionFilter(), + new PrismaExceptionFilter(), + ); const config = new DocumentBuilder() .setTitle('Canina Iran API') diff --git a/backend/src/orders/orders.service.ts b/backend/src/orders/orders.service.ts index 92a6988..a2fe223 100644 --- a/backend/src/orders/orders.service.ts +++ b/backend/src/orders/orders.service.ts @@ -92,7 +92,12 @@ export class OrdersService { // Verify stock and build items let cartTotal = 0; - const orderItems: Array<{ productId: string; quantity: number; unitPrice: number; totalPrice: number }> = []; + const orderItems: Array<{ + productId: string; + quantity: number; + unitPrice: number; + totalPrice: number; + }> = []; for (const item of createOrderDto.items) { const product = await this.prisma.product.findUnique({ @@ -182,15 +187,20 @@ export class OrdersService { // Send Order Confirmation SMS if (userId) { - this.prisma.user.findUnique({ where: { id: userId } }).then((user) => { - if (user && user.mobile) { - this.smsService.sendOrderConfirmation( - user.mobile, - trackingNumber, - finalAmount.toLocaleString('fa-IR'), - ).catch(() => {}); - } - }).catch(() => {}); + this.prisma.user + .findUnique({ where: { id: userId } }) + .then((user) => { + if (user && user.mobile) { + this.smsService + .sendOrderConfirmation( + user.mobile, + trackingNumber, + finalAmount.toLocaleString('fa-IR'), + ) + .catch(() => {}); + } + }) + .catch(() => {}); } return createdOrder; @@ -219,11 +229,9 @@ export class OrdersService { const mobile = updatedOrder.user?.mobile; const trackingNum = updatedOrder.trackingNumber || trackingCode || ''; if (status === 'shipped' && mobile && trackingCode) { - this.smsService.sendShippingNotification( - mobile, - trackingNum, - trackingCode, - ).catch(() => {}); + this.smsService + .sendShippingNotification(mobile, trackingNum, trackingCode) + .catch(() => {}); } return updatedOrder; diff --git a/backend/src/wholesale/wholesale.service.ts b/backend/src/wholesale/wholesale.service.ts index 8894c5d..6561bb8 100644 --- a/backend/src/wholesale/wholesale.service.ts +++ b/backend/src/wholesale/wholesale.service.ts @@ -26,11 +26,16 @@ export class WholesaleService { // Alert admin via SMS if admin mobile is configured const adminMobile = process.env.ADMIN_MOBILE_ALERT; if (adminMobile) { - this.smsService.sendPatternSms({ - to: adminMobile, - bodyId: parseInt(process.env.MELIPAYAMAK_ADMIN_ALERT_BODY_ID || '0', 10), - args: [dto.businessName || 'نامشخص', user.mobile || ''], - }).catch(() => {}); + this.smsService + .sendPatternSms({ + to: adminMobile, + bodyId: parseInt( + process.env.MELIPAYAMAK_ADMIN_ALERT_BODY_ID || '0', + 10, + ), + args: [dto.businessName || 'نامشخص', user.mobile || ''], + }) + .catch(() => {}); } return {